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

})();