Greasy Fork 支持简体中文。

✨ Simple Pull to Refresh

🌟 Minimal Premium: Простое и стабильное обновление страницы потягиванием для браузера Via

// ==UserScript==
// @name         ✨ Simple Pull to Refresh
// @namespace    r1kov
// @version      0.3-fix
// @description  🌟 Minimal Premium: Простое и стабильное обновление страницы потягиванием для браузера Via
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/UserScript==
(function() {
    'use strict';

    const config = {
        threshold: 100, // Минимальное расстояние для срабатывания обновления
        elementId: 'simple-refresh',
        overlayId: 'simple-overlay',
        colors: {
            pull: '#333',
            release: '#ffcc00',
            loading: '#007aff'
        },
        messages: {
            pull: 'потяните вниз',
            release: 'отпустите',
            loading: 'обновление'
        }
    };

    let state = {
        startY: 0,
        currentY: 0,
        isActive: false
    };

    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
            #${config.elementId} {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                height: 64px;
                display: flex;
                align-items: center;
                justify-content: center;
                z-index: 999999;
                transform: translateY(-100%);
                transition: transform 0.3s ease-out;
                pointer-events: none;
                font-family: -apple-system, system-ui, sans-serif;
            }
            #${config.elementId} .pill {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 12px 20px;
                background: ${config.colors.pull};
                color: white;
                border-radius: 26px;
                font-size: 14px;
                font-weight: 500;
                transition: background 0.3s ease-out;
            }
            #${config.elementId} .icon {
                font-size: 18px;
            }
            #${config.overlayId} {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(255, 255, 255, 0);
                backdrop-filter: blur(0px);
                -webkit-backdrop-filter: blur(0px);
                z-index: 999998;
                pointer-events: none;
                transition: all 0.3s ease-out;
            }
            @keyframes rotate {
                to { transform: rotate(360deg); }
            }
        `;
        document.head.appendChild(style);
    }

    function createElements() {
        if (document.getElementById(config.elementId) || document.getElementById(config.overlayId)) {
            return {
                overlay: document.getElementById(config.overlayId),
                refreshEl: document.getElementById(config.elementId)
            };
        }

        const overlay = document.createElement('div');
        overlay.id = config.overlayId;
        document.body.appendChild(overlay);

        const refreshEl = document.createElement('div');
        refreshEl.id = config.elementId;
        refreshEl.innerHTML = `
            <div class="pill">
                <span class="icon">↓</span>
                <span class="text">${config.messages.pull}</span>
            </div>
        `;
        document.body.appendChild(refreshEl);

        return { overlay, refreshEl };
    }

    function updateUI(distance) {
        const { overlay, refreshEl } = createElements();
        const translateY = Math.min(distance * 0.5, 64);
        refreshEl.style.transform = `translateY(${translateY - 64}px)`;
        overlay.style.background = `rgba(255, 255, 255, ${distance / config.threshold * 0.1})`;
        overlay.style.backdropFilter = `blur(${distance / config.threshold * 8}px)`;
        overlay.style.webkitBackdropFilter = `blur(${distance / config.threshold * 8}px)`;

        if (distance > config.threshold) {
            refreshEl.querySelector('.pill').style.background = config.colors.release;
            refreshEl.querySelector('.text').textContent = config.messages.release;
        } else {
            refreshEl.querySelector('.pill').style.background = config.colors.pull;
            refreshEl.querySelector('.text').textContent = config.messages.pull;
        }
    }

    function reset() {
        const { overlay, refreshEl } = createElements();
        refreshEl.style.transform = 'translateY(-100%)';
        overlay.style.background = 'rgba(255, 255, 255, 0)';
        overlay.style.backdropFilter = 'blur(0px)';
        overlay.style.webkitBackdropFilter = 'blur(0px)';

        setTimeout(() => {
            if (refreshEl && refreshEl.parentNode) refreshEl.remove();
            if (overlay && overlay.parentNode) overlay.remove();
        }, 300);
    }

    function refresh() {
        const { refreshEl } = createElements();
        refreshEl.querySelector('.pill').style.background = config.colors.loading;
        refreshEl.querySelector('.icon').textContent = '↻';
        refreshEl.querySelector('.text').textContent = config.messages.loading;

        setTimeout(() => {
            window.location.reload();
        }, 300);
    }

    function handleTouchStart(e) {
        if (window.scrollY === 0 && !e.target.matches('input, textarea, select')) {
            state.startY = e.touches[0].pageY;
            state.currentY = state.startY;
            state.isActive = true;
        }
    }

    function handleTouchMove(e) {
        if (!state.isActive) return;
        state.currentY = e.touches[0].pageY;
        const distance = state.currentY - state.startY;

        if (distance > 0 && window.scrollY === 0) {
            e.preventDefault();
            requestAnimationFrame(() => updateUI(distance));
        }
    }

    function handleTouchEnd() {
        if (!state.isActive) return;
        const distance = state.currentY - state.startY;

        if (distance > config.threshold) {
            refresh();
        } else {
            reset();
        }

        state.isActive = false;
    }

    injectStyles();

    document.addEventListener('touchstart', handleTouchStart, { passive: true });
    document.addEventListener('touchmove', handleTouchMove, { passive: false });
    document.addEventListener('touchend', handleTouchEnd, { passive: true });

    // Очистка элементов при переходе на другую страницу
    window.addEventListener('beforeunload', () => {
        const refreshEl = document.getElementById(config.elementId);
        const overlay = document.getElementById(config.overlayId);

        if (refreshEl && refreshEl.parentNode) refreshEl.remove();
        if (overlay && overlay.parentNode) overlay.remove();
    });
})();