Universal Scroll Top/Bottom Arrows

Adds floating up and down arrows to the bottom right of every webpage for quick scrolling. Works in dark/light mode.

Versão de: 28/11/2025. Veja: a última versão.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Universal Scroll Top/Bottom Arrows
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Adds floating up and down arrows to the bottom right of every webpage for quick scrolling. Works in dark/light mode.
// @author       Snow2122
// @license      MIT
// @match        *://*/*
// @run-at       document-idle
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    const CONFIG = {
        size: '45px',
        color: '#ffffff',
        bgColor: '#222222',
        opacity: '0.8',
        hoverOpacity: '1.0',
        distanceFromRight: '20px',
        distanceFromBottom: '70px', // Moved up to avoid footer ads/bars
        zIndex: '2147483647'
    };

    // --- Create Container ---
    const container = document.createElement('div');
    container.id = 'tm-scroll-btn-container';

    // Apply container styles with !important
    container.style.cssText = `
        position: fixed !important;
        bottom: ${CONFIG.distanceFromBottom} !important;
        right: ${CONFIG.distanceFromRight} !important;
        z-index: ${CONFIG.zIndex} !important;
        display: flex !important;
        flex-direction: column !important;
        gap: 8px !important;
        pointer-events: none !important;
        isolation: isolate !important; /* Fixes stacking context issues */
    `;

    // --- Helper Function to Create Buttons ---
    function createButton(svgIcon, action) {
        const btn = document.createElement('div'); // Changed to div to avoid default button styles
        btn.innerHTML = svgIcon;

        // Robust styling with !important
        btn.style.cssText = `
            width: ${CONFIG.size} !important;
            height: ${CONFIG.size} !important;
            background-color: ${CONFIG.bgColor} !important;
            color: ${CONFIG.color} !important;
            border: 2px solid rgba(255, 255, 255, 0.2) !important; /* Visible border for dark mode */
            border-radius: 50% !important; /* Circle shape looks cleaner */
            cursor: pointer !important;
            display: flex !important;
            align-items: center !important;
            justify-content: center !important;
            opacity: ${CONFIG.opacity} !important;
            transition: all 0.2s ease !important;
            box-shadow: 0 4px 6px rgba(0,0,0,0.3) !important;
            pointer-events: auto !important; /* Re-enable clicks for buttons */
            user-select: none !important;
            -webkit-user-select: none !important;
        `;

        // Hover Effects (via JS since we are using inline styles)
        btn.onmouseenter = () => {
            btn.style.opacity = CONFIG.hoverOpacity;
            btn.style.transform = 'scale(1.1)';
            btn.style.backgroundColor = '#000000';
            btn.style.borderColor = 'rgba(255, 255, 255, 0.8)';
        };

        btn.onmouseleave = () => {
            btn.style.opacity = CONFIG.opacity;
            btn.style.transform = 'scale(1)';
            btn.style.backgroundColor = CONFIG.bgColor;
            btn.style.borderColor = 'rgba(255, 255, 255, 0.2)';
        };

        // Click Action
        btn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            action();
        };

        return btn;
    }

    // --- Smart Scroll Logic ---
    // Finds the element that is actually scrollable (Window vs Wrapper Div)
    function getScrollableTarget() {
        // 1. Check if the main document is scrollable and not locked
        const bodyStyle = window.getComputedStyle(document.body);
        const htmlStyle = window.getComputedStyle(document.documentElement);

        if (bodyStyle.overflowY !== 'hidden' && htmlStyle.overflowY !== 'hidden' &&
           (document.body.scrollHeight > window.innerHeight || document.documentElement.scrollHeight > window.innerHeight)) {
            return window;
        }

        // 2. If main is locked, find the largest scrollable element on screen
        let largestScrollable = null;
        let maxArea = 0;

        // Efficiently query potential scroll containers (divs, main, article)
        const elements = document.querySelectorAll('div, main, article, section');

        for (let el of elements) {
            const style = window.getComputedStyle(el);
            const isScrollable = (style.overflowY === 'auto' || style.overflowY === 'scroll');

            if (isScrollable && el.scrollHeight > el.clientHeight) {
                const rect = el.getBoundingClientRect();
                const area = rect.width * rect.height;
                // We want the largest visible scroll area
                if (area > maxArea && rect.height > 100) {
                    maxArea = area;
                    largestScrollable = el;
                }
            }
        }

        return largestScrollable || window; // Fallback to window
    }

    const scrollUp = () => {
        const target = getScrollableTarget();
        target.scrollTo({
            top: 0,
            behavior: 'smooth'
        });
    };

    const scrollDown = () => {
        const target = getScrollableTarget();

        // Calculate height based on target type
        let topVal = 0;
        if (target === window) {
            topVal = Math.max(
                document.body.scrollHeight,
                document.documentElement.scrollHeight
            );
        } else {
            topVal = target.scrollHeight;
        }

        target.scrollTo({
            top: topVal,
            behavior: 'smooth'
        });
    };

    // --- Assemble UI ---
    const svgStyle = 'width: 24px; height: 24px; stroke-width: 3;';

    const svgUp = `<svg style="${svgStyle}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>`;
    const svgDown = `<svg style="${svgStyle}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>`;

    const btnUp = createButton(svgUp, scrollUp);
    const btnDown = createButton(svgDown, scrollDown);

    container.appendChild(btnUp);
    container.appendChild(btnDown);

    // --- Inject into DOM ---
    const init = () => {
        // Prefer appending to documentElement (html) to escape body stacking contexts
        const target = document.documentElement || document.body;
        if (target) {
            target.appendChild(container);
        } else {
            setTimeout(init, 500);
        }
    };

    // Run initialization
    init();

})();