PureReader

Distraction-free novel reader with translation compatibility, progress tracking, customizable typography, and much more.

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         PureReader
// @namespace    https://gitlab.com/wandersons13/purereader
// @version      0.2
// @description  Distraction-free novel reader with translation compatibility, progress tracking, customizable typography, and much more.
// @author       wandersons13
// @match        *://*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=readnovelfull.com
// @license      GNU
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {

    'use strict';
    let settings = JSON.parse(localStorage.getItem('gm_reader_settings')) || {
        theme: 'dark',
        fontSize: 22,
        lineHeight: 1.8,
        maxWidth: 800,
        fontIndex: 0,
        isActive: false,
        autoHide: false
    };
    const fonts = [{
            name: 'Inter',
            type: 'sans-serif'
        },
        {
            name: 'Merriweather',
            type: 'serif'
        },
        {
            name: 'Special Elite',
            type: 'cursive'
        }
    ];
    const saveSettings = () => localStorage.setItem('gm_reader_settings', JSON.stringify(settings));

    const scrollKey = 'gm_scroll_' + btoa(window.location.href.split('#')[0]).substring(0, 50);

    if (settings.isActive) {
        const bg = settings.theme === 'dark' ? '#20282e' : '#f4ecd8';
        const shield = document.createElement('style');
        shield.id = 'gm-protection-shield';
        shield.innerHTML = `html{background-color:${bg}!important;} body{opacity:0!important;overflow:hidden!important;} #gm-reader-overlay{opacity:1!important;display:block!important;}`;
        document.documentElement.appendChild(shield);
    }

    const initReader = () => {
        if (document.getElementById('gm-reader-overlay')) return;
        const fontLink = document.createElement('link');
        fontLink.rel = 'stylesheet';
        fontLink.href = 'https://fonts.googleapis.com/css2?family=Special+Elite&family=Inter:wght@400;700&family=Merriweather:wght@300;400;700&display=swap';
        document.head.appendChild(fontLink);

        const style = document.createElement('style');
        style.innerHTML = `
            #gm-reader-overlay.theme-sepia { --bg-color: #f4ecd8; --text-color: #2c2c2c; --icon-color: #333; --sidebar-bg: rgba(0,0,0,0.08); --sep-color: rgba(0,0,0,0.15); --accent: #d4a373; }
            #gm-reader-overlay.theme-dark { --bg-color: #20282e; --text-color: #999; --icon-color: #fff; --sidebar-bg: rgba(255,255,255,0.12); --sep-color: rgba(255,255,255,0.25); --accent: #4a9eff; }
            #gm-reader-overlay:focus { outline: none; }
            #gm-reader-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 2147483646; overflow-y: auto; overflow-x: hidden !important; display: none; background-color: var(--bg-color) !important; scroll-behavior: smooth; }
            .gm-reader-content-box { margin: 0 auto; padding: 80px 25px; min-height: 100vh; box-sizing: border-box; width: 100%; }
            .gm-reader-content-box * { color: var(--text-color) !important; background-color: transparent !important; max-width: 100% !important; box-sizing: border-box !important; white-space: normal !important; overflow-wrap: anywhere !important; }
            #gm-reader-sidebar {
                position: fixed; right: 30px; top: 50%; transform: translateY(-50%); z-index: 2147483647;
                width: 54px; padding: 12px 0; display: flex; flex-direction: column; align-items: center;
                background: var(--sidebar-bg); backdrop-filter: blur(20px); border: 1px solid var(--sep-color);
                border-radius: 27px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);
                transition: opacity 0.3s ease;
                opacity: 1;
            }
            #gm-reader-sidebar.auto-hide-active { opacity: 0.15; }
            #gm-reader-sidebar.auto-hide-active:hover { opacity: 1; }
            .sidebar-btn { background: none; border: none; width: 40px; height: 40px; margin: 2px 0; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 50%; fill: var(--icon-color); transition: all 0.2s; }
            .sidebar-btn:hover { background: rgba(128,128,128,0.2); }
            .sidebar-btn.active-mode { fill: var(--accent); }
            .sidebar-sep { width: 30px; height: 2px; background: var(--sep-color); margin: 8px 0; border-radius: 1px; }
        `;
        document.head.appendChild(style);

        const overlay = document.createElement('div');
        overlay.id = 'gm-reader-overlay';
        overlay.tabIndex = 0;
        const contentBox = document.createElement('div');
        contentBox.className = 'gm-reader-content-box';
        const sidebar = document.createElement('div');
        sidebar.id = 'gm-reader-sidebar';
        sidebar.innerHTML = `
            <button class="sidebar-btn" id="btn-theme" title="Trocar Tema"><svg viewBox="0 0 24 24" width="20"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zm0-2V4a8 8 0 110 16z"/></svg></button>
            <button class="sidebar-btn" id="btn-font" title="Trocar Fonte"><svg viewBox="0 0 24 24" width="20"><path d="M9.93 13.5h4.14L12 7.98zM20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-4.05 16.5l-1.14-3H9.17l-1.12 3H5.96l5.11-13h1.86l5.11 13h-2.09z"/></svg></button>
            <div class="sidebar-sep"></div>
            <button class="sidebar-btn" id="btn-f-plus" title="Aumentar Fonte"><svg viewBox="0 0 24 24" width="20"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg></button>
            <button class="sidebar-btn" id="btn-f-minus" title="Diminuir Fonte"><svg viewBox="0 0 24 24" width="20"><path d="M19 13H5v-2h14v2z"/></svg></button>
            <div class="sidebar-sep"></div>
            <button class="sidebar-btn" id="btn-lh-plus" title="Aumentar Espaçamento"><svg viewBox="0 0 24 24" width="20"><path d="M7 21V3h2v18H7zm7-18l-4 4h3v10h-3l4 4 4-4h-3V7h3l-4-4z"/></svg></button>
            <button class="sidebar-btn" id="btn-lh-minus" title="Diminuir Espaçamento"><svg viewBox="0 0 24 24" width="20"><path d="M7 21V3h2v18H7zm11-14l-4-4-4 4h3v10h-3l4 4 4-4h-3V7h3z"/></svg></button>
            <div class="sidebar-sep"></div>
            <button class="sidebar-btn" id="btn-w-plus" title="Aumentar Largura"><svg viewBox="0 0 24 24" width="20"><path d="M15 4h5v5h-2V6h-3V4zM4 15h2v3h3v2H4v-5zm14 3h-3v2h5v-5h-2v3zM6 6h3V4H4v5h2V6z"/></svg></button>
            <button class="sidebar-btn" id="btn-w-minus" title="Diminuir Largura"><svg viewBox="0 0 24 24" width="20"><path d="M18 16h3v2h-5v-5h2v3zM8 8h-3V6h5v5H8V8zM16 8V5h2v5h-5V8h3zM8 16v3H6v-5h5v2H8z"/></svg></button>
            <div class="sidebar-sep"></div>
            <button class="sidebar-btn" id="btn-autohide" title="Ativar/Desativar Auto-Hide"><svg viewBox="0 0 24 24" width="20"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg></button>
        `;
        overlay.append(contentBox, sidebar);
        document.body.appendChild(overlay);

        const applyStyles = () => {
            const f = fonts[settings.fontIndex];
            contentBox.style.maxWidth = settings.maxWidth + 'px';
            contentBox.style.setProperty('font-family', `'${f.name}', ${f.type}`, 'important');
            contentBox.style.setProperty('font-size', settings.fontSize + 'px', 'important');
            contentBox.style.setProperty('line-height', settings.lineHeight, 'important');

            contentBox.querySelectorAll('*').forEach(el => {
                el.style.setProperty('font-family', `'${f.name}', ${f.type}`, 'important');
                el.style.setProperty('font-size', settings.fontSize + 'px', 'important');
                el.style.setProperty('line-height', settings.lineHeight, 'important');
                if (el.tagName === 'P') el.style.setProperty('margin-bottom', '1.6em', 'important');
            });

            const btnHide = document.getElementById('btn-autohide');
            if (settings.autoHide) {
                sidebar.classList.add('auto-hide-active');
                btnHide.classList.add('active-mode');
            } else {
                sidebar.classList.remove('auto-hide-active');
                btnHide.classList.remove('active-mode');
            }
            saveSettings();
        };

        const updateUI = () => {
            overlay.className = 'theme-' + settings.theme;
            applyStyles();
        };

        const tryCapture = () => {
            let best = null,
                max = 0;
            document.querySelectorAll('article, main, .content, .post-body, #content, section, div:not(#gm-reader-overlay)').forEach(el => {
                const txt = el.innerText.trim();
                if (txt.length > max) {
                    max = txt.length;
                    best = el;
                }
            });
            if (best && max > 200) {
                const clone = best.cloneNode(true);
                clone.querySelectorAll('button, input, nav, footer, header, aside, form, svg, ul, ol, script, style, img, figure, .comments, #comments').forEach(el => el.remove());
                contentBox.innerHTML = clone.innerHTML;
                overlay.style.display = 'block';
                document.body.style.overflow = 'hidden';
                updateUI();

                setTimeout(() => {
                    overlay.focus();
                    const savedPos = localStorage.getItem(scrollKey);
                    if (savedPos) {
                        overlay.scrollTop = parseInt(savedPos);
                    } else {
                        overlay.scrollTop = 0;
                    }
                }, 100);

                const shield = document.getElementById('gm-protection-shield');
                if (shield) shield.remove();
                document.body.style.opacity = '1';
                return true;
            }
            return false;
        };

        overlay.addEventListener('scroll', () => {
            if (overlay.scrollTop > 100) {
                localStorage.setItem(scrollKey, overlay.scrollTop);
            }
        });

        window.addEventListener('keydown', (e) => {
            if (e.altKey && e.key.toLowerCase() === 'r') {
                e.preventDefault();
                settings.isActive = !settings.isActive;
                if (settings.isActive) {
                    tryCapture();
                } else {
                    overlay.style.display = 'none';
                    document.body.style.overflow = '';
                    document.body.style.opacity = '1';
                }
                saveSettings();
            }
        });

        const obs = new MutationObserver(() => {
            if (settings.isActive && tryCapture()) obs.disconnect();
        });
        obs.observe(document.body, {
            childList: true,
            subtree: true
        });

        if (settings.isActive) tryCapture();

        document.getElementById('btn-theme').onclick = () => {
            settings.theme = settings.theme === 'sepia' ? 'dark' : 'sepia';
            updateUI();
        };
        document.getElementById('btn-font').onclick = () => {
            settings.fontIndex = (settings.fontIndex + 1) % fonts.length;
            updateUI();
        };
        document.getElementById('btn-autohide').onclick = () => {
            settings.autoHide = !settings.autoHide;
            updateUI();
        };
        document.getElementById('btn-f-plus').onclick = () => {
            settings.fontSize += 2;
            updateUI();
        };
        document.getElementById('btn-f-minus').onclick = () => {
            settings.fontSize = Math.max(12, settings.fontSize - 2);
            updateUI();
        };
        document.getElementById('btn-lh-plus').onclick = () => {
            settings.lineHeight = parseFloat((settings.lineHeight + 0.1).toFixed(1));
            updateUI();
        };
        document.getElementById('btn-lh-minus').onclick = () => {
            settings.lineHeight = Math.max(1.0, parseFloat((settings.lineHeight - 0.1).toFixed(1)));
            updateUI();
        };
        document.getElementById('btn-w-plus').onclick = () => {
            settings.maxWidth = Math.min(1900, settings.maxWidth + 50);
            updateUI();
        };
        document.getElementById('btn-w-minus').onclick = () => {
            settings.maxWidth = Math.max(400, settings.maxWidth - 50);
            updateUI();
        };
    };

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        initReader();
    } else {
        window.addEventListener('DOMContentLoaded', initReader);
    }
})();