LOLICON Hentai Enhancer

E-Hentai/ExHentai Auto Window Adaptation, Adjustable Thumbnails (size/margin), Quick Favorite, Infinite Scroll, Load More Thumbnails

// ==UserScript==
// @name                LOLICON Hentai Enhancer
// @name:zh-CN          LOLICON Hentai 增强器
// @name:zh-TW          LOLICON Hentai 增強器
// @name:ja             LOLICON Hentai 強化版
// @name:ko             LOLICON Hentai 향상기
// @name:ru             LOLICON Hentai Улучшатель
// @namespace           https://greasyfork.org/scripts/516145
// @version             2025.09.16
// @description         E-Hentai/ExHentai Auto Window Adaptation, Adjustable Thumbnails (size/margin), Quick Favorite, Infinite Scroll, Load More Thumbnails
// @description:zh-CN   E-Hentai/ExHentai 自动适配窗口尺寸、缩略图调整(大小/间距)、快速收藏、无限滚动、加载更多缩略图
// @description:zh-TW   E-Hentai/ExHentai 自動適配視窗尺寸、縮圖調整(大小/間距)、快速收藏、無限滾動、加載更多縮圖
// @description:ja      E-Hentai/ExHentai ウィンドウ自動適応、サムネイルサイズ・間隔調整、クイックお気に入り、無限スクロール、サムネイル追加読み込み
// @description:ko      E-Hentai/ExHentai 자동 창 크기 조절, 썸네일 크기/간격 조절, 빠른 즐겨찾기, 무한 스크롤, 썸네일 더보기
// @description:ru      E-Hentai/ExHentai Автоматическая подгонка окна, Настройка миниатюр (размер/отступ), Быстрое добавление в избранное, Бесконечная прокрутка, Загрузка дополнительных миниатюр
// @icon                https://e-hentai.org/favicon.ico
// @match               *://e-hentai.org/*
// @match               *://exhentai.org/*
// @match               *://exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion/*
// @match               *://*.e-hentai.org/*
// @match               *://*.exhentai.org/*
// @match               *://*.exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion/*
// @run-at              document-end
// @grant               GM_setValue
// @grant               GM_getValue
// @grant               GM_deleteValue
// @grant               GM_registerMenuCommand
// @noframes
// ==/UserScript==

