// ==UserScript==
// @name scroll up and down buttons with settings
// @namespace http://tampermonkey.net/
// @version 1.4.0
// @description Adds up/down buttons, transparency toggle, and a settings panel with a hide timer.
// @author You
// @match *://*/*
// @grant none
// @license Apache 2.0
// ==/UserScript==
(function () {
'use strict';
// --- Initial checks ---
if (window.top !== window.self) return;
if (document.body && document.body.children.length === 1) {
const onlyChild = document.body.children[0];
const tag = onlyChild.tagName.toLowerCase();
if (['img', 'video', 'audio', 'embed', 'object'].includes(tag)) return;
}
// --- Smooth Scroll Logic (Unchanged) ---
let scrollAnimationId = null;
function smoothScrollTo(targetY) {
if (scrollAnimationId) cancelAnimationFrame(scrollAnimationId);
const startY = window.scrollY;
const distance = targetY - startY;
if (distance === 0) return;
const duration = 1000;
const startTime = performance.now();
function step(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = progress < 0.5 ? 2 * progress * progress : -1 + (4 - 2 * progress) * progress;
window.scrollTo(0, startY + distance * ease);
if (progress < 1) {
scrollAnimationId = requestAnimationFrame(step);
} else {
scrollAnimationId = null;
}
}
scrollAnimationId = requestAnimationFrame(step);
}
function stopScroll() {
if (scrollAnimationId) {
cancelAnimationFrame(scrollAnimationId);
scrollAnimationId = null;
}
}
['mousedown', 'wheel', 'touchstart', 'keydown'].forEach(evt => {
window.addEventListener(evt, stopScroll, { passive: true });
});
// --- Styles (Updated for new layout and settings panel) ---
const style = document.createElement('style');
style.textContent = `
/* Main container for the 2x2 grid */
.tm-scroll-container {
position: fixed;
bottom: 10px;
right: 10px;
display: grid;
grid-template-columns: auto auto;
gap: 6px;
z-index: 9999999;
pointer-events: auto;
align-items: center;
justify-items: center;
}
/* Unified button styles for a clean grid */
.tm-scroll-btn, .tm-toggle-btn, .tm-settings-btn {
width: 40px;
height: 40px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: none;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
transition: background-color 0.2s, opacity 0.2s;
color: white;
font-size: 16px;
}
/* Specific styles for each button type */
.tm-scroll-btn { background-color: #333; border-radius: 6px; }
.tm-toggle-btn { background-color: #555; border-radius: 50%; }
.tm-settings-btn { background-color: #000; border-radius: 50%; font-size: 20px; }
/* Transparency and active states */
.tm-scroll-btn.transparent { opacity: 0.1; }
.tm-toggle-btn.active { background-color: #1a73e8; }
/* NEW Settings Panel */
.tm-settings-panel {
display: none; /* Hidden by default */
position: fixed;
bottom: 110px; /* Positioned above the buttons */
right: 10px;
width: 250px;
background-color: #3a3a3a;
color: #f0f0f0;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
z-index: 10000000;
border: 1px solid #555;
}
.tm-settings-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
font-size: 16px;
font-weight: bold;
}
.tm-settings-panel-close {
background: none; border: none; color: #f0f0f0; font-size: 24px;
cursor: pointer; line-height: 1; padding: 0 5px;
}
.tm-settings-panel-content .setting { display: flex; flex-direction: column; gap: 8px; }
.tm-settings-panel-content label { font-size: 14px; }
.tm-settings-panel-content input {
padding: 8px; border-radius: 4px; border: 1px solid #777;
background-color: #2c2c2c; color: white; font-family: monospace;
}
.tm-settings-panel-content input.error { border-color: #e57373; }
.tm-settings-panel-content button {
margin-top: 5px; padding: 8px 12px; border-radius: 4px; border: none;
cursor: pointer; background-color: #1a73e8; color: white; font-weight: bold;
}
`;
document.head.appendChild(style);
// --- Main Container ---
const container = document.createElement('div');
container.className = 'tm-scroll-container';
// --- Create All Buttons ---
const toggleBtn = document.createElement('button');
toggleBtn.className = 'tm-toggle-btn';
toggleBtn.title = 'Toggle button transparency';
toggleBtn.setAttribute('aria-pressed', 'false');
toggleBtn.textContent = '◐';
const upBtn = document.createElement('button');
upBtn.className = 'tm-scroll-btn';
upBtn.textContent = '▲';
upBtn.title = 'Scroll to top';
upBtn.onclick = () => smoothScrollTo(0);
const settingsBtn = document.createElement('button');
settingsBtn.className = 'tm-settings-btn';
settingsBtn.title = 'Open Settings';
settingsBtn.innerHTML = '⚙️'; // Gear icon for settings
const downBtn = document.createElement('button');
downBtn.className = 'tm-scroll-btn';
downBtn.textContent = '▼';
downBtn.title = 'Scroll to bottom';
downBtn.onclick = () => smoothScrollTo(document.documentElement.scrollHeight || document.body.scrollHeight);
// Append buttons to container in grid order (top-left, top-right, bottom-left, bottom-right)
container.appendChild(toggleBtn);
container.appendChild(upBtn);
container.appendChild(settingsBtn);
container.appendChild(downBtn);
document.body.appendChild(container);
// --- NEW Settings Panel HTML ---
const settingsPanel = document.createElement('div');
settingsPanel.className = 'tm-settings-panel';
settingsPanel.innerHTML = `
<div class="tm-settings-panel-header">
<span>Settings</span>
<button class="tm-settings-panel-close" title="Close">×</button>
</div>
<div class="tm-settings-panel-content">
<div class="setting">
<label for="tm-hide-timer-input">Hide buttons temporarily</label>
<input type="text" id="tm-hide-timer-input" placeholder="HH-MM-SS">
<button id="tm-start-hide-timer">Start Timer</button>
</div>
</div>
`;
document.body.appendChild(settingsPanel);
// --- NEW Settings Panel Logic ---
const hideTimerInput = document.getElementById('tm-hide-timer-input');
const startHideTimerBtn = document.getElementById('tm-start-hide-timer');
const closeSettingsBtn = settingsPanel.querySelector('.tm-settings-panel-close');
settingsBtn.addEventListener('click', () => {
settingsPanel.style.display = settingsPanel.style.display === 'block' ? 'none' : 'block';
});
closeSettingsBtn.addEventListener('click', () => {
settingsPanel.style.display = 'none';
});
startHideTimerBtn.addEventListener('click', () => {
const timeValue = hideTimerInput.value;
const timePattern = /^(\d+)-(\d{1,2})-(\d{1,2})$/;
const match = timeValue.match(timePattern);
hideTimerInput.classList.remove('error');
if (!match) {
hideTimerInput.classList.add('error');
return;
}
const hours = parseInt(match[1], 10);
const minutes = parseInt(match[2], 10);
const seconds = parseInt(match[3], 10);
if (minutes >= 60 || seconds >= 60) {
hideTimerInput.classList.add('error');
return;
}
const totalMilliseconds = (hours * 3600 + minutes * 60 + seconds) * 1000;
if (totalMilliseconds > 0) {
container.style.display = 'none';
settingsPanel.style.display = 'none';
hideTimerInput.value = '';
setTimeout(() => {
if (!document.fullscreenElement) {
container.style.display = 'grid';
}
}, totalMilliseconds);
}
});
// --- Transparency Logic (Original behavior on scroll buttons only) ---
const STORAGE_KEY = 'tm_scroll_transparent_v1';
const buttonsToMakeTransparent = [upBtn, downBtn];
function applyTransparencyState(state) {
if (state) {
buttonsToMakeTransparent.forEach(btn => btn.classList.add('transparent'));
toggleBtn.classList.add('active');
toggleBtn.setAttribute('aria-pressed', 'true');
toggleBtn.textContent = '◑';
} else {
buttonsToMakeTransparent.forEach(btn => btn.classList.remove('transparent'));
toggleBtn.classList.remove('active');
toggleBtn.setAttribute('aria-pressed', 'false');
toggleBtn.textContent = '◐';
}
try {
localStorage.setItem(STORAGE_KEY, state ? '1' : '0');
} catch (e) { /* ignore storage errors */ }
}
let saved = null;
try { saved = localStorage.getItem(STORAGE_KEY); } catch (e) { saved = null; }
applyTransparencyState(saved === '1');
toggleBtn.addEventListener('click', (e) => {
const isCurrentlyTransparent = upBtn.classList.contains('transparent');
applyTransparencyState(!isCurrentlyTransparent);
e.stopPropagation();
});
// --- Fullscreen and Observer Logic (Updated to use 'grid') ---
document.addEventListener('fullscreenchange', () => {
if (container.style.display !== 'none') {
container.style.display = document.fullscreenElement ? 'none' : 'grid';
}
});
const observer = new MutationObserver(() => {
if (!document.body || !document.body.contains(container)) {
if (document.body) {
document.body.appendChild(container);
document.body.appendChild(settingsPanel);
}
}
});
observer.observe(document.documentElement || document, { childList: true, subtree: true });
})();