PureReader

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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