Tap button to open speed menu – works on Safari iOS
// ==UserScript==
// @name TimerHooker Mobile (Safari Fixed)
// @namespace http://tampermonkey.net/
// @version 1.5.0
// @description Tap button to open speed menu – works on Safari iOS
// @match *://*/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
console.log('[TimerHooker] Script started');
// ---- Speed control ----
let speed = 1.0;
let originalSetTimeout = window.setTimeout;
let originalSetInterval = window.setInterval;
let originalDateNow = Date.now;
// Simple hook – adjust delays
window.setTimeout = function(cb, delay, ...args) {
let newDelay = delay / speed;
if (newDelay < 0) newDelay = 0;
return originalSetTimeout(cb, newDelay, ...args);
};
window.setInterval = function(cb, delay, ...args) {
let newDelay = delay / speed;
if (newDelay < 1) newDelay = 1;
return originalSetInterval(cb, newDelay, ...args);
};
// Hook Date.now
let offset = 0;
let lastReal = Date.now();
let lastFake = lastReal;
setInterval(() => {
let now = originalDateNow();
let delta = (now - lastReal) * speed;
lastFake += delta;
lastReal = now;
offset = lastFake - now;
}, 50);
Date.now = () => originalDateNow() + offset;
function setSpeed(mult) {
speed = Math.min(16, Math.max(0.05, mult));
// Update videos
document.querySelectorAll('video, audio').forEach(v => v.playbackRate = speed);
// Update UI
let mainBtn = document.querySelector('._th-click-hover');
if (mainBtn) mainBtn.innerText = 'x' + speed.toFixed(2);
// Show popup
let popup = document.querySelector('._th_cover-all-show-times');
if (popup) {
popup.querySelector('._th_times').innerText = speed.toFixed(2) + 'x';
popup.classList.remove('_th_hidden');
setTimeout(() => popup.classList.add('_th_hidden'), 800);
}
console.log('[TimerHooker] Speed set to', speed);
}
// ---- Create UI ----
function createUI() {
if (document.querySelector('._th-container')) return;
const style = document.createElement('style');
style.textContent = `
._th-container {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 999999;
font-family: system-ui;
}
._th-click-hover {
width: 55px;
height: 55px;
background: aquamarine;
border-radius: 50%;
text-align: center;
line-height: 55px;
font-weight: bold;
font-size: 16px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
cursor: pointer;
transition: 0.1s;
}
._th-click-hover:active {
transform: scale(0.96);
}
._th-menu-items {
display: none;
flex-direction: column;
gap: 10px;
margin-top: 10px;
align-items: center;
}
._th-container._th-open ._th-menu-items {
display: flex;
}
._th-item {
width: 50px;
height: 50px;
background: rgba(127,255,212,0.95);
border-radius: 50%;
text-align: center;
line-height: 50px;
font-weight: bold;
font-size: 18px;
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
cursor: pointer;
}
._th-item:active {
transform: scale(0.96);
}
._th-item._item-10x { background: #ffaa66; }
._th-item._item-reset { background: #ff8888; }
._th_cover-all-show-times {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.3);
display: flex;
align-items: center;
justify-content: center;
z-index: 999999;
pointer-events: none;
transition: opacity 0.2s;
}
._th_cover-all-show-times._th_hidden {
opacity: 0;
z-index: -1;
}
._th_times {
background: aquamarine;
padding: 25px 45px;
border-radius: 20px;
font-size: 48px;
font-weight: bold;
box-shadow: 0 0 20px black;
}
`;
document.head.appendChild(style);
const html = `
<div class="_th-container">
<div class="_th-click-hover">x1.00</div>
<div class="_th-menu-items">
<div class="_th-item _item-x2">2x</div>
<div class="_th-item _item-x-2">½x</div>
<div class="_th-item _item-xx2">4x</div>
<div class="_th-item _item-xx-2">¼x</div>
<div class="_th-item _item-10x">10x</div>
<div class="_th-item _item-reset">1x</div>
</div>
</div>
<div class="_th_cover-all-show-times _th_hidden">
<div class="_th_times"></div>
</div>
`;
const div = document.createElement('div');
div.innerHTML = html;
document.body.appendChild(div);
const container = div.querySelector('._th-container');
const mainBtn = div.querySelector('._th-click-hover');
const menuItems = div.querySelectorAll('._th-item');
// Toggle menu on main button click
mainBtn.addEventListener('click', (e) => {
e.stopPropagation();
container.classList.toggle('_th-open');
console.log('[TimerHooker] Menu toggled, open:', container.classList.contains('_th-open'));
});
// Speed buttons
const speedMap = {
'_item-x2': 2,
'_item-x-2': 0.5,
'_item-xx2': 4,
'_item-xx-2': 0.25,
'_item-10x': 10,
'_item-reset': 1
};
menuItems.forEach(item => {
const className = Array.from(item.classList).find(c => speedMap[c]);
if (className) {
item.addEventListener('click', (e) => {
e.stopPropagation();
setSpeed(speedMap[className]);
container.classList.remove('_th-open'); // close menu after selection
});
}
});
// Close menu when tapping outside
document.addEventListener('click', (e) => {
if (!container.contains(e.target) && container.classList.contains('_th-open')) {
container.classList.remove('_th-open');
}
});
}
// Wait for body to exist
if (document.body) {
createUI();
setSpeed(1);
} else {
document.addEventListener('DOMContentLoaded', () => {
createUI();
setSpeed(1);
});
}
// Watch for new videos
const obs = new MutationObserver(() => {
document.querySelectorAll('video, audio').forEach(v => v.playbackRate = speed);
});
obs.observe(document.documentElement, { childList: true, subtree: true });
})();