surfer

Vibrant colours, 240 FPS cap, Unity quality boost, custom tab icon/title, and bloat removal for the Subway Surfers Unity page.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         surfer
// @namespace    https://greasyfork.org/
// @version      1.4
// @description  Vibrant colours, 240 FPS cap, Unity quality boost, custom tab icon/title, and bloat removal for the Subway Surfers Unity page.
// @author       You
// @match        https://yell0wsuit.page/assets/games/subway-surfers-unity/index.html
// @match        *://*/*
// @grant        none
// @run-at       document-star
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  /* ─────────────────────────────────────────────
     1. VIBRANT COLORS  (applies to every matched page)
  ───────────────────────────────────────────────*/
  const SATURATION  = 1.30;
  const CONTRAST    = 1.08;
  const BRIGHTNESS  = 1.02;

  function injectColorFilter () {
    const style = document.createElement('style');
    style.id = 'ys-vibrant-filter';
    style.textContent = `
      html {
        filter: saturate(${SATURATION}) contrast(${CONTRAST}) brightness(${BRIGHTNESS}) !important;
      }
      ::-webkit-scrollbar { filter: none !important; }
    `;
    (document.head || document.documentElement).appendChild(style);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', injectColorFilter, { once: true });
  } else {
    injectColorFilter();
  }

  /* ─────────────────────────────────────────────
     SUBWAY SURFERS ONLY BELOW
  ───────────────────────────────────────────────*/
  const TARGET_PATH  = '/assets/games/subway-surfers-unity/index.html';
  const FPS_CAP      = 240;
  const FRAME_MIN_MS = 1000 / FPS_CAP;

  const CUSTOM_ICON  = 'https://purepng.com/public/uploads/large/purepng.com-moonmoonskyrealisticnightblack-and-white-591521584248xjtz1.png';
  const CUSTOM_TITLE = 'surf';

  const isSubwaySurfers =
    location.hostname === 'yell0wsuit.page' &&
    location.pathname  === TARGET_PATH;

  if (!isSubwaySurfers) return;

  /* ─────────────────────────────────────────────
     2. CUSTOM TAB ICON
     Removes all existing favicons and injects ours.
     A MutationObserver fights back if the page tries
     to restore its own icon.
  ───────────────────────────────────────────────*/
  function setFavicon () {
    // Nuke every existing favicon link
    document.querySelectorAll('link[rel*="icon"]').forEach(el => el.remove());

    const link = document.createElement('link');
    link.rel   = 'icon';
    link.type  = 'image/png';
    link.href  = CUSTOM_ICON;
    link.id    = 'ys-custom-favicon';
    document.head.appendChild(link);
  }

  /* ─────────────────────────────────────────────
     3. CUSTOM TAB TITLE
     Overwrites document.title and keeps it locked
     via a property descriptor + MutationObserver.
  ───────────────────────────────────────────────*/
  function lockTitle () {
    // Override the title property so any JS assignment is intercepted
    Object.defineProperty(document, 'title', {
      get () { return CUSTOM_TITLE; },
      set ()  { /* swallow any attempt to change it */ },
      configurable: true,
    });
    document.querySelector('title') && (document.querySelector('title').textContent = CUSTOM_TITLE);
  }

  /* ─────────────────────────────────────────────
     4. MUTATION OBSERVER — guard title & favicon
     The page or Unity loader may insert new <title>
     or <link rel="icon"> nodes after boot.
  ───────────────────────────────────────────────*/
  function installHeadGuard () {
    setFavicon();
    lockTitle();

    const observer = new MutationObserver(mutations => {
      for (const m of mutations) {
        for (const node of m.addedNodes) {
          if (node.nodeType !== 1) continue;
          // Kill any injected favicon that isn't ours
          if (node.tagName === 'LINK' && node.rel && node.rel.includes('icon') && node.id !== 'ys-custom-favicon') {
            node.remove();
            setFavicon(); // re-ensure ours is present
          }
          // Kill any injected <title>
          if (node.tagName === 'TITLE') {
            node.textContent = CUSTOM_TITLE;
          }
        }
        // If characterData changed on an existing title node
        if (m.type === 'characterData' && m.target.parentElement && m.target.parentElement.tagName === 'TITLE') {
          m.target.textContent = CUSTOM_TITLE;
        }
      }
    });

    observer.observe(document.head || document.documentElement, {
      childList:     true,
      subtree:       true,
      characterData: true,
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', installHeadGuard, { once: true });
  } else {
    installHeadGuard();
  }

  /* ─────────────────────────────────────────────
     5. BLOAT REMOVAL
     Kills things that waste CPU/memory/network on
     a game page where none of them are needed:
       • Google Analytics / Tag Manager
       • Facebook Pixel
       • Twitter/X widgets
       • Hotjar, Mixpanel, Segment, Amplitude
       • Intercom, Zendesk, Drift chat widgets
       • Push-notification prompts
       • Autoplaying background music/video (non-canvas)
       • Cookie consent banners
       • Any third-party ad scripts
  ───────────────────────────────────────────────*/

  // Block list — script src substrings to kill before they load
  const BLOCKED_SCRIPT_PATTERNS = [
    'google-analytics', 'googletagmanager', 'gtag',
    'facebook.net', 'connect.facebook',
    'platform.twitter', 'widgets.js',
    'hotjar', 'mixpanel', 'segment.io', 'segment.com', 'amplitude',
    'intercom', 'zendesk', 'drift.com',
    'onesignal', 'pushcrew', 'pushwoosh',
    'cookiebot', 'cookieconsent', 'onetrust', 'trustarc',
    'doubleclick', 'googlesyndication', 'adsbygoogle',
    'disqus', 'livechat', 'tawk.to',
  ];

  // Intercept script injection via document.createElement
  const _origCreateElement = document.createElement.bind(document);
  document.createElement = function (tag, opts) {
    const el = _origCreateElement(tag, opts);
    if (tag.toLowerCase() === 'script') {
      // Watch for src assignment and block if it matches
      let _src = '';
      Object.defineProperty(el, 'src', {
        get () { return _src; },
        set (val) {
          if (BLOCKED_SCRIPT_PATTERNS.some(p => val.includes(p))) {
            console.info('[Enhancer] Blocked bloat script:', val);
            return; // drop it
          }
          _src = val;
          el.setAttribute('src', val);
        },
        configurable: true,
      });
    }
    return el;
  };

  // Also nuke any already-present bloat scripts / iframes on DOMContentLoaded
  function removeBloatNodes () {
    // Scripts
    document.querySelectorAll('script[src]').forEach(s => {
      if (BLOCKED_SCRIPT_PATTERNS.some(p => s.src.includes(p))) {
        s.remove();
        console.info('[Enhancer] Removed bloat script:', s.src);
      }
    });

    // Iframes (ads, chat widgets, consent modals)
    document.querySelectorAll('iframe').forEach(f => {
      if (BLOCKED_SCRIPT_PATTERNS.some(p => (f.src || '').includes(p))) {
        f.remove();
      }
    });

    // Cookie / GDPR banners — common class/id patterns
    const bannerSelectors = [
      '#cookie-banner', '#cookie-notice', '#cookie-law-info-bar',
      '#cookieconsent', '.cookieconsent', '.cookie-banner', '.cookie-notice',
      '#onetrust-banner-sdk', '#onetrust-consent-sdk',
      '#CybotCookiebotDialog', '.cc-window',
      '#gdpr-cookie-notice', '.gdpr-banner',
    ];
    bannerSelectors.forEach(sel => {
      document.querySelectorAll(sel).forEach(el => el.remove());
    });

    // Mute / pause any <audio> or <video> elements that aren't in a canvas context
    document.querySelectorAll('audio, video').forEach(media => {
      if (!media.closest('canvas')) {
        media.pause();
        media.muted = true;
        media.src   = '';
      }
    });
  }

  // Run on DOM ready + again after full load (some widgets inject late)
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', removeBloatNodes, { once: true });
  } else {
    removeBloatNodes();
  }
  window.addEventListener('load', removeBloatNodes);

  // Continuously prune late-injected bloat nodes
  const bloatObserver = new MutationObserver(() => {
    document.querySelectorAll('script[src]').forEach(s => {
      if (BLOCKED_SCRIPT_PATTERNS.some(p => s.src.includes(p))) s.remove();
    });
    document.querySelectorAll('iframe').forEach(f => {
      if (BLOCKED_SCRIPT_PATTERNS.some(p => (f.src || '').includes(p))) f.remove();
    });
  });

  bloatObserver.observe(document.documentElement, { childList: true, subtree: true });

  /* ─────────────────────────────────────────────
     6. 240 FPS CAP
  ───────────────────────────────────────────────*/
  const _raf = window.requestAnimationFrame.bind(window);
  let lastTime = 0;

  window.requestAnimationFrame = function (callback) {
    return _raf(function (now) {
      if (now - lastTime >= FRAME_MIN_MS) {
        lastTime = now;
        callback(now);
      } else {
        window.requestAnimationFrame(callback);
      }
    });
  };

  /* ─────────────────────────────────────────────
     7. UNITY QUALITY + CANVAS BOOST
  ───────────────────────────────────────────────*/
  function applyUnityQuality (instance) {
    try { instance.SendMessage('Main Camera', 'SetQualityLevel', '5'); } catch (_) {}
    try { instance.Module.ccall('SetQualityLevel', null, ['number'], [5]); } catch (_) {}
  }

  let _unityInstance = null;
  Object.defineProperty(window, 'unityInstance', {
    configurable: true,
    get () { return _unityInstance; },
    set (v) {
      _unityInstance = v;
      setTimeout(() => applyUnityQuality(v), 3000);
    }
  });

  function patchCreateUnity (fn) {
    return async function (...args) {
      if (args[1] && typeof args[1] === 'object') {
        args[1].devicePixelRatio = window.devicePixelRatio || 1;
      }
      const inst = await fn.apply(this, args);
      setTimeout(() => applyUnityQuality(inst), 3000);
      return inst;
    };
  }

  if (typeof window.createUnityInstance === 'function') {
    window.createUnityInstance = patchCreateUnity(window.createUnityInstance);
  } else {
    Object.defineProperty(window, 'createUnityInstance', {
      configurable: true,
      get ()  { return window._ys_createUnity; },
      set (v) { window._ys_createUnity = patchCreateUnity(v); }
    });
  }

  function boostCanvasQuality () {
    document.querySelectorAll('canvas').forEach(canvas => {
      const dpr = window.devicePixelRatio || 1;
      if (dpr > 1 && canvas.width && canvas.height) {
        canvas.width  = canvas.clientWidth  * dpr;
        canvas.height = canvas.clientHeight * dpr;
        const ctx = canvas.getContext('2d');
        if (ctx) ctx.imageSmoothingEnabled = false;
      }
    });
  }

  window.addEventListener('load', () => {
    boostCanvasQuality();
    setTimeout(boostCanvasQuality, 5000);
  });

  /* ─────────────────────────────────────────────
     8. SUBTLE MOTION BLUR ON A / D KEYS
     Applies a very light CSS blur + horizontal
     translate to <html> while A or D is held,
     then smoothly eases back out on key-up.
     Values are intentionally tiny so it feels
     like a camera speed-line rather than lag.
  ───────────────────────────────────────────────*/
  (function setupMotionBlur () {
    const BLUR_PX    = 1.4;   // max blur radius in px  — keep ≤ 2
    const SHIFT_PX   = 3;     // max horizontal nudge in px
    const EASE_IN_MS = 80;    // ms to ramp up
    const EASE_OUT_MS= 220;   // ms to ease back (longer = smoother)

    let blurVal  = 0;
    let shiftVal = 0;
    let direction = 0;        // -1 = left (A), +1 = right (D)
    let rafId    = null;
    let target   = 0;         // 0 = off, 1 = on

    const keysHeld = { a: false, d: false };

    function applyFilter () {
      // Direction: A nudges left (negative), D nudges right (positive)
      const targetBlur  = target * BLUR_PX;
      const targetShift = target * SHIFT_PX * direction;
      const speed = target ? (1000 / 60) / EASE_IN_MS : (1000 / 60) / EASE_OUT_MS;

      blurVal  += (targetBlur  - blurVal)  * speed;
      shiftVal += (targetShift - shiftVal) * speed;

      const atRest = Math.abs(blurVal) < 0.01 && Math.abs(shiftVal) < 0.01;

      if (atRest && !target) {
        // Fully settled — remove filter to save GPU
        document.documentElement.style.setProperty('--ys-motion-blur', '');
        document.documentElement.style.setProperty('--ys-motion-shift', '');
        // Restore base filter without motion extras
        const styleEl = document.getElementById('ys-vibrant-filter');
        if (styleEl) {
          styleEl.textContent = styleEl.textContent.replace(/\s*blur\([^)]*\)/g, '');
        }
        rafId = null;
        return;
      }

      // Apply as an extra blur on top of the existing html filter
      // We inject a <style> override so it stacks cleanly
      let extra = document.getElementById('ys-motion-style');
      if (!extra) {
        extra = document.createElement('style');
        extra.id = 'ys-motion-style';
        document.head.appendChild(extra);
      }
      extra.textContent = `
        html {
          filter: saturate(${SATURATION}) contrast(${CONTRAST}) brightness(${BRIGHTNESS})
                  blur(${Math.max(0, blurVal).toFixed(3)}px) !important;
          transform: translateX(${shiftVal.toFixed(2)}px) !important;
          transition: transform 0ms !important;
        }
      `;

      rafId = requestAnimationFrame(applyFilter);
    }

    function startBlur (dir) {
      direction = dir;
      target    = 1;
      if (!rafId) rafId = requestAnimationFrame(applyFilter);
    }

    function stopBlur () {
      if (!keysHeld.a && !keysHeld.d) {
        target = 0;
        if (!rafId) rafId = requestAnimationFrame(applyFilter);
      }
    }

    window.addEventListener('keydown', e => {
      const key = e.key.toLowerCase();
      if (key === 'a' && !keysHeld.a) { keysHeld.a = true;  startBlur(-1); }
      if (key === 'd' && !keysHeld.d) { keysHeld.d = true;  startBlur(+1); }
    });

    window.addEventListener('keyup', e => {
      const key = e.key.toLowerCase();
      if (key === 'a') { keysHeld.a = false; stopBlur(); }
      if (key === 'd') { keysHeld.d = false; stopBlur(); }
    });

    // Safety: clear on focus loss so blur doesn't get stuck
    window.addEventListener('blur', () => {
      keysHeld.a = keysHeld.d = false;
      stopBlur();
    });
  })();

  /* ─────────────────────────────────────────────
     9. STARTUP BADGE
  ───────────────────────────────────────────────*/
  window.addEventListener('load', () => {
    const badge = document.createElement('div');
    badge.textContent = '⚡ surf · 240 FPS · Vibrant · Ultra · Bloat-free · Motion blur';
    Object.assign(badge.style, {
      position:      'fixed',
      bottom:        '12px',
      right:         '12px',
      zIndex:        '99999',
      background:    'rgba(0,0,0,0.75)',
      color:         '#a8ff78',
      fontFamily:    'monospace',
      fontSize:      '12px',
      padding:       '6px 12px',
      borderRadius:  '6px',
      pointerEvents: 'none',
      transition:    'opacity 1s ease',
      opacity:       '1',
    });
    document.body.appendChild(badge);
    setTimeout(() => { badge.style.opacity = '0'; }, 3500);
    setTimeout(() => { badge.remove(); },             4600);
  });

  console.info('[Enhancer v1.4] surf mode — FPS: 240 · Quality: Ultra · Bloat: blocked · Motion blur: A/D keys');

})();