Evades.io - NipachuMod

HUD (H): Zoom+Reset, Anti-AFK, Tracers, Time Travel (-2.24s projected), Rainbow Aura (slider), Avoid (clearance < 25), Invites highlighter, LB/Chat/ChatH toggles, Region filter, Tryhard toggle

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Evades.io - NipachuMod
// @namespace    https://evades.io/
// @version      1.0.0
// @description  HUD (H): Zoom+Reset, Anti-AFK, Tracers, Time Travel (-2.24s projected), Rainbow Aura (slider), Avoid (clearance < 25), Invites highlighter, LB/Chat/ChatH toggles, Region filter, Tryhard toggle
// @match        https://evades.io/*
// @match        https://*.evades.io/*
// @run-at       document-end
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

(() => {
  'use strict';

  // ---------- Styles ----------
  GM_addStyle(`
    @keyframes nmRainbow{0%{color:red;}16.666%{color:orange;}33.333%{color:yellow;}50%{color:green;}66.666%{color:blue;}83.333%{color:indigo;}100%{color:violet;}}
    #e_hud{position:fixed;left:28px;top:28px;z-index:2147483647;background:rgba(18,18,18,.94);color:#fff;padding:12px;border-radius:10px;border:1px solid #585858;box-shadow:0 8px 22px rgba(0,0,0,.55);font:14px system-ui,Segoe UI,Roboto,Arial;user-select:none;min-width:560px;display:none}
    #e_hud .hdr{cursor:move;display:flex;gap:10px;align-items:center;margin-bottom:10px}
    #e_hud .hdr .title{font-weight:700;font-size:18px;animation:nmRainbow 4s linear infinite}
    #e_hud .sub{font-size:11px;color:#aaa;margin-left:8px}
    #e_hud .row{display:flex;gap:8px;align-items:center}
    #e_hud .row.wrap{flex-wrap:wrap}
    #e_hud .section{border-top:1px solid #2f2f2f;margin:10px 0}
    .nm-btn{background:#030303;border:1px solid #2a2a2a;color:#fff;padding:6px 10px;border-radius:8px;cursor:pointer;transition:background .2s,transform .06s}
    .nm-btn:hover{background:#121212}.nm-btn:active{transform:translateY(1px)}.nm-btn:disabled{opacity:.6;cursor:default}
    #e_hud input[type="range"]{flex:1;height:6px;background:#4a5568;border-radius:999px;accent-color:#63b3ed}
    #e_hud input[type="text"]{flex:1;min-width:180px;background:#0e0e0e;border:1px solid #2a2a2a;color:#fff;padding:6px 8px;border-radius:8px}
    #nm_changelog{width:100%;max-height:220px;color:#fff;border:1px solid #585858;border-radius:5px;overflow:auto;background:rgba(0,0,0,.55);padding:10px;display:none}
    #nm_changelog h3{margin:0 0 6px;font-weight:700;font-size:16px}
    #nm_changelog .entry{font-size:13px;color:#ddd;margin:6px 0}
    canvas#nm_tracer{position:fixed;left:0;top:0;pointer-events:none;z-index:2147483646}

    /* Invite chips + leaderboard glow */
    .nm-chip{display:inline-flex;align-items:center;gap:6px;background:#111;border:1px solid #3a3a3a;border-radius:999px;padding:2px 8px;margin:2px 4px 0 0}
    .nm-chip .x{cursor:pointer;color:#bbb}
    .nm-lb-glow{box-shadow:0 0 12px rgba(0,200,255,.8); outline:1px solid rgba(0,200,255,.7)}
  `);

  // ---------- Storage / defaults ----------
  const STORE='evades_nipachumod', POSL='hud_left', POST='hud_top';
  let hudLeft=num(GM_getValue(POSL,28),28), hudTop=num(GM_getValue(POST,28),28);
  const PRESETS_LB=[0.8,1.0,1.2,1.4], PRESETS_CHAT=[0.8,1.0,1.2,1.4], PRESETS_CH=[150,200,260,320];

  const defaults={
    leaderboardScale:1.0, chatScale:1.0, chatHeight:200, hideUI:false, filterEnabled:false,
    zoom:1.0, antiAfk:false, antiAfkSeconds:45,
    tracersEnabled:true, tracerLine:2, tracerShowDist:true, tracerFont:'12px Arial',
    tracerLabelOffsetX:4, tracerLabelOffsetY:-4, tracerFallbackRadius:20,
    ttiEnabled:true,
    auraEnabled:true, auraRadius:200,
    showChangelog:true,
    avoidEnabled:false,
    invitesEnabled:true,
    invites:[] // array of names (string)
  };
  let st=load(STORE,defaults);

  // ---------- Game access helpers ----------
  function getCamera(){
    const q=document.querySelector('div.quests-launcher'); if(!q) return null;
    const rk=Object.keys(q).find(k=>k.startsWith('__reactFiber$'));
    return rk ? q[rk]?.memoizedProps?.children?._owner?.stateNode?.renderer?.camera : null;
  }
  function refs(){
    const el=document.querySelector('div.quests-launcher'); if(!el) return null;
    const fb=Object.keys(el).find(k=>k.startsWith('__reactFiber$'));
    const sn=el[fb]?.memoizedProps?.children?._owner?.stateNode; if(!sn) return null;
    const g=sn.gameState, cam=sn.renderer?.camera, me=g?.areaInfo?.self?.entity; if(!(g?.entities&&me&&cam)) return null;
    return {g,cam,me};
  }
  function refsLight(){
    const el=document.querySelector('div.quests-launcher'); if(!el) return null;
    const fb=Object.keys(el).find(k=>k.startsWith('__reactFiber$'));
    const sn=el[fb]?.memoizedProps?.children?._owner?.stateNode; if(!sn) return null;
    const g=sn.gameState, me=g?.areaInfo?.self?.entity; if(!(g?.entities&&me)) return null; return {g,me};
  }

  // ---------- Zoom ----------
  function setZoom(v){ const cam=getCamera(); if(cam) cam.scale=v; }
  setInterval(()=>{ if(document.querySelector('canvas')) setZoom(clamp(st.zoom,0.1,2)); },500);

  // ---------- Anti-AFK ----------
  let antiTimer=null;
  function startAntiAfk(){ stopAntiAfk(); antiTimer=setInterval(()=>{ tapShift(); wiggleCanvas(); }, Math.max(5, st.antiAfkSeconds|0)*1000); }
  function stopAntiAfk(){ if(antiTimer){ clearInterval(antiTimer); antiTimer=null; } }
  function applyAntiAfk(){ st.antiAfk?startAntiAfk():stopAntiAfk(); }
  function tapShift(){ const ke=(type)=>new KeyboardEvent(type,{key:'Shift',code:'ShiftLeft',bubbles:true}); [window,document,document.body].forEach(t=>{t.dispatchEvent(ke('keydown'));t.dispatchEvent(ke('keyup'));}); }
  function wiggleCanvas(){ const c=document.querySelector('canvas'); if(!c) return; const r=c.getBoundingClientRect(); const x=(r.left+r.width/2)|0, y=(r.top+r.height/2)|0; c.dispatchEvent(new MouseEvent('mousemove',{clientX:x+1,clientY:y,bubbles:true})); c.dispatchEvent(new MouseEvent('mousemove',{clientX:x,clientY:y,bubbles:true})); }

  // ---------- Overlay canvas ----------
  let dpr=window.devicePixelRatio||1, tcv=null, tctx=null, lastW=0,lastH=0;
  function ensureCanvas(){
    if(!tcv){ tcv=document.createElement('canvas'); tcv.id='nm_tracer'; document.documentElement.appendChild(tcv); tctx=tcv.getContext('2d'); }
    const w=window.innerWidth,h=window.innerHeight;
    if(w!==lastW||h!==lastH){ lastW=w; lastH=h; tcv.style.width=w+'px'; tcv.style.height=h+'px'; tcv.width=Math.floor(w*dpr); tcv.height=Math.floor(h*dpr); tctx.setTransform(dpr,0,0,dpr,0,0); }
  }
  function clearOverlay(){ if(!tcv||!tctx) return; tctx.clearRect(0,0,tcv.width/dpr,tcv.height/dpr); }

  // ---------- Time Travel Indicator (projected) ----------
  const TTI_MS=2240, SELF_MAX_MS=6000, SELF_BUF_CAP=1200;
  const selfBuf=[]; let lastSelf=null;

  function pushSelfSample(t,x,y){
    if(lastSelf){
      const jump=Math.hypot(x-lastSelf.x,y-lastSelf.y);
      if(jump>1200) selfBuf.length=0; // ignore big teleports
    }
    selfBuf.push({t,x,y}); lastSelf={t,x,y};
    const cut=t-SELF_MAX_MS;
    while(selfBuf.length&&selfBuf[0].t<cut) selfBuf.shift();
    if(selfBuf.length>SELF_BUF_CAP) selfBuf.splice(0,selfBuf.length-SELF_BUF_CAP);
  }
  function findIdxAt(time){ let lo=0,hi=selfBuf.length-1,ans=-1; while(lo<=hi){ const m=(lo+hi)>>1; if(selfBuf[m].t<=time){ ans=m; lo=m+1;} else hi=m-1;} return ans; }
  function sampleSelfAt(time){
    if(!selfBuf.length) return null;
    if(time<=selfBuf[0].t) return {x:selfBuf[0].x,y:selfBuf[0].y};
    if(time>=selfBuf[selfBuf.length-1].t) return {x:selfBuf[selfBuf.length-1].x,y:selfBuf[selfBuf.length-1].y};
    const i=findIdxAt(time); if(i<0||i>=selfBuf.length-1) return null;
    const A=selfBuf[i],B=selfBuf[i+1]; const dt=Math.max(1,B.t-A.t); const u=(time-A.t)/dt;
    return {x:A.x+(B.x-A.x)*u,y:A.y+(B.y-A.y)*u};
  }
  function estimatePastSelf(now){
    const n=selfBuf.length; if(n<4) return null;
    const latest=selfBuf[n-1];
    const refTime=now-180; // recent window for stable velocity
    const ref=sampleSelfAt(refTime);
    if(!ref) return sampleSelfAt(now-TTI_MS);
    const dt=Math.max(1,latest.t-refTime);
    const vx=(latest.x-ref.x)/dt, vy=(latest.y-ref.y)/dt;
    const speed=Math.hypot(vx,vy);
    if(!Number.isFinite(speed)||speed>5) return sampleSelfAt(now-TTI_MS);
    return {x:latest.x-vx*TTI_MS, y:latest.y-vy*TTI_MS};
  }

  // ---------- Threat detection ----------
  function isThreatLike(e, me){
    if(!e) return false;
    if(e.id === me.id) return false;
    if(e.dead || e.removed) return false;
    if(e.isItem || e.collectible) return false;
    const r = typeof e.radius === 'number' ? e.radius : 0;
    if(r <= 2) return false; // tiny dots
    if (e.isEnemy || e.isHazard || e.hazard || e.isProjectile) return true;
    if (typeof e.damage === 'number' && e.damage > 0) return true;
    if (typeof e.damageRadius === 'number' && e.damageRadius > 0) return true;
    return true; // treat unknown solids as threat
  }

  // ---------- Invite helpers ----------
  function norm(s){ return (s||'').trim().toLowerCase(); }
  function invitedSet(){ const set=new Set(); for(const n of st.invites) set.add(norm(n)); return set; }
  function entityName(e){ return typeof e.name === 'string' ? e.name : (e.username || e.playerName || null); }

  // best-effort glow in leaderboard DOM
  function applyInviteDomHighlights(){
    const lb=document.getElementById('leaderboard'); if(!lb) return;
    const L = invitedSet();
    for(const node of lb.children){
      const t=(node.textContent||'').trim().toLowerCase();
      node.classList.toggle('nm-lb-glow', L.size && [...L].some(n=>t.includes(n)));
    }
  }
  setInterval(applyInviteDomHighlights, 1000);

  // ---------- Rendering + main loop ----------
  function draw(){
    ensureCanvas();
    const ctx=tctx; if(!ctx) return;

    const R=refs();
    const W=tcv.width/dpr,H=tcv.height/dpr;
    ctx.clearRect(0,0,W,H);
    if(!R) return;

    const {g,cam,me}=R;
    const cvs=document.querySelector('canvas'); if(!cvs) return;
    const rect=cvs.getBoundingClientRect();
    const offX=rect.left, offY=rect.top;
    const scale=(typeof cam.originalGameScale==='number')?cam.originalGameScale:(cam.scale||1);
    const left=cam.left, top=cam.top;
    const cx=offX+rect.width/2, cy=offY+rect.height/2;
    const now=performance.now();

    // record your history
    pushSelfSample(now, me.x, me.y);

    ctx.save();
    ctx.font=st.tracerFont;

    // Aura (soft radial + rainbow ring)
    if(st.auraEnabled){
      const sx=offX+(me.x-left)*scale, sy=offY+(me.y-top)*scale, r=Math.max(4, st.auraRadius*scale);
      const rg=ctx.createRadialGradient(sx,sy,0,sx,sy,r); rg.addColorStop(0,'rgba(255,255,255,0.06)'); rg.addColorStop(1,'rgba(255,255,255,0)');
      ctx.fillStyle=rg; ctx.beginPath(); ctx.arc(sx,sy,r,0,Math.PI*2); ctx.fill();
      if(ctx.createConicGradient){ const cg=ctx.createConicGradient(0,sx,sy);
        cg.addColorStop(0/6,'#ff0000'); cg.addColorStop(1/6,'#ffa500'); cg.addColorStop(2/6,'#ffff00'); cg.addColorStop(3/6,'#00ff00'); cg.addColorStop(4/6,'#0000ff'); cg.addColorStop(5/6,'#4b0082'); cg.addColorStop(6/6,'#ff00ff'); ctx.strokeStyle=cg;
      } else ctx.strokeStyle='#fff';
      ctx.lineWidth=Math.max(2,r*0.02); ctx.beginPath(); ctx.arc(sx,sy,r,0,Math.PI*2); ctx.stroke();
    }

    // Tracers to threats
    if(st.tracersEnabled){
      for(const e of Object.values(g.entities)){
        if(!isThreatLike(e,me)) continue;
        const sx=offX+(e.x-left)*scale, sy=offY+(e.y-top)*scale; const rr=(typeof e.radius==='number'?e.radius:20)*scale;
        ctx.lineWidth=st.tracerLine; ctx.strokeStyle=e.color||'#ff0';
        const a=Math.atan2(sy-cy,sx-cx), ax=sx-Math.cos(a)*rr, ay=sy-Math.sin(a)*rr;
        ctx.beginPath(); ctx.moveTo(cx,cy); ctx.lineTo(ax,ay); ctx.stroke();
        if(st.tracerShowDist){
          ctx.fillStyle=e.color||'#ff0';
          const d=Math.hypot(e.x-me.x,e.y-me.y);
          ctx.fillText(Math.round(d), ax+st.tracerLabelOffsetX, ay+st.tracerLabelOffsetY);
        }
      }
    }

    // Invited players highlight
    if(st.invitesEnabled && st.invites.length){
      const L = invitedSet();
      for(const e of Object.values(g.entities)){
        const nm = entityName(e);
        if(!nm) continue;
        if(!L.has(norm(nm))) continue;

        // world -> screen
        const sx=offX+(e.x-left)*scale, sy=offY+(e.y-top)*scale;
        const r=(Math.max(10, (e.radius||10))*scale);
        const pulse = 0.5 + 0.5*Math.sin(now/220); // 0..1
        const c1 = `rgba(0,200,255,${0.25+0.35*pulse})`;
        const c2 = `rgba(255,0,200,${0.25+0.35*(1-pulse)})`;

        // dual ring
        ctx.lineWidth = 2 + 2*pulse;
        ctx.strokeStyle = c1;
        ctx.beginPath(); ctx.arc(sx,sy, r+10+6*pulse, 0, Math.PI*2); ctx.stroke();

        ctx.strokeStyle = c2;
        ctx.beginPath(); ctx.arc(sx,sy, r+18+6*(1-pulse), 0, Math.PI*2); ctx.stroke();

        // label
        ctx.fillStyle='rgba(0,0,0,0.65)';
        ctx.fillRect(sx-30, sy-(r+34), 60, 18);
        ctx.strokeStyle='rgba(0,200,255,0.9)';
        ctx.strokeRect(sx-30, sy-(r+34), 60, 18);
        ctx.fillStyle='#8ff';
        ctx.textAlign='center';
        ctx.textBaseline='middle';
        ctx.font='12px monospace';
        ctx.fillText('INVITE', sx, sy-(r+25));
      }
    }

    // Time travel indicator (your -2.24s projected position)
    if(st.ttiEnabled){
      const ghost=estimatePastSelf(now);
      if(ghost){
        const px=offX+(ghost.x-left)*scale, py=offY+(ghost.y-top)*scale;
        ctx.strokeStyle='rgba(80,200,255,0.9)'; ctx.fillStyle='rgba(80,200,255,0.25)';
        ctx.beginPath(); ctx.arc(px,py,8,0,Math.PI*2); ctx.fill(); ctx.stroke();
        const sx=offX+(me.x-left)*scale, sy=offY+(me.y-top)*scale;
        ctx.setLineDash([6,6]); ctx.lineWidth=2; ctx.beginPath(); ctx.moveTo(px,py); ctx.lineTo(sx,sy); ctx.stroke(); ctx.setLineDash([]);
      }
    }

    ctx.restore();
  }

  function mainLoop(){
    requestAnimationFrame(mainLoop);
    if(!st.tracersEnabled && !st.ttiEnabled && !st.auraEnabled && !(st.invitesEnabled && st.invites.length)){ clearOverlay(); return; }
    try{ draw(); }catch{}
    try{ avoidanceTick(); }catch{}
  }
  requestAnimationFrame(mainLoop);

  // ---------- Avoidance (clearance-based, nearest threat) ----------
  const AVOID_CLEARANCE_THRESHOLD = 25; // how close is "too close" after subtracting radii
  const held={w:false,a:false,s:false,d:false};

  function sendKey(code,type){
    const key=code.replace(/^Key/,'').toUpperCase();
    const ev=new KeyboardEvent(type,{key,code,keyCode:key.charCodeAt(0),which:key.charCodeAt(0),bubbles:true});
    const targets=[document.activeElement, document.querySelector('canvas'), document.body, document, window].filter(Boolean);
    for(const t of targets) t.dispatchEvent(ev);
  }
  function pressKey(k,down){
    if(held[k]===down && down){ sendKey({w:'KeyW',a:'KeyA',s:'KeyS',d:'KeyD'}[k],'keydown'); return; }
    if(held[k]===down) return;
    held[k]=down; const code={w:'KeyW',a:'KeyA',s:'KeyS',d:'KeyD'}[k]; if(!code) return;
    sendKey(code, down?'keydown':'keyup');
  }
  function releaseAll(){ ['w','a','s','d'].forEach(k=>pressKey(k,false)); }
  function uiActive(){ const el=document.activeElement; return el && /^(input|textarea)$/i.test(el.tagName); }

  function refsLightSafe(){
    try { return refsLight(); } catch { return null; }
  }

  function avoidanceTick(){
    if(!st.avoidEnabled){ releaseAll(); return; }
    if(uiActive()){ releaseAll(); return; }
    const R=refsLightSafe(); if(!R){ releaseAll(); return; }
    const {g,me}=R;

    const myR = typeof me.radius==='number' ? me.radius : 8;

    let nearest=null, bestClearance=Infinity, bestD=Infinity;
    for(const e of Object.values(g.entities)){
      if(!isThreatLike(e,me)) continue;
      const ex=e.x, ey=e.y;
      const d = Math.hypot(me.x-ex, me.y-ey);
      const er = typeof e.radius==='number' ? e.radius : 0;
      const clearance = d - (myR + er);
      if (clearance <= AVOID_CLEARANCE_THRESHOLD) {
        if (clearance < bestClearance || (clearance === bestClearance && d < bestD)) {
          bestClearance = clearance; bestD = d; nearest = e;
        }
      }
    }

    if(!nearest){ releaseAll(); return; }

    // Move away from the nearest threat
    const dx = me.x - nearest.x;
    const dy = me.y - nearest.y;
    const m = Math.hypot(dx, dy) || 1;
    const nx = dx / m;
    const ny = dy / m;
    const thr = 0.03;

    if(Math.abs(nx)>thr){
      if(nx>0){ pressKey('d',true); pressKey('a',false);} else { pressKey('a',true); pressKey('d',false); }
    } else { pressKey('a',false); pressKey('d',false); }

    if(Math.abs(ny)>thr){
      if(ny>0){ pressKey('s',true); pressKey('w',false);} else { pressKey('w',true); pressKey('s',false); }
    } else { pressKey('w',false); pressKey('s',false); }
  }

  // ---------- HUD ----------
  let hud;
  function buildHUD(){
    hud=document.createElement('div'); hud.id='e_hud'; hud.style.left=hudLeft+'px'; hud.style.top=hudTop+'px';
    hud.innerHTML=`
      <div class="hdr"><span class="title">NipachuMod</span><span class="sub">Press H to toggle HUD</span></div>

      <!-- Zoom -->
      <div class="row" style="margin-bottom:6px">
        <span class="sub">Zoom</span>
        <button class="nm-btn" id="zDec">-</button>
        <input id="zRange" type="range" min="0.1" max="2" step="0.01">
        <button class="nm-btn" id="zInc">+</button>
        <button class="nm-btn" id="zReset">Reset</button>
      </div>

      <div class="section"></div>

      <!-- Anti-AFK -->
      <div class="row"><button class="nm-btn" id="afkToggle"></button></div>

      <div class="section"></div>

      <!-- Tracers + TimeTravel -->
      <div class="row wrap" style="margin-bottom:6px">
        <button class="nm-btn" id="trToggle"></button>
        <button class="nm-btn" id="trDist"></button>
        <button class="nm-btn" id="ttiToggle"></button><span class="sub">(you -2.24s)</span>
      </div>

      <div class="section"></div>

      <!-- Aura -->
      <div class="row wrap" style="margin-bottom:6px">
        <button class="nm-btn" id="auraToggle"></button>
        <span class="sub">Aura</span>
        <input id="auraRange" type="range" min="20" max="600" step="5" style="width:240px">
        <span id="auraVal" class="sub"></span>
      </div>

      <div class="section"></div>

      <!-- Avoid -->
      <div class="row wrap" style="margin-bottom:6px">
        <button class="nm-btn" id="avoidToggle"></button>
        <span class="sub">Clearance threshold: 25</span>
      </div>

      <div class="section"></div>

      <!-- Invites -->
      <div class="row wrap" style="margin-bottom:6px">
        <button class="nm-btn" id="invToggle"></button>
        <input id="invInput" type="text" placeholder="Player name (case-insensitive)">
        <button class="nm-btn" id="invAdd">+ Add</button>
        <div id="invChips" class="row wrap" style="margin-top:6px"></div>
      </div>

      <div class="section"></div>

      <!-- UI tweaks -->
      <div class="row wrap" style="margin-bottom:6px">
        <button class="nm-btn" id="lbCycle">LB 1.00x</button>
        <button class="nm-btn" id="chatCycle">Chat 1.00x</button>
        <button class="nm-btn" id="chCycle">ChatH 200</button>
        <button class="nm-btn" id="filterRegion">Filter My Region</button>
        <button class="nm-btn" id="tryhardBtn">Tryhard</button>
        <button class="nm-btn" id="toggleChangelog">Changelog</button>
      </div>

      <div id="nm_changelog">
        <h3>Changelog</h3>
        <div class="entry">v1.0.0 - Invites highlighter (pulsing ring + label), Avoid uses clearance < 25 vs nearest threat, projected TimeTravel, Aura slider, Tryhard hides icons+leaderboard+chat.</div>
      </div>
    `;
    document.documentElement.appendChild(hud);
    drag(hud, hud.querySelector('.hdr'));

    // Zoom controls
    const zRange=gid('zRange');
    const applyZoom=v=>{
      const vv=clamp(parseFloat(v)||1,0.1,2);
      st.zoom=vv; save(STORE,st); setZoom(vv);
    };
    zRange.value=(st.zoom||1).toFixed(2);
    zRange.addEventListener('input',()=>applyZoom(zRange.value));
    gid('zDec').onclick=()=>applyZoom((parseFloat(zRange.value)-0.01).toFixed(2));
    gid('zInc').onclick=()=>applyZoom((parseFloat(zRange.value)+0.01).toFixed(2));
    gid('zReset').onclick=()=>applyZoom(1.00);

    // Anti-AFK
    const afkBtn=gid('afkToggle');
    const paintAfk=()=>{ afkBtn.textContent=st.antiAfk?'Anti-AFK: On':'Anti-AFK: Off'; };
    paintAfk();
    afkBtn.onclick=()=>{
      st.antiAfk=!st.antiAfk; save(STORE,st); paintAfk(); applyAntiAfk();
    };
    applyAntiAfk();

    // Tracers
    const paintTr=()=>{
      gid('trToggle').textContent=st.tracersEnabled?'Tracers: On':'Tracers: Off';
      gid('trDist').textContent='Distance: ' + (st.tracerShowDist?'On':'Off');
    };
    paintTr();
    gid('trToggle').onclick=()=>{
      st.tracersEnabled=!st.tracersEnabled; save(STORE,st); paintTr();
      if(!st.tracersEnabled && !st.ttiEnabled && !st.auraEnabled && !(st.invitesEnabled && st.invites.length)) clearOverlay();
    };
    gid('trDist').onclick=()=>{
      st.tracerShowDist=!st.tracerShowDist; save(STORE,st); paintTr();
    };

    // Time travel
    const ttiBtn=gid('ttiToggle');
    const paintTTI=()=>{ ttiBtn.textContent=st.ttiEnabled?'TimeTravel: On':'TimeTravel: Off'; };
    paintTTI();
    ttiBtn.onclick=()=>{
      st.ttiEnabled=!st.ttiEnabled; save(STORE,st); paintTTI();
      if(!st.tracersEnabled && !st.ttiEnabled && !st.auraEnabled && !(st.invitesEnabled && st.invites.length)) clearOverlay();
    };

    // Aura
    const auraRange=gid('auraRange'), auraVal=gid('auraVal');
    const aurBtn=gid('auraToggle');
    const paintAuraVals=()=>{ auraRange.value=st.auraRadius; auraVal.textContent=String(st.auraRadius); };
    const paintAura=()=>{ aurBtn.textContent = st.auraEnabled ? 'Aura: On' : 'Aura: Off'; };
    paintAuraVals(); paintAura();
    aurBtn.onclick=()=>{
      st.auraEnabled=!st.auraEnabled; save(STORE,st); paintAura();
      if(!st.tracersEnabled && !st.ttiEnabled && !st.auraEnabled && !(st.invitesEnabled && st.invites.length)) clearOverlay();
    };
    auraRange.addEventListener('input',()=>{
      st.auraRadius=Math.max(20, Math.min(600, parseInt(auraRange.value,10)||200));
      paintAuraVals(); save(STORE,st);
    });

    // Avoid
    const avT=gid('avoidToggle');
    const paintAvoid=()=>{ avT.textContent=st.avoidEnabled?'Avoid: On':'Avoid: Off'; };
    paintAvoid();
    avT.onclick=()=>{
      st.avoidEnabled=!st.avoidEnabled; save(STORE,st); paintAvoid();
      if(!st.avoidEnabled) releaseAll();
    };

    // Invites
    const invBtn = gid('invToggle');
    const invInput = gid('invInput');
    const invAdd = gid('invAdd');
    const invChips = gid('invChips');

    const paintInvToggle = () => { invBtn.textContent = st.invitesEnabled ? 'Invites: On' : 'Invites: Off'; };
    const paintChips = () => {
      invChips.innerHTML = '';
      st.invites.forEach((name, idx) => {
        const chip = document.createElement('div');
        chip.className = 'nm-chip';
        chip.innerHTML = `<span>${escapeHtml(name)}</span><span class="x" title="Remove">✖</span>`;
        chip.querySelector('.x').onclick = () => {
          st.invites.splice(idx,1); save(STORE,st); paintChips(); applyInviteDomHighlights();
        };
        invChips.appendChild(chip);
      });
    };
    function addInvite(name){
      name = (name||'').trim();
      if(!name) return;
      if(!st.invites.some(n=>norm(n)===norm(name))){
        st.invites.push(name);
        save(STORE,st);
        paintChips();
        applyInviteDomHighlights();
      }
      invInput.value='';
    }

    paintInvToggle();
    paintChips();
    invBtn.onclick = () => {
      st.invitesEnabled = !st.invitesEnabled;
      save(STORE,st);
      paintInvToggle();
      if(!st.tracersEnabled && !st.ttiEnabled && !st.auraEnabled && !(st.invitesEnabled && st.invites.length)) clearOverlay();
    };
    invAdd.onclick = () => addInvite(invInput.value);
    invInput.addEventListener('keydown', e => { if(e.key==='Enter') addInvite(invInput.value); });

    // LB/Chat/ChatH cycles
    const lbBtn=gid('lbCycle'), chatBtn=gid('chatCycle'), chBtn=gid('chCycle');
    const cycle=(arr,cur)=>arr[(Math.max(0,arr.findIndex(v=>Math.abs(v-cur)<1e-6))+1)%arr.length];
    const paintLB=()=>{ lbBtn.textContent=`LB ${st.leaderboardScale.toFixed(2)}x`; };
    const paintChat=()=>{ chatBtn.textContent=`Chat ${st.chatScale.toFixed(2)}x`; };
    const paintCH=()=>{ chBtn.textContent=`ChatH ${st.chatHeight}`; };
    const applyScales=()=>{
      const lb=document.getElementById('leaderboard'), ch=document.getElementById('chat');
      if(lb) lb.style.zoom=st.leaderboardScale===1?'':st.leaderboardScale;
      if(ch) ch.style.zoom=st.chatScale===1?'':st.chatScale;
      applyChatHeight(st.chatHeight);
    };
    lbBtn.onclick=()=>{ st.leaderboardScale=cycle(PRESETS_LB,st.leaderboardScale); save(STORE,st); paintLB(); applyScales(); };
    chatBtn.onclick=()=>{ st.chatScale=cycle(PRESETS_CHAT,st.chatScale); save(STORE,st); paintChat(); applyScales(); };
    chBtn.onclick=()=>{ st.chatHeight=cycle(PRESETS_CH,st.chatHeight); save(STORE,st); paintCH(); applyScales(); };
    paintLB(); paintChat(); paintCH(); applyScales();

    // Region filter toggle
    gid('filterRegion').onclick = toggleRegionFilter;

    // Tryhard
    const tryBtn=gid('tryhardBtn');
    const paintTry=()=>{ tryBtn.textContent=st.hideUI?'Tryhard: On':'Tryhard: Off'; };
    tryBtn.onclick=()=>{ st.hideUI=!st.hideUI; save(STORE,st); applyUIVisibility(); paintTry(); };
    paintTry();

    // Changelog panel
    const clBtn=gid('toggleChangelog'), clBox=gid('nm_changelog');
    clBtn.onclick=()=>{ st.showChangelog=!st.showChangelog; save(STORE,st); clBox.style.display=st.showChangelog?'block':'none'; };
    clBox.style.display=st.showChangelog?'block':'none';

    applyUIVisibility();
    if(st.filterEnabled) startRegionObserver();
  }

  // HUD toggle
  window.addEventListener('keydown', e=>{
    if((e.key||'').toLowerCase()==='h' && !e.repeat){
      const t=document.activeElement && /^(input|textarea)$/i.test(document.activeElement.tagName); if(t) return;
      if(!hud) buildHUD();
      hud.style.display=(hud.style.display==='none'||!hud.style.display)?'block':'none';
    }
  }, true);

  // ---------- UI/region-filter utilities ----------
  let leaderboard=null, chatBox=null, regionMO=null;
  const uiSelectors=['.settings-launcher','.quests-launcher','.mod-tools-launcher','#leaderboard','#chat'];
  function refreshRefs(){ const lb=document.getElementById('leaderboard'); const ch=document.getElementById('chat'); if(lb) leaderboard=lb; if(ch) chatBox=ch; }
  function applyChatHeight(h){ const win=document.getElementById('chat-window'); const input=document.getElementById('chat-input'); refreshRefs(); if(!chatBox||!win||!input) return; chatBox.style.height=h+'px'; win.style.height=(h-10)+'px'; input.style.top=h+'px'; }
  function applyUIVisibility(){ uiSelectors.forEach(s=>{ document.querySelectorAll(s).forEach(el=>{ el.style.display=st.hideUI?'none':''; el.style.pointerEvents=st.hideUI?'none':''; }); }); }
  function toggleRegionFilter(){ st.filterEnabled=!st.filterEnabled; save(STORE,st); if(st.filterEnabled) startRegionObserver(); else { stopRegionObserver(); showFullLB(); } }
  function startRegionObserver(){ refreshRefs(); if(!leaderboard) return; if(regionMO) regionMO.disconnect(); regionMO=new MutationObserver(()=>filterLB()); regionMO.observe(leaderboard,{childList:true,subtree:true}); filterLB(); }
  function stopRegionObserver(){ regionMO&&regionMO.disconnect(); regionMO=null; }
  function myRegion(){ refreshRefs(); if(!leaderboard) return null; let cur=null,my=null; for(const ch of leaderboard.children){ if(ch.classList?.contains('leaderboard-title-break')) cur=ch.textContent.trim(); else if(ch.querySelector('b,strong')){ my=cur; break; } } return my; }
  function filterLB(){ refreshRefs(); if(!leaderboard) return; const mr=myRegion(); if(!mr) return; let inReg=false; for(const c of leaderboard.children){ if(c.classList?.contains('leaderboard-title-break')){ const nm=c.textContent.trim(); inReg=(mr==='Ancient Abyss')?(nm==='Ancient Abyss'||nm==='Vast Void'):(nm.toLowerCase()===mr.toLowerCase()); c.style.display=inReg?'':'none'; } else c.style.display=inReg?'':'none'; } }
  function showFullLB() {
  refreshRefs();
  if (!leaderboard) return;
  const els = Array.from(leaderboard.children);
  els.forEach(c => { c.style.display = ''; });
}

  // ---------- tiny helpers ----------
  function gid(id){ return document.getElementById(id); }
  function drag(el,handle){ let go=false,ox=0,oy=0; handle.addEventListener('mousedown',e=>{ go=true; ox=e.clientX-el.offsetLeft; oy=e.clientY-el.offsetTop; e.preventDefault(); }); document.addEventListener('mouseup',()=>{ if(!go) return; go=false; hudLeft=el.offsetLeft; hudTop=el.offsetTop; GM_setValue(POSL,hudLeft); GM_setValue(POST,hudTop); }); document.addEventListener('mousemove',e=>{ if(!go) return; el.style.left=(e.clientX-ox)+'px'; el.style.top=(e.clientY-oy)+'px'; }); }
  function clamp(v,lo,hi){ return Math.max(lo, Math.min(hi,v)); }
  function num(v,d){ const n=Number(v); return Number.isFinite(n)?n:d; }
  function load(k,def){ try{ const raw=GM_getValue(k,null); return raw?{...def,...JSON.parse(raw)}:{...def}; }catch{ return {...def}; } }
  function save(k,obj){ try{ GM_setValue(k, JSON.stringify(obj)); }catch{} }
  function escapeHtml(s){ return s.replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[m])); }

})();