Anti-Visibility Cloak

Force pages to always think the tab is visible/focused; optionally spoofs mouse as always "in page" and blocks exit/enter intent; shows a tiny popup when a visibility or mouse check is detected

// ==UserScript==
// @name         Anti-Visibility Cloak
// @namespace    https://spin.rip/
// @version      1.2.1
// @description  Force pages to always think the tab is visible/focused; optionally spoofs mouse as always "in page" and blocks exit/enter intent; shows a tiny popup when a visibility or mouse check is detected
// @author       Spinfal
// @match        *://*/*
// @run-at       document-start
// @grant        none
// @all-frames   true
// @icon         https://cdn.spin.rip/r/antivisibility.png
// @license      gpl-3.0-or-later
// ==/UserScript==
(function() {
  'use strict';

  // easy mode: flip these first
  const CONFIG = {
    // safety levels (start here)
    strictMode: false,        // stronger filtering + harder to undo patches
    paranoidMode: false,      // periodic re-apply + extra guards
    pageRealmPatch: false,     // also patch inside the page's own js realm

    // optional extras
    patchTimers: false,       // smooth over background throttling (raf/idle)
    patchPointerCapture: true,// soften pointer-capture based exit-intent
    exposeApi: true,          // controls/stats from API available via a random name

    // what to show in the tiny toast
    notify: {
      properties: true,       // toast when code reads visibility props
      hasFocusCall: false,    // toast when document.hasFocus() is called
      burstWindowMs: 400,     // collapse duplicate toasts within this window
      silentBootMs: 600,      // suppress toasts right after load

      // toast when listeners are added for these events
      addListener: {
        visibilitychange: true,
        webkitvisibilitychange: true,
        mozvisibilitychange: true,
        msvisibilitychange: true,
        pagehide: true,
        freeze: true,
        resume: true,
        pageshow: true,
        blur: false,
        focus: false,
        mouseout: true,
        mouseleave: true,
        pointerout: true,
        pointerleave: true,
        mouseover: true,
        mouseenter: true,
        pointerover: true,
        focusin: true,
        focusout: true,
        pointermove: false,
        mousemove: false
      },

      // toast when those listeners actually run
      invoke: {
        visibilitychange: true,
        webkitvisibilitychange: true,
        mozvisibilitychange: true,
        msvisibilitychange: true,
        pagehide: true,
        freeze: true,
        resume: true,
        pageshow: true,
        blur: false,
        focus: false,
        mouseout: false,
        mouseleave: false,
        pointerout: false,
        pointerleave: false,
        mouseover: false,
        mouseenter: false,
        pointerover: false,
        focusin: true,
        focusout: true,
        pointermove: false,
        mousemove: false
      }
    },

    // avoid spamming toasts when you focus text inputs
    suppressFocusOnEditable: true,

    // mouse presence + enter/exit intent controls
    mouse: {
      spoofExit: true,          // block exit-intent on window/doc/html/body
      ignoreGlobalLeave: true,  // swallow leave where relatedTarget is null
      blockGlobalEnter: true,   // swallow enter where relatedTarget is null
      initialEnterOnce: true,   // let exactly one global enter through after load

      // fake movement to look "alive"
      fakeMovement: true,       // periodically dispatch synthetic movement
      fakeIntervalMs: 12000,    // base interval between moves
      randomizeInterval: true,  // add ±30% jitter to the interval
      moveJitterPx: 2,          // small pixel jitter so it’s not robotic
      alsoPointerMove: true,    // emit pointermove alongside mousemove
      lightScrollNudge: false   // tiny scroll nudge (off by default)
    },

    focus: {
      blockGlobalBlur: true // swallow window/document/html/body blur & focusout
    }
  };

  /* ---------- just a separator to keep things clean ----------- */



  // stronger source for ids used on the API surface
  function secureRandomString(len = 16) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    const buf = new Uint32Array(len);
    crypto.getRandomValues(buf);
    return Array.from(buf, n => chars[n % chars.length]).join('');
  }

  // stats counters for debugging
  const STATS = {
    addBlocked: Object.create(null),
    invokeSwallowed: Object.create(null),
    synthetic: Object.create(null),
    redefinitions: 0
  };

  // notifier (stealthier)
  const makeNotifier = () => {
    let shadow, wrap, root, last, lastAt = 0, hideTimer;
    const bootAt = Date.now();
    root = document.createElement('div');
    root.style.all = 'initial';
    root.style.position = 'fixed';
    root.style.zIndex = '2147483647';
    root.style.right = '10px';
    root.style.bottom = '10px';
    root.style.pointerEvents = 'none';
    shadow = root.attachShadow({ mode: 'open' });
    const style = document.createElement('style');
    style.textContent = `
      .wrap{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:12px;line-height:1.2;background:#111;color:#fff;border-radius:8px;padding:8px 10px;box-shadow:0 2px 12px rgba(0,0,0,.35);opacity:.95;max-width:260px;pointer-events:auto;display:flex;gap:8px;align-items:start}
      .dot{width:8px;height:8px;border-radius:50%;background:#4ade80;margin-top:4px;flex:0 0 8px}
      .msg{white-space:pre-line}
      .hide{animation:fadeout .4s forwards}
      @keyframes fadeout{to{opacity:0;transform:translateY(4px)}}
    `;
    wrap = document.createElement('div');
    wrap.className = 'wrap';
    wrap.innerHTML = `<div class="dot"></div><div class="msg"></div>`;
    shadow.append(style, wrap);
    const set = (text) => {
      if ((Date.now() - bootAt) < (CONFIG.notify.silentBootMs|0)) return;
      if (!root.isConnected) document.documentElement.appendChild(root);
      wrap.querySelector('.msg').textContent = text;
      wrap.classList.remove('hide');
      clearTimeout(hideTimer);
      hideTimer = setTimeout(() => {
        wrap.classList.add('hide');
        setTimeout(() => { if (root.isConnected) root.remove(); }, 450);
      }, 1600);
    };
    return (what) => {
      const now = Date.now();
      if (what === last && (now - lastAt) < (CONFIG.notify.burstWindowMs|0)) return;
      last = what; lastAt = now;
      set(`visibility/mouse check bypassed:\n${what}`);
    };
  };
  const notify = makeNotifier();

  // helpers
  const define = (target, key, descriptor) => {
    try {
      const desc = Object.getOwnPropertyDescriptor(target, key);
      if (desc && desc.configurable === false) return false;
      Object.defineProperty(target, key, { configurable: true, ...descriptor });
      return true;
    } catch { return false; }
  };
  const harden = (target, key, lock = {}) => {
    try {
      const d = Object.getOwnPropertyDescriptor(target, key);
      if (!d) return false;
      Object.defineProperty(target, key, {
        ...d,
        configurable: !!lock.configurable ? lock.configurable : false,
        writable: d.writable === true ? false : d.writable
      });
      return true;
    } catch { return false; }
  };
  const freezeFn = (fn) => { try { Object.freeze(fn); } catch {} return fn; };
  const asGlobalLike = (n) => (n===window || n===document || n===document.documentElement || n===document.body || n===window.visualViewport);

  // surface properties
  const forceVisibilityProps = () => {
    STATS.redefinitions++;
    const DocProto = Document.prototype;
    const HTMLDocProto = (window.HTMLDocument && HTMLDocument.prototype) || DocProto;
    const stableGet = (name, value) => function() {
      if (CONFIG.notify.properties) notify(`${name} read`);
      return value;
    };
    // main
    define(DocProto, 'visibilityState', { get: stableGet('document.visibilityState','visible') }) ||
      define(document, 'visibilityState', { get: stableGet('document.visibilityState','visible') });
    define(DocProto, 'hidden', { get: stableGet('document.hidden',false) }) ||
      define(document, 'hidden', { get: stableGet('document.hidden',false) });
    // legacy + aliases
    [['webkitVisibilityState','visible'],['mozVisibilityState','visible'],['msVisibilityState','visible'],
     ['webkitHidden',false],['mozHidden',false],['msHidden',false]].forEach(([k,v])=>{
      define(DocProto, k, { get: stableGet(`document.${k}`, v) }) ||
      define(document, k, { get: stableGet(`document.${k}`, v) });
    });
    // prerendering flag
    if ('prerendering' in document || DocProto.hasOwnProperty('prerendering')) {
      define(DocProto, 'prerendering', { get: stableGet('document.prerendering', false) }) ||
      define(document, 'prerendering', { get: stableGet('document.prerendering', false) });
    }
    // make sure htmldocument sees the same
    if (HTMLDocProto && HTMLDocProto !== DocProto) {
      define(HTMLDocProto, 'visibilityState', { get: stableGet('document.visibilityState','visible') });
      define(HTMLDocProto, 'hidden', { get: stableGet('document.hidden',false) });
    }
    // hasFocus
    const hasFocusImpl = freezeFn(function hasFocus() {
      if (CONFIG.notify.hasFocusCall) notify('document.hasFocus() call');
      return true;
    });
    define(DocProto, 'hasFocus', { value: hasFocusImpl }) || define(document, 'hasFocus', { value: hasFocusImpl });
    try { if (!document.__origHasFocus) Object.defineProperty(document, '__origHasFocus', { value: DocProto.hasFocus, configurable: true }); } catch {}

    // property-style handlers setters
    const propEvents = ['visibilitychange','webkitvisibilitychange','mozvisibilitychange','msvisibilitychange','focus','blur','focusin','focusout','pageshow','pagehide'];
    const wrapPropSetter = (host, prop) => {
      const key = 'on'+prop;
      const setWrapped = function(v){
        if (typeof v === 'function') {
          const wrapped = function(ev){
            // swallow global blur/focusout from property handlers too
            if (CONFIG.focus?.blockGlobalBlur && (prop === 'blur' || prop === 'focusout') && asGlobalLike(host)) {
              if (CONFIG.notify.invoke[prop]) notify(`${prop} ignored (global blur: prop handler)`);
              return;
            }
            return v.call(this, ev);
          };
          try { Object.defineProperty(v, '__visible_wrap__', { value: wrapped }); } catch {}
          return origSet.call(this, wrapped);
        }
        return origSet.call(this, v);
      };
      const desc = Object.getOwnPropertyDescriptor(host, key) || { configurable:true, enumerable:true };
      const origSet = desc.set || function(fn){ this.addEventListener(prop, fn); };
      define(host, key, { configurable:true, enumerable:desc.enumerable!==false, set:setWrapped, get: desc.get || function(){ return null; } });
    };
    propEvents.forEach(e => { wrapPropSetter(window, e); wrapPropSetter(document, e); });

    if (CONFIG.strictMode) {
      try { harden(Document.prototype,'visibilityState'); harden(Document.prototype,'hidden'); harden(document,'visibilityState'); harden(document,'hidden'); } catch {}
    }
  };

  // listener wrapping + filtering
  const forceVisibilityEvents = () => {
    const TYPES = [
      'visibilitychange','webkitvisibilitychange','mozvisibilitychange','msvisibilitychange',
      'pagehide','freeze','resume','pageshow',
      'blur','focus','focusin','focusout',
      'mouseleave','mouseout','pointerleave','pointerout',
      'mouseover','mouseenter','pointerover',
      'mousemove','pointermove'
    ];
    const EXIT_TYPES  = new Set(['mouseleave','mouseout','pointerleave','pointerout']);
    const ENTER_TYPES = new Set(['mouseover','mouseenter','pointerover']);
    const BLUR_TYPES  = new Set(['blur','focusout']);
    const isEditableTarget = (ev) => {
      if (!ev || !ev.target) return false;
      const t = ev.target;
      if (t.isContentEditable) return true;
      const tag = (t.tagName || '').toLowerCase();
      return tag === 'input' || tag === 'textarea' || tag === 'select';
    };

    let allowOneGlobalEnter = !!CONFIG.mouse.initialEnterOnce;

    const ensureVisibleDescriptors = () => {
      try {
        Object.defineProperty(document, 'visibilityState', { get: () => 'visible', configurable: true });
        Object.defineProperty(document, 'hidden', { get: () => false, configurable: true });
      } catch {}
    };

    const wrapHandler = (type, fn, ctx) => {
      const wrapped = function(event) {
        ensureVisibleDescriptors();
        // swallow global blur/focusout
        if (CONFIG.focus?.blockGlobalBlur && BLUR_TYPES.has(type) && asGlobalLike(ctx)) {
          if (CONFIG.notify.invoke[type]) notify(`${type} ignored (global blur)`);
          return; // don't call site handler
        }
        // swallow global exit
        if (CONFIG.mouse.ignoreGlobalLeave && EXIT_TYPES.has(type) && asGlobalLike(ctx) && (!event || event.relatedTarget == null)) {
          STATS.invokeSwallowed[type] = (STATS.invokeSwallowed[type]||0)+1;
          if (CONFIG.notify.invoke[type]) notify(`${type} ignored (global exit)`);
          return;
        }
        // swallow global enter
        if (CONFIG.mouse.blockGlobalEnter && ENTER_TYPES.has(type) && asGlobalLike(ctx) && (!event || event.relatedTarget == null)) {
          if (allowOneGlobalEnter) {
            allowOneGlobalEnter = false;
          } else {
            STATS.invokeSwallowed[type] = (STATS.invokeSwallowed[type]||0)+1;
            if (CONFIG.notify.invoke[type]) notify(`${type} ignored (global enter)`);
            return;
          }
        }
        // strict: also treat body/html/visualViewport as global always
        if (CONFIG.strictMode && (EXIT_TYPES.has(type) || ENTER_TYPES.has(type)) && (ctx===document.body || ctx===document.documentElement || ctx===window.visualViewport)) {
          STATS.invokeSwallowed[type] = (STATS.invokeSwallowed[type]||0)+1;
          if (CONFIG.notify.invoke[type]) notify(`${type} ignored (strict global ${type})`);
          return;
        }
        if (CONFIG.notify.invoke[type]) {
          if (!(CONFIG.suppressFocusOnEditable && (type === 'focus' || type === 'blur' || type==='focusin' || type==='focusout') && isEditableTarget(event))) {
            notify(`${type} listener invoked`);
          }
        }
        try { return fn.call(this, event); } catch(e) { setTimeout(() => { throw e; }); }
      };
      return freezeFn(wrapped);
    };

    const TYPE_SET = new Set(TYPES);
    const makeWrapped = (t, listener, ctx) => {
      // normalize to a callable
      let fn = listener;
      if (typeof listener === 'object' && typeof listener.handleEvent === 'function') {
        fn = function(ev){ return listener.handleEvent.call(listener, ev); };
      }
      if (typeof fn !== 'function') return null;
      const wrapped = wrapHandler(t, fn, ctx);
      try { Object.defineProperty(listener, '__visible_wrap__', { value: wrapped }); } catch {}
      return wrapped;
    };
    const patchAEL = (proto, label) => {
      const orig = proto.addEventListener;
      define(proto, 'addEventListener', {
        value: function(type, listener, options) {
          const t = String(type);
          if (!listener || !TYPE_SET.has(t)) return orig.call(this, type, listener, options);

          // block registration of global blur/focusout listeners
          if (CONFIG.focus?.blockGlobalBlur && BLUR_TYPES.has(t) && asGlobalLike(this)) {
            STATS.addBlocked[t] = (STATS.addBlocked[t]||0)+1;
            if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}") blocked on global target`);
            return;
          }

          // block registration of exit/enter listeners on global-ish targets
          if ((CONFIG.mouse.spoofExit && EXIT_TYPES.has(t) && asGlobalLike(this)) ||
              (CONFIG.mouse.blockGlobalEnter && ENTER_TYPES.has(t) && asGlobalLike(this)) ||
              (CONFIG.strictMode && (EXIT_TYPES.has(t) || ENTER_TYPES.has(t)) &&
                (this===document.body || this===document.documentElement || this===window.visualViewport))) {
            STATS.addBlocked[t] = (STATS.addBlocked[t]||0)+1;
            if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}") blocked on global target`);
            return;
          }

          if (CONFIG.notify.addListener[t]) notify(`${label}.addEventListener("${t}")`);

          const wrapped = makeWrapped(t, listener, this);
          // if it's not a function or EventListener object, just pass through
          return orig.call(this, type, wrapped || listener, options);
        }
      });

      const origRel = proto.removeEventListener;
      define(proto, 'removeEventListener', {
        value: function(type, listener, options) {
          const l = listener && listener.__visible_wrap__ ? listener.__visible_wrap__ : listener;
          return origRel.call(this, type, l, options);
        }
      });

      if (CONFIG.strictMode) { try { harden(proto,'addEventListener'); harden(proto,'removeEventListener'); } catch {} }
    };

    patchAEL(EventTarget.prototype, 'EventTarget');
  };

  // timers normalization (lightweight)
  const installTimerShims = () => {
    if (!CONFIG.patchTimers) return;
    try {
      const _raf = window.requestAnimationFrame.bind(window);
      const _caf = window.cancelAnimationFrame ? window.cancelAnimationFrame.bind(window) : ()=>{};
      let lastRaf = 0;
      const rafWrapped = freezeFn(function(cb){
        const start = performance.now();
        return _raf(function(t){
          const dt = t - lastRaf;
          lastRaf = t;
          // if raf is obviously throttled, simulate a smoother cadence
          if (dt > 80) {
            setTimeout(()=>cb(performance.now()), 16);
          } else {
            cb(t);
          }
        });
      });
      define(window, 'requestAnimationFrame', { value: rafWrapped });
      define(window, 'cancelAnimationFrame', { value: freezeFn(_caf) });

      const _ric = window.requestIdleCallback && window.requestIdleCallback.bind(window);
      if (_ric) {
        const ricWrapped = freezeFn(function(cb, opts){
          return _ric(function(deadline){
            if (!deadline || typeof deadline.timeRemaining !== 'function') {
              try { cb({ didTimeout:false, timeRemaining:()=>10 }); } catch {}
              return;
            }
            if (deadline.timeRemaining() < 5) {
              try { cb({ didTimeout:false, timeRemaining:()=>10 }); } catch {}
            } else {
              try { cb(deadline); } catch {}
            }
          }, opts);
        });
        define(window, 'requestIdleCallback', { value: ricWrapped });
      }
    } catch {}
  };

  // pointer capture softeners
  const installPointerCaptureShims = () => {
    if (!CONFIG.patchPointerCapture || !Element || !Element.prototype) return;
    try {
      const EP = Element.prototype;
      const _spc = EP.setPointerCapture;
      const _rpc = EP.releasePointerCapture;
      if (_spc) define(EP, 'setPointerCapture', { value: freezeFn(function(pointerId){
        try {
          const g = this === document.documentElement || this === document.body;
          if (g) return; // no-op on global-ish targets
        } catch {}
        return _spc.apply(this, arguments);
      })});
      if (_rpc) define(EP, 'releasePointerCapture', { value: freezeFn(function(pointerId){
        try { return _rpc.apply(this, arguments); } catch {}
      })});
      if (CONFIG.strictMode) { try { harden(EP, 'setPointerCapture'); harden(EP,'releasePointerCapture'); } catch {} }
    } catch {}
  };

  // synthetic mouse/pointer presence
  const installFakeMouse = () => {
    if (!CONFIG.mouse.fakeMovement) return;
    let lastX = Math.max(10, Math.min(window.innerWidth - 10, Math.floor(window.innerWidth / 2)));
    let lastY = Math.max(10, Math.min(window.innerHeight - 10, Math.floor(window.innerHeight / 2)));
    const update = (e) => {
      if (!e) return;
      if (typeof e.clientX === 'number') lastX = e.clientX;
      if (typeof e.clientY === 'number') lastY = e.clientY;
    };
    window.addEventListener('mousemove', update, { passive: true, capture: true });
    const base = Math.max(4000, CONFIG.mouse.fakeIntervalMs | 0);
    const nextDelay = () => {
      if (!CONFIG.mouse.randomizeInterval) return base;
      const jitter = base * 0.3;
      return Math.max(2000, base + (Math.random()*2*jitter - jitter));
    };
    const tick = () => {
      try {
        const jx = (Math.random()*CONFIG.mouse.moveJitterPx*2 - CONFIG.mouse.moveJitterPx)|0;
        const jy = (Math.random()*CONFIG.mouse.moveJitterPx*2 - CONFIG.mouse.moveJitterPx)|0;
        const x = Math.max(0, Math.min(window.innerWidth - 1, lastX + jx));
        const y = Math.max(0, Math.min(window.innerHeight - 1, lastY + jy));
        const mouseEv = new MouseEvent('mousemove', { bubbles:true, cancelable:false, clientX:x, clientY:y, screenX:x, screenY:y, view:window });
        document.dispatchEvent(mouseEv);
        STATS.synthetic.mousemove = (STATS.synthetic.mousemove||0)+1;
        if (CONFIG.notify.invoke.mousemove) notify('synthetic mousemove dispatched');
        if (CONFIG.mouse.alsoPointerMove && 'PointerEvent' in window) {
          const ptr = new PointerEvent('pointermove', { bubbles:true, cancelable:false, clientX:x, clientY:y, pointerType:'mouse', isPrimary:true, view:window });
          document.dispatchEvent(ptr);
          STATS.synthetic.pointermove = (STATS.synthetic.pointermove||0)+1;
        }
        if (CONFIG.mouse.lightScrollNudge && document.scrollingElement && (document.scrollingElement.scrollHeight > document.scrollingElement.clientHeight)) {
          const se = document.scrollingElement;
          const start = se.scrollTop;
          se.scrollTop = start + 0.5;
          se.scrollTop = start;
          STATS.synthetic.scroll = (STATS.synthetic.scroll||0)+1;
        }
      } catch {}
      setTimeout(tick, nextDelay());
    };
    setTimeout(tick, nextDelay());
  };

  // one-time visibility ping
  const dispatchInitialPing = () => {
    try { document.dispatchEvent(new Event('visibilitychange')); } catch {}
    try { document.dispatchEvent(new Event('pageshow')); } catch {}
  };

  // focus/blur interceptors on window
  const installWindowFocusShims = () => {
    try {
      const no = freezeFn(function(){ /* no-op */ });
      define(window, 'focus', { value: no });
      define(window, 'blur', { value: no });
      if (CONFIG.strictMode) { harden(window,'focus'); harden(window,'blur'); }
    } catch {}
  };

  // reapply guards
  const installReapplyGuards = () => {
    const reapply = () => {
      try {
        if (document.visibilityState !== 'visible' || document.hidden !== false) forceVisibilityProps();
      } catch {}
    };
    const mo = new MutationObserver(reapply);
    try { mo.observe(document.documentElement, { childList: true, subtree: true }); } catch {}
    if (CONFIG.paranoidMode) {
      setInterval(() => {
        try {
          forceVisibilityProps();
        } catch {}
      }, 1500);
    }
  };

  // page-realm patch (mirrors key pieces) — respects config and doesn't swallow focusout
  const injectPageRealm = () => {
    if (!CONFIG.pageRealmPatch) return;
    const src = `
      (function(){
        try{
          const BLOCK_GLOBAL_BLUR  = ${JSON.stringify(!!(CONFIG.focus && CONFIG.focus.blockGlobalBlur))};
          const BLOCK_GLOBAL_EXIT  = ${JSON.stringify(!!(CONFIG.mouse && CONFIG.mouse.ignoreGlobalLeave))};
          const BLOCK_GLOBAL_ENTER = ${JSON.stringify(!!(CONFIG.mouse && CONFIG.mouse.blockGlobalEnter))};
          const ALLOW_ONE_ENTER    = ${JSON.stringify(!!(CONFIG.mouse && CONFIG.mouse.initialEnterOnce))};
          let allowOneGlobalEnter = ALLOW_ONE_ENTER;

          const define = ${define.toString()};
          const freezeFn = ${freezeFn.toString()};
          const asGlobalLike = ${asGlobalLike.toString()};
          const stableGet=(name,val)=>function(){return val;};

          const DocProto=Document.prototype;
          define(DocProto,'visibilityState',{get:stableGet('document.visibilityState','visible')})||define(document,'visibilityState',{get:stableGet('document.visibilityState','visible')});
          define(DocProto,'hidden',{get:stableGet('document.hidden',false)})||define(document,'hidden',{get:stableGet('document.hidden',false)});
          [['webkitVisibilityState','visible'],['mozVisibilityState','visible'],['msVisibilityState','visible'],
           ['webkitHidden',false],['mozHidden',false],['msHidden',false]].forEach(([k,v])=>{
            define(DocProto,k,{get:stableGet('document.'+k,v)})||define(document,k,{get:stableGet('document.'+k,v)});
          });
          const hasFocusImpl = freezeFn(function hasFocus(){return true;});
          define(DocProto,'hasFocus',{value:hasFocusImpl})||define(document,'hasFocus',{value:hasFocusImpl});

          const TYPES=['visibilitychange','webkitvisibilitychange','mozvisibilitychange','msvisibilitychange','pagehide','freeze','resume','pageshow',
                       'blur','focus','focusin','focusout',
                       'mouseleave','mouseout','pointerleave','pointerout',
                       'mouseover','mouseenter','pointerover',
                       'mousemove','pointermove'];

          const EXIT_TYPES  = new Set(['mouseleave','mouseout','pointerleave','pointerout']);
          const ENTER_TYPES = new Set(['mouseover','mouseenter','pointerover']);
          const BLUR_TYPES  = new Set(['blur']); // key fix: don't treat focusout as a global blur

          const ensureVisible=()=>{try{
            Object.defineProperty(document,'visibilityState',{get:()=> 'visible',configurable:true});
            Object.defineProperty(document,'hidden',{get:()=> false,configurable:true});
          }catch(e){}};

          const wrapHandler=(type,fn,ctx)=>freezeFn(function(event){
            ensureVisible();
            if (BLOCK_GLOBAL_BLUR && BLUR_TYPES.has(type) && asGlobalLike(ctx)) return;
            if (BLOCK_GLOBAL_EXIT && EXIT_TYPES.has(type) && asGlobalLike(ctx) && (!event || event.relatedTarget==null)) return;
            if (BLOCK_GLOBAL_ENTER && ENTER_TYPES.has(type) && asGlobalLike(ctx) && (!event || event.relatedTarget==null)) {
              if (allowOneGlobalEnter) { allowOneGlobalEnter=false; } else { return; }
            }
            try { return fn.call(this,event); } catch(e){ setTimeout(()=>{ throw e; }); }
          });

          const patchAEL=(proto)=>{
            const orig=proto.addEventListener;
            define(proto,'addEventListener',{value:function(type,listener,options){
              const t=String(type);
              if (!listener || TYPES.indexOf(t)===-1) return orig.call(this,type,listener,options);

              if (BLOCK_GLOBAL_BLUR && BLUR_TYPES.has(t) && asGlobalLike(this)) return;
              if (((EXIT_TYPES.has(t) && BLOCK_GLOBAL_EXIT) || (ENTER_TYPES.has(t) && BLOCK_GLOBAL_ENTER)) && asGlobalLike(this)) return;

              let fn=listener;
              if (typeof listener==='object' && typeof listener.handleEvent==='function'){
                fn=function(ev){ return listener.handleEvent.call(listener,ev); };
              }
              if (typeof fn!=='function') return orig.call(this,type,listener,options);

              const wrapped=wrapHandler(t,fn,this);
              try { Object.defineProperty(listener,'__visible_wrap__',{value:wrapped}); } catch {}
              return orig.call(this,type,wrapped,options);
            }});

            const origRel=proto.removeEventListener;
            define(proto,'removeEventListener',{value:function(type,listener,options){
              const l=listener && listener.__visible_wrap__ ? listener.__visible_wrap__ : listener;
              return origRel.call(this,type,l,options);
            }});
          };

          patchAEL(EventTarget.prototype);
          try{ document.dispatchEvent(new Event('visibilitychange')); }catch(e){}
          try{ document.dispatchEvent(new Event('pageshow')); }catch(e){}
        }catch(e){}
      })();
    `;
    const blob = new Blob([src], { type: 'text/javascript' });
    const url = URL.createObjectURL(blob);
    const s = document.createElement('script');
    s.src = url;
    (document.head || document.documentElement).appendChild(s);
    s.onload = () => { try{ s.remove(); URL.revokeObjectURL(url); }catch{} };
  };

  // control api (randomized key + randomized method names)
  // note: prints the mapping to the console once so you can find it.
  // if you want zero console noise, delete the console.info line.
  const exposeApi = () => {
    if (!CONFIG.exposeApi) return;

    // build logical methods first
    const logical = {
      setStrict(v){ CONFIG.strictMode = !!v; },
      setParanoid(v){ CONFIG.paranoidMode = !!v; },
      setNotify(k,v){ if (k in CONFIG.notify) CONFIG.notify[k] = v; },
      setMouse(k,v){ if (k in CONFIG.mouse) CONFIG.mouse[k] = v; },
      stats(){ return JSON.parse(JSON.stringify(STATS)); },
      reapply(){ return forceVisibilityProps(); }
    };

    // create the randomized API object
    const api = {};
    const nameMap = {}; // logicalName -> randomizedName for convenience

    for (const [logicalName, fn] of Object.entries(logical)) {
      const randName = secureRandomString(12);
      nameMap[logicalName] = randName;
      Object.defineProperty(api, randName, {
        value: freezeFn(fn),
        configurable: false,
        enumerable: false,   // hide from for..in/Object.keys
        writable: false
      });
    }

    // random API handle on window each load
    const apiKey = '__' + secureRandomString(14);
    Object.defineProperty(window, apiKey, {
      value: api,
      configurable: false,
      enumerable: false,
      writable: false
    });

    // tells you how to call it each load
    try { console.info('[AVC] api key:', `window.${apiKey}`, 'methods:', nameMap); } catch {}
  };

  // apply
  forceVisibilityProps();
  forceVisibilityEvents();
  installWindowFocusShims();
  installPointerCaptureShims();
  installTimerShims();
  installFakeMouse();
  exposeApi();
  injectPageRealm();
  document.addEventListener('DOMContentLoaded', dispatchInitialPing, { once: true });
  installReapplyGuards();
})();