PureReader

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

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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);
    }
})();