scroll up and down buttons

Add up/down navigation buttons with a settings panel (timer & transparency).

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         scroll up and down buttons
// @namespace    http://tampermonkey.net/
// @version      1.6.1
// @description  Add up/down navigation buttons with a settings panel (timer & transparency).
// @author       the pie stealer
// @match        *://*/*
// @grant        none
// @license      Apache 2.0
// ==/UserScript==

(function () {
    'use strict';

    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;
    }

    let scrollAnimationId = null;
    let isTemporarilyHidden = false;

    function smoothScrollTo(targetY) {
        if (scrollAnimationId) cancelAnimationFrame(scrollAnimationId);

        const startY = window.scrollY;
        const distance = targetY - startY;
        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 });
    });

    const style = document.createElement('style');
    style.textContent = `
        .tm-scroll-container {
            position: fixed;
            bottom: 10px;
            right: 10px;
            display: flex;
            flex-direction: column;
            gap: 6px;
            z-index: 9999999;
            pointer-events: auto;
            align-items: center;
        }

        .tm-scroll-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;
        }

        .tm-settings-btn {
            width: 25px;
            height: 25px;
            font-size: 13px;
            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;
        }

        .tm-scroll-btn { background-color: #333; border-radius: 6px; }
        .tm-settings-btn { background-color: #000; border-radius: 50%; font-size: 20px; }
        .tm-scroll-btn.transparent, .tm-settings-btn.transparent { opacity: 0.1; }

        .tm-settings-panel {
            display: none;
            position: fixed;
            bottom: 110px;
            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 hr { border: none; border-top: 1px solid #555; margin: 15px 0; }
        .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 .setting-row { display: flex; justify-content: space-between; align-items: center; }
        .tm-settings-panel-content label { font-size: 14px; }
        .tm-settings-panel-content input[type="text"] {
            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;
        }

        .tm-settings-panel input::placeholder {
    color: #E6858A;
}

        .toggle-switch { position: relative; display: inline-block; width: 50px; height: 26px; }
        .toggle-switch input { opacity: 0; width: 0; height: 0; }
        .slider {
            position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
            background-color: #666; transition: .3s; border-radius: 26px;
        }
        .slider:before {
            position: absolute; content: ""; height: 18px; width: 18px; left: 4px; bottom: 4px;
            background-color: white; transition: .3s; border-radius: 50%;
        }
        input:checked + .slider { background-color: #1a73e8; }
        input:checked + .slider:before { transform: translateX(24px); }
    `;
    document.head.appendChild(style);

    const container = document.createElement('div');
    container.className = 'tm-scroll-container';

    const settingsBtn = document.createElement('button');
    settingsBtn.className = 'tm-settings-btn';
    settingsBtn.title = 'Open Settings';
    settingsBtn.innerHTML = '⚙️';

    const upBtn = document.createElement('button');
    upBtn.className = 'tm-scroll-btn';
    upBtn.textContent = '▲';
    upBtn.title = 'Scroll to top';
    upBtn.onclick = () => smoothScrollTo(0);

    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);

    container.appendChild(settingsBtn);
    container.appendChild(upBtn);
    container.appendChild(downBtn);
    document.body.appendChild(container);

    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">&times;</button>
        </div>
        <div class="tm-settings-panel-content">
            <div class="setting">
                <div class="setting-row">
                     <label for="tm-transparency-toggle">Make buttons transparent</label>
                     <label class="toggle-switch">
                         <input type="checkbox" id="tm-transparency-toggle">
                         <span class="slider"></span>
                    </label>
                </div>
            </div>
            <hr>
            <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" class="tm-hide-timer-input-text-color">
                <button id="tm-start-hide-timer">Start Timer</button>
            </div>
        </div>
    `;
    document.body.appendChild(settingsPanel);

    const closeSettingsBtn = settingsPanel.querySelector('.tm-settings-panel-close');
    const hideTimerInput = document.getElementById('tm-hide-timer-input');
    const startHideTimerBtn = document.getElementById('tm-start-hide-timer');
    const transparencyToggle = document.getElementById('tm-transparency-toggle');

    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 || parseInt(match[2], 10) >= 60 || parseInt(match[3], 10) >= 60) {
            hideTimerInput.classList.add('error');
            return;
        }

        const [ , hours, minutes, seconds] = match.map(Number);
        const totalMilliseconds = (hours * 3600 + minutes * 60 + seconds) * 1000;

        if (totalMilliseconds > 0) {
            isTemporarilyHidden = true;
            container.remove();
            settingsPanel.remove();
            style.remove();
            hideTimerInput.value = '';

            setTimeout(() => {
                isTemporarilyHidden = false;

                if (document.head) {
                    document.head.appendChild(style);
                }
                if (document.body) {
                    document.body.appendChild(container);
                    document.body.appendChild(settingsPanel);
                }

                settingsPanel.style.display = 'none';
                container.style.display = document.fullscreenElement ? 'none' : 'flex';
            }, totalMilliseconds);
        }
    });

    const STORAGE_KEY = 'tm_scroll_transparent_v1';
    const buttonsToMakeTransparent = [upBtn, downBtn, settingsBtn];

    function applyTransparencyState(state) {
        transparencyToggle.checked = state;
        buttonsToMakeTransparent.forEach(btn => {
            state ? btn.classList.add('transparent') : btn.classList.remove('transparent');
        });

        try {
            localStorage.setItem(STORAGE_KEY, state ? '1' : '0');
        } catch (e) { /* ignore storage errors */ }
    }

    transparencyToggle.addEventListener('change', () => {
        applyTransparencyState(transparencyToggle.checked);
    });

    try {
        const savedState = localStorage.getItem(STORAGE_KEY);
        applyTransparencyState(savedState === '1');
    } catch (e) { /* ignore storage errors */ }

    document.addEventListener('fullscreenchange', () => {
        container.style.display = document.fullscreenElement ? 'none' : 'flex';
    });

    const observer = new MutationObserver(() => {
        if (isTemporarilyHidden) return;

        if (!document.head || document.head.contains(style) === false) {
            if (document.head) {
                document.head.appendChild(style);
            }
        }

        if (!document.body || document.body.contains(container) === false) {
            if (document.body) {
                document.body.appendChild(container);
                document.body.appendChild(settingsPanel);
            }
        }
    });
    observer.observe(document.documentElement || document, { childList: true, subtree: true });
})();