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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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();
})();