(function () {
    'use strict';

    /** 根据 id 获取对应的 DOM 元素 */
    const $i = (id) => document.getElementById(id);
    /** 根据类名获取 DOM 集合 (HTMLCollection) */
    const $c = (name) => document.getElementsByClassName(name);
    /** querySelector 单个元素 */
    const $ = (sel) => document.querySelector(sel);
    /** querySelectorAll 多个元素 (NodeList) */
    const $$ = (sel) => document.querySelectorAll(sel);
    /** 创建元素 */
    const $create = (tag) => document.createElement(tag);

    /** 获取当前设备的设备像素比(DPR)*/
    const devicePixelRatio = window.devicePixelRatio || 1;

    /** 用于存储布局相关的动态数据 */
    const layout = {};

    /** 页面项目信息 */
    let pageItemsData = [];

    /** 页面项目序号 */
    let pageItemsIndex = 0;

    /** 配置项 */
    const config = {
        zoomFactorS: { def: 1, step: 0.01, min: 0.5, max: 10 },
        zoomFactorG: { def: 1, step: 0.01, min: 0.5, max: 10 },
        margin: { def: 10, step: 1, min: 0, max: 100 },
        spacing: { def: 15, step: 1, min: 0, max: 100 },
        pageMargin: { def: 10, step: 1, min: 0, max: 1000 },
        pagePadding: { def: 10, step: 1, min: 0, max: 1000 },
        fullScreenMode: { def: false },
        squareMode: { def: false },
        showIndex: { def: false },
        liveURLUpdate: { def: false },
        quickFavorite: { def: true },
        infiniteScroll: { def: false },
        maxPagesS: { def: 0, step: 1, min: 0, max: 1000 },
        moreThumbnail: { def: false },
        maxPagesG: { def: 0, step: 1, min: 0, max: 1000 }
    };

    /** 用于存储脚本的用户配置 */
    const cfg = {};

    /** cfg 从 GM_getValue 读取或写入默认值 */
    Object.entries(config).forEach(([key, conf]) => {
        let val = GM_getValue(key, conf.def);
        if (val === undefined) {
            GM_setValue(key, conf.def);
            val = conf.def;
        }
        cfg[key] = val;
    });

    /** 当前网页信息 */
    const pageInfo = {
        originalUrl: window.location.href,
        isExhentai: window.location.hostname.endsWith('exhentai.org'), // 判断是否是 ex变态
        isTor: window.location.hostname.endsWith('exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion'), // 判断是否是 Tor
        isGalleryPage: window.location.pathname.startsWith('/g/'), // /g/ 画廊页面
        isWatchedPage: window.location.pathname.startsWith('/watched'), // /watched 订阅页面
        isPopularPage: window.location.pathname.startsWith('/popular'), // /popular 热门页面
        isFavoritesPage: window.location.pathname.startsWith('/favorites.php'), // /favorites 收藏夹页面
        listDisplayMode: $('.searchnav div:last-child select')?.value // 获取当前列表的显示模式(m/p/l/e/t)
    };

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

    /** 定义语言包 */
    const _translations = {
        'zoomFactorS': {
            'en': 'Thumbnail Zoom',
            'zh-CN': '缩略图缩放',
            'zh-TW': '縮圖縮放',
            'ja': 'サムネイルズーム',
            'ko': '썸네일 확대 비율',
            'ru': 'Масштаб миниатюры'
        },
        'zoomFactorG': {
            'en': 'Gallery Thumbnail Zoom',
            'zh-CN': '画廊缩略图缩放',
            'zh-TW': '畫廊縮圖縮放',
            'ja': 'ギャラリーサムネイルズーム',
            'ko': '갤러리 썸네일 확대 비율',
            'ru': 'Масштаб миниатюр галереи'
        },
        'margin': {
            'en': 'Thumbnail Margin',
            'zh-CN': '缩略图边距',
            'zh-TW': '縮圖邊距',
            'ja': 'サムネイルマージン',
            'ko': '썸네일 여백',
            'ru': 'Отступы миниатюры'
        },
        'spacing': {
            'en': 'Thumbnail Spacing',
            'zh-CN': '缩略图间距',
            'zh-TW': '縮圖間距',
            'ja': 'サムネイル間隔',
            'ko': '썸네일 간격',
            'ru': 'Интервал миниатюр'
        },
        'pageMargin': {
            'en': 'Page Margin',
            'zh-CN': '页面外边距',
            'zh-TW': '頁面外邊距',
            'ja': 'ページマージン',
            'ko': '페이지 외부 여백',
            'ru': 'Внешний отступ страницы'
        },
        'pagePadding': {
            'en': 'Page Padding',
            'zh-CN': '页面内边距',
            'zh-TW': '頁面內邊距',
            'ja': 'ページパディング',
            'ko': '페이지 내부 여백',
            'ru': 'Внутренний отступ страницы'
        },
        'fullScreenMode': {
            'en': 'Full Screen Mode',
            'zh-CN': '全屏模式',
            'zh-TW': '全螢幕模式',
            'ja': 'フルスクリーンモード',
            'ko': '전체 화면 모드',
            'ru': 'Режим полного экрана'
        },
        'squareMode': {
            'en': 'Square Thumbnail',
            'zh-CN': '方形缩略图',
            'zh-TW': '方形縮圖',
            'ja': 'スクエアサムネイル',
            'ko': '정사각형 썸네일',
            'ru': 'Квадратная миниатюра'
        },
        'showIndex': {
            'en': 'Show Index',
            'zh-CN': '显示序号',
            'zh-TW': '顯示序號',
            'ja': 'インデックスを表示',
            'ko': '인덱스 표시',
            'ru': 'Показать индекс',
        },
        'liveURLUpdate': {
            'en': 'Live URL Update',
            'zh-CN': '实时更新网址',
            'zh-TW': '實時更新網址',
            'ja': 'URLのライブ更新',
            'ko': '실시간 URL 업데이트',
            'ru': 'Живое обновление URL',
        },
        'quickFavorite': {
            'en': 'Quick Favorite',
            'zh-CN': '快速收藏',
            'zh-TW': '快速收藏',
            'ja': 'クイックお気に入り',
            'ko': '빠른 즐겨찾기',
            'ru': 'Быстрое избранное'
        },
        'infiniteScroll': {
            'en': 'Infinite Scroll',
            'zh-CN': '无限滚动',
            'zh-TW': '無限滾動',
            'ja': '無限スクロール',
            'ko': '무한 스크롤',
            'ru': 'Бесконечная прокрутка',
        },
        'maxPagesS': {
            'en': 'Max Pages [0 = Unlimited]',
            'zh-CN': '最大页数 [0 = 无限]',
            'zh-TW': '最大頁數 [0 = 無限]',
            'ja': '最大ページ数 [0 = 無制限]',
            'ko': '최대 페이지 [0 = 무한]',
            'ru': 'Макс. страниц [0 = Бесконечно]'
        },
        'moreThumbnail': {
            'en': 'More Thumbnail',
            'zh-CN': '更多缩略图',
            'zh-TW': '更多縮圖',
            'ja': 'もっとサムネイル',
            'ko': '썸네일 더보기',
            'ru': 'Ещё миниатюры'
        },
        'maxPagesG': {
            'en': 'Max Pages [0 = Unlimited]',
            'zh-CN': '最大页数 [0 = 无限]',
            'zh-TW': '最大頁數 [0 = 無限]',
            'ja': '最大ページ数 [0 = 無制限]',
            'ko': '최대 페이지 [0 = 무한]',
            'ru': 'Макс. страниц [0 = Бесконечно]'
        },
        'settings': {
            'en': 'Settings',
            'zh-CN': '设置',
            'zh-TW': '設置',
            'ja': '設定',
            'ko': '설정',
            'ru': 'Настройки'
        },
        'settingsPanel': {
            'en': 'Settings Panel',
            'zh-CN': '设置面板',
            'zh-TW': '設定面板',
            'ja': '設定画面',
            'ko': '설정 패널',
            'ru': 'Панель настроек'
        },
        'save': {
            'en': 'Save',
            'zh-CN': '保存',
            'zh-TW': '儲存',
            'ja': '保存',
            'ko': '저장',
            'ru': 'Сохранить'
        },
        'cancel': {
            'en': 'Cancel',
            'zh-CN': '取消',
            'zh-TW': '取消',
            'ja': 'キャンセル',
            'ko': '취소',
            'ru': 'Отменить'
        },
        'InvalidPage': {
            'en': 'Unsupported page',
            'zh-CN': '不支持此页面',
            'zh-TW': '不支援此頁面',
            'ja': 'このページはサポートされていません',
            'ko': '이 페이지는 지원되지 않습니다',
            'ru': 'Эта страница не поддерживается'
        }
    };

    /** 输入报错模板 */
    const rangeTemplates = {
        'en': `Invalid {{label}}! Please enter a value between {{min}} and {{max}}. Default {{default}}.`,
        'zh-CN': `{{label}} 无效!请输入介于 {{min}} 和 {{max}} 之间的值。默认值为 {{default}}。`,
        'zh-TW': `{{label}} 無效!請輸入介於 {{min}} 和 {{max}} 之間的值。預設值為 {{default}}。`,
        'ja': `{{label}} が無効です!{{min}}から{{max}}までの値を入力してください。デフォルトは{{default}}です。`,
        'ko': `잘못된 {{label}}! {{min}} 에서 {{max}} 사이의 값을 입력하세요. 기본값 {{default}}`,
        'ru': `Неверный {{label}}! Пожалуйста, введите значение от {{min}} до {{max}}. По умолчанию {{default}}`
    };

    /** 模板替换函数 */
    function interpolate(template, values) {
        return template.replace(/{{(.*?)}}/g, (_, key) => values[key.trim()] ?? '');
    }

    /** 包装 Proxy */
    const translations = new Proxy(_translations, {
        get(target, prop) {
            // 如果访问的是 xxxRange
            const match = prop.match(/^(.+)Range$/);
            if (match) {
                const baseKey = match[1];
                const labelEntry = target[baseKey];
                if (!labelEntry || !config[baseKey]) return undefined;

                const output = {};
                for (const lang of Object.keys(rangeTemplates)) {
                    output[lang] = interpolate(rangeTemplates[lang], {
                        label: labelEntry[lang],
                        min: config[baseKey].min,
                        max: config[baseKey].max,
                        default: config[baseKey].def
                    });
                }
                return output;
            }

            // 普通字段直接返回
            return target[prop];
        }
    });

    /** 根据用户语言选择对应的文本 */
    const translate = (key) => {
        const userLang = navigator.language || navigator.userLanguage;
        const lang = userLang.substring(0, 2);
        const map = {
            zh: userLang.startsWith('zh-TW') ? 'zh-TW' : 'zh-CN',
            ja: 'ja',
            ko: 'ko',
            ru: 'ru'
        };
        const tKey = translations[key];
        if (!tKey) return '';

        return tKey[map[lang]] || tKey.en || key;
    };

    /** 创建控件 HTML */
    function createControlHTML(type, name, value, options = {}) {
        if (type === 'input') {
            const { step, min, max } = config[name];
            return `<div style='margin-bottom: 10px; display: flex; align-items: center;'>
            <label for='${name}Input' style='font-weight: bold; margin-right: 10px;'>${translate(name)} </label>
            <input type='number' id='${name}Input' value='${value}' step='${step}' min='${min}' max='${max}' style='width: 46px; padding: 4px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; margin-left: auto;'>
        </div>`;
        } else if (type === 'checkbox') {
            return `<div style='margin-bottom: 10px; display: flex; align-items: center;'>
            <label for='${name}Input' style='font-weight: bold; margin-right: 10px;'>${translate(name)} </label>
            <input type='checkbox' id='${name}Input' style='width: 20px; height: 20px; cursor: pointer; margin-left: auto;' ${value ? 'checked' : ''}>
        </div>`;
        } else if (type === 'buttons') {
            return `<div style='display: flex; justify-content: space-between;'>
            <button id='saveSettingsBtn' style='padding: 8px 12px; background-color: #00AAFF; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold;'>${translate('save')}</button>
            <button id='cancelSettingsBtn' style='padding: 8px 12px; background-color: #FF2222; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold;'>${translate('cancel')}</button>
        </div>`;
        } else if (type === 'message') {
            return `<div style='margin-top: 20px; margin-bottom: 20px; font-size: 16px; line-height: 2; font-weight: bold; text-align: center;'>${translate(name)}</div>`;
        }
    }

    /** 获取面板内容 */
    function getPanelContent() {
        let controlNames = [];

        if (pageInfo.listDisplayMode === 't') {
            controlNames = [
                'zoomFactorS', 'margin', 'pageMargin', 'pagePadding',
                'fullScreenMode', 'squareMode', 'showIndex',
                'infiniteScroll',
                'quickFavorite', 'liveURLUpdate'
            ];
        } else if (pageInfo.listDisplayMode) {
            controlNames = [
                'pageMargin', 'pagePadding',
                'fullScreenMode', 'showIndex',
                'infiniteScroll',
                'quickFavorite', 'liveURLUpdate'
            ];
        } else if ($i('searchbox')) {
            controlNames = [
                'pageMargin', 'pagePadding',
                'fullScreenMode',
            ];
        } else if (pageInfo.isGalleryPage) {
            controlNames = [
                'zoomFactorG', 'spacing', 'pageMargin',
                'fullScreenMode',
                'moreThumbnail',
                'quickFavorite'
            ];
        } else {
            return createControlHTML('message', 'InvalidPage');
        }

        const htmlPieces = [];
        controlNames.forEach(name => {
            const type = typeof config[name].def === 'boolean' ? 'checkbox' : 'input';
            htmlPieces.push(createControlHTML(type, name, cfg[name]));

            if (name === 'infiniteScroll' && cfg.infiniteScroll) {
                const maxPagesHTML = createControlHTML('input', 'maxPagesS', cfg.maxPagesS);
                htmlPieces.push(maxPagesHTML.replace("style='", "style='margin-left: 24px; color: #666; "));
            } else if (name === 'moreThumbnail' && cfg.moreThumbnail) {
                const maxPagesHTML = createControlHTML('input', 'maxPagesG', cfg.maxPagesG);
                htmlPieces.push(maxPagesHTML.replace("style='", "style='margin-left: 24px; color: #666; "));
            }
        });

        return htmlPieces.join('');
    }

    /** 创建和显示设置面板 */
    function showSettingsPanel() {
        if ($i('settings-panel')) return;
        const panel = $create('div');
        panel.id = 'settings-panel';
        panel.style = 'position: fixed; top: 24px; right: 24px; padding: 12px; background-color: rgba(255,255,255,0.9); border: 2px solid #00AAFF; border-radius: 9px; box-shadow: 0 0 12px rgba(0,0,0,0.24); z-index: 999999; font-size: 14px; color: #222; min-width: 160px;';
        panel.innerHTML = `
            <style>
            #settings-panel input[type=number]::-webkit-outer-spin-button,
            #settings-panel input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
            #settings-panel input[type=number]{-moz-appearance:textfield;}
            </style>
            <h3 style='margin: 0; margin-bottom: 10px; font-size: 18px; color: #00AAFF; text-align: center;'>${translate('settingsPanel')}</h3>
            <div id='settings-controls'>${getPanelContent()}</div>
            ${createControlHTML('buttons')}
        `;

        document.body.appendChild(panel);

        // 面板整体的行为与按钮绑定
        panel.addEventListener('wheel', e => { e.preventDefault(); e.stopPropagation(); }, { passive: false });
        panel.addEventListener('input', e => {
            if (e.target.type === 'number') handleInputChange(e);
            if (e.target.type === 'checkbox') handleCheckboxChange(e);
        });
        panel.addEventListener('wheel', handleWheelChange, { passive: false });

        panel.querySelectorAll('button').forEach(btn => {
            btn.addEventListener('mouseover', () => { btn.style.opacity = '0.8'; });
            btn.addEventListener('mouseout', () => { btn.style.opacity = '1'; });
        });

        const saveBtn = $i('saveSettingsBtn'), cancelBtn = $i('cancelSettingsBtn');
        if (pageInfo.listDisplayMode || $i('searchbox') || pageInfo.isGalleryPage) {
            saveBtn?.addEventListener('click', () => saveSettings(panel));
            cancelBtn?.addEventListener('click', () => cancelSettings(panel));
        } else {
            saveBtn?.addEventListener('click', () => panel.remove());
            cancelBtn?.addEventListener('click', () => panel.remove());
        }

        // 暴露局部刷新函数,方便外部调用
        panel.refreshControls = () => refreshSettingsControls(panel);
    }

    // ---- 局部刷新(只替换 #settings-controls 的 innerHTML,保留容器和已绑定的委托事件) ----
    function refreshSettingsControls(panel) {
        const container = panel.querySelector('#settings-controls');
        if (!container) return;
        container.innerHTML = getPanelContent();
        // 不需要重新绑定事件,因为事件委托绑定在 container 元素上并不会被替换
    }

    /** 通用输入赋值函数 */
    function setCfgByInput(id, value) {
        const key = id.replace(/Input$/, '');
        cfg[key] = value;
    }

    /** 输入框变化事件 */
    function handleInputChange(event) {
        const { id, value } = event.target;
        const numValue = parseFloat(value);
        const key = id.replace(/Input$/, '');
        if (
            config[key] &&
            numValue >= config[key].min &&
            numValue <= config[key].max
        ) {
            setCfgByInput(id, numValue);
            applyChanges();
        }
    }

    /** 复选框变化事件 */
    function handleCheckboxChange(event) {
        const { id, checked } = event.target;
        setCfgByInput(id, checked);
        applyChanges();

        // 如果这个复选框和展示逻辑有关(例如 infiniteScroll),局部刷新控件
        if (id === 'infiniteScrollInput' || id === 'moreThumbnailInput') {
            const panel = document.getElementById('settings-panel');
            if (panel && typeof panel.refreshControls === 'function') {
                panel.refreshControls();
            }
        }
    }

    /** 滚轮事件处理 */
    function handleWheelChange(event) {
        if (event.target.type !== 'number') return;
        event.preventDefault();
        const input = event.target;
        let value = parseFloat(input.value);
        const step = parseFloat(input.step);
        value = Math.min(parseFloat(input.max), Math.max(parseFloat(input.min), value + (event.deltaY < 0 ? step : -step)));
        input.value = step < 1 ? value.toFixed(2) : value;
        input.dispatchEvent(new InputEvent('input', { bubbles: true }));
    }

    /** 保存设置 */
    function saveSettings(panel) {
        let errors = [];
        panel.querySelectorAll('input[type="number"]').forEach(input => {
            const key = input.id.replace('Input', '');
            const value = parseFloat(input.value);
            if (isNaN(value) || value < config[key].min || value > config[key].max) {
                errors.push(translate(key + 'Range'));
            }
        });
        if (errors.length > 0) {
            alert(errors.join('\n\n'));
            return;
        }
        // 无错误时保存所有设置
        Object.keys(config).forEach(key => GM_setValue(key, cfg[key]));
        panel.remove();
    }

    /** 取消设置 */
    function cancelSettings(panel) {
        Object.keys(config).forEach(key => {
            cfg[key] = GM_getValue(key)
        });
        applyChanges();
        panel.remove();
    }

    /** 应用更改 */
    function applyChanges() {
        calculateDimensions();
        if (pageInfo.listDisplayMode) {
            throttledAdjustColumnsS();
            if (pageInfo.listDisplayMode === 't') modifyThumbnailSizeS();
            updateGlinkIndex();
            cfg.quickFavorite ? (initFavcat(), replaceFavClickS()) : restoreElements();
        } else if ($i('searchbox')) {
            throttledAdjustColumnsS();
        } else if (pageInfo.isGalleryPage) {
            throttledAdjustColumnsG();
            modifyThumbnailSizeG();
            cfg.quickFavorite ? (initFavcat(), replaceFavClickG()) : restoreElements();
        }
    }

    /** 初始化设置 如果为空 先保存初始值 */
    function initialize() {
        if (GM_getValue('zoomFactor') !== undefined && GM_getValue('zoomFactorS') === undefined) {
            GM_setValue('zoomFactorS', GM_getValue('zoomFactor'));
            GM_deleteValue('zoomFactor');
        }

        for (const [key, cfgItem] of Object.entries(config)) {
            let val = GM_getValue(key);
            if (val === undefined) {
                GM_setValue(key, cfgItem.def);
                val = cfgItem.def;
            }
            cfg[key] = val;
        }
    }

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

    /** 计算尺寸 */
    function calculateDimensions() {
        layout.columnWidthS = 250 * cfg.zoomFactorS + cfg.margin * 2; // 每列的宽度 250-400 270
        layout.columnWidthSb = layout.columnWidthS + (2 / devicePixelRatio); // 加上缩略图边框,边框宽度受设备像素比影响
        layout.columnWidthG = 100 * cfg.zoomFactorG + cfg.spacing; // 画廊每列的宽度(100X) spacing:15  + (2 / devicePixelRatio)
        layout.marginAdjustmentS = 14 + cfg.pageMargin * 2; // 页面边距调整值 body-padding:2 ido-padding:5
        layout.marginAdjustmentG = 34 + cfg.pageMargin * 2; // 画廊页面边距调整值 body-padding:2 gdt-padding:15
        layout.paddingAdjustmentS = cfg.pagePadding * 2; // 页面内边距调整值
    }

    /** 搜索类别行 */
    let initialTableRows = null;

    /** 根据页面宽度动态调整列数 画廊列表页面 */
    function adjustColumnsS() {
        console.log('LOLICON 画廊列表页面调整');

        const width = document.documentElement.clientWidth; // window.innerWidth
        const minWidthNumber = parseFloat(getComputedStyle($c('ido')[0]).minWidth);

        let clientWidthS_itg = Math.max(width - layout.marginAdjustmentS - layout.paddingAdjustmentS, minWidthNumber); // 计算宽度
        layout.columnsS = Math.max(Math.floor(clientWidthS_itg / layout.columnWidthSb), 1); // 计算列数
        const baseWidth = (pageInfo.listDisplayMode === 't') ? layout.columnsS * layout.columnWidthSb : Math.min(720 + 670 + 14, clientWidthS_itg);
        clientWidthS_itg = Math.max(baseWidth, cfg.fullScreenMode ? clientWidthS_itg : minWidthNumber); // 根据全屏模式调整

        let clientWidthS_ido = Math.min(clientWidthS_itg + layout.paddingAdjustmentS, width);
        $c('ido')[0].style.maxWidth = clientWidthS_ido + 'px'; // 设置最大宽度 1370
        if (pageInfo.listDisplayMode === 't' && $c('itg gld')[0]) {
            $c('itg gld')[0].style.gridTemplateColumns = 'repeat(' + layout.columnsS + ', 1fr)'; // 设置列数
            $c('itg gld')[0].style.width = clientWidthS_itg + 'px'; // 设置边距 '99%'
        } else if ($c('itg')[0]) {
            $c('itg')[0].style.maxWidth = clientWidthS_itg + 'px';
            $c('itg')[0].style.width = clientWidthS_itg + 'px';
        }

        const searchnavEls = $c('searchnav');
        const paddingValue = (width - layout.marginAdjustmentS - layout.paddingAdjustmentS >= minWidthNumber)
            ? cfg.pagePadding
            : (width - minWidthNumber - layout.marginAdjustmentS) / 2;
        for (let i = 0; i < 2; i++) {
            const el = searchnavEls[i];
            if (!el) continue;
            el.children[0].style.padding = '0 0 0 ' + + paddingValue + 'px';
            el.children[6].style.padding = '0 ' + paddingValue + 'px 0 0';
        }

        const searchbox = $i('searchbox'); // 搜索盒子
        if (searchbox) {
            const tbody = searchbox.querySelector('tbody');
            if (tbody) {
                // 保存搜索类别行
                if (!initialTableRows) {
                    initialTableRows = tbody.innerHTML;
                }
                if (clientWidthS_ido >= 720 + 670 + 14 + layout.paddingAdjustmentS) { //1460
                    // 合并搜索类别行
                    const rows = tbody.querySelectorAll('tr');
                    if (rows.length >= 2) {
                        const firstRow = rows[0];
                        const secondRow = rows[1];

                        Array.from(secondRow.children).forEach(td => {
                            firstRow.appendChild(td);
                        });
                        secondRow.remove();
                    }
                } else {
                    // 恢复为初始状态
                    tbody.innerHTML = initialTableRows;
                }
            }

            // 调整搜索盒子大小
            const isLargerWidth = clientWidthS_ido >= 720 + 670 + 14 + layout.paddingAdjustmentS; //1460
            if ($c('idi')[0]) { $c('idi')[0].style.width = (isLargerWidth ? 720 + 670 : 720) + 'px'; }
            if ($c('idi')[1]) { $c('idi')[1].style.width = (isLargerWidth ? 720 + 670 : 720) + 'px'; }
            if ($i('f_search')) { $i('f_search').style.width = (isLargerWidth ? 560 + 670 : 560) + 'px'; }
        }

        // 调整更窄的收藏页面,和首页保持一致
        if (pageInfo.isFavoritesPage && clientWidthS_ido < (930 + layout.paddingAdjustmentS)) {
            const noselWidth = Math.max(735, Math.min(825, clientWidthS_ido));
            if ($c('nosel')[1]) { $c('nosel')[1].style.width = noselWidth + 'px'; }
            const fpElements = $$('div.fp');
            const fpWidth = Math.max(142, Math.min(160, (clientWidthS_ido - 16) / 5 - 1)) + 'px';
            for (let i = 0; i < Math.min(10, fpElements.length); i++) {
                fpElements[i].style.width = fpWidth;
            }
            const idoTarget = $('.ido > div:nth-child(3)');
            if (idoTarget) {
                idoTarget.style.width = noselWidth + 'px';
                const inputTarget = idoTarget.querySelector('form:nth-child(1) > div:nth-child(2) > input:nth-child(1)');
                if (inputTarget) {
                    inputTarget.setAttribute('size', Math.max(84, Math.min(90, 84 + (noselWidth - 735) / 15)));
                }
            }
        } else if (pageInfo.isFavoritesPage) {
            if ($c('nosel')[1]) { $c('nosel')[1].style.width = '825px'; }
            const fpElements = $$('div.fp');
            for (let i = 0; i < Math.min(10, fpElements.length); i++) {
                fpElements[i].style.width = '160px';
            }
            const idoTarget = $('.ido > div:nth-child(3)');
            if (idoTarget) {
                idoTarget.style.width = '825px';
                const inputTarget = idoTarget.querySelector('form:nth-child(1) > div:nth-child(2) > input:nth-child(1)');
                if (inputTarget) {
                    inputTarget.setAttribute('size', '90');
                }
            }
        }

        if (layout.columnsS != layout.OLDcolumnsS && cfg.liveURLUpdate && !pageInfo.isPopularPage && !pageInfo.isFavoritesPage) {
            throttledGetRowInfo();
            layout.OLDcolumnsS = layout.columnsS;
        }
    }

    /** 根据页面宽度动态调整列数 画廊页面 */
    function adjustColumnsG() {
        console.log('LOLICON 画廊页面调整');

        const gdt = $i('gdt');
        if (gdt) {

            const width = window.innerWidth;
            const isGT200 = gdt.classList.contains('gt200');
            const pixelCorrection = 2 / devicePixelRatio;

            const spacingCorrection = isGT200 ? cfg.spacing * 2 : cfg.spacing;
            const columnWidthGL = isGT200 ? layout.columnWidthG * 2 + pixelCorrection : layout.columnWidthG + pixelCorrection;

            const clientWidthGL = Math.max(700, width - layout.marginAdjustmentG) + spacingCorrection;
            const columnsG = Math.floor(clientWidthGL / columnWidthGL);
            const clientWidthG_gdt = cfg.fullScreenMode ? Math.max(700, width - layout.marginAdjustmentG) : Math.max(700, columnsG * columnWidthGL - spacingCorrection);

            if ($c('gm')[0]) { $c('gm')[0].style.maxWidth = clientWidthG_gdt + 20 + 'px'; } // 设置最详情大宽度 720 960 1200
            if ($c('gm')[1]) { $c('gm')[1].style.maxWidth = clientWidthG_gdt + 20 + 'px'; } // 设置最评论区大宽度 720 960 1200
            if ($i('gdo')) { $i('gdo').style.maxWidth = clientWidthG_gdt + 20 + 'px'; } // 设置缩略图设置栏最大宽度 720 960 1200

            let clientWidthG_gdt_gd2 = clientWidthG_gdt - 255; // 设置标题栏宽度 710 925
            let clientWidthG_gdt_gmid = clientWidthG_gdt - 250; // 设置标签栏宽度 710 930
            let clientWidthG_gdt_gd4 = clientWidthG_gdt - 600; // 设置标签栏宽度 360 580

            if (width <= 1230) {
                clientWidthG_gdt_gd2 = clientWidthG_gdt_gd2 + 255;
                clientWidthG_gdt_gmid = clientWidthG_gdt_gmid + 255;
                clientWidthG_gdt_gd4 = clientWidthG_gdt_gd4 + 255;
            }

            if ($i('gd2')) { $i('gd2').style.width = clientWidthG_gdt_gd2 + 'px'; }
            if ($i('gmid')) { $i('gmid').style.width = clientWidthG_gdt_gmid + 'px'; }
            if ($i('gd4')) { $i('gd4').style.width = clientWidthG_gdt_gd4 + 'px'; }


            gdt.style.maxWidth = clientWidthG_gdt + 'px'; // 设置最大宽度 700 940 1180
            gdt.style.gridTemplateColumns = 'repeat(' + columnsG + ', 1fr)';
            gdt.style.gap = cfg.spacing + 'px';
        }
    }

    /** 收集画廊列表页面信息 */
    function collectDataS() {
        const strategies = {
            m: 'td:nth-child(4) > a:nth-child(1)',
            p: 'td:nth-child(4) > a:nth-child(1)',
            l: 'td:nth-child(3) > a:nth-child(1)',
            e: 'td:nth-child(1) > div:nth-child(1) > a:nth-child(1)',
            t: 'a:nth-child(1)'
        };
        if (pageInfo.listDisplayMode === 't') {
            const gElements = $$('.gl1t');
            gElements.forEach((el, index) => {
                if (index === pageItemsIndex) {
                    pageItemsIndex++;

                    const gl3t = el.querySelector('.gl3t');
                    const gl4t = el.querySelector('.gl4t');
                    const gl5t = el.querySelector('.gl5t');
                    const gl6t = el.querySelector('.gl6t');
                    const glink = el.querySelector('.glink');
                    const gl5tFirstChildDiv = gl5t?.querySelector('div:nth-child(1)');
                    const img = gl3t?.querySelector('img');

                    const urlElement = el.querySelector(strategies[pageInfo.listDisplayMode]);
                    const match = urlElement?.href.match(/\/g\/(\d+)\//);
                    const gid = match ? Number(match[1]) : null;

                    pageItemsData.push({
                        el,
                        gl3t,
                        gl4t,
                        gl5t,
                        gl6t,
                        glink,
                        gl5tFirstChildDiv,
                        img,
                        gid,
                        originalWidth: gl3t?.clientWidth,
                        originalHeight: gl3t?.clientHeight,
                        originalImgWidth: img?.clientWidth,
                        originalImgHeight: img?.clientHeight,
                    });
                }
            });
        } else {
            const gElements = $$('.itg > tbody > tr');
            gElements.forEach((el, index) => {
                if (index === pageItemsIndex) {
                    pageItemsIndex++;

                    if (el.querySelector('td.itd')) return; // 跳过广告行
                    const glink = el.querySelector('.glink');

                    const urlElement = el.querySelector(strategies[pageInfo.listDisplayMode]);
                    const match = urlElement?.href.match(/\/g\/(\d+)\//);
                    const gid = match ? Number(match[1]) : null;

                    pageItemsData.push({
                        el,
                        glink,
                        gid,
                    });
                }
            });
        }
    }

    /** 收集画廊页面信息 */
    function collectDataG() {
        const gdt = $i('gdt');
        const gdtThumbsSingle = gdt.querySelectorAll('a > div:nth-child(1)');
        const gdtThumbsDouble = gdt.querySelectorAll('a > div:nth-child(1) > div:nth-child(1)');
        const gdtThumbs = gdtThumbsDouble.length ? gdtThumbsDouble : gdtThumbsSingle;
        const gdtThumbPages = gdt.querySelectorAll('a > div:nth-child(1) > div:nth-child(2)');

        const spriteCountMap = new Map(); // 记录每张背景图出现次数

        gdtThumbs.forEach((el, index) => {
            if (index === pageItemsIndex) {
                pageItemsIndex++;

                const style = getComputedStyle(el);
                const backgroundPosition = style.backgroundPosition;
                const backgroundImage = style.backgroundImage;

                const spriteIndex = (spriteCountMap.get(backgroundImage) || 0) + 1;
                spriteCountMap.set(backgroundImage, spriteIndex);

                const width = el.clientWidth;
                const height = el.clientHeight;
                const itemWidth = (width === 200 || height === 300) ? 200 : 100;

                const pageEl = gdtThumbPages[index] ?? null;

                pageItemsData.push({
                    el,
                    backgroundPosition,
                    backgroundImage,
                    spriteIndex,
                    itemsPerSprite: null,
                    width,
                    height,
                    itemWidth,
                    pageEl,
                });
            }
        });

        pageItemsData.forEach(data => {
            if (data.itemsPerSprite == null) {
                data.itemsPerSprite = spriteCountMap.get(data.backgroundImage) || 1;
            }
        });
    }

    /** 修改画廊列表缩略图大小 */
    function modifyThumbnailSizeS() {
        console.log('LOLICON 修改缩略图大小');

        const minWidthNumber = parseFloat(getComputedStyle($c('ido')[0]).minWidth);
        let columnWidthSbm = Math.max(layout.columnWidthSb, minWidthNumber / Math.floor(Math.max(minWidthNumber / layout.columnWidthSb, 1)));

        if (cfg.fullScreenMode) {
            columnWidthSbm = layout.columnWidthS * 2;
        }

        pageItemsData.forEach((data, index) => {
            const {
                el,
                gl3t,
                gl4t,
                gl5t,
                gl6t,
                glink,
                gl5tFirstChildDiv,
                img,
                gid,
                originalWidth,
                originalHeight,
                originalImgWidth,
                originalImgHeight
            } = data;

            let zoomFactorL = cfg.zoomFactorS;

            if (cfg.squareMode && originalWidth < 250) {
                zoomFactorL = cfg.zoomFactorS * 250 / originalWidth;
            }

            // 设置 gl1t 的宽度
            el.style.minWidth = layout.columnWidthS + 'px';
            el.style.maxWidth = columnWidthSbm + 'px';

            // 调整 gl3t 的宽高
            if (gl3t) {
                const newWidth = originalWidth * zoomFactorL;
                const newHeight = originalHeight * zoomFactorL;
                gl3t.style.width = newWidth + 'px';
                gl3t.style.height = (cfg.squareMode ? newWidth : newHeight) + 'px';
            }

            // 小列宽时处理 gl5t 换行逻辑
            if (gl5t) {
                const isSmallWidth = layout.columnWidthS <= 199;
                gl5t.style.flexWrap = isSmallWidth ? 'wrap' : '';
                gl5t.style.height = isSmallWidth ? '92px' : '';

                if (gl5tFirstChildDiv) { gl5tFirstChildDiv.style.left = isSmallWidth ? '4.5px' : ''; }
            }

            // 调整图片的宽高
            if (img) {
                const newImgWidth = originalImgWidth * zoomFactorL;
                const newImgHeight = originalImgHeight * zoomFactorL;
                let width = newImgWidth;
                let height = newImgHeight;
                let top = '';
                let left = '';

                if (cfg.squareMode) {
                    if (newImgWidth <= newImgHeight) {
                        top = ((originalWidth * zoomFactorL) - newImgHeight) / 2 + 'px';
                    } else {
                        left = ((originalWidth * zoomFactorL) - (newImgWidth * newImgWidth / newImgHeight)) / 2 + 'px';
                        width = newImgWidth * newImgWidth / newImgHeight;
                        height = newImgWidth;
                    }
                } else {
                    top = ((originalHeight * zoomFactorL) - newImgHeight) / 2 + 'px';
                }

                img.style.width = width + 'px';
                img.style.height = height + 'px';
                img.style.top = top;
                img.style.left = left;
            }
        });
    }

    /** 调整 glink 的标题序号 */
    function updateGlinkIndex() {
        console.log('LOLICON 调整 glink 的标题序号');

        pageItemsData.forEach((data, index) => {
            const { glink } = data;

            if (glink) {
                const glinkSpan = glink.querySelector('span[data-LOLICON-index="true"]');

                if (cfg.showIndex) {
                    if (!glinkSpan) {
                        const span = $create('span');
                        span.setAttribute('data-LOLICON-index', 'true');
                        if (pageInfo.listDisplayMode === 't' || pageInfo.listDisplayMode === 'e') {
                            span.textContent = `【${index + 1}】 `;
                        } else {
                            span.textContent = `【${index}】 `;
                        }
                        glink.insertBefore(span, glink.firstChild);
                    }
                } else if (glinkSpan) {
                    glinkSpan.remove();
                }
            }
        });
    }

    /** 修改画廊缩略图大小 */
    function modifyThumbnailSizeG() {
        console.log('LOLICON 修改画廊缩略图大小');

        const isSprite = pageItemsData[0].itemsPerSprite !== 1 && pageItemsData.length > 1;

        pageItemsData.forEach((data, index) => {
            const {
                el,
                backgroundPosition,
                backgroundImage,
                spriteIndex,
                itemsPerSprite,
                width,
                height,
                itemWidth,
                pageEl
            } = data;

            // 设置缩略图尺寸
            el.style.width = width * cfg.zoomFactorG + 'px';
            el.style.height = height * cfg.zoomFactorG + 'px';

            // 背景图位置缩放
            const [x] = backgroundPosition.split(' ').map(parseFloat);
            el.style.backgroundPosition = x * cfg.zoomFactorG + 'px 0px';

            // 设置page最大宽度(便于居中)
            if (pageEl) {
                pageEl.style.maxWidth = itemWidth * cfg.zoomFactorG + 'px';
            }

            // 处理雪碧图尺寸
            if (isSprite) {
                el.style.backgroundSize = itemWidth * itemsPerSprite * cfg.zoomFactorG + 'px auto';
            } else {
                // 非雪碧图直接缩放原图
                el.style.backgroundSize = width * cfg.zoomFactorG + 'px auto';
            }
        });
    }
    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    /** 颜色映射表,用于给不同收藏夹分配颜色 */
    const COLOR_MAP = {
        0: '#cccccc', 1: '#ff8080', 2: '#ffaa55', 3: '#ffff00', 4: '#80ff80',
        5: '#aaff55', 6: '#00ffff', 7: '#aaaaff', 8: '#cc80ff', 9: '#ff80cc'
    };

    const COLOR_MAP_B = {
        0: 'rgb(0, 0, 0)', 1: 'rgb(255, 0, 0)', 2: 'rgb(255, 170, 0)', 3: 'rgb(221, 221, 0)', 4: 'rgb(0, 136, 0)',
        5: 'rgb(153, 255, 68)', 6: 'rgb(68, 187, 255)', 7: 'rgb(0, 0, 255)', 8: 'rgb(85, 0, 136)', 9: 'rgb(238, 136, 238)'
    };

    /** 异步获取收藏夹名称列表 */
    const getFavcatList = async () => {
        let names = [];
        try {
            if (location.pathname === '/uconfig.php') {
                const html = document.documentElement.innerHTML;
                names = [...html.matchAll(/input type="text" name="favorite_\d" value="(.*?)"/g)].map(m => m[1]);
            }
            else if (location.pathname === '/gallerypopups.php') {
                const nosel = $('.nosel');
                if (nosel) {
                    names = [...nosel.querySelectorAll('div[style*="cursor:pointer"]')]
                        .map(div => div.querySelector('div[style*="padding-top"]').textContent.trim());
                }
            } else {
                // 其他页面用 fetch 请求
                const res = await fetch(location.origin + '/uconfig.php');
                const html = await res.text();
                names = [...html.matchAll(/input type="text" name="favorite_\d" value="(.*?)"/g)].map(m => m[1]);
            }
        } catch (error) {
            console.error('LOLICON 获取收藏夹名称列表时发生错误:', error);
        }
        return names;
    };

    /** 收藏夹名称 */
    let favcat = [];

    /** 异步函数:更新收藏夹名称 */
    async function updateFavcat() {
        favcat = await getFavcatList();
        localStorage.favcat = JSON.stringify(favcat);
        console.log('LOLICON 更新收藏夹名称', favcat);
    }

    /** 异步函数:初始化收藏夹列表 */
    async function initFavcat() {
        if (['/uconfig.php', '/gallerypopups.php'].includes(location.pathname)) {
            await updateFavcat();
        } else if (!localStorage.favcat || localStorage.favcat === '[]') {
            await updateFavcat();
        } else {
            favcat = JSON.parse(localStorage.favcat);
        }
    }

    /** 异步函数:发送收藏或取消收藏请求 // url: 请求地址 // add: true为收藏,false为取消收藏 // favcat: 收藏夹编号 */
    const fetchFav = async (url, add, favcat) => {
        try {
            // 发送POST请求,提交收藏/取消收藏参数
            const res = await fetch(url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: add
                    ? `favcat=${favcat}&favnote=&apply=Add+to+Favorites&update=1`
                    : 'favcat=favdel&favnote=&update=1', // 取消收藏请求体
                credentials: 'same-origin' // 同源策略,携带cookie
            });

            const html = await res.text();
            // 从返回HTML中提取<script>代码(用于更新父窗口)
            const scripts = [...html.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/gi)];
            if (scripts.length > 1) {
                let updateCode = scripts[1][1];
                // 去除window.opener调用和窗口关闭代码,防止影响当前页面
                updateCode = updateCode
                    .replace(/window\.opener\./g, '')
                    .replace(/window\.close\(\);?/g, '');
                new Function(updateCode)(); // 执行提取的JS代码
            }
        } catch (error) {
            console.error('LOLICON 发送收藏或取消收藏请求时发生错误:', error);
        }
    };

    /** 显示收藏菜单 // anchorEl: 触发菜单的锚元素,用于定位菜单位置 // favUrl: 收藏请求URL */
    function showFavMenu(anchorEl, favUrl) {
        // 移除已有的收藏菜单,避免重复显示
        const existingMenu = $('.fav_popup_menu');
        if (existingMenu) existingMenu.remove();

        // 判断是否显示“取消收藏”菜单项
        const shouldShowRemoveItem = () => {
            if (anchorEl.id === 'gdf') {
                if (anchorEl.querySelector('div.i') === null) return false;
            } else {
                if (!anchorEl.hasAttribute('title')) return false;
            }
            return true;
        };

        // 创建菜单容器并设置基础样式
        const menu = $create('div');
        menu.className = 'fav_popup_menu';
        menu.style.position = 'absolute';
        menu.style.background = 'rgba(0, 0, 0, 0.8)';
        // menu.style.border = '1px solid #000';
        // menu.style.borderRadius = '6px';
        menu.style.boxShadow = '0 0 6px rgba(0,0,0,0.8)';
        menu.style.padding = '2px';
        menu.style.zIndex = 9999;
        menu.style.color = '#fff';
        menu.style.minWidth = '166px';
        menu.style.fontSize = '10pt';
        menu.style.fontWeight = 'bold';
        menu.style.textShadow = '0 0 1.2px #000, 0 0 2.4px #000, 0 0 3.6px #000';


        // 创建菜单项的辅助函数
        function createMenuItem(text, color, onClick, options = {}) {
            const item = $create('div');
            item.textContent = text;
            item.style.padding = '6px';
            item.style.cursor = 'pointer';
            item.style.color = color;
            item.style.userSelect = 'none';
            item.style.minHeight = '18px';
            item.style.lineHeight = '18px';
            // item.style.transition = 'background 0.1s';

            if (options.isAction) {
                item.style.flex = '1';
                item.style.textAlign = 'center';
                item.style.fontSize = (options.fontSize || 10) + 'pt';
            }

            item.addEventListener('mouseenter', () => {
                item.style.color = '#fff';
                item.style.background = color;
            });
            item.addEventListener('mouseleave', () => {
                item.style.color = color;
                item.style.background = 'transparent';
            });
            item.addEventListener('click', onClick);

            return item;
        }

        // 添加收藏夹菜单项
        favcat.forEach((name, idx) => {
            const item = createMenuItem(name, COLOR_MAP[idx] || '#fff', () => {
                fetchFav(favUrl, true, idx).then(() => {
                    const iconDiv = anchorEl.querySelector('div#fav div.i');
                    if (iconDiv) {
                        iconDiv.style.marginLeft = '0';
                    }
                });
                menu.remove();
            });
            menu.appendChild(item);
        });

        // 添加“取消收藏”和“收藏弹窗”同一行按钮
        const actionRow = $create('div');
        actionRow.style.display = 'flex';

        // 左侧:收藏弹窗
        const popupItem = createMenuItem('⭐', '#fff', () => {
            window.open(favUrl, '_blank', 'width=675,height=415');
            menu.remove();
        }, { isAction: true, fontSize: 12 });
        actionRow.appendChild(popupItem);

        // 右侧:取消收藏
        if (shouldShowRemoveItem()) {
            const removeItem = createMenuItem('❌', '#f22', () => {
                fetchFav(favUrl, false, 0);
                menu.remove();
            }, { isAction: true, fontSize: 12 });
            actionRow.appendChild(removeItem);
        }

        menu.appendChild(actionRow);

        // 插入页面并定位
        document.body.appendChild(menu);
        const rect = anchorEl.getBoundingClientRect();
        const menuHeight = menu.offsetHeight;
        let left = window.scrollX + rect.left;
        const scrollY = window.scrollY;
        let top = scrollY + rect.top - menuHeight;
        if (top < scrollY) {
            top = scrollY + rect.bottom;
        }

        menu.style.left = left + 'px';
        menu.style.top = top + 'px';

        // 关闭菜单的函数
        const closeMenu = () => {
            if (menu.parentNode) {
                menu.remove();
            }
            document.removeEventListener('mousedown', handler);
            document.removeEventListener('contextmenu', handler);
            window.removeEventListener('resize', handler);
        };

        const handler = e => {
            if (e.type === 'mousedown') {
                if (!menu.contains(e.target) && e.target !== anchorEl) {
                    closeMenu();
                }
            } else if (e.type === 'contextmenu' || e.type === 'resize') {
                closeMenu();
            }
        };

        // 绑定事件
        document.addEventListener('mousedown', handler);
        document.addEventListener('contextmenu', handler);
        window.addEventListener('resize', handler);
    }

    /** 用 Map 存储元素对应的原始状态,支持遍历批量操作 */
    const originalStates = new Map();

    /** 替换元素原onclick事件,绑定自定义点击事件显示收藏菜单 */
    const replaceOnClick = (el, favUrl) => {
        if (!Array.isArray(favcat) || favcat.length !== 10) return;

        // 先保存原始状态
        const originalOnClick = el.getAttribute('onclick');
        const originalCursor = el.style.cursor;

        // 自定义点击事件回调
        const clickHandler = e => {
            e.stopPropagation();
            showFavMenu(el, favUrl);
        };

        // 移除原onclick属性,防止冲突
        el.removeAttribute('onclick');

        // 设置鼠标样式为指针
        el.style.cursor = 'pointer';

        // 添加自定义事件监听
        el.addEventListener('click', clickHandler);

        // 保存状态以备恢复
        const oldState = originalStates.get(el) || {};
        originalStates.set(el, {
            ...oldState,
            originalOnClick,
            originalCursor,
            clickHandler,
        });
    };

    /** 恢复元素原onclick事件、鼠标样式、取消自定义点击事件 */
    function restoreElements() {
        // 先把所有保存的元素缓存到数组,避免边遍历边修改 Map 导致的问题
        const elements = Array.from(originalStates.keys());

        for (const el of elements) {
            const state = originalStates.get(el);
            if (!state) continue;

            const { originalOnClick, originalCursor, clickHandler, onMouseEnter, onMouseLeave, iconMarginLeft } = state;

            el.style.cursor = originalCursor || '';

            // 移除鼠标悬停事件监听
            el.removeEventListener('mouseenter', onMouseEnter);
            el.removeEventListener('mouseleave', onMouseLeave);

            if (pageInfo.listDisplayMode) {

            } else if (pageInfo.isGalleryPage) {
                el.removeAttribute('style');
                // 设置 gdf 内部 div#fav div.i 的 margin-left 为 0
                const iconDiv = el.querySelector('div#fav div.i');
                if (iconDiv) {
                    iconDiv.style.marginLeft = iconMarginLeft;
                }
            }

            // 恢复 onclick 属性
            if (originalOnClick) {
                el.setAttribute('onclick', originalOnClick);
            }

            // 解绑自定义点击事件
            el.removeEventListener('click', clickHandler);

            // 从缓存中移除,防止内存泄漏
            originalStates.delete(el);
        }
    }

    /** 给元素绑定鼠标悬停事件 */
    function bindHoverEffect(el) {
        function onMouseEnter() {
            let color = '#000000';
            if (pageInfo.isExhentai || pageInfo.isTor) { color = '#ffffff'; }
            if (pageInfo.listDisplayMode) {
                if (el.style.backgroundColor) return;
                el.style.borderColor = color;
            } else if (pageInfo.isGalleryPage) {
                el.style.backgroundColor = color + '24';
                el.style.boxShadow = 'inset 0 0 0 2px' + color + '12';
            }
        }
        function onMouseLeave() {
            if (pageInfo.listDisplayMode) {
                if (el.style.backgroundColor) return;
                el.style.borderColor = '';
            } else if (pageInfo.isGalleryPage) {
                el.style.backgroundColor = '';
                el.style.boxShadow = '';
            }
        }

        // 保存状态以备恢复
        const oldState = originalStates.get(el) || {};
        originalStates.set(el, {
            ...oldState,
            onMouseEnter,
            onMouseLeave,
        });

        // 绑定事件
        el.addEventListener('mouseenter', onMouseEnter);
        el.addEventListener('mouseleave', onMouseLeave);
    }

    /** 给列表页中的元素替换点击事件,启用收藏菜单 */
    function replaceFavClickS() {
        if (!pageInfo.listDisplayMode) return;

        // 不同显示模式对应的选择器,选出需要绑定收藏功能的元素
        const strategies = {
            m: '.glthumb + div',
            p: '.glthumb + div',
            l: '.glthumb + div > :first-child',
            e: '.gl3e>:nth-child(2)',
            t: '.gl5t>:first-child>:nth-child(2)'
        };

        // 遍历所有匹配元素
        $$(strategies[pageInfo.listDisplayMode]).forEach(el => {
            if (!el.onclick) return; // 无onclick则跳过

            bindHoverEffect(el);

            const favUrl = el.onclick.toString().match(/https.*addfav/)[0]; // 从onclick字符串提取收藏URL
            replaceOnClick(el, favUrl); // 替换点击事件绑定收藏弹窗
        });
    }

    /** 给画廊元素替换点击事件,启用收藏菜单 */
    function replaceFavClickG() {
        // 从URL路径解析画廊ID和类型
        const matchGallery = location.pathname.match(/\/g\/(\d+)\/(\w+)/);
        if (!matchGallery) return;

        // 拼接收藏请求地址
        const favUrl = `${location.origin}/gallerypopups.php?gid=${matchGallery[1]}&t=${matchGallery[2]}&act=addfav`;

        // 获取画廊按钮容器元素
        const gdf = $i('gdf');

        // 调整按钮容器样式,使内容居中且无左边距,设定固定高度和半透明背景
        gdf.style.paddingTop = '0';
        gdf.style.paddingLeft = '0';
        gdf.style.height = '36px';
        gdf.style.display = 'flex';
        gdf.style.justifyContent = 'center';
        gdf.style.alignItems = 'center';
        gdf.style.marginTop = '6px';

        bindHoverEffect(gdf);

        // 设置 gdf 内部 div#fav div.i 的 margin-left 为 0
        const iconDiv = gdf.querySelector('div#fav div.i');
        if (iconDiv) {
            // 保存状态以备恢复
            const oldState = originalStates.get(gdf) || {};
            originalStates.set(gdf, {
                ...oldState,
                iconMarginLeft: iconDiv.style.marginLeft,
            });
            iconDiv.style.marginLeft = '0';
        }

        replaceOnClick(gdf, favUrl); // 替换点击事件绑定收藏弹窗
    }

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    /** 加载下一页的状态 */
    const nextPage = {
        isLoading: false,
        nextPageLink: null,
        loadedCount: 1
    }

    /** 获取下一页链接 */
    function getNextPageLink(doc) {
        if (pageInfo.listDisplayMode) {
            nextPage.nextPageLink = doc.querySelector('#dnext')?.href;
            if (nextPage.nextPageLink) {
                $i('unext').href = nextPage.nextPageLink;
                $i('dnext').href = nextPage.nextPageLink;
            }
        } else if (pageInfo.isGalleryPage) {
            nextPage.nextPageLink = doc.querySelector('.ptb tr:first-child td:last-child a')?.href;
            if (nextPage.nextPageLink) {
                $('.ptt tr:first-child td:last-child a').href = nextPage.nextPageLink;
                $('.ptb tr:first-child td:last-child a').href = nextPage.nextPageLink;
            }
        }
    }

    /** 无限滚动加载下一页 */
    async function loadNextPage() {
        if (nextPage.isLoading || !nextPage.nextPageLink) return;

        nextPage.isLoading = true;
        try {
            console.log('LOLICON 加载下一页:', nextPage.nextPageLink);
            const response = await fetch(nextPage.nextPageLink);
            const html = await response.text();
            const parser = new DOMParser();
            const fetchedDoc = parser.parseFromString(html, 'text/html');
            let nextContent;
            if (pageInfo.listDisplayMode === 't') {
                nextContent = fetchedDoc.querySelectorAll('.gl1t');
            } else if (pageInfo.listDisplayMode) {
                nextContent = fetchedDoc.querySelectorAll('.itg > tbody > tr');
            } else if (pageInfo.isGalleryPage) {
                nextContent = fetchedDoc.querySelectorAll('#gdt > a');
            }

            if (nextContent.length > 0) {
                const fragment = document.createDocumentFragment();
                nextContent.forEach((item, index) => {
                    if (pageInfo.listDisplayMode === 't' || pageInfo.listDisplayMode === 'e' || pageInfo.isGalleryPage || index > 0) {
                        fragment.appendChild(item);
                    }
                });

                if (pageInfo.listDisplayMode === 't') {
                    $c('itg gld')[0].appendChild(fragment);
                } else if (pageInfo.listDisplayMode) {
                    $('.itg > tbody').appendChild(fragment);
                } else if (pageInfo.isGalleryPage) {
                    $i('gdt').appendChild(fragment);
                }

                nextPage.loadedCount++;
                console.log('LOLICON 下一页内容已成功加载。');
                if (pageInfo.listDisplayMode) {
                    collectDataS();
                    if (pageInfo.listDisplayMode === 't') modifyThumbnailSizeS();
                    updateGlinkIndex();
                    if (cfg.quickFavorite) replaceFavClickS();
                    if (cfg.liveURLUpdate && !pageInfo.isPopularPage && !pageInfo.isFavoritesPage) {
                        throttledGetRowInfo();
                    }
                } else if (pageInfo.isGalleryPage) {
                    collectDataG();
                    modifyThumbnailSizeG();
                    throttledAdjustColumnsG();
                }

            } else {
                console.log('LOLICON 未找到下一页的内容,停止加载。');
            }

            getNextPageLink(fetchedDoc);

            if (nextPage.nextPageLink) {
                console.log('LOLICON 下一页链接已更新为:', nextPage.nextPageLink);
            } else {
                console.log('LOLICON 已是最后一页');
            }

        } catch (error) {
            console.error('LOLICON 加载下一页时发生错误:', error);
        } finally {
            nextPage.isLoading = false;
        }

        if (pageInfo.listDisplayMode && document.body.offsetHeight <= window.innerHeight) {
            throttledLoadNextPage();
        } else if (pageInfo.isGalleryPage && $i('gdt').getBoundingClientRect().bottom <= window.innerHeight) {
            throttledLoadNextPage();
        }
    }

    /** 元素位置 */
    let elementPositions = [];

    /** 获取行信息 */
    function getRowInfo() {
        elementPositions = [];
        const scrollY = window.scrollY;
        const startIndex = (pageInfo.listDisplayMode === 't' || pageInfo.listDisplayMode === 'e') ? 0 : 1;
        const step = (pageInfo.listDisplayMode === 't') ? layout.columnsS : 1;

        for (let i = startIndex; i < pageItemsIndex; i += step) {
            const el = pageItemsData[i].el;
            elementPositions.push({
                bottom: el.getBoundingClientRect().bottom + scrollY,
                url: pageItemsData[i].gid + 1,
            });
        }

        updateURLOnScroll();
    }

    /** 最顶部元素的 URL */
    let topMostElementURL;

    /** 更新地址栏 */
    function updateURLOnScroll() {
        let newTopMostElementURL;
        const scrollY = window.scrollY;

        for (let i = 0; i < elementPositions.length; i++) {
            const { bottom, url } = elementPositions[i];
            if (bottom >= scrollY) {
                newTopMostElementURL = url;
                break;
            }
        }

        if (newTopMostElementURL != topMostElementURL) {
            let urlObj = new URL(pageInfo.originalUrl);
            urlObj.searchParams.delete('jump');
            urlObj.searchParams.delete('seek');
            urlObj.searchParams.set('next', newTopMostElementURL);
            window.history.replaceState(null, '', urlObj.toString());
            topMostElementURL = newTopMostElementURL;
        }
    }

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

    /** 防抖函数 */
    function debounce(func, wait) {
        let timeout;

        return function (...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func(...args), wait);
        };
    }

    /** 节流函数 */
    function throttle(func, wait) {
        let lastTime = 0;
        let timeout = null;

        return function (...args) {
            const now = Date.now();
            const remaining = wait - (now - lastTime);

            if (remaining <= 0) {
                lastTime = now;
                func(...args);
            } else if (!timeout) {
                timeout = setTimeout(() => {
                    lastTime = Date.now();
                    timeout = null;
                    func(...args);
                }, remaining);
            }
        };
    }

    /** 更新画廊列表页宽-节流 */
    const throttledAdjustColumnsS = throttle(adjustColumnsS, 60);
    /** 更新画廊页宽-节流 */
    const throttledAdjustColumnsG = throttle(adjustColumnsG, 60);
    /** 更新地址栏-节流 */
    const throttledUpdateURLOnScroll = throttle(updateURLOnScroll, 60);
    /** 获取行信息-节流 */
    const throttledGetRowInfo = throttle(getRowInfo, 240);
    /** 加载下一页-节流 */
    const throttledLoadNextPage = throttle(loadNextPage, 600);

    /** 监控无限滚动 */
    function monitorInfiniteScroll() {
        const observer = new IntersectionObserver(([entry]) => {
            if (entry.isIntersecting && cfg.infiniteScroll) {
                if (cfg.maxPagesS != 0 && nextPage.loadedCount >= cfg.maxPagesS) {
                    console.log('LOLICON 已达到最大页数限制: ', nextPage.loadedCount, ' >= ', cfg.maxPagesS);
                    return
                }
                throttledLoadNextPage();
            }
        });

        const bottomElement = $create('div');
        bottomElement.classList.add('LOLICON-infinite-scroll-trigger');
        document.body.appendChild(bottomElement);

        observer.observe(bottomElement);
    }

    /** 监控更缩略图 */
    function monitorMoreThumbnail() {
        const observer = new IntersectionObserver(([entry]) => {
            if (entry.isIntersecting && cfg.moreThumbnail) {
                if (cfg.maxPagesG != 0 && nextPage.loadedCount >= cfg.maxPagesG) {
                    console.log('LOLICON 已达到最大页数限制: ', nextPage.loadedCount, ' >= ', cfg.maxPagesG);
                    return
                }
                throttledLoadNextPage();
            }
        });

        const trigger = $create('div');
        trigger.id = 'LOLICON-more-thumbnail-trigger';
        $i('gdt').after(trigger);

        observer.observe(trigger);
    }

    console.log('LOLICON 开始');

    // 设置菜单
    GM_registerMenuCommand(translate('settings'), showSettingsPanel);

    // 初始化基础
    initialize();
    calculateDimensions();

    // 收藏页面修改
    if (pageInfo.isFavoritesPage) {
        $c('ido')[0].style.minWidth = '740px';
    }

    // 初始化收藏夹
    if (cfg.quickFavorite) {
        initFavcat();
    }

    if (pageInfo.listDisplayMode) {
        collectDataS();

        if (pageInfo.listDisplayMode === 't') {
            modifyThumbnailSizeS();
        }
        adjustColumnsS();
        updateGlinkIndex();

        getNextPageLink(document);
        monitorInfiniteScroll();

        if (cfg.quickFavorite) {
            replaceFavClickS();
        }
        window.addEventListener('resize', throttledAdjustColumnsS);
        window.addEventListener('scroll', () => {
            if (cfg.liveURLUpdate && !pageInfo.isPopularPage && !pageInfo.isFavoritesPage) {
                throttledUpdateURLOnScroll();
            }
        });
    } else if ($i('searchbox')) {
        adjustColumnsS();
        window.addEventListener('resize', throttledAdjustColumnsS);
    } else if (pageInfo.isGalleryPage) {
        collectDataG();
        modifyThumbnailSizeG();
        adjustColumnsG();

        getNextPageLink(document);
        monitorMoreThumbnail()

        if (cfg.quickFavorite) {
            replaceFavClickG();
        }
        window.addEventListener('resize', throttledAdjustColumnsG);
    }

})();