// ==UserScript==
// @name Web Speed Controller
// @namespace http://tampermonkey.net/
// @version 1.9
// @description control the speed of website timers, animations, and videos without changing video playback rate
// @author Minoa
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Create UI elements
const controls = document.createElement('div');
controls.style.cssText = `
position: fixed;
top: 13px;
right: 18px;
background: rgba(15, 23, 42, 0.8);
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.8);
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;
`;
// Remove spinner arrows
input.style.cssText += `
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
`;
const toggleButton = document.createElement('button');
toggleButton.textContent = '▶';
toggleButton.style.cssText = `
background: rgba(59, 130, 246, 0.5);
color: rgba(255, 255, 255, 0.6);
border: none;
border-radius: 6px;
width: 20px;
height: 20px;
font-size: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: none;
align-items: center;
justify-content: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
white-space: nowrap;
padding: 0;
`;
let isExpanded = false;
let isEnabled = false;
// Hover effect for small state
controls.addEventListener('mouseenter', () => {
if (!isExpanded) {
controls.style.background = 'rgba(15, 23, 42, 0.45)';
input.style.background = 'rgba(30, 41, 59, 0.5)';
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.background = 'rgba(30, 41, 59, 0.3)';
input.style.color = 'rgba(226, 232, 240, 0.6)';
}
});
function expandControls() {
if (!isExpanded) {
controls.style.width = 'auto';
controls.style.padding = '16px';
controls.style.background = 'rgba(15, 23, 42, 0.85)';
controls.style.backdropFilter = 'blur(10px)';
controls.style.webkitBackdropFilter = 'blur(10px)';
controls.style.borderRadius = '12px';
controls.style.gap = '12px';
controls.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.2)';
controls.style.border = '1px solid rgba(255, 255, 255, 0.1)';
input.style.width = '70px';
input.style.height = '36px';
input.style.padding = '4px 8px';
input.style.fontSize = '14px';
input.style.background = 'rgba(30, 41, 59, 0.8)';
input.style.borderRadius = '8px';
input.style.border = '2px solid rgba(148, 163, 184, 0.2)';
input.style.color = '#e2e8f0';
toggleButton.style.display = 'flex';
toggleButton.style.width = '90px';
toggleButton.style.height = '36px';
toggleButton.style.padding = '8px 16px';
toggleButton.style.fontSize = '14px';
toggleButton.style.borderRadius = '8px';
toggleButton.textContent = 'Enable';
toggleButton.style.background = '#3b82f6';
toggleButton.style.color = '#ffffff';
isExpanded = true;
}
}
function adjustInputWidth() {
if (isExpanded) {
const span = document.createElement('span');
span.style.cssText = `
position: absolute;
top: -9999px;
font: ${window.getComputedStyle(input).font};
padding: ${window.getComputedStyle(input).padding};
`;
span.textContent = input.value;
document.body.appendChild(span);
const newWidth = Math.max(70, span.offsetWidth + 24);
input.style.width = `${newWidth}px`;
document.body.removeChild(span);
}
}
// Expand on input focus
input.addEventListener('focus', () => {
expandControls();
input.style.borderColor = '#3b82f6';
input.style.boxShadow = '0 0 0 3px rgba(59, 130, 246, 0.3)';
});
input.addEventListener('blur', () => {
if (isExpanded) {
input.style.borderColor = 'rgba(148, 163, 184, 0.2)';
input.style.boxShadow = 'none';
}
input.value = Math.round(input.value) || 1;
});
// Handle input changes
input.addEventListener('input', () => {
input.value = Math.round(input.value);
adjustInputWidth();
});
// Keyboard navigation
input.addEventListener('keydown', (e) => {
const currentValue = parseInt(input.value) || 1;
if (e.key === 'ArrowUp') {
e.preventDefault();
const newValue = currentValue + 1;
input.value = newValue;
adjustInputWidth();
if (isEnabled) updateSpeed();
} else if (e.key === 'ArrowDown') {
e.preventDefault();
const newValue = Math.max(1, currentValue - 1);
input.value = newValue;
adjustInputWidth();
if (isEnabled) updateSpeed();
}
});
// Button styling
toggleButton.addEventListener('mouseover', () => {
if (isExpanded) {
toggleButton.style.background = isEnabled ? '#dc2626' : '#2563eb';
toggleButton.style.transform = 'translateY(-1px)';
}
});
toggleButton.addEventListener('mouseout', () => {
if (isExpanded) {
toggleButton.style.background = isEnabled ? '#ef4444' : '#3b82f6';
toggleButton.style.transform = 'translateY(0)';
}
});
// Store original timing functions
const original = {
setTimeout: window.setTimeout.bind(window),
setInterval: window.setInterval.bind(window),
requestAnimationFrame: window.requestAnimationFrame.bind(window),
dateNow: Date.now.bind(Date),
originalDate: Date,
// Store original media element methods
mediaElementCurrentTime: Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'currentTime'),
mediaElementPlaybackRate: Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate'),
mediaElementPlay: HTMLMediaElement.prototype.play,
mediaElementPause: HTMLMediaElement.prototype.pause
};
let speedMultiplier = 1;
let startTime = original.dateNow();
let modifiedTime = startTime;
function updateSpeed() {
speedMultiplier = parseInt(input.value) || 1;
modifiedTime = original.dateNow();
startTime = original.dateNow();
applySpeedMultiplier();
}
function applySpeedMultiplier() {
window.setTimeout = function(callback, delay, ...args) {
return original.setTimeout(callback, delay / speedMultiplier, ...args);
};
window.setInterval = function(callback, delay, ...args) {
return original.setInterval(callback, delay / speedMultiplier, ...args);
};
window.requestAnimationFrame = function(callback) {
return original.requestAnimationFrame((timestamp) => {
const adjustedTimestamp = timestamp * speedMultiplier;
callback(adjustedTimestamp);
});
};
function TimeWarpDate(...args) {
if (args.length === 0) {
const now = original.dateNow();
const timePassed = now - startTime;
const adjustedTime = modifiedTime + (timePassed * speedMultiplier);
return new original.originalDate(adjustedTime);
}
return new original.originalDate(...args);
}
TimeWarpDate.prototype = original.originalDate.prototype;
TimeWarpDate.now = function() {
const now = original.dateNow();
const timePassed = now - startTime;
return modifiedTime + (timePassed * speedMultiplier);
};
TimeWarpDate.parse = original.originalDate.parse;
TimeWarpDate.UTC = original.originalDate.UTC;
window.Date = TimeWarpDate;
// Override HTMLMediaElement methods to control video timing
const mediaElements = {};
const mediaStartTimes = {};
// Override currentTime getter and setter
Object.defineProperty(HTMLMediaElement.prototype, 'currentTime', {
get: function() {
const originalGet = original.mediaElementCurrentTime.get;
const actualTime = originalGet.call(this);
return actualTime;
},
set: function(value) {
const originalSet = original.mediaElementCurrentTime.set;
const id = this.dataset.speedControllerId || (this.dataset.speedControllerId = Math.random().toString(36).substr(2, 9));
mediaElements[id] = this;
// Store the start time if not already stored
if (!mediaStartTimes[id]) {
mediaStartTimes[id] = {
realTime: original.dateNow(),
mediaTime: value
};
}
originalSet.call(this, value);
},
configurable: true
});
// Override play method
HTMLMediaElement.prototype.play = function() {
const id = this.dataset.speedControllerId || (this.dataset.speedControllerId = Math.random().toString(36).substr(2, 9));
mediaElements[id] = this;
// Update start time when play is called
mediaStartTimes[id] = {
realTime: original.dateNow(),
mediaTime: this.currentTime
};
return original.mediaElementPlay.call(this);
};
// Override pause method
HTMLMediaElement.prototype.pause = function() {
return original.mediaElementPause.call(this);
};
// Create a function to update all media elements
function updateMediaElements() {
for (const id in mediaElements) {
const element = mediaElements[id];
if (!element.paused && mediaStartTimes[id]) {
const originalGet = original.mediaElementCurrentTime.get;
const originalSet = original.mediaElementCurrentTime.set;
const currentRealTime = original.dateNow();
const realTimePassed = currentRealTime - mediaStartTimes[id].realTime;
const adjustedTimePassed = realTimePassed * speedMultiplier;
const newMediaTime = mediaStartTimes[id].mediaTime + (adjustedTimePassed / 1000);
// Only update if the difference is significant
const currentMediaTime = originalGet.call(element);
if (Math.abs(newMediaTime - currentMediaTime) > 0.1) {
originalSet.call(element, newMediaTime);
// Update start time to prevent drift
mediaStartTimes[id] = {
realTime: currentRealTime,
mediaTime: newMediaTime
};
}
}
}
// Continue updating media elements
if (speedMultiplier !== 1) {
original.setTimeout(updateMediaElements, 100);
}
}
// Start updating media elements
if (speedMultiplier !== 1) {
updateMediaElements();
}
}
function restoreOriginal() {
window.setTimeout = original.setTimeout;
window.setInterval = original.setInterval;
window.requestAnimationFrame = original.requestAnimationFrame;
window.Date = original.originalDate;
modifiedTime = original.dateNow();
startTime = original.dateNow();
// Restore HTMLMediaElement methods
Object.defineProperty(HTMLMediaElement.prototype, 'currentTime', original.mediaElementCurrentTime);
HTMLMediaElement.prototype.play = original.mediaElementPlay;
HTMLMediaElement.prototype.pause = original.mediaElementPause;
}
input.addEventListener('change', () => {
if (isEnabled) {
updateSpeed();
}
});
toggleButton.addEventListener('click', () => {
isEnabled = !isEnabled;
toggleButton.textContent = isEnabled ? 'Disable' : 'Enable';
toggleButton.style.background = isEnabled ? '#ef4444' : '#3b82f6';
if (isEnabled) {
updateSpeed();
} else {
restoreOriginal();
}
});
controls.appendChild(input);
controls.appendChild(toggleButton);
document.body.appendChild(controls);
})();