NiceFont(耐視字體)

NiceFont:是一款優化網頁字體顯示的強大工具,讓瀏覽更清晰、舒適!「真正調整字體,而非頁面縮放,拒絕將就」!可直接修改網頁的字體大小與風格,儲存你的字體設定,輕鬆應用到每個網頁,支援首次、定時或動態調整字體,適配子域名、整站或全局設定,相容B站評論區、釘釘文件、知乎、論壇等幾乎所有網站!

目前為 2025-05-19 提交的版本,檢視 最新版本

// ==UserScript==
// @name         NiceFont (耐视字体)
// @name:zh-CN    NiceFont (耐视字体)
// @name:zh-TW    NiceFont(耐視字體)
// @name:en       NiceFont
// @name:ko       NiceFont (좋은 글꼴)
// @name:ja       NiceFont (いいフォント)
// @name:ru       NiceFont (Хороший шрифт)
// @name:fr       NiceFont (Police agréable)
// @name:de       NiceFont (Schöne Schrift)
// @name:es       NiceFont (Fuente agradable)
// @name:pt       NiceFont (Fonte agradável)
// @version      3.1
// @author       DD1024z
// @description  NiceFont: 是一款优化网页字体显示的强大工具,让浏览更清晰、舒适!“真正调整字体,而非页面缩放,拒绝将就”!可直接修改网页的字体大小与风格,保存你的字体设置,轻松应用到每个网页,支持首次、定时或动态调整字体,适配子域名、整站或全局设置,兼容B站评论区、钉钉文档、知乎、论坛等几乎所有网站!
// @description:zh-CN  NiceFont: 是一款优化网页字体显示的强大工具,让浏览更清晰、舒适!“真正调整字体,而非页面缩放,拒绝将就”!可直接修改网页的字体大小与风格,保存你的字体设置,轻松应用到每个网页,支持首次、定时或动态调整字体,适配子域名、整站或全局设置,兼容B站评论区、钉钉文档、知乎、论坛等几乎所有网站!
// @description:zh-TW  NiceFont:是一款優化網頁字體顯示的強大工具,讓瀏覽更清晰、舒適!「真正調整字體,而非頁面縮放,拒絕將就」!可直接修改網頁的字體大小與風格,儲存你的字體設定,輕鬆應用到每個網頁,支援首次、定時或動態調整字體,適配子域名、整站或全局設定,相容B站評論區、釘釘文件、知乎、論壇等幾乎所有網站!
// @description:en     NiceFont: A powerful tool to optimize web font display for clearer, more comfortable browsing! "Truly adjusts fonts, not page scaling—settle for nothing less!" Directly modifies font size and style, saves your settings, and applies them effortlessly to every page. Supports one-time, scheduled, or dynamic font adjustments, adaptable to subdomains, entire sites, or global settings. Compatible with nearly all websites, including Bilibili comments, DingTalk docs, Zhihu, forums, and more!
// @description:ko     NiceFont: 웹 폰트 표시를 최적화하여 더 선명하고 편안한 브라우징을 제공하는 강력한 도구! "페이지를 스케일링하지 않고 진정으로 폰트를 조정—타협하지 마세요!" 폰트 크기와 스타일을 직접 수정하고, 설정을 저장하여 모든 페이지에 쉽게 적용합니다. 최초, 정기 또는 동적 폰트 조정을 지원하며, 서브도메인, 전체 사이트 또는 전역 설정에 적응 가능. Bilibili 댓글, DingTalk 문서, Zhihu, 포럼 등 거의 모든 웹사이트와 호환!
// @description:ja     NiceFont:ウェブフォント表示を最適化し、よりクリアで快適な閲覧を実現する強力なツール!「ページのスケーリングではなく、フォントを本当の意味で調整—妥協はなし!」フォントサイズとスタイルを直接変更し、設定を保存してすべてのページに簡単に適用。初回、定期、または動的フォント調整をサポートし、サブドメイン、サイト全体、またはグローバル設定に適応可能。ビリビリコメント、DingTalkドキュメント、Zhihu、フォーラムなど、ほぼすべてのウェブサイトに対応!
// @description:ru     NiceFont: Мощный инструмент для оптимизации отображения веб-шрифтов, обеспечивающий более четкое и комфортное чтение! "По-настоящему регулирует шрифты, а не масштабирует страницу — никаких компромиссов!" Позволяет напрямую изменять размер и стиль шрифта, сохранять настройки и легко применять их к каждой странице. Поддерживает разовую, запланированную или динамическую настройку шрифтов, адаптируется к поддоменам, целым сайтам или глобальным настройкам. Совместим с большинством сайтов, включая комментарии на Bilibili, документы DingTalk, Zhihu, форумы и другие!
// @description:fr     NiceFont : Un outil puissant pour optimiser l'affichage des polices web, rendant la navigation plus claire et confortable ! « Ajuste réellement les polices, pas un simple zoom de page — refusez les compromis ! » Modifie directement la taille et le style des polices, enregistre vos paramètres et les applique facilement à chaque page. Prend en charge les ajustements uniques, programmés ou dynamiques des polices, adaptable aux sous-domaines, sites entiers ou paramètres globaux. Compatible avec presque tous les sites, y compris les commentaires de Bilibili, les documents DingTalk, Zhihu, les forums, et plus encore !
// @description:de     NiceFont: Ein leistungsstarkes Tool zur Optimierung der Webschriftanzeige für klareres und angenehmeres Surfen! "Passt Schriften wirklich an, statt die Seite zu skalieren — keine Kompromisse!" Ändert Schriftgröße und -stil direkt, speichert Ihre Einstellungen und wendet sie mühelos auf jede Seite an. Unterstützt einmalige, geplante oder dynamische Schrifteinstellungen, anpassbar an Subdomains, ganze Websites oder globale Einstellungen. Kompatibel mit fast allen Websites, einschließlich Bilibili-Kommentaren, DingTalk-Dokumenten, Zhihu, Foren und mehr!
// @description:es     NiceFont: ¡Una poderosa herramienta para optimizar la visualización de fuentes web, haciendo que la navegación sea más clara y cómoda! "Ajusta realmente las fuentes, no solo escala la página — ¡sin concesiones!" Modifica directamente el tamaño y estilo de la fuente, guarda tus configuraciones y las aplica fácilmente a cada página. Admite ajustes únicos, programados o dinámicos de fuentes, adaptable a subdominios, sitios completos o configuraciones globales. Compatible con casi todos los sitios web, incluidos comentarios de Bilibili, documentos de DingTalk, Zhihu, foros y más!
// @description:pt     NiceFont: Uma ferramenta poderosa para otimizar a exibição de fontes na web, tornando a navegação mais clara e confortável! "Ajusta realmente as fontes, não apenas escala a página — sem concessões!" Modifica diretamente o tamanho e estilo da fonte, salva suas configurações e as aplica facilmente a cada página. Suporta ajustes únicos, agendados ou dinâmicos de fontes, adaptável a subdomínios, sites inteiros ou configurações globais. Compatível com quase todos os sites, incluindo comentários do Bilibili, documentos do DingTalk, Zhihu, fóruns e mais!
// @homepageURL   https://github.com/10D24D/NiceFont/
// @namespace    https://github.com/10D24D/NiceFont/
// @icon         https://raw.githubusercontent.com/10D24D/NiceFont/main/static/logo.png
// @match        *://*/*
// @license      Apache License 2.0
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_info
// @run-at       document-start
// @compatible   edge version≥90 (Compatible Tampermonkey, Violentmonkey)
// @compatible   Chrome version≥90 (Compatible Tampermonkey, Violentmonkey)
// @compatible   Firefox version≥84 (Compatible Greasemonkey, Tampermonkey, Violentmonkey)
// @compatible   Opera version≥78 (Compatible Tampermonkey, Violentmonkey)
// @compatible   Safari version≥15.4 (Compatible Tampermonkey, Userscripts)
// @create       2025-4-18
// @copyright    2025, DD1024z
// ==/UserScript==

