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.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();