E-Hentai Reader Assistant

🌟Add preloading to e-hentai. 🌟Add click zones (left=prev/right=next) to image sections. 🌟Load images without page reload. 🌟Support keyboard shortcuts. 🌟Support i18n.

// ==UserScript==
// @name E-Hentai Reader Assistant
// @name:en-US EX-Hentai Reader Assistant
// @name:zh-CN EX-Hentai 助手
// @name:ja-JP EX-Hentai リーダーアシスタント
// @namespace   EX-Hentai Reader Assistant
// @match       https://e-hentai.org/s/*
// @match       https://exhentai.org/s/*
// @grant       none
// @version     1.3
// @author      Assistant
// @description 🌟Add preloading to e-hentai. 🌟Add click zones (left=prev/right=next) to image sections. 🌟Load images without page reload. 🌟Support keyboard shortcuts. 🌟Support i18n.
// @description:en-US 🌟Add preloading to e-hentai. 🌟Add click zones (left=prev/right=next) to image sections. 🌟Load images without page reload. 🌟Support keyboard shortcuts. 🌟Support i18n.
// @description:zh-CN 🌟为 e 站添加预加载。🌟添加图片分区点击左边上一页,右边下一页。🌟让e站可以在不刷新的情况下加载图片。🌟支持键盘操作。🌟支持i18n。
// @description:ja-JP 🌟e-hentai にプリロードを追加。🌟画像セクションのクリックゾーン(左=前/右=次)を追加。🌟ページ遷移なしで画像を読み込む。🌟キーボード操作をサポート。🌟多言語対応。
// @run-at      document-start
// @license     CC-BY-NC-SA-4.0
// @noframes    true
// ==/UserScript==


