surfer

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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

})();