NHentai Flow

Several Quality of Life features: Quick Preview, Queue System, Smart Scroll, Tag Selector, and 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         NHentai Flow
// @namespace    NEnhanced
// @version      1.3.2
// @description  Several Quality of Life features: Quick Preview, Queue System, Smart Scroll, Tag Selector, and more.
// @author       Testador
// @match        https://nhentai.net/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      nhentai.net
// @connect      i.nhentai.net
// @icon         https://external-content.duckduckgo.com/ip3/nhentai.net.ico
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // ==========================================================================
    // 0. SETTINGS & CONFIGURATION MANAGER
    // ==========================================================================

    const DEFAULT_SETTINGS = {
        previewNav: true,
        highlightVisited: true,
        showTagOverlay: true,
        enableQueue: true,
        enableTagSelect: true,
        enableSavedSearch: true,
        smartNav: true,
        paginationRight: false
    };

    let settings = { ...DEFAULT_SETTINGS, ...JSON.parse(localStorage.getItem('nhentai_flow_settings') || '{}') };

    function saveSettings() {
        localStorage.setItem('nhentai_flow_settings', JSON.stringify(settings));
        applySettings();
    }

    function applySettings() {
        const body = document.body;

        settings.highlightVisited ? body.classList.add('f-visited') : body.classList.remove('f-visited');
        settings.paginationRight ? body.classList.add('f-pag-right') : body.classList.remove('f-pag-right');
        settings.showTagOverlay ? body.classList.add('f-tags') : body.classList.remove('f-tags');
        settings.enableQueue ? body.classList.add('f-queue') : body.classList.remove('f-queue');
        settings.enableTagSelect ? body.classList.add('f-tag-select') : body.classList.remove('f-tag-select');
        settings.enableSavedSearch ? body.classList.add('f-saved-search') : body.classList.remove('f-saved-search');

        settings.previewNav ? body.classList.remove('disable-preview-nav') : body.classList.add('disable-preview-nav');
    }

    applySettings();

    // ==========================================================================
    // 1. SHARED UTILS & CSS
    // ==========================================================================
    const EXT_MAP = { 'j': 'jpg', 'p': 'png', 'g': 'gif', 'w': 'webp' };
    const CACHE_LIMIT = 5;
    const cache = new Map();
    const states = new Map();
    let hoveredGallery = null;
    let hoverTimeout = null;
    let readingQueue = JSON.parse(localStorage.getItem('nhentai_queue_v1') || '[]');
    const SMART_NAV_THRESHOLD = 600;

    const isReader = !!document.querySelector('#image-container');
    if (!isReader) document.body.classList.add('is-gallery-page');

    const css = `
        /* --- CORE PREVIEW STYLES --- */
        .gallery { position: relative; vertical-align: top; }
        .gallery.is-previewing .cover { padding-bottom: 0 !important; height: auto !important; display: flex; flex-direction: column; }
        .gallery.is-previewing .cover img { position: relative !important; height: auto !important; width: 100% !important; max-height: none !important; object-fit: contain; }
        .inline-preview-ui { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; }
        .gallery:hover .inline-preview-ui, .gallery.is-previewing .inline-preview-ui { display: block; }
        .gallery { vertical-align: bottom !important; }

        /* --- HIGHLIGHT VISITED --- */
        body.f-visited .gallery a:visited .caption { background: #900c2a !important; color: #ecc !important; }

        /* --- UI ELEMENTS (Hotzones/Seek) --- */
        .hotzone { position: absolute; top: 0; height: calc(100% - 15px); width: 40%; cursor: default; z-index: 20; }
        .hotzone-left { left: 0; } .hotzone-right { right: 0; }
        .seek-container { position: absolute; bottom: 0; left: 0; width: 100%; height: 20px; z-index: 40; cursor: pointer; display: flex; align-items: flex-end; }
        .seek-bg { width: 100%; height: 3px; background: rgba(255,255,255,0.2); transition: height 0.1s; position: relative; backdrop-filter: blur(2px); }
        .seek-container:hover .seek-bg { height: 15px; background: rgba(255,255,255,0.3); }
        .seek-fill { height: 100%; background: #ed2553; width: 0%; transition: width 0.1s; }
        .seek-tooltip { position: absolute; bottom: 17px; transform: translateX(-50%); background: #ed2553; color: #fff; font-size: 10px; padding: 2px 4px; border-radius: .3em; opacity: 0; pointer-events: none; white-space: nowrap; font-weight: bold; transition: opacity 0.1s; }
        .seek-container:hover .seek-tooltip { opacity: 1; }

        /* --- TAGS & QUEUE TRIGGERS --- */
        .tag-trigger, .queue-trigger { position: absolute; top: 5px; background: rgba(0,0,0,0.6); color: #fff; font-size: 10px; padding: 2px 6px; border-radius: .3em; cursor: pointer; z-index: 50; font-family: sans-serif; opacity: 0.7; border: 1px solid rgba(255,255,255,0.2); }
        .tag-trigger { left: 5px; cursor: help; }
        .queue-trigger { right: 5px; }
        .tag-trigger:hover, .queue-trigger:hover { opacity: 1; background: #ed2553; border-color: #ed2553; }
        .tag-trigger:hover { width: 190px; }
        .queue-trigger.in-queue { background: #ed2553; border-color: #ed2553; opacity: 1; }

        body:not(.f-tags) .tag-trigger { display: none !important; }
        body:not(.f-queue) .queue-trigger { display: none !important; }

        /* --- TAG POPUP --- */
        .tag-popup { display: none; position: absolute; top: 24px; left: 5px; width: 215px; max-height: 250px; overflow-y: auto; background: rgba(15,15,15,0.95); color: #ddd; border: 1px solid #333; border-radius: .3em; padding: 8px; font-size: 11px; z-index: 60; box-shadow: 0 4px 10px rgba(0,0,0,0.5); text-align: left; line-height: 1.4; }
        .tag-trigger:hover + .tag-popup, .tag-popup:hover { display: block; }
        .tag-category { color: #ed2553; font-weight: bold; margin-bottom: 2px; margin-top: 6px; font-size: 10px; text-transform: uppercase; }
        .tag-pill { display: inline-block; background: #333; padding: 1px 4px; margin: 1px; border-radius: .3em; color: #ccc; }
        .tag-pill.tier-mythic { border: 1px solid #b655f7; color: #d6a0fb; text-shadow: 0 0 5px rgba(168, 85, 247, 0.8); }
        .tag-pill.tier-rare { border: 1px solid #eab308; color: #fef08a; }
        .tag-pill.tier-uncommon { border: 1px solid #0740EB; }
        .tag-pill.style-lgbt { border: none !important; background-image: linear-gradient(144deg, rgba(231, 0, 0, 1) 0%, rgba(255, 140, 0, 1) 20%, rgba(255, 239, 0, 1) 40%, rgba(0, 129, 31, 1) 60%, rgba(0, 68, 255, 1) 80%, rgba(118, 0, 137, 1) 100%); color: #000000 !important; font-weight: 900; text-shadow: 0 0 2px rgba(255,255,255,0.8); }

        /* --- READER STYLES --- */
        #image-container { cursor: default; }
        .exit-fs-indicator { display: none; }
        :fullscreen .exit-fs-indicator { display: block; position: fixed; top: 0; left: 50%; transform: translateX(-50%); font-size: 40px; cursor: pointer; transition: all 0.2s; text-shadow: 0 2px 5px rgba(0,0,0,0.8); padding: 20px 65px; opacity: 0; }
        :fullscreen .exit-fs-indicator:hover { color: #ed2553; transform: translateX(-50%) scale(1.4); opacity: 1; }

        /* --- SMART NAVIGATION & PAGINATION --- */
        .smart-nav-bar { position: fixed; bottom: 0; left: 0; height: 5px; background: #ed2553; width: 0%; z-index: 9999; transition: width 0.1s linear; box-shadow: 0 -2px 10px rgba(237, 37, 83, 0.5); pointer-events: none; }
        body.is-gallery-page #content { padding-bottom: 200px !important; }

        @media (min-width: 900px) {
            body.is-gallery-page .pagination { position: fixed !important; top: 50% !important; transform: translateY(-50%) !important; display: flex !important; flex-direction: column !important; z-index: 4; left: 8px !important; right: auto !important; }
            body.is-gallery-page a.first, body.is-gallery-page a.previous, body.is-gallery-page a.last, body.is-gallery-page a.next { transform: rotate(90deg); }
            body.f-pag-right.is-gallery-page .pagination { left: auto !important; right: 16px !important; }
        }

        /* --- TAG SELECTOR & QUEUE BTN --- */
        @media (min-width: 900px) { #info { width: 580px; } }
        .btn-tag-selector.is-active, .btn-queue-add.in-queue { background-color: #ed2553 !important; }
        .tag-container .tag.tag-selected .name { background: #ed2553 !important; opacity: 1 !important; }
        .tags-selecting-mode .tag:not(.tag-selected) { opacity: 0.6; }
        body:not(.f-queue) .btn-queue-add, body:not(.f-queue) .btn-next-queue { display: none !important; }
        body:not(.f-tag-select) .btn-tag-selector { display: none !important; }

        /* --- QUEUE DOCK --- */
        .queue-dock { position: fixed; bottom: 20px; right: 20px; display: flex; flex-direction: column; align-items: flex-end; z-index: 100; pointer-events: none; }
        body:not(.f-queue) .queue-dock { display: none !important; }

        .queue-toggle-btn { width: 40px; height: 40px; border-radius: 50%; background: #1f1f1f; border: 2px solid #333; color: #fff; font-size: 15px; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; transition: all 0.2s; position: relative; pointer-events: auto; }
        .queue-toggle-btn:hover { background: #ed2553; border-color: #ed2553; transform: scale(1.3); }
        .queue-count { position: absolute; top: -5px; right: -5px; background: #ed2553; color: #fff; font-size: 10px; font-weight: bold; width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; border: 2px solid #1f1f1f; }
        .queue-panel { background: #1f1f1f; width: 300px; border: 1px solid #333; border-radius: 5px; overflow: hidden; margin-bottom: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.6); display: block; visibility: hidden; opacity: 0; transform: translateY(20px); transition: transform 0.2s, opacity 0.1s ease; pointer-events: auto; }
        .queue-panel.is-visible { visibility: visible; opacity: 1; transform: translateY(0); }
        .queue-header { padding: 10px 15px; background: #222; border-bottom: 1px solid #333; display: flex; justify-content: space-between; align-items: center; font-weight: bold; color: #eee; font-size: 13px; }
        .queue-clear, .queue-view-full { cursor: pointer; color: #ccc; font-size: 12px; transition: color 0.2s; }
        .queue-clear:hover, .queue-view-full:hover { color: #fff; }
        .queue-view-full { margin-right: 15px; }
        .queue-list { max-height: 350px; overflow-y: auto; padding: 0; margin: 0; list-style: none; }
        .queue-item { display: flex; padding: 8px; border-bottom: 1px solid #2a2a2a; transition: background 0.2s; position: relative; content-visibility: auto; contain-intrinsic-size: 62.5834px; }
        .queue-item:hover { background: #2a2a2a; }
        .queue-item img { width: 40px; height: 58px; object-fit: cover; border-radius: .3em; margin-right: 10px; }
        .queue-info { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; }
        .queue-title { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; margin-bottom: 4px; }
        .queue-title:hover { color: #ed2553; }
        .queue-id { font-size: 10px; color: #666; }
        .queue-remove { margin: 8px; color: #555; cursor: pointer; padding: 5px; display: flex; align-items: center; }
        .queue-remove:hover { color: #ed2553; }
        .queue-empty { padding: 20px; text-align: center; color: #666; font-size: 12px; font-style: italic; }

        .queue-toolbar { display: flex; gap: 10px; }
        .btn-q-tool { background: #4d4d4d; color: #fff; border: none; padding: 6px 12px; border-radius: .3em; font-size: 12px; cursor: pointer; display: flex; align-items: center; font-weight: bold; }
        .btn-q-tool:hover { background: #595959;  }
        .btn-q-tool.active { background: #ed2553; }
        .btn-q-tool i { margin-right: 6px; }
        @media (max-width: 600px) { .queue-toolbar { flex-wrap: wrap; } }

        /* --- PREVIEW NAV SETTINGS --- */
        body.disable-preview-nav .hotzone,
        body.disable-preview-nav .seek-container { display: none !important; }

        /* --- SAVED SEARCHES STYLES  --- */
        .search-saved-trigger { position: absolute; right: 45px; top: 0; height: 100%; width: 35px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #888; z-index: 5; }
        .search-saved-trigger:hover, .search-saved-trigger.is-active { color: #ed2553; }
        .saved-search-extension { position: relative; width: 100%; background: #1f1f1f; padding: 15px 20px; box-sizing: border-box; z-index: 5; display: block; visibility: hidden; opacity: 0; transform: scale(0.95); position: absolute; transition: transform 0.2s, opacity 0.1s ease }
        .saved-search-extension.is-visible { visibility: visible; opacity: 1; transform: scale(1); position: relative; }
        .sse-header { display: flex; align-items: center; margin-bottom: 12px; border-bottom: 1px solid #333; padding-bottom: 8px; }
        .sse-title { font-weight: bold; color: #eee; margin-right: 15px; }
        .sse-actions { margin-left: auto; }
        .btn-sse-save, .btn-sse-edit { background: #4d4d4d; color: #fff; border: none; padding: 4px 10px; border-radius: .3em; font-size: 11px; cursor: pointer; }
        .btn-sse-save:hover, .btn-sse-edit:hover { background: #595959; }
        .btn-sse-edit.is-active { background: #ed2553; animation: pulseRed 2s infinite; }
        @keyframes pulseRed { 0% { box-shadow: 0 0 0 0 rgba(237, 37, 83, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(237, 37, 83, 0); } 100% { box-shadow: 0 0 0 0 rgba(237, 37, 83, 0); } }
        .sse-empty { color: #666; font-style: italic; font-size: 12px; }
        .sse-list { display: flex; flex-wrap: wrap; gap: 8px; }
        .ss-pill { font-weight: 700; display: inline-flex; align-items: stretch; border-radius: .3em; overflow: hidden; }
        .ss-pill.is-current .ss-text { background: #ed2553; }
        .ss-part { padding: .13em .39em; cursor: pointer; display: flex; align-items: center; background: #4d4d4d }
        .ss-add { padding: .13em .69em; background: #333; color: grey; box-shadow: inset 0 0 .4em #2b2b2b; }
        .ss-add:hover { background: #404040; color: #fff; }
        .ss-text:hover { background: #595959; }
        .sse-list.delete-mode .ss-add { pointer-events: none; opacity: 0.3; background: #000; }
        .sse-list.delete-mode .ss-text { color: #f15478; cursor: alias; }
        .sse-list.delete-mode .ss-text:hover { background: #f15478; color: #fff; text-decoration: line-through; }
        body:not(.f-saved-search) .search-saved-trigger,
        body:not(.f-saved-search) .saved-search-extension { display: none !important; }

        /* --- SEARCH SHORTCUT HINT --- */
        .search-slash-hint { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: #999; font-size: 12px; pointer-events: none; font-family: Consolas, monospace; }
        form.search input:focus ~ .search-slash-hint, form.search input:not(:placeholder-shown) ~ .search-slash-hint { opacity: 0; }
        form.search { position: relative; }

        /* --- SETTINGS MODAL STYLES --- */
        .nh-settings-modal { display: flex; visibility: hidden; opacity: 0; pointer-events: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 11000; align-items: center; justify-content: center; }
        .nh-settings-modal.is-open { visibility: visible; opacity: 1; pointer-events: auto; }
        .nh-settings-panel { background: #1f1f1f; width: 450px; padding: 10px 30px 15px; border-radius: 5px; box-shadow: 0 0 60px rgba(0,0,0,.5); display: flex; flex-direction: column; transform: scale(0.95) translateY(-20px); opacity: 0; transition: transform 0.2s, opacity 0.1s ease; }
        .nh-settings-modal.is-open .nh-settings-panel { transform: scale(1) translateY(0); opacity: 1; }
        .nh-settings-panel h2 { margin: .67em 0; color: #d9d9d9; font-size: 1.5em; font-weight: 700; text-align: center; }
        .nh-setting-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 0; }
        .nh-setting-label { color: #d9d9d9; font-size: 14px; font-weight: 700; }
        .nh-toggle { cursor: pointer; display: inline-block; position: relative; width: 44px; height: 24px; background: #333; border-radius: 12px; transition: .2s; box-shadow: inset 0 1px 3px rgba(0,0,0,0.3); }
        .nh-toggle::after { content: ''; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; background: #888; border-radius: 50%; transition: .2s; box-shadow: 0 1px 2px rgba(0,0,0,0.3); }
        .nh-toggle.active { background: #ed2553; }
        .nh-toggle.active::after { left: 23px; background: #fff; }
        .nh-close-btn { width: 100%; background: #ed2553; color: #fff; border: none; padding: 12px; margin-top: 20px; cursor: pointer; border-radius: .3em; font-weight: bold; }
        .nh-close-btn:hover { background: #f15478; }
        .nav-settings-btn { cursor: pointer; padding-left: 10px; padding-right: 10px; line-height: 50px; display: block; }
        .nav-settings-btn:hover { background-color: #383838; }
        .nh-settings-actions { margin-top: 20px; display: flex; gap: 10px; }
        .btn-data { flex: 1; padding: 8px; border: none; background: #4d4d4d; color: #fff; cursor: pointer; border-radius: .3em; font-weight: bold }
        .btn-data:hover { background: #595959; }
        .btn-data i { margin-right: 5px; }

    `;

    (typeof GM_addStyle !== "undefined") ? GM_addStyle(css) : document.head.appendChild(Object.assign(document.createElement('style'), { textContent: css }));

    // ==========================================================================
    // 2. QUEUE LOGIC
    // ==========================================================================

    let saveQueueTimeout;
    let currentSortMode = localStorage.getItem('nhentai_queue_sort') || 'newest';

    function saveQueue() {
        updateQueueWidget();
        updateAllQueueButtons();

        clearTimeout(saveQueueTimeout);
        saveQueueTimeout = setTimeout(() => {
            localStorage.setItem('nhentai_queue_v1', JSON.stringify(readingQueue));
        }, 1000);
    }

    function toggleQueueItem(id, title, coverUrl, galleryUrl) {
        const index = readingQueue.findIndex(i => i.id == id);

        if (index > -1) {
            readingQueue.splice(index, 1);
        } else {
            const newItem = { id, title, coverUrl, galleryUrl, addedAt: Date.now() };

            readingQueue.push(newItem);

            if (currentSortMode === 'newest') {
                readingQueue.sort((a, b) => (b.addedAt || 0) - (a.addedAt || 0));
            } else if (currentSortMode === 'oldest') {
                readingQueue.sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0));
            }
        }

        saveQueue();
    }

    function isQueued(id) {
        return readingQueue.some(i => i.id == id);
    }

    function clearQueue() {
        if(confirm('Clear reading queue?')) {
            readingQueue = [];
            saveQueue();
            if (window.location.search.includes('view=queue')) renderQueuePage();
        }
    }

    function updateAllQueueButtons() {
        if (!settings.enableQueue) return;

        document.querySelectorAll('.gallery[data-gid]').forEach(gallery => {
            const id = gallery.dataset.gid;
            const btn = gallery.querySelector('.queue-trigger');
            if (btn) {
                if (isQueued(id)) {
                    btn.classList.add('in-queue');
                    btn.innerHTML = '<i class="fa fa-check"></i>';
                } else {
                    btn.classList.remove('in-queue');
                    btn.innerHTML = '<i class="fa fa-plus"></i>';
                }
            }
        });

        const pageBtn = document.querySelector('.btn-queue-add');
        if (pageBtn) {
            const id = window.location.href.match(/\/g\/(\d+)/)?.[1];
            if (id && isQueued(id)) {
                pageBtn.innerHTML = '<i class="fa fa-check"></i> Saved';
                pageBtn.classList.add('in-queue');
            } else {
                pageBtn.innerHTML = '<i class="fa fa-plus"></i> Queue';
                pageBtn.classList.remove('in-queue');
            }
        }
    }

    function updateQueueWidget() {
        if (!settings.enableQueue) return;

        const list = document.querySelector('.queue-list');
        const count = document.querySelector('.queue-count');
        if (!list || !count) return;

        count.textContent = readingQueue.length;
        count.style.display = readingQueue.length > 0 ? 'flex' : 'none';

        if (readingQueue.length === 0) {
            list.innerHTML = '<li class="queue-empty">Queue is empty.</li>';
        } else {
            list.innerHTML = readingQueue.map(item => `
                <li class="queue-item" data-id="${item.id}"> <a href="${item.galleryUrl}">
                        <img src="${item.coverUrl}" loading="lazy">
                    </a>
                    <div class="queue-info">
                        <a href="${item.galleryUrl}" class="queue-title" title="${item.title}">${item.title}</a>
                        <div class="queue-id">#${item.id}</div>
                    </div>
                    <div class="queue-remove" data-id="${item.id}" title="Remove"><i class="fa fa-times"></i></div>
                </li>
            `).join('');

            list.querySelectorAll('.queue-remove').forEach(btn => {
                btn.onclick = (e) => {
                    e.stopPropagation();
                    const item = readingQueue.find(i => i.id == btn.dataset.id);
                    if(item) toggleQueueItem(item.id, item.title, item.coverUrl, item.galleryUrl);
                };
            });
        }
    }

    function initQueueWidget() {
        if (!settings.enableQueue) return;
        if (isReader) return;
        if (document.querySelector('.queue-dock')) return;

        const dock = document.createElement('div');
        dock.className = 'queue-dock';

        dock.innerHTML = `
            <div class="queue-panel">
                <div class="queue-header">
                    <span><i class="fa fa-book"></i> Reading Queue</span>
                    <div>
                        <a href="/?view=queue" class="queue-view-full">View Page</a>
                        <span class="queue-clear">Clear All</span>
                    </div>
                </div>
                <ul class="queue-list"></ul>
            </div>
            <div class="queue-toggle-btn" title="Toggle Queue">
                <i class="fa fa-list-ul"></i>
                <div class="queue-count">0</div>
            </div>
        `;

        document.body.appendChild(dock);

        const toggle = dock.querySelector('.queue-toggle-btn');
        const panel = dock.querySelector('.queue-panel');
        const clearBtn = dock.querySelector('.queue-clear');
        const list = dock.querySelector('.queue-list');

        toggle.onclick = () => {
            const isVisible = panel.classList.toggle('is-visible');

            if (isVisible) {
                const currentId = window.location.pathname.match(/\/g\/(\d+)/)?.[1];

                if (currentId) {
                    const activeItem = list.querySelector(`.queue-item[data-id="${currentId}"]`);

                    if (activeItem) {
                        requestAnimationFrame(() => {
                            requestAnimationFrame(() => {
                                activeItem.scrollIntoView({ behavior: 'auto', block: 'center' });

                                const originalBg = activeItem.style.background;
                                activeItem.style.background = '#333';
                                activeItem.style.transition = 'background 0.5s';
                                setTimeout(() => { activeItem.style.background = originalBg; }, 1000);
                            });
                        });
                    }
                }
            }
        };

        clearBtn.onclick = clearQueue;
        updateQueueWidget();
    }

    function createPagination(totalItems, currentPage, itemsPerPage) {
        const totalPages = Math.ceil(totalItems / itemsPerPage);
        if (totalPages <= 1) return '';

        let html = '<section class="pagination">';

        if (currentPage > 1) {
            html += `<a href="/?view=queue&page=${currentPage - 1}" class="page previous"><i class="fa fa-chevron-left"></i></a>`;
        }

        for (let i = 1; i <= totalPages; i++) {
            if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) {
                const isCurrent = i === currentPage ? 'current' : '';
                html += `<a href="/?view=queue&page=${i}" class="page ${isCurrent}">${i}</a>`;
            } else if (i === currentPage - 3 || i === currentPage + 3) {
                html += `<span class="page">...</span>`;
            }
        }

        if (currentPage < totalPages) {
            html += `<a href="/?view=queue&page=${currentPage + 1}" class="page next"><i class="fa fa-chevron-right"></i></a>`;
        }

        html += '</section>';
        return html;
    }

    function applyQueueSort(mode) {
        currentSortMode = mode;
        localStorage.setItem('nhentai_queue_sort', mode);

        if (mode === 'shuffle') {
            for (let i = readingQueue.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [readingQueue[i], readingQueue[j]] = [readingQueue[j], readingQueue[i]];
            }
        } else if (mode === 'newest') {
            readingQueue.sort((a, b) => (b.addedAt || 0) - (a.addedAt || 0));
        } else if (mode === 'oldest') {
            readingQueue.sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0));
        }

        saveQueue();

        const url = new URL(window.location);
        url.searchParams.set('page', '1');
        window.history.pushState({}, '', url);
        renderQueuePage();
    }

    function renderQueuePage() {
        const params = new URLSearchParams(window.location.search);
        if (params.get('view') !== 'queue') return;

        document.title = "Reading Queue";
        const content = document.getElementById('content');
        if (!content) return;
        content.innerHTML = '';

        const ITEMS_PER_PAGE = 25;
        const currentPage = parseInt(params.get('page')) || 1;
        const totalItems = readingQueue.length;

        const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
        const endIndex = startIndex + ITEMS_PER_PAGE;
        const currentItems = readingQueue.slice(startIndex, endIndex);

        let queueSizeKB = "0.00";
        try {
            const jsonString = JSON.stringify(readingQueue);
            const bytes = new TextEncoder().encode(jsonString).length;
            queueSizeKB = (bytes / 1024).toFixed(2);
        } catch (e) { console.error("Error calc size", e); }

        const header = document.createElement('div');
        header.className = 'container';

        header.innerHTML = `
            <h1>
                <span class="name">Reading Queue</span>
                <span class="count">(${totalItems})</span>
            </h1>
            <div style="margin-bottom: 20px; color: #888; font-size: 13px;">
                 <span style="margin-right: 15px;">
                    <i class="fa fa-database"></i> Storage Used: <b>${queueSizeKB} KB</b>
                 </span>
                 <span>
                    <i class="fa fa-check-circle"></i> Data is stored locally
                 </span>
            </div>
        `;
        content.appendChild(header);

        if (totalItems > 1) {
            const toolbar = document.createElement('div');
            toolbar.className = 'container queue-toolbar';

            const buttons = [
                { id: 'newest', icon: 'fa-sort-amount-down', label: 'Newest First' },
                { id: 'oldest', icon: 'fa-sort-amount-up', label: 'Oldest First' },
                { id: 'shuffle', icon: 'fa-random', label: 'Shuffle' }
            ];

            buttons.forEach(btn => {
                const buttonEl = document.createElement('button');
                const isActive = currentSortMode === btn.id ? 'active' : '';
                buttonEl.className = `btn-q-tool ${isActive}`;
                buttonEl.innerHTML = `<i class="fa ${btn.icon}"></i> ${btn.label}`;

                buttonEl.onclick = () => applyQueueSort(btn.id);

                toolbar.appendChild(buttonEl);
            });

            content.appendChild(toolbar);
        }

        const grid = document.createElement('div');
        grid.className = 'container index-container';

        if (totalItems === 0) {
            grid.innerHTML = '<div style="padding: 50px; text-align: center; font-size: 18px; color: #666;">Your queue is empty.</div>';
        } else {
            currentItems.forEach(item => {
                const galleryDiv = document.createElement('div');
                galleryDiv.className = 'gallery';
                galleryDiv.setAttribute('data-gid', item.id);

                const defaultPadding = '145%';

                galleryDiv.innerHTML = `
                    <a href="${item.galleryUrl}" class="cover" style="padding:0 0 ${defaultPadding} 0; position: relative; display: block;">
                        <img class="lazyload"
                             alt="${item.title}"
                             src="${item.coverUrl}"
                             style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;" />
                        <div class="caption">${item.title}</div>
                    </a>
                `;

                const img = galleryDiv.querySelector('img');
                const setRatio = function() {
                     if (this.naturalWidth && this.naturalHeight) {
                        const aspectRatio = (this.naturalHeight / this.naturalWidth) * 100;
                        const coverLink = galleryDiv.querySelector('.cover');
                        coverLink.style.padding = `0 0 ${aspectRatio}% 0`;
                    }
                };

                img.onload = setRatio;
                if (img.complete) setRatio.call(img);

                grid.appendChild(galleryDiv);
            });
        }

        content.appendChild(grid);

        if (totalItems > ITEMS_PER_PAGE) {
            const paginationHTML = createPagination(totalItems, currentPage, ITEMS_PER_PAGE);
            const pagContainer = document.createElement('div');
            pagContainer.innerHTML = paginationHTML;
            content.appendChild(pagContainer);
        }

        if (typeof initPreviewUI === 'function') {
            grid.querySelectorAll('.gallery').forEach(initPreviewUI);
        }
    }

    // ==========================================================================
    // 3. PREVIEW LOGIC
    // ==========================================================================

    function getMeta(id) {
        if (cache.has(id)) return Promise.resolve(cache.get(id));

        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `/api/gallery/${id}`,
                timeout: 10000,
                onload: (res) => {
                    if (res.status === 200) {
                        try {
                            const data = JSON.parse(res.responseText);
                            const meta = {
                                id: data.media_id,
                                pages: data.images.pages,
                                total: data.num_pages,
                                tags: data.tags,
                                title: data.title.english || data.title.japanese || data.title.pretty,
                                cover_type: data.images.cover.t
                            };

                            if (cache.size >= CACHE_LIMIT) {
                                const oldestKey = cache.keys().next().value;
                                cache.delete(oldestKey);
                            }

                            cache.set(id, meta);
                            resolve(meta);
                        } catch(e) {
                            console.error("NH Flow: Error in suing JSON", e);
                            resolve(null);
                        }
                    } else {
                        resolve(null);
                    }
                },
                onerror: () => resolve(null),
                ontimeout: () => resolve(null)
            });
        });
    }

    function buildTagList(tags) {
        const groups = { artist: [], parody: [], character: [], tag: [] };
        const fmt = (n) => n >= 1000 ? (n/1000).toFixed(1) + 'k' : n;
        const getTier = (c) => {
            if (c < 1000) return 'tier-mythic';
            if (c < 5000) return 'tier-rare';
            if (c < 20000) return 'tier-uncommon';
            return '';
        };
        const getGenreStyle = (name) => {
            if (['yaoi', 'males only', 'bara', 'yuri', 'females only', 'lesbian', 'futanari', 'tomgirl', 'otokonoko', 'dickgirl', 'shemale', 'bisexual'].includes(name)) return 'style-lgbt';
            return '';
        };

        tags.forEach(t => {
            const count = t.count || 0;
            let className = '';
            if (t.type === 'tag') {
                className = `${getTier(count)} ${getGenreStyle(t.name)}`;
            }
            const html = `<span class="tag-pill ${className}" title="${t.name} (${fmt(count)})">${t.name}</span>`;
            if (groups[t.type]) groups[t.type].push(html);
            else if (t.type === 'group') groups.artist.push(`<span class="tag-pill">[${t.name}]</span>`);
        });

        let html = '';
        const addGroup = (title, list) => { if (list.length) html += `<div class="tag-category">${title}</div>` + list.join(''); };
        addGroup('Artists', groups.artist); addGroup('Parodies', groups.parody); addGroup('Characters', groups.character); addGroup('Tags', groups.tag);
        return html || '<div style="padding:5px">No tags</div>';
    }

    function update(gallery, val, isJump = false) {
        const id = gallery.dataset.gid;
        const state = states.get(id) || { curr: 1, req: 0 };
        states.set(id, state);

        getMeta(id).then(meta => {
            if (!meta) return;

            let next = isJump ? val : state.curr + val;
            if (next < 1) next = 1; if (next > meta.total) next = meta.total;

            const popup = gallery.querySelector('.tag-popup');
            if (popup && !popup.innerHTML) popup.innerHTML = buildTagList(meta.tags);

            if (next === state.curr && !isJump && val !== 0) return;
            state.curr = next;
            const reqId = ++state.req;

            if (state.curr !== 1) gallery.classList.add('is-previewing');

            const barFill = gallery.querySelector('.seek-fill');
            if (barFill) barFill.style.width = `${(state.curr / meta.total) * 100}%`;

            const pageData = meta.pages[state.curr - 1];
            const src = `https://i.nhentai.net/galleries/${meta.id}/${state.curr}.${EXT_MAP[pageData.t]}`;
            const img = gallery.querySelector('a.cover img');
            const loader = new Image();
            loader.onload = () => { if (state.req === reqId) { img.style.aspectRatio = `${pageData.w}/${pageData.h}`; img.src = src; } };
            loader.src = src;
        });
    }

    function initPreviewUI(gallery) {
        const link = gallery.querySelector('a.cover');
        if (!link) return;
        const id = link.href.match(/\/g\/(\d+)\//)?.[1];
        if (!id) return;
        gallery.dataset.gid = id; gallery.dataset.init = '1';

        const ui = document.createElement('div');
        ui.className = 'inline-preview-ui';

        ui.innerHTML = `
            <div class="tag-trigger">TAGS</div>
            <div class="tag-popup"></div>
            <div class="queue-trigger" title="Add/Remove from Queue (Q)"><i class="fa fa-plus"></i></div>
            <div class="hotzone hotzone-left"></div>
            <div class="hotzone hotzone-right"></div>
            <div class="seek-container"><div class="seek-bg"><div class="seek-fill"></div></div><div class="seek-tooltip">Pg 1</div></div>
        `;

        const qBtn = ui.querySelector('.queue-trigger');
        if (isQueued(id)) {
            qBtn.classList.add('in-queue');
            qBtn.innerHTML = '<i class="fa fa-check"></i>';
        }

        qBtn.onclick = (e) => {
            e.preventDefault(); e.stopPropagation();
            if (cache.has(id)) {
                const meta = cache.get(id);
                const coverUrl = gallery.querySelector('a.cover img').dataset.src || gallery.querySelector('a.cover img').src;
                toggleQueueItem(id, meta.title, coverUrl, link.href);
            } else {
                qBtn.innerHTML = '<i class="fas fa-ellipsis-h"></i>';
                getMeta(id).then(meta => {
                    if (!meta) {
                        qBtn.innerHTML = '<i class="fa fa-exclamation"></i>';
                        return;
                    }
                    const coverUrl = gallery.querySelector('a.cover img').dataset.src || gallery.querySelector('a.cover img').src;
                    toggleQueueItem(id, meta.title, coverUrl, link.href);
                });
            }
        };

        ui.querySelector('.hotzone-left').onclick = (e) => { e.preventDefault(); e.stopPropagation(); update(gallery, -1); };
        ui.querySelector('.hotzone-right').onclick = (e) => { e.preventDefault(); e.stopPropagation(); update(gallery, 1); };

        const seek = ui.querySelector('.seek-container');
        const tip = ui.querySelector('.seek-tooltip');
        seek.onmousemove = (e) => {
            if (!cache.has(id)) return;
            const meta = cache.get(id); const rect = seek.getBoundingClientRect();
            const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
            tip.style.left = `${e.clientX - rect.left}px`; tip.textContent = Math.ceil(pct * meta.total) || 1;
        };

        seek.onclick = (e) => {
            e.preventDefault(); e.stopPropagation();
            if (!cache.has(id)) {
                 update(gallery, 0).then(() => {
                     const rect = seek.getBoundingClientRect();
                     const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
                     const meta = cache.get(id);
                     update(gallery, Math.ceil(pct * meta.total) || 1, true);
                 });
                 return;
            }
            const rect = seek.getBoundingClientRect();
            update(gallery, Math.ceil(((e.clientX - rect.left) / rect.width) * cache.get(id).total) || 1, true);
        };

        gallery.onmouseenter = () => {
            hoveredGallery = gallery;
            if (!cache.has(id)) {
                hoverTimeout = setTimeout(() => { update(gallery, 0); }, 300);
            } else { update(gallery, 0); }
        };

        gallery.onmouseleave = () => {
            hoveredGallery = null;
            if (hoverTimeout) { clearTimeout(hoverTimeout); hoverTimeout = null; }
        };

        link.style.position = 'relative'; link.appendChild(ui);
    }

    // ==========================================================================
    // 4. READER LOGIC
    // ==========================================================================

    function initReaderMode() {
        const imageContainer = document.querySelector('#image-container');
        if (!imageContainer || imageContainer.dataset.readerInit) return;
        imageContainer.dataset.readerInit = '1';

        const exitIcon = document.createElement('div');
        exitIcon.className = 'exit-fs-indicator';
        exitIcon.innerHTML = '<i class="fa fa-times"></i>';

        exitIcon.onclick = (e) => {
            e.stopPropagation();
            if (document.fullscreenElement) { document.exitFullscreen(); }
        };
        imageContainer.appendChild(exitIcon);

        imageContainer.addEventListener('click', (e) => {
            if (e.target.tagName === 'IMG' || e.target.tagName === 'A') return;
            const rect = imageContainer.getBoundingClientRect();
            if (e.clientX - rect.left > rect.width / 2) {
                const nextBtn = document.querySelector('.reader-pagination .next');
                if (nextBtn) nextBtn.click();
            } else {
                const prevBtn = document.querySelector('.reader-pagination .previous');
                if (prevBtn) prevBtn.click();
            }
        });

        const toolbars = document.querySelectorAll('.reader-buttons-right');
        if (toolbars.length === 0) return;
        const toolbar = toolbars[toolbars.length - 1];

        // Fullscreen
        if (!document.querySelector('.btn-fullscreen-custom')) {
            const fsBtn = document.createElement('button');
            fsBtn.className = 'btn btn-unstyled btn-fullscreen-custom';
            fsBtn.innerHTML = '<i class="fa fa-expand"></i>';
            fsBtn.title = "Fullscreen (T)";

            const toggleFS = () => {
                if (!document.fullscreenElement) {
                    imageContainer.requestFullscreen().catch(err => console.log(err));
                } else {
                    document.exitFullscreen();
                }
            };
            fsBtn.onclick = toggleFS;

            toolbar.insertBefore(fsBtn, toolbar.firstChild);

            document.addEventListener('keydown', (e) => {
                if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
                if (e.key === 't' || e.key === 'T') { e.preventDefault(); toggleFS(); }
            });

            document.addEventListener('fullscreenchange', () => {
                if (!document.fullscreenElement) {
                    fsBtn.innerHTML = '<i class="fa fa-expand"></i>';
                    setTimeout(() => {
                        imageContainer.scrollIntoView({ behavior: 'auto', block: 'start' });
                    }, 50);
                }
            });
        }

        // Next Queue
        if (settings.enableQueue) {
            const fsBtn = document.querySelector('.btn-fullscreen-custom');
            const currentId = window.location.pathname.match(/\/g\/(\d+)/)?.[1];

            if (currentId && readingQueue.length > 0) {
                const currentIndex = readingQueue.findIndex(i => i.id == currentId);
                if (currentIndex > -1 && currentIndex < readingQueue.length - 1) {
                    const nextItem = readingQueue[currentIndex + 1];
                    const nextQBtn = document.createElement('a');
                    nextQBtn.className = 'btn btn-unstyled btn-next-queue';
                    nextQBtn.innerHTML = `<span style="font-weight:bold;">Next Queue <i class="fa fa-step-forward"></i></span>`;
                    nextQBtn.href = `/g/${nextItem.id}/1/`;
                    nextQBtn.title = `Read Next: ${nextItem.title}`;
                    nextQBtn.style.marginLeft = '10px';

                    if (fsBtn && fsBtn.nextSibling) {
                        toolbar.insertBefore(nextQBtn, fsBtn.nextSibling);
                    } else {
                        toolbar.appendChild(nextQBtn);
                    }
                }
            }
        }

        // Random Favorite
        const hasFavorites = !!document.querySelector('nav a[href*="/favorites/"]');
        if (hasFavorites) {
            const randFavBtn = document.createElement('a');
            randFavBtn.className = 'btn btn-unstyled btn-random-fav';
            randFavBtn.innerHTML = '<span><i class="fa fa-circle-notch fa-spin"></i></span>';
            randFavBtn.style.marginRight = '10px';
            randFavBtn.style.cursor = 'wait';
            randFavBtn.style.opacity = '0.7';

            toolbar.insertBefore(randFavBtn, toolbar.firstChild);

            fetch('/favorites/random')
                .then(response => {
                    const finalUrl = response.url;
                    if (finalUrl && finalUrl.includes('/g/')) {
                        let cleanUrl = finalUrl.split('?')[0];
                        if (!cleanUrl.endsWith('/')) cleanUrl += '/';
                        randFavBtn.href = `${cleanUrl}1/`;
                        randFavBtn.innerHTML = '<span style="font-weight:bold;"></i> Fav <i class="fa fa-random"></i></span>';
                        randFavBtn.title = "Read Random Favorite";
                        randFavBtn.style.cursor = 'pointer';
                        randFavBtn.style.opacity = '1';
                    } else {
                        randFavBtn.remove();
                    }
                })
                .catch(err => { randFavBtn.remove(); });
        }
    }

    // ==========================================================================
    // 5. RANDOM CONTEXTUAL
    // ==========================================================================

    function initRandomContextual() {
        const sortContainer = document.querySelector('.sort');
        if (!sortContainer || sortContainer.querySelector('.btn-random-ctx')) return;

        const btnContainer = document.createElement('div');
        btnContainer.className = 'sort-type';

        const btn = document.createElement('a');
        btn.className = 'btn-random-ctx';
        const ORIGINAL_HTML = '<i class="fa fa-random"></i>';
        btn.innerHTML = ORIGINAL_HTML;
        btn.style.cursor = 'pointer';
        btn.style.padding = '10px 12px';
        btn.title = "Roll a random gallery from these search results";

        const resetBtn = () => {
            btn.innerHTML = ORIGINAL_HTML;
            btn.style.pointerEvents = 'auto';
            btn.style.opacity = '1';
        };

        window.addEventListener('pageshow', (event) => {
            if (event.persisted) { resetBtn(); }
        });
        resetBtn();

        btn.onclick = async (e) => {
            e.preventDefault();
            btn.innerHTML = '<i class="fa fa-circle-notch fa-spin"></i>';
            btn.style.pointerEvents = 'none';
            btn.style.opacity = '0.7';

            try {
                const lastPageBtn = document.querySelector('.pagination .last');
                let totalPages = 1;
                if (lastPageBtn) {
                    const match = lastPageBtn.href.match(/page=(\d+)/);
                    if (match) totalPages = parseInt(match[1], 10);
                } else {
                    const pages = document.querySelectorAll('.pagination .page');
                    if (pages.length > 0) {
                        const lastNum = pages[pages.length - 1].textContent;
                        if (!isNaN(lastNum)) totalPages = parseInt(lastNum, 10);
                    }
                }
                if (totalPages === 1) {
                    const galleries = document.querySelectorAll('.gallery a.cover');
                    if (galleries.length === 0) throw new Error("No galleries found");
                    const randomGallery = galleries[Math.floor(Math.random() * galleries.length)];
                    window.location.href = randomGallery.href;
                    return;
                }

                const randomPage = Math.floor(Math.random() * totalPages) + 1;
                const targetUrl = new URL(window.location.href);
                targetUrl.searchParams.set('page', randomPage);

                const response = await fetch(targetUrl.href);
                const html = await response.text();
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                const galleries = doc.querySelectorAll('.gallery a.cover');
                if (galleries.length === 0) throw new Error("No galleries found");
                const randomGallery = galleries[Math.floor(Math.random() * galleries.length)];
                window.location.href = randomGallery.href;

            } catch (err) {
                btn.innerHTML = '<i class="fa fa-exclamation-triangle"></i>';
                setTimeout(() => { resetBtn(); }, 2000);
            }
        };

        btnContainer.appendChild(btn);
        sortContainer.appendChild(btnContainer);
    }

    // ==========================================================================
    // 6. POPULAR SHORTCUT BUTTON
    // ==========================================================================

    function initPopularShortcut() {
        const popularContainer = document.querySelector('.index-popular');
        if (!popularContainer || popularContainer.querySelector('.btn-view-all-popular')) return;

        const link = document.createElement('a');
        link.href = '/search/?q=pages%3A%3E0&sort=popular-today';
        link.className = 'btn btn-secondary btn-view-all-popular';
        link.style.display = 'block';
        link.style.marginTop = '15px';
        link.innerHTML = '<i class="fa fa-compass"></i> Explore Today’s Trending';
        popularContainer.appendChild(link);
    }

    // ==========================================================================
    // 7. SMART NAVIGATION
    // ==========================================================================

    function initSmartNavigation() {
        if (!settings.smartNav) return;
        if (isReader) return;

        if (document.body.dataset.smartNavInit) return;
        document.body.dataset.smartNavInit = '1';

        const navBar = document.createElement('div');
        navBar.className = 'smart-nav-bar';
        document.body.appendChild(navBar);

        let accumulatedDelta = 0;
        let isNavigating = false;
        let isProcessingScroll = false;

        window.addEventListener('wheel', (e) => {
            if (isNavigating || isProcessingScroll) return;

            isProcessingScroll = true;

            window.requestAnimationFrame(() => {
                const nextLink = document.querySelector('.pagination .next');

                if (!nextLink) {
                    isProcessingScroll = false;
                    return;
                }

                const scrollBottom = window.scrollY + window.innerHeight;
                const docHeight = document.body.scrollHeight;
                const isAtBottom = (docHeight - scrollBottom) < 100;

                if (isAtBottom && e.deltaY > 0) {
                    accumulatedDelta += e.deltaY;
                    const percent = Math.min(100, (accumulatedDelta / SMART_NAV_THRESHOLD) * 100);
                    navBar.style.width = `${percent}%`;

                    if (accumulatedDelta > SMART_NAV_THRESHOLD) {
                        isNavigating = true;
                        navBar.style.background = "#fff";
                        navBar.style.boxShadow = "0 0 15px #fff";
                        window.location.href = nextLink.href;
                    }
                } else {
                    if (accumulatedDelta > 0) {
                        accumulatedDelta = 0;
                        navBar.style.width = '0%';
                    }
                }

                isProcessingScroll = false;
            });
        }, { passive: true });
    }

    // ==========================================================================
    // 8. GALLERY PAGE FEATURES
    // ==========================================================================

    function initGalleryPageFeatures() {
        const btnContainer = document.querySelector('#info-block .buttons');
        const searchInput = document.querySelector('form.search input[name="q"]');
        if (!btnContainer) return;

        // --- 1. Queue Button ---
        if (settings.enableQueue && !document.querySelector('.btn-queue-add')) {
            const qBtn = document.createElement('button');
            qBtn.className = 'btn btn-secondary btn-queue-add';
            qBtn.innerHTML = '<i class="fa fa-plus"></i> Queue';

            const galleryId = window.location.href.match(/\/g\/(\d+)/)?.[1];

            if (galleryId) {
                if (isQueued(galleryId)) {
                    qBtn.innerHTML = '<i class="fa fa-check"></i> Saved';
                    qBtn.classList.add('in-queue');
                }

                qBtn.onclick = () => {
                   const title = document.querySelector('h1.title').textContent;
                   const coverImg = document.querySelector('#cover img');
                   const coverUrl = coverImg ? (coverImg.dataset.src || coverImg.src) : '';
                   toggleQueueItem(galleryId, title, coverUrl, window.location.href);
                };
                btnContainer.appendChild(qBtn);
            }
        }

        // --- 2. Tag Selector ---
        if (!searchInput || document.querySelector('.btn-tag-selector') || !settings.enableTagSelect) return;

        let isSelectionMode = false;
        const toggleBtn = document.createElement('button');
        toggleBtn.className = 'btn btn-secondary btn-tag-selector';
        toggleBtn.innerHTML = '<i class="fa fa-tags"></i> Tag Select';
        toggleBtn.type = 'button';
        btnContainer.appendChild(toggleBtn);

        const tagsContainer = document.querySelector('#tags');
        tagsContainer.addEventListener('click', (e) => {
            if (!isSelectionMode) return;
            const tagLink = e.target.closest('a.tag');
            if (tagLink) {
                e.preventDefault(); e.stopPropagation();
                if (!tagLink.href.includes('q=pages')) {
                    tagLink.classList.toggle('tag-selected');
                    updateSearchBar();
                }
            }
        }, true);

        toggleBtn.onclick = () => {
            isSelectionMode = !isSelectionMode;
            if (isSelectionMode) {
                toggleBtn.classList.add('is-active');
                tagsContainer.classList.add('tags-selecting-mode');
                toggleBtn.innerHTML = '<i class="fa fa-check"></i> Done';
            } else {
                toggleBtn.classList.remove('is-active');
                tagsContainer.classList.remove('tags-selecting-mode');
                toggleBtn.innerHTML = '<i class="fa fa-tags"></i> Tag Select';
            }
        };

        function updateSearchBar() {
            const selectedTags = tagsContainer.querySelectorAll('.tag.tag-selected');
            const queryTerms = Array.from(selectedTags).map(tag => {
                const nameSpan = tag.querySelector('.name');
                let tagName = nameSpan ? nameSpan.textContent.trim() : '';
                const href = tag.getAttribute('href');

                if (tagName.includes(' ')) tagName = `"${tagName}"`;
                if (href.includes('/artist/')) return `artist:${tagName}`;
                if (href.includes('/group/')) return `group:${tagName}`;
                if (href.includes('/parody/')) return `parody:${tagName}`;
                if (href.includes('/character/')) return `character:${tagName}`;
                return tagName;
            });
            searchInput.value = queryTerms.join(' ');
        }
    }

    // ==========================================================================
    // 9. SAVED SEARCHES
    // ==========================================================================

    function initSearchFlow() {
        if (!settings.enableSavedSearch) return;
        const form = document.querySelector('form.search');
        const nav = document.querySelector('nav[role="navigation"]');

        if (!form || !nav || form.dataset.savedInit) return;
        form.dataset.savedInit = '1';

        const input = form.querySelector('input[name="q"]');
        if (input) input.style.paddingRight = '40px';

        let searchData = JSON.parse(localStorage.getItem('nhentai_search_flow') || '{"saved":[]}');
        let isDeleteMode = false;
        const saveSearch = () => localStorage.setItem('nhentai_search_flow', JSON.stringify(searchData));

        const toggleSavedItem = (query) => {
            if (!query) return;
            const idx = searchData.saved.indexOf(query);
            if (idx > -1) searchData.saved.splice(idx, 1);
            else searchData.saved.push(query);
            saveSearch();

            if (searchData.saved.length === 0) isDeleteMode = false;
            renderBar();
        };

        const trigger = document.createElement('div');
        trigger.className = 'search-saved-trigger';
        trigger.innerHTML = '<i class="fa fa-bookmark"></i>';
        trigger.title = "Saved Searches";
        form.appendChild(trigger);

        const barContainer = document.createElement('div');
        barContainer.className = 'saved-search-extension';
        nav.parentNode.insertBefore(barContainer, nav.nextSibling);

        const renderBar = () => {
            const currentQ = input ? input.value.trim() : '';
            const isCurrentSaved = currentQ && searchData.saved.includes(currentQ);

            const listClass = isDeleteMode ? 'sse-list delete-mode' : 'sse-list';
            const editBtnText = isDeleteMode ? '<i class="fa fa-times"></i> Done' : '<i class="fas fa-eraser"></i> Delete';
            const editBtnClass = isDeleteMode ? 'btn-sse-edit is-active' : 'btn-sse-edit';

            let html = `
                <div class="sse-header">
                    <span class="sse-title"><i class="fa fa-tags"></i> Saved Searches</span>
                        ${currentQ && !isCurrentSaved ?
                            `<button class="btn-sse-save" id="btn-save-curr-bar"><i class="fa fa-plus"></i> Save Current</button>` : ''}

                    <div class="sse-actions">
                        ${searchData.saved.length > 0 ?
                            `<button class="${editBtnClass}" id="btn-toggle-edit">${editBtnText}</button>` : ''}
                    </div>
                </div>
                <div class="${listClass}">
            `;

            if (searchData.saved.length === 0) {
                html += `<div class="sse-empty">No saved searches yet.</div>`;
            } else {
                searchData.saved.forEach(q => {
                    const isCurrent = q === currentQ ? 'is-current' : '';
                    const safeQ = q.replace(/"/g, '&quot;');
                    html += `
                        <div class="ss-pill ${isCurrent}">
                            <div class="ss-part ss-add" data-q="${safeQ}" title="Add to current input"><i class="fa fa-plus" style="font-size: 10px;"></i></div>
                            <div class="ss-part ss-text" title="${isDeleteMode ? 'Click to DELETE' : 'Click to Search'}">${q}</div>
                        </div>
                    `;
                });
            }
            html += `</div>`;

            barContainer.innerHTML = html;

            const btnEdit = barContainer.querySelector('#btn-toggle-edit');
            if (btnEdit) {
                btnEdit.onclick = (e) => {
                    e.stopPropagation();
                    isDeleteMode = !isDeleteMode;
                    renderBar();
                };
            }

            const btnSave = barContainer.querySelector('#btn-save-curr-bar');
            if (btnSave) {
                btnSave.onclick = (e) => {
                    e.stopPropagation();
                    toggleSavedItem(currentQ);
                };
            }

            barContainer.querySelectorAll('.ss-text').forEach(el => {
                const text = el.textContent.trim();
                const fullQuery = searchData.saved.find(s => s.trim() === text) || text;

                el.onclick = (e) => {
                    e.stopPropagation();
                    if (isDeleteMode) {
                        toggleSavedItem(fullQuery);
                    } else {
                        if(fullQuery) {
                            window.location.href = `/search/?q=${encodeURIComponent(fullQuery)}`;
                        }
                    }
                };
            });

            barContainer.querySelectorAll('.ss-add').forEach(el => {
                el.onclick = (e) => {
                    e.stopPropagation();
                    if (isDeleteMode) return;
                    const queryToAdd = el.dataset.q;
                    const currentVal = input.value.trim();
                    input.value = currentVal ? currentVal + ' ' + queryToAdd : queryToAdd;
                    input.focus();
                };
            });
        };

        trigger.onclick = (e) => {
            e.stopPropagation();
            const isVisible = barContainer.classList.contains('is-visible');

            if (!isVisible) {
                isDeleteMode = false;
                renderBar();
                barContainer.classList.add('is-visible');
                trigger.classList.add('is-active');
            } else {
                barContainer.classList.remove('is-visible');
                trigger.classList.remove('is-active');
            }
        };
    }

    // ==========================================================================
    // 10. DATA MANAGEMENT (BACKUP/RESTORE)
    // ==========================================================================

    function exportData() {
        const data = {
            settings: JSON.parse(localStorage.getItem('nhentai_flow_settings') || '{}'),
            queue: JSON.parse(localStorage.getItem('nhentai_queue_v1') || '[]'),
            savedSearches: JSON.parse(localStorage.getItem('nhentai_search_flow') || '{"saved":[]}')
        };

        const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        const timestamp = new Date().toISOString().slice(0, 10);
        a.href = url;
        a.download = `nh-flow-backup-${timestamp}.json`;
        document.body.appendChild(a);
        a.click();

        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    }

    function importData() {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json';

        input.onchange = e => {
            const file = e.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = event => {
                try {
                    const data = JSON.parse(event.target.result);

                    if (data.settings) localStorage.setItem('nhentai_flow_settings', JSON.stringify(data.settings));
                    if (data.queue) localStorage.setItem('nhentai_queue_v1', JSON.stringify(data.queue));
                    if (data.savedSearches) localStorage.setItem('nhentai_search_flow', JSON.stringify(data.savedSearches));

                    alert('Imported data successfully! The page will reload.');
                    window.location.reload();
                } catch (err) {
                    alert('Failed to read file: invalid or corrupted JSON.');
                    console.error(err);
                }
            };
            reader.readAsText(file);
        };

        input.click();
    }

    // ==========================================================================
    // 11. SETTINGS MENU UI
    // ==========================================================================

    function initSettingsMenu() {
        const navContainer = document.querySelector('nav.menu .right') || document.querySelector('ul.menu.right');
        if (!navContainer || document.querySelector('.nav-settings-btn')) return;

        const btnLi = document.createElement('li');
        btnLi.innerHTML = '<span class="nav-settings-btn"><i class="fa fa-cog"></i></span>';
        btnLi.onclick = toggleModal;
        navContainer.insertBefore(btnLi, navContainer.firstChild);

        const modal = document.createElement('div');
        modal.className = 'nh-settings-modal';
        modal.innerHTML = `
            <div class="nh-settings-panel">
                <h2>NHentai Flow Settings</i></h2>
                <div id="settings-list"></div>
                <button class="nh-close-btn">Close</button>
            </div>
        `;
        document.body.appendChild(modal);

        modal.querySelector('.nh-close-btn').onclick = toggleModal;
        modal.onclick = (e) => { if(e.target === modal) toggleModal(); };

        function toggleModal() {
            const isOpen = modal.classList.toggle('is-open');
            if (isOpen) renderSettingsList();
        }

        function renderSettingsList() {
            const list = modal.querySelector('#settings-list');
            list.innerHTML = '';

            const options = [
                { key: 'previewNav', label: 'Hover Preview (thumbnails)' },
                { key: 'highlightVisited', label: 'Highlight read galleries' },
                { key: 'showTagOverlay', label: 'Show tags on hover' },
                { key: 'enableTagSelect', label: 'Enable tag selector' },
                { key: 'enableSavedSearch', label: 'Enable saved searches' },
                { key: 'enableQueue', label: 'Enable reading queue' },
                { key: 'smartNav', label: 'Smart navigation (auto-scroll)' },
                { key: 'paginationRight', label: 'Show pagination on the right' }
            ];

            options.forEach(opt => {
                const div = document.createElement('div');
                div.className = 'nh-setting-item';
                div.innerHTML = `
                    <span class="nh-setting-label">${opt.label}</span>
                    <div class="nh-toggle ${settings[opt.key] ? 'active' : ''}"></div>
                `;

                div.querySelector('.nh-toggle').onclick = function() {
                    settings[opt.key] = !settings[opt.key];
                    this.classList.toggle('active');
                    saveSettings();
                };
                list.appendChild(div);
            });
            const actionDiv = document.createElement('div');
            actionDiv.className = 'nh-settings-actions';

            const btnExport = document.createElement('button');
            btnExport.className = 'btn-data';
            btnExport.innerHTML = '<i class="fa fa-download"></i> Export Data';
            btnExport.onclick = exportData;

            const btnImport = document.createElement('button');
            btnImport.className = 'btn-data';
            btnImport.innerHTML = '<i class="fa fa-upload"></i> Import Data';
            btnImport.onclick = importData;

            actionDiv.appendChild(btnExport);
            actionDiv.appendChild(btnImport);
            list.appendChild(actionDiv);
        }
    }

    // ==========================================================================
    // 12. GLOBAL SHORTCUTS
    // ==========================================================================

    function initGlobalShortcuts() {
        if (document.body.dataset.shortcutsInit) return;
        document.body.dataset.shortcutsInit = '1';

        const searchForm = document.querySelector('form.search');
        const searchInput = document.querySelector('form.search input[name="q"]');

        if (searchForm && searchInput) {
            if (!searchForm.querySelector('.search-slash-hint')) {
                const hint = document.createElement('div');
                hint.className = 'search-slash-hint';
                hint.textContent = 'Type / to search';
                searchForm.insertBefore(hint, searchInput.nextSibling);
            }
        }

        document.addEventListener('keydown', (e) => {
            const target = e.target;
            if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
                if (e.key === 'Escape') target.blur();
                return;
            }
            if (e.ctrlKey || e.altKey || e.metaKey) return;

            if (e.key === '/') {
                if (searchInput) {
                    e.preventDefault();
                    searchInput.focus();
                    searchInput.select();
                }
            }
        });
    }

    // ==========================================================================
    // 13. INIT & OBSERVERS
    // ==========================================================================

    function scan() {
        initSettingsMenu();
        initSearchFlow();
        document.querySelectorAll('.gallery:not([data-init])').forEach(initPreviewUI);
        initReaderMode();
        initGalleryPageFeatures();
        initGlobalShortcuts();
        initQueueWidget();
        initRandomContextual();
    }

    document.addEventListener('keydown', (e) => {
        if (hoveredGallery && !document.fullscreenElement) {
            if (e.key === 'ArrowRight') { e.preventDefault(); update(hoveredGallery, 1); }
            else if (e.key === 'ArrowLeft') { e.preventDefault(); update(hoveredGallery, -1); }
            else if (e.key === 'q') {
                if (!settings.enableQueue) return;

                const btn = hoveredGallery.querySelector('.queue-trigger');
                if (btn) btn.click();
            }
        }
    });

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            scan();
            initSmartNavigation();
            if (window.location.pathname === '/') initPopularShortcut();
            renderQueuePage();
        });
    } else {
        scan();
        initSmartNavigation();
        if (window.location.pathname === '/') initPopularShortcut();
        renderQueuePage();
    }

    let observerTimeout;
    const observer = new MutationObserver(() => {
        if (observerTimeout) clearTimeout(observerTimeout);

        observerTimeout = setTimeout(() => {
            scan();
        }, 150);
    });

    const contentNode = document.getElementById('content') || document.body;
    observer.observe(contentNode, { childList: true, subtree: true });

})();