(function() {
    'use strict';

    // 配置
    const CONFIG = {
        preloadCount: 3, // 预加载页面数量
        maxRetries: 3,   // 最大重试次数
        retryDelay: 100, // 重试延迟(ms)
        updateUrl: false, // 是否在不刷新情况下更新地址栏
        // 统一时间/延迟参数
        preloadStepDelay: 120, // 预加载步进小延迟(ms)
        imageTransitionMs: 210, // 图片切换过渡时长(ms)
        resizeThrottleMs: 100,  // 窗口重排节流(ms)
        hintAutoHideMs: 5000,   // 操作提示自动隐藏(ms)
        hintFadeMs: 1000,       // 操作提示淡出时长(ms)
        errorDurationMs: 3000,  // 错误提示显示时长(ms)
        messageDurationMs: 2000 // 普通消息显示时长(ms)
    };

    // 全局变量
    let currentPage = 1;
    let totalPages = 1;
    let imageCache = new Map(); // 图片缓存
    let pageDataCache = new Map(); // 页面数据缓存
    let pageUrlCache = new Map(); // 页码 -> 真实 URL(含 token)
    let isLoading = false;
    let fitMode = localStorage.getItem('ehx_fit_mode') || 'width'; // width | height

    // 新增:预加载状态跟踪
    let preloadStatus = new Map(); // 页码 -> 状态 ('waiting', 'loading', 'completed', 'failed')
    let pendingPreloadRender = false; // rAF 批处理标志
    let scriptEnabled = localStorage.getItem('ehx_script_enabled') !== 'false'; // 脚本开关
    let showPreloadStatus = localStorage.getItem('ehx_show_preload_status') !== 'false'; // 预加载状态显示开关
    let langSetting = localStorage.getItem('ehx_lang') || 'auto'; // 语言设置: auto | en | zh-CN | ja

    // 许可证
    const LICENSE = 'CC-BY-NC-SA-4.0';

    // 国际化字典
    const I18N = {
        'en': {
            fitWidth: 'Fit width',
            fitHeight: 'Fit height',
            settings: 'Settings',
            loading: 'Loading...',
            navHintTitle: 'Keyboard:',
            navHintLeft: '← / A: Prev page',
            navHintRight: '→ / D / Space: Next page',
            navHintClick: 'Click image: Left=Prev, Right=Next',
            menuTitle: 'E-Hentai Reader Settings',
            basicSettings: 'General',
            enableScript: 'Enable script',
            showPreloadStatus: 'Show preload status',
            updateUrl: 'Update address bar',
            preloadSettings: 'Preload',
            preloadCount: 'Preload pages',
            maxRetries: 'Max retries',
            retryDelayMs: 'Retry delay (ms)',
            cacheManagement: 'Cache',
            clearImageCache: 'Clear image cache',
            clearPageCache: 'Clear page cache',
            clearAllCache: 'Clear all cache',
            resetSettings: 'Reset settings',
            statusInfo: 'Status',
            cacheImageCount: 'Image cache',
            cachePageCount: 'Page cache',
            preloadStatus: 'Preload Status',
            close: 'Close',
            language: 'Language',
            lang_auto: 'Follow browser',
            lang_en: 'English',
            lang_zhCN: '简体中文',
            lang_ja: '日本語',
            confirmReset: 'Reset all settings?',
            msgScriptEnabled: 'Enabled. Please refresh the page.',
            msgScriptDisabled: 'Disabled.',
            msgSettingsSaved: 'Settings saved',
            msgImgCacheCleared: 'Image cache cleared',
            msgPageCacheCleared: 'Page cache cleared',
            msgAllCleared: 'All caches cleared',
            msgSettingsReset: 'Settings reset. Please refresh the page',
            loadFailed: 'Load failed, please retry',
            status_waiting: 'waiting',
            status_loading: 'loading',
            status_completed: 'completed',
            status_failed: 'failed',
            status_unknown: 'unknown',
        },
        'zh-CN': {
            fitWidth: '适应宽度',
            fitHeight: '适应高度',
            settings: '脚本设置',
            loading: '正在加载...',
            navHintTitle: '键盘操作:',
            navHintLeft: '← / A: 上一页',
            navHintRight: '→ / D / 空格: 下一页',
            navHintClick: '点击图片左半: 上一页,右半: 下一页',
            menuTitle: 'E-Hentai 助手设置',
            basicSettings: '基础设置',
            enableScript: '启用脚本',
            showPreloadStatus: '显示预加载状态',
            updateUrl: '地址栏同步',
            preloadSettings: '预加载设置',
            preloadCount: '预加载页数',
            maxRetries: '重试次数',
            retryDelayMs: '重试延迟(ms)',
            cacheManagement: '缓存管理',
            clearImageCache: '清除图片缓存',
            clearPageCache: '清除页面缓存',
            clearAllCache: '清除所有缓存',
            resetSettings: '重置设置',
            statusInfo: '状态信息',
            cacheImageCount: '图片缓存',
            cachePageCount: '页面缓存',
            preloadStatus: '预加载状态',
            close: '关闭',
            language: '语言',
            lang_auto: '自动',
            lang_en: 'English',
            lang_zhCN: '简体中文',
            lang_ja: '日本語',
            confirmReset: '确定要重置所有设置吗?',
            msgScriptEnabled: '脚本已启用,请刷新页面生效',
            msgScriptDisabled: '脚本已禁用',
            msgSettingsSaved: '设置已保存',
            msgImgCacheCleared: '图片缓存已清除',
            msgPageCacheCleared: '页面缓存已清除',
            msgAllCleared: '所有缓存已清除',
            msgSettingsReset: '设置已重置,请刷新页面',
            loadFailed: '加载失败,请重试',
            status_waiting: '等待',
            status_loading: '加载中',
            status_completed: '完成',
            status_failed: '失败',
            status_unknown: '未知',
        },
        'ja': {
            fitWidth: '幅に合わせる',
            fitHeight: '高さに合わせる',
            settings: '設定',
            loading: '読み込み中...',
            navHintTitle: 'キーボード:',
            navHintLeft: '← / A: 前のページ',
            navHintRight: '→ / D / Space: 次のページ',
            navHintClick: '画像クリック: 左=前, 右=次',
            menuTitle: 'E-Hentai リーダー設定',
            basicSettings: '基本設定',
            enableScript: 'スクリプトを有効',
            showPreloadStatus: 'プリロード状態を表示',
            updateUrl: 'アドレスバーを更新',
            preloadSettings: 'プリロード',
            preloadCount: 'プリロード枚数',
            maxRetries: '再試行回数',
            retryDelayMs: '再試行遅延(ms)',
            cacheManagement: 'キャッシュ',
            clearImageCache: '画像キャッシュを削除',
            clearPageCache: 'ページキャッシュを削除',
            clearAllCache: 'すべてのキャッシュを削除',
            resetSettings: '設定をリセット',
            statusInfo: 'ステータス',
            cacheImageCount: '画像キャッシュ',
            cachePageCount: 'ページキャッシュ',
            preloadStatus: 'プリロード状態',
            close: '閉じる',
            language: '言語',
            lang_auto: 'ブラウザに従う',
            lang_en: 'English',
            lang_zhCN: '简体中文',
            lang_ja: '日本語',
            confirmReset: 'すべての設定をリセットしますか?',
            msgScriptEnabled: '有効にしました。ページを更新してください。',
            msgScriptDisabled: '無効にしました。',
            msgSettingsSaved: '設定を保存しました',
            msgImgCacheCleared: '画像キャッシュを削除しました',
            msgPageCacheCleared: 'ページキャッシュを削除しました',
            msgAllCleared: 'すべてのキャッシュを削除しました',
            msgSettingsReset: '設定をリセットしました。ページを更新してください',
            loadFailed: '読み込みに失敗しました。再試行してください',
            status_waiting: '待機',
            status_loading: '読み込み中',
            status_completed: '完了',
            status_failed: '失敗',
            status_unknown: '不明',
        }
    };

    function resolveDefaultLang() {
        const n = (navigator.language || navigator.userLanguage || 'en').toLowerCase();
        if (n.startsWith('zh')) return 'zh-CN';
        if (n.startsWith('ja')) return 'ja';
        return 'en';
    }
    function getCurrentLang() {
        return langSetting === 'auto' ? resolveDefaultLang() : langSetting;
    }
    function t(key) {
        const lang = getCurrentLang();
        const dict = I18N[lang] || I18N['en'];
        return (dict && dict[key]) || I18N['en'][key] || key;
    }

    // 保持 document-start,但不在此阶段阻断事件,避免干扰后续绑定

    // 初始化
    function init() {
        // 添加样式(总是需要,包括菜单样式)
        addStyles();

        // 添加管理菜单
        addControlMenu();

        // 如果脚本被禁用,只显示菜单,不执行主要功能
        if (!scriptEnabled) {
            console.log('脚本已禁用,仅显示控制菜单');
            return;
        }

        // 解析当前页面信息
        parseCurrentPage();

        // 用当前 DOM 为当前页建立初始化数据(含 next/prev 实时 URL)
        seedCurrentPageData();

        // 绑定事件
        bindEvents();

        // 开始预加载
        startPreloading();

        // 添加加载指示器
        addLoadingIndicator();

        // 添加预加载状态显示
        if (showPreloadStatus) {
            addPreloadStatusDisplay();
        }
    }

    // 解析当前页面信息
    function parseCurrentPage() {
        const url = window.location.href;
        const match = url.match(/\/s\/([^\/]+)\/(\d+)-(\d+)/);

        if (match) {
            currentPage = parseInt(match[3]);
        }

        // 从 DOM 解析当前/总页
        const spans = document.querySelectorAll('.sn span');
        if (spans && spans.length >= 2) {
            const cur = parseInt(spans[0].textContent.trim());
            const tot = parseInt(spans[1].textContent.trim());
            if (!Number.isNaN(cur)) currentPage = cur;
            if (!Number.isNaN(tot)) totalPages = tot;
        }

        // 建立 URL 映射
        pageUrlCache.set(currentPage, window.location.href);
        const nextA = document.getElementById('next');
        const prevA = document.getElementById('prev');
        if (nextA && nextA.href) pageUrlCache.set(currentPage + 1, nextA.href);
        if (prevA && prevA.href) pageUrlCache.set(currentPage - 1, prevA.href);

        console.log(`当前页: ${currentPage}/${totalPages}`);
    }

    // 用当前 DOM 初始化当前页数据,确保实时 next/prev
    function seedCurrentPageData() {
        const img = document.getElementById('img');
        if (!img) return;
        const nextA = document.getElementById('next');
        const prevA = document.getElementById('prev');
        const imageData = {
            src: img.src,
            width: img.style.width,
            height: img.style.height,
            pageNum: currentPage,
            nextUrl: nextA && nextA.href ? nextA.href : undefined,
            prevUrl: prevA && prevA.href ? prevA.href : undefined,
        };
        pageDataCache.set(currentPage, imageData);
        if (imageData.nextUrl) pageUrlCache.set(currentPage + 1, imageData.nextUrl);
        if (imageData.prevUrl) pageUrlCache.set(currentPage - 1, imageData.prevUrl);
    }

    // 绑定事件
    function bindEvents() {
        const img = document.getElementById('img');
        const imgContainer = document.getElementById('i3');

        if (img && imgContainer) {
            // 用中性容器替换外层 <a>,彻底打断默认跳转与站内 onclick
            const link = imgContainer.querySelector('a');
            if (link && link.contains(img)) {
                const holder = document.createElement('div');
                holder.id = 'img-holder';
                holder.style.cursor = 'pointer';
                holder.style.display = 'inline-block';
                link.parentNode.replaceChild(holder, link);
                holder.appendChild(img);
                // 添加局部加载层
                const loader = document.createElement('div');
                loader.className = 'eh-img-loader';
                loader.innerHTML = '<div class="eh-spinner"></div>';
                holder.appendChild(loader);

                holder.addEventListener('click', function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    const rect = holder.getBoundingClientRect();
                    const x = e.clientX - rect.left;
                    if (x < rect.width / 2) {
                        goToPrevPage();
                    } else {
                        goToNextPage();
                    }
                }, { capture: true });
            }
        }

        // 在冒泡阶段拦截,并作为后备触发我们的逻辑
        document.addEventListener('click', function(e) {
            const target = e.target;
            if (!target) return;
            const inNext = target.closest('a#next');
            const inPrev = target.closest('a#prev');
            const inImgArea = target.closest('#i3');
            if (inNext) {
                e.preventDefault();
                e.stopPropagation();
                goToNextPage();
                return;
            }
            if (inPrev) {
                e.preventDefault();
                e.stopPropagation();
                goToPrevPage();
                return;
            }
            if (inImgArea) {
                e.preventDefault();
                e.stopPropagation();
                const rect = inImgArea.getBoundingClientRect();
                const x = e.clientX - rect.left;
                if (x < rect.width / 2) {
                    goToPrevPage();
                } else {
                    goToNextPage();
                }
                return;
            }
        }, false);

        // 绑定导航按钮
        bindNavigationButtons();

        // 绑定键盘事件(捕获阶段,阻止站点快捷键)
        document.addEventListener('keydown', handleKeyboard, true);
    }

    // 绑定导航按钮
    function bindNavigationButtons() {
        const prevBtn = document.getElementById('prev');
        const nextBtn = document.getElementById('next');

        if (prevBtn) {
            // 不再覆盖 href,保留真实链接,仅拦截点击
            prevBtn.addEventListener('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                goToPrevPage();
            }, { capture: true });
        }

        if (nextBtn) {
            // 不再覆盖 href,保留真实链接,仅拦截点击
            nextBtn.addEventListener('click', function(e) {
                e.preventDefault();
                e.stopPropagation();
                goToNextPage();
            }, { capture: true });
        }
    }

    // 键盘事件处理
    function handleKeyboard(e) {
        // 阻断站点注册的快捷键
        const code = e.code || e.key;
        if (!code) return;
        // 忽略按住不放产生的重复事件,避免一次按键翻多页
        if (e.repeat) {
            e.preventDefault();
            e.stopPropagation();
            return;
        }
        if (isLoading) {
            e.preventDefault();
            e.stopPropagation();
            return;
        }
        if (code === 'ArrowLeft' || code === 'KeyA') {
            e.preventDefault();
            e.stopPropagation();
            goToPrevPage();
        } else if (code === 'ArrowRight' || code === 'KeyD' || code === 'Space') {
            e.preventDefault();
            e.stopPropagation();
            goToNextPage();
        }
    }

    // 跳转到下一页(优先使用当前页解析得到的 next 实时链接)
    async function goToNextPage() {
        if (isLoading || currentPage >= totalPages) return;

        setLoading(true);

        try {
            let nextPage = currentPage + 1;
            const curData = pageDataCache.get(currentPage);
            if (curData && curData.nextUrl) {
                const p = extractPageNum(curData.nextUrl);
                if (Number.isFinite(p)) nextPage = p;
                pageUrlCache.set(nextPage, curData.nextUrl);
            }

            const imageData = await getPageImage(nextPage);

            if (imageData) {
                updateImage(imageData);
                currentPage = nextPage;
                updatePageInfo();
                updateNavigationButtons();

                // 继续预加载
                preloadPages();

                // 更新状态显示
                updatePreloadStatusDisplay();
            }
        } catch (error) {
            console.error('加载下一页失败:', error);
            showError(t('loadFailed'));
        } finally {
            setLoading(false);
        }
    }

    // 跳转到上一页(优先使用当前页解析得到的 prev 实时链接)
    async function goToPrevPage() {
        if (isLoading || currentPage <= 1) return;

        setLoading(true);

        try {
            let prevPage = currentPage - 1;
            const curData = pageDataCache.get(currentPage);
            if (curData && curData.prevUrl) {
                const p = extractPageNum(curData.prevUrl);
                if (Number.isFinite(p)) prevPage = p;
                pageUrlCache.set(prevPage, curData.prevUrl);
            }

            const imageData = await getPageImage(prevPage);

            if (imageData) {
                updateImage(imageData);
                currentPage = prevPage;
                updatePageInfo();
                updateNavigationButtons();

                // 继续预加载(向前翻页后同样触发)
                preloadPages();

                // 更新状态显示
                updatePreloadStatusDisplay();
            }
        } catch (error) {
            console.error('加载上一页失败:', error);
            showError(t('loadFailed'));
        } finally {
            setLoading(false);
        }
    }

    // 获取指定页面的图片信息
    async function getPageImage(pageNum, retryCount = 0) {
        if (pageDataCache.has(pageNum)) {
            // 如果已缓存,更新状态为完成
            updatePreloadStatus(pageNum, 'completed');
            return pageDataCache.get(pageNum);
        }

        // 设置状态为加载中
        updatePreloadStatus(pageNum, 'loading');

        try {
            const url = getPageUrl(pageNum);
            const response = await fetch(url);

            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }

            const html = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');

            const img = doc.getElementById('img');
            if (!img) {
                throw new Error('未找到图片元素');
            }

            let imageData = {
                src: img.src,
                width: img.style.width,
                height: img.style.height,
                pageNum: pageNum
            };

            // 完善 URL 映射与总页数,并记录当前页的 prev/next 实时链接
            try {
                const nextA2 = doc.getElementById('next');
                const prevA2 = doc.getElementById('prev');
                if (nextA2 && nextA2.href) {
                    pageUrlCache.set(pageNum + 1, nextA2.href);
                    imageData.nextUrl = nextA2.href;
                }
                if (prevA2 && prevA2.href) {
                    pageUrlCache.set(pageNum - 1, prevA2.href);
                    imageData.prevUrl = prevA2.href;
                }
                const spans2 = doc.querySelectorAll('.sn span');
                if (spans2.length >= 2) {
                    const tot2 = parseInt(spans2[1].textContent.trim());
                    if (!Number.isNaN(tot2) && tot2 > totalPages) {
                        totalPages = tot2;
                        // 实时更新页面显示
                        setTimeout(updatePageInfo, 0);
                    }
                }
            } catch (_) {}

            // 缓存数据
            pageDataCache.set(pageNum, imageData);

            // 预加载图片
            preloadImage(imageData.src);

            // 更新状态为完成
            updatePreloadStatus(pageNum, 'completed');

            return imageData;

        } catch (error) {
            console.error(`获取页面 ${pageNum} 失败:`, error);

            if (retryCount < CONFIG.maxRetries) {
                console.log(`重试获取页面 ${pageNum} (${retryCount + 1}/${CONFIG.maxRetries})`);
                await new Promise(resolve => setTimeout(resolve, CONFIG.retryDelay));
                return getPageImage(pageNum, retryCount + 1);
            }

            // 更新状态为失败
            updatePreloadStatus(pageNum, 'failed');
            throw error;
        }
    }

    // 预加载图片
    function preloadImage(src) {
        if (imageCache.has(src)) return;

        const img = new Image();
        img.onload = () => {
            imageCache.set(src, img);
            console.log('图片预加载完成:', src);
        };
        img.onerror = () => {
            console.error('图片预加载失败:', src);
        };
        img.src = src;
    }

    // 获取页面URL
    function getPageUrl(pageNum) {
        if (pageUrlCache.has(pageNum)) return pageUrlCache.get(pageNum);

        // 临近页尝试使用 DOM 中的真实链接
        if (pageNum === currentPage + 1) {
            const nextA = document.getElementById('next');
            if (nextA && nextA.href) {
                pageUrlCache.set(pageNum, nextA.href);
                return nextA.href;
            }
        }
        if (pageNum === currentPage - 1) {
            const prevA = document.getElementById('prev');
            if (prevA && prevA.href) {
                pageUrlCache.set(pageNum, prevA.href);
                return prevA.href;
            }
        }

        // 回退:基于当前 URL 猜测(可能无效)
        const m = window.location.href.match(/^(.*-)(\d+)([^\d]*)$/);
        if (m) {
            const guess = `${m[1]}${pageNum}${m[3] || ''}`;
            return guess;
        }
        return window.location.href;
    }

    // 辅助:从链接中提取页码
    function extractPageNum(url) {
        const m = url && url.match(/\/s\/[^\/]+\/(\d+)-(\d+)/);
        if (m) return parseInt(m[2]);
        const m2 = url && url.match(/-(\d+)(?:[^\d]*)$/);
        return m2 ? parseInt(m2[1]) : NaN;
    }

    // 更新图片
    function updateImage(imageData) {
        const img = document.getElementById('img');
        if (img) {
            // 过渡开始:隐藏旧图,显示局部加载动画
            beginImageTransition();

            const applyNewSrc = () => {
                img.src = imageData.src;
                // 让 CSS 接管尺寸控制,避免因不同页的 inline 宽高导致布局跳变
                img.style.width = '';
                img.style.height = '';
                // 新图加载完成后淡入
                if (img.complete) {
                    endImageTransition();
                } else {
                    img.onload = () => endImageTransition();
                    img.onerror = () => endImageTransition(true);
                }
                // 更新图片信息
                updateImageInfo(imageData);
            };

            // 若已预加载,直接应用
            if (imageCache.has(imageData.src)) {
                const preImg = imageCache.get(imageData.src);
                if (preImg && preImg.complete) {
                    applyNewSrc();
                } else {
                    // 保险:等待预加载完成再应用
                    preImg.onload = () => applyNewSrc();
                    preImg.onerror = () => applyNewSrc();
                }
            } else {
                applyNewSrc();
            }
        }
    }

    // 更新图片信息
    function updateImageInfo(imageData) {
        const infoElements = document.querySelectorAll('#i2 > div:last-child, #i4 > div:first-child');

        // 从图片URL提取文件名和尺寸信息
        const urlParts = imageData.src.split('/');
        const filename = urlParts[urlParts.length - 1].split('?')[0];

        infoElements.forEach(element => {
            if (element.textContent.includes('::')) {
                // 保持原有格式,只更新文件名
                const parts = element.textContent.split('::');
                if (parts.length >= 2) {
                    element.textContent = `${filename} :: ${parts[1].trim()} :: ${parts[2] ? parts[2].trim() : ''}`;
                }
            }
        });
    }

    // 更新页面信息
    function updatePageInfo() {
        // 更新原页面的所有页码显示(顶部和底部)
        const pageSpans = document.querySelectorAll('.sn span');
        for (let i = 0; i < pageSpans.length; i += 2) {
            if (pageSpans[i]) {
                pageSpans[i].textContent = currentPage;
            }
            if (pageSpans[i + 1] && totalPages) {
                pageSpans[i + 1].textContent = totalPages;
            }
        }

        // 更新自定义工具条的页码显示
        const cur = document.getElementById('ehx-cur');
        const tot = document.getElementById('ehx-total');
        if (cur) cur.textContent = currentPage;
        if (tot && totalPages) tot.textContent = totalPages;

        // 是否更新浏览器地址栏(不刷新页面)
        if (CONFIG.updateUrl) {
            const newUrl = pageUrlCache.get(currentPage) || getPageUrl(currentPage);
            window.history.replaceState(null, '', newUrl);
        }
    }

    // 更新导航按钮状态
    function updateNavigationButtons() {
        // 更新原页面的导航按钮
        const prevBtn = document.getElementById('prev');
        const nextBtn = document.getElementById('next');

        if (prevBtn) {
            if (currentPage <= 1) {
                prevBtn.style.opacity = '0.5';
                prevBtn.style.cursor = 'not-allowed';
            } else {
                prevBtn.style.opacity = '1';
                prevBtn.style.cursor = 'pointer';
            }
        }

        if (nextBtn) {
            if (currentPage >= totalPages) {
                nextBtn.style.opacity = '0.5';
                nextBtn.style.cursor = 'not-allowed';
            } else {
                nextBtn.style.opacity = '1';
                nextBtn.style.cursor = 'pointer';
            }
        }
    }

    // 开始预加载
    function startPreloading() {
        preloadPages();
    }

    // 预加载页面
    async function preloadPages() {
        // 顺序向前预加载,以确保 token 链正确
        for (let step = 1; step <= CONFIG.preloadCount; step++) {
            const p = currentPage + step;
            if (p > totalPages) break;
            if (pageDataCache.has(p)) continue;

            // 设置等待状态
            updatePreloadStatus(p, 'waiting');

            try {
                // 小延迟避免阻塞
                // eslint-disable-next-line no-await-in-loop
                await new Promise(r => setTimeout(r, CONFIG.preloadStepDelay));
                // eslint-disable-next-line no-await-in-loop
                await getPageImage(p);
            } catch (e) {
                console.log(`预加载页面 ${p} 失败:`, e && e.message ? e.message : e);
                updatePreloadStatus(p, 'failed');
                break;
            }
        }
        // 回看一页
        const back = currentPage - 1;
        if (back >= 1 && !pageDataCache.has(back)) {
            updatePreloadStatus(back, 'waiting');
            getPageImage(back).catch(() => {
                updatePreloadStatus(back, 'failed');
            });
        }
    }

    // 设置加载状态
    function setLoading(loading) {
        isLoading = loading;
        const indicator = document.getElementById('loading-indicator');
        if (indicator) {
            indicator.style.display = loading ? 'block' : 'none';
        }

        // 禁用/启用导航按钮
        const buttons = document.querySelectorAll('#prev, #next');
        buttons.forEach(btn => {
            if (loading) {
                btn.style.pointerEvents = 'none';
                btn.style.opacity = '0.5';
            } else {
                btn.style.pointerEvents = 'auto';
                updateNavigationButtons();
            }
        });
    }

    // 开始图片切换的过渡,显示局部 loading
    function beginImageTransition() {
        const img = document.getElementById('img');
        if (!img) return;
        const holder = document.getElementById('img-holder');
        const isHeightMode = document.body.classList.contains('ehx-fit-height');

        if (holder && !isHeightMode) {
            // 只在宽度模式下固定高度,避免切换瞬间塌陷/跳变
            const rect = holder.getBoundingClientRect();
            holder.style.height = rect.height + 'px';
        }
        img.style.opacity = '0';
        if (holder) holder.classList.add('loading');

        // 预留图片信息区域高度,避免下方元素跳动
        reserveInfoAreas();
    }

    // 结束图片切换的过渡,隐藏局部 loading
    function endImageTransition(isError = false) {
        const img = document.getElementById('img');
        if (!img) return;
        const holder = document.getElementById('img-holder');
        const isHeightMode = document.body.classList.contains('ehx-fit-height');

        // 根据新图自然高度更新容器高度,再释放
        if (holder) {
            const release = () => {
                requestAnimationFrame(() => {
                    if (!isHeightMode) {
                        holder.style.height = '';
                    }
                    holder.classList.remove('loading');
                    // 延后释放信息区域的 min-height,避免移动端闪烁
                    setTimeout(releaseInfoAreas, 0);
                });
            };

            if (!isHeightMode) {
                // 只在宽度模式下做高度过渡
                const tmpImg = imageCache.get(img.src) || img;
                const naturalH = tmpImg.naturalHeight && tmpImg.naturalWidth ? (holder.clientWidth * tmpImg.naturalHeight / tmpImg.naturalWidth) : holder.clientHeight;
                if (naturalH && Number.isFinite(naturalH)) {
                    holder.style.height = Math.round(naturalH) + 'px';
                    setTimeout(release, CONFIG.imageTransitionMs);
                } else {
                    release();
                }
            } else {
                // 高度模式下直接释放
                release();
            }
        }
        img.style.opacity = '1';
    }

    // 预留信息区域高度
    function reserveInfoAreas() {
        const infoTop = document.querySelector('#i2 > div:last-child');
        const infoBottom = document.querySelector('#i4 > div:first-child');
        freezeElementHeight(infoTop);
        freezeElementHeight(infoBottom);
    }

    // 释放信息区域高度
    function releaseInfoAreas() {
        const infoTop = document.querySelector('#i2 > div:last-child');
        const infoBottom = document.querySelector('#i4 > div:first-child');
        releaseElementHeight(infoTop);
        releaseElementHeight(infoBottom);
    }

    // 冻结元素高度
    function freezeElementHeight(el) {
        if (!el) return;
        const rect = el.getBoundingClientRect();
        const h = Math.round(rect.height);
        if (h > 0) {
            // 仅设置最小高度,避免强制 height/overflow 造成移动端重绘闪烁
            el.style.minHeight = h + 'px';
        }
    }

    // 释放元素高度
    function releaseElementHeight(el) {
        if (!el) return;
        el.style.minHeight = '';
    }

    // 显示错误信息
    function showError(message) {
        const errorDiv = document.createElement('div');
        errorDiv.id = 'error-message';
        errorDiv.textContent = message;
        errorDiv.className = 'stuffbox';
        errorDiv.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 10px 15px;
            z-index: 10000;
            font-size: 14px;
        `;

        document.body.appendChild(errorDiv);

        setTimeout(() => {
            if (errorDiv.parentNode) {
                errorDiv.parentNode.removeChild(errorDiv);
            }
        }, CONFIG.errorDurationMs);
    }

    // 添加样式(移除自定义配色与文字色,改为继承站点样式)
    function addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            /* 页面框架:仅结构与布局,颜色继承站点 */
            .ehx-reader-bar { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 8px; backdrop-filter: saturate(120%) blur(3px); }
            .ehx-reader-bar .group { display: inline-flex; align-items: center; gap: 6px; }
            .ehx-btn { appearance: none; border: 1px solid currentColor; background: transparent; color: inherit; padding: 6px 10px; border-radius: 6px; cursor: pointer; }
            .ehx-btn:disabled { opacity: .5; cursor: not-allowed; }
            .ehx-btn:hover { filter: brightness(1.02); }
            .ehx-btn.active { outline: 2px solid currentColor; }
            .ehx-sep { width: 1px; height: 24px; background: currentColor; opacity: .2; }
            .ehx-counter { user-select: none; min-width: 84px; text-align: center; }

            /* 主容器:跟随窗口宽度居中 */
            .ehx-container { margin: 0 auto; padding: 6px 10px; max-width: min(96vw, 1200px); }
            .ehx-image-wrap { display: flex; justify-content: center; align-items: flex-start; }

            /* 适应高度模式:整个显示区域适应浏览器视口 */
            body.ehx-fit-height .ehx-container { height: var(--ehx-available-height, 400px); display: flex; flex-direction: column; }
            body.ehx-fit-height .ehx-image-wrap { flex: 1; align-items: center; min-height: 0; }
            body.ehx-fit-height #img-holder { height: 100%; max-height: 100%; width: auto; display: flex; align-items: center; justify-content: center; }
            body.ehx-fit-height #img { max-height: 100%; max-width: 100%; width: auto; height: auto; object-fit: contain; }

            #loading-indicator {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                padding: 15px 25px;
                z-index: 10000;
                font-size: 16px;
                display: none;
            }

            .eh-nav-hint {
                position: fixed;
                bottom: 20px;
                right: 20px;
                padding: 10px;
                font-size: 12px;
                z-index: 9999;
            }

            #img { transition: opacity 0.18s ease; max-width: 100%; height: auto; display: block; }
            .loading #img { opacity: 0.7; }

            /* 局部加载遮罩 */
            #img-holder { position: relative; display: inline-block; max-width: 100%; }
            #img-holder .eh-img-loader { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; }
            #img-holder.loading .eh-img-loader { display: flex; }
            .eh-spinner { width: 32px; height: 32px; border-radius: 50%; border: 3px solid currentColor; border-top-color: transparent; animation: spin 0.8s linear infinite; }
            @keyframes spin { to { transform: rotate(360deg); } }

            /* 容器自适应与高度平滑过渡,减少抖动 */
            #i3 { display: flex; justify-content: center; }
            #img-holder { transition: height 0.2s ease; }
            body.ehx-fit-height #img-holder { transition: none; }

            /* 控制菜单样式(定位 + 结构,外观由站点类接管) */
            .ehx-control-menu { position: fixed; top: 10px; right: 10px; padding: 12px; z-index: 10001; font-size: 12px; min-width: 280px; display: none; }
            .ehx-control-menu.show { display: block; }
            .ehx-menu-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid currentColor; font-weight: bold; }
            .ehx-menu-close { background: none; border: none; font-size: 16px; cursor: pointer; padding: 0; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; }
            .ehx-menu-section { margin-bottom: 12px; }
            .ehx-menu-section h4 { margin: 0 0 6px 0; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
            .ehx-menu-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; padding: 4px 0; }
            .ehx-menu-item label { font-size: 11px; cursor: pointer; }
            .ehx-toggle { position: relative; width: 40px; height: 20px; border: 1px solid currentColor; border-radius: 10px; cursor: pointer; }
            .ehx-toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; background: currentColor; border-radius: 50%; transition: transform 0.3s; }
            .ehx-toggle.active::after { transform: translateX(20px); }
            .ehx-menu-input { padding: 4px 6px; border: 1px solid currentColor; border-radius: 4px; font-size: 11px; width: 120px; min-height: 26px; text-align: center; text-align-last: center; background: transparent; color: inherit; }
            .ehx-menu-btn { padding: 6px 12px; border: 1px solid currentColor; border-radius: 4px; cursor: pointer; font-size: 11px; margin: 2px; background: transparent; color: inherit; }

            /* 预加载状态显示(外观交给站点类) */
            .ehx-preload-status { position: fixed; bottom: 10px; left: 10px; padding: 10px; z-index: 10001; font-size: 11px; max-width: 300px; max-height: 200px; overflow: hidden; display: none; }
            .ehx-preload-status.show { display: block; }
            .ehx-status-header { font-weight: bold; margin-bottom: 8px; text-align: center; padding-bottom: 4px; border-bottom: 1px solid currentColor; }
            .ehx-status-list { max-height: 160px; overflow-y: auto; padding-right: 4px; scrollbar-width: thin; }
            .ehx-status-item { display: flex; justify-content: space-between; align-items: center; padding: 2px 0; border-bottom: 1px solid currentColor; opacity: .6; }
            .ehx-status-item:last-child { border-bottom: none; }
            .ehx-status-page { font-weight: bold; }
            .ehx-status-state { font-size: 10px; font-weight: bold; text-transform: uppercase; }
        `;
        document.head.appendChild(style);
    }

    // 添加加载指示器
    function addLoadingIndicator() {
        // 顶部工具条(简洁,参考结构但不抄配色)
        const topBar = document.createElement('div');
        topBar.className = 'ehx-reader-bar stuffbox';
        topBar.innerHTML = `
            <div class="group">
                <span class="ehx-counter"><span id="ehx-cur">${currentPage}</span> / <span id="ehx-total">${totalPages}</span></span>
            </div>
            <div class="group">
                <button class="ehx-btn" id="ehx-fit-width">${t('fitWidth')}</button>
                <button class="ehx-btn" id="ehx-fit-height">${t('fitHeight')}</button>
                <button class="ehx-btn" id="ehx-menu-open" title="${t('settings')}">⚙️</button>
            </div>
        `;
        document.body.insertBefore(topBar, document.body.firstChild);

        // 容器包裹(居中与内边距)
        const container = document.createElement('div');
        container.className = 'ehx-container';
        const i3 = document.getElementById('i3');
        if (i3 && i3.parentNode) {
            i3.parentNode.insertBefore(container, i3);
            container.appendChild(i3);
            i3.classList.add('ehx-image-wrap');
        }

        // 全局加载指示器
        const indicator = document.createElement('div');
        indicator.id = 'loading-indicator';
        indicator.className = 'stuffbox';
        indicator.textContent = t('loading');
        document.body.appendChild(indicator);

        // 图像局部加载容器
        const img = document.getElementById('img');
        const imgContainer = document.getElementById('i3');
        if (img && imgContainer) {
            // 若尚未包裹,建立 holder
            let holder = document.getElementById('img-holder');
            if (!holder) {
                holder = document.createElement('div');
                holder.id = 'img-holder';
                holder.style.display = 'inline-block';
                img.parentNode.insertBefore(holder, img);
                holder.appendChild(img);
                const loader = document.createElement('div');
                loader.className = 'eh-img-loader';
                loader.innerHTML = '<div class="eh-spinner"></div>';
                holder.appendChild(loader);
            }
        }

        // 添加操作提示
        const hint = document.createElement('div');
        hint.className = 'eh-nav-hint stuffbox';
        hint.innerHTML = `
            <div>${t('navHintTitle')}</div>
            <div>${t('navHintLeft')}</div>
            <div>${t('navHintRight')}</div>
            <div>${t('navHintClick')}</div>
        `;
        document.body.appendChild(hint);

        // N 秒后隐藏提示
        setTimeout(() => {
            hint.style.transition = 'opacity 1s ease';
            hint.style.opacity = '0';
            setTimeout(() => {
                if (hint.parentNode) {
                    hint.parentNode.removeChild(hint);
                }
            }, CONFIG.hintFadeMs);
        }, CONFIG.hintAutoHideMs);

        // 工具条事件
        const btnFitW = document.getElementById('ehx-fit-width');
        const btnFitH = document.getElementById('ehx-fit-height');
        const btnMenu = document.getElementById('ehx-menu-open');
        if (btnFitW) btnFitW.onclick = () => setFitMode('width');
        if (btnFitH) btnFitH.onclick = () => setFitMode('height');
        if (btnMenu) btnMenu.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            const menu = document.querySelector('.ehx-control-menu');
            if (menu) {
                const willShow = !menu.classList.contains('show');
                menu.classList.toggle('show');
                if (willShow) updateMenuInfo();
            }
        };

        // 初始应用适配模式
        applyFitMode();
        updateFitModeButtons();
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // 适应模式
    function setFitMode(mode) {
        fitMode = mode === 'height' ? 'height' : 'width';
        localStorage.setItem('ehx_fit_mode', fitMode);
        applyFitMode();
        updateFitModeButtons();
    }

    function updateFitModeButtons() {
        const btnFitW = document.getElementById('ehx-fit-width');
        const btnFitH = document.getElementById('ehx-fit-height');
        if (btnFitW && btnFitH) {
            btnFitW.classList.toggle('active', fitMode === 'width');
            btnFitH.classList.toggle('active', fitMode === 'height');
        }
    }

    function applyFitMode() {
        const holder = document.getElementById('img-holder');
        const img = document.getElementById('img');
        if (!holder || !img) return;

        if (fitMode === 'height') {
            // 高度适应模式:让整个容器适应浏览器高度
            document.body.classList.add('ehx-fit-height');
            const topBar = document.querySelector('.ehx-reader-bar');
            const topH = topBar ? topBar.getBoundingClientRect().height : 0;
            const avail = Math.max(300, window.innerHeight - topH - 12);
            document.documentElement.style.setProperty('--ehx-available-height', avail + 'px');

            // 清除宽度模式的内联样式
            holder.style.maxHeight = '';
            holder.style.width = '';
            holder.style.height = '';
            img.style.maxWidth = '';
            img.style.maxHeight = '';
        } else {
            // 宽度适应模式:图片宽度撑满容器
            document.body.classList.remove('ehx-fit-height');
            document.documentElement.style.removeProperty('--ehx-available-height');

            // 设置宽度模式样式
            holder.style.maxHeight = '';
            holder.style.width = '100%';
            holder.style.height = '';
            img.style.maxWidth = '100%';
            img.style.maxHeight = 'none';
        }
    }
    // 窗口尺寸变化节流,减少高频调用导致的抖动
    let resizeThrottleId = null;
    window.addEventListener('resize', () => {
        if (resizeThrottleId) clearTimeout(resizeThrottleId);
        resizeThrottleId = setTimeout(() => {
            resizeThrottleId = null;
            applyFitMode();
        }, CONFIG.resizeThrottleMs);
    });

    // ==================== 新增功能:控制菜单和预加载状态 ====================

    // 添加控制菜单
    function addControlMenu() {
        // 控制菜单(移除悬浮触发按钮,改由工具条按钮控制)
        const menu = document.createElement('div');
        menu.className = 'ehx-control-menu stuffbox';
        menu.innerHTML = `
            <div class="ehx-menu-header">
                <span>${t('menuTitle')}</span>
                <button class="ehx-menu-close" title="${t('close')}">×</button>
            </div>

            <div class="ehx-menu-section">
                <h4>${t('basicSettings')}</h4>
                <div class="ehx-menu-item">
                    <label>${t('enableScript')}</label>
                    <div class="ehx-toggle ${scriptEnabled ? 'active' : ''}" data-setting="script-enabled"></div>
                </div>
                <div class="ehx-menu-item">
                    <label>${t('showPreloadStatus')}</label>
                    <div class="ehx-toggle ${showPreloadStatus ? 'active' : ''}" data-setting="show-preload-status"></div>
                </div>
                <div class="ehx-menu-item">
                    <label>${t('updateUrl')}</label>
                    <div class="ehx-toggle ${CONFIG.updateUrl ? 'active' : ''}" data-setting="update-url"></div>
                </div>
                <div class="ehx-menu-item">
                    <label>${t('language')}</label>
                    <select class="ehx-menu-input" data-setting="lang">
                        <option value="auto" ${langSetting==='auto' ? 'selected' : ''}>${t('lang_auto')}</option>
                        <option value="en" ${getCurrentLang()==='en' && langSetting!=='auto' ? 'selected' : ''}>${t('lang_en')}</option>
                        <option value="zh-CN" ${getCurrentLang()==='zh-CN' && langSetting!=='auto' ? 'selected' : ''}>${t('lang_zhCN')}</option>
                        <option value="ja" ${getCurrentLang()==='ja' && langSetting!=='auto' ? 'selected' : ''}>${t('lang_ja')}</option>
                    </select>
                </div>
            </div>

            <div class="ehx-menu-section">
                <h4>${t('preloadSettings')}</h4>
                <div class="ehx-menu-item">
                    <label>${t('preloadCount')}</label>
                    <input type="number" class="ehx-menu-input" min="1" max="10" value="${CONFIG.preloadCount}" data-setting="preload-count">
                </div>
                <div class="ehx-menu-item">
                    <label>${t('maxRetries')}</label>
                    <input type="number" class="ehx-menu-input" min="1" max="10" value="${CONFIG.maxRetries}" data-setting="max-retries">
                </div>
                <div class="ehx-menu-item">
                    <label>${t('retryDelayMs')}</label>
                    <input type="number" class="ehx-menu-input" min="500" max="5000" step="500" value="${CONFIG.retryDelay}" data-setting="retry-delay">
                </div>
            </div>

            <div class="ehx-menu-section">
                <h4>${t('cacheManagement')}</h4>
                <div style="display: flex; flex-wrap: wrap; gap: 4px;">
                    <button class="ehx-menu-btn" data-action="clear-cache">${t('clearImageCache')}</button>
                    <button class="ehx-menu-btn" data-action="clear-page-cache">${t('clearPageCache')}</button>
                    <button class="ehx-menu-btn" data-action="clear-all-cache">${t('clearAllCache')}</button>
                    <button class="ehx-menu-btn" data-action="reset-settings">${t('resetSettings')}</button>
                </div>
            </div>

            <div class="ehx-menu-section">
                <h4>${t('statusInfo')}</h4>
                <div style="font-size: 10px; line-height: 1.4;">
                    <div>${t('cacheImageCount')}: <span id="ehx-cache-count">${imageCache.size}</span></div>
                    <div>${t('cachePageCount')}: <span id="ehx-page-cache-count">${pageDataCache.size}</span></div>
                    <div>${t('preloadStatus')}: <span id="ehx-preload-count">${preloadStatus.size}</span></div>
                </div>
            </div>
        `;

        document.body.appendChild(menu);

        menu.querySelector('.ehx-menu-close').addEventListener('click', () => {
            menu.classList.remove('show');
        });

        // 点击菜单外部关闭
        document.addEventListener('click', (e) => {
            if (!menu.contains(e.target)) {
                menu.classList.remove('show');
            }
        });

        // 绑定设置控制事件
        bindMenuControls(menu);
    }

    // 绑定菜单控制事件
    function bindMenuControls(menu) {
        // 开关控制
        menu.querySelectorAll('.ehx-toggle').forEach(toggle => {
            toggle.addEventListener('click', () => {
                const setting = toggle.dataset.setting;
                const isActive = toggle.classList.contains('active');

                toggle.classList.toggle('active');

                switch(setting) {
                    case 'script-enabled':
                        scriptEnabled = !isActive;
                        localStorage.setItem('ehx_script_enabled', scriptEnabled);
                        showMessage(scriptEnabled ? t('msgScriptEnabled') : t('msgScriptDisabled'));
                        break;
                    case 'show-preload-status':
                        showPreloadStatus = !isActive;
                        localStorage.setItem('ehx_show_preload_status', showPreloadStatus);
                        if (showPreloadStatus) {
                            addPreloadStatusDisplay();
                        } else {
                            const statusDisplay = document.querySelector('.ehx-preload-status');
                            if (statusDisplay) statusDisplay.remove();
                        }
                        break;
                    case 'update-url':
                        CONFIG.updateUrl = !isActive;
                        localStorage.setItem('ehx_update_url', CONFIG.updateUrl);
                        break;
                }
            });
        });

        // 输入框控制
        menu.querySelectorAll('.ehx-menu-input').forEach(input => {
            input.addEventListener('change', () => {
                const setting = input.dataset.setting;
                const value = setting === 'lang' ? String(input.value) : (parseInt(input.value) || 1);

                switch(setting) {
                    case 'preload-count':
                        CONFIG.preloadCount = Math.max(1, Math.min(10, value));
                        localStorage.setItem('ehx_preload_count', CONFIG.preloadCount);
                        input.value = CONFIG.preloadCount;
                        break;
                    case 'max-retries':
                        CONFIG.maxRetries = Math.max(1, Math.min(10, value));
                        localStorage.setItem('ehx_max_retries', CONFIG.maxRetries);
                        input.value = CONFIG.maxRetries;
                        break;
                    case 'retry-delay':
                        CONFIG.retryDelay = Math.max(500, Math.min(5000, value));
                        localStorage.setItem('ehx_retry_delay', CONFIG.retryDelay);
                        input.value = CONFIG.retryDelay;
                        break;
                    case 'lang':
                        langSetting = value;
                        localStorage.setItem('ehx_lang', langSetting);
                        showMessage(t('msgSettingsSaved'));
                        applyLanguage();
                        return;
                }
                showMessage(t('msgSettingsSaved'));
            });
        });

        // 按钮控制
        menu.querySelectorAll('.ehx-menu-btn').forEach(btn => {
            btn.addEventListener('click', () => {
                const action = btn.dataset.action;

                switch(action) {
                    case 'clear-cache':
                        imageCache.clear();
                        showMessage(t('msgImgCacheCleared'));
                        break;
                    case 'clear-page-cache':
                        pageDataCache.clear();
                        showMessage(t('msgPageCacheCleared'));
                        break;
                    case 'clear-all-cache':
                        imageCache.clear();
                        pageDataCache.clear();
                        pageUrlCache.clear();
                        preloadStatus.clear();
                        showMessage(t('msgAllCleared'));
                        updatePreloadStatusDisplay();
                        break;
                    case 'reset-settings':
                        if (confirm(t('confirmReset'))) {
                            localStorage.removeItem('ehx_script_enabled');
                            localStorage.removeItem('ehx_show_preload_status');
                            localStorage.removeItem('ehx_fit_mode');
                            localStorage.removeItem('ehx_update_url');
                            localStorage.removeItem('ehx_preload_count');
                            localStorage.removeItem('ehx_max_retries');
                            localStorage.removeItem('ehx_retry_delay');
                            localStorage.removeItem('ehx_lang');
                            showMessage(t('msgSettingsReset'));
                        }
                        break;
                }
                updateMenuInfo();
            });
        });
    }

    // 更新菜单信息
    function updateMenuInfo() {
        const cacheCount = document.getElementById('ehx-cache-count');
        const pageCacheCount = document.getElementById('ehx-page-cache-count');
        const preloadCount = document.getElementById('ehx-preload-count');

        if (cacheCount) cacheCount.textContent = imageCache.size;
        if (pageCacheCount) pageCacheCount.textContent = pageDataCache.size;
        if (preloadCount) preloadCount.textContent = preloadStatus.size;
    }

    // 添加预加载状态显示
    function addPreloadStatusDisplay() {
        // 移除已存在的显示
        const existing = document.querySelector('.ehx-preload-status');
        if (existing) existing.remove();

        const statusDisplay = document.createElement('div');
        statusDisplay.className = 'ehx-preload-status show stuffbox';
        statusDisplay.innerHTML = `
            <div class="ehx-status-header">${t('preloadStatus')}</div>
            <div class="ehx-status-list"></div>
        `;

        document.body.appendChild(statusDisplay);

        // 添加点击头部切换显示/隐藏功能
        statusDisplay.querySelector('.ehx-status-header').addEventListener('click', () => {
            const list = statusDisplay.querySelector('.ehx-status-list');
            list.style.display = list.style.display === 'none' ? 'block' : 'none';
        });

        updatePreloadStatusDisplay();
    }

    // 更新预加载状态
    function updatePreloadStatus(pageNum, status) {
        preloadStatus.set(pageNum, status);
        updatePreloadStatusDisplay();
    }

    // 更新预加载状态显示
    function updatePreloadStatusDisplay() {
        if (pendingPreloadRender) return;
        pendingPreloadRender = true;
        requestAnimationFrame(() => {
            pendingPreloadRender = false;
            const statusDisplay = document.querySelector('.ehx-preload-status');
            if (!statusDisplay) return;

            const statusList = statusDisplay.querySelector('.ehx-status-list');
            if (!statusList) return;

            // 获取当前页面附近的状态
            const relevantPages = [];
            for (let i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + CONFIG.preloadCount + 2); i++) {
                relevantPages.push(i);
            }

            statusList.innerHTML = relevantPages.map(pageNum => {
                const status = preloadStatus.get(pageNum) || (pageDataCache.has(pageNum) ? 'completed' : 'unknown');
                const isCurrent = pageNum === currentPage;
                const statusText = getStatusText(status);
                const statusClass = `ehx-status-${status}`;

                return `
                    <div class="ehx-status-item ${isCurrent ? 'current' : ''}">
                        <span class="ehx-status-page">${isCurrent ? `► ${pageNum}` : pageNum}</span>
                        <span class="ehx-status-state ${statusClass}">${statusText}</span>
                    </div>
                `;
            }).join('');
        });
    }

    // 获取状态文本
    function getStatusText(status) {
        switch(status) {
            case 'waiting': return t('status_waiting');
            case 'loading': return t('status_loading');
            case 'completed': return t('status_completed');
            case 'failed': return t('status_failed');
            default: return t('status_unknown');
        }
    }

    // 应用当前语言到 UI
    function applyLanguage() {
        // 顶部按钮与标题
        const btnW = document.getElementById('ehx-fit-width');
        const btnH = document.getElementById('ehx-fit-height');
        const btnMenu = document.getElementById('ehx-menu-open');
        const indicator = document.getElementById('loading-indicator');
        if (btnW) btnW.textContent = t('fitWidth');
        if (btnH) btnH.textContent = t('fitHeight');
        if (btnMenu) btnMenu.title = t('settings');
        if (indicator) indicator.textContent = t('loading');
        // 预加载状态标题
        const statusHeader = document.querySelector('.ehx-preload-status .ehx-status-header');
        if (statusHeader) statusHeader.textContent = t('preloadStatus');

        // 重新渲染菜单(保留显示状态)
        const oldMenu = document.querySelector('.ehx-control-menu');
        const wasShown = oldMenu && oldMenu.classList.contains('show');
        if (oldMenu) oldMenu.remove();
        addControlMenu();
        const newMenu = document.querySelector('.ehx-control-menu');
        if (newMenu && wasShown) newMenu.classList.add('show');

        // 刷新预加载状态里的文本
        updatePreloadStatusDisplay();
    }

})();