// ==UserScript==
// @name Universal HTML5 Speed Hack (Pro Enhanced)
// @namespace https://greasyforks.org/users/1498931-kubabreak
// @version 2.1
// @description Lets you change the speed of any website. Made with ChatGPT, later enhanced by Claude.
// @author kubabreak
// @match *://*/*
// @grant none
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Configuration
const CONFIG = {
defaultSpeed: 1.0,
minSpeed: 0.001,
maxSpeed: 100.0,
step: 0.001,
uiUpdateInterval: 100,
favoritePresets: [0.1, 0.25, 0.5, 1, 2, 5, 10]
};
// Global state
let globalSpeed = CONFIG.defaultSpeed;
let uiElement = null;
let isInitialized = false;
let startTime = Date.now();
const globalState = {
perfNow: false,
dateNow: false,
setTimeout: false,
setInterval: false,
raf: false,
clearTimeouts: true,
customPresets: [],
debugLogging: false,
autoInjectFrames: true,
persistentUI: true
};
// Storage for persistence
const STORAGE_KEY = 'speedhack_settings';
function saveSettings() {
try {
const settings = {
speed: globalSpeed,
state: globalState,
uiPosition: uiElement ? {
left: uiElement.style.left,
top: uiElement.style.top
} : null
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
} catch (e) {
console.warn('[SpeedHack] Could not save settings:', e);
}
}
function loadSettings() {
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
const settings = JSON.parse(saved);
globalSpeed = settings.speed || CONFIG.defaultSpeed;
Object.assign(globalState, settings.state || {});
return settings;
}
} catch (e) {
console.warn('[SpeedHack] Could not load settings:', e);
}
return null;
}
function createInjectionScript(speed, state) {
return `
(function() {
if (window.__speedHackInjected) return;
window.__speedHackInjected = true;
const realPerfNow = performance.now.bind(performance);
const realDateNow = Date.now;
const realSetTimeout = window.setTimeout;
const realSetInterval = window.setInterval;
const realClearTimeout = window.clearTimeout;
const realClearInterval = window.clearInterval;
const realRAF = window.requestAnimationFrame;
const realCAF = window.cancelAnimationFrame;
let currentSpeed = ${speed};
let perfStartTime = realPerfNow();
let virtualStartTime = perfStartTime;
let dateStartTime = realDateNow();
let virtualDateStart = dateStartTime;
const timeoutMap = new Map();
const intervalMap = new Map();
let timeoutId = 1;
let intervalId = 1;
// Performance.now override
${state.perfNow ? `
performance.now = function() {
const realNow = realPerfNow();
const realElapsed = realNow - perfStartTime;
return virtualStartTime + (realElapsed * currentSpeed);
};
` : ''}
// Date.now override
${state.dateNow ? `
Date.now = function() {
const realNow = realPerfNow();
const realElapsed = realNow - perfStartTime;
return Math.floor(virtualDateStart + (realElapsed * currentSpeed));
};
` : ''}
// setTimeout override
${state.setTimeout ? `
window.setTimeout = function(callback, delay, ...args) {
if (typeof callback !== 'function') return realSetTimeout(callback, delay, ...args);
const adjustedDelay = Math.max(0, delay / currentSpeed);
const id = timeoutId++;
const realId = realSetTimeout(() => {
timeoutMap.delete(id);
callback.apply(this, args);
}, adjustedDelay);
timeoutMap.set(id, realId);
return id;
};
${state.clearTimeouts ? `
window.clearTimeout = function(id) {
const realId = timeoutMap.get(id);
if (realId !== undefined) {
timeoutMap.delete(id);
return realClearTimeout(realId);
}
return realClearTimeout(id);
};
` : ''}
` : ''}
// setInterval override
${state.setInterval ? `
window.setInterval = function(callback, delay, ...args) {
if (typeof callback !== 'function') return realSetInterval(callback, delay, ...args);
const adjustedDelay = Math.max(1, delay / currentSpeed);
const id = intervalId++;
const realId = realSetInterval(() => {
callback.apply(this, args);
}, adjustedDelay);
intervalMap.set(id, realId);
return id;
};
${state.clearTimeouts ? `
window.clearInterval = function(id) {
const realId = intervalMap.get(id);
if (realId !== undefined) {
intervalMap.delete(id);
return realClearInterval(realId);
}
return realClearInterval(id);
};
` : ''}
` : ''}
// requestAnimationFrame override
${state.raf ? `
window.requestAnimationFrame = function(callback) {
return realRAF((timestamp) => {
const virtualTime = performance.now();
callback(virtualTime);
});
};
` : ''}
// Update speed function
window.__updateSpeedHack = function(newSpeed, newState) {
currentSpeed = newSpeed;
// Update time references to prevent jumps
const realNow = realPerfNow();
const currentVirtualTime = virtualStartTime + ((realNow - perfStartTime) * currentSpeed);
perfStartTime = realNow;
virtualStartTime = currentVirtualTime;
const currentVirtualDate = virtualDateStart + ((realNow - perfStartTime) * currentSpeed);
virtualDateStart = currentVirtualDate;
};
${state.debugLogging ? `console.log('[SpeedHack] Injected at ' + currentSpeed + 'x speed');` : ''}
})();
`;
}
function injectIntoWindow(targetWindow, speed, state) {
try {
if (!targetWindow || !targetWindow.document) return false;
const script = targetWindow.document.createElement('script');
script.textContent = createInjectionScript(speed, state);
script.setAttribute('data-speedhack', 'true');
const target = targetWindow.document.documentElement || targetWindow.document.head || targetWindow.document.body;
if (target) {
target.appendChild(script);
return true;
}
} catch (e) {
if (globalState.debugLogging) {
console.warn('[SpeedHack] Injection failed:', e);
}
}
return false;
}
function updateAllWindows() {
// Update main window
injectIntoWindow(window, globalSpeed, globalState);
if (window.__updateSpeedHack) {
window.__updateSpeedHack(globalSpeed, globalState);
}
// Update all iframes if enabled
if (globalState.autoInjectFrames) {
const frames = document.querySelectorAll('iframe');
frames.forEach(frame => {
try {
const frameWindow = frame.contentWindow;
if (frameWindow && frameWindow.document) {
injectIntoWindow(frameWindow, globalSpeed, globalState);
if (frameWindow.__updateSpeedHack) {
frameWindow.__updateSpeedHack(globalSpeed, globalState);
}
}
} catch (e) {
// Cross-origin iframe, ignore
}
});
}
}
function createUI() {
if (uiElement) return;
const ui = document.createElement('div');
ui.id = 'speedhack-ui';
ui.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
padding: 16px;
background: linear-gradient(135deg, rgba(0,0,0,0.95), rgba(30,30,30,0.95));
color: #fff;
z-index: 2147483647;
border-radius: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 13px;
min-width: 280px;
max-width: 320px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
border: 1px solid rgba(255,255,255,0.15);
backdrop-filter: blur(20px);
user-select: none;
transition: all 0.3s ease;
`;
ui.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<strong style="color: #4CAF50; font-size: 14px;">⚡ Speed Hack Pro</strong>
<div style="display: flex; gap: 8px;">
<button id="speedhack-settings" style="background: rgba(255,255,255,0.1); border: none; color: #fff; cursor: pointer; font-size: 14px; padding: 4px 8px; border-radius: 4px; transition: background 0.2s;">⚙️</button>
<button id="speedhack-minimize" style="background: rgba(255,255,255,0.1); border: none; color: #fff; cursor: pointer; font-size: 14px; padding: 4px 8px; border-radius: 4px; transition: background 0.2s;">−</button>
</div>
</div>
<div id="speedhack-content">
<div style="margin-bottom: 12px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; font-size: 12px;">
<label style="display: flex; align-items: center; cursor: pointer; padding: 4px; border-radius: 4px; transition: background 0.2s;" onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background='transparent'">
<input type="checkbox" id="toggle-perfnow" style="margin-right: 6px; accent-color: #4CAF50;"> performance.now()
</label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 4px; border-radius: 4px; transition: background 0.2s;" onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background='transparent'">
<input type="checkbox" id="toggle-datenow" style="margin-right: 6px; accent-color: #4CAF50;"> Date.now()
</label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 4px; border-radius: 4px; transition: background 0.2s;" onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background='transparent'">
<input type="checkbox" id="toggle-settimeout" style="margin-right: 6px; accent-color: #4CAF50;"> setTimeout
</label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 4px; border-radius: 4px; transition: background 0.2s;" onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background='transparent'">
<input type="checkbox" id="toggle-setinterval" style="margin-right: 6px; accent-color: #4CAF50;"> setInterval
</label>
<label style="display: flex; align-items: center; cursor: pointer; padding: 4px; border-radius: 4px; transition: background 0.2s; grid-column: span 2;" onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background='transparent'">
<input type="checkbox" id="toggle-raf" style="margin-right: 6px; accent-color: #4CAF50;"> requestAnimationFrame
</label>
</div>
</div>
<hr style="border: none; border-top: 1px solid rgba(255,255,255,0.2); margin: 12px 0;">
<!-- Speed Control Section -->
<div style="margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-weight: 500;">Speed Control:</span>
<span id="speed-display" style="color: #4CAF50; font-weight: bold; font-size: 14px;">${globalSpeed.toFixed(3)}x</span>
</div>
<!-- Custom Speed Input -->
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
<input type="number" id="speed-input"
value="${globalSpeed}"
min="${CONFIG.minSpeed}"
max="${CONFIG.maxSpeed}"
step="any"
placeholder="Enter speed..."
style="flex: 1; padding: 6px 8px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: #fff; border-radius: 6px; font-size: 12px; outline: none; transition: all 0.2s;"
onfocus="this.style.borderColor='#4CAF50'; this.style.background='rgba(76,175,80,0.1)'"
onblur="this.style.borderColor='rgba(255,255,255,0.2)'; this.style.background='rgba(255,255,255,0.1)'">
<button id="speed-apply" style="padding: 6px 12px; background: #4CAF50; border: none; color: white; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 500; transition: background 0.2s;" onmouseover="this.style.background='#45a049'" onmouseout="this.style.background='#4CAF50'">Apply</button>
</div>
<!-- Speed Slider -->
<input type="range" id="speed-slider"
min="${Math.log(CONFIG.minSpeed)}"
max="${Math.log(CONFIG.maxSpeed)}"
value="${Math.log(globalSpeed)}"
step="0.01"
style="width: 100%; margin-bottom: 8px; accent-color: #4CAF50;">
<!-- Quick Presets -->
<div style="display: flex; gap: 4px; margin-bottom: 8px;">
<button class="speed-preset" data-speed="0.1" style="flex: 1; padding: 4px; font-size: 10px; background: #333; border: 1px solid #555; color: #fff; border-radius: 4px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='#444'" onmouseout="this.style.background='#333'">0.1x</button>
<button class="speed-preset" data-speed="0.25" style="flex: 1; padding: 4px; font-size: 10px; background: #333; border: 1px solid #555; color: #fff; border-radius: 4px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='#444'" onmouseout="this.style.background='#333'">0.25x</button>
<button class="speed-preset" data-speed="0.5" style="flex: 1; padding: 4px; font-size: 10px; background: #333; border: 1px solid #555; color: #fff; border-radius: 4px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='#444'" onmouseout="this.style.background='#333'">0.5x</button>
<button class="speed-preset" data-speed="1" style="flex: 1; padding: 4px; font-size: 10px; background: #4CAF50; border: 1px solid #4CAF50; color: #fff; border-radius: 4px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='#45a049'" onmouseout="this.style.background='#4CAF50'">1x</button>
<button class="speed-preset" data-speed="2" style="flex: 1; padding: 4px; font-size: 10px; background: #333; border: 1px solid #555; color: #fff; border-radius: 4px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='#444'" onmouseout="this.style.background='#333'">2x</button>
<button class="speed-preset" data-speed="5" style="flex: 1; padding: 4px; font-size: 10px; background: #333; border: 1px solid #555; color: #fff; border-radius: 4px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='#444'" onmouseout="this.style.background='#333'">5x</button>
<button class="speed-preset" data-speed="10" style="flex: 1; padding: 4px; font-size: 10px; background: #333; border: 1px solid #555; color: #fff; border-radius: 4px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='#444'" onmouseout="this.style.background='#333'">10x</button>
</div>
<!-- Advanced Controls -->
<div style="display: flex; gap: 4px;">
<button id="speed-save-preset" style="flex: 1; padding: 6px; font-size: 11px; background: rgba(255,193,7,0.8); border: 1px solid #FFC107; color: #000; border-radius: 4px; cursor: pointer; font-weight: 500; transition: all 0.2s;" onmouseover="this.style.background='#FFC107'" onmouseout="this.style.background='rgba(255,193,7,0.8)'">💾 Save</button>
<button id="speed-reset" style="flex: 1; padding: 6px; font-size: 11px; background: rgba(244,67,54,0.8); border: 1px solid #F44336; color: #fff; border-radius: 4px; cursor: pointer; font-weight: 500; transition: all 0.2s;" onmouseover="this.style.background='#F44336'" onmouseout="this.style.background='rgba(244,67,54,0.8)'">🔄 Reset</button>
</div>
</div>
<!-- Statistics -->
<div id="speed-stats" style="font-size: 11px; color: #aaa; text-align: center; padding: 8px; background: rgba(255,255,255,0.05); border-radius: 6px;">
<div>Active: <span id="active-functions">0</span> functions</div>
<div>Uptime: <span id="uptime">0s</span></div>
</div>
</div>
<!-- Advanced Settings Panel -->
<div id="speedhack-settings-panel" style="display: none; margin-top: 12px; padding: 12px; background: rgba(255,255,255,0.05); border-radius: 8px; border-top: 1px solid rgba(255,255,255,0.1);">
<div style="font-weight: 500; margin-bottom: 8px; color: #4CAF50;">⚙️ Advanced Settings</div>
<div style="font-size: 12px;">
<label style="display: flex; align-items: center; margin-bottom: 6px; cursor: pointer;">
<input type="checkbox" id="auto-inject-frames" checked style="margin-right: 6px; accent-color: #4CAF50;"> Auto-inject into iframes
</label>
<label style="display: flex; align-items: center; margin-bottom: 6px; cursor: pointer;">
<input type="checkbox" id="debug-logging" style="margin-right: 6px; accent-color: #4CAF50;"> Debug logging
</label>
<label style="display: flex; align-items: center; margin-bottom: 8px; cursor: pointer;">
<input type="checkbox" id="persistent-ui" checked style="margin-right: 6px; accent-color: #4CAF50;"> Remember UI position
</label>
<button id="clear-settings" style="width: 100%; padding: 6px; background: rgba(244,67,54,0.2); border: 1px solid #F44336; color: #F44336; border-radius: 4px; cursor: pointer; font-size: 11px;">Clear All Settings</button>
</div>
</div>
`;
document.body.appendChild(ui);
uiElement = ui;
// Set initial checkbox states
ui.querySelector('#toggle-perfnow').checked = globalState.perfNow;
ui.querySelector('#toggle-datenow').checked = globalState.dateNow;
ui.querySelector('#toggle-settimeout').checked = globalState.setTimeout;
ui.querySelector('#toggle-setinterval').checked = globalState.setInterval;
ui.querySelector('#toggle-raf').checked = globalState.raf;
ui.querySelector('#auto-inject-frames').checked = globalState.autoInjectFrames;
ui.querySelector('#debug-logging').checked = globalState.debugLogging;
ui.querySelector('#persistent-ui').checked = globalState.persistentUI;
// Event listeners
const speedSlider = ui.querySelector('#speed-slider');
const speedDisplay = ui.querySelector('#speed-display');
const speedInput = ui.querySelector('#speed-input');
const speedApply = ui.querySelector('#speed-apply');
// Logarithmic slider handling
function updateSpeedFromSlider() {
const logValue = parseFloat(speedSlider.value);
const actualSpeed = Math.exp(logValue);
globalSpeed = Math.round(actualSpeed * 1000) / 1000; // Round to 3 decimal places
speedDisplay.textContent = `${globalSpeed.toFixed(3)}x`;
speedInput.value = globalSpeed;
updateAllWindows();
saveSettings();
updatePresetHighlight();
}
function updateSliderFromSpeed(speed) {
speedSlider.value = Math.log(speed);
speedDisplay.textContent = `${speed.toFixed(3)}x`;
speedInput.value = speed;
updatePresetHighlight();
}
function updatePresetHighlight() {
ui.querySelectorAll('.speed-preset').forEach(btn => {
const presetSpeed = parseFloat(btn.dataset.speed);
if (Math.abs(presetSpeed - globalSpeed) < 0.001) {
btn.style.background = '#4CAF50';
btn.style.borderColor = '#4CAF50';
} else {
btn.style.background = '#333';
btn.style.borderColor = '#555';
}
});
}
// Speed input validation and application
function applyCustomSpeed() {
let inputValue = parseFloat(speedInput.value);
if (isNaN(inputValue) || inputValue <= 0) {
speedInput.value = globalSpeed;
speedInput.style.borderColor = '#F44336';
setTimeout(() => {
speedInput.style.borderColor = 'rgba(255,255,255,0.2)';
}, 1000);
return;
}
inputValue = Math.max(CONFIG.minSpeed, Math.min(CONFIG.maxSpeed, inputValue));
globalSpeed = inputValue;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
// Visual feedback
speedApply.textContent = '✓';
speedApply.style.background = '#4CAF50';
setTimeout(() => {
speedApply.textContent = 'Apply';
speedApply.style.background = '#4CAF50';
}, 500);
}
speedSlider.addEventListener('input', updateSpeedFromSlider);
speedApply.addEventListener('click', applyCustomSpeed);
speedInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
applyCustomSpeed();
speedInput.blur();
}
});
// Update statistics
function updateStats() {
const activeFunctions = Object.values(globalState).filter((val, key) =>
typeof val === 'boolean' && val && key < 5
).length;
const uptime = Math.floor((Date.now() - startTime) / 1000);
const statsElement = ui.querySelector('#active-functions');
const uptimeElement = ui.querySelector('#uptime');
if (statsElement) statsElement.textContent = activeFunctions;
if (uptimeElement) {
if (uptime < 60) {
uptimeElement.textContent = `${uptime}s`;
} else if (uptime < 3600) {
uptimeElement.textContent = `${Math.floor(uptime/60)}m ${uptime%60}s`;
} else {
uptimeElement.textContent = `${Math.floor(uptime/3600)}h ${Math.floor((uptime%3600)/60)}m`;
}
}
}
setInterval(updateStats, 1000);
updateStats();
// Checkbox listeners
ui.querySelector('#toggle-perfnow').addEventListener('change', (e) => {
globalState.perfNow = e.target.checked;
updateAllWindows();
saveSettings();
});
ui.querySelector('#toggle-datenow').addEventListener('change', (e) => {
globalState.dateNow = e.target.checked;
updateAllWindows();
saveSettings();
});
ui.querySelector('#toggle-settimeout').addEventListener('change', (e) => {
globalState.setTimeout = e.target.checked;
updateAllWindows();
saveSettings();
});
ui.querySelector('#toggle-setinterval').addEventListener('change', (e) => {
globalState.setInterval = e.target.checked;
updateAllWindows();
saveSettings();
});
ui.querySelector('#toggle-raf').addEventListener('change', (e) => {
globalState.raf = e.target.checked;
updateAllWindows();
saveSettings();
});
// Advanced settings listeners
ui.querySelector('#auto-inject-frames').addEventListener('change', (e) => {
globalState.autoInjectFrames = e.target.checked;
saveSettings();
});
ui.querySelector('#debug-logging').addEventListener('change', (e) => {
globalState.debugLogging = e.target.checked;
saveSettings();
});
ui.querySelector('#persistent-ui').addEventListener('change', (e) => {
globalState.persistentUI = e.target.checked;
saveSettings();
});
// Preset buttons with improved handling
ui.querySelectorAll('.speed-preset').forEach(button => {
button.addEventListener('click', () => {
const speed = parseFloat(button.dataset.speed);
globalSpeed = speed;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
});
});
// Advanced controls
ui.querySelector('#speed-save-preset').addEventListener('click', () => {
const presetName = prompt(`Save current speed (${globalSpeed.toFixed(3)}x) as preset:`, `${globalSpeed.toFixed(3)}x`);
if (presetName) {
if (!globalState.customPresets) globalState.customPresets = [];
globalState.customPresets.push({ name: presetName, speed: globalSpeed });
saveSettings();
// Show confirmation
const btn = ui.querySelector('#speed-save-preset');
const originalText = btn.textContent;
btn.textContent = '✓ Saved';
setTimeout(() => btn.textContent = originalText, 1000);
}
});
ui.querySelector('#speed-reset').addEventListener('click', () => {
if (confirm('Reset speed to 1.0x?')) {
globalSpeed = 1.0;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
}
});
// Settings panel toggle
ui.querySelector('#speedhack-settings').addEventListener('click', () => {
const panel = ui.querySelector('#speedhack-settings-panel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
});
// Settings panel controls
ui.querySelector('#clear-settings').addEventListener('click', () => {
if (confirm('Clear all saved settings? This will reset everything to defaults.')) {
localStorage.removeItem(STORAGE_KEY);
location.reload();
}
});
// Minimize button
ui.querySelector('#speedhack-minimize').addEventListener('click', () => {
const content = ui.querySelector('#speedhack-content');
const button = ui.querySelector('#speedhack-minimize');
const settingsPanel = ui.querySelector('#speedhack-settings-panel');
if (content.style.display === 'none') {
content.style.display = 'block';
button.textContent = '−';
ui.style.minWidth = '280px';
} else {
content.style.display = 'none';
settingsPanel.style.display = 'none';
button.textContent = '+';
ui.style.minWidth = 'auto';
ui.style.width = 'auto';
}
});
// Make draggable with improved positioning
let isDragging = false;
let dragOffset = { x: 0, y: 0 };
ui.addEventListener('mousedown', (e) => {
if (!e.target.matches('input, button, label')) {
isDragging = true;
dragOffset.x = e.clientX - ui.offsetLeft;
dragOffset.y = e.clientY - ui.offsetTop;
ui.style.cursor = 'grabbing';
ui.style.transition = 'none';
}
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
const newX = Math.max(0, Math.min(window.innerWidth - ui.offsetWidth, e.clientX - dragOffset.x));
const newY = Math.max(0, Math.min(window.innerHeight - ui.offsetHeight, e.clientY - dragOffset.y));
ui.style.left = newX + 'px';
ui.style.top = newY + 'px';
ui.style.right = 'auto';
ui.style.bottom = 'auto';
}
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
ui.style.cursor = 'default';
ui.style.transition = 'all 0.3s ease';
if (globalState.persistentUI) {
saveSettings();
}
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey) {
switch(e.code) {
case 'KeyS':
e.preventDefault();
const panel = ui.querySelector('#speedhack-settings-panel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
break;
case 'KeyR':
e.preventDefault();
globalSpeed = 1.0;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
break;
case 'KeyH':
e.preventDefault();
const content = ui.querySelector('#speedhack-content');
const button = ui.querySelector('#speedhack-minimize');
if (content.style.display === 'none') {
content.style.display = 'block';
button.textContent = '−';
} else {
content.style.display = 'none';
button.textContent = '+';
}
break;
case 'Digit1':
e.preventDefault();
globalSpeed = 0.1;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
break;
case 'Digit2':
e.preventDefault();
globalSpeed = 0.25;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
break;
case 'Digit3':
e.preventDefault();
globalSpeed = 0.5;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
break;
case 'Digit4':
e.preventDefault();
globalSpeed = 1.0;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
break;
case 'Digit5':
e.preventDefault();
globalSpeed = 2.0;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
break;
case 'Digit6':
e.preventDefault();
globalSpeed = 5.0;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
break;
case 'Digit7':
e.preventDefault();
globalSpeed = 10.0;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
break;
}
}
});
// Load saved UI position if persistent UI is enabled
const savedSettings = loadSettings();
if (globalState.persistentUI && savedSettings && savedSettings.uiPosition) {
if (savedSettings.uiPosition.left && savedSettings.uiPosition.top) {
ui.style.left = savedSettings.uiPosition.left;
ui.style.top = savedSettings.uiPosition.top;
ui.style.right = 'auto';
}
}
// Initial slider position and preset highlight
updateSliderFromSpeed(globalSpeed);
// Add mouse wheel support for speed adjustment
ui.addEventListener('wheel', (e) => {
if (e.ctrlKey) {
e.preventDefault();
const delta = e.deltaY > 0 ? 0.9 : 1.1;
globalSpeed = Math.max(CONFIG.minSpeed, Math.min(CONFIG.maxSpeed, globalSpeed * delta));
globalSpeed = Math.round(globalSpeed * 1000) / 1000;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
}
});
// Add double-click to reset speed
ui.addEventListener('dblclick', (e) => {
if (e.target === speedDisplay) {
globalSpeed = 1.0;
updateSliderFromSpeed(globalSpeed);
updateAllWindows();
saveSettings();
}
});
// Add context menu for quick actions
ui.addEventListener('contextmenu', (e) => {
e.preventDefault();
const contextMenu = document.createElement('div');
contextMenu.style.cssText = `
position: fixed;
left: ${e.clientX}px;
top: ${e.clientY}px;
background: rgba(0,0,0,0.9);
border: 1px solid rgba(255,255,255,0.2);
border-radius: 6px;
padding: 8px 0;
z-index: 2147483648;
font-size: 12px;
min-width: 150px;
backdrop-filter: blur(10px);
`;
const menuItems = [
{ text: 'Reset to 1.0x', action: () => { globalSpeed = 1.0; updateSliderFromSpeed(globalSpeed); updateAllWindows(); saveSettings(); }},
{ text: 'Toggle All Functions', action: () => {
const allEnabled = Object.values(globalState).slice(0, 5).every(v => v);
['perfNow', 'dateNow', 'setTimeout', 'setInterval', 'raf'].forEach(key => {
globalState[key] = !allEnabled;
ui.querySelector(`#toggle-${key.toLowerCase()}`).checked = !allEnabled;
});
updateAllWindows(); saveSettings();
}},
{ text: 'Copy Current Speed', action: () => {
navigator.clipboard.writeText(globalSpeed.toString());
speedDisplay.textContent = 'Copied!';
setTimeout(() => speedDisplay.textContent = `${globalSpeed.toFixed(3)}x`, 1000);
}},
{ text: 'Export Settings', action: () => {
const settings = JSON.stringify({ speed: globalSpeed, state: globalState }, null, 2);
navigator.clipboard.writeText(settings);
alert('Settings copied to clipboard!');
}}
];
menuItems.forEach(item => {
const menuItem = document.createElement('div');
menuItem.textContent = item.text;
menuItem.style.cssText = `
padding: 6px 12px;
cursor: pointer;
color: #fff;
transition: background 0.2s;
`;
menuItem.addEventListener('mouseover', () => menuItem.style.background = 'rgba(255,255,255,0.1)');
menuItem.addEventListener('mouseout', () => menuItem.style.background = 'transparent');
menuItem.addEventListener('click', () => {
item.action();
document.body.removeChild(contextMenu);
});
contextMenu.appendChild(menuItem);
});
document.body.appendChild(contextMenu);
// Remove context menu when clicking elsewhere
const removeMenu = (e) => {
if (!contextMenu.contains(e.target)) {
document.body.removeChild(contextMenu);
document.removeEventListener('click', removeMenu);
}
};
setTimeout(() => document.addEventListener('click', removeMenu), 100);
});
// Add notification system
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: ${type === 'error' ? 'rgba(244,67,54,0.9)' : 'rgba(76,175,80,0.9)'};
color: white;
padding: 8px 16px;
border-radius: 6px;
font-size: 12px;
z-index: 2147483648;
backdrop-filter: blur(10px);
animation: slideDown 0.3s ease;
`;
const style = document.createElement('style');
style.textContent = `
@keyframes slideDown {
from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
to { opacity: 1; transform: translateX(-50%) translateY(0); }
}
`;
document.head.appendChild(style);
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
document.body.removeChild(notification);
document.head.removeChild(style);
}, 3000);
}
// Add success notification for settings save/load
const originalSaveSettings = saveSettings;
window.speedHackShowNotification = showNotification;
}
function initialize() {
if (isInitialized) return;
isInitialized = true;
loadSettings();
// Initial injection
updateAllWindows();
// Create UI when DOM is ready
if (document.body) {
createUI();
} else {
const observer = new MutationObserver((mutations, obs) => {
if (document.body) {
createUI();
obs.disconnect();
}
});
observer.observe(document, { childList: true, subtree: true });
}
// Monitor for new iframes
if (globalState.autoInjectFrames) {
const frameObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const iframes = node.tagName === 'IFRAME' ? [node] : node.querySelectorAll('iframe');
iframes.forEach((iframe) => {
iframe.addEventListener('load', () => {
setTimeout(() => updateAllWindows(), 100);
});
});
}
});
});
});
frameObserver.observe(document, { childList: true, subtree: true });
}
// Add window resize handler for UI positioning
window.addEventListener('resize', () => {
if (uiElement && globalState.persistentUI) {
const rect = uiElement.getBoundingClientRect();
if (rect.right > window.innerWidth) {
uiElement.style.left = (window.innerWidth - uiElement.offsetWidth - 10) + 'px';
}
if (rect.bottom > window.innerHeight) {
uiElement.style.top = (window.innerHeight - uiElement.offsetHeight - 10) + 'px';
}
}
});
if (globalState.debugLogging) {
console.log('[SpeedHack] Pro Enhanced version initialized successfully');
}
}
// Initialize immediately if possible, otherwise wait for DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
setTimeout(initialize, 100);
}
})();