(function () {
    'use strict';

    // 调试开关,生产环境中禁用日志
    const enableLogging = true;

    // 关闭跟踪常量
    const CLOSE_TRACKING_WINDOW = 1800 * 1000; // 30 分钟(毫秒)
    const CLOSE_COUNT_THRESHOLD = 2; // 连续关闭两次

    /**
     * 自定义日志函数,仅在调试模式下输出
     * @param {...any} args - 日志参数
     */
    function log(...args) {
        if (enableLogging) {
            console.log('[NiceFont]', ...args);
        }
    }

    // 跳过 iframe 执行
    if (window.top !== window.self) {
        log('跳过 iframe 执行');
        return;
    }

    // --- 工具函数模块 ---
    const Utils = {
        /**
         * 节流函数,限制函数调用频率
         * @param {Function} fn - 要节流的函数
         * @param {number} wait - 节流间隔(毫秒)
         * @returns {Function} 节流后的函数
         */
        throttle(fn, wait) {
            let lastCall = 0;
            return function (...args) {
                const now = Date.now();
                if (now - lastCall >= wait) {
                    lastCall = now;
                    fn(...args);
                }
            };
        },

        /**
         * 将字体大小单位转换为像素
         * @param {HTMLElement} el - 元素
         * @param {string} fontSize - 字体大小(带单位)
         * @returns {number} 像素值
         */
        convertToPx(el, fontSize) {
            if (!fontSize) return 16;
            if (fontSize.includes('rem')) {
                const rootFontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
                return parseFloat(fontSize) * rootFontSize;
            }
            if (fontSize.includes('em')) {
                const parentFontSize = parseFloat(window.getComputedStyle(el.parentElement).fontSize);
                return parseFloat(fontSize) * parentFontSize;
            }
            if (fontSize.includes('%')) {
                const parentFontSize = parseFloat(window.getComputedStyle(el.parentElement).fontSize);
                return (parseFloat(fontSize) / 100) * parentFontSize;
            }
            if (fontSize.includes('pt')) {
                return parseFloat(fontSize) * 1.3333;
            }
            if (fontSize.includes('vw')) {
                return parseFloat(fontSize) * window.innerWidth / 100;
            }
            if (fontSize.includes('vh')) {
                return parseFloat(fontSize) * window.innerHeight / 100;
            }
            return parseFloat(fontSize);
        },

        /**
         * 检查元素是否包含可见文本
         * @param {HTMLElement} el - 元素
         * @returns {boolean} 是否包含可见文本
         */
        hasVisibleText(el) {
            return Array.from(el.childNodes).some(
                node => node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== ''
            );
        },

        /**
         * 获取顶级域名
         * @returns {string} 顶级域名(如 .example.com)
         */
        getTopLevelDomain() {
            const hostname = window.location.hostname;
            const parts = hostname.split('.');
            return parts.length >= 2 ? `.${parts.slice(-2).join('.')}` : hostname;
        }
    };

    // --- 状态管理 ---
    const State = {
        fontIncrement: 1,
        currentFontFamily: 'none',
        currentAdjustment: 0,
        watchDOMChanges: false,
        intervalSeconds: 0,
        firstAdjustment: false,
        firstAdjustmentTime: 3,
        currentLanguage: 'en',
        panelType: 'floating',
        isConfigModified: false,
        targetScope: 1,
        pendingScopeChange: null,
        observer: null,
        timer: null,

        /**
         * 获取状态值
         * @param {string} key - 状态键
         * @returns {any} 状态值
         */
        get(key) {
            return this[key];
        },

        /**
         * 设置状态值
         * @param {string} key - 状态键
         * @param {any} value - 状态值
         */
        set(key, value) {
            this[key] = value;
        }
    };

    // --- 配置范围管理 ---
    const ConfigScopeManager = {
        BASE_STORAGE_KEY: 'NiceFont_config',
        GLOBAL_DEFAULT_KEY: 'NiceFont_global_default_config',
        PANEL_TYPE_KEY: 'NiceFont_panelType',
        scopeMap: { 1: 'subdomain', 2: 'topLevelDomain', 3: 'allWebsites' },

        /**
         * 初始化动态键
         */
        initKeys() {
            this.subdomainKey = `${this.BASE_STORAGE_KEY}_${window.location.hostname}`;
            this.topLevelKey = `${this.BASE_STORAGE_KEY}_${Utils.getTopLevelDomain()}`;
        },

        /**
         * 获取当前配置键
         * @returns {string} 配置键
         */
        getConfigKey() {
            this.initKeys();
            const scope = State.get('targetScope');
            if (scope === 1) return this.subdomainKey;
            if (scope === 2) return this.topLevelKey;
            return this.GLOBAL_DEFAULT_KEY;
        },

        /**
         * 获取当前生效的配置范围
         * @returns {number} 范围(1: 子域名, 2: 顶级域名, 3: 所有网站)
         */
        getEffectiveScope() {
            this.initKeys();
            const subdomainConfig = GM_getValue(this.subdomainKey, {});
            const topLevelConfig = GM_getValue(this.topLevelKey, {});
            const globalConfig = GM_getValue(this.GLOBAL_DEFAULT_KEY, {});
            if (Object.keys(subdomainConfig).length > 0) return 1;
            if (Object.keys(topLevelConfig).length > 0) return 2;
            if (Object.keys(globalConfig).length > 0) return 3;
            return 1; // 默认返回子域名
        },

        /**
         * 检查当前网站是否已有配置
         * @returns {boolean} 是否存在配置
         */
        hasConfig() {
            this.initKeys();
            const configKey = this.getConfigKey();
            const config = GM_getValue(configKey, null);
            const hasConfig = !!config && Object.keys(config).length > 0;
            log(`检查配置: key=${configKey}, hasConfig=${hasConfig}, config=${JSON.stringify(config)}`);
            return hasConfig;
        },

        /**
         * 获取范围显示文本
         * @param {number} scope - 范围
         * @param {Object} t - 翻译对象
         * @returns {string} 显示文本
         */
        getScopeText(scope, t) {
            return scope === 1 ? t.subdomain : scope === 2 ? t.topLevelDomain : t.allWebsites;
        },

        /**
         * 获取当前配置来源文本
         * @param {Object} t - 翻译对象
         * @returns {string} 配置来源文本
         */
        getCurrentConfigText(t) {
            this.initKeys();
            const subdomainConfig = GM_getValue(this.subdomainKey, {});
            const topLevelConfig = GM_getValue(this.topLevelKey, {});
            const globalConfig = GM_getValue(this.GLOBAL_DEFAULT_KEY, {});
            if (Object.keys(subdomainConfig).length > 0) return window.location.hostname;
            if (Object.keys(topLevelConfig).length > 0) return `*.${Utils.getTopLevelDomain().replace(/^\./, '')}`;
            if (Object.keys(globalConfig).length > 0) return t.allWebsites;
            return t.notConfigured;
        },

        /**
         * 获取配置范围显示文本(包含目标范围)
         * @param {Object} t - 翻译对象
         * @returns {string} 显示文本
         */
        getConfigScopeDisplayText(t) {
            const effectiveScope = this.getEffectiveScope();
            const currentScopeText = this.getScopeText(effectiveScope, t);
            const pendingScope = State.get('pendingScopeChange');
            if (pendingScope && pendingScope !== effectiveScope) {
                const targetScopeText = this.getScopeText(pendingScope, t);
                return `${currentScopeText} -> ${targetScopeText}`;
            }
            return currentScopeText;
        },

        /**
         * 删除指定范围的配置
         * @param {number} scope - 范围
         * @returns {boolean} 是否删除成功
         */
        deleteConfig(scope) {
            this.initKeys();
            const t = translations[State.get('currentLanguage')] || translations.en;
            let key, target;
            if (scope === 1) {
                key = this.subdomainKey;
                target = window.location.hostname;
            } else if (scope === 2) {
                key = this.topLevelKey;
                target = `*.${Utils.getTopLevelDomain().replace(/^\./, '')}`;
            } else {
                key = this.GLOBAL_DEFAULT_KEY;
                target = t.allWebsites;
            }
            GM_setValue(key, {});
            log(`删除配置: ${target}`);
            return true;
        }
    };

    // --- 配置管理 ---
    const ConfigManager = {
        /**
         * 加载配置
         */
        loadConfig() {
            ConfigScopeManager.initKeys();
            let config = GM_getValue(ConfigScopeManager.subdomainKey, {});
            let effectiveScope = 1;
            if (Object.keys(config).length === 0) {
                config = GM_getValue(ConfigScopeManager.topLevelKey, {});
                effectiveScope = 2;
                if (Object.keys(config).length === 0) {
                    config = GM_getValue(ConfigScopeManager.GLOBAL_DEFAULT_KEY, {});
                    effectiveScope = Object.keys(config).length > 0 ? 3 : 1; // 空全局配置时默认子域名
                }
            }
            State.set('fontIncrement', config.increment || 1);
            State.set('currentFontFamily', config.fontFamily || 'none');
            State.set('currentAdjustment', config.resize || 0);
            State.set('watchDOMChanges', config.watcher || false);
            State.set('intervalSeconds', config.timer || 0);
            State.set('firstAdjustment', config.first || false);
            State.set('firstAdjustmentTime', config.firstTime || 3);
            State.set('targetScope', effectiveScope);
            log('加载配置:', config, '生效范围:', effectiveScope);
        },

        /**
         * 保存配置
         */
        saveConfig() {
            const t = translations[State.get('currentLanguage')] || translations.en;
            // 使用 pendingScopeChange(若存在),否则使用 targetScope
            let scope = State.get('pendingScopeChange') !== null ? State.get('pendingScopeChange') : State.get('targetScope');
            // 如果配置已修改且无 pendingScopeChange,优先使用 UI 显示的 scope
            if (State.get('isConfigModified') && State.get('pendingScopeChange') === null) {
                scope = ConfigScopeManager.getEffectiveScope();
                if (scope === 3 && Object.keys(GM_getValue(ConfigScopeManager.GLOBAL_DEFAULT_KEY, {})).length === 0) {
                    scope = 1; // 无全局配置时,默认子域名
                }
            }
            const scopeText = ConfigScopeManager.getScopeText(scope, t);
            const target = scope === 1 ? window.location.hostname :
                scope === 2 ? `*.${Utils.getTopLevelDomain().replace(/^\./, '')}` : t.allWebsites;
            const confirmMessage = scope === 3 ?
                t.saveConfigConfirm.replace('{scope}', scopeText).replace(' [{target}]', '') :
                t.saveConfigConfirm.replace('{scope}', scopeText).replace('{target}', target);

            if (confirm(confirmMessage)) {
                const config = {
                    increment: State.get('fontIncrement'),
                    resize: State.get('currentAdjustment'),
                    watcher: State.get('watchDOMChanges'),
                    timer: State.get('intervalSeconds'),
                    fontFamily: State.get('currentFontFamily'),
                    first: State.get('firstAdjustment'),
                    firstTime: State.get('firstAdjustmentTime')
                };
                ConfigScopeManager.initKeys();
                const key = scope === 1 ? ConfigScopeManager.subdomainKey :
                    scope === 2 ? ConfigScopeManager.topLevelKey : ConfigScopeManager.GLOBAL_DEFAULT_KEY;
                GM_setValue(key, config);
                State.set('isConfigModified', false);
                State.set('targetScope', scope);
                State.set('pendingScopeChange', null);
                ConfigManager.loadConfig(); // 刷新配置
                UIManager.updateUI();
                log(`保存配置到: ${target} (scope=${scope})`);
            }
        },

        /**
         * 更改配置范围
         */
        changeConfigScope() {
            const t = translations[State.get('currentLanguage')] || translations.en;
            const effectiveScope = ConfigScopeManager.getEffectiveScope();
            const currentScopeText = ConfigScopeManager.getScopeText(effectiveScope, t);
            const input = prompt(
                t.configScopePrompt
                    .replace('{scope}', currentScopeText)
                    .replace('{hostname}', window.location.hostname)
                    .replace('{tld}', Utils.getTopLevelDomain().replace(/^\./, '')),
                State.get('targetScope')
            );
            const newScope = parseInt(input, 10);
            if (![1, 2, 3].includes(newScope)) {
                if (input !== null) alert(t.invalidInput);
                return;
            }
            if (newScope === effectiveScope) {
                log(`新范围与当前范围相同: ${ConfigScopeManager.scopeMap[newScope]}`);
                return;
            }
            ConfigScopeManager.initKeys();
            const hasConfig = effectiveScope === 1 ? Object.keys(GM_getValue(ConfigScopeManager.subdomainKey, {})).length > 0 :
                effectiveScope === 2 ? Object.keys(GM_getValue(ConfigScopeManager.topLevelKey, {})).length > 0 :
                    Object.keys(GM_getValue(ConfigScopeManager.GLOBAL_DEFAULT_KEY, {})).length > 0;

            if (newScope > effectiveScope && hasConfig) {
                const confirmMessage = effectiveScope === 3 ?
                    `${t.currentConfigScope}: ${ConfigScopeManager.getCurrentConfigText(t)}\n${t.deleteBeforeScopeChangeConfirm.replace('{scope}', ConfigScopeManager.getScopeText(effectiveScope, t)).replace(' [{target}]', '')}` :
                    `${t.currentConfigScope}: ${ConfigScopeManager.getCurrentConfigText(t)}\n${t.deleteBeforeScopeChangeConfirm.replace('{scope}', ConfigScopeManager.getScopeText(effectiveScope, t)).replace('{target}', ConfigScopeManager.getCurrentConfigText(t))}`;
                if (confirm(confirmMessage)) {
                    ConfigScopeManager.deleteConfig(effectiveScope);
                    State.set('pendingScopeChange', newScope);
                    State.set('targetScope', newScope);
                    State.set('isConfigModified', true);
                    UIManager.updateUI();
                    log(`标记范围更改为: ${ConfigScopeManager.scopeMap[newScope]}`);
                }
            } else {
                State.set('pendingScopeChange', newScope);
                State.set('targetScope', newScope);
                State.set('isConfigModified', true);
                UIManager.updateUI();
                log(`标记范围更改为: ${ConfigScopeManager.scopeMap[newScope]}`);
            }
        },

        /**
         * 删除当前配置
         */
        deleteCurrentConfig() {
            const effectiveScope = ConfigScopeManager.getEffectiveScope();
            const t = translations[State.get('currentLanguage')] || translations.en;
            const scopeText = ConfigScopeManager.getScopeText(effectiveScope, t);
            const target = ConfigScopeManager.getCurrentConfigText(t);

            if (target === t.notConfigured) {
                log('无配置可删除');
                return false;
            }

            const confirmMessage = effectiveScope === 3 ?
                `${t.currentConfigScope}: ${target}\n${t.deleteConfigConfirm.replace('{scope}', scopeText).replace(' [{target}]', '')}` :
                `${t.currentConfigScope}: ${target}\n${t.deleteConfigConfirm.replace('{scope}', scopeText).replace('{target}', target)}`;

            if (confirm(confirmMessage)) {
                ConfigScopeManager.deleteConfig(effectiveScope);
                State.set('targetScope', 1); // 强制设为子域名
                State.set('pendingScopeChange', null); // 清空待定范围
                ConfigManager.loadConfig();
                UIManager.updateUI();
                log('配置已删除,targetScope 重置为 1');
                return true;
            }
            return false;
        }
    };

    // --- 字体管理 ---
    const FontManager = {
        supportFonts: [
            'custom', 'auto', 'Arial', 'cursive', 'fangsong', 'fantasy', 'monospace', 'none',
            'sans-serif', 'serif', 'system-ui', 'ui-monospace', 'ui-rounded', 'ui-sans-serif',
            'ui-serif', '-webkit-body', 'inherit', 'initial', 'unset', 'Verdana', 'Helvetica',
            'Tahoma', 'Times New Roman', 'Georgia', 'Courier New', 'Comic Sans MS'
        ],
        styleCache: new WeakMap(),

        /**
         * 获取缓存的计算样式
         * @param {HTMLElement} el - 元素
         * @returns {CSSStyleDeclaration} 计算样式
         */
        getCachedStyle(el) {
            if (!this.styleCache.has(el)) {
                this.styleCache.set(el, window.getComputedStyle(el));
            }
            return this.styleCache.get(el);
        },

        /**
         * 递归遍历 DOM 元素
         * @param {HTMLElement} el - 根元素
         * @param {Function} callback - 回调函数
         */
        traverseDOM(el, callback) {
            if (el.nodeType !== Node.ELEMENT_NODE || el.id === 'NiceFont_panel' || el.hasAttribute('data-nicefont-panel')) {
                return;
            }
            callback(el);
            if (el.tagName === 'IFRAME') {
                try {
                    const iframeDoc = el.contentDocument || el.contentWindow.document;
                    if (iframeDoc) {
                        this.traverseDOM(iframeDoc.body, callback);
                        const font = State.get('currentFontFamily');
                        if (font !== 'none') {
                            iframeDoc.documentElement.style.setProperty('--nicefont-family', font);
                        } else {
                            iframeDoc.documentElement.style.removeProperty('--nicefont-family');
                        }
                    }
                } catch (e) {
                    console.error('[NiceFont] 访问 iframe 失败:', e);
                }
            }
            if (el.shadowRoot) {
                try {
                    el.shadowRoot.querySelectorAll('*').forEach(child => this.traverseDOM(child, callback));
                } catch (e) {
                    console.error('[NiceFont] 处理 Shadow DOM 失败:', e);
                }
            }
            Array.from(el.children).forEach(child => requestAnimationFrame(() => this.traverseDOM(child, callback)));
        },

        /**
         * 应用字体调整
         * @param {HTMLElement} el - 根元素
         * @param {number} increment - 字体大小增量(px)
         */
        applyFontRecursively(el, increment) {
            this.traverseDOM(el, (node) => {
                const style = this.getCachedStyle(node);
                const isVisible = style.display !== 'none' && style.visibility !== 'hidden';
                if (Utils.hasVisibleText(node) && isVisible) {
                    let currentFontSize = node.style.fontSize || style.fontSize;
                    if (!node.hasAttribute('data-default-fontsize')) {
                        node.setAttribute('data-default-fontsize', currentFontSize);
                    }
                    const baseFontSize = parseFloat(Utils.convertToPx(node, node.getAttribute('data-default-fontsize')));
                    if (!isNaN(baseFontSize)) {
                        node.style.fontSize = `${baseFontSize + increment}px`;
                    }
                }
            });
            const font = State.get('currentFontFamily');
            if (font !== 'none') {
                document.documentElement.style.setProperty('--nicefont-family', font);
            } else {
                document.documentElement.style.removeProperty('--nicefont-family');
            }
        },

        /**
         * 重置字体
         * @param {HTMLElement} el - 根元素
         */
        resetFont(el) {
            this.traverseDOM(el, (node) => {
                const defaultSize = node.getAttribute('data-default-fontsize');
                if (defaultSize) {
                    node.style.fontSize = defaultSize;
                    node.removeAttribute('data-default-fontsize');
                } else {
                    node.style.removeProperty('font-size');
                }
                node.style.removeProperty('font-family');
            });
            try {
                document.documentElement.style.removeProperty('--nicefont-family');
            } catch (e) {
                console.error('[NiceFont] 移除 --nicefont-family 失败:', e);
            }
            // 重置关闭跟踪状态
            GM_setValue('NiceFont_closeCount', 0);
            GM_setValue('NiceFont_lastCloseTime', 0);
            GM_setValue('NiceFont_autoOpenDisabled', false);
            log('重置关闭跟踪状态');
        },

        /**
         * 修改字体大小
         * @param {number} increment - 增量(px)
         */
        changeFontSize(increment) {
            State.set('currentAdjustment', State.get('currentAdjustment') + increment);
            this.applyFontRecursively(document.body, State.get('currentAdjustment'));
            State.set('isConfigModified', true);
            UIManager.updateUI();
            log(`字体大小调整: ${increment}px, 当前: ${State.get('currentAdjustment')}px`);
        }
    };

    // --- 界面管理 ---
    const UIManager = {
        menuHandles: [],
        panelCache: null,
        overlayCache: null,
        lastToggleTime: 0, // 用于防抖

        /**
         * 定义命令配置
         * @returns {Array} 命令配置数组
         */
        getCommandsConfig() {
            const t = translations[State.get('currentLanguage')] || translations.en;
            return [
                {
                    id: 'setFontFamily',
                    getText: () => `🔠 ${t.setFontFamily}: ${State.get('currentFontFamily')}`,
                    action: () => {
                        const t = translations[State.get('currentLanguage')] || translations.en;
                        let select = document.getElementById('NiceFont_font-family');
                        if (select) {
                            select.remove();
                            document.removeEventListener('click', this.closeDropdown);
                            return;
                        }
                        select = document.createElement('select');
                        select.id = 'NiceFont_font-family';
                        select.className = 'font-family-select';
                        select.innerHTML = FontManager.supportFonts.map(font =>
                            `<option value="${font}" ${font === State.get('currentFontFamily') ? 'selected' : ''}>${font === 'custom' ? (State.get('currentLanguage') === 'zh' ? '手动输入' : 'Custom Input') : font}</option>`
                        ).join('');
                        const btn = document.getElementById('NiceFont_setFontFamily');
                        if (btn) btn.appendChild(select);
                        select.focus();
                        select.addEventListener('click', e => e.stopPropagation());
                        select.addEventListener('change', (e) => {
                            const selectedFont = e.target.value;
                            if (selectedFont === 'custom') {
                                const input = prompt(`${t.setFontFamilyPrompt}\n\n${t.supportFontFamily}\n${FontManager.supportFonts.slice(0, -1).join(', ')}`, '');
                                if (input && input.trim()) {
                                    const newFont = input.trim();
                                    if (!FontManager.supportFonts.includes(newFont)) {
                                        FontManager.supportFonts.splice(FontManager.supportFonts.length - 1, 0, newFont);
                                        const option = document.createElement('option');
                                        option.value = newFont;
                                        option.textContent = newFont;
                                        select.insertBefore(option, select.lastChild);
                                    }
                                    State.set('currentFontFamily', newFont);
                                    select.value = newFont;
                                } else {
                                    select.value = State.get('currentFontFamily');
                                    select.remove();
                                    document.removeEventListener('click', this.closeDropdown);
                                    log('取消自定义字体输入');
                                    return;
                                }
                            } else {
                                State.set('currentFontFamily', selectedFont);
                            }
                            FontManager.applyFontRecursively(document.body, State.get('currentAdjustment'));
                            State.set('isConfigModified', true);
                            UIManager.updateUI();
                            select.remove();
                            document.removeEventListener('click', this.closeDropdown);
                            log(`字体类型设置为: ${State.get('currentFontFamily')}`);
                        });
                        this.closeDropdown = (event) => {
                            if (!select.contains(event.target) && !btn.contains(event.target)) {
                                select.remove();
                                document.removeEventListener('click', this.closeDropdown);
                                log('下拉菜单关闭');
                            }
                        };
                        document.addEventListener('click', this.closeDropdown);
                    }
                },
                {
                    id: 'status',
                    getText: () => `📏 ${t.fontSizeAdjustment}: ${State.get('currentAdjustment') >= 0 ? '+' : ''}${State.get('currentAdjustment')}px`,
                    action: () => { }
                },
                {
                    id: 'increase',
                    getText: () => `🔼 ${t.increase}`,
                    action: () => FontManager.changeFontSize(State.get('fontIncrement')),
                    autoClose: false
                },
                {
                    id: 'decrease',
                    getText: () => `🔽 ${t.decrease}`,
                    action: () => FontManager.changeFontSize(-State.get('fontIncrement')),
                    autoClose: false
                },
                {
                    id: 'reset',
                    getText: () => `🔄️ ${t.reset}`,
                    action: () => {
                        FontManager.resetFont(document.body);
                        State.set('currentAdjustment', 0);
                        State.set('currentFontFamily', 'none');
                        State.set('watchDOMChanges', false);
                        State.set('intervalSeconds', 0);
                        State.set('firstAdjustment', false);
                        State.set('firstAdjustmentTime', 3);
                        if (State.get('observer')) {
                            State.get('observer').disconnect();
                            State.set('observer', null);
                        }
                        if (State.get('timer')) {
                            clearInterval(State.get('timer'));
                            State.set('timer', null);
                        }
                        State.set('isConfigModified', true);
                        UIManager.updateUI();
                        log('字体设置重置');
                    }
                },
                {
                    id: 'first-adjustment',
                    getText: () => `1️⃣ ${State.get('firstAdjustment') ? t.firstAdjustmentEnabled : t.firstAdjustmentDisabled} ${State.get('firstAdjustment') ? `【${State.get('firstAdjustmentTime')}s】` : ''}`,
                    action: () => {
                        const input = prompt(t.firstAdjustmentConfirm, State.get('firstAdjustmentTime').toString());
                        const secs = parseInt(input, 10);
                        if (!isNaN(secs)) {
                            State.set('firstAdjustment', !State.get('firstAdjustment'));
                            State.set('firstAdjustmentTime', secs);
                            if (secs === 0) State.set('firstAdjustment', false);
                            if (State.get('firstAdjustment')) {
                                setTimeout(() => {
                                    FontManager.applyFontRecursively(document.body, State.get('currentAdjustment'));
                                    log('应用首次字体调整');
                                }, State.get('firstAdjustmentTime') * 1000);
                            }
                            State.set('isConfigModified', true);
                            if (this.panelCache) {
                                this.updatePanelContent();
                            }
                            log(`首次调整设置为: ${secs}s`);
                        }
                    }
                },
                {
                    id: 'timer-adjustment',
                    getText: () => `⏱️ ${State.get('intervalSeconds') > 0 ? t.timerAdjustmentEnabled : t.timerAdjustmentDisabled} ${State.get('intervalSeconds') > 0 ? `【${State.get('intervalSeconds')}s】` : ''}`,
                    action: () => {
                        const input = prompt(t.timerPrompt, State.get('intervalSeconds').toString());
                        const secs = parseInt(input, 10);
                        if (!isNaN(secs)) {
                            State.set('intervalSeconds', secs);
                            if (secs > 0) {
                                State.set('watchDOMChanges', false);
                                if (State.get('observer')) State.get('observer').disconnect();
                                if (State.get('timer')) clearInterval(State.get('timer'));
                                State.set('timer', setInterval(() => {
                                    FontManager.applyFontRecursively(document.body, State.get('currentAdjustment'));
                                }, secs * 1000));
                                log(`定时调整设置为: ${secs}s`);
                            } else {
                                if (State.get('timer')) clearInterval(State.get('timer'));
                                log('定时调整禁用');
                            }
                            State.set('isConfigModified', true);
                            if (this.panelCache) {
                                this.updatePanelContent();
                            }
                            log(`定时调整设置为: ${secs}s`);
                        }
                    }
                },
                {
                    id: 'dynamic-adjustment',
                    getText: () => `🔎 ${State.get('watchDOMChanges') ? t.dynamicAdjustmentEnabled : t.dynamicAdjustmentDisabled}`,
                    action: () => {
                        if (confirm(t.dynamicWatchConfirm)) {
                            State.set('watchDOMChanges', !State.get('watchDOMChanges'));
                            if (State.get('watchDOMChanges')) {
                                State.set('intervalSeconds', 0);
                                if (State.get('timer')) clearInterval(State.get('timer'));
                                const nodeCount = document.body.getElementsByTagName('*').length;
                                const throttleTime = nodeCount > 10000 ? 200 : 100;
                                State.set('observer', new MutationObserver(Utils.throttle(() => {
                                    FontManager.applyFontRecursively(document.body, State.get('currentAdjustment'));
                                }, throttleTime)));
                                State.get('observer').observe(document.body, { childList: true, subtree: true });
                                log('动态调整启用');
                            } else {
                                if (State.get('observer')) State.get('observer').disconnect();
                                log('动态调整禁用');
                            }
                            State.set('isConfigModified', true);
                            if (this.panelCache) {
                                this.updatePanelContent();
                            }
                        }
                    }
                },
                {
                    id: 'switch-language',
                    getText: () => `🌐 ${t.usageLanguage}: ${State.get('currentLanguage')}`,
                    action: () => {
                        let input;
                        do {
                            input = prompt('zh: 汉语 \t en: English \t ko: 한국어 \t ja: 日本語 \t ru: Русский \t fr: Français \t de: Deutsch \t es: Español \t pt: Português', State.get('currentLanguage'));
                            if (input && !Object.keys(translations).includes(input.trim())) {
                                alert('Invalid language code!');
                            }
                        } while (input && !Object.keys(translations).includes(input.trim()));
                        if (input && input.trim()) {
                            State.set('currentLanguage', input.trim());
                            GM_setValue('language', State.get('currentLanguage'));
                            UIManager.updateUI();
                            if (this.panelCache) {
                                this.panelCache.remove();
                                this.overlayCache.remove();
                                this.createFloatingPanel();
                            }
                            log(`语言切换为: ${State.get('currentLanguage')}`);
                        }
                    }
                },
                {
                    id: 'switch-panel',
                    getText: () => `🎨 ${t.switchPanel}: ${State.get('panelType') === 'tampermonkey' ? t.tampermonkeyPanel : t.floatingPanel}`,
                    action: () => {
                        const newPanelType = State.get('panelType') === 'tampermonkey' ? 'floating' : 'tampermonkey';
                        GM_setValue(ConfigScopeManager.PANEL_TYPE_KEY, newPanelType);
                        State.set('panelType', newPanelType);
                        if (this.panelCache) {
                            this.panelCache.remove();
                            this.overlayCache.remove();
                            this.panelCache = null;
                            this.overlayCache = null;
                        }
                        UIManager.updateUI();
                        log(`切换到面板类型: ${newPanelType}`);
                    }
                },
                {
                    id: 'show-panel',
                    getText: () => `📅 ${t.showPanel}`,
                    action: () => this.togglePanel(),
                    tampermonkeyOnly: true
                },
                {
                    id: 'currentConfigScope',
                    getText: () => `📍 ${t.currentConfigScope}: ${ConfigScopeManager.getCurrentConfigText(t)}`,
                    action: ConfigManager.deleteCurrentConfig
                },
                {
                    id: 'config-scope',
                    getText: () => `ℹ️ ${t.configScope}: ${ConfigScopeManager.getConfigScopeDisplayText(t)}`,
                    action: ConfigManager.changeConfigScope
                },
                {
                    id: 'save-config',
                    getText: () => `💾 ${State.get('isConfigModified') ? t.saveConfigPending : t.saveConfig}`,
                    action: ConfigManager.saveConfig
                }
            ];
        },

        /**
         * 创建浮动面板
         */
        createFloatingPanel() {
            if (this.panelCache) {
                log('panelCache 已存在,跳过创建');
                return;
            }
            const t = translations[State.get('currentLanguage')] || translations.en;
            const scriptName = GM_info?.script?.name || 'NiceFont';

            // 初始化面板
            this.panelCache = document.createElement('div');
            this.panelCache.id = 'NiceFont_panel';
            this.panelCache.setAttribute('data-nicefont-panel', 'true');
            this.panelCache.style.position = 'fixed';
            this.panelCache.style.width = '300px';
            this.panelCache.style.background = '#fff';
            this.panelCache.style.border = '1px solid #ccc';
            this.panelCache.style.borderRadius = '5px';
            this.panelCache.style.padding = '10px';
            this.panelCache.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
            this.panelCache.style.zIndex = '10001';
            this.panelCache.style.fontFamily = 'sans-serif';
            this.panelCache.style.fontSize = '15px';
            this.panelCache.style.userSelect = 'none';

            // 初始化遮罩层
            this.overlayCache = document.createElement('div');
            this.overlayCache.id = 'NiceFont_overlay';
            this.overlayCache.style.display = 'none';

            // 加载保存的面板位置
            const savedPosition = GM_getValue('NiceFont_panelPosition', { top: '50px', right: '20px' });
            this.panelCache.style.top = savedPosition.top;
            this.panelCache.style.right = savedPosition.right;
            this.panelCache.style.left = 'auto';

            // 设置面板内容
            this.panelCache.innerHTML = `
                <div class="NiceFont_header" style="position: relative; z-index: 10002; display: flex; align-items: center; justify-content: space-between;">
                    <div style="font-size: 16px; text-align: left; flex-grow: 1; cursor: grab; margin: 5px; font-weight: bold;">${scriptName}</div>
                    <button class="NiceFont_close-btn" id="NiceFont_close-btn" style="border: none; border-radius: 3px; padding: 1px 6px; cursor: pointer; line-height: 16px; font-size: 12px; background: none; color: #000;">✖️</button>
                </div>
                <div class="NiceFont_content"></div>
            `;

            // 填充内容区域
            this.updatePanelContent();

            // 确保 DOM 可用并添加面板
            try {
                if (document.body) {
                    document.body.appendChild(this.overlayCache);
                    document.body.appendChild(this.panelCache);
                    log('浮动面板创建成功');
                } else {
                    console.error('[NiceFont] document.body 不可用');
                    return;
                }
            } catch (e) {
                console.error('[NiceFont] 创建面板失败:', e);
                return;
            }

            // 获取 header 元素
            const header = this.panelCache.querySelector('.NiceFont_header');
            if (!header) {
                console.error('[NiceFont] 未找到 .NiceFont_header,无法绑定拖拽事件');
                return;
            }

            // 添加拖动功能
            let isDragging = false;
            let initialX;
            let initialY;
            let rafId = null;

            header.addEventListener('mousedown', (e) => {
                if (e.target.classList.contains('NiceFont_close-btn')) {
                    log('点击关闭按钮,忽略拖拽');
                    return;
                }
                isDragging = true;
                initialX = e.clientX + parseFloat(this.panelCache.style.right || '0');
                initialY = e.clientY - parseFloat(this.panelCache.style.top || '0');
                header.style.cursor = 'grabbing';
                log('开始拖拽');
                e.preventDefault();
                e.stopPropagation();
            }, { capture: true, passive: false });

            document.addEventListener('mousemove', (e) => {
                if (isDragging) {
                    e.preventDefault();
                    if (rafId) cancelAnimationFrame(rafId);
                    rafId = requestAnimationFrame(() => {
                        let newX = initialX - e.clientX;
                        let newY = e.clientY - initialY;
                        newX = Math.max(0, Math.min(newX, window.innerWidth - this.panelCache.offsetWidth));
                        newY = Math.max(0, Math.min(newY, window.innerHeight - this.panelCache.offsetHeight));
                        this.panelCache.style.right = `${newX}px`;
                        this.panelCache.style.top = `${newY}px`;
                        this.panelCache.style.left = 'auto';
                        log(`拖拽中: right=${newX}px, top=${newY}px`);
                    });
                }
            }, { capture: true, passive: false });

            document.addEventListener('mouseup', (e) => {
                if (isDragging) {
                    isDragging = false;
                    header.style.cursor = 'grab';
                    if (rafId) {
                        cancelAnimationFrame(rafId);
                        rafId = null;
                    }
                    GM_setValue('NiceFont_panelPosition', {
                        top: this.panelCache.style.top,
                        right: this.panelCache.style.right
                    });
                    log('拖拽结束, 面板位置保存:', this.panelCache.style.top, this.panelCache.style.right);
                    e.stopPropagation();
                }
            }, { capture: true, passive: false });

            // 添加长按功能
            let longPressTimer = null;
            const startLongPress = (action, interval = 100) => {
                action();
                longPressTimer = setInterval(action, interval);
            };
            const stopLongPress = () => {
                if (longPressTimer) {
                    clearInterval(longPressTimer);
                    longPressTimer = null;
                }
            };

            this.panelCache.addEventListener('mousedown', (e) => {
                const btn = e.target.closest('.action-btn');
                if (btn) {
                    const commandId = btn.id.replace('NiceFont_', '');
                    if (commandId === 'increase' || commandId === 'decrease') {
                        const command = this.getCommandsConfig().find(c => c.id === commandId);
                        if (command) {
                            startLongPress(command.action);
                        }
                    }
                }
            }, { capture: false });

            this.panelCache.addEventListener('mouseup', stopLongPress, { capture: false });
            this.panelCache.addEventListener('mouseleave', stopLongPress, { capture: false });

            // 绑定点击事件
            this.panelCache.addEventListener('click', (e) => {
                const btn = e.target.closest('.action-btn');
                if (btn) {
                    const command = this.getCommandsConfig().find(c => c.id === btn.id.replace('NiceFont_', ''));
                    if (command && command.id !== 'increase' && command.id !== 'decrease') {
                        log(`执行命令: ${command.id}`);
                        command.action();
                    }
                }
                if (e.target.id === 'NiceFont_close-btn') {
                    this.panelCache.style.display = 'none';
                    this.overlayCache.style.display = 'none';
                    // 检查是否因无配置自动弹出
                    if (!ConfigScopeManager.hasConfig()) {
                        const now = Date.now();
                        const lastCloseTime = GM_getValue('NiceFont_lastCloseTime', 0);
                        let closeCount = GM_getValue('NiceFont_closeCount', 0);

                        if (now - lastCloseTime > CLOSE_TRACKING_WINDOW) {
                            // 重置计数(超出时间窗口)
                            closeCount = 0;
                            log('关闭计数重置(超出时间窗口)');
                        }

                        closeCount += 1;
                        GM_setValue('NiceFont_closeCount', closeCount);
                        GM_setValue('NiceFont_lastCloseTime', now);
                        log(`面板关闭(无配置源): closeCount=${closeCount}, lastCloseTime=${now}`);

                        if (closeCount >= CLOSE_COUNT_THRESHOLD) {
                            GM_setValue('NiceFont_autoOpenDisabled', true);
                            log('禁用无配置源自动弹出(连续关闭达到阈值)');
                        }
                    } else {
                        log('面板关闭(有配置源)');
                    }
                }
                e.stopPropagation();
            }, { capture: false });
        },

        /**
         * 更新面板内容
         */
        updatePanelContent() {
            if (!this.panelCache) return;
            const t = translations[State.get('currentLanguage')] || translations.en;
            const contentContainer = this.panelCache.querySelector('.NiceFont_content');
            if (contentContainer) {
                contentContainer.innerHTML = this.getCommandsConfig()
                    .filter(cmd => !cmd.tampermonkeyOnly)
                    .map(cmd =>
                        `<div class="action-btn" id="NiceFont_${cmd.id}">${cmd.getText()}</div>`
                    ).join('');
                log('面板内容更新成功');
            } else {
                console.error('[NiceFont] 未找到 .NiceFont_content,无法更新内容');
            }
        },

        /**
         * 更新油猴菜单
         */
        updateTampermonkeyMenu() {
            this.menuHandles.forEach(handle => {
                try {
                    GM_unregisterMenuCommand(handle);
                } catch (e) {
                    console.error('[NiceFont] 取消注册菜单失败:', e);
                }
            });
            this.menuHandles = [];
            const commands = State.get('panelType') === 'tampermonkey'
                ? this.getCommandsConfig().filter(cmd => cmd.id !== 'show-panel')
                : this.getCommandsConfig().filter(cmd => ['switch-panel', 'show-panel'].includes(cmd.id));
            commands.forEach(cmd => {
                const handle = GM_registerMenuCommand(cmd.getText(), () => {
                    cmd.action();
                    log(`执行油猴菜单命令: ${cmd.id}`);
                }, { autoClose: cmd.autoClose });
                this.menuHandles.push(handle);
                log(`注册菜单: ${cmd.id}`);
            });
        },

        /**
         * 显示/隐藏面板
         */
        togglePanel() {
            if (State.get('panelType') !== 'floating') return;
            const now = Date.now();
            if (now - this.lastToggleTime < 300) {
                log('togglePanel 防抖,忽略快速重复调用');
                return;
            }
            this.lastToggleTime = now;
            if (!this.panelCache) {
                this.createFloatingPanel();
                if (this.panelCache) {
                    this.panelCache.style.display = 'block';
                    this.overlayCache.style.display = 'block';
                    log('面板显示状态: block (新建)');
                }
            } else {
                const isHidden = this.panelCache.style.display === 'none';
                const display = isHidden ? 'block' : 'none';
                this.panelCache.style.display = display;
                this.overlayCache.style.display = display;
                log(`面板显示状态: ${display}`);
            }
        },

        /**
         * 更新界面
         */
        updateUI() {
            log('调用 updateUI, panelType:', State.get('panelType'));
            if (State.get('panelType') === 'tampermonkey') {
                this.updateTampermonkeyMenu();
                if (this.panelCache) {
                    this.panelCache.remove();
                    this.overlayCache.remove();
                    this.panelCache = null;
                    this.overlayCache = null;
                    log('移除浮动面板(切换到油猴菜单)');
                }
            } else {
                this.updateTampermonkeyMenu();
                const hasConfig = ConfigScopeManager.hasConfig();
                const autoOpenDisabled = GM_getValue('NiceFont_autoOpenDisabled', false);
                if (!this.panelCache && !hasConfig && !autoOpenDisabled) {
                    this.createFloatingPanel();
                    if (this.panelCache) {
                        this.panelCache.style.display = 'block';
                        this.overlayCache.style.display = 'block';
                        log('自动创建并显示浮动面板(无配置且未禁用自动弹出)');
                    }
                } else if (this.panelCache) {
                    this.updatePanelContent();
                    log('更新已有面板内容');
                } else {
                    log('跳过自动创建面板(已有配置、已禁用自动弹出或面板已移除)');
                }
                const t = translations[State.get('currentLanguage')] || translations.en;
                const saveBtn = this.panelCache?.querySelector('#NiceFont_save-config');
                if (saveBtn) {
                    saveBtn.textContent = `💾 ${State.get('isConfigModified') ? t.saveConfigPending : t.saveConfig}`;
                }
            }
        }
    };

    // --- 多语言支持 ---
    // 支持的多语言:汉语(zh)、英语(en)、韩语(ko)、日语(ja)、俄语(ru)、法语(fr)、德语(de)、西班牙语(es)、葡萄牙语(pt)
    const translations = {
        zh: {
            increase: '增大字体',
            decrease: '减小字体',
            reset: '恢复字体',
            fontSizeAdjustment: '字体大小调整',
            setFontFamily: '字体类型调整',
            setFontFamilyPrompt: '请输入字体类型',
            supportFontFamily: '支持的字体类型:',
            invalidFontFamilyAlert: '请输入有效的字体类型!',
            firstAdjustmentConfirm: '请输入首次调整时间(秒,0表示禁用):',
            firstAdjustmentEnabled: '首次调整字体: ✔️',
            firstAdjustmentDisabled: '首次调整字体: ✖️',
            timerPrompt: '请输入定时调整间隔(秒,0表示禁用):',
            timerAdjustmentEnabled: '定时调整字体: ✔️',
            timerAdjustmentDisabled: '定时调整字体: ✖️',
            dynamicWatchConfirm: '是否启用/禁用动态调整?',
            dynamicAdjustmentEnabled: '动态调整字体: ✔️',
            dynamicAdjustmentDisabled: '动态调整字体: ✖️',
            usageLanguage: '切换菜单语言',
            switchPanel: '切换菜单面板',
            tampermonkeyPanel: '油猴菜单',
            floatingPanel: '页面菜单',
            showPanel: '显示面板',
            configScope: '配置作用范围',
            subdomain: '子域名',
            topLevelDomain: '顶级域名',
            allWebsites: '所有网站',
            configScopePrompt: '请输入配置作用范围:\n1: 子域名 ({hostname})\n2: 顶级域名 ({tld})\n3: 所有网站\n当前范围: {scope}',
            invalidInput: '请输入有效的范围(1, 2, 或 3)!',
            currentConfigScope: '当前配置源于',
            notConfigured: '未配置',
            saveConfig: '保存配置',
            saveConfigPending: '保存配置(需确定)',
            saveConfigConfirm: '确定保存配置到:{scope} [{target}]?',
            deleteConfigConfirm: '确定删除当前配置吗?(将删除:{scope} [{target}])',
            deleteBeforeScopeChangeConfirm: '更改为更广的作用范围需要先删除当前配置。\n确定删除当前配置吗?(将删除:{scope} [{target}])'
        },
        en: {
            increase: 'Increase Font',
            decrease: 'Decrease Font',
            reset: 'Reset Font',
            fontSizeAdjustment: 'Font Size Adjustment',
            setFontFamily: 'Set Font Family',
            setFontFamilyPrompt: 'Enter font family',
            supportFontFamily: 'Supported font families:',
            invalidFontFamilyAlert: 'Please enter a valid font family!',
            firstAdjustmentConfirm: 'Enter first adjustment time (seconds, 0 to disable):',
            firstAdjustmentEnabled: 'First Font Adjustment: ✔️',
            firstAdjustmentDisabled: 'First Font Adjustment: ✖️',
            timerPrompt: 'Enter timer adjustment interval (seconds, 0 to disable):',
            timerAdjustmentEnabled: 'Timer Font Adjustment: ✔️',
            timerAdjustmentDisabled: 'Timer Font Adjustment: ✖️',
            dynamicWatchConfirm: 'Enable/Disable dynamic adjustment?',
            dynamicAdjustmentEnabled: 'Dynamic Font Adjustment: ✔️',
            dynamicAdjustmentDisabled: 'Dynamic Font Adjustment: ✖️',
            usageLanguage: 'Switch Menu Language',
            switchPanel: 'Switch Menu Panel',
            tampermonkeyPanel: 'Tampermonkey Menu',
            floatingPanel: 'Page Menu',
            showPanel: 'Show Panel',
            configScope: 'Config Scope',
            subdomain: 'Subdomain',
            topLevelDomain: 'Top-Level Domain',
            allWebsites: 'All Websites',
            configScopePrompt: 'Enter config scope:\n1: Subdomain ({hostname})\n2: Top-Level Domain ({tld})\n3: All Websites\nCurrent scope: {scope}',
            invalidInput: 'Please enter a valid scope (1, 2, or 3)!',
            currentConfigScope: 'Current Config Scope',
            notConfigured: 'Not Configured',
            saveConfig: 'Save Config',
            saveConfigPending: 'Save Config (Pending)',
            saveConfigConfirm: 'Save configuration to: {scope} [{target}]?',
            deleteConfigConfirm: 'Are you sure to delete the current configuration? (Will delete: {scope} [{target}])',
            deleteBeforeScopeChangeConfirm: 'Changing to a broader scope requires deleting the current configuration.\nAre you sure to delete the current configuration? (Will delete: {scope} [{target}])'
        },
        ko: {
            increase: '글꼴 확대',
            decrease: '글꼴 축소',
            reset: '글꼴 초기화',
            fontSizeAdjustment: '글꼴 크기 조정',
            setFontFamily: '글꼴 설정',
            setFontFamilyPrompt: '글꼴을 입력하세요',
            supportFontFamily: '지원되는 글꼴:',
            invalidFontFamilyAlert: '유효한 글꼴을 입력하세요!',
            firstAdjustmentConfirm: '첫 조정 시간 입력 (초, 0은 비활성화):',
            firstAdjustmentEnabled: '첫 글꼴 조정: ✔️',
            firstAdjustmentDisabled: '첫 글꼴 조정: ✖️',
            timerPrompt: '타이머 조정 간격 입력 (초, 0은 비활성화):',
            timerAdjustmentEnabled: '타이머 글꼴 조정: ✔️',
            timerAdjustmentDisabled: '타이머 글꼴 조정: ✖️',
            dynamicWatchConfirm: '동적 조정을 활성화/비활성화 하시겠습니까?',
            dynamicAdjustmentEnabled: '동적 글꼴 조정: ✔️',
            dynamicAdjustmentDisabled: '동적 글꼴 조정: ✖️',
            usageLanguage: '메뉴 언어 전환',
            switchPanel: '메뉴 패널 전환',
            tampermonkeyPanel: '탬퍼몽키 메뉴',
            floatingPanel: '페이지 메뉴',
            showPanel: '패널 표시',
            configScope: '설정 범위',
            subdomain: '서브도메인',
            topLevelDomain: '최상위 도메인',
            allWebsites: '모든 웹사이트',
            configScopePrompt: '설정 범위를 입력하세요:\n1: 서브도메인 ({hostname})\n2: 최상위 도메인 ({tld})\n3: 모든 웹사이트\n현재 범위: {scope}',
            invalidInput: '유효한 범위를 입력하세요 (1, 2, 또는 3)!',
            currentConfigScope: '현재 설정 범위',
            notConfigured: '설정되지 않음',
            saveConfig: '설정 저장',
            saveConfigPending: '설정 저장 (확인 필요)',
            saveConfigConfirm: '설정을 다음에 저장하시겠습니까: {scope} [{target}]?',
            deleteConfigConfirm: '현재 설정을 삭제하시겠습니까? (삭제될 항목: {scope} [{target}])',
            deleteBeforeScopeChangeConfirm: '더 넓은 범위로 변경하려면 현재 설정을 삭제해야 합니다.\n현재 설정을 삭제하시겠습니까? (삭제될 항목: {scope} [{target}])'
        },
        ja: {
            increase: 'フォントを大きくする',
            decrease: 'フォントを小さくする',
            reset: 'フォントをリセット',
            fontSizeAdjustment: 'フォントサイズ調整',
            setFontFamily: 'フォントファミリー設定',
            setFontFamilyPrompt: 'フォントファミリーを入力してください',
            supportFontFamily: 'サポートされているフォントファミリー:',
            invalidFontFamilyAlert: '有効なフォントファミリーを入力してください!',
            firstAdjustmentConfirm: '初回調整時間を入力してください(秒、0で無効):',
            firstAdjustmentEnabled: '初回フォント調整:✔️',
            firstAdjustmentDisabled: '初回フォント調整:✖️',
            timerPrompt: 'タイマー調整間隔を入力してください(秒、0で無効):',
            timerAdjustmentEnabled: 'タイマーフォント調整:✔️',
            timerAdjustmentDisabled: 'タイマーフォント調整:✖️',
            dynamicWatchConfirm: '動的調整を有効/無効にしますか?',
            dynamicAdjustmentEnabled: '動的フォント調整:✔️',
            dynamicAdjustmentDisabled: '動的フォント調整:✖️',
            usageLanguage: 'メニュー言語の切り替え',
            switchPanel: 'メニューパネルの切り替え',
            tampermonkeyPanel: 'Tampermonkeyメニュー',
            floatingPanel: 'ページメニュー',
            showPanel: 'パネルを表示',
            configScope: '設定範囲',
            subdomain: 'サブドメイン',
            topLevelDomain: 'トップレベルドメイン',
            allWebsites: 'すべてのウェブサイト',
            configScopePrompt: '設定範囲を入力してください:\n1: サブドメイン ({hostname})\n2: トップレベルドメイン ({tld})\n3: すべてのウェブサイト\n現在の範囲: {scope}',
            invalidInput: '有効な範囲(1、2、または3)を入力してください!',
            currentConfigScope: '現在の設定範囲',
            notConfigured: '未設定',
            saveConfig: '設定を保存',
            saveConfigPending: '設定を保存(確認が必要)',
            saveConfigConfirm: '設定を保存しますか:{scope} [{target}]?',
            deleteConfigConfirm: '現在の設定を削除しますか?(削除対象:{scope} [{target}])',
            deleteBeforeScopeChangeConfirm: 'より広い範囲に変更するには、現在の設定を削除する必要があります。\n現在の設定を削除しますか?(削除対象:{scope} [{target}])'
        },
        ru: {
            increase: 'Увеличить шрифт',
            decrease: 'Уменьшить шрифт',
            reset: 'Сбросить шрифт',
            fontSizeAdjustment: 'Регулировка размера шрифта',
            setFontFamily: 'Установить семейство шрифтов',
            setFontFamilyPrompt: 'Введите семейство шрифтов',
            supportFontFamily: 'Поддерживаемые семейства шрифтов:',
            invalidFontFamilyAlert: 'Пожалуйста, введите действительное семейство шрифтов!',
            firstAdjustmentConfirm: 'Введите время первой настройки (секунды, 0 для отключения):',
            firstAdjustmentEnabled: 'Первая настройка шрифта: ✔️',
            firstAdjustmentDisabled: 'Первая настройка шрифта: ✖️',
            timerPrompt: 'Введите интервал таймера настройки (секунды, 0 для отключения):',
            timerAdjustmentEnabled: 'Настройка шрифта по таймеру: ✔️',
            timerAdjustmentDisabled: 'Настройка шрифта по таймеру: ✖️',
            dynamicWatchConfirm: 'Включить/отключить динамическую настройку?',
            dynamicAdjustmentEnabled: 'Динамическая настройка шрифта: ✔️',
            dynamicAdjustmentDisabled: 'Динамическая настройка шрифта: ✖️',
            usageLanguage: 'Переключить язык меню',
            switchPanel: 'Переключить панель меню',
            tampermonkeyPanel: 'Меню Tampermonkey',
            floatingPanel: 'Меню страницы',
            showPanel: 'Показать панель',
            configScope: 'Область конфигурации',
            subdomain: 'Субдомен',
            topLevelDomain: 'Домен верхнего уровня',
            allWebsites: 'Все веб-сайты',
            configScopePrompt: 'Введите область конфигурации:\n1: Субдомен ({hostname})\n2: Домен верхнего уровня ({tld})\n3: Все веб-сайты\nТекущая область: {scope}',
            invalidInput: 'Пожалуйста, введите действительную область (1, 2 или 3)!',
            currentConfigScope: 'Текущая область конфигурации',
            notConfigured: 'Не настроено',
            saveConfig: 'Сохранить конфигурацию',
            saveConfigPending: 'Сохранить конфигурацию (ожидает подтверждения)',
            saveConfigConfirm: 'Сохранить конфигурацию в: {scope} [{target}]?',
            deleteConfigConfirm: 'Вы уверены, что хотите удалить текущую конфигурацию? (Будет удалено: {scope} [{target}])',
            deleteBeforeScopeChangeConfirm: 'Для изменения на более широкую область необходимо удалить текущую конфигурацию.\nВы уверены, что хотите удалить текущую конфигурацию? (Будет удалено: {scope} [{target}])'
        },
        fr: {
            increase: 'Augmenter la police',
            decrease: 'Réduire la police',
            reset: 'Réinitialiser la police',
            fontSizeAdjustment: 'Ajustement de la taille de la police',
            setFontFamily: 'Définir la famille de polices',
            setFontFamilyPrompt: 'Entrez la famille de polices',
            supportFontFamily: 'Familles de polices prises en charge :',
            invalidFontFamilyAlert: 'Veuillez entrer une famille de polices valide !',
            firstAdjustmentConfirm: 'Entrez le temps du premier ajustement (secondes, 0 pour désactiver) :',
            firstAdjustmentEnabled: 'Premier ajustement de police : ✔️',
            firstAdjustmentDisabled: 'Premier ajustement de police : ✖️',
            timerPrompt: 'Entrez l’intervalle d’ajustement du minuteur (secondes, 0 pour désactiver) :',
            timerAdjustmentEnabled: 'Ajustement de police par minuteur : ✔️',
            timerAdjustmentDisabled: 'Ajustement de police par minuteur : ✖️',
            dynamicWatchConfirm: 'Activer/désactiver l’ajustement dynamique ?',
            dynamicAdjustmentEnabled: 'Ajustement dynamique de la police : ✔️',
            dynamicAdjustmentDisabled: 'Ajustement dynamique de la police : ✖️',
            usageLanguage: 'Changer la langue du menu',
            switchPanel: 'Changer de panneau de menu',
            tampermonkeyPanel: 'Menu Tampermonkey',
            floatingPanel: 'Menu de la page',
            showPanel: 'Afficher le panneau',
            configScope: 'Portée de la configuration',
            subdomain: 'Sous-domaine',
            topLevelDomain: 'Domaine de premier niveau',
            allWebsites: 'Tous les sites web',
            configScopePrompt: 'Entrez la portée de la configuration :\n1 : Sous-domaine ({hostname})\n2 : Domaine de premier niveau ({tld})\n3 : Tous les sites web\nPortée actuelle : {scope}',
            invalidInput: 'Veuillez entrer une portée valide (1, 2 ou 3) !',
            currentConfigScope: 'Portée de configuration actuelle',
            notConfigured: 'Non configuré',
            saveConfig: 'Enregistrer la configuration',
            saveConfigPending: 'Enregistrer la configuration (en attente)',
            saveConfigConfirm: 'Enregistrer la configuration dans : {scope} [{target}] ?',
            deleteConfigConfirm: 'Êtes-vous sûr de vouloir supprimer la configuration actuelle ? (Supprimera : {scope} [{target}])',
            deleteBeforeScopeChangeConfirm: 'Changer pour une portée plus large nécessite de supprimer la configuration actuelle.\nÊtes-vous sûr de vouloir supprimer la configuration actuelle ? (Supprimera : {scope} [{target}])'
        },
        de: {
            increase: 'Schriftart vergrößern',
            decrease: 'Schriftart verkleinern',
            reset: 'Schriftart zurücksetzen',
            fontSizeAdjustment: 'Schriftgrößenanpassung',
            setFontFamily: 'Schriftfamilie festlegen',
            setFontFamilyPrompt: 'Geben Sie die Schriftfamilie ein',
            supportFontFamily: 'Unterstützte Schriftfamilien:',
            invalidFontFamilyAlert: 'Bitte geben Sie eine gültige Schriftfamilie ein!',
            firstAdjustmentConfirm: 'Geben Sie die Zeit für die erste Anpassung ein (Sekunden, 0 zum Deaktivieren):',
            firstAdjustmentEnabled: 'Erste Schrifteinstellung: ✔️',
            firstAdjustmentDisabled: 'Erste Schrifteinstellung: ✖️',
            timerPrompt: 'Geben Sie das Intervall für die Timer-Anpassung ein (Sekunden, 0 zum Deaktivieren):',
            timerAdjustmentEnabled: 'Timer-Schrifteinstellung: ✔️',
            timerAdjustmentDisabled: 'Timer-Schrifteinstellung: ✖️',
            dynamicWatchConfirm: 'Dynamische Anpassung aktivieren/deaktivieren?',
            dynamicAdjustmentEnabled: 'Dynamische Schrifteinstellung: ✔️',
            dynamicAdjustmentDisabled: 'Dynamische Schrifteinstellung: ✖️',
            usageLanguage: 'Menüsprache wechseln',
            switchPanel: 'Menüpanel wechseln',
            tampermonkeyPanel: 'Tampermonkey-Menü',
            floatingPanel: 'Seitenmenü',
            showPanel: 'Panel anzeigen',
            configScope: 'Konfigurationsbereich',
            subdomain: 'Subdomain',
            topLevelDomain: 'Top-Level-Domain',
            allWebsites: 'Alle Websites',
            configScopePrompt: 'Geben Sie den Konfigurationsbereich ein:\n1: Subdomain ({hostname})\n2: Top-Level-Domain ({tld})\n3: Alle Websites\nAktueller Bereich: {scope}',
            invalidInput: 'Bitte geben Sie einen gültigen Bereich ein (1, 2 oder 3)!',
            currentConfigScope: 'Aktueller Konfigurationsbereich',
            notConfigured: 'Nicht konfiguriert',
            saveConfig: 'Konfiguration speichern',
            saveConfigPending: 'Konfiguration speichern (ausstehend)',
            saveConfigConfirm: 'Konfiguration speichern in: {scope} [{target}]?',
            deleteConfigConfirm: 'Möchten Sie die aktuelle Konfiguration wirklich löschen? (Wird gelöscht: {scope} [{target}])',
            deleteBeforeScopeChangeConfirm: 'Zum Wechseln zu einem breiteren Bereich muss die aktuelle Konfiguration gelöscht werden.\nMöchten Sie die aktuelle Konfiguration wirklich löschen? (Wird gelöscht: {scope} [{target}])'
        },
        es: {
            increase: 'Aumentar fuente',
            decrease: 'Reducir fuente',
            reset: 'Restablecer fuente',
            fontSizeAdjustment: 'Ajuste del tamaño de fuente',
            setFontFamily: 'Establecer familia de fuentes',
            setFontFamilyPrompt: 'Ingrese la familia de fuentes',
            supportFontFamily: 'Familias de fuentes compatibles:',
            invalidFontFamilyAlert: '¡Por favor, ingrese una familia de fuentes válida!',
            firstAdjustmentConfirm: 'Ingrese el tiempo del primer ajuste (segundos, 0 para desactivar):',
            firstAdjustmentEnabled: 'Primer ajuste de fuente: ✔️',
            firstAdjustmentDisabled: 'Primer ajuste de fuente: ✖️',
            timerPrompt: 'Ingrese el intervalo de ajuste del temporizador (segundos, 0 para desactivar):',
            timerAdjustmentEnabled: 'Ajuste de fuente por temporizador: ✔️',
            timerAdjustmentDisabled: 'Ajuste de fuente por temporizador: ✖️',
            dynamicWatchConfirm: '¿Activar/desactivar el ajuste dinámico?',
            dynamicAdjustmentEnabled: 'Ajuste dinámico de fuente: ✔️',
            dynamicAdjustmentDisabled: 'Ajuste dinámico de fuente: ✖️',
            usageLanguage: 'Cambiar idioma del menú',
            switchPanel: 'Cambiar panel del menú',
            tampermonkeyPanel: 'Menú de Tampermonkey',
            floatingPanel: 'Menú de página',
            showPanel: 'Mostrar panel',
            configScope: 'Alcance de la configuración',
            subdomain: 'Subdominio',
            topLevelDomain: 'Dominio de nivel superior',
            allWebsites: 'Todos los sitios web',
            configScopePrompt: 'Ingrese el alcance de la configuración:\n1: Subdominio ({hostname})\n2: Dominio de nivel superior ({tld})\n3: Todos los sitios web\nAlcance actual: {scope}',
            invalidInput: '¡Por favor, ingrese un alcance válido (1, 2 o 3)!',
            currentConfigScope: 'Alcance de configuración actual',
            notConfigured: 'No configurado',
            saveConfig: 'Guardar configuración',
            saveConfigPending: 'Guardar configuración (pendiente)',
            saveConfigConfirm: '¿Guardar configuración en: {scope} [{target}]?',
            deleteConfigConfirm: '¿Está seguro de que desea eliminar la configuración actual? (Se eliminará: {scope} [{target}])',
            deleteBeforeScopeChangeConfirm: 'Cambiar a un alcance más amplio requiere eliminar la configuración actual.\n¿Está seguro de que desea eliminar la configuración actual? (Se eliminará: {scope} [{target}])'
        },
        pt: {
            increase: 'Aumentar fonte',
            decrease: 'Diminuir fonte',
            reset: 'Redefinir fonte',
            fontSizeAdjustment: 'Ajuste do tamanho da fonte',
            setFontFamily: 'Definir família de fontes',
            setFontFamilyPrompt: 'Digite a família de fontes',
            supportFontFamily: 'Famílias de fontes suportadas:',
            invalidFontFamilyAlert: 'Por favor, insira uma família de fontes válida!',
            firstAdjustmentConfirm: 'Digite o tempo do primeiro ajuste (segundos, 0 para desativar):',
            firstAdjustmentEnabled: 'Primeiro ajuste de fonte: ✔️',
            firstAdjustmentDisabled: 'Primeiro ajuste de fonte: ✖️',
            timerPrompt: 'Digite o intervalo de ajuste do temporizador (segundos, 0 para desativar):',
            timerAdjustmentEnabled: 'Ajuste de fonte por temporizador: ✔️',
            timerAdjustmentDisabled: 'Ajuste de fonte por temporizador: ✖️',
            dynamicWatchConfirm: 'Ativar/desativar ajuste dinâmico?',
            dynamicAdjustmentEnabled: 'Ajuste dinâmico de fonte: ✔️',
            dynamicAdjustmentDisabled: 'Ajuste dinâmico de fonte: ✖️',
            usageLanguage: 'Mudar idioma do menu',
            switchPanel: 'Mudar painel do menu',
            tampermonkeyPanel: 'Menu Tampermonkey',
            floatingPanel: 'Menu da página',
            showPanel: 'Mostrar painel',
            configScope: 'Escopo da configuração',
            subdomain: 'Subdomínio',
            topLevelDomain: 'Domínio de nível superior',
            allWebsites: 'Todos os sites',
            configScopePrompt: 'Digite o escopo da configuração:\n1: Subdomínio ({hostname})\n2: Domínio de nível superior ({tld})\n3: Todos os sites\nEscopo atual: {scope}',
            invalidInput: 'Por favor, insira um escopo válido (1, 2 ou 3)!',
            currentConfigScope: 'Escopo de configuração atual',
            notConfigured: 'Não configurado',
            saveConfig: 'Salvar configuração',
            saveConfigPending: 'Salvar configuração (pendente)',
            saveConfigConfirm: 'Salvar configuração em: {scope} [{target}]?',
            deleteConfigConfirm: 'Tem certeza de que deseja excluir a configuração atual? (Será excluído: {scope} [{target}])',
            deleteBeforeScopeChangeConfirm: 'Mudar para um escopo mais amplo exige a exclusão da configuração atual.\nTem certeza de que deseja excluir a configuração atual? (Será excluído: {scope} [{target}])'
        }
    };

    // --- CSS 样式 ---
    GM_addStyle(`
        :root {
            --nicefont-family: none;
        }
        *:not(#NiceFont_panel):not([data-nicefont-panel]):not(#NiceFont_panel *) {
            font-family: var(--nicefont-family, inherit) !important;
        }
        #NiceFont_panel {
            position: fixed;
            width: 300px;
            background: #fff;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            z-index: 10001;
            font-family: sans-serif !important;
            font-size: 15px;
            user-select: none;
        }
        #NiceFont_panel .NiceFont_header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 10002;
        }
        #NiceFont_panel .NiceFont_header > div {
            font-size: 16px !important;
            font-family: sans-serif !important;
            text-align: left;
            flex-grow: 1;
            cursor: grab;
            margin: 5px;
            font-weight: bold;
        }
        #NiceFont_panel .NiceFont_close-btn {
            border: none;
            border-radius: 3px;
            padding: 1px 6px;
            cursor: pointer;
            line-height: 16px;
            font-size: 12px;
            background: none;
            color: #000;
        }
        #NiceFont_panel .NiceFont_close-btn:hover {
            text-decoration: underline;
        }
        #NiceFont_panel .action-btn {
            display: block;
            padding: 2px;
            border-radius: 3px;
            cursor: pointer;
            text-align: left;
        }
        #NiceFont_panel .action-btn:hover {
            text-decoration: underline;
        }
        #NiceFont_panel #NiceFont_set-font-size-btn {
            padding: 2px;
            text-decoration: none !important;
        }
        #NiceFont_panel .font-family-select {
            display: inline-block;
            width: auto;
            padding: 2px;
            margin-left: 5px;
            border: 1px solid #ddd;
            border-radius: 3px;
            font-size: 14px;
            vertical-align: middle;
        }
    `);

    // --- 初始化 ---
    /**
     * 初始化脚本
     */
    function init() {
        // 初始化语言
        let lang = GM_getValue('language', navigator.language);
        if (!translations[lang]) {
            lang = lang.startsWith('zh') ? 'zh' : 'en';
            GM_setValue('language', lang);
        }
        State.set('currentLanguage', lang);
        log(`语言设置为: ${lang}`);

        // 初始化面板类型
        let panelType = GM_getValue(ConfigScopeManager.PANEL_TYPE_KEY, 'floating');
        State.set('panelType', panelType);
        log(`面板类型设置为: ${panelType}`);

        // 加载配置
        ConfigManager.loadConfig();

        // 初始化界面
        UIManager.updateUI();

        // 初始化字体调整
        window.addEventListener('load', () => {
            if (State.get('currentAdjustment') !== 0 || State.get('currentFontFamily') !== 'none') {
                if (State.get('firstAdjustment') && State.get('firstAdjustmentTime') > 0) {
                    setTimeout(() => {
                        FontManager.applyFontRecursively(document.body, State.get('currentAdjustment'));
                        log('应用首次字体调整');
                    }, State.get('firstAdjustmentTime') * 1000);
                }
                if (State.get('watchDOMChanges')) {
                    if (State.get('timer')) clearInterval(State.get('timer'));
                    const nodeCount = document.body.getElementsByTagName('*').length;
                    const throttleTime = nodeCount > 10000 ? 200 : 100;
                    State.set('observer', new MutationObserver(Utils.throttle(() => {
                        FontManager.applyFontRecursively(document.body, State.get('currentAdjustment'));
                    }, throttleTime)));
                    State.get('observer').observe(document.body, { childList: true, subtree: true });
                    log('动态调整启用');
                } else if (State.get('intervalSeconds') > 0) {
                    if (State.get('observer')) State.get('observer').disconnect();
                    State.set('timer', setInterval(() => {
                        FontManager.applyFontRecursively(document.body, State.get('currentAdjustment'));
                    }, State.get('intervalSeconds') * 1000));
                    log(`定时调整启用: ${State.get('intervalSeconds')}s`);
                }
            }
        });

        log('脚本初始化完成');
    }

    init();
})();