have fun ngas dont do anything stupid
// ==UserScript==
// @name Void Bacon+ v3.5
// @namespace VoidBacon
// @version 3.5
// @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';
const EDIT_STORE='voidbacon.edit';
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'];
// ── EDIT PERSISTENCE ──────────────────────────────────────────
let _bgSaved={type:'image',url:'',fit:'cover',dim:0};
let _vbRules={};
let _draggedEls={}; // stableKey -> {left,top}
let _deletedKeys=[]; // array of stableKeys
const VB_STYLE_ID='vb-el-overrides';
const VB_KEY_ATTR='data-vb-key';
let _vbKeyCtr=0;
// Generate a stable, reload-safe key for an element
function _vbKey(el){
if(el.getAttribute(VB_KEY_ATTR))return el.getAttribute(VB_KEY_ATTR);
let key;
if(el.id){
key='#'+el.id;
} else {
const tag=el.tagName.toLowerCase();
const cls=el.className&&typeof el.className==='string'?el.className.trim().split(/\s+/).filter(Boolean).map(c=>'.'+c).join(''):'';
const sel=tag+cls;
let idx=0;
try{const all=[...document.querySelectorAll(sel)];idx=Math.max(0,all.indexOf(el));}catch{}
key=sel+'::'+idx;
}
el.setAttribute(VB_KEY_ATTR,key);
return key;
}
// Find element on page from a saved key
function _elByKey(key){
if(!key)return null;
if(key.startsWith('#')){
const id=key.split('::')[0].slice(1);
return document.getElementById(id)||null;
}
const parts=key.split('::');
const sel=parts[0];
const idx=parseInt(parts[1]||'0',10);
try{return document.querySelectorAll(sel)[idx]||null;}catch{return null;}
}
// CSS injection — uses background-color only so background-image (potato logo) survives
function _darkenHex(hex,amt){
const v=String(hex||'').replace('#','');
if(!/^[0-9a-f]{6}$/i.test(v))return'#000000';
return'#'+[0,2,4].map(i=>Math.max(0,Math.round(parseInt(v.slice(i,i+2),16)*(1-amt))).toString(16).padStart(2,'0')).join('');
}
function _vbFlush(){
let s=document.getElementById(VB_STYLE_ID);
if(!s){s=document.createElement('style');s.id=VB_STYLE_ID;document.head.appendChild(s);}
const lines=[];
for(const[key,r]of Object.entries(_vbRules)){
const attr=VB_KEY_ATTR;
const sel=`[${attr}="${key.replace(/\\/g,'\\\\').replace(/"/g,'\\"')}"]`;
if(r.bgMode==='solid'){
const dark=_darkenHex(r.bgColor,0.35);
lines.push(`${sel}{background-color:${r.bgColor}!important;box-shadow:inset 0 -3px ${dark}!important;border-bottom-color:${dark}!important;}`);
lines.push(`${sel}.btn-mode-potato{box-shadow:inset 0 -3px ${dark}!important;border-bottom:4px solid ${dark}!important;}`);
} else if(r.bgMode==='gradient'){
lines.push(`${sel}{background:linear-gradient(${r.gradAngle}deg,${r.gradA},${r.gradB})!important;}`);
} else if(r.bgMode==='none'){
lines.push(`${sel}{background-color:transparent!important;background:none!important;}`);
}
if(r.textColor){
lines.push(`${sel}{color:${r.textColor}!important;text-shadow:none!important;}`);
lines.push(`${sel}>*{color:${r.textColor}!important;}`);
}
if(r.fontSize>0){
lines.push(`${sel} span,${sel} a{font-size:${r.fontSize}px!important;}`);
}
if(r.borderW>0){
lines.push(`${sel}{border:${r.borderW}px solid ${r.borderColor}!important;}`);
} else if(r.borderColor&&r.borderColor!=='#36a7ff'){
lines.push(`${sel}{border-color:${r.borderColor}!important;outline:2px solid ${r.borderColor}!important;}`);
}
lines.push(`${sel}{border-radius:${r.borderR}px!important;}`);
if(r.scale&&r.scale!==1){
const base=r.baseTransform?r.baseTransform+' ':'';
lines.push(`${sel}{transform:${base}scale(${r.scale})!important;transform-origin:center center!important;}`);
}
if(r.opacity!=null&&r.opacity!==1)lines.push(`${sel}{opacity:${r.opacity}!important;}`);
}
s.textContent=lines.join('\n');
}
// ── SAVE / LOAD ───────────────────────────────────────────────
function saveCfg(){
const out={};
for(const k of SAVEABLE_BOOLS)out[k]=cfg[k];
out.smoothAmount=cfg.smoothAmount;
out.fovSize=cfg.fovSize;
out._bg=_bgSaved;
localStorage.setItem(CFG_STORE,JSON.stringify(out));
const editOut={rules:_vbRules,dragged:_draggedEls,deleted:_deletedKeys};
localStorage.setItem(EDIT_STORE,JSON.stringify(editOut));
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;
if(saved._bg)_bgSaved=Object.assign(_bgSaved,saved._bg);
}catch(e){}
try{
const es=JSON.parse(localStorage.getItem(EDIT_STORE)||'{}');
if(es.rules&&typeof es.rules==='object')_vbRules=es.rules;
if(es.dragged&&typeof es.dragged==='object')_draggedEls=es.dragged;
if(Array.isArray(es.deleted))_deletedKeys=es.deleted;
}catch(e){}
}
// Restore edit state — called after DOM ready and on rescan
function restoreEditState(){
// Re-stamp keys and flush CSS rules immediately (works any time)
for(const key of Object.keys(_vbRules)){
const el=_elByKey(key);
if(el&&!el.getAttribute(VB_KEY_ATTR))el.setAttribute(VB_KEY_ATTR,key);
}
if(Object.keys(_vbRules).length)_vbFlush();
// Re-apply drag positions immediately
for(const[key,pos]of Object.entries(_draggedEls)){
const el=_elByKey(key);
if(!el)continue;
el.style.setProperty('position','fixed','important');
el.style.setProperty('left',pos.left+'px','important');
el.style.setProperty('top',pos.top+'px','important');
el.style.setProperty('margin','0','important');
}
// Re-delete deleted elements
for(const key of _deletedKeys){
const el=_elByKey(key);
if(el)el.remove();
}
// Background needs #background div which only exists after game/menu loads
// Poll for it so we don't silently fail
if(_bgSaved.url)_bgApplyWhenReady();
}
// Poll until #background exists then apply — retries every 300ms for up to 15s
function _bgApplyWhenReady(){
if(!_bgSaved.url)return;
const target=document.getElementById('background');
if(target){_bgApplyToPage(_bgSaved.type,_bgSaved.url,_bgSaved.fit,_bgSaved.dim);return;}
let tries=0;
const poll=setInterval(()=>{
tries++;
const t=document.getElementById('background');
if(t){clearInterval(poll);_bgApplyToPage(_bgSaved.type,_bgSaved.url,_bgSaved.fit,_bgSaved.dim);return;}
if(tries>50)clearInterval(poll); // give up after 15s
},300);
}
// ── GAME DATA / MAPPINGS ──────────────────────────────────────
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 ─────────────────────────────────────────────────────
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 _kbListening=null;
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'};
// ── AUTO-MAPPER ───────────────────────────────────────────────
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'};
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}`);return null;};
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');
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');
const barnObj=barnKey?_safeGet(game,barnKey):null;
const playerPoolKey=_poolArr(barnObj?.playerPool)?.key;
playerPoolKey?found('playerPoolKey',playerPoolKey):nope('playerPoolKey');
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;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){}}
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;}
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;}}
// ── SA2 AIM ENGINE (SimpleAim v2) ─────────────────────────────
// Mouse proxy: game always reads aimX/aimY (lerped), real mouse tracked separately
const MIN_MOVE=0.05;
const velMap=new Map();
let _saAimX=0,_saAimY=0; // what the game sees
let _saRealX=0,_saRealY=0; // actual hardware mouse
let _saLerpX=0,_saLerpY=0; // current interpolated position
let _saHasTarget=false;
let _saInputHooked=false;
// Track real mouse movement always
document.addEventListener('mousemove',e=>{
_saRealX=e.clientX;_saRealY=e.clientY;
if(!_saHasTarget){_saLerpX=_saRealX;_saLerpY=_saRealY;}
},true);
// Hook input.mousePos with a proxy so game reads our lerped value
function _saHookMouse(inp){
if(_saInputHooked||!inp)return;
_saLerpX=_saAimX=inp.mousePos?.x??0;
_saLerpY=_saAimY=inp.mousePos?.y??0;
_saRealX=_saLerpX;_saRealY=_saLerpY;
const proxy={
get x(){return _saAimX;},set x(v){_saRealX=v;},
get y(){return _saAimY;},set y(v){_saRealY=v;},
};
try{Object.defineProperty(inp,'mousePos',{configurable:true,get:()=>proxy});_saInputHooked=true;}
catch(e){console.warn('[VB] aim hook failed',e);}
}
function _saLerp(a,b,t){return a+(b-a)*t;}
// SA2 velocity tracker — EMA with decay-to-zero when stationary
function _getVelocity(id,pos,now){
if(!velMap.has(id)){velMap.set(id,{vx:0,vy:0,lx:pos.x,ly:pos.y,lt:now});return{vx:0,vy:0};}
const s=velMap.get(id);const dt=(now-s.lt)/1000;const moved=Math.hypot(pos.x-s.lx,pos.y-s.ly);
if(dt>0&&dt<0.25){if(moved>MIN_MOVE){let rx=(pos.x-s.lx)/dt,ry=(pos.y-s.ly)/dt;const spd=Math.hypot(rx,ry);if(spd>22){const sc=22/spd;rx*=sc;ry*=sc;}s.vx=0.80*rx+0.20*s.vx;s.vy=0.80*ry+0.20*s.vy;}else{s.vx*=0.85;s.vy*=0.85;if(Math.abs(s.vx)<0.01)s.vx=0;if(Math.abs(s.vy)<0.01)s.vy=0;}}
s.lx=pos.x;s.ly=pos.y;s.lt=now;return{vx:s.vx,vy:s.vy};
}
// Quadratic intercept solver
function _solveIntercept(dx,dy,vx,vy,spd){
const a=vx*vx+vy*vy-spd*spd,b=2*(dx*vx+dy*vy),c=dx*dx+dy*dy;
if(Math.abs(a)<1e-6){const t=(Math.abs(b)>1e-6)?-c/b:Math.hypot(dx,dy)/spd;return(t>0.005&&t<2)?t:Math.hypot(dx,dy)/spd;}
const disc=b*b-4*a*c;if(disc<0)return Math.hypot(dx,dy)/spd;
const sq=Math.sqrt(disc),t1=(-b-sq)/(2*a),t2=(-b+sq)/(2*a);
return[t1,t2].filter(t=>t>0.005&&t<2).sort((a,b)=>a-b)[0]??Math.hypot(dx,dy)/spd;
}
function calcLeadWorld(target,myWorldPos,bulletSpeed,now){
const pos=getPlayerPos(target);if(!pos)return{wx:myWorldPos.x,wy:myWorldPos.y,t:0};
const{vx,vy}=_getVelocity(target.__id,pos,now);
const dx=pos.x-myWorldPos.x,dy=pos.y-myWorldPos.y;
const t=_solveIntercept(dx,dy,vx,vy,bulletSpeed);
return{wx:pos.x+vx*t,wy:pos.y+vy*t,t};
}
// Main SA2 aim tick — call once per frame with the chosen target screen pos.
// Returns the current lerped aim position for drawing.
function _saApplyAim(targetSX,targetSY){
const speed=0.04+(cfg.smoothAmount/100)*0.31; // maps slider 0-100 → 0.04-0.35
_saLerpX=_saLerp(_saLerpX,targetSX,speed);
_saLerpY=_saLerp(_saLerpY,targetSY,speed);
_saAimX=_saLerpX;
_saAimY=_saLerpY;
return{x:_saLerpX,y:_saLerpY};
}
// When no target — glide back to real mouse
function _saReturnToMouse(){
_saHasTarget=false;
_saLerpX=_saLerp(_saLerpX,_saRealX,0.12);
_saLerpY=_saLerp(_saLerpY,_saRealY,0.12);
_saAimX=_saLerpX;_saAimY=_saLerpY;
}
// ── AIM / MAGNET / MISC ──────────────────────────────────────
const FIRE_GATE_RAD=2.5*(Math.PI/180);
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>';});};
// ── RESCAN ────────────────────────────────────────────────────
function doRescan(){
setStatus('SCANNING...');
localStorage.removeItem(_MAPPER_STORE);
_lastScanResult=null;
window.__VB_KEYMAP__=null;
// Reset SA2 mouse hook so it re-attaches to the new input object
_saInputHooked=false;
const R=runAutoMapper();
if(R&&!R.error&&applyMapperResult(R)){
const missing=R._missing?.length??0;
toast(missing===0?'Rescan OK — all keys found':`Rescan done — ${missing} key(s) missing`);
setStatus(missing===0?'READY':'PARTIAL');
} else {
toast('Rescan failed — game not loaded yet');
setStatus('RESCAN FAILED');
}
// Re-apply edit state after rescan in case DOM was rebuilt
setTimeout(restoreEditState, 300);
}
// ============================================================
// 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:8px;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 11px;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);}
.rescan-btn{display:flex;align-items:center;justify-content:center;gap:5px;padding:6px 11px;border-radius:10px;background:linear-gradient(180deg,rgba(88,168,255,.18),rgba(60,120,255,.08));border:1px solid rgba(88,168,255,.3);color:#58a8ff;font:700 10px Inter,sans-serif;letter-spacing:1px;cursor:pointer;white-space:nowrap;transition:.18s;}
.rescan-btn:hover{background:linear-gradient(180deg,rgba(88,168,255,.28),rgba(60,120,255,.14));transform:translateY(-1px);}
.rescan-btn.scanning{animation:rescanPulse .7s infinite;}
@keyframes rescanPulse{0%,100%{opacity:1;}50%{opacity:.5;}}
#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;}
.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);}
.edit-input{width:100%;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);border-radius:8px;padding:7px 11px;font:11px Inter,sans-serif;color:#ebecf0;outline:none;}
.edit-input:focus{border-color:rgba(88,101,242,.5);}
.edit-row{display:flex;align-items:center;gap:8px;margin-bottom:8px;}
.edit-row label{font-size:10px;color:#7b7f88;letter-spacing:1px;min-width:52px;flex-shrink:0;}
.edit-row input[type=color]{flex:1;height:30px;border:1px solid rgba(255,255,255,.1);border-radius:6px;cursor:pointer;background:transparent;}
.edit-row input[type=range]{flex:1;}
.edit-row select{flex:1;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);border-radius:6px;padding:5px 8px;color:#ebecf0;font:11px Inter,sans-serif;}
.edit-row .edit-val{font-size:10px;color:#ebecf0;min-width:32px;text-align:right;}
.edit-btn{padding:8px 0;border-radius:8px;font:700 10px Inter,sans-serif;cursor:pointer;letter-spacing:.5px;border:1px solid;}
@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+ v3.5</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="edit">EDIT</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.5</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">▢</button>
<button class="ctrl" data-act="save">⟲</button>
<button class="ctrl" data-act="close">×</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>
<button class="rescan-btn" id="_vmRescanBtn" onclick="window._vmRescan()">🔄 RESCAN</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 (EMA+decay)</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 style="font-size:10px;color:#7b7f88;margin-top:4px">Predict: EMA vel + quadratic intercept. No jitter on stationary.</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">Predict</b> EMA velocity + quadratic intercept. Decays to zero when target stops — no oscillation.<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">Rescan</b> re-scans scrambled game data and re-applies all saved edits.
</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>
<!-- EDIT -->
<div class="page" data-page="edit">
<div style="display:flex;flex-direction:column;gap:12px">
<div class="pcard">
<div class="psec" style="margin-bottom:10px">BACKGROUND</div>
<div style="font-size:10px;color:#7b7f88;margin-bottom:8px;line-height:1.6">Set an image or video. Hit APPLY then 💾 SAVE SETTINGS — it auto-restores on reload.</div>
<div class="pgrid" style="gap:8px;margin-bottom:10px">
<div class="ptgl blue" id="_vmBgTgl-image" onclick="window._vmBgSetType('image')"><span class="tlbl">IMAGE</span><div class="tpip"></div></div>
<div class="ptgl purple" id="_vmBgTgl-video" onclick="window._vmBgSetType('video')"><span class="tlbl">VIDEO</span><div class="tpip"></div></div>
</div>
<div class="edit-row">
<label>URL</label>
<input type="text" id="_vmBgUrl" class="edit-input" placeholder="https://..."/>
</div>
<div class="edit-row">
<label>UPLOAD</label>
<label id="_vmBgFileLbl" style="flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px;background:rgba(255,255,255,.04);border:1px dashed rgba(255,255,255,.12);border-radius:8px;cursor:pointer;font:700 9px Inter,sans-serif;color:#7b7f88;letter-spacing:.5px">
📁 CHOOSE FILE
<input id="_vmBgFile" type="file" accept="image/*,video/*" style="display:none"/>
</label>
</div>
<div id="_vmBgFileName" style="font-size:10px;color:#7b7f88;text-align:center;margin-bottom:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></div>
<div class="edit-row">
<label>FIT</label>
<select id="_vmBgFit" class="edit-input" style="padding:5px 8px">
<option value="cover">cover</option>
<option value="contain">contain</option>
<option value="fill">stretch</option>
</select>
</div>
<div class="edit-row">
<label>DIM</label>
<input type="range" id="_vmBgDim" min="0" max="0.85" step="0.01" value="0" style="flex:1"/>
<span class="edit-val" id="_vmBgDimVal">0%</span>
</div>
<div id="_vmBgPreview" style="width:100%;height:90px;border-radius:8px;border:1px solid rgba(255,255,255,.07);background:#0a0a0a;overflow:hidden;position:relative;display:flex;align-items:center;justify-content:center;margin-bottom:10px">
<span id="_vmBgPreviewLbl" style="font-size:10px;color:#444;letter-spacing:1px;position:absolute">NO MEDIA</span>
<img id="_vmBgPreviewImg" style="width:100%;height:100%;object-fit:cover;display:none" alt=""/>
<video id="_vmBgPreviewVid" muted loop style="width:100%;height:100%;object-fit:cover;display:none"></video>
<div id="_vmBgPreviewDim" style="position:absolute;inset:0;background:#000;opacity:0;pointer-events:none"></div>
</div>
<div class="pgrid" style="gap:8px">
<button onclick="window._vmBgApply()" class="edit-btn" style="background:rgba(91,196,112,.14);border-color:rgba(91,196,112,.3);color:#5bc470">APPLY</button>
<button onclick="window._vmBgClear()" class="edit-btn" style="background:rgba(255,80,80,.08);border-color:rgba(255,80,80,.2);color:#ff6464">CLEAR</button>
</div>
</div>
<div class="pcard">
<div class="psec" style="margin-bottom:10px">ELEMENT EDITOR</div>
<div style="font-size:10px;color:#7b7f88;margin-bottom:8px;line-height:1.6">Pick → style → drag. Hit 💾 SAVE SETTINGS to persist across reloads.</div>
<div style="display:flex;gap:6px;margin-bottom:8px">
<button id="_vmEditPickBtn" onclick="window._vmEditStartPick()" class="edit-btn" style="flex:1;background:rgba(88,168,255,.12);border-color:rgba(88,168,255,.25);color:#58a8ff">PICK ELEMENT</button>
<button id="_vmEditDragBtn" onclick="window._vmEditToggleDrag()" class="edit-btn" style="flex:1;background:rgba(255,200,50,.08);border-color:rgba(255,200,50,.18);color:#ffc43c">DRAG: OFF</button>
<button onclick="window._vmEditReset()" class="edit-btn" style="flex:1;background:rgba(255,80,80,.08);border-color:rgba(255,80,80,.18);color:#ff6464">RESET</button>
</div>
<div id="_vmEditSelected" style="font-size:10px;color:#7b7f88;padding:6px 10px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.05);border-radius:7px;margin-bottom:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">nothing selected</div>
<div style="font-size:10px;color:#7b7f88;letter-spacing:1.5px;margin-bottom:6px">BACKGROUND</div>
<div class="pgrid3" style="gap:5px;margin-bottom:8px">
<div class="ptgl" id="_vmElBgTgl-solid" onclick="window._vmElBgMode('solid')" style="height:30px;padding:0 8px"><span class="tlbl" style="font-size:9px">SOLID</span><div class="tpip"></div></div>
<div class="ptgl" id="_vmElBgTgl-gradient" onclick="window._vmElBgMode('gradient')" style="height:30px;padding:0 8px"><span class="tlbl" style="font-size:9px">GRAD</span><div class="tpip"></div></div>
<div class="ptgl" id="_vmElBgTgl-none" onclick="window._vmElBgMode('none')" style="height:30px;padding:0 8px"><span class="tlbl" style="font-size:9px">NONE</span><div class="tpip"></div></div>
</div>
<div id="_vmElSolid"><div class="edit-row"><label>COLOR</label><input type="color" id="_vmElBgColor" value="#111111"/></div></div>
<div id="_vmElGrad" style="display:none">
<div class="edit-row"><label>FROM</label><input type="color" id="_vmElGradA" value="#0f0c29"/></div>
<div class="edit-row"><label>TO</label><input type="color" id="_vmElGradB" value="#24243e"/></div>
<div class="edit-row"><label>ANGLE</label><input type="range" id="_vmElGradAngle" min="0" max="360" step="1" value="135" style="flex:1"/><span class="edit-val" id="_vmElGradAngleVal">135°</span></div>
</div>
<div style="font-size:10px;color:#7b7f88;letter-spacing:1.5px;margin:8px 0 6px">TEXT</div>
<div class="edit-row"><label>COLOR</label><input type="color" id="_vmElTxtColor" value="#ffffff"/></div>
<div class="edit-row"><label>SIZE</label><input type="range" id="_vmElFontSize" min="8" max="64" step="1" value="14" style="flex:1"/><span class="edit-val" id="_vmElFontSizeVal">14px</span></div>
<div style="font-size:10px;color:#7b7f88;letter-spacing:1.5px;margin:8px 0 6px">BORDER + RADIUS</div>
<div class="edit-row"><label>COLOR</label><input type="color" id="_vmElBorderColor" value="#36a7ff"/></div>
<div class="edit-row"><label>WIDTH</label><input type="range" id="_vmElBorderW" min="0" max="10" step="1" value="0" style="flex:1"/><span class="edit-val" id="_vmElBorderWVal">0px</span></div>
<div class="edit-row"><label>RADIUS</label><input type="range" id="_vmElRadius" min="0" max="40" step="1" value="0" style="flex:1"/><span class="edit-val" id="_vmElRadiusVal">0px</span></div>
<div style="font-size:10px;color:#7b7f88;letter-spacing:1.5px;margin:8px 0 6px">SIZE + OPACITY</div>
<div class="edit-row"><label>SCALE</label><input type="range" id="_vmElScale" min="0.1" max="3" step="0.05" value="1" style="flex:1"/><span class="edit-val" id="_vmElScaleVal">1.0×</span></div>
<div class="edit-row"><label>OPACITY</label><input type="range" id="_vmElOpacity" min="0" max="1" step="0.01" value="1" style="flex:1"/><span class="edit-val" id="_vmElOpacityVal">1.0</span></div>
<div style="display:flex;gap:6px;margin-top:8px">
<button onclick="window._vmEditApply()" class="edit-btn" style="flex:1;padding:10px;background:linear-gradient(180deg,rgba(91,196,112,.18),rgba(60,160,80,.08));border-color:rgba(91,196,112,.3);color:#5bc470;font-size:11px">APPLY</button>
<button onclick="window._vmEditDelete()" class="edit-btn" style="flex:1;padding:10px;background:rgba(255,80,80,.1);border-color:rgba(255,80,80,.25);color:#ff6464;font-size:11px">DELETE</button>
</div>
</div>
<button onclick="window._vmReturnToNormal()" style="width:100%;margin-top:4px;padding:14px;border-radius:12px;border:1px solid rgba(255,120,50,.35);background:linear-gradient(180deg,rgba(255,120,50,.14),rgba(200,80,20,.08));color:#ff8c42;font:700 11px Inter,sans-serif;letter-spacing:1.2px;cursor:pointer;transition:.18s;" onmouseover="this.style.background='linear-gradient(180deg,rgba(255,120,50,.26),rgba(200,80,20,.16))'" onmouseout="this.style.background='linear-gradient(180deg,rgba(255,120,50,.14),rgba(200,80,20,.08))'">↩ RETURN TO NORMAL — reset background, positions & colors</button>
</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>
<!-- 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.5</span>
<span style="font-size:9px;color:#5bc470;letter-spacing:1px">LATEST</span>
</div>
<div style="font-size:11px;color:#7b7f88;line-height:1.8">
<span style="color:#5bc470">+</span> Updated prediction + aimbot system — replaced with SimpleAim v2 engine: mouse proxy hook, frame-rate independent lerp smooth aim, glides back to real cursor when no target<br>
<span style="color:#5bc470">+</span> EDIT tab — background image/video, element color editor, drag mode<br>
<span style="color:#5bc470">+</span> 💾 Save Settings now persists background, element colors, drag positions and deletions across reloads<br>
<span style="color:#5bc470">+</span> 🔄 Rescan button — re-scans scrambled game keys and re-applies all saved edits instantly<br>
<span style="color:#5bc470">+</span> Element editor uses CSS injection (background-color only) — preserves background-image like potato logo<br>
<span style="color:#5bc470">+</span> Proper box-shadow darkening for button bottom highlight
</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.4</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>
</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>
</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>
</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();};
window._vmRescan=function(){
const btn=document.getElementById('_vmRescanBtn');
if(btn)btn.classList.add('scanning');
setTimeout(()=>{
doRescan();
if(btn)btn.classList.remove('scanning');
},100);
};
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);
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);};
// ============================================================
// BG EDITOR
// ============================================================
let _bgType='image';
let _bgEl=null;
window._vmBgSetType=function(t){_bgType=t;document.getElementById('_vmBgTgl-image').classList.toggle('on',t==='image');document.getElementById('_vmBgTgl-video').classList.toggle('on',t==='video');};
window._vmBgSetType('image');
function _bgUpdatePreview(url){
const img=document.getElementById('_vmBgPreviewImg');const vid=document.getElementById('_vmBgPreviewVid');const lbl=document.getElementById('_vmBgPreviewLbl');const fit=document.getElementById('_vmBgFit')?.value||'cover';
if(!url){img.style.display='none';vid.style.display='none';lbl.style.display='block';return;}
lbl.style.display='none';
if(_bgType==='video'){img.style.display='none';vid.style.display='block';vid.src=url;vid.style.objectFit=fit;vid.play().catch(()=>{});}
else{vid.style.display='none';img.style.display='block';img.src=url;img.style.objectFit=fit;}
}
document.getElementById('_vmBgUrl').addEventListener('input',function(){_bgUpdatePreview(this.value.trim());});
document.getElementById('_vmBgDim').addEventListener('input',function(){document.getElementById('_vmBgDimVal').textContent=Math.round(this.value*100)+'%';const d=document.getElementById('_vmBgPreviewDim');if(d)d.style.opacity=this.value;});
let _bgObjUrl=null;
document.getElementById('_vmBgFile').addEventListener('change',function(){
const f=this.files[0];if(!f)return;
if(_bgObjUrl){URL.revokeObjectURL(_bgObjUrl);_bgObjUrl=null;}
const isVid=f.type.startsWith('video/');
window._vmBgSetType(isVid?'video':'image');
document.getElementById('_vmBgFileName').textContent=f.name;
document.getElementById('_vmBgFileLbl').style.background='rgba(91,196,112,.08)';
document.getElementById('_vmBgFileLbl').style.borderColor='rgba(91,196,112,.3)';
// Read as base64 so it persists across reloads (blob: URLs die on reload)
const reader=new FileReader();
reader.onload=function(ev){
const dataUrl=ev.target.result;
document.getElementById('_vmBgUrl').value=dataUrl;
_bgUpdatePreview(dataUrl);
};
reader.readAsDataURL(f);
});
window._vmBgApply=function(){
const url=(document.getElementById('_vmBgUrl').value||'').trim();
if(!url){toast('Enter a URL or upload a file first');return;}
const fit=document.getElementById('_vmBgFit').value||'cover';
const dim=parseFloat(document.getElementById('_vmBgDim').value)||0;
_bgSaved={type:_bgType,url,fit,dim};
_bgApplyToPage(_bgType,url,fit,dim);
toast('Background applied — hit 💾 to save');
};
function _bgApplyToPage(type,url,fit,dim){
if(_bgEl&&document.body.contains(_bgEl))_bgEl.remove();
if(!url)return;
const target=document.getElementById('background');
if(!target)return;
_bgEl=document.createElement('div');
_bgEl.id='vb-bg';
_bgEl.style.cssText='position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden;';
if(type==='video'){
const v=document.createElement('video');v.src=url;v.autoplay=true;v.muted=true;v.loop=true;
v.style.cssText=`width:100%;height:100%;object-fit:${fit};display:block;`;
_bgEl.appendChild(v);
} else {
_bgEl.style.backgroundImage=`url("${url}")`;
_bgEl.style.backgroundSize=fit==='fill'?'100% 100%':fit;
_bgEl.style.backgroundPosition='center';
_bgEl.style.backgroundRepeat='no-repeat';
}
const dimDiv=document.createElement('div');
dimDiv.style.cssText=`position:absolute;inset:0;background:#000;opacity:${dim};pointer-events:none`;
_bgEl.appendChild(dimDiv);
target.style.setProperty('position','absolute','important');
target.style.setProperty('overflow','hidden','important');
target.insertBefore(_bgEl,target.firstChild);
// sync UI
const urlEl=document.getElementById('_vmBgUrl');if(urlEl&&!urlEl.value)urlEl.value=url;
_bgUpdatePreview(url);
}
window._vmBgClear=function(){
if(_bgEl&&document.body.contains(_bgEl))_bgEl.remove();
_bgEl=null;
_bgSaved={type:'image',url:'',fit:'cover',dim:0};
document.getElementById('_vmBgUrl').value='';
const fn=document.getElementById('_vmBgFileName');if(fn)fn.textContent='';
const lbl=document.getElementById('_vmBgFileLbl');if(lbl){lbl.style.background='rgba(255,255,255,.04)';lbl.style.borderColor='rgba(255,255,255,.12)';}
const fi=document.getElementById('_vmBgFile');if(fi)fi.value='';
const img=document.getElementById('_vmBgPreviewImg');if(img){img.style.display='none';img.src='';}
const vid=document.getElementById('_vmBgPreviewVid');if(vid){vid.style.display='none';vid.src='';}
document.getElementById('_vmBgPreviewLbl').style.display='block';
toast('Background cleared');
};
// ============================================================
// ELEMENT EDITOR
// ============================================================
let _elTarget=null,_elOrigStyle={},_elPicking=false,_elBgModeVal='solid',_elDragOn=false;
let _elDragState={active:false,startX:0,startY:0,origLeft:0,origTop:0};
const _vbBaseXform=new WeakMap();
window._vmElBgMode=function(mode){
_elBgModeVal=mode;
['solid','gradient','none'].forEach(m=>{document.getElementById('_vmElBgTgl-'+m)?.classList.toggle('on',m===mode);});
document.getElementById('_vmElSolid').style.display=mode==='solid'?'block':'none';
document.getElementById('_vmElGrad').style.display=mode==='gradient'?'block':'none';
};
window._vmElBgMode('solid');
function _elDragMove(e){
if(!_elDragState.active||!_elTarget)return;
const cx=e.touches?e.touches[0].clientX:e.clientX;
const cy=e.touches?e.touches[0].clientY:e.clientY;
const newLeft=_elDragState.origLeft+(cx-_elDragState.startX);
const newTop=_elDragState.origTop+(cy-_elDragState.startY);
_elTarget.style.setProperty('position','fixed','important');
_elTarget.style.setProperty('left',newLeft+'px','important');
_elTarget.style.setProperty('top',newTop+'px','important');
_elTarget.style.setProperty('margin','0','important');
// Update drag record live so it's ready to save
const key=_elTarget.getAttribute(VB_KEY_ATTR);
if(key)_draggedEls[key]={left:newLeft,top:newTop};
}
function _elDragEnd(){_elDragState.active=false;}
function _elDragDown(e){
if(!_elDragOn||!_elTarget)return;
if(e.target.closest('#voidUI'))return;
if(e.target!==_elTarget&&!_elTarget.contains(e.target))return;
e.preventDefault();
// Ensure element has a stable key before we start tracking its position
_vbKey(_elTarget);
const cx=e.touches?e.touches[0].clientX:e.clientX;
const cy=e.touches?e.touches[0].clientY:e.clientY;
const r=_elTarget.getBoundingClientRect();
_elDragState={active:true,startX:cx,startY:cy,origLeft:r.left,origTop:r.top};
}
window._vmEditToggleDrag=function(){
_elDragOn=!_elDragOn;
const btn=document.getElementById('_vmEditDragBtn');
if(btn){btn.textContent='DRAG: '+(_elDragOn?'ON':'OFF');btn.style.background=_elDragOn?'rgba(255,200,50,.22)':'rgba(255,200,50,.08)';btn.style.borderColor=_elDragOn?'rgba(255,200,50,.5)':'rgba(255,200,50,.18)';}
if(_elTarget)_elTarget.style.cursor=_elDragOn?'grab':'';
if(_elDragOn){
document.addEventListener('mousedown',_elDragDown,true);document.addEventListener('mousemove',_elDragMove,true);document.addEventListener('mouseup',_elDragEnd,true);
document.addEventListener('touchstart',_elDragDown,{passive:false,capture:true});document.addEventListener('touchmove',_elDragMove,{passive:false,capture:true});document.addEventListener('touchend',_elDragEnd,true);
toast('Drag ON — grab element');
} else {
document.removeEventListener('mousedown',_elDragDown,true);document.removeEventListener('mousemove',_elDragMove,true);document.removeEventListener('mouseup',_elDragEnd,true);
document.removeEventListener('touchstart',_elDragDown,true);document.removeEventListener('touchmove',_elDragMove,true);document.removeEventListener('touchend',_elDragEnd,true);
toast('Drag OFF');
}
};
function _elPickClick(e){
if(e.target.closest('#voidUI')){_elStopPick();return;}
e.preventDefault();e.stopPropagation();
_elTarget=e.target;
_elOrigStyle={};
for(const p of _elTarget.style)_elOrigStyle[p]=_elTarget.style.getPropertyValue(p);
if(!_vbBaseXform.has(_elTarget)){const cs=getComputedStyle(_elTarget).transform;_vbBaseXform.set(_elTarget,cs&&cs!=='none'?cs:'');}
const tag=_elTarget.tagName.toLowerCase();
const id=_elTarget.id?'#'+_elTarget.id:'';
const cls=typeof _elTarget.className==='string'&&_elTarget.className.trim()?'.'+_elTarget.className.trim().split(/\s+/)[0]:'';
const lbl=document.getElementById('_vmEditSelected');
if(lbl)lbl.textContent=(tag+(id||cls))+' — selected ✓';
if(_elDragOn)_elTarget.style.cursor='grab';
_elStopPick();
toast('Picked — style or drag it');
}
function _elStopPick(){
_elPicking=false;
document.removeEventListener('click',_elPickClick,true);
document.body.style.cursor='';
const btn=document.getElementById('_vmEditPickBtn');
if(btn){btn.textContent='PICK ELEMENT';btn.style.background='rgba(88,168,255,.12)';}
}
window._vmEditStartPick=function(){
if(_elPicking){_elStopPick();return;}
_elPicking=true;
document.addEventListener('click',_elPickClick,true);
document.body.style.cursor='crosshair';
const btn=document.getElementById('_vmEditPickBtn');
if(btn){btn.textContent='CLICK AN ELEMENT…';btn.style.background='rgba(255,200,50,.18)';}
toast('Click anything on the page');
};
window._vmEditApply=function(){
if(!_elTarget){toast('Pick an element first');return;}
const key=_vbKey(_elTarget);
_vbRules[key]={
bgMode:_elBgModeVal,
bgColor:document.getElementById('_vmElBgColor')?.value??'#111111',
gradA:document.getElementById('_vmElGradA')?.value??'#0f0c29',
gradB:document.getElementById('_vmElGradB')?.value??'#24243e',
gradAngle:Number(document.getElementById('_vmElGradAngle')?.value)||135,
textColor:document.getElementById('_vmElTxtColor')?.value??'#ffffff',
fontSize:Number(document.getElementById('_vmElFontSize')?.value)||0,
borderColor:document.getElementById('_vmElBorderColor')?.value??'#36a7ff',
borderW:Number(document.getElementById('_vmElBorderW')?.value)||0,
borderR:Number(document.getElementById('_vmElRadius')?.value)||0,
scale:Number(document.getElementById('_vmElScale')?.value)||1,
opacity:parseFloat(document.getElementById('_vmElOpacity')?.value??'1'),
baseTransform:_vbBaseXform.get(_elTarget)||''
};
_vbFlush();
toast('Applied ✓ — hit 💾 to save');
};
window._vmEditReset=function(){
if(!_elTarget){toast('Nothing selected');return;}
const key=_elTarget.getAttribute(VB_KEY_ATTR);
if(key){delete _vbRules[key];delete _draggedEls[key];_elTarget.removeAttribute(VB_KEY_ATTR);_vbFlush();}
while(_elTarget.style.length)_elTarget.style.removeProperty(_elTarget.style[0]);
for(const[p,v]of Object.entries(_elOrigStyle))_elTarget.style.setProperty(p,v);
document.getElementById('_vmEditSelected').textContent='nothing selected';
_elTarget=null;
toast('Reset to original');
};
window._vmEditDelete=function(){
if(!_elTarget){toast('Nothing selected');return;}
const tag=_elTarget.tagName.toLowerCase();
const id=_elTarget.id?'#'+_elTarget.id:'';
const key=_vbKey(_elTarget); // stamp key before removing
if(key){
delete _vbRules[key];
delete _draggedEls[key];
// Record deletion so it can be restored on reload
if(!_deletedKeys.includes(key))_deletedKeys.push(key);
_vbFlush();
}
_elTarget.remove();
_elTarget=null;
document.getElementById('_vmEditSelected').textContent='nothing selected';
toast('Deleted '+tag+id+' — hit 💾 to save');
};
window._vmReturnToNormal=function(){
if(!confirm('Reset everything back to normal? This clears background, all moved/colored/deleted elements and wipes saved edit state.'))return;
// 1. Remove background
window._vmBgClear();
// 2. Remove all CSS injection rules
_vbRules={};
_vbFlush();
// 3. Restore all dragged elements to original position
for(const key of Object.keys(_draggedEls)){
const el=_elByKey(key);
if(!el)continue;
el.style.removeProperty('position');
el.style.removeProperty('left');
el.style.removeProperty('top');
el.style.removeProperty('margin');
}
_draggedEls={};
// 4. Restore deleted elements — reload page is cleanest since removed nodes are gone from DOM
// We clear the list; if any were deleted this session they're already gone, page reload will fix
const hadDeleted=_deletedKeys.length>0;
_deletedKeys=[];
// 5. Remove all data-vb-key stamps
document.querySelectorAll(`[${VB_KEY_ATTR}]`).forEach(el=>el.removeAttribute(VB_KEY_ATTR));
// 6. Clear selected target
_elTarget=null;
const selEl=document.getElementById('_vmEditSelected');
if(selEl)selEl.textContent='nothing selected';
// 7. Wipe saved edit state from localStorage
localStorage.removeItem(EDIT_STORE);
if(hadDeleted){
toast('Done — reload page to restore deleted elements');
} else {
toast('Returned to normal ✓');
}
};
[['_vmElGradAngle','_vmElGradAngleVal',v=>v+'°'],['_vmElFontSize','_vmElFontSizeVal',v=>v+'px'],['_vmElBorderW','_vmElBorderWVal',v=>v+'px'],['_vmElRadius','_vmElRadiusVal',v=>v+'px'],['_vmElScale','_vmElScaleVal',v=>parseFloat(v).toFixed(2)+'×'],['_vmElOpacity','_vmElOpacityVal',v=>parseFloat(v).toFixed(2)]].forEach(([rid,vid,fmt])=>{const r=document.getElementById(rid),v=document.getElementById(vid);if(r&&v)r.oninput=()=>{v.textContent=fmt(r.value);};});
// ── AUTO-MAPPER
function _autoMapperRun(){const R=runAutoMapper();if(R&&!R.error){applyMapperResult(R);console.log('[VB] Auto-mapper:',R._missing.length===0?'all keys found':`${R._missing.length} missing`);}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)velMap.clear();
if(key==='stickyTarget'&&!cfg.stickyTarget)_stickyTarget=null;
if(key==='lockOn'&&!cfg.lockOn){_saReturnToMouse();_saHasTarget=false;}
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'&¯o.running)macro.stop();if(key==='zoom'){_zoomMultiplier=1.0;_zoomHooked=false;hookZoom();updateZoomUI();}}};
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');});
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;}
// Restore bg url in editor
if(_bgSaved.url){const urlEl=document.getElementById('_vmBgUrl');if(urlEl)urlEl.value=_bgSaved.url;window._vmBgSetType(_bgSaved.type||'image');}
}
// ============================================================
// 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);
// Cache DOM refs so we don't query every frame
const _elFps=document.getElementById('fpsChip');
const _elMs=document.getElementById('msChip');
let _lastKillText='';
let _slowTick=0; // counter for throttled work (velMap cleanup, DOM status)
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);
_slowTick++;
// FPS chip — update once per second
if(now-_fpsLast>=1000){_fps=_fpsFrames;_fpsFrames=0;_fpsLast=now;if(_elFps){_elFps.textContent=`${_fps} FPS`;_elFps.style.color=_fps>=55?'#5bc470':_fps>=30?'#e8c060':'#e87a7a';}}
// MS chip — every 6 frames (~100ms) is plenty
if(_slowTick%6===0&&_elMs){_elMs.textContent=`${_ms} MS`;_elMs.style.color=_ms<=16?'#5bc470':_ms<=33?'#e8c060':'#e87a7a';}
// Kill count — DOM query throttled to every 30 frames
if(_slowTick%30===0){const killEl=document.querySelector('.js-ui-player-kills');if(killEl&&killEl.textContent!==_lastKillText){_lastKillText=killEl.textContent;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();
// X-ray every 10 frames — no need to spam it
if(_slowTick%1===0)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;
// Hook mouse proxy on first available input (SA2 style)
if(input&&!_saInputHooked)_saHookMouse(input);
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();}
// VelMap cleanup throttled to every 60 frames
if(_slowTick%60===0){const activeIds=new Set(players.filter(p=>p.active&&!isPlayerDead(p)).map(p=>p.__id));for(const[id] of velMap)if(!activeIds.has(id))velMap.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&&(targetHasLOS||!cfg.wallcheck)){
_saHasTarget=true;
let tsx=activeTarget.sx,tsy=activeTarget.sy;
if(cfg.predict&&myWorldPos&&worldContainer){
const aw=(me.weapTypeOld??'').toLowerCase();const bspd=(GUN_DATA[aw]??{bulletSpeed:100}).bulletSpeed;
const lead=calcLeadWorld(activeTarget.p,myWorldPos,bspd,now);
const psx=worldContainer.x+lead.wx*STUD;const psy=worldContainer.y-lead.wy*STUD;
// draw prediction ripple
const pulse=(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;
const flightMs=Math.round(lead.t*1000);octx.font='bold 9px monospace';octx.textAlign='center';octx.textBaseline='top';octx.strokeStyle='rgba(0,0,0,0.8)';octx.lineWidth=2.5;octx.strokeText(`${flightMs}ms`,psx,psy+9);octx.fillStyle='rgba(200,160,255,0.85)';octx.fillText(`${flightMs}ms`,psx,psy+9);octx.restore();
tsx=psx;tsy=psy;
}
// SA2: smooth lerp toward target — game reads _saAimX/Y via proxy
const aimed=_saApplyAim(tsx,tsy);
// Draw aim line from self to current lerped aim point
octx.save();
octx.beginPath();octx.moveTo(msx,msy);octx.lineTo(aimed.x,aimed.y);
octx.setLineDash(cfg.predict?[]:[6,4]);
octx.strokeStyle=cfg.predict?'rgba(168,85,247,0.7)':'rgba(255,80,80,0.75)';
octx.lineWidth=2;octx.stroke();octx.setLineDash([]);
octx.beginPath();octx.arc(aimed.x,aimed.y,6,0,Math.PI*2);
octx.fillStyle=cfg.predict?'rgba(168,85,247,0.85)':'rgba(255,80,80,0.85)';
octx.fill();octx.strokeStyle='#fff';octx.lineWidth=1.5;octx.stroke();
octx.restore();
} else {
// No target or locked off — glide smoothly back to real mouse
_saReturnToMouse();
}
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();}});
// Restore edit state (CSS rules, drag positions, deletions, bg) — bg uses polling so it's fine to call early
restoreEditState();
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.5] ESC=panel | T=lock | E=magnet | Q=macro | N=sticky | HOLD-Y=layer | 💾=save all | 🔄=rescan','color:#a855f7;font-weight:bold');
})();