Universal Auto-Scroll (Modified)

Adds auto-scrolling functionality to any website with true dynamic UI scaling and display sleep prevention.

// ==UserScript==
// @name         Universal Auto-Scroll (Modified)
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Adds auto-scrolling functionality to any website with true dynamic UI scaling and display sleep prevention.
// @author       MasuRii
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Wait a bit for page to be fully ready
    setTimeout(initAutoScroll, 1000);

    function initAutoScroll() {
        // ===================================================================
        // *** USER CUSTOMIZATION: CHANGE THIS VALUE TO RESIZE THE UI PANEL ***
        // 1.0 = default size. 0.8 = 80% size (smaller). 1.2 = 120% size (larger).
        const uiScaleFactor = 0.8;
        // ===================================================================

        // ===================================================================
        // *** USER CUSTOMIZATION: SCROLLING SPEED VALUES ***
        // These are the speed multipliers for the 9 pre-defined speed levels on the slider.
        // These values will be displayed as read-only in the panel.
        const speedMultipliers = [
            0.15, // Speed 1: Snail
            0.25, // Speed 2: Turtle
            0.35, // Speed 3: Leisurely
            0.38, // Speed 4: Relaxed
            0.5,  // Speed 5: Very Slow
            0.75, // Speed 6: Slow
            1.0,  // Speed 7: Medium (Default)
            1.5,  // Speed 8: Fast
            2.5   // Speed 9: Very Fast
        ];
        // ===================================================================

        // Configuration
        const DEFAULT_SETTINGS = {
            isActive: false,
            speed: 7, // Default is "Medium" on the new 1-10 scale
            isMinimized: false,
            pixelsPerSecond: 50,  // Base scrolling speed - will be modified by speed setting
            userSpeedMultiplier: 5.0 // Default value for the user-defined speed level
        };

        // State variables
        let isScrolling = false;
        let scrollIntervalId = null;
        let userScrollDetected = false;
        let userScrollTimeout = null;
        let lastScrollPosition = window.scrollY;
        let wakeLockSentinel = null; // For preventing display sleep

        // Load saved settings or use defaults
        let scrollSettings;
        try {
            const saved = JSON.parse(GM_getValue('autoScrollSettings', '{}'));
            scrollSettings = { ...DEFAULT_SETTINGS, ...saved }; // Merge saved settings with defaults
        } catch (e) {
            console.log('Could not load settings, using defaults');
            scrollSettings = DEFAULT_SETTINGS;
        }

        // DOM elements references
        let container;
        let autoScrollPanel;
        let bubbleView;
        let toggleButton;
        let speedSlider;
        let speedValue;
        let speedValueInput; // NEW: For the speed multiplier input box

        // Adjust panel size using CSS transform to counteract browser zoom
        function adjustPanelForZoom() {
            if (!window.visualViewport || !container) return;
            const browserZoom = window.visualViewport.scale;
            const finalScale = uiScaleFactor / browserZoom;
            container.style.transform = `scale(${finalScale})`;
            container.style.transformOrigin = 'bottom right';
        }

        // Create and inject the UI
        function createUI() {
            const containerId = 'auto-scroll-container-' + Math.random().toString(36).substr(2, 9);
            container = document.createElement('div');
            container.id = containerId;
            document.body.appendChild(container);

            const styleElement = document.createElement('style');
            styleElement.textContent = `
                #${containerId} {
                    all: initial; position: fixed; bottom: 80px; right: 20px; width: 220px; height: auto; z-index: 9999999;
                }
                #${containerId} * {
                    all: unset; box-sizing: border-box; font-family: Arial, sans-serif; color: white;
                }
                #${containerId} .auto-scroll-panel, #${containerId} .auto-scroll-bubble {
                    position: absolute; bottom: 0; right: 0;
                }
                #${containerId} .auto-scroll-panel {
                    background-color: rgba(0, 0, 0, 0.8); padding: 15px; border-radius: 8px; width: 220px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
                    display: ${scrollSettings.isMinimized ? 'none' : 'block'};
                }
                #${containerId} .panel-header {
                    display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.2); padding-bottom: 8px;
                }
                #${containerId} .panel-title { font-weight: bold; font-size: 16px; display: block; }
                #${containerId} .minimize-button { background: none; border: none; cursor: pointer; font-size: 20px; padding: 0; margin: 0; display: block; }
                #${containerId} .section { margin-bottom: 12px; }
                #${containerId} .section-title { font-size: 14px; color: #ffcc00; margin-bottom: 8px; display: block; }
                #${containerId} .toggle-button {
                    background-color: #2196F3; border: none; padding: 10px 15px; text-align: center; font-size: 14px; border-radius: 6px; width: 100%; cursor: pointer;
                    display: flex; justify-content: center; align-items: center; gap: 8px; margin-bottom: 10px;
                }
                #${containerId} .toggle-button.active { background-color: #4CAF50; }
                #${containerId} .slider-container { width: 100%; display: flex; flex-direction: column; gap: 8px; }
                #${containerId} .slider-top { display: flex; justify-content: space-between; width: 100%; font-size: 14px; }
                #${containerId} .speed-value { font-weight: bold; }
                #${containerId} .speed-slider {
                    width: 100%; height: 5px; -webkit-appearance: none; appearance: none; background: #444; outline: none; border-radius: 3px; display: block;
                }
                #${containerId} .speed-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 15px; height: 15px; border-radius: 50%; background: #ffcc00; cursor: pointer; }
                #${containerId} .speed-slider::-moz-range-thumb { width: 15px; height: 15px; border-radius: 50%; background: #ffcc00; cursor: pointer; }
                #${containerId} .slider-labels { display: flex; justify-content: space-between; width: 100%; font-size: 12px; }
                #${containerId} .auto-scroll-bubble {
                    width: 60px; height: 60px; border-radius: 50%; background-color: rgba(0, 0, 0, 0.8); display: ${scrollSettings.isMinimized ? 'flex' : 'none'};
                    flex-direction: column; justify-content: center; align-items: center; cursor: pointer; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); font-size: 24px;
                }
                /* NEW STYLES for speed value input */
                #${containerId} .speed-value-container { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 14px; }
                #${containerId} .speed-value-input {
                    width: 60px; background-color: #333; border: 1px solid #555; border-radius: 4px; padding: 4px 8px; text-align: center; font-size: 14px;
                }
                #${containerId} .speed-value-input:disabled { background-color: #222; color: #888; cursor: not-allowed; }
            `;
            document.head.appendChild(styleElement);

            autoScrollPanel = document.createElement('div');
            autoScrollPanel.className = 'auto-scroll-panel';
            container.appendChild(autoScrollPanel);

            // ... (Panel Header and Status Section are unchanged)
            const panelHeader = document.createElement('div');
            panelHeader.className = 'panel-header';
            const panelTitle = document.createElement('div');
            panelTitle.className = 'panel-title';
            panelTitle.textContent = 'Auto-Scroll Controls';
            const minimizeButton = document.createElement('button');
            minimizeButton.className = 'minimize-button';
            minimizeButton.textContent = '−';
            minimizeButton.addEventListener('click', toggleMinimize);
            panelHeader.appendChild(panelTitle);
            panelHeader.appendChild(minimizeButton);
            autoScrollPanel.appendChild(panelHeader);

            const statusSection = document.createElement('div');
            statusSection.className = 'section';
            const statusTitle = document.createElement('div');
            statusTitle.className = 'section-title';
            statusTitle.textContent = 'Scrolling Status';
            toggleButton = document.createElement('button');
            toggleButton.className = 'toggle-button' + (scrollSettings.isActive ? ' active' : '');
            toggleButton.innerHTML = scrollSettings.isActive ? '<span>⏸️</span> Pause Scrolling' : '<span>▶️</span> Start Scrolling';
            toggleButton.addEventListener('click', toggleScrolling);
            statusSection.appendChild(statusTitle);
            statusSection.appendChild(toggleButton);
            autoScrollPanel.appendChild(statusSection);


            // --- Speed Section (Modified) ---
            const speedSection = document.createElement('div');
            speedSection.className = 'section';
            const speedTitle = document.createElement('div');
            speedTitle.className = 'section-title';
            speedTitle.textContent = 'Scroll Speed';
            const sliderContainer = document.createElement('div');
            sliderContainer.className = 'slider-container';
            const sliderTop = document.createElement('div');
            sliderTop.className = 'slider-top';
            const speedLabel = document.createElement('span');
            speedLabel.textContent = 'Speed:';
            speedValue = document.createElement('span');
            speedValue.className = 'speed-value';
            sliderTop.appendChild(speedLabel);
            sliderTop.appendChild(speedValue);
            speedSlider = document.createElement('input');
            speedSlider.type = 'range';
            speedSlider.className = 'speed-slider';
            speedSlider.min = '1';
            speedSlider.max = '10'; // Increased max to 10 for custom speed
            speedSlider.value = scrollSettings.speed;
            speedSlider.addEventListener('input', handleSpeedChange);
            const sliderLabels = document.createElement('div');
            sliderLabels.className = 'slider-labels';
            const slowLabel = document.createElement('span');
            slowLabel.textContent = 'Slow';
            const fastLabel = document.createElement('span');
            fastLabel.textContent = 'Fast';
            sliderLabels.appendChild(slowLabel);
            sliderLabels.appendChild(fastLabel);
            sliderContainer.appendChild(sliderTop);
            sliderContainer.appendChild(speedSlider);
            sliderContainer.appendChild(sliderLabels);
            speedSection.appendChild(speedTitle);
            speedSection.appendChild(sliderContainer);

            // --- NEW: Speed Value Input Section ---
            const speedValueContainer = document.createElement('div');
            speedValueContainer.className = 'speed-value-container';
            const speedValueLabel = document.createElement('span');
            speedValueLabel.textContent = 'Speed Multiplier:';
            speedValueInput = document.createElement('input');
            speedValueInput.type = 'number';
            speedValueInput.className = 'speed-value-input';
            speedValueInput.step = '0.1';
            speedValueInput.min = '0.1';
            speedValueInput.addEventListener('input', handleUserSpeedInputChange);
            speedValueContainer.appendChild(speedValueLabel);
            speedValueContainer.appendChild(speedValueInput);
            speedSection.appendChild(speedValueContainer);

            autoScrollPanel.appendChild(speedSection);

            // ... (Bubble View is unchanged)
            bubbleView = document.createElement('div');
            bubbleView.className = 'auto-scroll-bubble';
            bubbleView.innerHTML = '📜';
            bubbleView.addEventListener('click', toggleMinimize);
            container.appendChild(bubbleView);

            // --- Initial UI State Setup ---
            updateSpeedDisplay(scrollSettings.speed);
            updateSpeedValueInputState(scrollSettings.speed);
            if (scrollSettings.isActive) {
                startScrolling();
            }
        }

        function toggleMinimize() {
            scrollSettings.isMinimized = !scrollSettings.isMinimized;
            autoScrollPanel.style.display = scrollSettings.isMinimized ? 'none' : 'block';
            bubbleView.style.display = scrollSettings.isMinimized ? 'flex' : 'none';
            saveSettings();
        }

        function toggleScrolling() {
            scrollSettings.isActive = !scrollSettings.isActive;
            if (scrollSettings.isActive) {
                toggleButton.classList.add('active');
                toggleButton.innerHTML = '<span>⏸️</span> Pause Scrolling';
                startScrolling();
            } else {
                toggleButton.classList.remove('active');
                toggleButton.innerHTML = '<span>▶️</span> Start Scrolling';
                stopScrolling();
            }
            saveSettings();
        }

        function handleSpeedChange() {
            const value = parseInt(speedSlider.value);
            scrollSettings.speed = value;
            updateSpeedDisplay(value);
            updateSpeedValueInputState(value); // NEW: Update the input box state
            if (isScrolling) {
                stopScrolling();
                startScrolling();
            }
            saveSettings();
        }

        // NEW: Handle user input in the custom speed box
        function handleUserSpeedInputChange() {
            const newSpeed = parseFloat(speedValueInput.value);
            if (!isNaN(newSpeed) && newSpeed > 0) {
                scrollSettings.userSpeedMultiplier = newSpeed;
                saveSettings();
                if (isScrolling) {
                    stopScrolling();
                    startScrolling();
                }
            }
        }

        function updateSpeedDisplay(value) {
            let speedText;
            if (value === 1) speedText = "Snail";
            else if (value === 2) speedText = "Turtle";
            else if (value === 3) speedText = "Leisurely";
            else if (value === 4) speedText = "Relaxed";
            else if (value === 5) speedText = "Very Slow";
            else if (value === 6) speedText = "Slow";
            else if (value === 7) speedText = "Medium";
            else if (value === 8) speedText = "Fast";
            else if (value === 9) speedText = "Very Fast";
            else speedText = "Custom"; // Level 10
            speedValue.textContent = `${speedText} (${value})`;
        }

        // NEW: Update the state (value and disabled status) of the speed multiplier input box
        function updateSpeedValueInputState(value) {
            if (value < 10) {
                speedValueInput.value = speedMultipliers[value - 1];
                speedValueInput.disabled = true;
            } else { // value is 10 (Custom)
                speedValueInput.value = scrollSettings.userSpeedMultiplier;
                speedValueInput.disabled = false;
            }
        }

        async function requestWakeLock() {
            if ('wakeLock' in navigator && wakeLockSentinel === null) {
                try {
                    wakeLockSentinel = await navigator.wakeLock.request('screen');
                    wakeLockSentinel.addEventListener('release', () => { wakeLockSentinel = null; });
                } catch (err) {
                    console.error(`Auto-Scroll Wake Lock Error: ${err.name}, ${err.message}`);
                }
            }
        }

        async function releaseWakeLock() {
            if (wakeLockSentinel !== null) {
                await wakeLockSentinel.release();
                wakeLockSentinel = null;
            }
        }

        function startScrolling() {
            if (isScrolling) return;
            isScrolling = true;
            lastScrollPosition = window.scrollY;
            requestWakeLock();

            let currentMultiplier;
            if (scrollSettings.speed < 10) {
                currentMultiplier = speedMultipliers[scrollSettings.speed - 1];
            } else {
                currentMultiplier = scrollSettings.userSpeedMultiplier;
            }

            const pixelsPerSecond = DEFAULT_SETTINGS.pixelsPerSecond * currentMultiplier;
            const scrollDelay = Math.floor(1000 / pixelsPerSecond);
            scrollIntervalId = setInterval(() => {
                if (!userScrollDetected) {
                    window.scrollBy(0, 1);
                }
            }, scrollDelay);
        }

        function stopScrolling() {
            if (!isScrolling) return;
            isScrolling = false;
            releaseWakeLock();
            if (scrollIntervalId !== null) {
                clearInterval(scrollIntervalId);
                scrollIntervalId = null;
            }
        }

        function saveSettings() {
            try {
                GM_setValue('autoScrollSettings', JSON.stringify(scrollSettings));
            } catch (e) {
                console.error('Failed to save settings:', e);
            }
        }

        function handleUserScroll() {
            const currentPosition = window.scrollY;
            if (isScrolling && Math.abs(currentPosition - lastScrollPosition) > 3) {
                userScrollDetected = true;
                if (userScrollTimeout) clearTimeout(userScrollTimeout);
                userScrollTimeout = setTimeout(() => {
                    userScrollDetected = false;
                    lastScrollPosition = window.scrollY;
                }, 5000);
            }
            lastScrollPosition = currentPosition;
        }

        function handleVisibilityChange() {
            if (document.visibilityState === 'visible' && isScrolling) {
                requestWakeLock();
            }
        }

        if (document.body && document.body.scrollHeight > window.innerHeight) {
            createUI();
            if (window.visualViewport) {
                adjustPanelForZoom(); // Initial adjustment
                window.visualViewport.addEventListener('resize', adjustPanelForZoom);
            }
            window.addEventListener('scroll', handleUserScroll, { passive: true });
            document.addEventListener('visibilitychange', handleVisibilityChange);
            window.addEventListener('beforeunload', () => {
                stopScrolling();
                if (window.visualViewport) {
                    window.visualViewport.removeEventListener('resize', adjustPanelForZoom);
                }
                document.removeEventListener('visibilitychange', handleVisibilityChange);
            });
        }
    }
})();