Web Speed Controller

control the speed of website timers, animations, videos, and Date.now

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         Web Speed Controller
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  control the speed of website timers, animations, videos, and Date.now
// @author       Minoa
// @match        *://*/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // ─── UI ────────────────────────────────────────────────────────────────────

    const controls = document.createElement('div');
    controls.style.cssText = `
        position: fixed;
        top: 13px;
        right: 18px;
        background: rgba(15, 23, 42, 0.25);
        padding: 4px;
        border: 1px solid rgba(255, 255, 255, 0.08);
        border-radius: 8px;
        z-index: 9999999;
        display: flex;
        gap: 4px;
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.22);
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
        align-items: center;
        transition: all 0.3s ease;
        width: 45px;
        overflow: hidden;
    `;

    const input = document.createElement('input');
    input.type = 'number';
    input.step = '1';
    input.value = '1';
    input.style.cssText = `
        width: 22px;
        height: 22px;
        background: rgba(30, 41, 59, 0.3);
        border: 1px solid rgba(148, 163, 184, 0.1);
        color: rgba(226, 232, 240, 0.6);
        border-radius: 6px;
        padding: 2px;
        font-size: 12px;
        font-weight: 500;
        text-align: center;
        outline: none;
        transition: all 0.3s ease;
        -moz-appearance: textfield;
        cursor: pointer;
    `;

    const toggleButton = document.createElement('button');
    toggleButton.textContent = 'Enable';
    toggleButton.style.cssText = `
        background: #3b82f6;
        color: #ffffff;
        border: none;
        border-radius: 8px;
        width: 90px;
        height: 36px;
        font-size: 14px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.3s ease;
        display: none;
        align-items: center;
        justify-content: center;
        padding: 8px 16px;
        white-space: nowrap;
    `;

    let isExpanded = false;
    let isEnabled = false;

    controls.addEventListener('mouseenter', () => {
        if (!isExpanded) {
            controls.style.background = 'rgba(15, 23, 42, 0.45)';
            input.style.color = 'rgba(226, 232, 240, 0.8)';
        }
    });
    controls.addEventListener('mouseleave', () => {
        if (!isExpanded) {
            controls.style.background = 'rgba(15, 23, 42, 0.25)';
            input.style.color = 'rgba(226, 232, 240, 0.6)';
        }
    });

    function expandControls() {
        if (isExpanded) return;
        controls.style.cssText += `
            width: auto;
            padding: 16px;
            background: rgba(15, 23, 42, 0.85);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border-radius: 12px;
            gap: 12px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
            border: 1px solid rgba(255, 255, 255, 0.1);
        `;
        input.style.cssText += `
            width: 70px;
            height: 36px;
            padding: 4px 8px;
            font-size: 14px;
            background: rgba(30, 41, 59, 0.8);
            border-radius: 8px;
            border: 2px solid rgba(148, 163, 184, 0.2);
            color: #e2e8f0;
        `;
        toggleButton.style.display = 'flex';
        isExpanded = true;
    }

    input.addEventListener('focus', () => {
        expandControls();
        input.style.borderColor = '#3b82f6';
        input.style.boxShadow = '0 0 0 3px rgba(59, 130, 246, 0.3)';
    });
    input.addEventListener('blur', () => {
        input.style.borderColor = 'rgba(148, 163, 184, 0.2)';
        input.style.boxShadow = 'none';
        input.value = Math.max(1, Math.round(parseFloat(input.value)) || 1);
    });
    input.addEventListener('input', () => {
        input.value = Math.round(parseFloat(input.value));
    });
    input.addEventListener('keydown', (e) => {
        const v = parseInt(input.value) || 1;
        if (e.key === 'ArrowUp') { e.preventDefault(); input.value = v + 1; if (isEnabled) applySpeed(); }
        else if (e.key === 'ArrowDown') { e.preventDefault(); input.value = Math.max(1, v - 1); if (isEnabled) applySpeed(); }
    });

    toggleButton.addEventListener('mouseover', () => {
        toggleButton.style.background = isEnabled ? '#dc2626' : '#2563eb';
        toggleButton.style.transform = 'translateY(-1px)';
    });
    toggleButton.addEventListener('mouseout', () => {
        toggleButton.style.background = isEnabled ? '#ef4444' : '#3b82f6';
        toggleButton.style.transform = 'translateY(0)';
    });
    toggleButton.addEventListener('click', () => {
        isEnabled = !isEnabled;
        toggleButton.textContent = isEnabled ? 'Disable' : 'Enable';
        toggleButton.style.background = isEnabled ? '#ef4444' : '#3b82f6';
        if (isEnabled) applySpeed();
        else restoreAll();
    });
    input.addEventListener('change', () => { if (isEnabled) applySpeed(); });

    controls.appendChild(input);
    controls.appendChild(toggleButton);
    document.body.appendChild(controls);

    // ─── Core ──────────────────────────────────────────────────────────────────

    const orig = {
        setTimeout:           window.setTimeout.bind(window),
        setInterval:          window.setInterval.bind(window),
        clearTimeout:         window.clearTimeout.bind(window),
        clearInterval:        window.clearInterval.bind(window),
        requestAnimationFrame: window.requestAnimationFrame.bind(window),
        dateNow:              Date.now.bind(Date),
        Date:                 Date,
        perfNow:              performance.now.bind(performance),
    };

    let speed = 1;
    // Wall-clock anchor for Date/performance warping
    let warpRealBase  = orig.dateNow();
    let warpVirtBase  = orig.dateNow();
    let perfRealBase  = orig.perfNow();
    let perfVirtBase  = orig.perfNow();

    // ── 1. setTimeout / setInterval ───────────────────────────────────────────
    function patchTimers() {
        window.setTimeout = (cb, delay, ...args) =>
            orig.setTimeout(cb, (delay || 0) / speed, ...args);
        window.setInterval = (cb, delay, ...args) =>
            orig.setInterval(cb, (delay || 0) / speed, ...args);
    }

    // ── 2. requestAnimationFrame ──────────────────────────────────────────────
    function patchRAF() {
        window.requestAnimationFrame = (cb) =>
            orig.requestAnimationFrame((ts) => cb(ts * speed));
    }

    // ── 3. Date.now / new Date() ──────────────────────────────────────────────
    function virtualNow() {
        const elapsed = orig.dateNow() - warpRealBase;
        return warpVirtBase + elapsed * speed;
    }

    function patchDate() {
        function FakeDate(...args) {
            if (args.length === 0) return new orig.Date(virtualNow());
            return new orig.Date(...args);
        }
        FakeDate.prototype      = orig.Date.prototype;
        FakeDate.now            = virtualNow;
        FakeDate.parse          = orig.Date.parse;
        FakeDate.UTC            = orig.Date.UTC;
        window.Date             = FakeDate;
    }

    // ── 4. performance.now() ──────────────────────────────────────────────────
    function patchPerformance() {
        const desc = Object.getOwnPropertyDescriptor(Performance.prototype, 'now');
        Object.defineProperty(performance, 'now', {
            value: function() {
                const elapsed = orig.perfNow() - perfRealBase;
                return perfVirtBase + elapsed * speed;
            },
            configurable: true,
            writable: true,
        });
    }

    // ── 5. Videos / audio (<video> and <audio>) ───────────────────────────────
    function applyMediaSpeed() {
        document.querySelectorAll('video, audio').forEach(el => {
            el.playbackRate = speed;
        });
    }

    // Watch for new media elements added after patch
    let mediaObserver = null;
    function watchNewMedia() {
        if (mediaObserver) return;
        mediaObserver = new MutationObserver((mutations) => {
            if (!isEnabled) return;
            for (const m of mutations) {
                m.addedNodes.forEach(node => {
                    if (node.nodeName === 'VIDEO' || node.nodeName === 'AUDIO') {
                        node.playbackRate = speed;
                    }
                    if (node.querySelectorAll) {
                        node.querySelectorAll('video, audio').forEach(el => {
                            el.playbackRate = speed;
                        });
                    }
                });
            }
        });
        mediaObserver.observe(document.documentElement, { childList: true, subtree: true });
    }

    // Also patch the play() method so any newly-played element gets the right rate
    const origPlay = HTMLMediaElement.prototype.play;
    HTMLMediaElement.prototype.play = function() {
        if (isEnabled) this.playbackRate = speed;
        return origPlay.call(this);
    };

    // ── 6. CSS / Web Animations ───────────────────────────────────────────────
    function applyAnimationSpeed() {
        // Web Animations API — covers CSS animations & transitions that are
        // represented as Animation objects (Chrome/Firefox/Edge)
        if (document.getAnimations) {
            document.getAnimations().forEach(anim => {
                anim.playbackRate = speed;
            });
        }

        // document.timeline.currentTime is read-only in most browsers, but
        // individual Animation.playbackRate is the right lever above.
        // As a fallback, inject a <style> that overrides animation/transition
        // durations on every element via a CSS custom property trick.
        applyAnimationCSS();
    }

    let animStyleEl = null;
    function applyAnimationCSS() {
        if (!animStyleEl) {
            animStyleEl = document.createElement('style');
            animStyleEl.id = '__wsc_anim__';
            document.head.appendChild(animStyleEl);
        }
        // Divide all declared durations by the speed factor.
        // This targets elements that don't use the Web Animations API.
        animStyleEl.textContent = `
            *, *::before, *::after {
                animation-duration:        calc(var(--wsc-dur, 1s) / ${speed}) !important;
                animation-delay:           calc(var(--wsc-del, 0s) / ${speed}) !important;
                transition-duration:       calc(var(--wsc-tdur, 0s) / ${speed}) !important;
                transition-delay:          calc(var(--wsc-tdel, 0s) / ${speed}) !important;
            }
        `;
        // Note: because most sites set literal values (not --wsc-* vars) this
        // override is imperfect for CSS-defined durations, but the Web Animations
        // playbackRate above covers the vast majority of animated content.
    }

    function removeAnimationCSS() {
        if (animStyleEl) {
            animStyleEl.textContent = '';
        }
    }

    // Re-apply Web Animations playbackRate on new animations (MutationObserver
    // doesn't catch new Animation objects, but we can poll lightly)
    let animPoller = null;
    function startAnimPoller() {
        if (animPoller) return;
        animPoller = orig.setInterval(() => {
            if (!isEnabled || !document.getAnimations) return;
            document.getAnimations().forEach(anim => {
                if (anim.playbackRate !== speed) anim.playbackRate = speed;
            });
        }, 200);
    }
    function stopAnimPoller() {
        if (animPoller) { orig.clearInterval(animPoller); animPoller = null; }
    }

    // ─── Apply / Restore ───────────────────────────────────────────────────────

    function applySpeed() {
        speed = Math.max(1, parseInt(input.value) || 1);

        // Reset time warp anchors so there's no jump
        warpRealBase = orig.dateNow();
        warpVirtBase = orig.dateNow();
        perfRealBase = orig.perfNow();
        perfVirtBase = orig.perfNow();

        patchTimers();
        patchRAF();
        patchDate();
        patchPerformance();
        applyMediaSpeed();
        watchNewMedia();
        applyAnimationSpeed();
        startAnimPoller();
    }

    function restoreAll() {
        window.setTimeout           = orig.setTimeout;
        window.setInterval          = orig.setInterval;
        window.requestAnimationFrame = orig.requestAnimationFrame;
        window.Date                 = orig.Date;
        Object.defineProperty(performance, 'now', {
            value: orig.perfNow, configurable: true, writable: true
        });

        // Restore media
        document.querySelectorAll('video, audio').forEach(el => {
            el.playbackRate = 1;
        });

        // Restore animations
        if (document.getAnimations) {
            document.getAnimations().forEach(anim => { anim.playbackRate = 1; });
        }
        removeAnimationCSS();
        stopAnimPoller();

        if (mediaObserver) { mediaObserver.disconnect(); mediaObserver = null; }

        speed = 1;
    }

})();