WTR-Lab Reader Content Width

Control the max-width of the main content area on wtr-lab.com (chapter reader, library, and more). Opens a small panel via the Tampermonkey menu or the floating button.

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         WTR-Lab Reader Content Width
// @description  Control the max-width of the main content area on wtr-lab.com (chapter reader, library, and more). Opens a small panel via the Tampermonkey menu or the floating button.
// @version      1.2.0
// @author       Extracted from WTR-Lab Reader & UI Enhancer by MasuRii
// @namespace    http://tampermonkey.net/
// @match        https://wtr-lab.com/en/novel/*/*/chapter-*
// @include      https://wtr-lab.com/en/library*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // ── Constants ────────────────────────────────────────────────────────────
    const STORAGE_KEY   = 'wtr_lab_reader_width';
    // Covers:
    //   • Chapter pages  → .fix-size[data-slot="card"]  /  .fix-size.chapter-theme
    //   • Library page   → .fix-size.fix-edge (the shared content-width wrapper)
    // All three match the same reader/content container depending on the page type.
    // Nav inner elements also carry fix-size but not these combinations, so they
    // are unaffected.
    const SELECTOR      = '.fix-size[data-slot="card"], .fix-size.chapter-theme, .fix-size.fix-edge';
    const DEFAULT_WIDTH = 760;
    const MIN_WIDTH     = 300;
    const STEP          = 50;

    // ── Styles ───────────────────────────────────────────────────────────────
    GM_addStyle(`
        /* ---- floating trigger button ---- */
        #wtrw-btn {
            position: fixed;
            bottom: 18px;
            right: 18px;
            z-index: 99998;
            background: #0d6efd;
            color: #fff;
            border: none;
            border-radius: 50%;
            width: 42px;
            height: 42px;
            font-size: 18px;
            cursor: pointer;
            box-shadow: 0 2px 8px rgba(0,0,0,.35);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background .15s;
            line-height: 1;
        }
        #wtrw-btn:hover { background: #0b5ed7; }

        /* ---- overlay ---- */
        #wtrw-overlay {
            display: none;
            position: fixed;
            inset: 0;
            background: rgba(0,0,0,.65);
            backdrop-filter: blur(4px);
            z-index: 99999;
            align-items: center;
            justify-content: center;
        }
        #wtrw-overlay.open { display: flex; }

        /* ---- panel ---- */
        #wtrw-panel {
            background: var(--bs-component-bg, #fff);
            color: var(--bs-body-color, #212529);
            border: 1px solid var(--bs-border-color, #dee2e6);
            border-radius: 10px;
            padding: 28px 24px 20px;
            width: 320px;
            max-width: 94vw;
            box-shadow: 0 20px 40px rgba(0,0,0,.18);
            display: flex;
            flex-direction: column;
            gap: 18px;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        }

        #wtrw-panel h2 {
            margin: 0;
            font-size: 1.05rem;
            font-weight: 600;
            text-align: center;
            color: inherit;
        }

        /* ---- control row ---- */
        #wtrw-controls {
            display: flex;
            align-items: center;
            gap: 8px;
        }

        #wtrw-controls label {
            display: block;
            font-size: .8rem;
            margin-bottom: 6px;
            font-weight: 500;
            color: inherit;
        }

        .wtrw-label-row {
            display: flex;
            flex-direction: column;
            width: 100%;
        }

        #wtrw-input {
            flex: 1;
            min-width: 0;
            text-align: center;
            padding: 8px 6px;
            border: 1px solid var(--bs-border-color, #dee2e6);
            border-radius: 6px;
            background: var(--bs-tertiary-bg, #f8f9fa);
            color: inherit;
            font-size: .9rem;
            height: 40px;
        }

        .wtrw-btn {
            height: 40px;
            min-width: 40px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            font-size: .9rem;
            background: #0d6efd;
            color: #fff;
            transition: background .15s;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
        }
        .wtrw-btn:hover  { background: #0b5ed7; }
        .wtrw-btn.reset  { background: #dc3545; }
        .wtrw-btn.reset:hover { background: #bb2d3b; }
        .wtrw-btn.close  { background: #6c757d; width: 100%; }
        .wtrw-btn.close:hover { background: #5a6268; }

        #wtrw-hint {
            font-size: .75rem;
            color: var(--bs-secondary-color, #6c757d);
            text-align: center;
        }
    `);

    // ── State ─────────────────────────────────────────────────────────────────
    let currentWidth = Math.max(MIN_WIDTH, parseInt(GM_getValue(STORAGE_KEY, DEFAULT_WIDTH), 10));

    // ── Apply width to page ───────────────────────────────────────────────────
    function applyWidth(width) {
        currentWidth = Math.max(MIN_WIDTH, parseInt(width, 10));
        if (isNaN(currentWidth)) currentWidth = DEFAULT_WIDTH;

        let el = document.getElementById('wtrw-style');
        if (!el) {
            el = document.createElement('style');
            el.id = 'wtrw-style';
            document.head.appendChild(el);
        }
        el.textContent = `${SELECTOR} { max-width: ${currentWidth}px !important; }`;
        GM_setValue(STORAGE_KEY, currentWidth);
    }

    // ── Build UI ──────────────────────────────────────────────────────────────
    function buildUI() {
        // Floating button
        const btn = document.createElement('button');
        btn.id = 'wtrw-btn';
        btn.title = 'Reader Width';
        btn.textContent = '↔';
        document.body.appendChild(btn);

        // Overlay
        const overlay = document.createElement('div');
        overlay.id = 'wtrw-overlay';
        overlay.innerHTML = `
            <div id="wtrw-panel">
                <h2>Reader Content Width</h2>
                <div class="wtrw-label-row">
                    <label for="wtrw-input">Width (px)</label>
                    <div id="wtrw-controls">
                        <button class="wtrw-btn" id="wtrw-dec">−</button>
                        <input type="number" id="wtrw-input" min="${MIN_WIDTH}" step="${STEP}" value="${currentWidth}">
                        <button class="wtrw-btn" id="wtrw-inc">+</button>
                        <button class="wtrw-btn reset" id="wtrw-reset">Reset</button>
                    </div>
                </div>
                <div id="wtrw-hint">Default: ${DEFAULT_WIDTH} px · Min: ${MIN_WIDTH} px · Step: ${STEP} px</div>
                <button class="wtrw-btn close" id="wtrw-close">Close</button>
            </div>
        `;
        document.body.appendChild(overlay);

        // ── Event wiring ──────────────────────────────────────────────────────
        const input = overlay.querySelector('#wtrw-input');

        function setAndSync(val) {
            const v = Math.max(MIN_WIDTH, parseInt(val, 10) || DEFAULT_WIDTH);
            input.value = v;
            applyWidth(v);
        }

        btn.addEventListener('click', openPanel);
        overlay.querySelector('#wtrw-close').addEventListener('click', closePanel);
        overlay.addEventListener('click', e => { if (e.target === overlay) closePanel(); });

        overlay.querySelector('#wtrw-inc').addEventListener('click', () =>
            setAndSync(currentWidth + STEP));

        overlay.querySelector('#wtrw-dec').addEventListener('click', () =>
            setAndSync(currentWidth - STEP));

        overlay.querySelector('#wtrw-reset').addEventListener('click', () =>
            setAndSync(DEFAULT_WIDTH));

        input.addEventListener('change', () => setAndSync(input.value));

        // Keyboard: Escape closes
        document.addEventListener('keydown', e => {
            if (e.key === 'Escape' && overlay.classList.contains('open')) closePanel();
        });
    }

    function openPanel() {
        const input = document.getElementById('wtrw-input');
        if (input) input.value = currentWidth;
        document.getElementById('wtrw-overlay').classList.add('open');
    }

    function closePanel() {
        document.getElementById('wtrw-overlay').classList.remove('open');
    }

    // ── Re-apply on Next.js client-side navigation ───────────────────────────
    // Next.js swaps page content without a full reload; a MutationObserver on
    // the body re-injects the style whenever the DOM changes significantly.
    function watchForNavigation() {
        let lastHref = location.href;
        const observer = new MutationObserver(() => {
            if (location.href !== lastHref) {
                lastHref = location.href;
                // Small delay so Next.js finishes rendering the new page
                setTimeout(() => applyWidth(currentWidth), 300);
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // ── Init ──────────────────────────────────────────────────────────────────
    function init() {
        applyWidth(currentWidth);
        buildUI();
        GM_registerMenuCommand('Reader Content Width — Open Settings', openPanel);
        watchForNavigation();
    }

    // Wait for body (usually already present on chapter/library pages)
    if (document.body) {
        init();
    } else {
        document.addEventListener('DOMContentLoaded', init);
    }

})();