LiveReload

Randomized auto-refresh

À partir de 2026-02-14. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         LiveReload
// @namespace    https://github.com/RustwuIf
// @version      2.2.8
// @description  Randomized auto-refresh
// @author       Rustwulf
// @match        <all_urls>
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';
 
    function LiveReload() {
        // ==================== STORAGE KEYS ====================
        // Keys for persisting user preferences and statistics to localStorage
        const storageKey = 'autoRefreshState';
        const themeKey = 'autoRefreshTheme';
        const opacityKey = 'autoRefreshOpacity';
        const statsKey = 'autoRefreshStats';
        const defaults = { min: 5, max: 15, opacity: 1 };
 
        // ==================== STATE VARIABLES ====================
        // Core runtime state for the auto-reload functionality
        let isRunning = false;
        let isPausedBySafety = false;
        let isPausedByInactivity = false;
        let remainingTime = 0;
        let maxRemainingTime = 0;
        let timerId = null;
        let sessionTimerId = null;
        let inactivityTimeout = null;
 
        // ==================== CONFIGURATION ====================
        // Thresholds and defaults for inactivity detection
        const INACTIVITY_THRESHOLD = 60000; // 60 seconds
        const originalFavicon = document.querySelector("link[rel~='icon']")?.href || '/favicon.ico';
 
        // ==================== STATISTICS OBJECT ====================
        // Tracks usage metrics and session data
        let stats = {
            totalReloads: 0,
            totalRunningTime: 0,
            waitTimes: [],
            maxReloadsLimit: 0,
            jitterMode: false,
            sessionStartTime: null
        };
 
        // ==================== STORAGE FUNCTIONS ====================
        // Load and save statistics from/to localStorage with error handling
        const loadStats = () => {
            try {
                const stored = localStorage.getItem(statsKey);
                if (stored) stats = { ...stats, ...JSON.parse(stored) };
            } catch (e) {
                console.error('Stats load error:', e);
            }
        };
 
        const saveStats = () => {
            try {
                localStorage.setItem(statsKey, JSON.stringify(stats));
            } catch (e) {
                console.error('Stats save error:', e);
            }
        };
 
        // ==================== SETTINGS FUNCTIONS ====================
        // Retrieve user settings from localStorage with fallback to defaults
        const getSettings = () => ({
            min: parseInt(localStorage.getItem('minDelay'), 10) || defaults.min,
            max: parseInt(localStorage.getItem('maxDelay'), 10) || defaults.max,
            opacity: localStorage.getItem(opacityKey) || defaults.opacity
        });
 
        // ==================== FAVICON UPDATE ====================
        // Updates favicon with countdown timer and status indicator
        // Shows red circle when time is critical (≤3s), green when waiting, blue when paused
        const updateFavicon = (number) => {
            const canvas = document.createElement('canvas');
            canvas.width = canvas.height = 32;
            const ctx = canvas.getContext('2d');
            const img = new Image();
            img.crossOrigin = "Anonymous";
            img.src = originalFavicon;
            
            img.onload = () => {
                ctx.drawImage(img, 0, 0, 32, 32);
                ctx.beginPath();
                ctx.arc(20, 20, 12, 0, 2 * Math.PI);
                ctx.fillStyle = isPausedBySafety ? '#3498db' : (number <= 3 ? '#ff5f6d' : '#2ecc71');
                ctx.fill();
                ctx.fillStyle = "white";
                ctx.font = "bold 16px Arial";
                ctx.textAlign = "center";
                ctx.fillText(isPausedBySafety ? "!!" : number, 20, 26);
                
                let link = document.querySelector("link[rel~='icon']") || (() => {
                    const l = document.createElement('link');
                    l.rel = 'icon';
                    document.head.appendChild(l);
                    return l;
                })();
                link.href = canvas.toDataURL('image/png');
            };
        };
 
        // ==================== NOTIFICATION SYSTEM ====================
        // Displays temporary toast notifications for user feedback (jitter applied, limits reached, saved settings)
        const showToast = (message) => {
            const toast = document.createElement('div');
            toast.style.cssText = 'position: fixed; bottom: 100px; left: 20px; background: rgba(52, 152, 219, 0.95); color: white; padding: 16px 24px; border-radius: 12px; font-weight: 600; z-index: 2147483646; box-shadow: 0 8px 25px rgba(0,0,0,0.3); backdrop-filter: blur(10px); animation: slideInUp 0.3s ease-out;';
            toast.textContent = message;
            document.body.appendChild(toast);
            setTimeout(() => toast.remove(), 2500);
        };
 
        // ==================== RELOAD COUNTER FUNCTIONS ====================
        // Manages reload count in localStorage and stats, prevents exceeding max reload limit
        const getReloadCount = () => parseInt(localStorage.getItem('reloadCounter')) || 0;
        const incrementReloadCount = () => {
            const newCount = getReloadCount() + 1;
            localStorage.setItem('reloadCounter', newCount.toString());
            stats.totalReloads = newCount;
            saveStats();
            return newCount;
        };
        const resetReloadCount = () => {
            localStorage.setItem('reloadCounter', '0');
            stats.totalReloads = 0;
            saveStats();
        };
 
        // ==================== JITTER MODE ====================
        // Adds ±10% random delay to prevent predictable reload patterns
        // Returns object with newValue, jitterAmount, and applied flag
        const applyJitter = (value) => {
            const jitterEnabled = localStorage.getItem('jitterMode') === 'true';
            if (!jitterEnabled) {
                return { newValue: value, jitterAmount: 0, applied: false };
            }
            
            // Calculate 10% of the value
            const jitterRange = Math.floor(value * 0.1);
            
            // Generate random adjustment between -jitterRange and +jitterRange
            const adjustment = Math.floor(Math.random() * (jitterRange * 2 + 1)) - jitterRange;
            
            // Ensure the final value is at least 1
            const newValue = Math.max(1, value + adjustment);
            
            return { newValue: newValue, jitterAmount: adjustment, applied: true };
        };
 
        // ==================== ACTIVITY TRACKING ====================
        // Monitors user activity and resumes from inactivity pause
        const recordActivity = () => {
            if (isPausedByInactivity) {
                isPausedByInactivity = false;
                updateUIState();
            }
            resetInactivityTimeout();
        };
 
        // ==================== INACTIVITY DETECTION ====================
        // Automatically pauses reload when user hasn't interacted with page for 60 seconds
        const resetInactivityTimeout = () => {
            if (inactivityTimeout) clearTimeout(inactivityTimeout);
            if (!isRunning || localStorage.getItem('inactivityPauseEnabled') !== 'true') return;
            
            inactivityTimeout = setTimeout(() => {
                if (isRunning && !isPausedBySafety) {
                    isPausedByInactivity = true;
                    updateUIState();
                }
            }, INACTIVITY_THRESHOLD);
        };
 
        // ==================== UI ELEMENTS ====================
        // References to Shadow DOM elements (populated during createShadowUI)
        let host, shadow, mainBtn, settingsBtn, themeBtn, settingsModal, opacitySlider;
 
        // ==================== SHADOW DOM CREATION ====================
        // Creates isolated UI with shadow DOM for styling isolation and positioning
        function createShadowUI() {
            host = document.createElement('div');
            host.id = 'live-reload-host';
            host.style.cssText = 'position: fixed; bottom: 20px; left: 20px; z-index: 2147483647; font-family: "Inter", sans-serif;';
            
            // ========== DRAG & DROP HANDLER ==========
            // Allows user to drag widget to any position on screen and persist location
            let isDragging = false, dragOffsetX = 0, dragOffsetY = 0;
            
            const startDrag = (clientX, clientY) => {
                isDragging = true;
                dragOffsetX = clientX - host.offsetLeft;
                dragOffsetY = clientY - host.offsetTop;
                host.style.cursor = 'grabbing';
            };
            
            const moveDrag = (clientX, clientY) => {
                if (!isDragging) return;
                host.style.left = (clientX - dragOffsetX) + 'px';
                host.style.top = (clientY - dragOffsetY) + 'px';
                host.style.bottom = 'auto';
                localStorage.setItem('liveReloadPos', JSON.stringify({ x: clientX - dragOffsetX, y: clientY - dragOffsetY }));
            };
            
            const endDrag = () => {
                isDragging = false;
                host.style.cursor = 'grab';
            };
            
            const checkIfDraggable = (path) => !path.some(el => 
                el.tagName === 'INPUT' || (el.className && (el.className.includes('modal') || el.className.includes('stats-section')))
            );
            
            // Mouse and touch event listeners for dragging
            host.addEventListener('mousedown', (e) => {
                if (e.button !== 0 || !checkIfDraggable(e.composedPath())) return;
                startDrag(e.clientX, e.clientY);
            });
            
            host.addEventListener('touchstart', (e) => {
                if (!checkIfDraggable(e.composedPath())) return;
                startDrag(e.touches[0].clientX, e.touches[0].clientY);
            });
            
            document.addEventListener('mousemove', (e) => moveDrag(e.clientX, e.clientY));
            document.addEventListener('touchmove', (e) => moveDrag(e.touches[0].clientX, e.touches[0].clientY));
            document.addEventListener('mouseup', endDrag);
            document.addEventListener('touchend', endDrag);
            
            // Restore saved position on page load
            try {
                const pos = JSON.parse(localStorage.getItem('liveReloadPos'));
                if (pos) {
                    host.style.left = pos.x + 'px';
                    host.style.top = pos.y + 'px';
                    host.style.bottom = 'auto';
                }
            } catch (e) {}
            
            document.body.appendChild(host);
            shadow = host.attachShadow({ mode: 'open' });
 
            // ========== SHADOW DOM STYLES ==========
            // Complete styling for all UI elements, themes, animations, and responsive design
            const style = document.createElement('style');
            style.textContent = `
                :host { --accent-red: linear-gradient(135deg, #ff5f6d, #ffc371); --accent-green: linear-gradient(135deg, #11998e, #38ef7d); --accent-blue: #3498db; }
                :host([theme="dark"]) { --bg: rgba(20,20,25,0.95); --modal-bg: rgba(15,15,20,0.98); --text: #fff; --border: rgba(255,255,255,0.08); --input-bg: rgba(255,255,255,0.05); }
                :host([theme="light"]) { --bg: rgba(240,242,245,0.95); --modal-bg: rgba(255,255,255,0.98); --text: #1a1a1b; --border: rgba(0,0,0,0.08); --input-bg: rgba(0,0,0,0.03); }
                
                .btn { cursor: pointer; color: var(--text); border-radius: 12px; padding: 11px 20px; font-weight: 600; border: 1px solid var(--border); display: flex; align-items: center; gap: 8px; backdrop-filter: blur(20px); background: var(--bg); transition: all 0.25s ease; font-size: 14px; }
                .btn:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.15); border-color: rgba(255,255,255,0.15); }
                .btn:active { transform: translateY(0px); }
                .btn:hover .icon { animation: iconRotate 0.6s ease-in-out forwards; }
                .icon { display: inline-block; font-size: 16px; }
                
                .btn-main { background: var(--accent-red); border: none; color: white; min-width: 130px; justify-content: center; box-shadow: 0 6px 20px rgba(255, 95, 109, 0.3); position: relative; font-size: 15px; font-weight: 700; }
                .btn-main::before { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 160px; height: 160px; border: 3px solid rgba(255, 255, 255, 0.2); border-radius: 50%; animation: rotatePulse 3s linear infinite; pointer-events: none; z-index: -1; }
                .btn-main.active::before { border-color: rgba(255, 255, 255, 0.7); animation: rotatePulse 2s linear infinite; }
                .btn-main.safety::before { border-color: rgba(52, 152, 219, 0.7); animation: rotatePulse 1.5s linear infinite; }
                @keyframes rotatePulse { 0% { transform: translate(-50%, -50%) rotate(0deg); } 100% { transform: translate(-50%, -50%) rotate(360deg); } }
                .btn-main.active { background: linear-gradient(135deg, #f1c40f, #f39c12); color: #000; box-shadow: 0 6px 20px rgba(241, 196, 15, 0.4); }
                .btn-main.safety { background: var(--accent-blue); color: white; box-shadow: 0 6px 20px rgba(52, 152, 219, 0.3); animation: softPulse 2s infinite; }
                
                .modal { position: absolute; bottom: 75px; left: 0; background: var(--modal-bg); padding: 0; border-radius: 18px; border: 1px solid var(--border); box-shadow: 0 25px 60px rgba(0,0,0,0.2); color: var(--text); display: none; flex-direction: row; gap: 0; width: 370px; backdrop-filter: blur(30px); max-height: 85vh; overflow: hidden; align-items: stretch; }
                .modal-wrapper { display: flex; flex-direction: column; flex: 1; height: 320px; }
                .modal-content { flex: 1; display: flex; flex-direction: column; gap: 5px; overflow-y: auto; padding: 12px; padding-right: 8px; padding-bottom: 20px; scrollbar-width: none; }
                .modal-content::-webkit-scrollbar { width: 0px; height: 0px; }
                .modal-content::-webkit-scrollbar-track { background: transparent; }
                .modal-content::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; display: none; }
                .modal-content::-webkit-scrollbar-thumb:hover { background: transparent; }
                
                .modal-footer { display: flex; flex-direction: column; gap: 8px; padding: 16px 12px 12px 12px; border-top: 1px solid var(--border); flex-shrink: 0; align-items: center; }
                
                .modal-tabs { display: flex; flex-direction: column; gap: 4px; padding: 8px; border-left: 1px solid var(--border); flex-shrink: 0; align-items: center; justify-content: flex-start; position: sticky; top: 0; min-width: 54px; background: var(--modal-bg); z-index: 1; height: 320px; }
                .tab-btn { background: transparent; border: none; color: var(--text); width: 38px; height: 38px; border-radius: 8px; cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center; transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); opacity: 0.5; flex-shrink: 0; }
                .tab-btn:hover { opacity: 0.75; }
                .tab-btn:active { opacity: 0.9; }
                .tab-btn.active { background: rgba(17, 153, 142, 0.3); color: #11998e; opacity: 1; box-shadow: inset 0 0 0 1px rgba(17, 153, 142, 0.4), 0 0 12px rgba(17, 153, 142, 0.3); animation: tabGlow 0.4s ease-out; }
                @keyframes tabGlow { 0% { box-shadow: inset 0 0 0 1px rgba(17, 153, 142, 0.4), 0 0 6px rgba(17, 153, 142, 0.1); } 50% { box-shadow: inset 0 0 0 1px rgba(17, 153, 142, 0.4), 0 0 18px rgba(17, 153, 142, 0.4); } 100% { box-shadow: inset 0 0 0 1px rgba(17, 153, 142, 0.4), 0 0 12px rgba(17, 153, 142, 0.3); } }
                
                .tab-content { display: none; opacity: 0; transform: translateY(-8px); transition: opacity 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); pointer-events: none; }
                .tab-content.active { display: flex; flex-direction: column; gap: 5px; opacity: 1; transform: translateY(0); pointer-events: auto; animation: slideInContent 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); }
                @keyframes slideInContent { 0% { opacity: 0; transform: translateY(-8px); } 100% { opacity: 1; transform: translateY(0); } }
                
                .modal::-webkit-scrollbar { width: 6px; }
                .modal::-webkit-scrollbar-track { background: transparent; }
                .modal::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }
                .modal::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.3); }
                
                .modal, .modal * { cursor: auto !important; }
                .modal button { cursor: pointer !important; }
                .modal input { cursor: pointer !important; }
                
                input[type="range"] { width: 100%; height: 6px; border-radius: 5px; background: var(--input-bg); outline: none; -webkit-appearance: none; appearance: none; accent-color: #11998e; }
                input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%; background: linear-gradient(135deg, #11998e, #38ef7d); cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2); transition: all 0.2s ease; }
                input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.15); box-shadow: 0 4px 12px rgba(17,153,142,0.4); }
                input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: linear-gradient(135deg, #11998e, #38ef7d); cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2); border: none; transition: all 0.2s ease; }
                input[type="range"]::-moz-range-thumb:hover { transform: scale(1.15); box-shadow: 0 4px 12px rgba(17,153,142,0.4); }
                
                input[type="number"] { background: var(--input-bg); border: 1px solid var(--border); color: var(--text); border-radius: 8px; padding: 8px 12px; font-size: 13px; transition: all 0.2s ease; font-family: inherit; }
                input[type="number"]:focus { border-color: #11998e; background: var(--input-bg); box-shadow: 0 0 0 2px rgba(17,153,142,0.1); }
                
                input[type="checkbox"] { width: 18px; height: 18px; cursor: pointer; accent-color: #11998e; }
                
                .preset-btn { background: rgba(17, 153, 142, 0.15); border: 1px solid rgba(17, 153, 142, 0.3); color: var(--text); padding: 7px 10px; border-radius: 8px; font-weight: 600; font-size: 11px; cursor: pointer; transition: all 0.2s ease; font-family: inherit; }
                .preset-btn:hover { background: rgba(17, 153, 142, 0.25); border-color: rgba(17, 153, 142, 0.5); transform: translateY(-1px); }
                .preset-btn:active { transform: translateY(0); }
                
                .btn-save { background: var(--accent-green); border: none; color: white; padding: 10px 40px; border-radius: 10px; font-weight: 700; margin: 8px auto 0 auto; display: block; cursor: pointer; font-size: 13px; transition: all 0.2s ease; box-shadow: 0 4px 15px rgba(17,153,142,0.3); font-family: inherit; flex-shrink: 0; width: fit-content; }
                .btn-save:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(17,153,142,0.4); }
                .btn-save:active { transform: translateY(0); }
                
                .section-title { font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; opacity: 0.7; margin-top: 2px; margin-bottom: 4px; }
                .input-group { display: flex; flex-direction: column; gap: 4px; }
                .input-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
                .input-row label { font-size: 13px; font-weight: 500; }
                .input-row strong { color: #11998e; font-weight: 700; }
                
                @keyframes softPulse { 0% { opacity: 1; } 50% { opacity: 0.75; } 100% { opacity: 1; } }
                @keyframes iconRotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
                
                .mode-badge { display: inline-block; background: rgba(17, 153, 142, 0.2); border: 1px solid rgba(17, 153, 142, 0.4); color: #11998e; padding: 2px 6px; border-radius: 4px; font-size: 9px; font-weight: 600; }
                .mode-badge.active { background: rgba(17, 153, 142, 0.35); border-color: rgba(17, 153, 142, 0.6); animation: pulse-badge 1.8s infinite; }
                @keyframes pulse-badge { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
                
                .mode-card { padding: 8px; background: var(--input-bg); border-radius: 8px; border: 1px solid var(--border); display: flex; align-items: center; gap: 10px; transition: all 0.2s; }
                .mode-card.active { background: rgba(17, 153, 142, 0.1); border-color: rgba(17, 153, 142, 0.3); box-shadow: inset 0 0 0 1px rgba(17, 153, 142, 0.2); }
                .mode-label { display: flex; align-items: center; gap: 8px; flex: 1; margin: 0; }
                .mode-label label { font-size: 12px; font-weight: 500; margin: 0; cursor: pointer; }
                .mode-info { font-size: 9px; opacity: 0.6; margin-top: 2px; font-style: italic; line-height: 1.2; }
                
                @media (max-width: 480px) {
                    .modal { width: 90vw; max-width: 340px; bottom: 60px; left: 50%; transform: translateX(-50%); }
                    .btn { padding: 9px 14px; font-size: 13px; min-height: 40px; }
                    .btn-main { min-width: 100px; }
                    .preset-btn { min-height: 36px; font-size: 10px; }
                    .mode-card { flex-wrap: wrap; }
                    .mode-label label { font-size: 13px; }
                    .tab-btn { width: 34px; height: 34px; font-size: 16px; }
                }
            `;
 
            // ========== DOM STRUCTURE ==========
            // Build UI elements: wrapper, buttons, and settings modal
            const wrapper = document.createElement('div');
            wrapper.style.opacity = getSettings().opacity;
            const container = document.createElement('div');
            container.style.cssText = 'display: flex; gap: 10px;';
 
            // Main play/pause button
            mainBtn = document.createElement('button');
            mainBtn.className = 'btn btn-main';
            mainBtn.style.position = 'relative';
            mainBtn.innerHTML = '<span class="icon">▶</span><span>Start</span>';
            
            // Settings button
            settingsBtn = document.createElement('button');
            settingsBtn.className = 'btn';
            settingsBtn.innerHTML = '<span class="icon">⚙</span>';
            
            // Theme toggle button
            themeBtn = document.createElement('button');
            themeBtn.className = 'btn';
 
            // ========== SETTINGS MODAL STRUCTURE ==========
            // Three-tab modal: Timing/Presets, Controls, and Statistics
            settingsModal = document.createElement('div');
            settingsModal.className = 'modal';
            settingsModal.innerHTML = `
                <div class="modal-wrapper">
                    <div class="modal-content">
                        <!-- Tab 1: Timing & Presets -->
                        <div class="tab-content active" id="tab-timing">
                            <div style="font-weight: 700; font-size: 11px; margin-bottom: 4px;">⚡ Presets</div>
                            <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; margin-bottom: 4px;">
                                <button class="preset-btn" id="preset-1-5">1-5s</button>
                                <button class="preset-btn" id="preset-5-15">5-15s</button>
                                <button class="preset-btn" id="preset-5-10">5-10s</button>
                                <button class="preset-btn" id="preset-10-15">10-15s</button>
                                <button class="preset-btn" id="preset-15-20">15-20s</button>
                                <button class="preset-btn" id="preset-20-30">20-30s</button>
                                <button class="preset-btn" id="preset-1-5m" style="grid-column: 1 / -1;">1-5m</button>
                            </div>
                            <div class="section-title">⏱ Timing</div>
                            <div class="input-group">
                                <div class="input-row">
                                    <label>Min Wait</label>
                                    <strong id="min-display">5</strong><span style="opacity: 0.6;">s</span>
                                </div>
                                <input type="range" id="min-input" min="1" max="300" value="5">
                            </div>
                            <div class="input-group">
                                <div class="input-row">
                                    <label>Max Wait</label>
                                    <strong id="max-display">15</strong><span style="opacity: 0.6;">s</span>
                                </div>
                                <input type="range" id="max-input" min="1" max="300" value="15">
                            </div>
                        </div>
                        
                        <!-- Tab 2: Controls -->
                        <div class="tab-content" id="tab-controls">
                            <div class="section-title">🎛 Controls</div>
                            <div class="input-group">
                                <div class="input-row">
                                    <label>Max Reloads</label>
                                    <strong id="max-reloads-display">0</strong><span style="opacity: 0.6;">(0=∞)</span>
                                </div>
                                <input type="range" id="max-reloads-input" min="0" max="100" value="0">
                            </div>
                            <div class="input-group">
                                <div class="input-row">
                                    <label>Opacity</label>
                                    <strong style="color: #11998e;" id="opacity-display">100</strong><span style="opacity: 0.6;">%</span>
                                </div>
                                <input type="range" id="opacity-slider" min="0.2" max="1" step="0.1" value="1">
                            </div>
                            <div class="mode-card" id="inactivity-card">
                                <div class="mode-label">
                                    <input type="checkbox" id="inactivity-toggle">
                                    <label for="inactivity-toggle">Inactivity Pause</label>
                                </div>
                                <span class="mode-badge" id="inactivity-badge">OFF</span>
                            </div>
                            <div class="mode-info">Pauses auto-reload when you stop using the page (60s timeout)</div>
                            <div class="mode-card" id="jitter-card">
                                <div class="mode-label">
                                    <input type="checkbox" id="jitter-toggle">
                                    <label for="jitter-toggle">Jitter Mode</label>
                                </div>
                                <span class="mode-badge" id="jitter-badge">OFF</span>
                            </div>
                            <div class="mode-info">Adds ±10% random delay to prevent predictable reload patterns</div>
                        </div>
                        
                        <!-- Tab 3: Statistics -->
                        <div class="tab-content" id="tab-stats">
                            <div class="section-title">📊 Statistics</div>
                            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; font-size: 11px;">
                                <div style="padding: 6px; background: var(--input-bg); border-radius: 8px; border: 1px solid var(--border);">
                                    <div style="opacity: 0.7; margin-bottom: 2px; font-size: 10px;">Reloads</div>
                                    <div style="font-size: 14px; font-weight: 700; color: #11998e;" id="stat-reloads">0</div>
                                </div>
                                <div style="padding: 6px; background: var(--input-bg); border-radius: 8px; border: 1px solid var(--border);">
                                    <div style="opacity: 0.7; margin-bottom: 2px; font-size: 10px;">Limit</div>
                                    <div style="font-size: 14px; font-weight: 700; color: #11998e;" id="stat-max-limit">0</div>
                                </div>
                                <div style="padding: 6px; background: var(--input-bg); border-radius: 8px; border: 1px solid var(--border);">
                                    <div style="opacity: 0.7; margin-bottom: 2px; font-size: 10px;">Session</div>
                                    <div style="font-size: 12px; font-weight: 700; color: #11998e;" id="stat-session">0m 0s</div>
                                </div>
                                <div style="padding: 6px; background: var(--input-bg); border-radius: 8px; border: 1px solid var(--border);">
                                    <div style="opacity: 0.7; margin-bottom: 2px; font-size: 10px;">Avg Wait</div>
                                    <div style="font-size: 12px; font-weight: 700; color: #11998e;" id="stat-avg">0s</div>
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <div class="modal-footer">
                        <button class="btn-save" id="save-btn">SAVE</button>
                        <button class="preset-btn" id="reset-stats-btn" style="padding: 8px; font-size: 11px;">Reset Stats</button>
                    </div>
                </div>
                
                <div class="modal-tabs">
                    <button class="tab-btn active" data-tab="timing" title="Timing / Presets">⏱</button>
                    <button class="tab-btn" data-tab="controls" title="Controls">🎛</button>
                    <button class="tab-btn" data-tab="stats" title="Stats">📊</button>
                </div>
            `;
 
            container.append(mainBtn, themeBtn, settingsBtn);
            wrapper.append(container, settingsModal);
            shadow.append(style, wrapper);
 
            // ========== INPUT ELEMENTS CACHE ==========
            // Cache frequently accessed input elements
            opacitySlider = settingsModal.querySelector('#opacity-slider');
            const minInput = settingsModal.querySelector('#min-input');
            const maxInput = settingsModal.querySelector('#max-input');
            const minDisplay = settingsModal.querySelector('#min-display');
            const maxDisplay = settingsModal.querySelector('#max-display');
            const maxReloadsInput = settingsModal.querySelector('#max-reloads-input');
            const maxReloadsDisplay = settingsModal.querySelector('#max-reloads-display');
            const opacityDisplay = settingsModal.querySelector('#opacity-display');
            
            // ========== INPUT VALIDATION & SYNC ==========
            // Validate min/max inputs and update displays in real-time
            minInput.oninput = (e) => {
                const minVal = parseInt(e.target.value), maxVal = parseInt(maxInput.value);
                if (minVal > maxVal) { minInput.value = maxVal; minDisplay.textContent = maxVal; }
                else minDisplay.textContent = minVal;
            };
            
            maxInput.oninput = (e) => {
                const minVal = parseInt(minInput.value), maxVal = parseInt(e.target.value);
                if (maxVal < minVal) { maxInput.value = minVal; maxDisplay.textContent = minVal; }
                else maxDisplay.textContent = maxVal;
            };
            
            maxReloadsInput.oninput = (e) => { maxReloadsDisplay.textContent = e.target.value; };
            opacitySlider.oninput = (e) => { 
                wrapper.style.opacity = e.target.value;
                opacityDisplay.textContent = Math.round(e.target.value * 100);
            };
            
            // ========== TAB SWITCHING ==========
            // Handle tab navigation in settings modal
            const tabButtons = settingsModal.querySelectorAll('.tab-btn');
            const tabContents = settingsModal.querySelectorAll('.tab-content');
            
            tabButtons.forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const tabName = btn.dataset.tab;
                    tabButtons.forEach(b => b.classList.remove('active'));
                    tabContents.forEach(content => content.classList.remove('active'));
                    btn.classList.add('active');
                    settingsModal.querySelector(`#tab-${tabName}`).classList.add('active');
                });
            });
        }
 
        // ==================== UI STATE MANAGEMENT ====================
        // Updates button display and favicon based on current running state
        function updateUIState() {
            if (isPausedByInactivity) {
                mainBtn.className = 'btn btn-main safety';
                mainBtn.innerHTML = '<span class="icon">💤</span><span>NO ACTIVITY</span>';
            } else if (isPausedBySafety) {
                mainBtn.className = 'btn btn-main safety';
                mainBtn.innerHTML = '<span class="icon">🛡</span><span>PAUSED</span>';
            } else if (isRunning) {
                mainBtn.className = 'btn btn-main active';
                mainBtn.innerHTML = `<span class="icon">⏸</span><span>${remainingTime}s</span>`;
            } else {
                mainBtn.className = 'btn btn-main';
                mainBtn.innerHTML = '<span class="icon">▶</span><span>Start</span>';
            }
            updateFavicon(remainingTime);
            document.title = (isRunning && !isPausedBySafety && !isPausedByInactivity) 
                ? `[${remainingTime}s] ` + document.title.replace(/\[\d+s\]\s/, '')
                : document.title.replace(/\[\d+s\]\s/, '');
        }
 
        // ==================== RELOAD CYCLE ====================
        // Main loop: calculate wait time, start timers, handle reloads
        function startCycle() {
            const { min, max } = getSettings();
            let waitTime = Math.max(1, Math.floor(Math.random() * (max - min) + min));
            const jitterResult = applyJitter(waitTime);
            waitTime = jitterResult.newValue;
            
            // ========== FIXED: ALWAYS show jitter notification when jitter mode is enabled ==========
            if (jitterResult.applied) {
                const sign = jitterResult.jitterAmount > 0 ? '+' : '';
                const jitterDisplay = jitterResult.jitterAmount === 0 ? '±0' : sign + jitterResult.jitterAmount;
                showToast(`⚡ Jitter: ${jitterDisplay}s (${waitTime}s total)`);
            }
            
            remainingTime = maxRemainingTime = waitTime;
            stats.waitTimes.push(waitTime);
            
            if (!sessionTimerId) {
                sessionTimerId = setInterval(() => {
                    stats.totalRunningTime += 1;
                    saveStats();
                }, 1000);
            }
            
            isPausedByInactivity = false;
            resetInactivityTimeout();
            updateUIState();
            
            // ========== COUNTDOWN & RELOAD TIMER ==========
            // Decrements every second and triggers reload when reaching zero
            timerId = setInterval(() => {
                if (!isPausedBySafety && !isPausedByInactivity) {
                    remainingTime--;
                    updateUIState();
                    if (remainingTime <= 0) {
                        const maxReloadsLimit = parseInt(localStorage.getItem('maxReloadsLimit')) || 0;
                        const currentCount = getReloadCount();
                        
                        if (maxReloadsLimit > 0 && currentCount >= maxReloadsLimit) {
                            showToast('🛑 Max reached');
                            isRunning = false;
                            clearInterval(timerId);
                            updateUIState();
                            return;
                        }
                        
                        incrementReloadCount();
                        window.location.reload();
                    }
                }
            }, 1000);
        }
 
        // ==================== SAFETY CHECK ====================
        // Prevents reload while user is typing in input fields (form protection)
        const checkSafety = (e) => {
            const active = document.activeElement;
            const isTyping = active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.isContentEditable);
            isPausedBySafety = isRunning && isTyping;
            updateUIState();
        };
 
        // ==================== INITIALIZATION ====================
        // Creates UI, loads settings, restores state
        createShadowUI();
        host.setAttribute('theme', localStorage.getItem(themeKey) || 'dark');
        themeBtn.innerHTML = host.getAttribute('theme') === 'dark' ? '<span class="icon">☀️</span>' : '<span class="icon">🌙</span>';
 
        // ==================== BUTTON EVENT LISTENERS ====================
        // Main button: toggle start/stop
        mainBtn.onclick = () => {
            isRunning = !isRunning;
            isPausedBySafety = isPausedByInactivity = false;
            localStorage.setItem(storageKey, isRunning);
            updateUIState();
            if (isRunning) {
                startCycle();
            } else {
                clearInterval(timerId);
                clearTimeout(inactivityTimeout);
                if (sessionTimerId) clearInterval(sessionTimerId), sessionTimerId = null;
            }
        };
 
        // ==================== DOCUMENT EVENT LISTENERS ====================
        // Safety: detect typing, activity tracking, keyboard shortcuts
        document.addEventListener('focusin', checkSafety);
        document.addEventListener('focusout', () => setTimeout(checkSafety, 100));
        document.addEventListener('mousemove', recordActivity);
        document.addEventListener('keypress', recordActivity);
        document.addEventListener('click', recordActivity);
        document.addEventListener('keydown', (e) => {
            if (e.shiftKey && e.key.toUpperCase() === 'T') {
                e.preventDefault();
                mainBtn.click();
            }
        });
        
        // ==================== LOAD PERSISTENT DATA ====================
        // Restore stats and reload count on page load
        loadStats();
        stats.totalReloads = getReloadCount();
 
        // ==================== THEME TOGGLE ==========
        // Switch between dark and light themes
        themeBtn.onclick = () => {
            const next = host.getAttribute('theme') === 'dark' ? 'light' : 'dark';
            host.setAttribute('theme', next);
            localStorage.setItem(themeKey, next);
            themeBtn.innerHTML = next === 'dark' ? '<span class="icon">☀️</span>' : '<span class="icon">🌙</span>';
        };
 
        // ==================== SETTINGS MODAL HANDLERS ====================
        // Open/close settings and sync UI with stored values
        settingsBtn.onclick = () => {
            settingsModal.style.display = settingsModal.style.display === 'flex' ? 'none' : 'flex';
            const s = getSettings();
            
            shadow.querySelector('#min-input').value = s.min;
            shadow.querySelector('#max-input').value = s.max;
            shadow.querySelector('#min-display').textContent = s.min;
            shadow.querySelector('#max-display').textContent = s.max;
            
            const inactivityToggle = shadow.querySelector('#inactivity-toggle');
            const jitterToggle = shadow.querySelector('#jitter-toggle');
            const inactivityBadge = shadow.querySelector('#inactivity-badge');
            const jitterBadge = shadow.querySelector('#jitter-badge');
            const inactivityCard = shadow.querySelector('#inactivity-card');
            const jitterCard = shadow.querySelector('#jitter-card');
            
            inactivityToggle.checked = localStorage.getItem('inactivityPauseEnabled') === 'true';
            jitterToggle.checked = localStorage.getItem('jitterMode') === 'true';
            
            const updateBadges = () => {
                const updateBadge = (toggle, badge, card) => {
                    if (toggle.checked) {
                        badge.textContent = 'ON';
                        badge.classList.add('active');
                        card.classList.add('active');
                    } else {
                        badge.textContent = 'OFF';
                        badge.classList.remove('active');
                        card.classList.remove('active');
                    }
                };
                updateBadge(inactivityToggle, inactivityBadge, inactivityCard);
                updateBadge(jitterToggle, jitterBadge, jitterCard);
            };
            
            updateBadges();
            inactivityToggle.oninput = jitterToggle.oninput = updateBadges;
            
            const maxReloadsValue = localStorage.getItem('maxReloadsLimit') || '0';
            shadow.querySelector('#max-reloads-input').value = maxReloadsValue;
            shadow.querySelector('#max-reloads-display').textContent = maxReloadsValue;
            
            opacitySlider.value = s.opacity;
            shadow.querySelector('#opacity-display').textContent = Math.round(s.opacity * 100);
            
            // Calculate and display session statistics
            const sessionTime = (stats.totalRunningTime || 0) * 1000;
            const minutes = Math.floor(sessionTime / 60000);
            const seconds = Math.floor((sessionTime % 60000) / 1000);
            const avgWait = stats.waitTimes.length > 0 
                ? Math.round(stats.waitTimes.reduce((a, b) => a + b) / stats.waitTimes.length) 
                : 0;
            
            shadow.querySelector('#stat-reloads').textContent = stats.totalReloads;
            shadow.querySelector('#stat-max-limit').textContent = maxReloadsValue;
            shadow.querySelector('#stat-session').textContent = `${minutes}m ${seconds}s`;
            shadow.querySelector('#stat-avg').textContent = avgWait + 's';
        };
 
        // ==================== PRESET BUTTONS ====================
        // Quick settings presets for common timing scenarios
        const updateSliderDisplay = (minVal, maxVal) => {
            shadow.querySelector('#min-input').value = minVal;
            shadow.querySelector('#max-input').value = maxVal;
            shadow.querySelector('#min-display').textContent = minVal;
            shadow.querySelector('#max-display').textContent = maxVal;
        };
 
        shadow.querySelector('#preset-1-5').onclick = () => updateSliderDisplay(1, 5);
        shadow.querySelector('#preset-5-15').onclick = () => updateSliderDisplay(5, 15);
        shadow.querySelector('#preset-5-10').onclick = () => updateSliderDisplay(5, 10);
        shadow.querySelector('#preset-10-15').onclick = () => updateSliderDisplay(10, 15);
        shadow.querySelector('#preset-15-20').onclick = () => updateSliderDisplay(15, 20);
        shadow.querySelector('#preset-20-30').onclick = () => updateSliderDisplay(20, 30);
        shadow.querySelector('#preset-1-5m').onclick = () => updateSliderDisplay(60, 300);
 
        // ==================== SAVE SETTINGS ====================
        // Persist all user settings to localStorage
        shadow.querySelector('#save-btn').onclick = () => {
            localStorage.setItem('minDelay', shadow.querySelector('#min-input').value);
            localStorage.setItem('maxDelay', shadow.querySelector('#max-input').value);
            localStorage.setItem(opacityKey, opacitySlider.value);
            localStorage.setItem('inactivityPauseEnabled', shadow.querySelector('#inactivity-toggle').checked);
            localStorage.setItem('jitterMode', shadow.querySelector('#jitter-toggle').checked);
            
            const maxReloadsValue = parseInt(shadow.querySelector('#max-reloads-input').value) || 0;
            localStorage.setItem('maxReloadsLimit', maxReloadsValue.toString());
            
            stats.maxReloadsLimit = maxReloadsValue;
            stats.jitterMode = shadow.querySelector('#jitter-toggle').checked;
            saveStats();
            showToast('✅ Settings saved!');
            settingsModal.style.display = 'none';
        };
 
        // ==================== RESET STATISTICS ====================
        // Clear all reload counts and timing history
        shadow.querySelector('#reset-stats-btn').onclick = () => {
            if (confirm('Reset all statistics?')) {
                resetReloadCount();
                stats = {
                    totalReloads: 0,
                    totalRunningTime: 0,
                    waitTimes: [],
                    maxReloadsLimit: 0,
                    jitterMode: false,
                    sessionStartTime: null
                };
                saveStats();
                shadow.querySelector('#stat-reloads').textContent = '0';
                shadow.querySelector('#stat-max-limit').textContent = '0';
                shadow.querySelector('#stat-session').textContent = '0m 0s';
                shadow.querySelector('#stat-avg').textContent = '0s';
                showToast('🔄 Stats reset!');
            }
        };
 
        // ==================== RESTORE SESSION STATE ====================
        // Resume auto-reload if it was running on previous page load
        if (localStorage.getItem(storageKey) === 'true') { 
            isRunning = true; 
            startCycle(); 
        }
    }
 
    LiveReload();
})();