多功能 Markdown 編輯器

在任意網頁上啟動多款 Markdown 編輯器(EasyMDE、Toast UI、Cherry Markdown、Vditor)。功能:快速存檔插槽、拖曳導入、檔案系統備份、尋找與取代、完整備份管理、工具列自訂。

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Multi Markdown Editor
// @name:zh-TW   多功能 Markdown 編輯器
// @name:zh-CN   多功能 Markdown 编辑器
// @namespace    https://github.com/multi-markdown-editor
// @version      1.0.0
// @description  Launch multiple Markdown editors (EasyMDE, Toast UI, Cherry Markdown, Vditor) on any webpage. Features: quick slots, drag-drop import, file system backup, find & replace, comprehensive backup management, and customizable toolbar.
// @description:zh-TW  在任意網頁上啟動多款 Markdown 編輯器(EasyMDE、Toast UI、Cherry Markdown、Vditor)。功能:快速存檔插槽、拖曳導入、檔案系統備份、尋找與取代、完整備份管理、工具列自訂。
// @description:zh-CN  在任意网页上启动多款 Markdown 编辑器(EasyMDE、Toast UI、Cherry Markdown、Vditor)。功能:快速存档插槽、拖拽导入、文件系统备份、查找与替换、完整备份管理、工具栏自定义。
// @author       Marx Einstein
// @match        *://*/*
// @match        file:///*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @connect      cdn.jsdelivr.net
// @connect      fastly.jsdelivr.net
// @connect      unpkg.com
// @connect      cdnjs.cloudflare.com
// @connect      uicdn.toast.com
// @connect      *
// @run-at       document-idle
// @noframes
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // ========================================
    // 防止重複載入
    // ========================================
    if (window.__MULTI_MD_EDITOR_LOADED__) return;
    window.__MULTI_MD_EDITOR_LOADED__ = true;

    // ========================================
    // 全域常量
    // ========================================

    /** @type {Window} 頁面 window 參考(用於訪問編輯器全局對象) */
    const PAGE_WIN = (typeof unsafeWindow !== 'undefined' && unsafeWindow) ? unsafeWindow : window;

    /** @type {string} 腳本版本 */
    const SCRIPT_VERSION = '1.0.0';

    /** @type {boolean} 除錯模式 */
    const DEBUG = true;

    /**
     * 除錯日誌(僅在 DEBUG 模式下輸出)
     * @param {...any} args - 日誌參數
     */
    const log = (...args) => {
        if (DEBUG) console.log('[MME]', ...args);
    };

    /**
     * 錯誤日誌(始終輸出,用於重要錯誤)
     * @param {...any} args - 錯誤參數
     */
    const logError = (...args) => {
        console.error('[MME]', ...args);
    };

    /**
     * 警告日誌(始終輸出,用於潛在問題)
     * @param {...any} args - 警告參數
     */
    const logWarn = (...args) => {
        console.warn('[MME]', ...args);
    };

    // ========================================
    // 全域配置
    // ========================================

    /** @type {Object} 全域配置物件 */
    const CONFIG = {
        /**
         * 編輯器配置
         * 每個編輯器包含:名稱、版本、圖標、描述、CDN 列表、檔案路徑、全局檢查函數等
         */
        editors: {
            easymde: {
                name: 'EasyMDE',
                version: '2.20.0',
                icon: '✏️',
                description: '簡潔輕量的純文本編輯器,穩定可靠',
                order: 1,
                cdn: [
                    'https://cdn.jsdelivr.net/npm/[email protected]',
                    'https://fastly.jsdelivr.net/npm/[email protected]',
                    'https://unpkg.com/[email protected]'
                ],
                files: {
                    js: '/dist/easymde.min.js',
                    css: '/dist/easymde.min.css'
                },
                globalCheck: () => typeof PAGE_WIN.EasyMDE !== 'undefined',
                extraDeps: {
                    marked: {
                        js: 'https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js',
                        global: 'marked',
                        optional: true
                    }
                }
            },

            toastui: {
                name: 'Toast UI',
                version: '3.2.2',
                icon: '🍞',
                description: 'NHN 開源編輯器,功能豐富(注意:此項目已停止維護)',
                order: 2,
                cdn: [
                    'https://uicdn.toast.com/editor/3.2.2',
                    'https://cdn.jsdelivr.net/npm/@toast-ui/[email protected]/dist',
                    'https://unpkg.com/@toast-ui/[email protected]/dist'
                ],
                files: {
                    js: '/toastui-editor-all.min.js',
                    css: '/toastui-editor.min.css'
                },
                extraCss: ['/theme/toastui-editor-dark.min.css'],
                globalCheck: () => !!(PAGE_WIN.toastui && PAGE_WIN.toastui.Editor)
            },

            cherry: {
                name: 'Cherry Markdown',
                version: '0.10.3',
                icon: '🍒',
                description: '騰訊開源編輯器,雙欄預覽,功能豐富',
                order: 3,
                cdn: [
                    'https://cdn.jsdelivr.net/npm/[email protected]',
                    'https://fastly.jsdelivr.net/npm/[email protected]',
                    'https://unpkg.com/[email protected]'
                ],
                files: {
                    js: '/dist/cherry-markdown.min.js',
                    css: '/dist/cherry-markdown.min.css'
                },
                globalCheck: () => typeof PAGE_WIN.Cherry !== 'undefined',
                extraDeps: {
                    katex: {
                        css: [
                            'https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css',
                            'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.25/katex.min.css'
                        ],
                        js: [
                            'https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js',
                            'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.25/katex.min.js'
                        ],
                        global: 'katex',
                        optional: false,
                        ready: () => !!(PAGE_WIN.katex && typeof PAGE_WIN.katex.renderToString === 'function')
                    }
                }
            },

            vditor: {
                name: 'Vditor',
                version: '3.11.2',
                icon: '📝',
                description: '功能豐富,支援圖表/公式/流程圖,多種編輯模式',
                order: 4,
                cdn: [
                    'https://cdn.jsdelivr.net/npm/[email protected]',
                    'https://fastly.jsdelivr.net/npm/[email protected]',
                    'https://unpkg.com/[email protected]'
                ],
                files: {
                    js: '/dist/index.min.js',
                    css: '/dist/index.css'
                },
                globalCheck: () => typeof PAGE_WIN.Vditor !== 'undefined',
                cacheId: 'mme_vditor_cache'
            }
        },

        /**
         * 儲存鍵名
         * 統一管理所有 localStorage/GM 儲存的鍵名
         */
        storageKeys: {
            // 基本設定
            theme: 'mme_theme',
            content: 'mme_draft',
            editor: 'mme_editor',
            editorMode: 'mme_editor_mode',
            buttonPos: 'mme_btn_pos',
            modalSize: 'mme_modal_size',
            modalPos: 'mme_modal_pos',
            welcomed: 'mme_welcomed',
            lastSaveTime: 'mme_last_save_time',

            // Vditor 專用
            vditorSnapshot: 'mme_vditor_snapshot',
            vditorSnapshotMeta: 'mme_vditor_snapshot_meta',
            vditorSafeReinitFlag: 'mme_vditor_safe_reinit',

            // 工具列與偏好設定
            toolbarCfg: 'mme_toolbar_cfg',
            preferences: 'mme_preferences',
            focusMode: 'mme_focus_mode',

            // 備份管理
            backupIndex: 'mme_backup_index',
            backupPrefix: 'mme_backup_',
            backupSettings: 'mme_backup_settings',
            backupPage: 'mme_backup_page',
            backupSizeWarningEnabled: 'mme_backup_size_warning', // 是否顯示備份大小警告(預設 true)
            backupSizeWarningThreshold: 'mme_backup_size_threshold', // 警告閾值(位元組,預設 1MB)

            // 診斷
            diagEnabled: 'mme_diag_enabled',

            // 語言
            locale: 'mme_locale',

            // ========================================
            // 第一階段預留:快速插槽系統 (Phase 2)
            // ========================================
            slotPrefix: 'mme_slot_',              // 插槽內容前綴(後接 1-9)
            slotMetaPrefix: 'mme_slot_meta_',     // 插槽 meta 前綴
            slotSettings: 'mme_slot_settings',    // 插槽設定(啟用數量、顯示位置等)

            // ========================================
            // 第一階段預留:拖曳導入功能 (Phase 3)
            // ========================================
            dragDropHintShown: 'mme_dragdrop_hint_shown',  // 拖曳提示是否已顯示

            // ========================================
            // 第一階段預留:檔案系統管理 (Phase 4)
            // ========================================
            fsDirectoryName: 'mme_fs_dir_name',    // 已選擇的資料夾名稱
            fsEnabled: 'mme_fs_enabled',           // 是否啟用檔案系統備份
            fsAutoBackup: 'mme_fs_auto_backup'     // 是否自動備份到資料夾
        },

        /** 預設編輯器 */
        defaultEditor: 'easymde',

        /** 預設主題 */
        defaultTheme: 'light',

        /**
         * 時間設定 (毫秒)
         */
        autoSaveInterval: 30000,      // 自動保存間隔:30 秒
        toastDuration: 3000,          // Toast 顯示時間:3 秒
        loadTimeout: 60000,           // 編輯器載入超時:60 秒
        cdnTestTimeout: 8000,         // CDN 測試超時:8 秒

        /**
         * 備份設定
         * 採用分層保留策略,平衡儲存空間與備份密度
         */
        backup: {
            maxBackups: 50,           // 最大備份數量
            autoInterval: 120000,     // 自動備份間隔:2 分鐘
            minChangeChars: 30,       // 觸發備份的最小變更字數
            pageSize: 20,
            /**
             * 保留策略分層:
             * - 1 小時內:每 2 分鐘保留一筆
             * - 24 小時內:每 10 分鐘保留一筆
             * - 7 天內:每天保留一筆
             * - 超過 7 天:自動刪除(除非已釘選)
             */
            retentionTiers: [
                { age: 3600000, interval: 120000 },      // 1 小時內
                { age: 86400000, interval: 600000 },     // 24 小時內
                { age: 604800000, interval: 86400000 }   // 7 天內
            ]
        },

        /** 草稿大小限制 (5 MB) */
        maxDraftBytes: 5 * 1024 * 1024,

        /**
         * 時間相關設定(毫秒)
         * 統一管理各種延遲和間隔,便於調整和理解
         */
        timing: {
            // Vditor 相關
            vditorSnapshotInterval: 3000,       // SV 快照自動保存間隔
            vditorContentCheckInterval: 1000,   // 內容完整性檢查間隔
            vditorRestoreCooldown: 2000,        // 還原操作冷卻時間
            vditorSafeInitDelay: 200,           // 安全初始化後的延遲
            vditorModeChangeCheckDelay: 500,    // 模式切換後檢查延遲

            // Modal 相關
            modalTransitionWait: 80,            // Modal 開啟後的過渡等待
            editorRefreshDelay: 60,             // 編輯器刷新延遲
            editorSwitchDelay: 120,             // 編輯器切換後的穩定延遲

            // UI 相關
            tooltipHideDelay: 100,              // Tooltip 隱藏延遲
            wordCountUpdateInterval: 3000,      // 字數統計更新間隔
            dragDropHintDelay: 5000,            // 拖曳導入提示延遲顯示
            dragDropHintDuration: 10000,        // 拖曳導入提示顯示時長
            focusModeHintDuration: 4500,        // 專注模式提示顯示時長

            // FAB 相關
            fabLongPressDelay: 520,             // FAB 長按觸發延遲
            fabDragEndDelay: 120                // FAB 拖曳結束後的 click 防護延遲
        },

        /**
         * UI 尺寸設定(像素)
         */
        dimensions: {
            focusTriggerZoneHeight: 60,         // 專注模式底部觸發區高度
            modalMinWidth: 380,                 // Modal 最小寬度
            modalMinHeight: 350,                // Modal 最小高度
            panelMaxWidth: 320,                 // 彈出面板最大寬度
            tooltipPadding: 5                   // Tooltip 與邊界的間距
        },

        /** UI 設定 */
        zIndex: 2147483640,           // 極高的 z-index 確保在最上層
        prefix: 'mme-'                // CSS class 前綴,避免與頁面樣式衝突
    };

    // ========================================
    // Vditor 工具列配置
    // ========================================

    /**
     * Vditor 工具列配置
     * 定義 Vditor 編輯器的工具列按鈕佈局
     */
    const VDITOR_TOOLBAR = [
        'emoji', 'headings', 'bold', 'italic', 'strike', 'link', '|',
        'list', 'ordered-list', 'check', 'outdent', 'indent', '|',
        'quote', 'line', 'code', 'inline-code', 'insert-before', 'insert-after', '|',
        'upload', 'table', '|',
        'undo', 'redo', '|',
        'fullscreen', 'edit-mode',
        {
            name: 'more',
            toolbar: [
                'both', 'code-theme', 'content-theme',
                'export', 'outline', 'preview', 'devtools', 'info', 'help'
            ]
        }
    ];

    // ========================================
    // 快捷鍵定義
    // ========================================

    /**
     * 快捷鍵一覽
     * 用於快捷鍵說明面板
     */
    const KEYBOARD_SHORTCUTS = [
        {
            category: '基本操作',
            items: [
                { key: 'Alt + M', desc: '開啟/關閉編輯器' },
                { key: 'Ctrl + S', desc: '保存草稿' },
                { key: 'Escape', desc: '關閉面板 → 退出專注模式 → 關閉編輯器' }
            ]
        },
        {
            category: '檔案操作',
            items: [
                { key: 'Ctrl + O', desc: '開啟檔案' },
                { key: 'Ctrl + Shift + C', desc: '複製 Markdown 到剪貼簿' }
            ]
        },
        {
            category: '編輯',
            items: [
                { key: 'Ctrl + F', desc: '尋找' },
                { key: 'Ctrl + H', desc: '尋找與取代' },
                { key: 'Enter', desc: '尋找下一個(在尋找框中)' },
                { key: 'Shift + Enter', desc: '尋找上一個(在尋找框中)' }
            ]
        },
        {
            category: '視窗控制',
            items: [
                { key: 'F9', desc: '切換全螢幕模式' },
                { key: '雙擊標題列', desc: '切換全螢幕模式' }
            ]
        },
        {
            category: '快速存檔插槽',
            items: [
                { key: 'Ctrl + 1~9', desc: '載入對應插槽' },
                { key: 'Ctrl + Shift + 1~9', desc: '儲存到對應插槽' }
            ]
        },
        {
            category: '其他',
            items: [
                { key: '拖曳檔案到按鈕', desc: '導入 Markdown 檔案' },
                { key: '拖曳文字到按鈕', desc: '插入選取的文字' }
            ]
        }
    ];

    /**
     * 取得排序後的編輯器列表
     * @returns {Array<[string, Object]>} 排序後的編輯器 entries
     */
    function getSortedEditors() {
        return Object.entries(CONFIG.editors)
            .sort((a, b) => (a[1].order || 99) - (b[1].order || 99));
    }

    // ========================================
    // 工具函數
    // ========================================

    /**
     * 工具函數集合
     * 提供各種通用的輔助函數
     */
    const Utils = {
        /**
         * 防抖函數
         * 在延遲時間內的多次調用只會執行最後一次
         * @param {Function} fn - 要防抖的函數
         * @param {number} delay - 延遲時間 (毫秒)
         * @returns {Function} 防抖後的函數
         */
        debounce(fn, delay) {
            let timer = null;
            return function(...args) {
                clearTimeout(timer);
                timer = setTimeout(() => fn.apply(this, args), delay);
            };
        },

        /**
         * 節流函數
         * 在間隔時間內最多執行一次
         * @param {Function} fn - 要節流的函數
         * @param {number} delay - 間隔時間 (毫秒)
         * @returns {Function} 節流後的函數
         */
        throttle(fn, delay) {
            let last = 0;
            return function(...args) {
                const now = Date.now();
                if (now - last >= delay) {
                    last = now;
                    fn.apply(this, args);
                }
            };
        },

        /**
         * 簡易 hash (FNV-1a 32-bit)
         * 用於快速比對內容是否變更
         * @param {string} str - 字串
         * @returns {string} 8 位 16 進制 hash 值
         */
        hash32(str) {
            let hash = 2166136261;
            for (let i = 0; i < str.length; i++) {
                hash ^= str.charCodeAt(i);
                hash = (hash * 16777619) >>> 0;
            }
            return hash.toString(16).padStart(8, '0');
        },

        /**
         * 儲存工具
         * 優先使用 GM_* API,fallback 到 localStorage
         */
        storage: {
            /**
             * 讀取儲存值
             * @param {string} key - 鍵名
             * @param {*} defaultVal - 預設值
             * @returns {*} 儲存的值或預設值
             */
            get(key, defaultVal = null) {
                try {
                    if (typeof GM_getValue === 'function') {
                        const v = GM_getValue(key, null);
                        return v !== null ? v : defaultVal;
                    }
                } catch (e) {
                    // GM_getValue 不可用,嘗試 localStorage
                }
                try {
                    const v = localStorage.getItem(key);
                    if (v === null) return defaultVal;
                    try {
                        return JSON.parse(v);
                    } catch (e) {
                        return v;
                    }
                } catch (e) {
                    // localStorage 也不可用
                }
                return defaultVal;
            },

            /**
             * 寫入儲存值
             * @param {string} key - 鍵名
             * @param {*} value - 值
             * @returns {boolean} 是否成功
             */
            set(key, value) {
                try {
                    if (typeof GM_setValue === 'function') {
                        GM_setValue(key, value);
                        return true;
                    }
                } catch (e) {
                    // GM_setValue 不可用
                }
                try {
                    const str = typeof value === 'string' ? value : JSON.stringify(value);
                    localStorage.setItem(key, str);
                    return true;
                } catch (e) {
                    logWarn('Storage set failed:', e);
                    return false;
                }
            },

            /**
             * 刪除儲存值
             * @param {string} key - 鍵名
             */
            remove(key) {
                try {
                    if (typeof GM_deleteValue === 'function') {
                        GM_deleteValue(key);
                    }
                } catch (e) {
                    // GM_deleteValue 不可用
                }
                try {
                    localStorage.removeItem(key);
                } catch (e) {
                    // localStorage 不可用
                }
            },

            /**
             * 列出所有鍵名
             * @returns {Array<string>} 鍵名列表
             */
            listKeys() {
                try {
                    if (typeof GM_listValues === 'function') {
                        return GM_listValues();
                    }
                } catch (e) {
                    // GM_listValues 不可用
                }
                try {
                    const keys = [];
                    for (let i = 0; i < localStorage.length; i++) {
                        keys.push(localStorage.key(i));
                    }
                    return keys;
                } catch (e) {
                    // localStorage 不可用
                }
                return [];
            },

            /**
             * 估算已使用的儲存空間
             * @returns {Object} { used: number, available: number, usedMB: string }
             */
            estimateUsage() {
                try {
                    let totalSize = 0;
                    const keys = this.listKeys();

                    for (const key of keys) {
                        if (key.startsWith('mme_')) {
                            const value = this.get(key, '');
                            if (typeof value === 'string') {
                                totalSize += value.length * 2; // UTF-16 估算
                            } else {
                                totalSize += JSON.stringify(value).length * 2;
                            }
                        }
                    }

                    // localStorage 通常限制 5-10MB
                    const estimatedLimit = 5 * 1024 * 1024;

                    return {
                        used: totalSize,
                        available: Math.max(0, estimatedLimit - totalSize),
                        usedMB: (totalSize / 1024 / 1024).toFixed(2),
                        percentage: Math.min(100, (totalSize / estimatedLimit * 100)).toFixed(1)
                    };
                } catch (e) {
                    return { used: 0, available: 0, usedMB: '0', percentage: '0' };
                }
            },
        },

        /**
         * 複製到剪貼簿
         * 嘗試多種方法確保兼容性
         * @param {string} text - 要複製的文字
         * @returns {Promise<boolean>} 是否成功
         */
        async copyToClipboard(text) {
            // 方法 1:GM_setClipboard
            try {
                if (typeof GM_setClipboard === 'function') {
                    GM_setClipboard(text, 'text');
                    return true;
                }
            } catch (e) {
                // 繼續嘗試其他方法
            }

            // 方法 2:Clipboard API
            try {
                await navigator.clipboard.writeText(text);
                return true;
            } catch (e) {
                // 繼續嘗試其他方法
            }

            // 方法 3:execCommand (deprecated but widely supported)
            try {
                const ta = document.createElement('textarea');
                ta.value = text;
                ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0;pointer-events:none;';
                document.body.appendChild(ta);
                ta.focus();
                ta.select();
                const ok = document.execCommand('copy');
                ta.remove();
                return ok;
            } catch (e) {
                return false;
            }
        },

        /**
         * 取得頁面選取文字
         * @returns {string} 選取的文字
         */
        getSelectedText() {
            try {
                return window.getSelection()?.toString() || '';
            } catch (e) {
                return '';
            }
        },

        /**
         * 下載檔案
         * @param {string} content - 檔案內容
         * @param {string} filename - 檔案名稱
         * @param {string} mimeType - MIME 類型
         * @returns {boolean} 是否成功
         */
        downloadFile(content, filename, mimeType = 'text/plain;charset=utf-8') {
            try {
                const blob = new Blob([content], { type: mimeType });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = filename;
                a.style.display = 'none';
                document.body.appendChild(a);
                a.click();
                setTimeout(() => {
                    a.remove();
                    URL.revokeObjectURL(url);
                }, 150);
                return true;
            } catch (e) {
                logError('Download failed:', e);
                return false;
            }
        },

        /**
         * 讀取檔案內容
         * @param {File} file - 檔案物件
         * @returns {Promise<string>} 檔案內容
         */
        readFile(file) {
            return new Promise((resolve, reject) => {
                const reader = new FileReader();
                reader.onload = () => resolve(reader.result);
                reader.onerror = () => reject(reader.error);
                reader.readAsText(file);
            });
        },

        /**
         * 注入樣式
         * @param {string} css - CSS 內容
         * @param {string} id - 樣式元素 ID
         * @returns {HTMLStyleElement} 樣式元素
         */
        addStyle(css, id) {
            try {
                // 如果已存在,更新內容
                if (id) {
                    const exist = document.getElementById(id);
                    if (exist) {
                        exist.textContent = css;
                        return exist;
                    }
                }
                // 嘗試使用 GM_addStyle
                if (typeof GM_addStyle === 'function') {
                    const el = GM_addStyle(css);
                    if (id && el?.setAttribute) {
                        el.setAttribute('id', id);
                    }
                    return el;
                }
            } catch (e) {
                // GM_addStyle 不可用
            }

            // Fallback: 手動創建 style 元素
            const style = document.createElement('style');
            if (id) style.id = id;
            style.textContent = css;
            (document.head || document.documentElement).appendChild(style);
            return style;
        },

        /**
         * 格式化時間
         * @param {Date} date - 日期物件
         * @returns {string} 格式化的時間字串 (HH:MM:SS)
         */
        formatTime(date = new Date()) {
            return date.toLocaleTimeString('zh-TW', {
                hour: '2-digit',
                minute: '2-digit',
                second: '2-digit'
            });
        },

        /**
         * 格式化日期
         * @param {Date} date - 日期物件
         * @returns {string} 格式化的日期字串 (YYYY-MM-DD)
         */
        formatDate(date = new Date()) {
            return date.toISOString().slice(0, 10);
        },

        /**
         * 格式化相對時間
         * @param {number} ts - 時間戳
         * @returns {string} 相對時間描述
         */
        formatRelativeTime(ts) {
            const diff = Date.now() - ts;
            if (diff < 60000) return '剛才';
            if (diff < 3600000) return `${Math.floor(diff / 60000)} 分鐘前`;
            if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小時前`;
            if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`;
            return new Date(ts).toLocaleDateString('zh-TW');
        },

        /**
         * 限制數值範圍
         * @param {number} val - 數值
         * @param {number} min - 最小值
         * @param {number} max - 最大值
         * @returns {number} 限制後的數值
         */
        clamp(val, min, max) {
            return Math.min(Math.max(val, min), max);
        },

        /**
         * 計算文字統計
         * @param {string} text - 文字內容
         * @returns {Object} 統計結果
         */
        countText(text) {
            if (!text) return {
                chars: 0,
                charsNoSpace: 0,
                words: 0,
                lines: 0,
                readingTime: 0
            };

            const chars = text.length;
            const charsNoSpace = text.replace(/\s/g, '').length;
            const words = text.trim() ? text.trim().split(/\s+/).length : 0;
            const lines = text.split('\n').length;

            // 閱讀時間估算
            // 中文閱讀速度:約 300-500 字/分鐘,取 400
            // 英文閱讀速度:約 200-250 單詞/分鐘
            // 這裡使用字元數估算,適用於中文為主的內容
            // 最小為 1 分鐘
            const readingTime = Math.max(1, Math.ceil(charsNoSpace / 400));

            return { chars, charsNoSpace, words, lines, readingTime };
        },
        /**
         * 安全解析 JSON
         * @param {string} str - JSON 字串
         * @param {*} defaultVal - 預設值
         * @returns {*} 解析結果或預設值
         */
        safeJsonParse(str, defaultVal = null) {
            try {
                return JSON.parse(str);
            } catch (e) {
                return defaultVal;
            }
        },

        /**
         * HTML 跳脫
         * @param {string} str - 原始字串
         * @returns {string} 跳脫後的字串
         */
        escapeHtml(str) {
            const div = document.createElement('div');
            div.textContent = str;
            return div.innerHTML;
        },

        /**
         * 產生唯一 ID
         * @param {string} prefix - 前綴
         * @returns {string} 唯一 ID
         */
        generateId(prefix = 'mme') {
            return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
        },

        /**
         * GM_xmlhttpRequest 封裝
         * 用於跨域請求(繞過 CORS)
         * @param {string} url - 請求 URL
         * @param {number} timeout - 超時時間
         * @returns {Promise<string>} 回應內容
         */
        gmFetch(url, timeout = 30000) {
            return new Promise((resolve, reject) => {
                if (typeof GM_xmlhttpRequest !== 'function') {
                    // Fallback: 使用原生 fetch
                    fetch(url, { cache: 'no-store' })
                        .then(r => r.ok ? r.text() : Promise.reject(new Error(`HTTP ${r.status}`)))
                        .then(resolve)
                        .catch(reject);
                    return;
                }

                GM_xmlhttpRequest({
                    method: 'GET',
                    url,
                    timeout,
                    anonymous: true,
                    onload: (res) => {
                        if (res.status >= 200 && res.status < 400) {
                            resolve(res.responseText);
                        } else {
                            reject(new Error(`HTTP ${res.status}`));
                        }
                    },
                    onerror: () => reject(new Error('Network error')),
                    ontimeout: () => reject(new Error('Timeout'))
                });
            });
        },

        /**
         * 載入外部腳本
         * @param {string} url - 腳本 URL
         * @param {number} timeout - 超時時間
         * @returns {Promise<void>}
         */
        loadScript(url, timeout = 30000) {
            return new Promise((resolve, reject) => {
                // 檢查是否已載入
                if (document.querySelector(`script[src="${url}"]`)) {
                    return resolve();
                }

                const s = document.createElement('script');
                s.src = url;
                s.async = true;

                const timer = setTimeout(() => {
                    s.remove();
                    reject(new Error(`Script load timeout: ${url}`));
                }, timeout);

                s.onload = () => {
                    clearTimeout(timer);
                    resolve();
                };

                s.onerror = () => {
                    clearTimeout(timer);
                    s.remove();
                    reject(new Error(`Failed to load script: ${url}`));
                };

                document.head.appendChild(s);
            });
        },

        /**
         * 載入外部樣式表
         * @param {string} url - 樣式表 URL
         * @param {number} timeout - 超時時間
         * @returns {Promise<void>}
         */
        loadStylesheet(url, timeout = 15000) {
            return new Promise((resolve, reject) => {
                // 檢查是否已載入
                if (document.querySelector(`link[href="${url}"]`)) {
                    return resolve();
                }

                const link = document.createElement('link');
                link.rel = 'stylesheet';
                link.href = url;

                const timer = setTimeout(() => {
                    // 樣式表載入超時時不報錯,只是 resolve
                    resolve();
                }, timeout);

                link.onload = () => {
                    clearTimeout(timer);
                    resolve();
                };

                link.onerror = () => {
                    clearTimeout(timer);
                    reject(new Error(`Failed to load CSS: ${url}`));
                };

                document.head.appendChild(link);
            });
        },

        /**
         * 修復 CSS 中的相對 URL
         * 將相對路徑轉換為絕對路徑
         * @param {string} css - CSS 內容
         * @param {string} baseUrl - 基礎 URL
         * @returns {string} 修復後的 CSS
         */
        fixCssUrls(css, baseUrl) {
            const base = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1);
            return css.replace(
                /url\(\s*(['"]?)(?!data:|https?:|blob:|\/\/)([^'")]+)\1\s*\)/gi,
                (match, quote, path) => {
                    try {
                        return `url("${new URL(path, base).href}")`;
                    } catch (e) {
                        return match;
                    }
                }
            );
        },

        /**
         * 等待條件成立
         * @param {Function} conditionFn - 條件函數(返回 truthy 值表示條件成立)
         * @param {number} timeout - 超時時間
         * @param {number} interval - 檢查間隔
         * @returns {Promise<void>}
         */
        waitFor(conditionFn, timeout = 10000, interval = 100) {
            return new Promise((resolve, reject) => {
                const start = Date.now();
                const tick = () => {
                    let ok = false;
                    try {
                        ok = !!conditionFn();
                    } catch (e) {
                        ok = false;
                    }
                    if (ok) return resolve();
                    if (Date.now() - start >= timeout) {
                        return reject(new Error('Wait timeout'));
                    }
                    setTimeout(tick, interval);
                };
                tick();
            });
        },

        /**
         * 清除編輯器快取
         * @param {string} editorKey - 編輯器鍵名
         */
        clearEditorCache(editorKey) {
            const cfg = CONFIG.editors[editorKey];
            if (cfg?.cacheId) {
                try {
                    localStorage.removeItem(cfg.cacheId);
                } catch (e) {
                    // 忽略清除失敗
                }
            }
        },

        /**
         * 深度合併物件
         * @param {Object} target - 目標物件
         * @param {Object} source - 來源物件
         * @returns {Object} 合併後的物件
         */
        deepMerge(target, source) {
            const result = { ...target };
            for (const key of Object.keys(source)) {
                if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
                    result[key] = this.deepMerge(result[key] || {}, source[key]);
                } else {
                    result[key] = source[key];
                }
            }
            return result;
        },

        /**
         * 根據路徑獲取物件屬性
         * @param {Object} obj - 物件
         * @param {string} path - 路徑(如 'a.b.c')
         * @returns {*} 屬性值
         */
        getByPath(obj, path) {
            return path.split('.').reduce((o, k) => o?.[k], obj);
        },

        /**
         * 根據路徑設置物件屬性
         * @param {Object} obj - 物件
         * @param {string} path - 路徑(如 'a.b.c')
         * @param {*} value - 值
         */
        setByPath(obj, path, value) {
            const keys = path.split('.');
            const last = keys.pop();
            const target = keys.reduce((o, k) => {
                if (!o[k]) o[k] = {};
                return o[k];
            }, obj);
            target[last] = value;
        }
    };

    // ========================================
    // SafeExecute 安全執行工具
    // ========================================

    /**
     * 安全執行工具
     *
     * 設計意圖:
     * - 提供統一的錯誤捕獲機制
     * - 減少重複的 try-catch 代碼
     * - 確保錯誤被正確記錄而不中斷程式
     * - 支援同步和異步函數
     */
    const SafeExecute = {
        /**
         * 安全執行異步函數
         * @param {Function} fn - 要執行的異步函數
         * @param {*} fallback - 失敗時的返回值
         * @param {string} context - 上下文描述(用於日誌)
         * @returns {Promise<*>} 執行結果或 fallback
         */
        async run(fn, fallback = null, context = 'unknown') {
            try {
                return await fn();
            } catch (e) {
                logError(`SafeExecute [${context}]:`, e?.message || e);
                if (DEBUG) {
                    console.error(`SafeExecute [${context}] stack:`, e);
                }
                return fallback;
            }
        },

        /**
         * 包裝函數使其安全執行
         * @param {Function} fn - 要包裝的函數
         * @param {*} fallback - 失敗時的返回值
         * @param {string} context - 上下文描述
         * @returns {Function} 包裝後的安全函數
         */
        wrap(fn, fallback = null, context = 'unknown') {
            return (...args) => {
                try {
                    const result = fn(...args);
                    // 處理 Promise
                    if (result && typeof result.then === 'function') {
                        return result.catch(e => {
                            logError(`SafeExecute.wrap [${context}]:`, e?.message || e);
                            return fallback;
                        });
                    }
                    return result;
                } catch (e) {
                    logError(`SafeExecute.wrap [${context}]:`, e?.message || e);
                    return fallback;
                }
            };
        },

        /**
         * 安全執行 DOM 操作
         * @param {Function} fn - DOM 操作函數
         * @param {string} context - 上下文描述
         * @returns {boolean} 是否成功
         */
        dom(fn, context = 'DOM operation') {
            try {
                fn();
                return true;
            } catch (e) {
                logWarn(`SafeExecute.dom [${context}]:`, e?.message || e);
                return false;
            }
        },

        /**
         * 帶重試的安全執行
         * @param {Function} fn - 要執行的異步函數
         * @param {number} maxRetries - 最大重試次數
         * @param {number} delay - 重試延遲(毫秒)
         * @param {string} context - 上下文描述
         * @returns {Promise<*>} 執行結果
         */
        async withRetry(fn, maxRetries = 3, delay = 1000, context = 'retry') {
            let lastError;

            for (let i = 0; i <= maxRetries; i++) {
                try {
                    return await fn();
                } catch (e) {
                    lastError = e;
                    log(`SafeExecute.withRetry [${context}]: Attempt ${i + 1} failed`);

                    if (i < maxRetries) {
                        await new Promise(r => setTimeout(r, delay));
                    }
                }
            }

            logError(`SafeExecute.withRetry [${context}]: All attempts failed`);
            throw lastError;
        }
    };

    // ========================================
    // 儲存遷移
    // ========================================

    /**
     * 執行儲存資料遷移(相容舊版本)
     *
     * 設計意圖:
     * - 確保從舊版本升級的用戶不會丟失資料
     * - 將舊的儲存鍵名遷移到新的統一鍵名
     * - 只遷移一次,避免覆蓋用戶的新資料
     */
    function migrateStorage() {
        const keys = CONFIG.storageKeys;

        // 草稿遷移
        const currentDraft = Utils.storage.get(keys.content, null);
        if (currentDraft === null || currentDraft === undefined || currentDraft === '') {
            const candidates = ['mme_draft_v3', 'mme_draft_v31'];
            for (const k of candidates) {
                const v = Utils.storage.get(k, null);
                if (typeof v === 'string' && v.trim()) {
                    Utils.storage.set(keys.content, v);
                    log('Migrated draft from', k);
                    break;
                }
            }
        }

        // 主題遷移
        const currentTheme = Utils.storage.get(keys.theme, null);
        if (currentTheme === null) {
            const candidates = ['mme_theme_v3', 'mme_theme_v31'];
            for (const k of candidates) {
                const v = Utils.storage.get(k, null);
                if (typeof v === 'string' && v) {
                    Utils.storage.set(keys.theme, v);
                    log('Migrated theme from', k);
                    break;
                }
            }
        }

        // 編輯器選擇遷移
        const currentEditor = Utils.storage.get(keys.editor, null);
        if (currentEditor === null) {
            const candidates = ['mme_editor_v3', 'mme_editor_v31'];
            for (const k of candidates) {
                const v = Utils.storage.get(k, null);
                if (typeof v === 'string' && v) {
                    Utils.storage.set(keys.editor, v);
                    log('Migrated editor from', k);
                    break;
                }
            }
        }
    }

    // 執行遷移(腳本載入時立即執行)
    migrateStorage();

    // ========================================
    // 主題管理
    // ========================================

    /**
     * 主題管理器
     *
     * 設計意圖:
     * - 集中管理主題狀態
     * - 支援監聽系統主題變化(prefers-color-scheme)
     * - 提供訂閱機制讓其他模組響應主題變化
     */
    const Theme = {
        /** @type {string|null} 當前主題 ('light' | 'dark') */
        current: null,

        /** @type {Set<Function>} 主題變更監聽器集合 */
        listeners: new Set(),

        /**
         * 初始化主題系統
         * 應在腳本啟動時調用一次
         */
        init() {
            // 從儲存讀取主題,若無則使用預設值
            this.current = Utils.storage.get(CONFIG.storageKeys.theme, CONFIG.defaultTheme);

            // 監聽系統主題變化
            try {
                const mq = window.matchMedia('(prefers-color-scheme: dark)');
                const handler = () => {
                    // 只有當用戶設定為 'auto' 時才響應系統主題
                    if (Utils.storage.get(CONFIG.storageKeys.theme) === 'auto') {
                        const newTheme = mq.matches ? 'dark' : 'light';
                        this.current = newTheme;
                        this.notify();
                    }
                };

                // 兼容舊版瀏覽器
                if (mq.addEventListener) {
                    mq.addEventListener('change', handler);
                } else if (mq.addListener) {
                    mq.addListener(handler);
                }
            } catch (e) {
                // 某些環境不支援 matchMedia
                log('matchMedia not supported:', e.message);
            }
        },

        /**
         * 取得當前主題
         * @returns {string} 'light' 或 'dark'
         */
        get() {
            return this.current;
        },

        /**
         * 設定主題
         * @param {string} theme - 'light' 或 'dark'
         */
        set(theme) {
            this.current = theme;
            Utils.storage.set(CONFIG.storageKeys.theme, theme);
            this.notify();
        },

        /**
         * 切換主題(深色 ↔ 淺色)
         * @returns {string} 切換後的主題
         */
        toggle() {
            const next = this.current === 'dark' ? 'light' : 'dark';
            this.set(next);
            return next;
        },

        /**
         * 註冊主題變更監聽器
         * @param {Function} fn - 監聽函數,接收新主題作為參數
         * @returns {Function} 取消註冊函數
         */
        onChange(fn) {
            this.listeners.add(fn);
            return () => this.listeners.delete(fn);
        },

        /**
         * 通知所有監聽器主題已變更
         */
        notify() {
            this.listeners.forEach(fn => {
                try {
                    fn(this.current);
                } catch (e) {
                    logError('Theme listener error:', e);
                }
            });
        },

        /**
         * 檢查是否為深色主題
         * @returns {boolean}
         */
        isDark() {
            return this.current === 'dark';
        }
    };

    // ========================================
    // 編輯器通用預覽樣式
    // ========================================

    /**
     * 編輯器預覽樣式管理器
     *
     * 設計意圖:
     * - 統一各編輯器預覽區的配色,確保一致的閱讀體驗
     * - 修復某些編輯器原生樣式與我們主題不匹配的問題
     * - 確保代碼塊、語法高亮在亮/暗模式下都有良好的可讀性
     *
     * 注意:
     * - 使用 !important 是必要的,因為需要覆蓋編輯器原生樣式
     * - 這些樣式會在編輯器容器內生效,不會影響頁面其他部分
     */
    const EditorPreviewStyles = {
        /**
         * 注入所有編輯器的預覽區配色修復
         * 應在 Modal 創建時調用
         */
        inject() {
            const p = CONFIG.prefix;
            const styleId = `${p}editor-preview-fix`;

            // 避免重複注入
            if (document.getElementById(styleId)) return;

            Utils.addStyle(`
/* ========================================
   編輯器預覽區通用配色修復
   ======================================== */

/* ===== 亮色模式 ===== */

/* EasyMDE 預覽區 */
.${p}editor .editor-preview,
.${p}editor .editor-preview-side {
    background: #fff !important;
    color: #24292e !important;
    font-size: 14px !important;
    line-height: 1.6 !important;
}

/* 預覽區代碼塊 - 亮色 */
.${p}editor .editor-preview pre,
.${p}editor .editor-preview-side pre,
.${p}editor .editor-preview code,
.${p}editor .editor-preview-side code {
    background: #f6f8fa !important;
    color: #24292e !important;
    font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace !important;
}

.${p}editor .editor-preview pre,
.${p}editor .editor-preview-side pre {
    padding: 16px !important;
    border-radius: 6px !important;
    overflow-x: auto !important;
    border: 1px solid #e1e4e8 !important;
}

.${p}editor .editor-preview pre code,
.${p}editor .editor-preview-side pre code {
    background: transparent !important;
    padding: 0 !important;
    border: none !important;
    font-size: 13px !important;
    line-height: 1.5 !important;
}

.${p}editor .editor-preview code,
.${p}editor .editor-preview-side code {
    padding: 2px 6px !important;
    border-radius: 4px !important;
    font-size: 85% !important;
}

/* 預覽區標題 */
.${p}editor .editor-preview h1,
.${p}editor .editor-preview h2,
.${p}editor .editor-preview h3,
.${p}editor .editor-preview h4,
.${p}editor .editor-preview h5,
.${p}editor .editor-preview h6,
.${p}editor .editor-preview-side h1,
.${p}editor .editor-preview-side h2,
.${p}editor .editor-preview-side h3,
.${p}editor .editor-preview-side h4,
.${p}editor .editor-preview-side h5,
.${p}editor .editor-preview-side h6 {
    color: #24292e !important;
    border-bottom-color: #e1e4e8 !important;
    margin-top: 24px !important;
    margin-bottom: 16px !important;
    font-weight: 600 !important;
}

/* 預覽區連結 */
.${p}editor .editor-preview a,
.${p}editor .editor-preview-side a {
    color: #0366d6 !important;
    text-decoration: none !important;
}

.${p}editor .editor-preview a:hover,
.${p}editor .editor-preview-side a:hover {
    text-decoration: underline !important;
}

/* 預覽區引用 */
.${p}editor .editor-preview blockquote,
.${p}editor .editor-preview-side blockquote {
    border-left: 4px solid #dfe2e5 !important;
    color: #6a737d !important;
    padding: 0 16px !important;
    margin: 16px 0 !important;
    background: transparent !important;
}

/* 預覽區表格 */
.${p}editor .editor-preview table,
.${p}editor .editor-preview-side table {
    border-collapse: collapse !important;
    width: 100% !important;
    margin: 16px 0 !important;
}

.${p}editor .editor-preview th,
.${p}editor .editor-preview td,
.${p}editor .editor-preview-side th,
.${p}editor .editor-preview-side td {
    border: 1px solid #dfe2e5 !important;
    padding: 8px 12px !important;
    text-align: left !important;
}

.${p}editor .editor-preview th,
.${p}editor .editor-preview-side th {
    background: #f6f8fa !important;
    font-weight: 600 !important;
}

/* 預覽區列表 */
.${p}editor .editor-preview ul,
.${p}editor .editor-preview ol,
.${p}editor .editor-preview-side ul,
.${p}editor .editor-preview-side ol {
    padding-left: 2em !important;
    margin: 16px 0 !important;
}

.${p}editor .editor-preview li,
.${p}editor .editor-preview-side li {
    margin: 4px 0 !important;
}

/* 預覽區分隔線 */
.${p}editor .editor-preview hr,
.${p}editor .editor-preview-side hr {
    border: none !important;
    border-top: 1px solid #e1e4e8 !important;
    margin: 24px 0 !important;
}

/* 預覽區圖片 */
.${p}editor .editor-preview img,
.${p}editor .editor-preview-side img {
    max-width: 100% !important;
    border-radius: 4px !important;
}

/* ===== 深色模式 ===== */

/* EasyMDE 深色預覽區 */
.${p}editor .editor-preview.editor-preview-dark,
.${p}editor .editor-preview-side.editor-preview-dark,
.${p}editor-dark .editor-preview,
.${p}editor-dark .editor-preview-side {
    background: #0d1117 !important;
    color: #c9d1d9 !important;
}

/* 深色代碼塊 */
.${p}editor .editor-preview.editor-preview-dark pre,
.${p}editor .editor-preview-side.editor-preview-dark pre,
.${p}editor .editor-preview.editor-preview-dark code,
.${p}editor .editor-preview-side.editor-preview-dark code,
.${p}editor-dark .editor-preview pre,
.${p}editor-dark .editor-preview code,
.${p}editor-dark .editor-preview-side pre,
.${p}editor-dark .editor-preview-side code {
    background: #161b22 !important;
    color: #c9d1d9 !important;
    border-color: #30363d !important;
}

.${p}editor .editor-preview.editor-preview-dark pre code,
.${p}editor .editor-preview-side.editor-preview-dark pre code,
.${p}editor-dark .editor-preview pre code,
.${p}editor-dark .editor-preview-side pre code {
    background: transparent !important;
}

/* 深色標題 */
.${p}editor .editor-preview.editor-preview-dark h1,
.${p}editor .editor-preview.editor-preview-dark h2,
.${p}editor .editor-preview.editor-preview-dark h3,
.${p}editor .editor-preview.editor-preview-dark h4,
.${p}editor .editor-preview.editor-preview-dark h5,
.${p}editor .editor-preview.editor-preview-dark h6,
.${p}editor .editor-preview-side.editor-preview-dark h1,
.${p}editor .editor-preview-side.editor-preview-dark h2,
.${p}editor .editor-preview-side.editor-preview-dark h3,
.${p}editor .editor-preview-side.editor-preview-dark h4,
.${p}editor .editor-preview-side.editor-preview-dark h5,
.${p}editor .editor-preview-side.editor-preview-dark h6 {
    color: #c9d1d9 !important;
    border-bottom-color: #21262d !important;
}

/* 深色連結 */
.${p}editor .editor-preview.editor-preview-dark a,
.${p}editor .editor-preview-side.editor-preview-dark a {
    color: #58a6ff !important;
}

/* 深色引用 */
.${p}editor .editor-preview.editor-preview-dark blockquote,
.${p}editor .editor-preview-side.editor-preview-dark blockquote {
    border-left-color: #3b5998 !important;
    color: #8b949e !important;
}

/* 深色表格 */
.${p}editor .editor-preview.editor-preview-dark th,
.${p}editor .editor-preview.editor-preview-dark td,
.${p}editor .editor-preview-side.editor-preview-dark th,
.${p}editor .editor-preview-side.editor-preview-dark td {
    border-color: #30363d !important;
}

.${p}editor .editor-preview.editor-preview-dark th,
.${p}editor .editor-preview-side.editor-preview-dark th {
    background: #161b22 !important;
}

/* 深色分隔線 */
.${p}editor .editor-preview.editor-preview-dark hr,
.${p}editor .editor-preview-side.editor-preview-dark hr {
    border-top-color: #30363d !important;
}

/* ===== Toast UI 預覽區修復 ===== */

.${p}editor .toastui-editor-md-preview {
    color: #24292e !important;
}

.${p}editor .toastui-editor-md-preview pre,
.${p}editor .toastui-editor-md-preview code {
    background: #f6f8fa !important;
    color: #24292e !important;
}

.${p}editor .toastui-editor-md-preview pre {
    padding: 16px !important;
    border-radius: 6px !important;
    border: 1px solid #e1e4e8 !important;
}

.${p}editor .toastui-editor-md-preview pre code {
    background: transparent !important;
}

/* Toast UI 深色 */
.${p}editor .toastui-editor-dark .toastui-editor-md-preview {
    color: #c9d1d9 !important;
}

.${p}editor .toastui-editor-dark .toastui-editor-md-preview pre,
.${p}editor .toastui-editor-dark .toastui-editor-md-preview code {
    background: #161b22 !important;
    color: #c9d1d9 !important;
    border-color: #30363d !important;
}

/* ===== Cherry Markdown 預覽區修復 ===== */

.${p}editor .cherry-previewer {
    color: #24292e !important;
}

.${p}editor .cherry-previewer pre,
.${p}editor .cherry-previewer code {
    background: #f6f8fa !important;
    color: #24292e !important;
}

.${p}editor .cherry-previewer pre {
    padding: 16px !important;
    border-radius: 6px !important;
    border: 1px solid #e1e4e8 !important;
}

.${p}editor .cherry-previewer pre code {
    background: transparent !important;
}

/* Cherry 深色 - 使用 .cherry 容器上的類名判斷 */
.${p}editor .cherry.theme__dark .cherry-previewer,
.${p}editor .cherry[data-theme="dark"] .cherry-previewer {
    color: #c9d1d9 !important;
}

/* Cherry 深色 - KaTeX 數學公式顏色保護 */
.${p}editor .cherry.theme__dark .cherry-previewer .katex,
.${p}editor .cherry.theme__dark .cherry-previewer .katex *,
.${p}editor .cherry[data-theme="dark"] .cherry-previewer .katex,
.${p}editor .cherry[data-theme="dark"] .cherry-previewer .katex * {
    color: inherit;
}

.${p}editor .cherry.theme__dark .cherry-previewer .katex [style*="color"],
.${p}editor .cherry[data-theme="dark"] .cherry-previewer .katex [style*="color"],
.${p}editor .cherry.theme__dark .cherry-previewer [style*="color"],
.${p}editor .cherry[data-theme="dark"] .cherry-previewer [style*="color"] {
    color: unset !important;
}

.${p}editor .cherry.theme__dark .cherry-previewer pre,
.${p}editor .cherry.theme__dark .cherry-previewer code,
.${p}editor .cherry[data-theme="dark"] .cherry-previewer pre,
.${p}editor .cherry[data-theme="dark"] .cherry-previewer code {
    background: #161b22 !important;
    color: #c9d1d9 !important;
    border-color: #30363d !important;
}

/* ===== Vditor 預覽區修復 ===== */

.${p}editor .vditor-preview {
    color: #24292e !important;
}

.${p}editor .vditor-preview pre,
.${p}editor .vditor-preview code {
    background: #f6f8fa !important;
    color: #24292e !important;
}

.${p}editor .vditor-preview pre {
    padding: 16px !important;
    border-radius: 6px !important;
    border: 1px solid #e1e4e8 !important;
}

.${p}editor .vditor-preview pre code {
    background: transparent !important;
}

/* Vditor 深色 */
.${p}editor .vditor--dark .vditor-preview,
.vditor--dark .vditor-preview {
    color: #c9d1d9 !important;
}

.${p}editor .vditor--dark .vditor-preview pre,
.${p}editor .vditor--dark .vditor-preview code,
.vditor--dark .vditor-preview pre,
.vditor--dark .vditor-preview code {
    background: #161b22 !important;
    color: #c9d1d9 !important;
    border-color: #30363d !important;
}

/* ===== 語法高亮通用修復 ===== */

/* 亮色模式語法高亮 */
.${p}editor .hljs-keyword,
.${p}editor .cm-keyword,
.${p}editor .token.keyword {
    color: #cf222e !important;
}

.${p}editor .hljs-string,
.${p}editor .cm-string,
.${p}editor .token.string {
    color: #0a3069 !important;
}

.${p}editor .hljs-comment,
.${p}editor .cm-comment,
.${p}editor .token.comment {
    color: #6e7781 !important;
}

.${p}editor .hljs-function,
.${p}editor .hljs-title,
.${p}editor .cm-def,
.${p}editor .token.function {
    color: #8250df !important;
}

.${p}editor .hljs-number,
.${p}editor .cm-number,
.${p}editor .token.number {
    color: #0550ae !important;
}

/* 深色模式語法高亮 */
.vditor--dark .hljs-keyword,
.toastui-editor-dark .hljs-keyword,
.editor-preview-dark .hljs-keyword,
.${p}editor-dark .hljs-keyword {
    color: #ff7b72 !important;
}

.vditor--dark .hljs-string,
.toastui-editor-dark .hljs-string,
.editor-preview-dark .hljs-string,
.${p}editor-dark .hljs-string {
    color: #a5d6ff !important;
}

.vditor--dark .hljs-comment,
.toastui-editor-dark .hljs-comment,
.editor-preview-dark .hljs-comment,
.${p}editor-dark .hljs-comment {
    color: #8b949e !important;
}

.vditor--dark .hljs-function,
.vditor--dark .hljs-title,
.toastui-editor-dark .hljs-function,
.toastui-editor-dark .hljs-title,
.editor-preview-dark .hljs-function,
.editor-preview-dark .hljs-title,
.${p}editor-dark .hljs-function,
.${p}editor-dark .hljs-title {
    color: #d2a8ff !important;
}

.vditor--dark .hljs-number,
.toastui-editor-dark .hljs-number,
.editor-preview-dark .hljs-number,
.${p}editor-dark .hljs-number {
    color: #79c0ff !important;
}
            `, styleId);
        }
    };

    // ========================================
    // Portal 傳送門系統
    // ========================================

    /**
     * Portal 傳送門管理器
     *
     * 設計意圖:
     * - 將彈出元素(選單、面板等)渲染到 body 的直接子元素
     * - 避免被父容器的 overflow: hidden 裁切
     * - 提供統一的定位、拖曳、縮放功能
     *
     * 使用方式:
     * 1. Portal.append(element) - 將元素加入 Portal
     * 2. Portal.positionAt(element, anchor) - 相對於錨點定位
     * 3. Portal.enableDrag(element, handle) - 啟用拖曳
     * 4. Portal.remove(element) - 移除元素
     */
    const Portal = {
        /** @type {HTMLElement|null} Portal 容器 */
        container: null,

        /** @type {Map} 面板拖曳狀態 */
        dragStates: new Map(),

        /** @type {Set} 全域事件監聽器清理函數 */
        _globalCleanups: new Set(),

        /** @type {boolean} 是否已綁定全域事件 */
        _globalEventsBound: false,

        /**
         * 初始化 Portal(惰性初始化)
         */
        init() {
            if (this.container) return;

            const p = CONFIG.prefix;
            this.container = document.createElement('div');
            this.container.id = `${p}portal`;
            this.container.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 0;
                height: 0;
                z-index: ${CONFIG.zIndex + 500};
                pointer-events: none;
            `;
            document.body.appendChild(this.container);

            // 綁定全域滑鼠事件(用於拖曳)
            this._bindGlobalEvents();
        },

        /**
         * 綁定全域事件
         * @private
         */
        _bindGlobalEvents() {
            if (this._globalEventsBound) return;

            const onMouseMove = (e) => {
                this.dragStates.forEach((state, panel) => {
                    if (state.isDragging) {
                        this._handleDragMove(e, panel, state);
                    }
                });
            };

            const onMouseUp = () => {
                this.dragStates.forEach((state, panel) => {
                    if (state.isDragging) {
                        this._handleDragEnd(panel, state);
                    }
                });
            };

            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);

            this._globalCleanups.add(() => {
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', onMouseUp);
            });

            this._globalEventsBound = true;
        },

        /**
         * 將元素加入 Portal
         * @param {HTMLElement} el - 元素
         * @returns {HTMLElement} 元素
         */
        append(el) {
            this.init();
            el.style.pointerEvents = 'auto';
            this.container.appendChild(el);
            return el;
        },

        /**
         * 從 Portal 移除元素
         * @param {HTMLElement} el - 元素
         */
        remove(el) {
            if (!el) return;

            // 清理拖曳狀態
            if (this.dragStates.has(el)) {
                const state = this.dragStates.get(el);
                if (state.cleanup) {
                    state.cleanup();
                }
                this.dragStates.delete(el);
            }

            // 清理元素上的清理函數
            if (el._cleanupDrag) {
                el._cleanupDrag();
                delete el._cleanupDrag;
            }

            // 從 DOM 移除
            if (el.parentNode === this.container) {
                this.container.removeChild(el);
            }
        },

        /**
         * 根據錨點定位彈出面板
         * @param {HTMLElement} panel - 面板元素
         * @param {HTMLElement|null} anchor - 錨點元素(null 表示置中)
         * @param {Object} options - 選項
         */
        positionAt(panel, anchor, options = {}) {
            const {
                placement = 'bottom-end',  // 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' | 'center'
                offsetX = 0,
                offsetY = 8
            } = options;

            if (!panel) return;

            // 先顯示以計算尺寸
            const wasHidden = panel.style.display === 'none';
            if (wasHidden) {
                panel.style.visibility = 'hidden';
                panel.style.display = 'flex';
            }

            const panelRect = panel.getBoundingClientRect();
            const viewW = window.innerWidth;
            const viewH = window.innerHeight;

            let top, left;

            if (!anchor || placement === 'center') {
                // 置中
                left = (viewW - panelRect.width) / 2;
                top = (viewH - panelRect.height) / 2;
            } else {
                const anchorRect = anchor.getBoundingClientRect();
                const isTop = placement.startsWith('top');
                const isEnd = placement.endsWith('end');

                // 計算垂直位置
                if (isTop) {
                    top = anchorRect.top - panelRect.height - offsetY;
                } else {
                    top = anchorRect.bottom + offsetY;
                }

                // 計算水平位置
                if (isEnd) {
                    left = anchorRect.right - panelRect.width + offsetX;
                } else {
                    left = anchorRect.left + offsetX;
                }

                // 邊界檢查 - 垂直
                if (top < 10) {
                    top = anchorRect.bottom + offsetY;
                }
                if (top + panelRect.height > viewH - 10) {
                    top = anchorRect.top - panelRect.height - offsetY;
                }

                // 邊界檢查 - 水平
                if (left < 10) {
                    left = 10;
                }
                if (left + panelRect.width > viewW - 10) {
                    left = viewW - panelRect.width - 10;
                }
            }

            panel.style.position = 'fixed';
            panel.style.top = `${Math.max(10, top)}px`;
            panel.style.left = `${Math.max(10, left)}px`;

            if (wasHidden) {
                panel.style.visibility = 'visible';
            }
        },

        /**
         * 為面板啟用拖曳功能
         * @param {HTMLElement} panel - 面板元素
         * @param {HTMLElement} handle - 拖曳手柄(通常是標題列)
         */
        enableDrag(panel, handle) {
            if (!panel || !handle) return;

            const state = {
                isDragging: false,
                startX: 0,
                startY: 0,
                startLeft: 0,
                startTop: 0,
                cleanup: null
            };

            this.dragStates.set(panel, state);

            const onMouseDown = (e) => {
                // 排除按鈕等互動元素
                if (e.target.closest('button, input, select, a')) return;

                e.preventDefault();
                state.isDragging = true;
                state.startX = e.clientX;
                state.startY = e.clientY;

                const rect = panel.getBoundingClientRect();
                state.startLeft = rect.left;
                state.startTop = rect.top;

                panel.style.transition = 'none';
                handle.style.cursor = 'grabbing';
            };

            handle.style.cursor = 'move';
            handle.addEventListener('mousedown', onMouseDown);

            // 儲存清理函數
            state.cleanup = () => {
                handle.removeEventListener('mousedown', onMouseDown);
            };

            panel._cleanupDrag = state.cleanup;
        },

        /**
         * 處理拖曳移動
         * @private
         */
        _handleDragMove(e, panel, state) {
            const dx = e.clientX - state.startX;
            const dy = e.clientY - state.startY;

            let newLeft = state.startLeft + dx;
            let newTop = state.startTop + dy;

            // 邊界限制
            const rect = panel.getBoundingClientRect();
            newLeft = Utils.clamp(newLeft, 10, window.innerWidth - rect.width - 10);
            newTop = Utils.clamp(newTop, 10, window.innerHeight - rect.height - 10);

            panel.style.left = `${newLeft}px`;
            panel.style.top = `${newTop}px`;
        },

        /**
         * 處理拖曳結束
         * @private
         */
        _handleDragEnd(panel, state) {
            state.isDragging = false;
            panel.style.transition = '';

            // 找到對應的 handle 並恢復游標
            const handle = panel.querySelector('[style*="cursor: grabbing"], [style*="cursor:grabbing"]');
            if (handle) {
                handle.style.cursor = 'move';
            }
        },

        /**
         * 為面板啟用縮放功能
         * @param {HTMLElement} panel - 面板元素
         * @param {Object} options - 選項
         */
        enableResize(panel, options = {}) {
            const {
                minWidth = 300,
                minHeight = 200,
                maxWidth = window.innerWidth * 0.95,
                maxHeight = window.innerHeight * 0.9
            } = options;

            const p = CONFIG.prefix;

            // 建立縮放手柄
            const resizeHandle = document.createElement('div');
            resizeHandle.className = `${p}resize-handle`;
            resizeHandle.style.cssText = `
                position: absolute;
                right: 0;
                bottom: 0;
                width: 16px;
                height: 16px;
                cursor: nwse-resize;
                background: linear-gradient(135deg, transparent 50%, rgba(128,128,128,0.3) 50%);
                border-radius: 0 0 8px 0;
            `;
            panel.appendChild(resizeHandle);

            let isResizing = false;
            let startX, startY, startWidth, startHeight;

            const onMouseDown = (e) => {
                e.preventDefault();
                e.stopPropagation();
                isResizing = true;
                startX = e.clientX;
                startY = e.clientY;
                startWidth = panel.offsetWidth;
                startHeight = panel.offsetHeight;
                panel.style.transition = 'none';
            };

            const onMouseMove = (e) => {
                if (!isResizing) return;

                const dx = e.clientX - startX;
                const dy = e.clientY - startY;

                const newWidth = Utils.clamp(startWidth + dx, minWidth, maxWidth);
                const newHeight = Utils.clamp(startHeight + dy, minHeight, maxHeight);

                panel.style.width = `${newWidth}px`;
                panel.style.height = `${newHeight}px`;
            };

            const onMouseUp = () => {
                if (isResizing) {
                    isResizing = false;
                    panel.style.transition = '';
                }
            };

            resizeHandle.addEventListener('mousedown', onMouseDown);
            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);

            // 儲存清理函數
            const cleanup = () => {
                resizeHandle.removeEventListener('mousedown', onMouseDown);
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', onMouseUp);
                resizeHandle.remove();
            };

            if (!panel._cleanupResize) {
                panel._cleanupResize = cleanup;
            } else {
                const oldCleanup = panel._cleanupResize;
                panel._cleanupResize = () => {
                    oldCleanup();
                    cleanup();
                };
            }
        },

        /**
         * 銷毀所有 Portal 內容和事件
         * 用於腳本完全卸載時
         */
        destroyAll() {
            // 清理所有拖曳狀態
            this.dragStates.forEach((state, panel) => {
                if (state.cleanup) {
                    state.cleanup();
                }
            });
            this.dragStates.clear();

            // 清理全域事件
            this._globalCleanups.forEach(cleanup => {
                try {
                    cleanup();
                } catch (e) {
                    // 忽略清理錯誤
                }
            });
            this._globalCleanups.clear();
            this._globalEventsBound = false;

            // 移除容器
            if (this.container && this.container.parentNode) {
                this.container.parentNode.removeChild(this.container);
            }
            this.container = null;
        }
    };

    // ========================================
    // ResizeManager 縮放管理器
    // ========================================

    /**
     * 縮放管理器
     *
     * 設計意圖:
     * - 為主視窗和面板提供邊框縮放功能
     * - 支援多邊緣縮放(右、下、角落、左、上)
     * - 提供縮放回調以便其他模組響應尺寸變化
     *
     * 與 Portal.enableResize 的區別:
     * - Portal.enableResize 是為 Portal 內的小面板設計的簡化版
     * - ResizeManager 是為主視窗設計的完整版,支援更多邊緣和回調
     */
    const ResizeManager = {
        /** @type {Map} 各元素的縮放狀態 */
        states: new Map(),

        /** @type {boolean} 是否已綁定全域事件 */
        _globalEventsBound: false,

        /**
         * 為元素啟用邊框縮放
         * @param {HTMLElement} el - 目標元素
         * @param {Object} options - 選項
         */
        enable(el, options = {}) {
            const {
                minWidth = 380,
                minHeight = 350,
                maxWidth = window.innerWidth - 20,
                maxHeight = window.innerHeight - 20,
                edges = ['right', 'bottom', 'corner'],
                onResize = null,
                onResizeEnd = null
            } = options;

            const p = CONFIG.prefix;

            // 建立邊緣手柄
            const handles = {};

            if (edges.includes('right')) {
                handles.right = this._createHandle(el, 'right', p);
            }
            if (edges.includes('bottom')) {
                handles.bottom = this._createHandle(el, 'bottom', p);
            }
            if (edges.includes('corner')) {
                handles.corner = this._createHandle(el, 'corner', p);
            }
            if (edges.includes('left')) {
                handles.left = this._createHandle(el, 'left', p);
            }
            if (edges.includes('top')) {
                handles.top = this._createHandle(el, 'top', p);
            }

            const state = {
                isResizing: false,
                edge: null,
                startX: 0,
                startY: 0,
                startWidth: 0,
                startHeight: 0,
                startLeft: 0,
                startTop: 0,
                handles,
                options: { minWidth, minHeight, maxWidth, maxHeight, onResize, onResizeEnd }
            };

            this.states.set(el, state);

            // 綁定手柄事件
            Object.entries(handles).forEach(([edge, handle]) => {
                const mouseDownHandler = (e) => this._onMouseDown(e, el, edge);
                handle.addEventListener('mousedown', mouseDownHandler);
                // 儲存以便清理
                handle._mouseDownHandler = mouseDownHandler;
            });

            // 確保全域事件已綁定
            this._bindGlobalEvents();
        },

        /**
         * 建立縮放手柄
         * @private
         */
        _createHandle(el, edge, prefix) {
            const handle = document.createElement('div');
            handle.className = `${prefix}resize-handle ${prefix}resize-${edge}`;

            const styles = {
                right: `
                    position: absolute;
                    right: -4px;
                    top: 10%;
                    width: 8px;
                    height: 80%;
                    cursor: ew-resize;
                    z-index: 10;
                `,
                bottom: `
                    position: absolute;
                    bottom: -4px;
                    left: 10%;
                    width: 80%;
                    height: 8px;
                    cursor: ns-resize;
                    z-index: 10;
                `,
                corner: `
                    position: absolute;
                    right: -4px;
                    bottom: -4px;
                    width: 16px;
                    height: 16px;
                    cursor: nwse-resize;
                    z-index: 11;
                    background: linear-gradient(135deg, transparent 30%, rgba(128,128,128,0.4) 30%, rgba(128,128,128,0.4) 40%, transparent 40%, transparent 60%, rgba(128,128,128,0.4) 60%, rgba(128,128,128,0.4) 70%, transparent 70%);
                    border-radius: 0 0 8px 0;
                `,
                left: `
                    position: absolute;
                    left: -4px;
                    top: 10%;
                    width: 8px;
                    height: 80%;
                    cursor: ew-resize;
                    z-index: 10;
                `,
                top: `
                    position: absolute;
                    top: -4px;
                    left: 10%;
                    width: 80%;
                    height: 8px;
                    cursor: ns-resize;
                    z-index: 10;
                `
            };

            handle.style.cssText = styles[edge] || '';
            el.appendChild(handle);
            return handle;
        },

        /**
         * 綁定全域滑鼠事件
         * @private
         */
        _bindGlobalEvents() {
            if (this._globalEventsBound) return;

            // 使用箭頭函數保留 this 上下文
            this._globalMouseMoveHandler = (e) => {
                this.states.forEach((state, el) => {
                    if (state.isResizing) {
                        this._onMouseMove(e, el);
                    }
                });
            };

            this._globalMouseUpHandler = (e) => {
                this.states.forEach((state, el) => {
                    if (state.isResizing) {
                        this._onMouseUp(e, el);
                    }
                });
            };

            document.addEventListener('mousemove', this._globalMouseMoveHandler);
            document.addEventListener('mouseup', this._globalMouseUpHandler);

            this._globalEventsBound = true;
        },

        /**
         * 處理滑鼠按下
         * @private
         */
        _onMouseDown(e, el, edge) {
            e.preventDefault();
            e.stopPropagation();

            const state = this.states.get(el);
            if (!state) return;

            const rect = el.getBoundingClientRect();
            state.isResizing = true;
            state.edge = edge;
            state.startX = e.clientX;
            state.startY = e.clientY;
            state.startWidth = rect.width;
            state.startHeight = rect.height;
            state.startLeft = rect.left;
            state.startTop = rect.top;

            el.style.transition = 'none';
            el.classList.add(`${CONFIG.prefix}resizing`);
            document.body.style.cursor = this._getCursor(edge);
            document.body.style.userSelect = 'none';
        },

        /**
         * 處理滑鼠移動
         * @private
         */
        _onMouseMove(e, el) {
            const state = this.states.get(el);
            if (!state || !state.isResizing) return;

            const { edge, startX, startY, startWidth, startHeight, startLeft, startTop, options } = state;
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;

            let newWidth = startWidth;
            let newHeight = startHeight;
            let newLeft = parseFloat(el.style.left) || startLeft;
            let newTop = parseFloat(el.style.top) || startTop;

            // 根據邊緣計算新尺寸
            switch (edge) {
                case 'right':
                    newWidth = Utils.clamp(startWidth + dx, options.minWidth, options.maxWidth);
                    break;
                case 'corner':
                    newWidth = Utils.clamp(startWidth + dx, options.minWidth, options.maxWidth);
                    newHeight = Utils.clamp(startHeight + dy, options.minHeight, options.maxHeight);
                    break;
                case 'bottom':
                    newHeight = Utils.clamp(startHeight + dy, options.minHeight, options.maxHeight);
                    break;
                case 'left':
                    const widthChangeLeft = -dx;
                    newWidth = Utils.clamp(startWidth + widthChangeLeft, options.minWidth, options.maxWidth);
                    if (newWidth !== startWidth) {
                        newLeft = startLeft - (newWidth - startWidth);
                    }
                    break;
                case 'top':
                    const heightChangeTop = -dy;
                    newHeight = Utils.clamp(startHeight + heightChangeTop, options.minHeight, options.maxHeight);
                    if (newHeight !== startHeight) {
                        newTop = startTop - (newHeight - startHeight);
                    }
                    break;
            }

            // 應用新尺寸
            el.style.width = `${newWidth}px`;
            el.style.height = `${newHeight}px`;

            if (edge === 'left') {
                el.style.left = `${Math.max(10, newLeft)}px`;
            }
            if (edge === 'top') {
                el.style.top = `${Math.max(10, newTop)}px`;
            }

            // 回調
            if (typeof options.onResize === 'function') {
                options.onResize({ width: newWidth, height: newHeight });
            }
        },

        /**
         * 處理滑鼠放開
         * @private
         */
        _onMouseUp(e, el) {
            const state = this.states.get(el);
            if (!state || !state.isResizing) return;

            state.isResizing = false;
            state.edge = null;

            el.style.transition = '';
            el.classList.remove(`${CONFIG.prefix}resizing`);
            document.body.style.cursor = '';
            document.body.style.userSelect = '';

            // 回調
            if (typeof state.options.onResizeEnd === 'function') {
                const rect = el.getBoundingClientRect();
                state.options.onResizeEnd({ width: rect.width, height: rect.height });
            }
        },

        /**
         * 取得邊緣對應的游標樣式
         * @private
         */
        _getCursor(edge) {
            const cursors = {
                right: 'ew-resize',
                left: 'ew-resize',
                top: 'ns-resize',
                bottom: 'ns-resize',
                corner: 'nwse-resize'
            };
            return cursors[edge] || 'default';
        },

        /**
         * 移除縮放功能
         * @param {HTMLElement} el - 目標元素
         */
        disable(el) {
            const state = this.states.get(el);
            if (!state) return;

            // 移除手柄和事件監聽
            Object.values(state.handles).forEach(handle => {
                if (handle._mouseDownHandler) {
                    handle.removeEventListener('mousedown', handle._mouseDownHandler);
                }
                handle.remove();
            });

            this.states.delete(el);

            // 如果沒有任何元素需要縮放,清理全域事件
            if (this.states.size === 0) {
                this._unbindGlobalEvents();
            }
        },

        /**
         * 解除全域事件綁定
         * @private
         */
        _unbindGlobalEvents() {
            if (!this._globalEventsBound) return;

            if (this._globalMouseMoveHandler) {
                document.removeEventListener('mousemove', this._globalMouseMoveHandler);
            }
            if (this._globalMouseUpHandler) {
                document.removeEventListener('mouseup', this._globalMouseUpHandler);
            }

            this._globalMouseMoveHandler = null;
            this._globalMouseUpHandler = null;
            this._globalEventsBound = false;
        }
    };

    // ========================================
    // Toast 通知系統
    // ========================================

    /**
     * Toast 通知管理器
     *
     * 設計意圖:
     * - 提供統一的使用者通知機制
     * - 支援多種類型:info/success/warning/error
     * - 自動消失 + 手動關閉
     * - 堆疊顯示多個通知
     *
     * 使用方式:
     * Toast.success('操作成功');
     * Toast.error('操作失敗', 5000);  // 自定義顯示時間
     * const t = Toast.info('處理中...', 0);  // 不自動消失
     * t.close();  // 手動關閉
     */
    const Toast = {
        /** @type {HTMLElement|null} Toast 容器 */
        container: null,

        /** @type {boolean} 樣式是否已注入 */
        styleInjected: false,

        /**
         * 初始化 Toast 系統(惰性初始化)
         */
        init() {
            if (this.container) return;

            const p = CONFIG.prefix;

            // 注入樣式(只注入一次)
            if (!this.styleInjected) {
                Utils.addStyle(`
                    @keyframes ${p}toast-in {
                        from { transform: translateY(-20px); opacity: 0; }
                        to { transform: translateY(0); opacity: 1; }
                    }
                    @keyframes ${p}toast-out {
                        from { transform: translateY(0); opacity: 1; }
                        to { transform: translateY(-20px); opacity: 0; }
                    }
                    .${p}toast-container {
                        position: fixed;
                        bottom: 20px;
                        left: 20px;
                        z-index: ${CONFIG.zIndex + 1000};
                        display: flex;
                        flex-direction: column-reverse;
                        gap: 8px;
                        pointer-events: none;
                        max-width: 360px;
                    }
                    .${p}toast {
                        display: flex;
                        align-items: flex-start;
                        gap: 8px;
                        padding: 10px 14px;
                        border-radius: 8px;
                        font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                        box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                        pointer-events: auto;
                        animation: ${p}toast-in 0.25s ease;
                        backdrop-filter: blur(8px);
                        -webkit-backdrop-filter: blur(8px);
                    }
                    .${p}toast.${p}out {
                        animation: ${p}toast-out 0.2s ease forwards;
                    }
                    .${p}toast-info {
                        background: rgba(227,242,253,0.95);
                        border-left: 3px solid #2196f3;
                        color: #1565c0;
                    }
                    .${p}toast-success {
                        background: rgba(232,245,233,0.95);
                        border-left: 3px solid #4caf50;
                        color: #2e7d32;
                    }
                    .${p}toast-warning {
                        background: rgba(255,243,224,0.95);
                        border-left: 3px solid #ff9800;
                        color: #e65100;
                    }
                    .${p}toast-error {
                        background: rgba(255,235,238,0.95);
                        border-left: 3px solid #f44336;
                        color: #c62828;
                    }
                    .${p}toast-icon {
                        font-size: 16px;
                        flex-shrink: 0;
                    }
                    .${p}toast-msg {
                        flex: 1;
                        word-break: break-word;
                        font-size: 12px;
                        white-space: pre-wrap;
                    }
                    .${p}toast-close {
                        cursor: pointer;
                        opacity: 0.5;
                        font-size: 14px;
                        padding: 2px;
                        margin: -2px -2px -2px 4px;
                        transition: opacity 0.15s;
                        border: none;
                        background: none;
                        color: inherit;
                        line-height: 1;
                    }
                    .${p}toast-close:hover {
                        opacity: 1;
                    }
                `, `${p}toast-style`);
                this.styleInjected = true;
            }

            // 建立容器
            this.container = document.createElement('div');
            this.container.className = `${p}toast-container`;
            document.body.appendChild(this.container);
        },

        /**
         * 顯示 Toast 通知
         * @param {string} message - 訊息內容
         * @param {string} type - 類型 ('info' | 'success' | 'warning' | 'error')
         * @param {number} duration - 顯示時間 (毫秒),0 表示不自動消失
         * @returns {Object} 包含 close 方法的物件
         */
        show(message, type = 'info', duration = CONFIG.toastDuration) {
            this.init();

            const p = CONFIG.prefix;
            const icons = {
                info: 'ℹ️',
                success: '✅',
                warning: '⚠️',
                error: '❌'
            };

            const toast = document.createElement('div');
            toast.className = `${p}toast ${p}toast-${type}`;
            toast.innerHTML = `
                <span class="${p}toast-icon">${icons[type] || icons.info}</span>
                <span class="${p}toast-msg">${Utils.escapeHtml(message)}</span>
                <button class="${p}toast-close" type="button" aria-label="關閉">✕</button>
            `;

            // 關閉函數
            const close = () => {
                if (toast.classList.contains(`${p}out`)) return;
                toast.classList.add(`${p}out`);
                setTimeout(() => {
                    if (toast.parentNode) {
                        toast.remove();
                    }
                }, 300);
            };

            // 綁定關閉按鈕
            toast.querySelector(`.${p}toast-close`).addEventListener('click', close);

            // 加入容器
            this.container.appendChild(toast);

            // 自動消失
            if (duration > 0) {
                setTimeout(close, duration);
            }

            return { close };
        },

        /**
         * 顯示 info 類型通知
         * @param {string} msg - 訊息
         * @param {number} duration - 顯示時間
         * @returns {Object} 包含 close 方法的物件
         */
        info(msg, duration) {
            return this.show(msg, 'info', duration);
        },

        /**
         * 顯示 success 類型通知
         * @param {string} msg - 訊息
         * @param {number} duration - 顯示時間
         * @returns {Object} 包含 close 方法的物件
         */
        success(msg, duration) {
            return this.show(msg, 'success', duration);
        },

        /**
         * 顯示 warning 類型通知
         * @param {string} msg - 訊息
         * @param {number} duration - 顯示時間
         * @returns {Object} 包含 close 方法的物件
         */
        warning(msg, duration) {
            return this.show(msg, 'warning', duration);
        },

        /**
         * 顯示 error 類型通知
         * @param {string} msg - 訊息
         * @param {number} duration - 顯示時間
         * @returns {Object} 包含 close 方法的物件
         */
        error(msg, duration) {
            return this.show(msg, 'error', duration);
        }
    };

    // ========================================
    // Vditor 診斷系統
    // ========================================

    /**
     * Vditor 診斷管理器
     *
     * 設計意圖:
     * - 追蹤和診斷 Vditor 模式切換時的內容丟失問題
     * - 記錄快照、模式切換、還原等關鍵事件
     * - 提供診斷報告供開發者分析問題
     *
     * 核心策略:
     * - 以 SV 模式的內容作為「真相來源」
     * - 因為 SV 模式是最穩定的,getValue() 返回的內容最可靠
     * - 在切換到其他模式時,保存 SV 快照以便恢復
     *
     * 使用方式:
     * VditorDiag.log('event-type', { data });
     * VditorDiag.printReport();  // 在控制台輸出診斷報告
     */
    const VditorDiag = {
        /** @type {boolean} 是否啟用 */
        enabled: true,

        /** @type {Array} 診斷日誌 */
        logs: [],

        /** @type {number} 最大日誌數 */
        maxLogs: 200,

        /** @type {Object|null} 最後一次 SV 模式的完整快照 */
        lastSVSnapshot: null,

        /**
         * 記錄診斷資訊
         * @param {string} type - 事件類型
         * @param {Object} data - 事件資料
         */
        log(type, data) {
            if (!this.enabled) return;

            const entry = {
                ts: Date.now(),
                type,
                ...data
            };

            this.logs.push(entry);

            // 限制日誌數量
            if (this.logs.length > this.maxLogs) {
                this.logs.shift();
            }

            // 在 DEBUG 模式下輸出到控制台
            if (DEBUG) {
                console.log('[MME Diag]', type, data);
            }
        },

        /**
         * 記錄快照事件
         * @param {string} reason - 快照原因
         * @param {string} mode - 當前模式
         * @param {string} content - 內容
         */
        logSnapshot(reason, mode, content) {
            const len = (content || '').replace(/\s/g, '').length;
            const hash = Utils.hash32(content || '');

            this.log('snapshot', {
                reason,
                mode,
                len,
                hash,
                preview: (content || '').substring(0, 50)
            });

            // 記錄 SV 模式的完整快照
            if (mode === 'sv' && content) {
                this.lastSVSnapshot = {
                    content,
                    len,
                    hash,
                    ts: Date.now()
                };
                this.log('sv-snapshot-saved', { len, hash });
            }
        },

        /**
         * 記錄模式切換事件
         * @param {string} fromMode - 來源模式
         * @param {string} toMode - 目標模式
         * @param {number} beforeLen - 切換前內容長度(去空白)
         * @param {number} afterLen - 切換後內容長度(去空白)
         */
        logModeSwitch(fromMode, toMode, beforeLen, afterLen) {
            const lostRatio = beforeLen > 0 ? (beforeLen - afterLen) / beforeLen : 0;

            this.log('mode-switch', {
                from: fromMode,
                to: toMode,
                beforeLen,
                afterLen,
                lost: beforeLen - afterLen,
                lostRatio: (lostRatio * 100).toFixed(1) + '%',
                suspicious: lostRatio > 0.2
            });

            // 警告:內容異常縮水
            if (lostRatio > 0.2 && beforeLen > 100) {
                console.warn('[MME Diag] 🚨 內容異常縮水!', {
                    from: fromMode,
                    to: toMode,
                    lost: beforeLen - afterLen,
                    ratio: lostRatio
                });
            }
        },

        /**
         * 記錄還原事件
         * @param {string} reason - 還原原因
         * @param {number} restoredLen - 還原後內容長度
         */
        logRestore(reason, restoredLen) {
            this.log('restore', { reason, restoredLen });
        },

        /**
         * 取得診斷報告
         * @returns {Object} 診斷報告
         */
        getReport() {
            const modeSwitches = this.logs.filter(l => l.type === 'mode-switch');
            const snapshots = this.logs.filter(l => l.type === 'snapshot');
            const restores = this.logs.filter(l => l.type === 'restore');
            const modeChanges = this.logs.filter(l => l.type === 'mode-change-detected');
            const clicks = this.logs.filter(l => l.type.startsWith('click-'));

            return {
                totalLogs: this.logs.length,
                modeSwitches: modeSwitches.length,
                modeChangesDetected: modeChanges.length,
                clickEvents: clicks.length,
                snapshots: snapshots.length,
                restores: restores.length,
                suspiciousSwitches: modeSwitches.filter(l => l.suspicious).length,
                lastSVSnapshot: this.lastSVSnapshot ? {
                    len: this.lastSVSnapshot.len,
                    hash: this.lastSVSnapshot.hash,
                    age: Math.round((Date.now() - this.lastSVSnapshot.ts) / 1000) + 's'
                } : null,
                modeDistribution: this._getModeDistribution(),
                recentLogs: this.logs.slice(-30)
            };
        },

        /**
         * 統計各模式的分佈
         * @private
         */
        _getModeDistribution() {
            const modes = { sv: 0, ir: 0, wysiwyg: 0, null: 0 };
            this.logs.forEach(l => {
                if (l.mode !== undefined) {
                    modes[l.mode || 'null']++;
                }
            });
            return modes;
        },

        /**
         * 清除日誌
         */
        clear() {
            this.logs = [];
            this.lastSVSnapshot = null;
        },

        /**
         * 輸出報告到控制台
         */
        printReport() {
            console.group('[MME] Vditor 診斷報告');

            const report = this.getReport();

            console.log('📊 統計摘要:');
            console.table({
                '總日誌數': report.totalLogs,
                '模式切換偵測': report.modeChangesDetected,
                '模式切換完成': report.modeSwitches,
                '點擊事件': report.clickEvents,
                '快照數': report.snapshots,
                '還原次數': report.restores,
                '可疑切換': report.suspiciousSwitches
            });

            console.log('📈 模式分佈:', report.modeDistribution);
            console.log('💾 最後 SV 快照:', report.lastSVSnapshot);

            console.log('📝 最近 30 筆日誌:');
            console.table(report.recentLogs);

            console.log('🔍 完整日誌:', this.logs);

            console.groupEnd();
        }
    };

    // ========================================
    // PerfMonitor 效能監控(僅 DEBUG 模式)
    // ========================================

    /**
     * 效能監控工具
     *
     * 設計意圖:
     * - 提供簡單的效能測量能力
     * - 幫助識別效能瓶頸
     * - 僅在 DEBUG 模式下有實際作用
     *
     * 使用方式:
     * const timer = PerfMonitor.start('operation-name');
     * // ... 執行操作 ...
     * timer.end(); // 會輸出耗時
     */
    const PerfMonitor = {
        /** @type {Object} 計時標記 */
        _marks: {},

        /** @type {Array} 測量記錄 */
        _records: [],

        /** @type {number} 最大記錄數 */
        _maxRecords: 100,

        /**
         * 開始計時
         * @param {string} name - 操作名稱
         * @returns {Object} 包含 end() 方法的物件
         */
        start(name) {
            if (!DEBUG) {
                return { end: () => {} };
            }

            const startTime = performance.now();
            this._marks[name] = startTime;

            return {
                end: () => this.end(name)
            };
        },

        /**
         * 結束計時並記錄
         * @param {string} name - 操作名稱
         * @returns {number} 耗時(毫秒)
         */
        end(name) {
            if (!DEBUG) return 0;

            const startTime = this._marks[name];
            if (!startTime) {
                log(`PerfMonitor: No start mark for "${name}"`);
                return 0;
            }

            const duration = performance.now() - startTime;
            delete this._marks[name];

            // 記錄
            this._records.push({
                name,
                duration,
                timestamp: Date.now()
            });

            // 限制記錄數量
            if (this._records.length > this._maxRecords) {
                this._records.shift();
            }

            // 輸出日誌
            log(`[Perf] ${name}: ${duration.toFixed(2)}ms`);

            return duration;
        },

        /**
         * 測量函數執行時間
         * @param {string} name - 操作名稱
         * @param {Function} fn - 要測量的函數
         * @returns {*} 函數返回值
         */
        measure(name, fn) {
            if (!DEBUG) {
                return fn();
            }

            const timer = this.start(name);
            try {
                const result = fn();
                // 處理 Promise
                if (result && typeof result.then === 'function') {
                    return result.finally(() => timer.end());
                }
                timer.end();
                return result;
            } catch (e) {
                timer.end();
                throw e;
            }
        },

        /**
         * 取得效能報告
         * @returns {Object} 效能報告
         */
        getReport() {
            if (!DEBUG) {
                return { message: 'PerfMonitor only available in DEBUG mode' };
            }

            const grouped = {};
            this._records.forEach(record => {
                if (!grouped[record.name]) {
                    grouped[record.name] = [];
                }
                grouped[record.name].push(record.duration);
            });

            const stats = {};
            Object.entries(grouped).forEach(([name, durations]) => {
                const sum = durations.reduce((a, b) => a + b, 0);
                stats[name] = {
                    count: durations.length,
                    total: sum.toFixed(2) + 'ms',
                    avg: (sum / durations.length).toFixed(2) + 'ms',
                    min: Math.min(...durations).toFixed(2) + 'ms',
                    max: Math.max(...durations).toFixed(2) + 'ms'
                };
            });

            return {
                recordCount: this._records.length,
                operations: stats
            };
        },

        /**
         * 輸出效能報告到控制台
         */
        printReport() {
            if (!DEBUG) {
                console.log('PerfMonitor only available in DEBUG mode');
                return;
            }

            console.group('[MME] 效能報告');
            console.table(this.getReport().operations);
            console.groupEnd();
        },

        /**
         * 清除記錄
         */
        clear() {
            this._marks = {};
            this._records = [];
        }
    };

    // ========================================
    // BackupManager 備份管理器
    // ========================================

    /**
     * 備份管理器
     *
     * 設計意圖:
     * - 提供完整的備份生命週期管理
     * - 使用分層保留策略平衡儲存空間與備份密度
     * - 支援釘選功能保護重要備份
     * - 自動備份與手動備份並行
     *
     * 儲存結構:
     * - mme_backup_index: 所有備份的 metadata 陣列(快速列表)
     * - mme_backup_<id>: 單個備份的實際內容
     *
     * 分層保留策略(CONFIG.backup.retentionTiers):
     * - 1 小時內:每 2 分鐘保留一筆
     * - 24 小時內:每 10 分鐘保留一筆
     * - 7 天內:每天保留一筆
     * - 超過 7 天:自動刪除(除非已釘選)
     */
    const BackupManager = {
        /** @type {number|null} 自動備份計時器 */
        autoTimer: null,

        /** @type {string|null} 上次備份的 hash(用於避免重複備份) */
        lastBackupHash: null,

        /**
         * 取得備份索引
         * @returns {Array} 備份 metadata 陣列
         */
        getIndex() {
            return Utils.storage.get(CONFIG.storageKeys.backupIndex, []);
        },

        /**
         * 儲存備份索引
         * @param {Array} index - 備份 metadata 陣列
         */
        saveIndex(index) {
            Utils.storage.set(CONFIG.storageKeys.backupIndex, index);
        },

        /**
         * 取得備份內容
         * @param {string} id - 備份 ID
         * @returns {string|null} 備份內容
         */
        getBackup(id) {
            return Utils.storage.get(CONFIG.storageKeys.backupPrefix + id, null);
        },

        /**
         * 儲存備份內容
         * @param {string} id - 備份 ID
         * @param {string} content - 內容
         * @returns {boolean} 是否成功
         */
        saveBackup(id, content) {
            const ok = Utils.storage.set(CONFIG.storageKeys.backupPrefix + id, content);
            if (!ok) {
                logWarn('Backup save failed:', id);
            }
            return ok;
        },

        /**
         * 刪除備份內容
         * @param {string} id - 備份 ID
         */
        deleteBackup(id) {
            Utils.storage.remove(CONFIG.storageKeys.backupPrefix + id);
        },

        /**
         * 建立備份
         *
         * 設計意圖:
         * - 自動跳過空內容和無變更的內容
         * - 使用分層保留策略管理備份數量
         * - 支援手動和自動備份兩種模式
         *
         * @param {string} content - 要備份的內容
         * @param {Object} options - 選項
         * @param {string} [options.editorKey] - 編輯器鍵名
         * @param {string} [options.mode] - 編輯器模式(用於 Vditor)
         * @param {boolean} [options.manual=false] - 是否為手動備份
         * @param {boolean} [options.pinned=false] - 是否釘選
         * @returns {Object|null} 備份 metadata,若跳過則返回 null
         */
        create(content, options = {}) {
            // 空內容不備份
            if (!content || !content.trim()) {
                return null;
            }

            const {
                editorKey = null,
                mode = null,
                manual = false,
                pinned = false
            } = options;

            // 檢查備份大小並顯示警告(若啟用)
            this._checkAndWarnBackupSize(content, manual);

            const hash = Utils.hash32(content);

            // 檢查是否與上次備份相同(非手動備份時)
            if (!manual && hash === this.lastBackupHash) {
                log('Backup skipped: same content');
                return null;
            }

            // 檢查最小變更量(非手動備份時)
            const index = this.getIndex();
            if (!manual && index.length > 0) {
                const lastBackup = this.getBackup(index[0].id);
                if (lastBackup) {
                    const lastLen = lastBackup.replace(/\s/g, '').length;
                    const curLen = content.replace(/\s/g, '').length;
                    const diff = Math.abs(curLen - lastLen);
                    if (diff < CONFIG.backup.minChangeChars) {
                        log('Backup skipped: minimal change', { diff });
                        return null;
                    }
                }
            }

            // 建立備份 ID 和 metadata
            const id = Utils.generateId('bk');
            const stats = Utils.countText(content);
            const meta = {
                id,
                ts: Date.now(),
                chars: stats.charsNoSpace,
                lines: stats.lines,
                editorKey,
                mode,
                hash,
                pinned,
                url: location.href,
                title: document.title?.substring(0, 50) || ''
            };

            // 儲存備份內容
            const saveOk = this.saveBackup(id, content);
            if (!saveOk) {
                logWarn('Failed to save backup content');
                return null;
            }

            // 更新索引(新備份插入到開頭)
            index.unshift(meta);
            this.saveIndex(index);
            this.lastBackupHash = hash;

            log('Backup created:', meta);

            // 執行清理(異步,不阻塞)
            setTimeout(() => this.cleanup(), 100);

            return meta;
        },

        /**
         * 檢查備份大小並顯示警告
         *
         * 設計意圖:
         * - 當備份內容較大時提醒使用者
         * - 建議使用匯出功能備份到本機
         * - 使用者可在設定中關閉此警告
         *
         * @param {string} content - 備份內容
         * @param {boolean} manual - 是否為手動備份(手動備份時更明確提示)
         */
        _checkAndWarnBackupSize(content, manual = false) {
            try {
                // 檢查是否啟用警告功能
                const warningEnabled = Utils.storage.get(
                    CONFIG.storageKeys.backupSizeWarningEnabled,
                    true  // 預設啟用
                );

                if (!warningEnabled) {
                    return;
                }

                // 取得警告閾值(預設 1MB)
                const threshold = Utils.storage.get(
                    CONFIG.storageKeys.backupSizeWarningThreshold,
                    1 * 1024 * 1024  // 1 MB
                );

                // 計算內容大小
                const bytes = new Blob([content]).size;

                if (bytes > threshold) {
                    const sizeMB = (bytes / 1024 / 1024).toFixed(2);
                    const thresholdMB = (threshold / 1024 / 1024).toFixed(1);

                    // 根據是否為手動備份調整訊息
                    const message = manual
                        ? `📦 備份內容較大(${sizeMB} MB)\n` +
                          `瀏覽器儲存空間有限,建議使用「匯出」功能\n` +
                          `將重要內容備份到本機。\n\n` +
                          `(此提示可在「偏好設定」中關閉)`
                        : `📦 自動備份內容較大(${sizeMB} MB)\n` +
                          `建議使用「匯出」備份到本機\n` +
                          `(可在設定中關閉此提示)`;

                    Toast.warning(message, manual ? 8000 : 5000);

                    log('Backup size warning:', {
                        bytes,
                        sizeMB,
                        threshold,
                        manual
                    });
                }
            } catch (e) {
                // 大小檢查失敗不應阻擋備份流程
                log('Backup size check error:', e.message);
            }
        },

        /**
         * 取得備份大小警告設定
         * @returns {Object} { enabled: boolean, thresholdMB: number }
         */
        getSizeWarningSettings() {
            return {
                enabled: Utils.storage.get(CONFIG.storageKeys.backupSizeWarningEnabled, true),
                thresholdMB: Utils.storage.get(
                    CONFIG.storageKeys.backupSizeWarningThreshold,
                    1 * 1024 * 1024
                ) / 1024 / 1024
            };
        },

        /**
         * 設定備份大小警告
         * @param {boolean} enabled - 是否啟用
         * @param {number} thresholdMB - 閾值(MB)
         */
        setSizeWarningSettings(enabled, thresholdMB = 1) {
            Utils.storage.set(CONFIG.storageKeys.backupSizeWarningEnabled, enabled);
            Utils.storage.set(
                CONFIG.storageKeys.backupSizeWarningThreshold,
                thresholdMB * 1024 * 1024
            );
            log('Backup size warning settings updated:', { enabled, thresholdMB });
        },

        /**
         * 還原備份
         * @param {string} id - 備份 ID
         * @returns {string|null} 備份內容
         */
        restore(id) {
            const content = this.getBackup(id);
            if (!content) {
                logWarn('Backup not found:', id);
                return null;
            }

            // 更新索引中的訪問時間
            const index = this.getIndex();
            const meta = index.find(m => m.id === id);
            if (meta) {
                meta.lastAccess = Date.now();
                this.saveIndex(index);
            }

            log('Backup restored:', id);
            return content;
        },

        /**
         * 刪除備份
         * @param {string} id - 備份 ID
         */
        delete(id) {
            this.deleteBackup(id);
            const index = this.getIndex().filter(m => m.id !== id);
            this.saveIndex(index);
            log('Backup deleted:', id);
        },

        /**
         * 切換釘選狀態
         * @param {string} id - 備份 ID
         * @returns {boolean} 新的釘選狀態
         */
        togglePin(id) {
            const index = this.getIndex();
            const meta = index.find(m => m.id === id);
            if (meta) {
                meta.pinned = !meta.pinned;
                this.saveIndex(index);
                log('Backup pin toggled:', id, meta.pinned);
                return meta.pinned;
            }
            return false;
        },

        /**
         * 清理舊備份(分層保留策略)
         *
         * 演算法說明:
         * 1. 分離釘選和未釘選的備份
         * 2. 對未釘選的備份,根據時間層級分配到對應的時間桶
         * 3. 同一時間桶內只保留一筆備份
         * 4. 超過所有層級的備份直接丟棄
         * 5. 限制總數不超過 maxBackups
         */
        cleanup() {
            const index = this.getIndex();
            const now = Date.now();
            const tiers = CONFIG.backup.retentionTiers;
            const maxBackups = CONFIG.backup.maxBackups;

            // 分離釘選和未釘選
            const pinned = index.filter(m => m.pinned);
            const unpinned = index.filter(m => !m.pinned);

            // 根據分層策略過濾
            const kept = [];
            const tierBuckets = tiers.map(() => ({})); // 每個層級的時間桶

            for (const meta of unpinned) {
                const age = now - meta.ts;
                let shouldKeep = false;

                // 遍歷各層級,找到適用的層級
                for (let i = 0; i < tiers.length; i++) {
                    const tier = tiers[i];
                    if (age <= tier.age) {
                        // 計算時間桶(同一桶內只保留一筆)
                        const bucket = Math.floor(meta.ts / tier.interval);
                        if (!tierBuckets[i][bucket]) {
                            tierBuckets[i][bucket] = meta;
                            shouldKeep = true;
                        }
                        break;
                    }
                }

                // 如果在某個層級內被保留,加入 kept 陣列
                if (shouldKeep) {
                    kept.push(meta);
                } else {
                    // 檢查是否在最後一個層級內(但可能與同桶的其他備份重複)
                    const lastTier = tiers[tiers.length - 1];
                    if (age <= lastTier.age) {
                        const bucket = Math.floor(meta.ts / lastTier.interval);
                        const lastIdx = tiers.length - 1;
                        if (!tierBuckets[lastIdx][bucket]) {
                            tierBuckets[lastIdx][bucket] = meta;
                            kept.push(meta);
                        }
                    }
                    // 超過最後一個層級的備份會被丟棄
                }
            }

            // 限制未釘選備份的最大數量
            let finalUnpinned = kept;
            const maxUnpinned = maxBackups - pinned.length;
            if (finalUnpinned.length > maxUnpinned) {
                finalUnpinned = finalUnpinned.slice(0, Math.max(0, maxUnpinned));
            }

            // 找出需要刪除的備份
            const keptIds = new Set([...pinned, ...finalUnpinned].map(m => m.id));
            let deletedCount = 0;
            for (const meta of unpinned) {
                if (!keptIds.has(meta.id)) {
                    this.deleteBackup(meta.id);
                    deletedCount++;
                }
            }

            if (deletedCount > 0) {
                log('Backup cleanup: deleted', deletedCount, 'old backups');
            }

            // 更新索引(按時間排序:新的在前)
            const newIndex = [...pinned, ...finalUnpinned].sort((a, b) => b.ts - a.ts);
            this.saveIndex(newIndex);
        },

        /**
         * 清除所有備份
         */
        clearAll() {
            const index = this.getIndex();
            for (const meta of index) {
                this.deleteBackup(meta.id);
            }
            this.saveIndex([]);
            this.lastBackupHash = null;
            log('All backups cleared');
        },

        /**
         * 開始自動備份
         */
        startAuto() {
            this.stopAuto();

            this.autoTimer = setInterval(() => {
                // 這裡只是觸發備份,實際的 EditorManager 引用在 Modal 中處理
                // 為了避免循環依賴,我們使用事件或回調機制
                if (typeof this._autoBackupCallback === 'function') {
                    this._autoBackupCallback();
                }
            }, CONFIG.backup.autoInterval);

            log('Auto backup started, interval:', CONFIG.backup.autoInterval);
        },

        /**
         * 停止自動備份
         */
        stopAuto() {
            if (this.autoTimer) {
                clearInterval(this.autoTimer);
                this.autoTimer = null;
                log('Auto backup stopped');
            }
        },

        /**
         * 設定自動備份回調
         * @param {Function} callback - 回調函數
         */
        setAutoBackupCallback(callback) {
            this._autoBackupCallback = callback;
        },

        /**
         * 取得統計資訊
         * @returns {Object} 統計資訊
         */
        getStats() {
            const index = this.getIndex();
            return {
                total: index.length,
                pinned: index.filter(m => m.pinned).length,
                oldest: index.length > 0 ? index[index.length - 1].ts : null,
                newest: index.length > 0 ? index[0].ts : null
            };
        },

        // ========================================
        // 匯出/匯入功能(傳統 fallback)
        // ========================================

        /**
         * 匯出所有備份為 JSON
         * @returns {string} JSON 字串
         */
        exportAllBackupsAsJson() {
            const index = this.getIndex();
            const data = {
                version: SCRIPT_VERSION,
                exportedAt: Date.now(),
                source: 'Multi Markdown Editor',
                backupCount: 0,
                backups: []
            };

            for (const meta of index) {
                const content = this.getBackup(meta.id);
                if (content) {
                    data.backups.push({
                        meta: { ...meta },
                        content
                    });
                    data.backupCount++;
                }
            }

            log('BackupManager: Exported', data.backupCount, 'backups');
            return JSON.stringify(data, null, 2);
        },

        /**
         * 從 JSON 匯入備份
         * @param {string} jsonString - JSON 字串
         * @param {Object} options - 選項
         * @returns {Object} { success, imported, skipped, message }
         */
        importBackupsFromJson(jsonString, options = {}) {
            const {
                skipDuplicates = true,  // 跳過重複的備份(根據 hash)
                preserveTimestamp = true // 保留原始時間戳
            } = options;

            try {
                const data = JSON.parse(jsonString);

                // 驗證格式
                if (!data || typeof data !== 'object') {
                    return { success: false, imported: 0, skipped: 0, message: '無效的 JSON 格式' };
                }

                if (!data.backups || !Array.isArray(data.backups)) {
                    return { success: false, imported: 0, skipped: 0, message: '找不到備份資料' };
                }

                // 取得現有備份的 hash 集合
                const existingHashes = new Set();
                if (skipDuplicates) {
                    const existingIndex = this.getIndex();
                    existingIndex.forEach(m => {
                        if (m.hash) existingHashes.add(m.hash);
                    });
                }

                let imported = 0;
                let skipped = 0;

                for (const item of data.backups) {
                    if (!item.content || typeof item.content !== 'string') {
                        skipped++;
                        continue;
                    }

                    // 檢查重複
                    const hash = Utils.hash32(item.content);
                    if (skipDuplicates && existingHashes.has(hash)) {
                        skipped++;
                        continue;
                    }

                    // 建立新備份
                    const stats = Utils.countText(item.content);
                    const id = Utils.generateId('bk');

                    const meta = {
                        id,
                        ts: preserveTimestamp && item.meta?.ts ? item.meta.ts : Date.now(),
                        chars: stats.charsNoSpace,
                        lines: stats.lines,
                        editorKey: item.meta?.editorKey || null,
                        mode: item.meta?.mode || null,
                        hash,
                        pinned: item.meta?.pinned || false,
                        url: item.meta?.url || '',
                        title: item.meta?.title || '',
                        importedAt: Date.now()
                    };

                    // 儲存
                    const contentOk = this.saveBackup(id, item.content);
                    if (contentOk) {
                        const index = this.getIndex();
                        index.push(meta);
                        this.saveIndex(index.sort((a, b) => b.ts - a.ts));
                        existingHashes.add(hash);
                        imported++;
                    } else {
                        skipped++;
                    }
                }

                log('BackupManager: Import completed', { imported, skipped });

                // 執行清理
                setTimeout(() => this.cleanup(), 100);

                let message = `已匯入 ${imported} 筆備份`;
                if (skipped > 0) {
                    message += `(跳過 ${skipped} 筆)`;
                }

                return { success: true, imported, skipped, message };

            } catch (e) {
                logError('BackupManager: Import error:', e);
                return { success: false, imported: 0, skipped: 0, message: '無法解析備份檔案:' + e.message };
            }
        },

        /**
         * 下載所有備份為 JSON 檔案
         * @returns {boolean}
         */
        downloadAllBackups() {
            const json = this.exportAllBackupsAsJson();
            const date = Utils.formatDate();
            const filename = `mme_backups_${date}.json`;

            return Utils.downloadFile(json, filename, 'application/json;charset=utf-8');
        },

        /**
         * 從檔案匯入備份(觸發檔案選擇器)
         * @returns {Promise<Object>} 匯入結果
         */
        async importBackupsFromFile() {
            return new Promise((resolve) => {
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = '.json';
                input.style.display = 'none';

                input.onchange = async (e) => {
                    const file = e.target.files?.[0];
                    if (!file) {
                        resolve({ success: false, message: '未選擇檔案' });
                        input.remove();
                        return;
                    }

                    try {
                        const text = await Utils.readFile(file);
                        const result = this.importBackupsFromJson(text);
                        resolve(result);
                    } catch (err) {
                        resolve({ success: false, message: '檔案讀取失敗' });
                    }

                    input.remove();
                };

                input.oncancel = () => {
                    resolve({ success: false, message: '已取消' });
                    input.remove();
                };

                document.body.appendChild(input);
                input.click();
            });
        }
    };

    // ========================================
    // QuickSlots 快速插槽系統
    // ========================================

    /**
     * 快速存檔插槽管理器
     *
     * 設計意圖:
     * - 提供 0-9 組可配置的快速存檔插槽
     * - 資料儲存於硬碟(localStorage/GM storage),避免資料遺失
     * - 使用者可在偏好設定中自訂啟用數量和顯示位置
     * - 插槽是「使用者主動管理的文件」,與備份(系統自動保護)獨立
     *
     * 儲存結構:
     * - mme_slot_<n>: 第 n 個插槽的內容
     * - mme_slot_meta_<n>: 第 n 個插槽的 metadata(時間、字數、標籤等)
     * - mme_slot_settings: 插槽系統設定(啟用數量、顯示位置等)
     */
    const QuickSlots = {
        /** @type {number} 最大插槽數量 */
        MAX_SLOTS: 9,

        /** @type {number} 預設啟用插槽數量 */
        DEFAULT_ENABLED_COUNT: 5,

        /** @type {number} 單一插槽最大容量(位元組),超過時警告 */
        MAX_SLOT_SIZE: 2 * 1024 * 1024, // 2 MB

        /**
         * 取得插槽設定
         * @returns {Object} 設定物件
         */
        getSettings() {
            const defaults = {
                enabledCount: this.DEFAULT_ENABLED_COUNT,  // 啟用的插槽數量 (0-9)
                showInToolbar: true,                       // 是否在工具列顯示按鈕
                confirmBeforeOverwrite: true,              // 覆蓋前是否確認
                confirmBeforeLoad: true,                   // 載入前是否確認(當前有內容時)
                autoBackupBeforeLoad: true,                // 載入前是否自動備份當前內容

                // 迷你插槽列(工具列快速按鈕)
                // 預設策略(建議):
                // - enabledCount <= 5:預設顯示(不太擁擠,符合「快速」)
                // - enabledCount > 5:預設不顯示(避免工具列太滿)
                showMiniBar: true,
                miniBarCount: 5,
            };

            const saved = Utils.storage.get(CONFIG.storageKeys.slotSettings, null);
            if (!saved) {
                // 根據 enabledCount 做智慧預設
                defaults.showMiniBar = defaults.enabledCount <= 5;
                defaults.miniBarCount = Math.min(defaults.enabledCount, 5);
                return defaults;
            }

            const merged = { ...defaults, ...saved };

            // 向後相容:舊版沒有 showMiniBar/miniBarCount 時,給智慧預設
            if (typeof merged.showMiniBar !== 'boolean') {
                merged.showMiniBar = (merged.enabledCount || 0) <= 5;
            }
            if (!Number.isFinite(merged.miniBarCount)) {
                merged.miniBarCount = Math.min(merged.enabledCount || 0, 5) || 5;
            }

            return merged;
        },

        /**
         * 儲存插槽設定
         * @param {Object} settings - 設定物件
         */
        saveSettings(settings) {
            const current = this.getSettings();
            const merged = { ...current, ...settings };
            Utils.storage.set(CONFIG.storageKeys.slotSettings, merged);
            log('QuickSlots: Settings saved', merged);
        },

        /**
         * 驗證插槽編號
         * @param {number} slot - 插槽編號
         * @returns {boolean} 是否有效
         */
        _isValidSlot(slot) {
            return Number.isInteger(slot) && slot >= 1 && slot <= this.MAX_SLOTS;
        },

        /**
         * 檢查插槽是否在啟用範圍內
         * @param {number} slot - 插槽編號
         * @returns {boolean}
         */
        isSlotEnabled(slot) {
            if (!this._isValidSlot(slot)) return false;
            const settings = this.getSettings();
            return slot <= settings.enabledCount;
        },

        /**
         * 儲存內容到指定插槽
         * @param {number} slot - 插槽編號 (1-9)
         * @param {string} content - 內容
         * @param {Object} options - 選項
         * @returns {Object} 結果 { success, message, meta }
         */
        saveToSlot(slot, content, options = {}) {
            const {
                label = null,           // 自訂標籤
                skipSizeCheck = false,  // 跳過大小檢查
                editorKey = null        // 編輯器鍵名(由呼叫者傳入,避免過早引用 EditorManager)
            } = options;

            // 驗證插槽編號
            if (!this._isValidSlot(slot)) {
                logWarn('QuickSlots: Invalid slot number:', slot);
                return { success: false, message: '無效的插槽編號' };
            }

            // 驗證內容
            if (content === null || content === undefined) {
                return { success: false, message: '內容不可為空' };
            }

            // 檢查大小
            if (!skipSizeCheck) {
                try {
                    const bytes = new Blob([content]).size;
                    if (bytes > this.MAX_SLOT_SIZE) {
                        const sizeMB = (bytes / 1024 / 1024).toFixed(2);
                        return {
                            success: false,
                            message: `內容過大(${sizeMB} MB),超過單一插槽限制(2 MB)`
                        };
                    }
                } catch (e) {
                    // 無法計算大小,繼續儲存
                }
            }

            const key = CONFIG.storageKeys.slotPrefix + slot;
            const metaKey = CONFIG.storageKeys.slotMetaPrefix + slot;

            // 建立 metadata
            const stats = Utils.countText(content);
            // 解耦:editorKey 僅使用呼叫者傳入值
            // 設計理由:QuickSlots 不應依賴 EditorManager(避免循環依賴與初始化時序問題)
            const resolvedEditorKey = editorKey || null;

            const meta = {
                ts: Date.now(),
                chars: stats.charsNoSpace,
                lines: stats.lines,
                words: stats.words,
                hash: Utils.hash32(content),
                editorKey: resolvedEditorKey,
                label: label || this.getSlotLabel(slot) || null,
                size: content.length
            };

            // 儲存內容和 metadata
            const contentOk = Utils.storage.set(key, content);
            const metaOk = Utils.storage.set(metaKey, meta);

            if (contentOk && metaOk) {
                log('QuickSlots: Saved to slot', slot, meta);
                return { success: true, message: '儲存成功', meta };
            }

            logWarn('QuickSlots: Failed to save to slot', slot);
            return { success: false, message: '儲存失敗,可能是儲存空間不足' };
        },

        /**
         * 從指定插槽載入內容
         * @param {number} slot - 插槽編號 (1-9)
         * @returns {Object} 結果 { success, content, meta, message }
         */
        loadFromSlot(slot) {
            if (!this._isValidSlot(slot)) {
                return { success: false, content: null, meta: null, message: '無效的插槽編號' };
            }

            const key = CONFIG.storageKeys.slotPrefix + slot;
            const content = Utils.storage.get(key, null);

            if (content === null) {
                return { success: false, content: null, meta: null, message: '此插槽為空' };
            }

            const meta = this.getSlotMeta(slot);

            // 更新最後存取時間
            if (meta) {
                meta.lastAccess = Date.now();
                Utils.storage.set(CONFIG.storageKeys.slotMetaPrefix + slot, meta);
            }

            log('QuickSlots: Loaded from slot', slot);
            return { success: true, content, meta, message: '載入成功' };
        },

        /**
         * 取得指定插槽的 metadata
         * @param {number} slot - 插槽編號 (1-9)
         * @returns {Object|null} metadata
         */
        getSlotMeta(slot) {
            if (!this._isValidSlot(slot)) return null;

            const metaKey = CONFIG.storageKeys.slotMetaPrefix + slot;
            return Utils.storage.get(metaKey, null);
        },

        /**
         * 取得插槽標籤
         * @param {number} slot - 插槽編號
         * @returns {string|null}
         */
        getSlotLabel(slot) {
            const meta = this.getSlotMeta(slot);
            return meta?.label || null;
        },

        /**
         * 設定插槽標籤
         * @param {number} slot - 插槽編號
         * @param {string} label - 標籤(空字串表示清除)
         * @returns {boolean} 是否成功
         */
        setSlotLabel(slot, label) {
            if (!this._isValidSlot(slot)) return false;

            const meta = this.getSlotMeta(slot);
            if (!meta) return false; // 插槽為空,無法設定標籤

            meta.label = label || null;
            Utils.storage.set(CONFIG.storageKeys.slotMetaPrefix + slot, meta);
            log('QuickSlots: Set label for slot', slot, label);
            return true;
        },

        /**
         * 清空指定插槽
         * @param {number} slot - 插槽編號 (1-9)
         * @returns {boolean} 是否成功
         */
        clearSlot(slot) {
            if (!this._isValidSlot(slot)) return false;

            Utils.storage.remove(CONFIG.storageKeys.slotPrefix + slot);
            Utils.storage.remove(CONFIG.storageKeys.slotMetaPrefix + slot);
            log('QuickSlots: Cleared slot', slot);
            return true;
        },

        /**
         * 清空所有插槽
         * @returns {number} 清空的插槽數量
         */
        clearAllSlots() {
            let count = 0;
            for (let i = 1; i <= this.MAX_SLOTS; i++) {
                if (this.getSlotMeta(i)) {
                    this.clearSlot(i);
                    count++;
                }
            }
            log('QuickSlots: Cleared all slots, count:', count);
            return count;
        },

        /**
         * 取得所有插槽狀態
         * @param {boolean} onlyEnabled - 是否只返回已啟用的插槽
         * @returns {Array} 插槽狀態陣列
         */
        getAllSlotStatus(onlyEnabled = true) {
            const settings = this.getSettings();
            const maxSlot = onlyEnabled ? settings.enabledCount : this.MAX_SLOTS;
            const slots = [];

            for (let i = 1; i <= maxSlot; i++) {
                const meta = this.getSlotMeta(i);
                slots.push({
                    slot: i,
                    isEmpty: !meta,
                    isEnabled: i <= settings.enabledCount,
                    meta: meta
                });
            }

            return slots;
        },

        /**
         * 檢查插槽是否為空
         * @param {number} slot - 插槽編號
         * @returns {boolean}
         */
        isSlotEmpty(slot) {
            return !this.getSlotMeta(slot);
        },

        /**
         * 取得已使用的插槽數量
         * @returns {number}
         */
        getUsedCount() {
            let count = 0;
            const settings = this.getSettings();

            for (let i = 1; i <= settings.enabledCount; i++) {
                if (this.getSlotMeta(i)) count++;
            }

            return count;
        },

        /**
         * 取得下一個空插槽編號
         * @returns {number|null} 空插槽編號,若無則返回 null
         */
        getNextEmptySlot() {
            const settings = this.getSettings();

            for (let i = 1; i <= settings.enabledCount; i++) {
                if (!this.getSlotMeta(i)) return i;
            }

            return null;
        },

        /**
         * 取得最舊的插槽編號(用於自動覆蓋)
         * @returns {number|null} 最舊插槽編號,若全空則返回 null
         */
        getOldestSlot() {
            const settings = this.getSettings();
            let oldest = null;
            let oldestTs = Infinity;

            for (let i = 1; i <= settings.enabledCount; i++) {
                const meta = this.getSlotMeta(i);
                if (meta && meta.ts < oldestTs) {
                    oldestTs = meta.ts;
                    oldest = i;
                }
            }

            return oldest;
        },

        /**
         * 取得最近使用的插槽編號
         * @returns {number|null}
         */
        getMostRecentSlot() {
            const settings = this.getSettings();
            let recent = null;
            let recentTs = 0;

            for (let i = 1; i <= settings.enabledCount; i++) {
                const meta = this.getSlotMeta(i);
                if (meta) {
                    const ts = meta.lastAccess || meta.ts;
                    if (ts > recentTs) {
                        recentTs = ts;
                        recent = i;
                    }
                }
            }

            return recent;
        },

        /**
         * 匯出所有插槽為 JSON
         * @returns {Object} 匯出資料
         */
        exportAllSlots() {
            const settings = this.getSettings();
            const slots = {};

            for (let i = 1; i <= this.MAX_SLOTS; i++) {
                const content = Utils.storage.get(CONFIG.storageKeys.slotPrefix + i, null);
                const meta = this.getSlotMeta(i);

                if (content !== null) {
                    slots[i] = { content, meta };
                }
            }

            return {
                version: SCRIPT_VERSION,
                exportedAt: Date.now(),
                settings,
                slots
            };
        },

        /**
         * 從 JSON 匯入插槽
         * @param {Object} data - 匯入資料
         * @param {Object} options - 選項
         * @returns {Object} 結果 { success, imported, skipped, message }
         */
        importSlots(data, options = {}) {
            const {
                overwrite = false,          // 是否覆蓋非空插槽
                importSettings = false      // 是否匯入設定
            } = options;

            if (!data || typeof data !== 'object') {
                return { success: false, imported: 0, skipped: 0, message: '無效的匯入資料' };
            }

            let imported = 0;
            let skipped = 0;

            // 匯入設定
            if (importSettings && data.settings) {
                this.saveSettings(data.settings);
            }

            // 匯入插槽
            if (data.slots && typeof data.slots === 'object') {
                for (const [slotStr, slotData] of Object.entries(data.slots)) {
                    const slot = parseInt(slotStr);
                    if (!this._isValidSlot(slot)) continue;

                    const exists = !this.isSlotEmpty(slot);
                    if (exists && !overwrite) {
                        skipped++;
                        continue;
                    }

                    if (slotData.content !== undefined) {
                        const result = this.saveToSlot(slot, slotData.content, {
                            label: slotData.meta?.label,
                            skipSizeCheck: true,
                            editorKey: slotData.meta?.editorKey || null
                        });

                        if (result.success) {
                            imported++;
                        } else {
                            skipped++;
                        }
                    }
                }
            }

            log('QuickSlots: Import completed', { imported, skipped });
            return {
                success: true,
                imported,
                skipped,
                message: `已匯入 ${imported} 個插槽${skipped > 0 ? `,跳過 ${skipped} 個` : ''}`
            };
        },

        /**
         * 取得插槽系統統計資訊
         * @returns {Object}
         */
        getStats() {
            const settings = this.getSettings();
            const allStatus = this.getAllSlotStatus(false);

            const used = allStatus.filter(s => !s.isEmpty && s.isEnabled).length;
            const total = settings.enabledCount;
            const totalChars = allStatus
                .filter(s => !s.isEmpty)
                .reduce((sum, s) => sum + (s.meta?.chars || 0), 0);

            return {
                used,
                total,
                available: total - used,
                totalChars,
                oldestSlot: this.getOldestSlot(),
                newestSlot: this.getMostRecentSlot()
            };
        }
    };

    // ========================================
    // DragDropManager 拖曳導入管理器
    // ========================================

    /**
     * 拖曳導入管理器
     *
     * 設計意圖:
     * - 支援將文字或檔案拖曳到 FAB 或 Modal 進行導入
     * - 拖曳導入前自動備份當前內容(若有)
     * - 首次使用時顯示溫和的提示(10秒後消失,最多顯示2次)
     * - 提供清晰的視覺回饋(覆蓋層、高亮效果)
     *
     * 支援的檔案類型:
     * - .md, .txt, .markdown, .mdown, .mkd, .mkdn, .mdwn, .mdtxt, .mdtext, .text
     *
     * 事件處理策略:
     * - 使用 dragenter/dragleave 計數器處理巢狀元素問題
     * - FAB 和 Modal 各自處理拖曳事件,共用核心邏輯
     * - 全域監聽用於顯示/隱藏覆蓋層
     */
    const DragDropManager = {
        /** @type {boolean} 是否已初始化 */
        _initialized: false,

        /** @type {HTMLElement|null} 拖曳提示覆蓋層 */
        _dropOverlay: null,

        /** @type {boolean} 覆蓋層是否正在顯示 */
        _overlayVisible: false,

        /** @type {number} dragenter 計數器(處理巢狀元素) */
        _dragEnterCount: 0,

        /** @type {boolean} FAB 是否正在高亮 */
        _fabHighlighted: false,

        /** @type {boolean} Modal 是否正在高亮 */
        _modalHighlighted: false,

        /** @type {Array<Function>} 事件清理函數列表 */
        _cleanupFns: [],

        /** @type {Array<string>} 支援的檔案副檔名 */
        SUPPORTED_EXTENSIONS: [
            '.md', '.txt', '.markdown', '.mdown', '.mkd',
            '.mkdn', '.mdwn', '.mdtxt', '.mdtext', '.text'
        ],

        /** @type {number} 最大檔案大小(5MB) */
        MAX_FILE_SIZE: 5 * 1024 * 1024,

        /** @type {number} 提示最大顯示次數 */
        MAX_HINT_COUNT: 2,

        /** @type {number} 提示延遲顯示時間(毫秒) */
        HINT_DELAY: 5000,

        /** @type {number} 提示顯示時長(毫秒) */
        HINT_DURATION: 10000,

        // ========================================
        // 初始化與清理
        // ========================================

        /**
         * 初始化拖曳導入功能
         */
        init() {
            if (this._initialized) return;
            this._initialized = true;

            log('DragDropManager: Initializing...');

            // 建立覆蓋層
            this._createDropOverlay();

            // 綁定全域拖曳事件(用於顯示覆蓋層)
            this._bindGlobalDragEvents();

            // 綁定 FAB 拖曳事件
            this._bindFABDragEvents();

            // 注意:Modal 拖曳事件在 Modal 初始化後綁定
            // 這裡提供一個方法供 Modal 調用

            log('DragDropManager: Initialized');
        },

        /**
         * 綁定 Modal 拖曳事件(由 Modal 在初始化後調用)
         *
         * 設計意圖:
         * - 為 Modal 視窗啟用拖曳導入功能
         * - 需在 Modal DOM 創建後調用
         *
         * @param {HTMLElement} modalElement - Modal 元素
         */
        bindModalDragEvents(modalElement) {
            if (!modalElement) {
                logWarn('DragDropManager.bindModalDragEvents: modalElement is null, binding skipped');
                return;
            }

            log('DragDropManager: Binding Modal drag events');

            const onDragEnter = (e) => {
                e.preventDefault();
                e.stopPropagation();
                this._setModalHighlight(true);
            };

            const onDragOver = (e) => {
                e.preventDefault();
                e.stopPropagation();
                // 設置拖曳效果
                if (e.dataTransfer) {
                    e.dataTransfer.dropEffect = 'copy';
                }
            };

            const onDragLeave = (e) => {
                e.preventDefault();
                e.stopPropagation();

                // 檢查是否真的離開了 Modal
                const rect = modalElement.getBoundingClientRect();
                const x = e.clientX;
                const y = e.clientY;

                if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
                    this._setModalHighlight(false);
                }
            };

            const onDrop = (e) => {
                e.preventDefault();
                e.stopPropagation();
                this._setModalHighlight(false);
                this._hideDropOverlay();
                this.handleDrop(e);
            };

            modalElement.addEventListener('dragenter', onDragEnter);
            modalElement.addEventListener('dragover', onDragOver);
            modalElement.addEventListener('dragleave', onDragLeave);
            modalElement.addEventListener('drop', onDrop);

            // 儲存清理函數
            this._cleanupFns.push(() => {
                modalElement.removeEventListener('dragenter', onDragEnter);
                modalElement.removeEventListener('dragover', onDragOver);
                modalElement.removeEventListener('dragleave', onDragLeave);
                modalElement.removeEventListener('drop', onDrop);
            });
        },

        /**
         * 銷毀拖曳導入功能
         */
        destroy() {
            log('DragDropManager: Destroying...');

            // 執行所有清理函數
            this._cleanupFns.forEach(fn => {
                try {
                    fn();
                } catch (e) {
                    // 忽略清理錯誤
                }
            });
            this._cleanupFns = [];

            // 移除覆蓋層
            if (this._dropOverlay && this._dropOverlay.parentNode) {
                this._dropOverlay.parentNode.removeChild(this._dropOverlay);
            }
            this._dropOverlay = null;

            this._initialized = false;
            this._dragEnterCount = 0;
            this._overlayVisible = false;
            this._fabHighlighted = false;
            this._modalHighlighted = false;

            log('DragDropManager: Destroyed');
        },

        // ========================================
        // 覆蓋層管理
        // ========================================

        /**
         * 建立拖曳覆蓋層
         */
        _createDropOverlay() {
            if (this._dropOverlay) return;

            const p = CONFIG.prefix;

            this._dropOverlay = document.createElement('div');
            this._dropOverlay.className = `${p}drop-overlay`;
            this._dropOverlay.innerHTML = `
                <div class="${p}drop-hint">
                    <div class="${p}drop-icon">
                        ${Icons.import}
                    </div>
                    <div class="${p}drop-title">拖曳到目標位置</div>
                    <div class="${p}drop-subtitle">
                        請將檔案拖曳到 <b>右下角按鈕</b> 或 <b>編輯器視窗</b> 上<br>
                        支援 .md、.txt、.markdown 等格式
                    </div>
                </div>
            `;

            document.body.appendChild(this._dropOverlay);

            // 覆蓋層上的 dragover 事件
            this._dropOverlay.addEventListener('dragover', (e) => {
                e.preventDefault();
                if (e.dataTransfer) {
                    // 顯示為「不可放置」,引導使用者拖到正確位置
                    e.dataTransfer.dropEffect = 'none';
                }
            });

            // 覆蓋層上的 drop 事件 - 只取消操作,不導入
            this._dropOverlay.addEventListener('drop', (e) => {
                e.preventDefault();
                e.stopPropagation();

                // 隱藏覆蓋層,但不執行導入
                this._hideDropOverlay();
                this._setFABHighlight(false);
                this._setModalHighlight(false);

                // 提示使用者正確的操作方式
                Toast.info('請將檔案拖曳到右下角按鈕或編輯器視窗上');
            });

            // 點擊覆蓋層關閉
            this._dropOverlay.addEventListener('click', () => {
                this._hideDropOverlay();
            });
        },

        /**
         * 顯示拖曳覆蓋層
         */
        _showDropOverlay() {
            if (!this._dropOverlay || this._overlayVisible) return;

            const p = CONFIG.prefix;
            this._dropOverlay.classList.add(`${p}active`);
            this._overlayVisible = true;
        },

        /**
         * 隱藏拖曳覆蓋層
         */
        _hideDropOverlay() {
            if (!this._dropOverlay || !this._overlayVisible) return;

            const p = CONFIG.prefix;
            this._dropOverlay.classList.remove(`${p}active`);
            this._overlayVisible = false;
            this._dragEnterCount = 0;
        },

        // ========================================
        // FAB 拖曳處理
        // ========================================

        /**
         * 綁定 FAB 拖曳事件
         */
        _bindFABDragEvents() {
            // FAB 可能尚未建立,延遲綁定(設定最大重試次數避免無限輪詢)
            let retryCount = 0;
            const maxRetries = 20; // 最多重試 20 次(約 10 秒)

            const bindWhenReady = () => {
                const fab = FAB?.el;
                if (!fab) {
                    retryCount++;
                    if (retryCount < maxRetries) {
                        setTimeout(bindWhenReady, 500);
                    } else {
                        log('DragDropManager: FAB not found after', maxRetries, 'retries, giving up');
                    }
                    return;
                }

                log('DragDropManager: Binding FAB drag events');

                const onDragEnter = (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    this._setFABHighlight(true);
                };

                const onDragOver = (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    if (e.dataTransfer) {
                        e.dataTransfer.dropEffect = 'copy';
                    }
                };

                const onDragLeave = (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    this._setFABHighlight(false);
                };

                const onDrop = (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    this._setFABHighlight(false);
                    this._hideDropOverlay();
                    this.handleDrop(e);
                };

                fab.addEventListener('dragenter', onDragEnter);
                fab.addEventListener('dragover', onDragOver);
                fab.addEventListener('dragleave', onDragLeave);
                fab.addEventListener('drop', onDrop);

                // 儲存清理函數
                this._cleanupFns.push(() => {
                    fab.removeEventListener('dragenter', onDragEnter);
                    fab.removeEventListener('dragover', onDragOver);
                    fab.removeEventListener('dragleave', onDragLeave);
                    fab.removeEventListener('drop', onDrop);
                });
            };

            bindWhenReady();
        },

        /**
         * 設置 FAB 高亮狀態
         * @param {boolean} highlighted
         */
        _setFABHighlight(highlighted) {
            const fab = FAB?.el;
            if (!fab) return;

            const p = CONFIG.prefix;

            if (highlighted && !this._fabHighlighted) {
                fab.classList.add(`${p}drag-over`);
                this._fabHighlighted = true;
            } else if (!highlighted && this._fabHighlighted) {
                fab.classList.remove(`${p}drag-over`);
                this._fabHighlighted = false;
            }
        },

        /**
         * 設置 Modal 高亮狀態
         * @param {boolean} highlighted
         */
        _setModalHighlight(highlighted) {
            const modal = Modal?.modal;
            if (!modal) return;

            const p = CONFIG.prefix;

            if (highlighted && !this._modalHighlighted) {
                modal.classList.add(`${p}drag-over`);
                this._modalHighlighted = true;
            } else if (!highlighted && this._modalHighlighted) {
                modal.classList.remove(`${p}drag-over`);
                this._modalHighlighted = false;
            }
        },

        // ========================================
        // 全域拖曳監聽
        // ========================================

        /**
         * 綁定全域拖曳事件
         * 用於在拖曳進入頁面時顯示覆蓋層
         */
        _bindGlobalDragEvents() {
            const onDragEnter = (e) => {
                // 只有當拖曳包含我們關心的資料類型時才顯示覆蓋層
                if (!this._hasSupportedData(e)) return;

                this._dragEnterCount++;

                if (this._dragEnterCount === 1) {
                    this._showDropOverlay();
                }
            };

            const onDragLeave = (e) => {
                this._dragEnterCount--;

                if (this._dragEnterCount <= 0) {
                    this._dragEnterCount = 0;
                    this._hideDropOverlay();
                    this._setFABHighlight(false);
                    this._setModalHighlight(false);
                }
            };

            const onDragOver = (e) => {
                // 必須 preventDefault 才能接收 drop
                if (this._hasSupportedData(e)) {
                    e.preventDefault();
                }
            };

            const onDrop = (e) => {
                // 重置狀態
                this._dragEnterCount = 0;
                this._hideDropOverlay();
                this._setFABHighlight(false);
                this._setModalHighlight(false);

                // 注意:不在這裡處理 drop,讓它冒泡到具體的目標元素
            };

            const onDragEnd = () => {
                // 拖曳結束(例如按 Esc 取消)
                this._dragEnterCount = 0;
                this._hideDropOverlay();
                this._setFABHighlight(false);
                this._setModalHighlight(false);
            };

            document.addEventListener('dragenter', onDragEnter);
            document.addEventListener('dragleave', onDragLeave);
            document.addEventListener('dragover', onDragOver);
            document.addEventListener('drop', onDrop);
            document.addEventListener('dragend', onDragEnd);

            // 儲存清理函數
            this._cleanupFns.push(() => {
                document.removeEventListener('dragenter', onDragEnter);
                document.removeEventListener('dragleave', onDragLeave);
                document.removeEventListener('dragover', onDragOver);
                document.removeEventListener('drop', onDrop);
                document.removeEventListener('dragend', onDragEnd);
            });
        },

        /**
         * 檢查拖曳事件是否包含我們支援的資料類型
         * @param {DragEvent} e
         * @returns {boolean}
         */
        _hasSupportedData(e) {
            const dt = e.dataTransfer;
            if (!dt) return false;

            // 檢查是否有檔案
            if (dt.types.includes('Files')) {
                return true;
            }

            // 檢查是否有文字
            if (dt.types.includes('text/plain') || dt.types.includes('text/html')) {
                return true;
            }

            return false;
        },

        // ========================================
        // 拖曳內容處理
        // ========================================

        /**
         * 處理拖曳放置事件
         * @param {DragEvent} e
         */
        async handleDrop(e) {
            log('DragDropManager: handleDrop called');

            // 取得拖曳內容
            const dropContent = this._getDropContent(e);

            if (!dropContent) {
                Toast.warning('無法識別拖曳的內容');
                return;
            }

            // 備份當前內容
            const backupSuccess = await this._backupCurrentContent();
            if (backupSuccess === false) {
                // 備份失敗且有內容,詢問是否繼續
                const hasContent = EditorManager.isReady() &&
                    EditorManager.getValue()?.trim();

                if (hasContent) {
                    if (!confirm('無法備份當前內容。是否仍要繼續導入?')) {
                        return;
                    }
                }
            }

            // 確保 Modal 開啟
            if (!Modal.isOpen) {
                try {
                    await Modal.open();
                    // 等待 Modal 和編輯器就緒
                    await new Promise(r => setTimeout(r, 500));
                } catch (err) {
                    Toast.error('無法開啟編輯器');
                    return;
                }
            }

            // 確保編輯器就緒
            if (!EditorManager.isReady()) {
                await new Promise(r => setTimeout(r, 300));
                if (!EditorManager.isReady()) {
                    Toast.error('編輯器尚未就緒,請稍後重試');
                    return;
                }
            }

            // 根據內容類型處理
            if (dropContent.type === 'file') {
                await this._handleFileDrop(dropContent.data);
            } else {
                await this._handleTextDrop(dropContent.data);
            }
        },

        /**
         * 從拖曳事件中提取內容
         * @param {DragEvent} e
         * @returns {Object|null} { type: 'file'|'text', data: File|string }
         */
        _getDropContent(e) {
            const dt = e.dataTransfer;
            if (!dt) return null;

            // 優先處理檔案
            if (dt.files && dt.files.length > 0) {
                const file = dt.files[0];
                // 只處理第一個檔案
                if (this.isFileSupported(file)) {
                    return { type: 'file', data: file };
                } else {
                    Toast.warning(`不支援的檔案格式:${file.name}`);
                    return null;
                }
            }

            // 處理純文字
            const text = dt.getData('text/plain');
            if (text && text.trim()) {
                return { type: 'text', data: text };
            }

            // 處理 HTML(轉換為純文字)
            const html = dt.getData('text/html');
            if (html) {
                const div = document.createElement('div');
                div.innerHTML = html;
                const extractedText = div.textContent || div.innerText || '';
                if (extractedText.trim()) {
                    return { type: 'text', data: extractedText };
                }
            }

            return null;
        },

        /**
         * 處理檔案拖曳
         * @param {File} file
         */
        async _handleFileDrop(file) {
            log('DragDropManager: Handling file drop:', file.name);

            // 驗證檔案
            const validation = this._validateFile(file);
            if (!validation.valid) {
                Toast.error(validation.reason);
                return;
            }

            try {
                // 讀取檔案內容
                const content = await Utils.readFile(file);

                // 設定編輯器內容
                EditorManager.setValue(content);

                Toast.success(`已導入檔案:${file.name}`);
                Modal.updateWordCount?.();

                // 記錄到診斷
                log('DragDropManager: File imported successfully', {
                    name: file.name,
                    size: file.size,
                    contentLength: content.length
                });

            } catch (err) {
                logError('DragDropManager: File read error:', err);
                Toast.error('檔案讀取失敗');
            }
        },

        /**
         * 處理文字拖曳
         * @param {string} text
         */
        async _handleTextDrop(text) {
            log('DragDropManager: Handling text drop, length:', text.length);

            if (!text || !text.trim()) {
                Toast.warning('拖曳的內容為空');
                return;
            }

            // 插入文字到編輯器
            EditorManager.insertValue(text);

            const charCount = text.length;
            Toast.success(`已導入文字(${charCount} 字元)`);
            Modal.updateWordCount?.();
        },

        /**
         * 備份當前編輯器內容
         * @returns {Promise<boolean|null>} true=成功, false=失敗, null=無需備份
         */
        async _backupCurrentContent() {
            if (!EditorManager.isReady()) {
                return null; // 編輯器未就緒,無需備份
            }

            const content = EditorManager.getValue();
            if (!content || !content.trim()) {
                return null; // 無內容,無需備份
            }

            try {
                const info = EditorManager.getCurrentInfo();
                const meta = BackupManager.create(content, {
                    editorKey: info?.key,
                    mode: info?.adapter?._detectModeFromDOM?.(),
                    manual: false
                });

                if (meta) {
                    Toast.info('已自動備份當前內容', 2500);
                    log('DragDropManager: Auto-backed up current content');
                    return true;
                }

                return false;
            } catch (err) {
                logError('DragDropManager: Backup failed:', err);
                return false;
            }
        },

        // ========================================
        // 檔案驗證
        // ========================================

        /**
         * 檢查檔案是否為支援的類型
         * @param {File} file - 檔案物件
         * @returns {boolean}
         */
        isFileSupported(file) {
            if (!file?.name) return false;

            const name = file.name.toLowerCase();
            const ext = '.' + name.split('.').pop();
            return this.SUPPORTED_EXTENSIONS.includes(ext);
        },

        /**
         * 驗證檔案
         * @param {File} file
         * @returns {Object} { valid: boolean, reason?: string }
         */
        _validateFile(file) {
            // 檢查副檔名
            if (!this.isFileSupported(file)) {
                const ext = file.name.split('.').pop();
                return {
                    valid: false,
                    reason: `不支援的檔案格式(.${ext})\n支援的格式:.md, .txt, .markdown 等`
                };
            }

            // 檢查大小
            if (file.size > this.MAX_FILE_SIZE) {
                const sizeMB = (file.size / 1024 / 1024).toFixed(2);
                return {
                    valid: false,
                    reason: `檔案過大(${sizeMB} MB)\n最大支援 5 MB`
                };
            }

            // 檢查是否為空檔案
            if (file.size === 0) {
                return {
                    valid: false,
                    reason: '檔案內容為空'
                };
            }

            return { valid: true };
        },

        // ========================================
        // 首次使用提示
        // ========================================

        /**
         * 顯示首次使用提示(若尚未達到最大次數)
         */
        showHintIfNeeded() {
            const key = CONFIG.storageKeys.dragDropHintShown;
            const shownCount = Utils.storage.get(key, 0);

            // 檢查是否已達到最大顯示次數
            if (shownCount >= this.MAX_HINT_COUNT) {
                return;
            }

            // 延遲顯示,避免干擾使用者的初始操作
            setTimeout(() => {
                // 再次檢查(可能在延遲期間已經顯示過)
                const currentCount = Utils.storage.get(key, 0);
                if (currentCount >= this.MAX_HINT_COUNT) return;

                Toast.info(
                    '💡 小技巧:您可以將文字或 Markdown 檔案拖曳到右下角按鈕上直接導入',
                    this.HINT_DURATION
                );

                // 更新計數
                Utils.storage.set(key, currentCount + 1);
                log('DragDropManager: Hint shown, count:', currentCount + 1);

            }, this.HINT_DELAY);
        },

        /**
         * 重置提示計數(供測試或設定使用)
         */
        resetHintCount() {
            Utils.storage.set(CONFIG.storageKeys.dragDropHintShown, 0);
            log('DragDropManager: Hint count reset');
        }
    };

    // ========================================
    // FileSystemManager 檔案系統管理器
    // ========================================

    /**
     * 檔案系統管理器
     *
     * 設計意圖:
     * - 讓使用者選擇自訂的備份/暫存資料夾
     * - 使用 File System Access API(Chrome/Edge 86+)
     * - 同時提供傳統的手動匯入/匯出作為 fallback(所有瀏覽器)
     *
     * 注意事項:
     * - File System Access API 僅在 Chrome/Edge 支援
     * - 權限在頁面重新載入後失效,需重新授權
     * - 目錄句柄無法持久化儲存,只能記住名稱供顯示
     *
     * 儲存結構:
     * - mme_fs_enabled: 是否啟用檔案系統備份
     * - mme_fs_auto_backup: 是否自動備份到資料夾
     * - mme_fs_dir_name: 已選擇的資料夾名稱(僅供顯示)
     */
    const FileSystemManager = {
        /** @type {FileSystemDirectoryHandle|null} 已選擇的目錄句柄 */
        _directoryHandle: null,

        /** @type {boolean|null} API 支援狀態(快取) */
        _supported: null,

        /** @type {boolean} 是否有有效權限 */
        _hasPermission: false,

        /** @type {number} 最後一次操作時間 */
        _lastOperationTime: 0,

        /** @type {number} 資料夾備份保留數量 */
        MAX_FOLDER_BACKUPS: 30,

        /** @type {string} 備份檔名前綴 */
        BACKUP_PREFIX: 'mme_backup_',

        // ========================================
        // API 支援與設定
        // ========================================

        /**
         * 檢查 File System Access API 是否可用
         * @returns {boolean}
         */
        isSupported() {
            if (this._supported !== null) return this._supported;

            try {
                this._supported = (
                    typeof window.showDirectoryPicker === 'function' &&
                    typeof window.showOpenFilePicker === 'function' &&
                    typeof window.showSaveFilePicker === 'function' &&
                    typeof FileSystemDirectoryHandle !== 'undefined' &&
                    typeof FileSystemFileHandle !== 'undefined'
                );
            } catch (e) {
                this._supported = false;
            }

            log('FileSystemManager: API supported:', this._supported);
            return this._supported;
        },

        /**
         * 取得設定
         * @returns {Object}
         */
        getSettings() {
            return {
                enabled: Utils.storage.get(CONFIG.storageKeys.fsEnabled, false),
                autoBackup: Utils.storage.get(CONFIG.storageKeys.fsAutoBackup, false),
                directoryName: Utils.storage.get(CONFIG.storageKeys.fsDirectoryName, null)
            };
        },

        /**
         * 儲存設定
         * @param {Object} settings
         */
        saveSettings(settings) {
            if (settings.enabled !== undefined) {
                Utils.storage.set(CONFIG.storageKeys.fsEnabled, settings.enabled);
            }
            if (settings.autoBackup !== undefined) {
                Utils.storage.set(CONFIG.storageKeys.fsAutoBackup, settings.autoBackup);
            }
            if (settings.directoryName !== undefined) {
                Utils.storage.set(CONFIG.storageKeys.fsDirectoryName, settings.directoryName);
            }
            log('FileSystemManager: Settings saved', settings);
        },

        /**
         * 取得當前狀態
         * @returns {Object}
         */
        getStatus() {
            const settings = this.getSettings();
            return {
                supported: this.isSupported(),
                enabled: settings.enabled,
                autoBackup: settings.autoBackup,
                directoryName: settings.directoryName,
                hasHandle: !!this._directoryHandle,
                hasPermission: this._hasPermission
            };
        },

        // ========================================
        // 資料夾選擇與權限
        // ========================================

        /**
         * 讓使用者選擇備份資料夾
         * @returns {Promise<FileSystemDirectoryHandle|null>}
         */
        async selectDirectory() {
            if (!this.isSupported()) {
                Toast.warning(
                    '您的瀏覽器不支援檔案系統存取 API\n' +
                    '請使用 Chrome 或 Edge 86 以上版本,\n' +
                    '或使用下方的「手動匯出/匯入」功能'
                );
                return null;
            }

            try {
                // 開啟資料夾選擇器
                const handle = await window.showDirectoryPicker({
                    mode: 'readwrite',
                    startIn: 'documents'
                });

                // 驗證並請求權限
                let permission = await handle.queryPermission({ mode: 'readwrite' });

                if (permission !== 'granted') {
                    permission = await handle.requestPermission({ mode: 'readwrite' });

                    if (permission !== 'granted') {
                        Toast.warning('未獲得資料夾寫入權限\n請在彈出視窗中點擊「允許」');
                        return null;
                    }
                }

                // 儲存句柄和狀態
                this._directoryHandle = handle;
                this._hasPermission = true;

                this.saveSettings({
                    directoryName: handle.name,
                    enabled: true
                });

                Toast.success(`已選擇備份資料夾:${handle.name}`);
                log('FileSystemManager: Directory selected:', handle.name);

                return handle;

            } catch (e) {
                if (e.name === 'AbortError') {
                    // 使用者取消選擇,不顯示錯誤
                    log('FileSystemManager: User cancelled directory selection');
                    return null;
                }

                if (e.name === 'SecurityError') {
                    Toast.warning('安全限制:無法存取檔案系統\n請確認網站有權限存取檔案');
                    return null;
                }

                logError('FileSystemManager: selectDirectory error:', e);
                Toast.error('無法選擇資料夾:' + (e.message || '未知錯誤'));
                return null;
            }
        },

        /**
         * 檢查權限狀態
         * @returns {Promise<string>} 'granted' | 'denied' | 'prompt' | 'none' | 'error'
         */
        async checkPermission() {
            if (!this._directoryHandle) {
                return 'none';
            }

            try {
                const permission = await this._directoryHandle.queryPermission({ mode: 'readwrite' });
                this._hasPermission = (permission === 'granted');
                return permission;
            } catch (e) {
                logError('FileSystemManager: checkPermission error:', e);
                return 'error';
            }
        },

        /**
         * 確保有有效權限(必要時請求)
         * @returns {Promise<boolean>}
         */
        async ensurePermission() {
            const status = await this.checkPermission();

            if (status === 'granted') {
                return true;
            }

            if (status === 'prompt' && this._directoryHandle) {
                try {
                    const request = await this._directoryHandle.requestPermission({ mode: 'readwrite' });
                    this._hasPermission = (request === 'granted');
                    return this._hasPermission;
                } catch (e) {
                    logError('FileSystemManager: ensurePermission error:', e);
                    return false;
                }
            }

            return false;
        },

        /**
         * 清除已選擇的資料夾
         */
        clearDirectory() {
            this._directoryHandle = null;
            this._hasPermission = false;
            this.saveSettings({
                directoryName: null,
                enabled: false,
                autoBackup: false
            });
            log('FileSystemManager: Directory cleared');
            Toast.info('已清除備份資料夾設定');
        },

        // ========================================
        // 檔案操作
        // ========================================

        /**
         * 儲存檔案到資料夾
         * @param {string} filename - 檔案名稱
         * @param {string} content - 檔案內容
         * @param {Object} options - 選項
         * @returns {Promise<boolean>}
         */
        async saveToDirectory(filename, content, options = {}) {
            const { showToast = true, showError = true } = options;

            if (!this._directoryHandle) {
                if (showError) Toast.warning('請先選擇備份資料夾');
                return false;
            }

            try {
                // 確保有權限
                const hasPermission = await this.ensurePermission();
                if (!hasPermission) {
                    if (showError) {
                        Toast.warning('資料夾存取權限已過期\n請重新選擇資料夾');
                    }
                    return false;
                }

                // 建立或覆蓋檔案
                const fileHandle = await this._directoryHandle.getFileHandle(filename, { create: true });
                const writable = await fileHandle.createWritable();
                await writable.write(content);
                await writable.close();

                this._lastOperationTime = Date.now();
                log('FileSystemManager: File saved:', filename);

                if (showToast) {
                    Toast.success(`已儲存到資料夾:${filename}`);
                }

                return true;

            } catch (e) {
                logError('FileSystemManager: saveToDirectory error:', e);

                if (e.name === 'NotAllowedError' || e.name === 'SecurityError') {
                    this._hasPermission = false;
                    if (showError) {
                        Toast.warning('資料夾存取被拒絕\n請重新選擇資料夾');
                    }
                } else if (e.name === 'QuotaExceededError') {
                    if (showError) {
                        Toast.error('磁碟空間不足');
                    }
                } else {
                    if (showError) {
                        Toast.error('儲存失敗:' + (e.message || '未知錯誤'));
                    }
                }

                return false;
            }
        },

        /**
         * 從資料夾讀取檔案
         * @param {string} filename - 檔案名稱
         * @returns {Promise<string|null>}
         */
        async readFromDirectory(filename) {
            if (!this._directoryHandle) {
                return null;
            }

            try {
                const hasPermission = await this.ensurePermission();
                if (!hasPermission) {
                    return null;
                }

                const fileHandle = await this._directoryHandle.getFileHandle(filename);
                const file = await fileHandle.getFile();
                const content = await file.text();

                log('FileSystemManager: File read:', filename);
                return content;

            } catch (e) {
                if (e.name === 'NotFoundError') {
                    // 檔案不存在,正常情況
                    return null;
                }

                logError('FileSystemManager: readFromDirectory error:', e);
                return null;
            }
        },

        /**
         * 列出資料夾中的備份檔案
         * @returns {Promise<Array>}
         */
        async listBackupFiles() {
            if (!this._directoryHandle) {
                return [];
            }

            try {
                const hasPermission = await this.ensurePermission();
                if (!hasPermission) {
                    return [];
                }

                const files = [];

                for await (const entry of this._directoryHandle.values()) {
                    if (entry.kind === 'file' && entry.name.startsWith(this.BACKUP_PREFIX)) {
                        try {
                            const file = await entry.getFile();
                            files.push({
                                name: entry.name,
                                size: file.size,
                                lastModified: file.lastModified,
                                handle: entry
                            });
                        } catch (e) {
                            // 單個檔案讀取失敗,跳過
                        }
                    }
                }

                // 按時間排序(新的在前)
                files.sort((a, b) => b.lastModified - a.lastModified);

                log('FileSystemManager: Listed', files.length, 'backup files');
                return files;

            } catch (e) {
                logError('FileSystemManager: listBackupFiles error:', e);
                return [];
            }
        },

        /**
         * 刪除資料夾中的檔案
         * @param {string} filename - 檔案名稱
         * @returns {Promise<boolean>}
         */
        async deleteFromDirectory(filename) {
            if (!this._directoryHandle) {
                return false;
            }

            try {
                const hasPermission = await this.ensurePermission();
                if (!hasPermission) {
                    return false;
                }

                await this._directoryHandle.removeEntry(filename);
                log('FileSystemManager: File deleted:', filename);
                return true;

            } catch (e) {
                if (e.name === 'NotFoundError') {
                    return true; // 檔案本來就不存在
                }
                logError('FileSystemManager: deleteFromDirectory error:', e);
                return false;
            }
        },

        // ========================================
        // 自動備份
        // ========================================

        /**
         * 執行自動備份到資料夾
         * @param {string} content - 備份內容
         * @returns {Promise<boolean>}
         */
        async autoBackupToDirectory(content) {
            const settings = this.getSettings();

            // 檢查是否啟用
            if (!settings.enabled || !settings.autoBackup) {
                return false;
            }

            // 檢查是否有目錄句柄
            if (!this._directoryHandle) {
                log('FileSystemManager: Auto backup skipped - no directory handle');
                return false;
            }

            // 檢查內容
            if (!content || !content.trim()) {
                return false;
            }

            // 生成檔名
            const date = new Date().toISOString()
                .replace(/[:.]/g, '-')
                .slice(0, 19);
            const filename = `${this.BACKUP_PREFIX}${date}.md`;

            // 儲存檔案
            const success = await this.saveToDirectory(filename, content, {
                showToast: false,
                showError: false
            });

            if (success) {
                log('FileSystemManager: Auto backup saved:', filename);

                // 清理舊備份
                await this._cleanupOldBackups();
            }

            return success;
        },

        /**
         * 清理舊備份(保留最近 N 個)
         */
        async _cleanupOldBackups() {
            try {
                const files = await this.listBackupFiles();

                if (files.length <= this.MAX_FOLDER_BACKUPS) {
                    return;
                }

                // 刪除超出數量的舊檔案
                const toDelete = files.slice(this.MAX_FOLDER_BACKUPS);

                for (const file of toDelete) {
                    await this.deleteFromDirectory(file.name);
                }

                log('FileSystemManager: Cleaned up', toDelete.length, 'old backups');

            } catch (e) {
                // 清理失敗不影響其他操作
                logError('FileSystemManager: cleanup error:', e);
            }
        },

        // ========================================
        // 手動備份/還原(使用 File System API 但不依賴預選資料夾)
        // ========================================

        /**
         * 手動儲存檔案(讓使用者選擇位置)
         * @param {string} content - 內容
         * @param {string} suggestedName - 建議檔名
         * @returns {Promise<boolean>}
         */
        async saveFileAs(content, suggestedName = 'document.md') {
            if (!this.isSupported()) {
                // Fallback:使用傳統下載
                return Utils.downloadFile(content, suggestedName, 'text/markdown;charset=utf-8');
            }

            try {
                const handle = await window.showSaveFilePicker({
                    suggestedName,
                    types: [{
                        description: 'Markdown 文件',
                        accept: { 'text/markdown': ['.md'] }
                    }, {
                        description: '文字文件',
                        accept: { 'text/plain': ['.txt'] }
                    }]
                });

                const writable = await handle.createWritable();
                await writable.write(content);
                await writable.close();

                Toast.success(`已儲存:${handle.name}`);
                return true;

            } catch (e) {
                if (e.name === 'AbortError') {
                    return false;
                }

                // Fallback:使用傳統下載
                logError('FileSystemManager: saveFileAs error, falling back:', e);
                return Utils.downloadFile(content, suggestedName, 'text/markdown;charset=utf-8');
            }
        },

        /**
         * 手動開啟檔案(讓使用者選擇)
         * @returns {Promise<{content: string, name: string}|null>}
         */
        async openFile() {
            if (!this.isSupported()) {
                // Fallback:使用傳統 file input
                return this._openFileTraditional();
            }

            try {
                const [handle] = await window.showOpenFilePicker({
                    types: [{
                        description: 'Markdown 文件',
                        accept: {
                            'text/markdown': ['.md', '.markdown', '.mdown', '.mkd'],
                            'text/plain': ['.txt', '.text']
                        }
                    }],
                    multiple: false
                });

                const file = await handle.getFile();
                const content = await file.text();

                return { content, name: file.name };

            } catch (e) {
                if (e.name === 'AbortError') {
                    return null;
                }

                logError('FileSystemManager: openFile error:', e);
                return this._openFileTraditional();
            }
        },

        /**
         * 傳統方式開啟檔案
         * @returns {Promise<{content: string, name: string}|null>}
         */
        _openFileTraditional() {
            return new Promise((resolve) => {
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = '.md,.txt,.markdown,.mdown,.mkd,.mkdn,.mdwn,.mdtxt,.mdtext,.text';
                input.style.display = 'none';

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

                    try {
                        const content = await Utils.readFile(file);
                        resolve({ content, name: file.name });
                    } catch (err) {
                        Toast.error('檔案讀取失敗');
                        resolve(null);
                    }

                    input.remove();
                };

                input.oncancel = () => {
                    resolve(null);
                    input.remove();
                };

                document.body.appendChild(input);
                input.click();
            });
        }
    };

    // ========================================
    // FindReplace 尋找與取代
    // ========================================

    /**
     * 尋找與取代管理器
     *
     * 設計意圖:
     * - 提供基本的文字搜尋和取代功能
     * - 支援大小寫敏感和正規表達式
     * - 使用浮動式搜尋框,不干擾編輯
     *
     * 限制:
     * - 由於不同編輯器 API 差異,高亮功能可能受限
     * - 主要依賴編輯器的 getValue/setValue 操作
     */
    const FindReplace = {
        /** @type {boolean} 面板是否開啟 */
        isOpen: false,

        /** @type {HTMLElement|null} 面板元素 */
        panel: null,

        /** @type {string} 搜尋字串 */
        query: '',

        /** @type {string} 取代字串 */
        replacement: '',

        /** @type {Object} 搜尋選項 */
        options: {
            caseSensitive: false,
            useRegex: false,
            wholeWord: false
        },

        /** @type {Array} 匹配結果 */
        matches: [],

        /** @type {number} 當前匹配索引 */
        currentIndex: -1,

        /** @type {string} 上次搜尋的內容快照 */
        _lastContent: '',

        /**
         * 顯示尋找面板
         * @param {boolean} showReplace - 是否顯示取代欄位
         */
        show(showReplace = false) {
            if (!this.panel) {
                this._createPanel();
            }

            this.isOpen = true;
            this.panel.style.display = 'flex';

            const p = CONFIG.prefix;
            const replaceRow = this.panel.querySelector(`.${p}find-replace-row`);
            if (replaceRow) {
                replaceRow.style.display = showReplace ? 'flex' : 'none';
            }

            // 定位在 Modal 右上角
            if (Modal.modal) {
                const modalRect = Modal.modal.getBoundingClientRect();
                this.panel.style.top = `${modalRect.top + 50}px`;
                this.panel.style.right = `${window.innerWidth - modalRect.right + 10}px`;
                this.panel.style.left = 'auto';
            }

            // 聚焦搜尋框
            const input = this.panel.querySelector(`#${p}find-input`);
            if (input) {
                input.focus();
                input.select();
            }

            // 如果有選取文字,使用它作為搜尋詞
            const selectedText = this._getEditorSelection();
            if (selectedText && selectedText.length < 100) {
                this.query = selectedText;
                if (input) input.value = selectedText;
                this._doFind();
            }
        },

        /**
         * 隱藏尋找面板
         */
        hide() {
            if (this.panel) {
                this.panel.style.display = 'none';
            }
            this.isOpen = false;
            this.matches = [];
            this.currentIndex = -1;
            this._updateStatus();
        },

        /**
         * 切換面板顯示
         * @param {boolean} showReplace
         */
        toggle(showReplace = false) {
            if (this.isOpen) {
                this.hide();
            } else {
                this.show(showReplace);
            }
        },

        /**
         * 建立面板 DOM
         */
        _createPanel() {
            const p = CONFIG.prefix;

            this.panel = document.createElement('div');
            this.panel.className = `${p}find-panel`;
            this.panel.innerHTML = `
                <div class="${p}find-row">
                    <input type="text"
                           id="${p}find-input"
                           class="${p}find-input"
                           placeholder="尋找..."
                           aria-label="搜尋文字">
                    <button class="${p}icon-btn" data-action="find-prev" title="上一個 (Shift+Enter)">
                        ${Icons.arrowUp}
                    </button>
                    <button class="${p}icon-btn" data-action="find-next" title="下一個 (Enter)">
                        ${Icons.arrowDown}
                    </button>
                    <button class="${p}icon-btn" data-action="find-close" title="關閉 (Escape)">
                        ${Icons.close}
                    </button>
                </div>
                <div class="${p}find-replace-row" style="display:none;">
                    <input type="text"
                           id="${p}replace-input"
                           class="${p}find-input"
                           placeholder="取代為..."
                           aria-label="取代文字">
                    <button class="${p}btn ${p}btn-sm" data-action="replace-one">
                        取代
                    </button>
                    <button class="${p}btn ${p}btn-sm" data-action="replace-all">
                        全部取代
                    </button>
                </div>
                <div class="${p}find-options">
                    <label class="${p}find-option">
                        <input type="checkbox" id="${p}find-case">
                        <span>區分大小寫</span>
                    </label>
                    <label class="${p}find-option">
                        <input type="checkbox" id="${p}find-regex">
                        <span>正規表達式</span>
                    </label>
                    <label class="${p}find-option">
                        <input type="checkbox" id="${p}find-whole">
                        <span>全字匹配</span>
                    </label>
                </div>
                <div class="${p}find-status" id="${p}find-status">
                    就緒
                </div>
            `;

            Portal.append(this.panel);
            this._bindEvents();
        },

        /**
         * 綁定事件
         */
        _bindEvents() {
            const p = CONFIG.prefix;

            // 搜尋框輸入
            const findInput = this.panel.querySelector(`#${p}find-input`);
            findInput?.addEventListener('input', Utils.debounce(() => {
                this.query = findInput.value;
                this._doFind();
            }, 200));

            findInput?.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    if (e.shiftKey) {
                        this.findPrev();
                    } else {
                        this.findNext();
                    }
                } else if (e.key === 'Escape') {
                    this.hide();
                }
            });

            // 取代框
            const replaceInput = this.panel.querySelector(`#${p}replace-input`);
            replaceInput?.addEventListener('input', () => {
                this.replacement = replaceInput.value;
            });

            replaceInput?.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    this.replaceOne();
                } else if (e.key === 'Escape') {
                    this.hide();
                }
            });

            // 選項
            this.panel.querySelector(`#${p}find-case`)?.addEventListener('change', (e) => {
                this.options.caseSensitive = e.target.checked;
                this._doFind();
            });

            this.panel.querySelector(`#${p}find-regex`)?.addEventListener('change', (e) => {
                this.options.useRegex = e.target.checked;
                this._doFind();
            });

            this.panel.querySelector(`#${p}find-whole`)?.addEventListener('change', (e) => {
                this.options.wholeWord = e.target.checked;
                this._doFind();
            });

            // 按鈕
            this.panel.querySelector('[data-action="find-prev"]')?.addEventListener('click', () => {
                this.findPrev();
            });

            this.panel.querySelector('[data-action="find-next"]')?.addEventListener('click', () => {
                this.findNext();
            });

            this.panel.querySelector('[data-action="find-close"]')?.addEventListener('click', () => {
                this.hide();
            });

            this.panel.querySelector('[data-action="replace-one"]')?.addEventListener('click', () => {
                this.replaceOne();
            });

            this.panel.querySelector('[data-action="replace-all"]')?.addEventListener('click', () => {
                this.replaceAll();
            });
        },

        /**
         * 執行搜尋
         */
        _doFind() {
            if (!this.query) {
                this.matches = [];
                this.currentIndex = -1;
                this._updateStatus();
                return;
            }

            const content = EditorManager.getValue() || '';
            this._lastContent = content;

            try {
                let regex;
                let pattern = this.query;

                if (!this.options.useRegex) {
                    // 跳脫特殊字元
                    pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                }

                if (this.options.wholeWord) {
                    pattern = `\\b${pattern}\\b`;
                }

                const flags = this.options.caseSensitive ? 'g' : 'gi';
                regex = new RegExp(pattern, flags);

                this.matches = [];
                let match;
                while ((match = regex.exec(content)) !== null) {
                    this.matches.push({
                        index: match.index,
                        length: match[0].length,
                        text: match[0]
                    });
                    // 防止無限迴圈
                    if (match.index === regex.lastIndex) {
                        regex.lastIndex++;
                    }
                }

                // 重置到第一個匹配
                this.currentIndex = this.matches.length > 0 ? 0 : -1;

            } catch (e) {
                // 正規表達式錯誤
                this.matches = [];
                this.currentIndex = -1;
                log('FindReplace: Regex error:', e.message);
            }

            this._updateStatus();
        },

        /**
         * 尋找下一個
         */
        findNext() {
            if (this.matches.length === 0) {
                this._doFind();
                if (this.matches.length === 0) return;
            }

            this.currentIndex = (this.currentIndex + 1) % this.matches.length;
            this._goToMatch();
        },

        /**
         * 尋找上一個
         */
        findPrev() {
            if (this.matches.length === 0) {
                this._doFind();
                if (this.matches.length === 0) return;
            }

            this.currentIndex = (this.currentIndex - 1 + this.matches.length) % this.matches.length;
            this._goToMatch();
        },

        /**
         * 跳轉到當前匹配並選取
         *
         * 設計意圖:
         * - 使用統一的適配器介面 selectRange
         * - 若適配器未完整實現,至少聚焦編輯器
         */
        _goToMatch() {
            if (this.currentIndex < 0 || this.currentIndex >= this.matches.length) return;

            const match = this.matches[this.currentIndex];
            const adapter = EditorManager.currentAdapter;

            if (!adapter) {
                log('FindReplace: No adapter available');
                this._updateStatus();
                return;
            }

            try {
                // 嘗試使用統一的 selectRange 介面
                if (typeof adapter.selectRange === 'function') {
                    const success = adapter.selectRange(match.index, match.index + match.length);
                    if (success) {
                        this._updateStatus();
                        return;
                    }
                }

                // Fallback: 至少聚焦編輯器
                if (typeof adapter.focus === 'function') {
                    adapter.focus();
                }
            } catch (e) {
                log('FindReplace: goToMatch error:', e.message);
            }

            this._updateStatus();
        },

        /**
         * 取代當前匹配
         */
        replaceOne() {
            if (this.currentIndex < 0 || this.currentIndex >= this.matches.length) return;

            const match = this.matches[this.currentIndex];
            const content = EditorManager.getValue() || '';

            const newContent =
                content.substring(0, match.index) +
                this.replacement +
                content.substring(match.index + match.length);

            EditorManager.setValue(newContent);

            // 重新搜尋
            this._doFind();

            Toast.success('已取代 1 處');
            Modal.updateWordCount?.();
        },

        /**
         * 取代所有匹配
         */
        replaceAll() {
            if (this.matches.length === 0) {
                this._doFind();
                if (this.matches.length === 0) {
                    Toast.info('沒有找到匹配項');
                    return;
                }
            }

            const count = this.matches.length;
            let content = EditorManager.getValue() || '';

            try {
                let pattern = this.query;

                if (!this.options.useRegex) {
                    pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                }

                if (this.options.wholeWord) {
                    pattern = `\\b${pattern}\\b`;
                }

                const flags = this.options.caseSensitive ? 'g' : 'gi';
                const regex = new RegExp(pattern, flags);

                content = content.replace(regex, this.replacement);
                EditorManager.setValue(content);

                // 重新搜尋
                this._doFind();

                Toast.success(`已取代 ${count} 處`);
                Modal.updateWordCount?.();

            } catch (e) {
                Toast.error('取代失敗:' + e.message);
            }
        },

        /**
         * 更新狀態顯示
         */
        _updateStatus() {
            const p = CONFIG.prefix;
            const statusEl = this.panel?.querySelector(`#${p}find-status`);
            if (!statusEl) return;

            if (!this.query) {
                statusEl.textContent = '輸入搜尋詞';
                statusEl.style.color = '';
            } else if (this.matches.length === 0) {
                statusEl.textContent = '無匹配結果';
                statusEl.style.color = '#dc3545';
            } else {
                statusEl.textContent = `${this.currentIndex + 1} / ${this.matches.length} 個匹配`;
                statusEl.style.color = '';
            }
        },

        /**
         * 取得編輯器選取文字
         */
        _getEditorSelection() {
            try {
                const adapter = EditorManager.currentAdapter;

                if (adapter?.instance?.codemirror) {
                    return adapter.instance.codemirror.getSelection();
                }

                return '';
            } catch (e) {
                return '';
            }
        }
    };

    // ========================================
    // Loader 資源載入器
    // ========================================

    /**
     * 資源載入管理器
     *
     * 設計意圖:
     * - 統一管理編輯器資源(JS/CSS)的載入
     * - 多 CDN 容錯:自動測試並選擇可用的 CDN
     * - 防止重複載入:使用 loadingPromises 追蹤載入中的請求
     * - 依賴處理:某些編輯器有額外依賴(如 KaTeX)
     *
     * 載入流程:
     * 1. 檢查是否已載入
     * 2. 測試可用的 CDN
     * 3. 載入額外依賴
     * 4. 載入編輯器 CSS
     * 5. 載入編輯器 JS
     * 6. 等待全局物件可用
     */
    const Loader = {
        /** @type {Object} 已載入的編輯器(key: editorKey, value: cdnBase) */
        loaded: {},

        /** @type {Object} 已驗證可用的 CDN(key: editorKey, value: cdnBase) */
        cdnCache: {},

        /** @type {Object} 載入中的 Promise(防止重複載入) */
        loadingPromises: {},

        /** @type {Object} 內聯樣式元素(用於 CSS inline 載入) */
        styleElements: {},

        /** @type {Set} 曾失敗的 CDN(避免重複嘗試) */
        failedCdnOnce: new Set(),

        /**
         * 測試 CDN 可用性
         * @param {string} url - 測試 URL
         * @param {number} timeout - 超時時間
         * @returns {Promise<boolean>} 是否可用
         */
        async testCdn(url, timeout = CONFIG.cdnTestTimeout) {
            // 嘗試 HEAD 請求(更輕量)
            const headOk = await new Promise((resolve) => {
                if (typeof GM_xmlhttpRequest !== 'function') {
                    return resolve(null); // 不支援,跳過
                }

                GM_xmlhttpRequest({
                    method: 'HEAD',
                    url,
                    timeout,
                    anonymous: true,
                    onload: (r) => resolve(r.status >= 200 && r.status < 400),
                    onerror: () => resolve(null),
                    ontimeout: () => resolve(null)
                });
            });

            if (headOk === true) return true;
            if (headOk === false) return false;

            // HEAD 不支援或失敗,嘗試 GET
            try {
                await Utils.gmFetch(url, timeout);
                return true;
            } catch (e) {
                return false;
            }
        },

        /**
         * 取得可用的 CDN
         * @param {string} editorKey - 編輯器鍵名
         * @param {Function} onProgress - 進度回調
         * @returns {Promise<string>} CDN 基礎路徑
         */
        async getAvailableCdn(editorKey, onProgress) {
            const cfg = CONFIG.editors[editorKey];
            if (!cfg) throw new Error(`Unknown editor: ${editorKey}`);

            // 檢查快取
            if (this.cdnCache[editorKey]) {
                return this.cdnCache[editorKey];
            }

            // 第一輪:跳過曾失敗的 CDN
            for (const cdnBase of cfg.cdn) {
                if (this.failedCdnOnce.has(cdnBase)) continue;

                const testUrl = cdnBase + cfg.files.js;
                try {
                    const host = new URL(cdnBase).hostname;
                    onProgress?.(`測試 CDN: ${host}...`);
                } catch (e) {
                    onProgress?.(`測試 CDN...`);
                }

                const ok = await this.testCdn(testUrl, CONFIG.cdnTestTimeout);
                if (ok) {
                    this.cdnCache[editorKey] = cdnBase;
                    log('CDN available:', cdnBase);
                    return cdnBase;
                } else {
                    this.failedCdnOnce.add(cdnBase);
                    log('CDN failed:', cdnBase);
                }
            }

            // 第二輪:重新嘗試所有 CDN
            for (const cdnBase of cfg.cdn) {
                const testUrl = cdnBase + cfg.files.js;
                onProgress?.(`重新測試 CDN...`);
                const ok = await this.testCdn(testUrl, CONFIG.cdnTestTimeout);
                if (ok) {
                    this.cdnCache[editorKey] = cdnBase;
                    log('CDN available (retry):', cdnBase);
                    return cdnBase;
                }
            }

            throw new Error(`無法連接到 ${cfg.name} 的任何 CDN`);
        },

        /**
         * 通過 link 標籤載入 CSS
         * @param {string} url - CSS URL
         * @returns {Promise<boolean>} 是否成功
         */
        async loadCSSByLink(url) {
            try {
                await Utils.loadStylesheet(url, 15000);
                return true;
            } catch (e) {
                log('CSS link load failed:', url, e.message);
                return false;
            }
        },

        /**
         * 內聯載入 CSS(用於跨域情況)
         * @param {string} url - CSS URL
         * @param {string} styleId - style 元素 ID
         * @returns {Promise<boolean>} 是否成功
         */
        async loadCSSInline(url, styleId) {
            try {
                const cssRaw = await Utils.gmFetch(url, 30000);
                const css = Utils.fixCssUrls(cssRaw, url);
                Utils.addStyle(css, styleId);
                return true;
            } catch (e) {
                log('CSS inline load failed:', url, e.message);
                return false;
            }
        },

        /**
         * 載入編輯器 CSS
         * @param {string} editorKey - 編輯器鍵名
         * @param {string} cdnBase - CDN 基礎路徑
         * @param {Function} onProgress - 進度回調
         */
        async loadEditorCSS(editorKey, cdnBase, onProgress) {
            const cfg = CONFIG.editors[editorKey];
            const cssUrl = cdnBase + cfg.files.css;
            const styleId = `${CONFIG.prefix}${editorKey}-css`;

            // 檢查是否已載入
            if (document.getElementById(styleId) || document.querySelector(`link[href="${cssUrl}"]`)) {
                return;
            }

            onProgress?.(`載入 ${cfg.name} CSS...`);

            // 嘗試 link 載入
            const ok = await this.loadCSSByLink(cssUrl);
            if (!ok) {
                // Fallback: 內聯載入
                try {
                    await this.loadCSSInline(cssUrl, styleId);
                } catch (e) {
                    logWarn(`CSS load failed (${editorKey}):`, e);
                }
            }

            // 載入額外 CSS(如 Toast UI 的深色主題)
            if (cfg.extraCss && Array.isArray(cfg.extraCss)) {
                for (const extraPath of cfg.extraCss) {
                    const extraUrl = cdnBase + extraPath;
                    await this.loadCSSByLink(extraUrl).catch(() => {
                        log('Extra CSS load failed:', extraPath);
                    });
                }
            }
        },

        /**
         * 載入編輯器 JS
         * @param {string} editorKey - 編輯器鍵名
         * @param {string} cdnBase - CDN 基礎路徑
         * @param {Function} onProgress - 進度回調
         */
        async loadEditorJS(editorKey, cdnBase, onProgress) {
            const cfg = CONFIG.editors[editorKey];
            const jsUrl = cdnBase + cfg.files.js;

            onProgress?.(`載入 ${cfg.name} JS...`);

            // 嘗試 script 標籤載入
            try {
                await Utils.loadScript(jsUrl, CONFIG.loadTimeout);
                return;
            } catch (e) {
                log('Script tag load failed, trying GM fetch:', e.message);
            }

            // Fallback: 使用 GM_xmlhttpRequest 獲取並執行
            const js = await Utils.gmFetch(jsUrl, CONFIG.loadTimeout);
            try {
                const fn = new Function(js);
                fn.call(PAGE_WIN);
            } catch (e) {
                throw new Error(`${cfg.name} 初始化失敗: ${e.message}`);
            }
        },

        /**
         * 載入依賴 CSS
         * @param {Object} dep - 依賴配置
         * @param {Function} onProgress - 進度回調
         */
        async loadDepCss(dep, onProgress) {
            if (!dep.css) return;

            const cssList = Array.isArray(dep.css) ? dep.css : [dep.css];

            for (const url of cssList) {
                try {
                    onProgress?.(`載入依賴樣式...`);
                    await Utils.loadStylesheet(url, 15000);
                    return; // 成功則返回
                } catch (e) {
                    log('Dep CSS load failed:', url);
                    // 繼續嘗試下一個
                }
            }
        },

        /**
         * 載入依賴 JS
         * @param {Object} dep - 依賴配置
         * @param {Function} onProgress - 進度回調
         */
        async loadDepJs(dep, onProgress) {
            if (!dep.js) return;

            const jsList = Array.isArray(dep.js) ? dep.js : [dep.js];
            let lastErr = null;

            for (const url of jsList) {
                try {
                    onProgress?.(`載入依賴腳本...`);
                    await Utils.loadScript(url, 30000);
                    return; // 成功則返回
                } catch (e) {
                    lastErr = e;
                    log('Dep JS script load failed:', url);

                    // Fallback: GM fetch
                    try {
                        const js = await Utils.gmFetch(url, 30000);
                        const fn = new Function(js);
                        fn.call(PAGE_WIN);
                        return; // 成功則返回
                    } catch (e2) {
                        lastErr = e2;
                        log('Dep JS GM fetch failed:', url);
                    }
                }
            }

            throw lastErr || new Error('Dependency JS load failed');
        },

        /**
         * 載入額外依賴
         * @param {string} editorKey - 編輯器鍵名
         * @param {Function} onProgress - 進度回調
         */
        async loadExtraDeps(editorKey, onProgress) {
            const cfg = CONFIG.editors[editorKey];
            if (!cfg.extraDeps) return;

            for (const [name, dep] of Object.entries(cfg.extraDeps)) {
                // 檢查是否已載入
                if (typeof dep.ready === 'function') {
                    try {
                        if (dep.ready()) {
                            log('Dep already loaded:', name);
                            continue;
                        }
                    } catch (e) {
                        // 繼續載入
                    }
                } else if (dep.global && PAGE_WIN[dep.global]) {
                    log('Dep already loaded:', name);
                    continue;
                }

                try {
                    onProgress?.(`載入依賴:${name}...`);

                    // 載入 CSS
                    await this.loadDepCss(dep, onProgress);

                    // 載入 JS
                    await this.loadDepJs(dep, onProgress);

                    // 等待就緒
                    if (typeof dep.ready === 'function') {
                        await Utils.waitFor(() => {
                            try {
                                return dep.ready();
                            } catch (e) {
                                return false;
                            }
                        }, 10000, 100);
                    } else if (dep.global) {
                        await Utils.waitFor(() => !!PAGE_WIN[dep.global], 10000, 100);
                    }

                    log('Dep loaded successfully:', name);

                } catch (e) {
                    if (dep.optional) {
                        logWarn(`Optional dep "${name}" load failed:`, e.message);
                        continue;
                    }
                    throw new Error(`依賴 ${name} 載入失敗:${e.message}`);
                }
            }
        },

        /**
         * 載入編輯器
         * @param {string} editorKey - 編輯器鍵名
         * @param {Function} onProgress - 進度回調
         * @returns {Promise<string>} CDN 基礎路徑
         */
        async loadEditor(editorKey, onProgress) {
            const cfg = CONFIG.editors[editorKey];
            if (!cfg) throw new Error(`Unknown editor: ${editorKey}`);

            // 檢查是否已載入
            if (cfg.globalCheck && cfg.globalCheck()) {
                this.loaded[editorKey] = this.cdnCache[editorKey] || cfg.cdn[0];
                log('Editor already loaded:', editorKey);
                return this.loaded[editorKey];
            }

            // 檢查是否正在載入(防止重複載入)
            if (this.loadingPromises[editorKey]) {
                log('Editor loading in progress:', editorKey);
                return this.loadingPromises[editorKey];
            }

            // 開始載入
            this.loadingPromises[editorKey] = (async () => {
                try {
                    // 1. 測試可用的 CDN
                    const cdnBase = await this.getAvailableCdn(editorKey, onProgress);

                    // 2. 載入額外依賴
                    await this.loadExtraDeps(editorKey, onProgress);

                    // 3. 載入編輯器 CSS
                    await this.loadEditorCSS(editorKey, cdnBase, onProgress);

                    // 4. 載入編輯器 JS
                    await this.loadEditorJS(editorKey, cdnBase, onProgress);

                    // 5. 等待全局物件可用
                    onProgress?.(`初始化 ${cfg.name}...`);
                    await Utils.waitFor(() => cfg.globalCheck(), 15000, 100);

                    this.loaded[editorKey] = cdnBase;
                    log('Editor loaded successfully:', editorKey);
                    return cdnBase;

                } finally {
                    // 無論成功失敗,都清除 loading 狀態
                    delete this.loadingPromises[editorKey];
                }
            })();

            return this.loadingPromises[editorKey];
        },

        /**
         * 檢查編輯器是否已載入
         * @param {string} editorKey - 編輯器鍵名
         * @returns {boolean}
         */
        isLoaded(editorKey) {
            return !!this.loaded[editorKey];
        },

        /**
         * 取得編輯器的 CDN 基礎路徑
         * @param {string} editorKey - 編輯器鍵名
         * @returns {string|null}
         */
        getCdnBase(editorKey) {
            return this.loaded[editorKey] || this.cdnCache[editorKey] || null;
        },

        /**
         * 重置載入狀態
         * @param {string} editorKey - 編輯器鍵名(可選,不傳則重置所有)
         */
        reset(editorKey) {
            if (editorKey) {
                delete this.loaded[editorKey];
                delete this.cdnCache[editorKey];
                delete this.loadingPromises[editorKey];
                log('Loader reset:', editorKey);
            } else {
                this.loaded = {};
                this.cdnCache = {};
                this.loadingPromises = {};
                this.failedCdnOnce.clear();
                log('Loader reset: all');
            }
        }
    };

    // ========================================
    // 編輯器適配器容器
    // ========================================

    /**
     * 編輯器適配器集合
     *
     * 設計意圖:
     * - 使用適配器模式統一不同編輯器的操作介面
     * - 每個適配器必須實現標準介面:init, getValue, setValue, getHTML,
     *   insertValue, focus, refresh, setTheme, destroy
     * - EditorManager 通過適配器與具體編輯器交互,無需關心實現細節
     */
    const EditorAdapters = {};

    // ========================================
    // EasyMDE 適配器
    // ========================================

    /**
     * EasyMDE 編輯器適配器
     *
     * 設計意圖:
     * - 封裝 EasyMDE 的特殊行為,使其在 Modal 中正常工作
     * - 實現「安全」的全螢幕和並排預覽模式(與 Modal 協調)
     * - 處理主題切換和樣式注入
     *
     * 特殊處理:
     * - EasyMDE 原生的 fullscreen 和 side-by-side 會使用 position:fixed
     *   覆蓋整個頁面,這在 Modal 中會造成問題
     * - 團隊實現了 _safeToggleFullScreen 和 _safeToggleSideBySide
     *   來替代原生行為,與 Modal 的佈局協調
     */
    EditorAdapters.easymde = {
        /** @type {Object|null} EasyMDE 實例 */
        instance: null,

        /** @type {HTMLElement|null} 容器元素 */
        container: null,

        /** @type {HTMLTextAreaElement|null} 底層 textarea */
        textarea: null,

        /** @type {string|null} 當前主題 */
        currentTheme: null,

        /** @type {boolean} 安全全螢幕模式狀態 */
        _safeFullscreenActive: false,

        /** @type {boolean} 安全並排預覽模式狀態 */
        _safeSideBySideActive: false,

        /** @type {HTMLElement|null} 並排預覽包裝器 */
        _sbsWrap: null,

        /** @type {HTMLElement|null} 預覽側面板 */
        _previewSide: null,

        /** @type {HTMLElement|null} CodeMirror 滾動容器 */
        _cmScroller: null,

        /** @type {Function|null} CodeMirror 滾動事件處理器 */
        _onCmScroll: null,

        /** @type {Function|null} 預覽面板滾動事件處理器 */
        _onPreviewScroll: null,

        /** @type {Function|null} 內容變更更新預覽的處理器 */
        _onChangeUpdatePreview: null,

        /**
         * 初始化 EasyMDE 編輯器
         * @param {HTMLElement} container - 容器元素
         * @param {string} content - 初始內容
         * @param {string} theme - 主題 ('light' | 'dark')
         */
        async init(container, content, theme) {
            const EasyMDEClass = PAGE_WIN.EasyMDE;
            if (!EasyMDEClass) {
                throw new Error('EasyMDE not loaded');
            }

            this.container = container;
            this.currentTheme = theme;

            // 清空容器並創建 textarea
            container.innerHTML = '';
            this.textarea = document.createElement('textarea');
            this.textarea.id = Utils.generateId('easymde');
            container.appendChild(this.textarea);

            // 創建 EasyMDE 實例
            this.instance = new EasyMDEClass({
                element: this.textarea,
                initialValue: content || '',
                autofocus: false,
                spellChecker: false,
                autosave: { enabled: false },
                placeholder: '開始撰寫 Markdown...',
                status: ['lines', 'words', 'cursor'],
                toolbar: [
                    'bold', 'italic', 'strikethrough', 'heading', '|',
                    'quote', 'unordered-list', 'ordered-list', '|',
                    'link', 'image', 'table', 'horizontal-rule', '|',
                    'code', 'clean-block', '|',
                    'preview', 'side-by-side', 'fullscreen', '|',
                    'undo', 'redo', '|',
                    'guide'
                ],
                renderingConfig: {
                    singleLineBreaks: false,
                    codeSyntaxHighlighting: true
                },
                // 根據主題設定預覽區 class
                previewClass: (theme === 'dark')
                    ? ['editor-preview', 'editor-preview-dark']
                    : ['editor-preview']
            });

            // 注入安全佈局樣式
            this._injectSafeLayoutStyles();

            // 修補原生行為
            this._patchEasyMDEActions();

            // 確保並排佈局結構存在
            this._ensureSideBySideLayout();

            // 應用主題
            if (theme === 'dark') {
                this.applyDarkTheme();
            } else {
                this.applyLightTheme();
            }

            // 等待初始化完成
            await new Promise(r => setTimeout(r, 50));

            log('EasyMDE initialized');
        },

        /**
         * 修補 EasyMDE 的原生行為
         *
         * 設計意圖:
         * - 替換原生的 toggleFullScreen 和 toggleSideBySide
         * - 使用我們的「安全」版本,與 Modal 佈局協調
         */
        _patchEasyMDEActions() {
            if (!this.instance) return;

            const inst = this.instance;
            const originalTogglePreview = inst.togglePreview?.bind(inst);
            const originalIsPreviewActive = inst.isPreviewActive?.bind(inst);

            // 替換全螢幕行為
            inst.toggleFullScreen = () => {
                this._safeToggleFullScreen();
            };

            // 替換並排預覽行為
            inst.toggleSideBySide = () => {
                this._safeToggleSideBySide();
            };

            // 修補普通預覽(需要先關閉並排預覽)
            inst.togglePreview = () => {
                if (this._safeSideBySideActive) {
                    this._safeToggleSideBySide(false);
                }
                if (typeof originalTogglePreview === 'function') {
                    originalTogglePreview();
                }
            };

            // 修補 isPreviewActive(考慮並排預覽狀態)
            if (typeof originalIsPreviewActive === 'function') {
                inst.isPreviewActive = () => {
                    try {
                        return this._safeSideBySideActive || originalIsPreviewActive();
                    } catch (e) {
                        return this._safeSideBySideActive;
                    }
                };
            }
        },

        /**
         * 安全的全螢幕切換
         *
         * 設計意圖:
         * - 不使用 EasyMDE 原生的 position:fixed 全螢幕
         * - 而是與 Modal 的全螢幕功能協調
         *
         * @param {boolean} force - 強制設定狀態
         */
        _safeToggleFullScreen(force) {
            const next = (typeof force === 'boolean') ? force : !this._safeFullscreenActive;

            try {
                // 嘗試使用 Modal 的全螢幕功能
                if (typeof Modal !== 'undefined' && Modal?.toggleFullscreen && Modal?.isOpen) {
                    if (!!Modal.isFullscreen !== next) {
                        Modal.toggleFullscreen();
                    }
                    this._safeFullscreenActive = !!Modal.isFullscreen;
                } else {
                    this._safeFullscreenActive = next;
                }
            } catch (e) {
                this._safeFullscreenActive = next;
            }

            // 更新工具列圖標狀態
            this._setToolbarIconActive('fa-arrows-alt', this._safeFullscreenActive);
            this.refresh(true);
        },

        /**
         * 安全的並排預覽切換
         *
         * 設計意圖:
         * - 不使用 EasyMDE 原生的並排預覽(會有佈局問題)
         * - 使用自己實現的佈局,確保在 Modal 中正常工作
         *
         * @param {boolean} force - 強制設定狀態
         */
        _safeToggleSideBySide(force) {
            const next = (typeof force === 'boolean') ? force : !this._safeSideBySideActive;

            // 如果普通預覽正在開啟,先關閉
            try {
                if (this.instance?.isPreviewActive?.() && next) {
                    this.instance.togglePreview?.();
                }
            } catch (e) {
                // 忽略錯誤
            }

            // 確保佈局結構存在
            this._ensureSideBySideLayout();
            this._safeSideBySideActive = next;

            // 更新 DOM 狀態
            const cmEl = this._getCodeMirrorEl();
            if (this._sbsWrap) {
                this._sbsWrap.classList.toggle('mme-easymde-sbs-active', next);
            }
            if (this._previewSide) {
                this._previewSide.style.display = next ? 'block' : 'none';
            }
            if (cmEl) {
                cmEl.classList.toggle('mme-easymde-sbs-cm', next);
            }

            // 更新工具列圖標狀態
            this._setToolbarIconActive('fa-columns', next);

            // 啟動或停止滾動同步
            if (next) {
                this._updateSidePreviewNow();
                this._attachSideBySideListeners();
            } else {
                this._detachSideBySideListeners();
            }

            this.refresh(true);
        },

        /**
         * 取得 CodeMirror 元素
         * @returns {HTMLElement|null}
         */
        _getCodeMirrorEl() {
            try {
                return this.container?.querySelector('.CodeMirror') || null;
            } catch (e) {
                return null;
            }
        },

        /**
         * 確保並排預覽的 DOM 結構存在
         *
         * 設計意圖:
         * - 創建一個 flex 容器包裹 CodeMirror 和預覽面板
         * - 這樣可以實現真正的並排佈局
         */
        _ensureSideBySideLayout() {
            if (!this.container) return;
            if (this._sbsWrap && this._previewSide) return;

            const mdeContainer = this.container.querySelector('.EasyMDEContainer');
            const cmEl = this._getCodeMirrorEl();
            const statusbar = this.container.querySelector('.editor-statusbar');
            if (!mdeContainer || !cmEl) return;

            // 創建並排包裝器
            const wrap = document.createElement('div');
            wrap.className = 'mme-easymde-sbs-wrap';
            wrap.style.cssText = 'flex:1;min-height:0;display:flex;gap:0;';

            // 將包裝器插入到正確位置
            const toolbar = this.container.querySelector('.editor-toolbar');
            if (toolbar && statusbar) {
                mdeContainer.insertBefore(wrap, statusbar);
            } else if (toolbar) {
                mdeContainer.appendChild(wrap);
            }

            // 將 CodeMirror 移入包裝器
            wrap.appendChild(cmEl);

            // 創建或獲取預覽面板
            let previewSide = this.container.querySelector('.editor-preview-side');
            if (!previewSide) {
                previewSide = document.createElement('div');
                previewSide.className = 'editor-preview-side';
                previewSide.style.display = 'none';
                previewSide.setAttribute('data-mme-preview-side', '1');
            }
            wrap.appendChild(previewSide);

            this._sbsWrap = wrap;
            this._previewSide = previewSide;

            // 獲取 CodeMirror 滾動容器
            try {
                this._cmScroller = this.instance?.codemirror?.getScrollerElement?.() || null;
            } catch (e) {
                this._cmScroller = null;
            }
        },

        /**
         * 附加並排預覽的事件監聽器
         *
         * 設計意圖:
         * - 監聽編輯器內容變更,即時更新預覽
         * - 實現雙向滾動同步
         */
        _attachSideBySideListeners() {
            // 先清理舊的監聽器
            this._detachSideBySideListeners();

            if (!this._previewSide || !this._cmScroller || !this.instance?.codemirror) return;

            // 內容變更時更新預覽(節流)
            this._onChangeUpdatePreview = Utils.throttle(() => {
                if (!this._safeSideBySideActive) return;
                this._updateSidePreviewNow();
            }, 250);

            try {
                this.instance.codemirror.on('change', this._onChangeUpdatePreview);
            } catch (e) {
                log('EasyMDE change listener attach failed:', e.message);
            }

            // 滾動同步(使用 lock 防止循環觸發)
            let lock = false;

            this._onCmScroll = () => {
                if (!this._safeSideBySideActive || lock) return;

                const a = this._cmScroller;
                const b = this._previewSide;
                const aMax = a.scrollHeight - a.clientHeight;
                const bMax = b.scrollHeight - b.clientHeight;
                if (aMax <= 0 || bMax <= 0) return;

                lock = true;
                const ratio = a.scrollTop / aMax;
                b.scrollTop = ratio * bMax;
                setTimeout(() => { lock = false; }, 0);
            };

            this._onPreviewScroll = () => {
                if (!this._safeSideBySideActive || lock) return;

                const a = this._previewSide;
                const b = this._cmScroller;
                const aMax = a.scrollHeight - a.clientHeight;
                const bMax = b.scrollHeight - b.clientHeight;
                if (aMax <= 0 || bMax <= 0) return;

                lock = true;
                const ratio = a.scrollTop / aMax;
                b.scrollTop = ratio * bMax;
                setTimeout(() => { lock = false; }, 0);
            };

            this._cmScroller.addEventListener('scroll', this._onCmScroll, { passive: true });
            this._previewSide.addEventListener('scroll', this._onPreviewScroll, { passive: true });
        },

        /**
         * 移除並排預覽的事件監聽器
         */
        _detachSideBySideListeners() {
            // 移除內容變更監聽
            if (this.instance?.codemirror && this._onChangeUpdatePreview) {
                try {
                    this.instance.codemirror.off('change', this._onChangeUpdatePreview);
                } catch (e) {
                    // 忽略錯誤
                }
            }
            this._onChangeUpdatePreview = null;

            // 移除滾動監聽
            if (this._cmScroller && this._onCmScroll) {
                try {
                    this._cmScroller.removeEventListener('scroll', this._onCmScroll);
                } catch (e) {
                    // 忽略錯誤
                }
            }
            if (this._previewSide && this._onPreviewScroll) {
                try {
                    this._previewSide.removeEventListener('scroll', this._onPreviewScroll);
                } catch (e) {
                    // 忽略錯誤
                }
            }

            this._onCmScroll = null;
            this._onPreviewScroll = null;
        },

        /**
         * 立即更新並排預覽的內容
         */
        _updateSidePreviewNow() {
            if (!this._previewSide || !this.instance) return;

            const md = this.getValue();

            // 嘗試使用 EasyMDE 的預覽渲染器
            try {
                const pr = this.instance.options?.previewRender;
                if (typeof pr === 'function') {
                    const out = pr(md, this._previewSide);
                    if (typeof out === 'string') {
                        this._previewSide.innerHTML = out;
                        return;
                    }
                    // 如果返回的不是字串,可能是異步渲染到了元素中
                    if (this._previewSide.innerHTML && this._previewSide.innerHTML.trim()) {
                        return;
                    }
                }
            } catch (e) {
                // 繼續嘗試其他方法
            }

            // 嘗試使用 marked
            try {
                if (PAGE_WIN.marked?.parse) {
                    this._previewSide.innerHTML = PAGE_WIN.marked.parse(md);
                    return;
                }
            } catch (e) {
                // 繼續 fallback
            }

            // Fallback: 純文本顯示
            this._previewSide.innerHTML = `<pre>${Utils.escapeHtml(md)}</pre>`;
        },

        /**
         * 設定工具列圖標的 active 狀態
         * @param {string} faClass - Font Awesome class 名稱
         * @param {boolean} active - 是否啟用
         */
        _setToolbarIconActive(faClass, active) {
            try {
                const btn = this.container?.querySelector(`.editor-toolbar a.${faClass}`);
                if (btn) btn.classList.toggle('active', !!active);
            } catch (e) {
                // 忽略錯誤
            }
        },

        /**
         * 注入安全佈局樣式
         *
         * 設計意圖:
         * - 覆蓋 EasyMDE 原生的 fullscreen 和 side-by-side 樣式
         * - 使這些功能在 Modal 中正常工作(不使用 position:fixed)
         */
        _injectSafeLayoutStyles() {
            const id = `${CONFIG.prefix}easymde-safe-layout`;
            if (document.getElementById(id)) return;

            Utils.addStyle(`
                /* 覆蓋原生的 fullscreen 和 side-by-side 樣式 */
                .${CONFIG.prefix}editor .CodeMirror-fullscreen,
                .${CONFIG.prefix}editor .editor-toolbar.fullscreen,
                .${CONFIG.prefix}editor .editor-preview-side {
                    position: relative !important;
                    top: auto !important;
                    left: auto !important;
                    right: auto !important;
                    bottom: auto !important;
                    z-index: auto !important;
                }

                /* 並排預覽包裝器 */
                .${CONFIG.prefix}editor .mme-easymde-sbs-wrap {
                    flex: 1 !important;
                    min-height: 0 !important;
                    display: flex !important;
                    flex-direction: row !important;
                    align-items: stretch !important;
                    overflow: hidden !important;
                }

                /* 並排模式下的 CodeMirror */
                .${CONFIG.prefix}editor .mme-easymde-sbs-wrap .CodeMirror {
                    flex: 1 1 50% !important;
                    min-width: 0 !important;
                    height: auto !important;
                }

                /* 並排預覽面板 */
                .${CONFIG.prefix}editor .mme-easymde-sbs-wrap .editor-preview-side {
                    flex: 1 1 50% !important;
                    min-width: 0 !important;
                    height: auto !important;
                    overflow: auto !important;
                    border-left: 1px solid rgba(127,127,127,0.25);
                    padding: 14px 18px;
                }

                /* 並排模式啟用時顯示預覽 */
                .${CONFIG.prefix}editor .mme-easymde-sbs-wrap.mme-easymde-sbs-active .editor-preview-side {
                    display: block !important;
                }
            `, id);
        },

        /**
         * 應用深色主題
         */
        applyDarkTheme() {
            const p = CONFIG.prefix;
            const styleId = `${p}easymde-dark`;
            if (document.getElementById(styleId)) return;

            Utils.addStyle(`
                .${p}editor .EasyMDEContainer {
                    background: #1a1a2e;
                }
                .${p}editor .EasyMDEContainer .CodeMirror {
                    background: #1a1a2e;
                    color: #e8e8e8;
                    border-color: #2d3748;
                }
                .${p}editor .EasyMDEContainer .editor-toolbar {
                    background: #1e1e2e;
                    border-color: #2d3748;
                }
                .${p}editor .EasyMDEContainer .editor-toolbar button {
                    color: #e8e8e8 !important;
                }
                .${p}editor .EasyMDEContainer .editor-toolbar button:hover {
                    background: #2d3748;
                }
                .${p}editor .EasyMDEContainer .editor-toolbar button.active {
                    background: #4facfe;
                }
                .${p}editor .EasyMDEContainer .editor-statusbar {
                    background: #1e1e2e;
                    border-color: #2d3748;
                    color: #a0a0a0;
                }
                .${p}editor .EasyMDEContainer .editor-preview {
                    background: #1a1a2e;
                    color: #e8e8e8;
                }
                .${p}editor .EasyMDEContainer .editor-preview-side {
                    background: #16213e;
                    color: #e8e8e8;
                    border-color: #2d3748;
                }
                .${p}editor .EasyMDEContainer .CodeMirror-cursor {
                    border-left-color: #e8e8e8;
                }
                /* CodeMirror 語法高亮 - 深色 */
                .${p}editor .EasyMDEContainer .cm-header {
                    color: #61afef !important;
                }
                .${p}editor .EasyMDEContainer .cm-strong {
                    color: #e5c07b !important;
                }
                .${p}editor .EasyMDEContainer .cm-em {
                    color: #c678dd !important;
                }
                .${p}editor .EasyMDEContainer .cm-link {
                    color: #61afef !important;
                }
                .${p}editor .EasyMDEContainer .cm-url {
                    color: #98c379 !important;
                }
                .${p}editor .EasyMDEContainer .cm-comment {
                    color: #5c6370 !important;
                }
                .${p}editor .EasyMDEContainer .cm-quote {
                    color: #5c6370 !important;
                    font-style: italic;
                }
            `, styleId);
        },

        /**
         * 應用淺色主題
         */
        applyLightTheme() {
            const p = CONFIG.prefix;
            const styleId = `${p}easymde-light`;
            if (document.getElementById(styleId)) return;

            Utils.addStyle(`
                .${p}editor .EasyMDEContainer .editor-toolbar button {
                    color: #333 !important;
                }
                .${p}editor .EasyMDEContainer .editor-toolbar button:hover {
                    background: #e0e0e0;
                }
                .${p}editor .EasyMDEContainer .editor-toolbar button.active {
                    background: #007bff;
                    color: #fff !important;
                }
                .${p}editor .EasyMDEContainer .editor-toolbar i.separator {
                    border-left-color: #ccc;
                    border-right-color: #ccc;
                }
                .${p}editor .EasyMDEContainer .CodeMirror {
                    border-color: #ccc;
                }
                /* CodeMirror 語法高亮 - 淺色 */
                .${p}editor .EasyMDEContainer .cm-header {
                    color: #0550ae !important;
                }
                .${p}editor .EasyMDEContainer .cm-strong {
                    color: #24292e !important;
                }
                .${p}editor .EasyMDEContainer .cm-em {
                    color: #6f42c1 !important;
                }
                .${p}editor .EasyMDEContainer .cm-link {
                    color: #0366d6 !important;
                }
                .${p}editor .EasyMDEContainer .cm-url {
                    color: #22863a !important;
                }
                .${p}editor .EasyMDEContainer .cm-comment {
                    color: #6a737d !important;
                }
                .${p}editor .EasyMDEContainer .cm-quote {
                    color: #6a737d !important;
                    font-style: italic;
                }
            `, styleId);
        },

        /**
         * 移除淺色主題樣式
         */
        removeLightTheme() {
            document.getElementById(`${CONFIG.prefix}easymde-light`)?.remove();
        },

        /**
         * 移除深色主題樣式
         */
        removeDarkTheme() {
            document.getElementById(`${CONFIG.prefix}easymde-dark`)?.remove();
        },

        /**
         * 取得編輯器內容
         * @returns {string} Markdown 內容
         */
        getValue() {
            try {
                return this.instance?.value() || '';
            } catch (e) {
                log('EasyMDE getValue error:', e.message);
                return '';
            }
        },

        /**
         * 設定編輯器內容
         * @param {string} value - Markdown 內容
         */
        setValue(value) {
            try {
                this.instance?.value(value ?? '');
            } catch (e) {
                log('EasyMDE setValue error:', e.message);
            }
        },

        /**
         * 取得 HTML 輸出
         * @returns {string} HTML 內容
         */
        getHTML() {
            try {
                const md = this.getValue();
                if (PAGE_WIN.marked?.parse) {
                    return PAGE_WIN.marked.parse(md);
                }
                return `<div class="markdown-body"><pre>${Utils.escapeHtml(md)}</pre></div>`;
            } catch (e) {
                log('EasyMDE getHTML error:', e.message);
                return '';
            }
        },

        /**
         * 在游標位置插入內容
         * @param {string} value - 要插入的內容
         */
        insertValue(value) {
            try {
                const cm = this.instance?.codemirror;
                if (cm) {
                    const doc = cm.getDoc();
                    doc.replaceRange(value, doc.getCursor());
                }
            } catch (e) {
                log('EasyMDE insertValue error:', e.message);
            }
        },

        /**
         * 選取指定範圍的文字
         *
         * 設計意圖:
         * - 為 FindReplace 提供跨編輯器的選取支援
         * - 使用字元索引而非行列座標,便於統一處理
         *
         * @param {number} start - 起始字元索引
         * @param {number} end - 結束字元索引
         * @returns {boolean} 是否成功
         */
        selectRange(start, end) {
            try {
                const cm = this.instance?.codemirror;
                if (!cm) return false;

                const doc = cm.getDoc();
                const startPos = doc.posFromIndex(start);
                const endPos = doc.posFromIndex(end);

                doc.setSelection(startPos, endPos);
                cm.scrollIntoView({ from: startPos, to: endPos }, 100);
                cm.focus();

                return true;
            } catch (e) {
                log('EasyMDE selectRange error:', e.message);
                return false;
            }
        },

        /**
         * 聚焦編輯器
         */
        focus() {
            try {
                this.instance?.codemirror?.focus();
            } catch (e) {
                log('EasyMDE focus error:', e.message);
            }
        },

        /**
         * 刷新編輯器佈局
         * @param {boolean} force - 是否強制延遲刷新
         */
        refresh(force = false) {
            try {
                const cm = this.instance?.codemirror;
                if (!cm) return;
                if (force) {
                    setTimeout(() => {
                        try {
                            cm.refresh();
                        } catch (e) {
                            // 忽略刷新錯誤
                        }
                    }, 60);
                } else {
                    cm.refresh();
                }
            } catch (e) {
                log('EasyMDE refresh error:', e.message);
            }
        },

        /**
         * 設定主題
         * @param {string} theme - 'light' 或 'dark'
         */
        setTheme(theme) {
            if (theme === 'dark') {
                this.removeLightTheme();
                this.applyDarkTheme();
            } else {
                this.removeDarkTheme();
                this.applyLightTheme();
            }
            this.currentTheme = theme;
        },

        /**
         * 銷毀編輯器
         */
        destroy() {
            log('EasyMDE destroying...');

            // 移除並排預覽監聽器
            try {
                this._detachSideBySideListeners();
            } catch (e) {
                log('EasyMDE detach listeners error:', e.message);
            }

            // 銷毀 EasyMDE 實例
            try {
                this.instance?.toTextArea();
            } catch (e) {
                log('EasyMDE toTextArea error:', e.message);
            }

            // 清理主題樣式
            this.removeDarkTheme();
            this.removeLightTheme();

            // 重置狀態
            this.instance = null;
            this.textarea?.remove();
            this.textarea = null;
            this.container = null;
            this._sbsWrap = null;
            this._previewSide = null;
            this._cmScroller = null;
            this._safeFullscreenActive = false;
            this._safeSideBySideActive = false;

            log('EasyMDE destroyed');
        }
    };

    // ========================================
    // Toast UI Editor 適配器
    // ========================================

    /**
     * Toast UI Editor 適配器
     *
     * 設計意圖:
     * - 封裝 Toast UI Editor 的操作
     * - 處理主題切換
     *
     * 注意:Toast UI Editor 已停止維護,但仍需支持現有用戶
     */
    EditorAdapters.toastui = {
        /** @type {Object|null} Toast UI Editor 實例 */
        instance: null,

        /** @type {HTMLElement|null} 容器元素 */
        container: null,

        /** @type {HTMLElement|null} 編輯器 div */
        editorDiv: null,

        /** @type {string|null} 當前主題 */
        currentTheme: null,

        /**
         * 初始化 Toast UI Editor
         * @param {HTMLElement} container - 容器元素
         * @param {string} content - 初始內容
         * @param {string} theme - 主題 ('light' | 'dark')
         */
        async init(container, content, theme) {
            const ToastEditor = PAGE_WIN.toastui?.Editor;
            if (!ToastEditor) {
                throw new Error('Toast UI Editor not loaded');
            }

            this.container = container;
            this.currentTheme = theme;
            const isDark = theme === 'dark';

            // 清空容器並創建編輯器 div
            container.innerHTML = '';
            this.editorDiv = document.createElement('div');
            this.editorDiv.id = Utils.generateId('toastui');
            this.editorDiv.style.cssText = 'width:100%;height:100%;';
            container.appendChild(this.editorDiv);

            // 創建編輯器實例
            this.instance = new ToastEditor({
                el: this.editorDiv,
                initialValue: content || '',
                initialEditType: 'markdown',
                previewStyle: 'vertical',
                height: '100%',
                theme: isDark ? 'dark' : 'light',
                usageStatistics: false,
                hideModeSwitch: false,
                toolbarItems: [
                    ['heading', 'bold', 'italic', 'strike'],
                    ['hr', 'quote'],
                    ['ul', 'ol', 'task', 'indent', 'outdent'],
                    ['table', 'image', 'link'],
                    ['code', 'codeblock'],
                    ['scrollSync']
                ],
                placeholder: '開始撰寫 Markdown...'
            });

            // 應用深色主題樣式
            if (isDark) {
                this.applyDarkTheme();
            }

            // 等待初始化完成
            await new Promise(r => setTimeout(r, 80));

            log('Toast UI Editor initialized');
        },

        /**
         * 應用深色主題
         */
        applyDarkTheme() {
            const p = CONFIG.prefix;
            const styleId = `${p}toastui-dark`;

            if (!document.getElementById(styleId)) {
                Utils.addStyle(`
                    .${p}editor .toastui-editor-defaultUI {
                        border-color: #2d3748 !important;
                    }
                    .${p}editor .toastui-editor-dark {
                        background: #1a1a2e;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-toolbar {
                        background: #1e1e2e;
                        border-color: #2d3748;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-md-container {
                        background: #1a1a2e;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-md-preview {
                        background: #16213e;
                    }
                    .${p}editor .toastui-editor-dark .ProseMirror {
                        color: #e8e8e8;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-toolbar button {
                        color: #e8e8e8;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-toolbar button:hover {
                        background: #2d3748;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-mode-switch {
                        background: #1e1e2e;
                        border-color: #2d3748;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-mode-switch .tab-item {
                        color: #a0a0a0;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-mode-switch .tab-item.active {
                        color: #e8e8e8;
                    }
                    /* 深色模式下的語法高亮 */
                    .${p}editor .toastui-editor-dark .toastui-editor-md-heading {
                        color: #61afef !important;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-md-strong {
                        color: #e5c07b !important;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-md-emph {
                        color: #c678dd !important;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-md-link,
                    .${p}editor .toastui-editor-dark .toastui-editor-md-link-url {
                        color: #61afef !important;
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-md-code {
                        color: #98c379 !important;
                        background: rgba(255,255,255,0.1);
                    }
                    .${p}editor .toastui-editor-dark .toastui-editor-md-block-quote {
                        color: #5c6370 !important;
                    }
                `, styleId);
            }

            // 添加深色主題 class
            try {
                const ui = this.editorDiv?.querySelector('.toastui-editor-defaultUI');
                if (ui) ui.classList.add('toastui-editor-dark');
            } catch (e) {
                log('Toast UI add dark class error:', e.message);
            }
        },

        /**
         * 移除深色主題
         */
        removeDarkTheme() {
            document.getElementById(`${CONFIG.prefix}toastui-dark`)?.remove();
            try {
                const ui = this.editorDiv?.querySelector('.toastui-editor-defaultUI');
                if (ui) ui.classList.remove('toastui-editor-dark');
            } catch (e) {
                log('Toast UI remove dark class error:', e.message);
            }
        },

        /**
         * 取得編輯器內容
         * @returns {string} Markdown 內容
         */
        getValue() {
            try {
                return this.instance?.getMarkdown() || '';
            } catch (e) {
                log('Toast UI getValue error:', e.message);
                return '';
            }
        },

        /**
         * 設定編輯器內容
         * @param {string} value - Markdown 內容
         */
        setValue(value) {
            try {
                this.instance?.setMarkdown(value ?? '');
            } catch (e) {
                log('Toast UI setValue error:', e.message);
            }
        },

        /**
         * 取得 HTML 輸出
         * @returns {string} HTML 內容
         */
        getHTML() {
            try {
                return this.instance?.getHTML() || '';
            } catch (e) {
                log('Toast UI getHTML error:', e.message);
                return '';
            }
        },

        /**
         * 在游標位置插入內容
         * @param {string} value - 要插入的內容
         */
        insertValue(value) {
            try {
                this.instance?.insertText(value);
            } catch (e) {
                log('Toast UI insertValue error:', e.message);
            }
        },

        /**
         * 選取指定範圍的文字(字元索引)
         *
         * 安全策略:
         * - 優先使用 Toast UI 可能提供的 getCodeMirror()
         * - 若無法取得 CodeMirror,則 fallback focus(避免依賴內部私有結構)
         */
        selectRange(start, end) {
            try {
                // 1) 官方/半官方可能存在的 API
                const cm =
                    this.instance?.getCodeMirror?.() ||
                    this.instance?.mdEditor?.cm ||
                    this.instance?.mdEditor?.editor?.cm ||
                    this.instance?.mdEditor?.editor?.getCodeMirror?.();

                if (!cm) {
                    this.focus();
                    return false;
                }

                const doc = cm.getDoc?.() || cm.doc;
                if (!doc?.posFromIndex || !doc?.setSelection) {
                    this.focus();
                    return false;
                }

                const from = doc.posFromIndex(start);
                const to = doc.posFromIndex(end);
                doc.setSelection(from, to);
                cm.scrollIntoView?.({ from, to }, 100);
                cm.focus?.();
                return true;
            } catch (e) {
                log('Toast UI selectRange error:', e?.message || e);
                try { this.focus(); } catch (_) {}
                return false;
            }
        },

        /**
         * 聚焦編輯器
         */
        focus() {
            try {
                this.instance?.focus();
            } catch (e) {
                log('Toast UI focus error:', e.message);
            }
        },

        /**
         * 刷新編輯器佈局
         * @param {boolean} force - 是否強制延遲刷新
         */
        refresh(force = false) {
            try {
                if (!this.editorDiv) return;
                if (force) {
                    setTimeout(() => window.dispatchEvent(new Event('resize')), 60);
                } else {
                    window.dispatchEvent(new Event('resize'));
                }
            } catch (e) {
                log('Toast UI refresh error:', e.message);
            }
        },

        /**
         * 設定主題
         * @param {string} theme - 'light' 或 'dark'
         */
        setTheme(theme) {
            if (theme === 'dark') {
                this.applyDarkTheme();
            } else {
                this.removeDarkTheme();
            }
            this.currentTheme = theme;
        },

        /**
         * 銷毀編輯器
         */
        destroy() {
            log('Toast UI Editor destroying...');

            try {
                this.instance?.destroy();
            } catch (e) {
                log('Toast UI destroy error:', e.message);
            }

            this.removeDarkTheme();
            this.instance = null;
            this.editorDiv?.remove();
            this.editorDiv = null;
            this.container = null;

            log('Toast UI Editor destroyed');
        }
    };

    // ========================================
    // Cherry Markdown 適配器
    // ========================================

    /**
     * Cherry Markdown 編輯器適配器
     *
     * 設計意圖:
     * - 封裝騰訊開源的 Cherry Markdown 編輯器
     * - 處理 KaTeX 依賴的載入和等待
     * - 實現完整的深色主題支持
     *
     * 特殊處理:
     * - Cherry 需要 KaTeX 來渲染數學公式,初始化時必須等待 KaTeX 就緒
     * - Cherry 原生的深色主題支持不完善,需要大量 CSS 覆蓋
     */
    EditorAdapters.cherry = {
        /** @type {Object|null} Cherry 實例 */
        instance: null,

        /** @type {HTMLElement|null} 容器元素 */
        container: null,

        /** @type {HTMLElement|null} 編輯器 div */
        editorDiv: null,

        /** @type {string|null} 當前主題 */
        currentTheme: null,

        /** @type {string} 深色主題樣式 ID */
        _darkStyleId: `${CONFIG.prefix}cherry-dark`,

        /**
         * 初始化 Cherry Markdown 編輯器
         * @param {HTMLElement} container - 容器元素
         * @param {string} content - 初始內容
         * @param {string} theme - 主題 ('light' | 'dark')
         */
        async init(container, content, theme) {
            const CherryClass = PAGE_WIN.Cherry;
            if (!CherryClass) {
                throw new Error('Cherry Markdown not loaded');
            }

            // 等待 KaTeX 就緒(Cherry 依賴 KaTeX 渲染數學公式)
            const katexReady = () => !!(
                PAGE_WIN.katex &&
                typeof PAGE_WIN.katex.renderToString === 'function'
            );

            try {
                await Utils.waitFor(katexReady, 10000, 100);
            } catch (e) {
                throw new Error('KaTeX 未就緒,Cherry 無法初始化');
            }

            this.container = container;
            this.currentTheme = theme;
            const isDark = theme === 'dark';

            // 清空容器並創建編輯器 div
            container.innerHTML = '';
            this.editorDiv = document.createElement('div');
            this.editorDiv.id = Utils.generateId('cherry');
            this.editorDiv.style.cssText = 'width:100%;height:100%;';
            container.appendChild(this.editorDiv);

            // 工具列配置
            const toolbarConfig = {
                toolbar: [
                    'bold', 'italic', 'strikethrough', '|',
                    'color', 'header', '|',
                    'list',
                    {
                        insert: [
                            'image', 'audio', 'video', 'link', 'hr', 'br',
                            'code', 'formula', 'toc', 'table', 'pdf', 'word'
                        ]
                    },
                    'graph',
                    'togglePreview',
                    'settings'
                ],
                bubble: [
                    'bold', 'italic', 'underline', 'strikethrough',
                    'sub', 'sup', 'quote', '|', 'size', 'color'
                ],
                float: [
                    'h1', 'h2', 'h3', '|',
                    'checklist', 'quote', 'quickTable', 'code'
                ]
            };

            // 創建 Cherry 實例
            try {
                this.instance = new CherryClass({
                    id: this.editorDiv.id,
                    value: content || '',
                    editor: {
                        theme: isDark ? 'dark' : 'default',
                        height: '100%',
                        defaultModel: 'edit&preview'
                    },
                    toolbars: {
                        theme: isDark ? 'dark' : 'light',
                        toolbar: toolbarConfig.toolbar,
                        bubble: toolbarConfig.bubble,
                        float: toolbarConfig.float
                    },
                    previewer: {
                        theme: isDark ? 'dark' : 'default'
                    },
                    engine: {
                        global: {
                            urlProcessor: (url) => url
                        },
                        syntax: {
                            table: { enableChart: false },
                            fontEmphasis: { allowWhitespace: true },
                            mathBlock: { engine: 'katex' },
                            inlineMath: { engine: 'katex' }
                        }
                    },
                    callback: {
                        afterInit: () => {
                            log('Cherry afterInit callback');
                        },
                        afterChange: () => {
                            // 內容變更回調(可用於自動保存等)
                        }
                    }
                });
            } catch (e) {
                logError('Cherry init error:', e);
                throw new Error(`Cherry 初始化失敗:${e.message}`);
            }

            // 應用深色主題樣式
            if (isDark) {
                this.applyDarkTheme();
            }

            // 等待初始化完成
            await new Promise(r => setTimeout(r, 120));

            log('Cherry Markdown initialized');
        },

        /**
         * 套用深色主題
         *
         * 設計意圖:
         * - Cherry 原生的深色主題支持不完善
         * - 需要大量 CSS 來覆蓋各種元素(工具列、下拉選單、編輯器、預覽區等)
         * - 確保在深色模式下有良好的可讀性
         */
        applyDarkTheme() {
            const p = CONFIG.prefix;
            const styleId = this._darkStyleId;
            if (document.getElementById(styleId)) return;

            Utils.addStyle(`
/* ===== Cherry Markdown 深色主題 ===== */

/* 主容器 */
.${p}editor .cherry,
.cherry {
    background: #1a1a2e !important;
    border: none !important;
    color: #e0e0e0 !important;
}

/* ===== 工具列 ===== */
.${p}editor .cherry .cherry-toolbar,
.cherry .cherry-toolbar {
    background: #1e1e2e !important;
    border-color: #2d3748 !important;
}

.${p}editor .cherry .cherry-toolbar .cherry-toolbar-button,
.cherry .cherry-toolbar .cherry-toolbar-button {
    color: #e0e0e0 !important;
}

.${p}editor .cherry .cherry-toolbar .cherry-toolbar-button:hover,
.cherry .cherry-toolbar .cherry-toolbar-button:hover {
    background: #2d3748 !important;
}

.${p}editor .cherry .cherry-toolbar svg,
.cherry .cherry-toolbar svg {
    fill: #e0e0e0 !important;
    stroke: #e0e0e0 !important;
}

/* ===== 下拉選單 ===== */
.cherry-dropdown,
.cherry-dropdown-menu,
.cherry-insert-table-menu,
.cherry-previewer-table-content-handler__input,
.cherry-color-wrap,
.cherry-dropdown-item,
.cherry-bubble,
.cherry-floatmenu,
.cherry .cherry-dropdown,
.cherry .cherry-dropdown-menu {
    background: #1e1e2e !important;
    background-color: #1e1e2e !important;
    border: 1px solid #2d3748 !important;
    color: #e0e0e0 !important;
    box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important;
}

/* 下拉選單項目 */
.cherry-dropdown-item,
.cherry-dropdown .cherry-dropdown-item,
.cherry-dropdown-menu .cherry-dropdown-item,
.cherry-dropdown-menu > *,
.cherry-dropdown > *,
.cherry-insert-table-menu td,
.cherry-color-wrap span,
.cherry-bubble button,
.cherry-floatmenu button {
    background: transparent !important;
    background-color: transparent !important;
    color: #e0e0e0 !important;
    border-color: #2d3748 !important;
}

.cherry-dropdown-item:hover,
.cherry-dropdown .cherry-dropdown-item:hover,
.cherry-dropdown-menu .cherry-dropdown-item:hover,
.cherry-bubble button:hover,
.cherry-floatmenu button:hover {
    background: #2d3748 !important;
    background-color: #2d3748 !important;
    color: #fff !important;
}

/* 表格插入選單 */
.cherry-insert-table-menu {
    background: #1e1e2e !important;
}

.cherry-insert-table-menu td {
    border-color: #3d4758 !important;
    background: transparent !important;
}

.cherry-insert-table-menu td.active,
.cherry-insert-table-menu td:hover {
    background: #4facfe !important;
}

/* 顏色選擇器 */
.cherry-color-wrap {
    background: #1e1e2e !important;
}

.cherry-color-wrap .cherry-color-item {
    border-color: #3d4758 !important;
}

/* ===== 編輯區主體 ===== */
.${p}editor .cherry .cherry-editor,
.cherry .cherry-editor {
    background: #1a1a2e !important;
}

/* ===== CodeMirror 編輯器 ===== */
.${p}editor .cherry .CodeMirror,
.cherry .CodeMirror {
    background: #1a1a2e !important;
    color: #e8e8e8 !important;
}

/* CodeMirror 所有文字 */
.${p}editor .cherry .CodeMirror pre,
.${p}editor .cherry .CodeMirror-line,
.${p}editor .cherry .CodeMirror-line span,
.${p}editor .cherry .CodeMirror-code,
.cherry .CodeMirror pre,
.cherry .CodeMirror-line,
.cherry .CodeMirror-line span,
.cherry .CodeMirror-code {
    color: #e8e8e8 !important;
}

/* CodeMirror 游標 */
.${p}editor .cherry .CodeMirror-cursor,
.cherry .CodeMirror-cursor {
    border-left-color: #4facfe !important;
    border-left-width: 2px !important;
}

/* CodeMirror 選中 */
.${p}editor .cherry .CodeMirror-selected,
.${p}editor .cherry .CodeMirror-selectedtext,
.cherry .CodeMirror-selected,
.cherry .CodeMirror-selectedtext {
    background: rgba(79,172,254,0.3) !important;
}

/* CodeMirror 行號 */
.${p}editor .cherry .CodeMirror-gutters,
.cherry .CodeMirror-gutters {
    background: #16213e !important;
    border-color: #2d3748 !important;
}

.${p}editor .cherry .CodeMirror-linenumber,
.cherry .CodeMirror-linenumber {
    color: #6c8eb0 !important;
}

/* CodeMirror 當前行 */
.${p}editor .cherry .CodeMirror-activeline-background,
.cherry .CodeMirror-activeline-background {
    background: rgba(79,172,254,0.08) !important;
}

/* ===== Markdown 語法高亮 ===== */

/* 標題 */
.${p}editor .cherry .cm-header,
.cherry .cm-header,
.${p}editor .cherry .cm-header-1,
.${p}editor .cherry .cm-header-2,
.${p}editor .cherry .cm-header-3,
.${p}editor .cherry .cm-header-4,
.${p}editor .cherry .cm-header-5,
.${p}editor .cherry .cm-header-6,
.cherry .cm-header-1,
.cherry .cm-header-2,
.cherry .cm-header-3,
.cherry .cm-header-4,
.cherry .cm-header-5,
.cherry .cm-header-6 {
    color: #61afef !important;
    font-weight: bold !important;
}

/* 粗體 */
.${p}editor .cherry .cm-strong,
.cherry .cm-strong {
    color: #fff !important;
    font-weight: bold !important;
}

/* 斜體 */
.${p}editor .cherry .cm-em,
.cherry .cm-em {
    color: #c9d1d9 !important;
    font-style: italic !important;
}

/* 刪除線 */
.${p}editor .cherry .cm-strikethrough,
.cherry .cm-strikethrough {
    color: #8b949e !important;
    text-decoration: line-through !important;
}

/* 連結 */
.${p}editor .cherry .cm-link,
.${p}editor .cherry .cm-url,
.cherry .cm-link,
.cherry .cm-url {
    color: #58a6ff !important;
}

/* 代碼 */
.${p}editor .cherry .cm-comment,
.cherry .cm-comment {
    color: #8b949e !important;
}

.${p}editor .cherry .cm-string,
.cherry .cm-string {
    color: #a5d6ff !important;
}

.${p}editor .cherry .cm-keyword,
.cherry .cm-keyword {
    color: #ff7b72 !important;
}

.${p}editor .cherry .cm-atom,
.cherry .cm-atom {
    color: #d2a8ff !important;
}

.${p}editor .cherry .cm-number,
.cherry .cm-number {
    color: #ffa657 !important;
}

.${p}editor .cherry .cm-variable,
.${p}editor .cherry .cm-variable-2,
.${p}editor .cherry .cm-variable-3,
.cherry .cm-variable,
.cherry .cm-variable-2,
.cherry .cm-variable-3 {
    color: #ffa657 !important;
}

.${p}editor .cherry .cm-def,
.cherry .cm-def {
    color: #79c0ff !important;
}

.${p}editor .cherry .cm-property,
.cherry .cm-property {
    color: #d2a8ff !important;
}

.${p}editor .cherry .cm-operator,
.cherry .cm-operator {
    color: #79c0ff !important;
}

.${p}editor .cherry .cm-meta,
.cherry .cm-meta {
    color: #8b949e !important;
}

.${p}editor .cherry .cm-tag,
.cherry .cm-tag {
    color: #7ee787 !important;
}

.${p}editor .cherry .cm-attribute,
.cherry .cm-attribute {
    color: #d2a8ff !important;
}

.${p}editor .cherry .cm-bracket,
.cherry .cm-bracket {
    color: #79c0ff !important;
}

/* 引用 */
.${p}editor .cherry .cm-quote,
.cherry .cm-quote {
    color: #8b949e !important;
    font-style: italic !important;
}

/* 列表符號 */
.${p}editor .cherry .cm-formatting-list,
.cherry .cm-formatting-list {
    color: #ffa657 !important;
}

/* 行內代碼 */
.${p}editor .cherry .cm-inline-code,
.cherry .cm-inline-code,
.${p}editor .cherry .cm-formatting-code,
.cherry .cm-formatting-code {
    color: #a5d6ff !important;
    background: rgba(110,118,129,0.2) !important;
}

/* ===== 預覽區 ===== */
.${p}editor .cherry .cherry-previewer,
.cherry .cherry-previewer {
    background: #16213e !important;
    color: #e0e0e0 !important;
    border-color: #2d3748 !important;
}

/* 預覽區標題 */
.${p}editor .cherry .cherry-previewer h1,
.${p}editor .cherry .cherry-previewer h2,
.${p}editor .cherry .cherry-previewer h3,
.${p}editor .cherry .cherry-previewer h4,
.${p}editor .cherry .cherry-previewer h5,
.${p}editor .cherry .cherry-previewer h6,
.cherry .cherry-previewer h1,
.cherry .cherry-previewer h2,
.cherry .cherry-previewer h3,
.cherry .cherry-previewer h4,
.cherry .cherry-previewer h5,
.cherry .cherry-previewer h6 {
    color: #fff !important;
    border-color: #2d3748 !important;
}

/* 預覽區段落和列表 - 排除 KaTeX 數學公式以保留顏色命令 */
.${p}editor .cherry .cherry-previewer p,
.${p}editor .cherry .cherry-previewer li,
.${p}editor .cherry .cherry-previewer td,
.${p}editor .cherry .cherry-previewer th,
.cherry .cherry-previewer p,
.cherry .cherry-previewer li,
.cherry .cherry-previewer td,
.cherry .cherry-previewer th {
    color: #e0e0e0 !important;
}

/* span 需要特殊處理:排除帶有 inline style 的元素(如 KaTeX 顏色) */
.${p}editor .cherry .cherry-previewer span:not([style]),
.cherry .cherry-previewer span:not([style]) {
    color: #e0e0e0 !important;
}

/* KaTeX 公式保持原生樣式 */
.${p}editor .cherry .cherry-previewer .katex,
.${p}editor .cherry .cherry-previewer .katex *,
.cherry .cherry-previewer .katex,
.cherry .cherry-previewer .katex * {
    color: inherit !important;
}

/* 允許 KaTeX 中的 color 命令生效 */
.${p}editor .cherry .cherry-previewer .katex [style*="color"],
.cherry .cherry-previewer .katex [style*="color"] {
    color: unset !important;
}

/* 預覽區連結 */
.${p}editor .cherry .cherry-previewer a,
.cherry .cherry-previewer a {
    color: #58a6ff !important;
}

/* 預覽區行內代碼 */
.${p}editor .cherry .cherry-previewer code,
.cherry .cherry-previewer code {
    background: rgba(110,118,129,0.3) !important;
    color: #a5d6ff !important;
    padding: 2px 6px !important;
    border-radius: 4px !important;
}

/* 預覽區代碼塊 */
.${p}editor .cherry .cherry-previewer pre,
.cherry .cherry-previewer pre {
    background: #0d1117 !important;
    border: 1px solid #30363d !important;
    border-radius: 6px !important;
}

.${p}editor .cherry .cherry-previewer pre code,
.cherry .cherry-previewer pre code {
    background: transparent !important;
    color: #e0e0e0 !important;
    padding: 0 !important;
}

/* 預覽區引用塊 */
.${p}editor .cherry .cherry-previewer blockquote,
.cherry .cherry-previewer blockquote {
    background: rgba(79,172,254,0.1) !important;
    border-left: 4px solid #4facfe !important;
    color: #a0a0a0 !important;
    padding: 12px 16px !important;
    margin: 16px 0 !important;
}

/* 預覽區表格 */
.${p}editor .cherry .cherry-previewer table,
.cherry .cherry-previewer table {
    border-color: #30363d !important;
}

.${p}editor .cherry .cherry-previewer th,
.cherry .cherry-previewer th {
    background: #21262d !important;
    border-color: #30363d !important;
}

.${p}editor .cherry .cherry-previewer td,
.cherry .cherry-previewer td {
    border-color: #30363d !important;
}

.${p}editor .cherry .cherry-previewer tr:nth-child(even),
.cherry .cherry-previewer tr:nth-child(even) {
    background: rgba(255,255,255,0.02) !important;
}

/* 預覽區分隔線 */
.${p}editor .cherry .cherry-previewer hr,
.cherry .cherry-previewer hr {
    border-color: #30363d !important;
    background: #30363d !important;
}

/* ===== 側邊欄/目錄 ===== */
.${p}editor .cherry .cherry-sidebar,
.cherry .cherry-sidebar {
    background: #16213e !important;
    border-color: #2d3748 !important;
}

.${p}editor .cherry .cherry-toc,
.cherry .cherry-toc {
    color: #e0e0e0 !important;
}

.${p}editor .cherry .cherry-toc a,
.cherry .cherry-toc a {
    color: #a0a0a0 !important;
}

.${p}editor .cherry .cherry-toc a:hover,
.cherry .cherry-toc a:hover {
    color: #4facfe !important;
}

/* ===== 狀態列 ===== */
.${p}editor .cherry .cherry-status,
.cherry .cherry-status {
    background: #1e1e2e !important;
    color: #a0a0a0 !important;
    border-color: #2d3748 !important;
}

/* ===== 編輯模式切換按鈕 ===== */
.${p}editor .cherry .cherry-editor-mask,
.cherry .cherry-editor-mask,
.${p}editor .cherry .cherry-switch-model,
.cherry .cherry-switch-model {
    background: #1e1e2e !important;
    color: #e0e0e0 !important;
}

/* ===== 滾動條 ===== */
.${p}editor .cherry ::-webkit-scrollbar,
.cherry ::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

.${p}editor .cherry ::-webkit-scrollbar-track,
.cherry ::-webkit-scrollbar-track {
    background: #1a1a2e;
}

.${p}editor .cherry ::-webkit-scrollbar-thumb,
.cherry ::-webkit-scrollbar-thumb {
    background: #3d4758;
    border-radius: 4px;
}

.${p}editor .cherry ::-webkit-scrollbar-thumb:hover,
.cherry ::-webkit-scrollbar-thumb:hover {
    background: #4a5568;
}
            `, styleId);
        },

        /**
         * 移除深色主題樣式
         */
        removeDarkTheme() {
            document.getElementById(this._darkStyleId)?.remove();
        },

        /**
         * 取得編輯器內容
         * @returns {string} Markdown 內容
         */
        getValue() {
            try {
                return this.instance?.getValue() || '';
            } catch (e) {
                log('Cherry getValue error:', e.message);
                return '';
            }
        },

        /**
         * 設定編輯器內容
         * @param {string} value - Markdown 內容
         */
        setValue(value) {
            try {
                this.instance?.setValue(value ?? '');
            } catch (e) {
                log('Cherry setValue error:', e.message);
            }
        },

        /**
         * 取得 HTML 輸出
         * @returns {string} HTML 內容
         */
        getHTML() {
            try {
                return this.instance?.getHtml() || '';
            } catch (e) {
                log('Cherry getHTML error:', e.message);
                return '';
            }
        },

        /**
         * 在游標位置插入內容
         * @param {string} value - 要插入的內容
         */
        insertValue(value) {
            try {
                this.instance?.insert(value);
            } catch (e) {
                log('Cherry insertValue error:', e.message);
            }
        },


        /**
         * 選取指定範圍的文字(字元索引)
         * Cherry 官方 API 提供 getCodeMirror(),穩定可用
         */
        selectRange(start, end) {
            try {
                const cm = this.instance?.getCodeMirror?.();
                if (!cm) {
                    this.focus();
                    return false;
                }

                const doc = cm.getDoc?.() || cm.doc;
                if (!doc?.posFromIndex || !doc?.setSelection) {
                    this.focus();
                    return false;
                }

                const from = doc.posFromIndex(start);
                const to = doc.posFromIndex(end);

                doc.setSelection(from, to);
                cm.scrollIntoView?.({ from, to }, 100);
                cm.focus?.();

                return true;
            } catch (e) {
                log('Cherry selectRange error:', e?.message || e);
                try { this.focus(); } catch (_) {}
                return false;
            }
        },

        /**
         * 聚焦編輯器
         */
        focus() {
            try {
                this.instance?.focus();
            } catch (e) {
                log('Cherry focus error:', e.message);
            }
        },

        /**
         * 刷新編輯器佈局
         * @param {boolean} force - 是否強制延遲刷新
         */
        refresh(force = false) {
            try {
                if (force) {
                    setTimeout(() => window.dispatchEvent(new Event('resize')), 60);
                } else {
                    window.dispatchEvent(new Event('resize'));
                }
            } catch (e) {
                log('Cherry refresh error:', e.message);
            }
        },

        /**
         * 設定主題
         * @param {string} theme - 'light' 或 'dark'
         */
        setTheme(theme) {
            if (theme === 'dark') {
                this.applyDarkTheme();
            } else {
                this.removeDarkTheme();
            }
            this.currentTheme = theme;
        },

        /**
         * 銷毀編輯器
         */
        destroy() {
            log('Cherry Markdown destroying...');

            try {
                this.instance?.destroy();
            } catch (e) {
                log('Cherry destroy error:', e.message);
            }

            this.removeDarkTheme();
            this.instance = null;
            this.editorDiv?.remove();
            this.editorDiv = null;
            this.container = null;

            log('Cherry Markdown destroyed');
        }
    };

    // ========================================
    // Vditor 適配器(含模式切換保護)
    // ========================================

    /**
     * Vditor 編輯器適配器
     *
     * 設計意圖:
     * - 封裝 Vditor 編輯器的操作
     * - 實現模式切換保護機制,防止內容丟失
     *
     * 核心問題:
     * Vditor 有三種編輯模式(SV/IR/WYSIWYG),在模式切換時
     * 內部同步機制有時會失敗,導致內容丟失或縮水。
     *
     * 解決方案:
     * 1. 以 SV 模式為「真相來源」(SV 模式下 getValue() 最可靠)
     * 2. 定期保存 SV 模式的快照
     * 3. 監聽模式變化,自動檢測內容縮水並還原
     * 4. 提供手動還原和下載快照功能
     */
    EditorAdapters.vditor = {
        /** @type {Object|null} Vditor 實例 */
        instance: null,

        /** @type {HTMLElement|null} 容器元素 */
        container: null,

        /** @type {HTMLElement|null} 編輯器 div */
        editorDiv: null,

        /** @type {string|null} 當前主題 */
        currentTheme: null,

        /** @type {MutationObserver|null} 模式變化觀察器 */
        _modeObserver: null,

        /** @type {string|null} 上次偵測到的模式 */
        _lastMode: null,

        /** @type {boolean} 是否啟用保護機制 */
        _guardEnabled: true,

        /** @type {number} 上次顯示保護提示的時間 */
        _lastGuardToastAt: 0,

        /** @type {Function|null} 點擊事件捕獲處理器 */
        _captureClickHandler: null,

        /** @type {Function|null} 鍵盤事件捕獲處理器 */
        _captureKeyHandler: null,

        /** @type {number|null} 自動快照計時器 */
        _autoSnapshotTimer: null,

        /** @type {number|null} 內容檢查計時器 */
        _contentCheckTimer: null,

        // ===== SV 快照(核心保護機制)=====
        /** @type {string|null} 最後一次 SV 模式的內容 */
        _lastSVContent: null,

        /** @type {number} 最後一次 SV 內容的長度(去空白) */
        _lastSVLength: 0,

        /** @type {string|null} 最後一次 SV 內容的 hash */
        _lastSVHash: null,

        /** @type {number} 最後一次 SV 快照的時間戳 */
        _lastSVTimestamp: 0,

        // ===== 還原鎖(防止重複還原)=====
        /** @type {boolean} 是否正在還原 */
        _restoreLock: false,

        /** @type {number} 上次還原的時間 */
        _lastRestoreAt: 0,

        /**
         * 初始化 Vditor 編輯器
         * @param {HTMLElement} container - 容器元素
         * @param {string} content - 初始內容
         * @param {string} theme - 主題 ('light' | 'dark')
         */
        async init(container, content, theme) {
            const VditorClass = PAGE_WIN.Vditor;
            if (!VditorClass) {
                throw new Error('Vditor not loaded');
            }

            this.container = container;
            this.currentTheme = theme;
            const isDark = theme === 'dark';

            // 讀取偏好的編輯模式(預設使用 SV,因為最穩定)
            let preferredMode = Utils.storage.get(CONFIG.storageKeys.editorMode, 'sv');
            if (!['sv', 'ir', 'wysiwyg'].includes(preferredMode)) {
                preferredMode = 'sv';
            }

            // 檢查安全重建標記(用於安全切換模式後的重建)
            const safeFlag = Utils.storage.get(CONFIG.storageKeys.vditorSafeReinitFlag, false);
            if (safeFlag) {
                try {
                    Utils.clearEditorCache('vditor');
                } catch (e) {
                    // 忽略清除錯誤
                }
                Utils.storage.remove(CONFIG.storageKeys.vditorSafeReinitFlag);
            }

            // 取得 CDN 基礎路徑
            const cdnBase = Loader.getCdnBase('vditor') || CONFIG.editors.vditor.cdn[0];
            const cfg = CONFIG.editors.vditor;

            // 清空容器並創建編輯器 div
            container.innerHTML = '';
            this.editorDiv = document.createElement('div');
            this.editorDiv.id = Utils.generateId('vditor');
            this.editorDiv.style.cssText = 'width:100%;height:100%;';
            container.appendChild(this.editorDiv);

            // 創建 Vditor 實例
            await new Promise((resolve, reject) => {
                try {
                    this.instance = new VditorClass(this.editorDiv, {
                        cdn: cdnBase,
                        mode: preferredMode,
                        theme: isDark ? 'dark' : 'classic',
                        icon: 'material',
                        lang: 'zh_TW',
                        width: '100%',
                        height: '100%',
                        placeholder: '開始撰寫 Markdown...',
                        toolbar: VDITOR_TOOLBAR,
                        toolbarConfig: { pin: true },
                        cache: {
                            enable: true,
                            id: cfg.cacheId
                        },
                        counter: {
                            enable: true,
                            type: 'markdown'
                        },
                        outline: {
                            enable: true,
                            position: 'right'
                        },
                        hint: {
                            emojiPath: `${cdnBase}/dist/images/emoji`
                        },
                        preview: {
                            theme: { current: isDark ? 'dark' : 'light' },
                            hljs: {
                                style: isDark ? 'dracula' : 'github',
                                lineNumber: true
                            },
                            markdown: {
                                toc: true,
                                mark: true,
                                footnotes: true,
                                autoSpace: true
                            },
                            math: { engine: 'KaTeX' }
                        },
                        value: content || '',
                        after: () => {
                            // 初始化完成後保存 SV 快照
                            setTimeout(() => {
                                this._saveSVSnapshot('init');
                                VditorDiag.log('init', {
                                    mode: preferredMode,
                                    contentLen: (content || '').replace(/\s/g, '').length
                                });
                            }, 200);
                            resolve();
                        }
                    });
                } catch (e) {
                    reject(e);
                }
            });

            // 安裝模式切換保護
            this._installModeSwitchGuard();
            this._lastMode = this._detectModeFromDOM();

            // 等待完全就緒
            await new Promise(r => setTimeout(r, 80));

            log('Vditor initialized, mode:', preferredMode);
        },

        /**
         * 偵測當前編輯模式
         *
         * 設計意圖:
         * - 使用多重策略偵測當前模式
         * - 因為 Vditor 的內部狀態有時不可靠
         *
         * @returns {string|null} 'sv' | 'ir' | 'wysiwyg' | null
         */
        _detectModeFromDOM() {
            try {
                // 策略 1:使用 API
                if (this.instance?.getCurrentMode) {
                    const mode = this.instance.getCurrentMode();
                    if (mode && ['sv', 'ir', 'wysiwyg'].includes(mode)) {
                        return mode;
                    }
                }

                // 策略 2:檢查 class
                const root = this.editorDiv?.querySelector('.vditor');
                if (root) {
                    if (root.classList.contains('vditor--sv')) return 'sv';
                    if (root.classList.contains('vditor--ir')) return 'ir';
                    if (root.classList.contains('vditor--wysiwyg')) return 'wysiwyg';

                    // 策略 3:檢查可見性
                    const svEl = root.querySelector('.vditor-sv');
                    const irEl = root.querySelector('.vditor-ir');
                    const wysiwygEl = root.querySelector('.vditor-wysiwyg');

                    const isVisible = (el) => {
                        if (!el) return false;
                        const style = window.getComputedStyle(el);
                        return style.display !== 'none' &&
                               style.visibility !== 'hidden' &&
                               el.offsetHeight > 0;
                    };

                    if (isVisible(svEl)) return 'sv';
                    if (isVisible(irEl)) return 'ir';
                    if (isVisible(wysiwygEl)) return 'wysiwyg';
                }

                return null;
            } catch (e) {
                return null;
            }
        },

        /**
         * 保存 SV 模式快照
         *
         * 設計意圖:
         * - 只在 SV 模式下保存(因為 SV 模式最穩定)
         * - 保存到內存和 localStorage 雙重備份
         * - 記錄到 VditorDiag 便於診斷
         *
         * @param {string} reason - 保存原因
         * @returns {Object|null} 快照信息
         */
        _saveSVSnapshot(reason = 'auto') {
            const mode = this._detectModeFromDOM();

            // 只在 SV 模式下保存
            if (mode !== 'sv') {
                VditorDiag.log('sv-save-skipped', { mode, reason });
                return null;
            }

            const md = this.getValue();
            if (!md) return null;

            const len = md.replace(/\s/g, '').length;
            const hash = Utils.hash32(md);

            // 檢查是否有變化
            if (hash === this._lastSVHash && len === this._lastSVLength) {
                return null; // 無變化
            }

            // 保存 SV 快照
            this._lastSVContent = md;
            this._lastSVLength = len;
            this._lastSVHash = hash;
            this._lastSVTimestamp = Date.now();

            // 持久化到 localStorage
            Utils.storage.set(CONFIG.storageKeys.vditorSnapshot, md);
            Utils.storage.set(CONFIG.storageKeys.vditorSnapshotMeta, {
                mode: 'sv',
                reason,
                ts: this._lastSVTimestamp,
                len,
                hash
            });

            VditorDiag.log('sv-snapshot-saved', { len, hash, reason });
            VditorDiag.lastSVSnapshot = {
                content: md,
                len,
                hash,
                ts: this._lastSVTimestamp
            };

            return { md, len, hash };
        },

        /**
         * 檢查並自動還原
         *
         * 設計意圖:
         * - 定期檢查內容是否異常縮水
         * - 如果在非 SV 模式下檢測到內容大幅縮水,自動還原
         */
        _checkAndAutoRestore() {
            // 如果正在還原,跳過
            if (this._restoreLock) return;

            // 必須有 SV 快照
            if (!this._lastSVContent || this._lastSVLength < 100) return;

            const currentMd = this.getValue();
            const currentLen = (currentMd || '').replace(/\s/g, '').length;
            const currentMode = this._detectModeFromDOM();

            // 如果在 SV 模式,更新快照(而不是檢查還原)
            if (currentMode === 'sv') {
                if (currentLen >= this._lastSVLength * 0.9) {
                    this._saveSVSnapshot('auto-sv');
                }
                return;
            }

            // 在非 SV 模式下,檢查內容是否縮水
            const ratio = currentLen / this._lastSVLength;
            const lost = this._lastSVLength - currentLen;

            // 縮水閾值:丟失超過 20% 或超過 300 字
            if (ratio < 0.8 || (lost > 300 && ratio < 0.9)) {
                const now = Date.now();

                // 防止連續還原(2 秒冷卻)
                if (now - this._lastRestoreAt < 2000) return;

                VditorDiag.log('auto-restore-trigger', {
                    currentLen,
                    svLen: this._lastSVLength,
                    lost,
                    ratio: (ratio * 100).toFixed(1) + '%',
                    mode: currentMode
                });

                this._performRestore('內容異常縮水');
            }
        },

        /**
         * 執行還原
         * @param {string} reason - 還原原因
         * @returns {Promise<boolean>} 是否成功
         */
        async _performRestore(reason) {
            if (this._restoreLock) return false;
            if (!this._lastSVContent) return false;

            this._restoreLock = true;
            this._lastRestoreAt = Date.now();

            try {
                const restoreContent = this._lastSVContent;
                const restoreLen = this._lastSVLength;

                // 設定內容
                try {
                    if (this.instance?.setValue) {
                        this.instance.setValue(restoreContent);
                    }
                } catch (e) {
                    log('Restore setValue failed:', e);
                }

                // 延遲再次設定確保成功
                await new Promise(r => setTimeout(r, 100));

                try {
                    if (this.instance?.setValue) {
                        this.instance.setValue(restoreContent);
                    }
                } catch (e) {
                    // 忽略
                }

                // 驗證還原
                await new Promise(r => setTimeout(r, 200));
                const afterLen = (this.getValue() || '').replace(/\s/g, '').length;

                VditorDiag.logRestore(reason, afterLen);

                if (afterLen >= restoreLen * 0.9) {
                    // 還原成功
                    const now = Date.now();
                    if (now - this._lastGuardToastAt > 3000) {
                        this._lastGuardToastAt = now;
                        Toast.warning(
                            `⚠️ Vditor 偵測到內容異常,已從 SV 快照還原。\n建議使用選單中的「安全切換模式」功能。`,
                            6000
                        );
                    }
                    return true;
                } else {
                    // 還原失敗
                    Toast.error(
                        `⚠️ 自動還原失敗!\n請從「備份管理」或「還原快照」手動恢復內容。`,
                        0
                    );
                    return false;
                }

            } finally {
                // 延遲解鎖
                setTimeout(() => {
                    this._restoreLock = false;
                }, 1000);
            }
        },

        /**
         * 安裝模式切換保護機制
         *
         * 設計意圖:
         * - 定期保存 SV 快照
         * - 監聽模式切換事件
         * - 定期檢查內容完整性
         */
        _installModeSwitchGuard() {
            if (!this._guardEnabled || !this.editorDiv) return;

            // 初始保存 SV 快照
            setTimeout(() => {
                this._saveSVSnapshot('init');
            }, 500);

            // 定期保存 SV 快照(只在 SV 模式下)
            this._autoSnapshotTimer = setInterval(() => {
                const mode = this._detectModeFromDOM();
                if (mode === 'sv') {
                    this._saveSVSnapshot('auto-interval');
                }
            }, CONFIG.timing.vditorSnapshotInterval);

            // 監聽模式切換點擊
            this._captureClickHandler = (ev) => {
                try {
                    const t = ev.target;
                    if (!(t instanceof Element)) return;
                    if (!this.editorDiv?.contains(t)) return;

                    // edit-mode 按鈕
                    const editModeBtn = t.closest('[data-type="edit-mode"]');
                    if (editModeBtn) {
                        const currentMode = this._detectModeFromDOM();
                        VditorDiag.log('click-edit-mode', { beforeMode: currentMode });

                        // 在切換前保存 SV 快照
                        if (currentMode === 'sv') {
                            this._saveSVSnapshot('before-mode-switch');
                        }
                        return;
                    }

                    // 模式選擇面板
                    const panelItem = t.closest('.vditor-panel--left button, .vditor-hint button');
                    if (panelItem) {
                        const txt = (panelItem.textContent || '').toLowerCase();
                        if (txt.includes('sv') || txt.includes('ir') || txt.includes('wysiwyg') ||
                            txt.includes('分屏') || txt.includes('即時') || txt.includes('所見')) {

                            const currentMode = this._detectModeFromDOM();
                            VditorDiag.log('click-mode-panel', {
                                text: txt.trim(),
                                beforeMode: currentMode
                            });

                            if (currentMode === 'sv') {
                                this._saveSVSnapshot('before-mode-select');
                            }
                        }
                    }
                } catch (e) {
                    // 忽略錯誤
                }
            };
            document.addEventListener('click', this._captureClickHandler, true);

            // MutationObserver 監聽模式變化
            this._modeObserver = new MutationObserver(Utils.throttle(() => {
                const mode = this._detectModeFromDOM();
                if (!mode) return;

                if (this._lastMode && mode !== this._lastMode) {
                    VditorDiag.log('mode-change-detected', {
                        from: this._lastMode,
                        to: mode
                    });

                    // 如果切換到 SV,保存快照
                    if (mode === 'sv') {
                        setTimeout(() => this._saveSVSnapshot('after-switch-to-sv'), 300);
                    }

                    // 如果從 SV 切換到其他模式,延遲檢查
                    if (this._lastMode === 'sv' && mode !== 'sv') {
                        setTimeout(() => {
                            this._checkAndAutoRestore();
                        }, 500);
                    }
                }

                this._lastMode = mode;
            }, 200));

            const vditorRoot = this.editorDiv?.querySelector('.vditor');
            if (vditorRoot) {
                this._modeObserver.observe(vditorRoot, {
                    subtree: true,
                    childList: true,
                    attributes: true,
                    attributeFilter: ['class', 'style']
                });
            } else {
                this._modeObserver.observe(this.editorDiv, {
                    subtree: true,
                    childList: true,
                    attributes: true
                });
            }

            // 定期檢查內容完整性
            this._contentCheckTimer = setInterval(() => {
                this._checkAndAutoRestore();
            }, CONFIG.timing.vditorContentCheckInterval);

            log('Vditor mode switch guard installed');
        },

        /**
         * 卸載模式切換保護機制
         */
        _uninstallModeSwitchGuard() {
            // 清除計時器
            if (this._autoSnapshotTimer) {
                clearInterval(this._autoSnapshotTimer);
                this._autoSnapshotTimer = null;
            }
            if (this._contentCheckTimer) {
                clearInterval(this._contentCheckTimer);
                this._contentCheckTimer = null;
            }

            // 移除事件監聽
            if (this._captureClickHandler) {
                document.removeEventListener('click', this._captureClickHandler, true);
                this._captureClickHandler = null;
            }
            if (this._captureKeyHandler) {
                document.removeEventListener('keydown', this._captureKeyHandler, true);
                this._captureKeyHandler = null;
            }

            // 斷開 MutationObserver
            try {
                this._modeObserver?.disconnect();
            } catch (e) {
                // 忽略錯誤
            }
            this._modeObserver = null;

            log('Vditor mode switch guard uninstalled');
        },

        /**
         * 還原最後的快照
         * @returns {boolean} 是否成功
         */
        restoreLastSnapshot() {
            // 優先使用記憶體中的 SV 快照
            let md = this._lastSVContent;

            // 如果沒有,從 storage 讀取
            if (!md) {
                md = Utils.storage.get(CONFIG.storageKeys.vditorSnapshot, '');
            }

            if (!md) {
                Toast.info('沒有可用的 Vditor 快照', 3500);
                return false;
            }

            this.setValue(md);

            // 更新 SV 快照
            this._lastSVContent = md;
            this._lastSVLength = md.replace(/\s/g, '').length;
            this._lastSVHash = Utils.hash32(md);

            Toast.success('已從快照還原內容', 3500);
            return true;
        },

        /**
         * 下載最後的快照
         * @returns {boolean} 是否成功
         */
        downloadLastSnapshot() {
            let md = this._lastSVContent ||
                     Utils.storage.get(CONFIG.storageKeys.vditorSnapshot, '');
            if (!md) {
                Toast.info('沒有可下載的 Vditor 快照', 3500);
                return false;
            }
            const meta = Utils.storage.get(CONFIG.storageKeys.vditorSnapshotMeta, {});
            const date = new Date(meta?.ts || Date.now())
                .toISOString()
                .replace(/[:.]/g, '-')
                .slice(0, 19);
            return Utils.downloadFile(
                md,
                `vditor_snapshot_${date}.md`,
                'text/markdown;charset=utf-8'
            );
        },

        /**
         * 取得編輯器內容
         * @returns {string} Markdown 內容
         */
        getValue() {
            try {
                return this.instance?.getValue() || '';
            } catch (e) {
                log('Vditor getValue error:', e.message);
                return '';
            }
        },

        /**
         * 設定編輯器內容
         * @param {string} value - Markdown 內容
         */
        setValue(value) {
            try {
                this.instance?.setValue(value ?? '');
            } catch (e) {
                log('Vditor setValue error:', e.message);
            }
        },

        /**
         * 取得 HTML 輸出
         * @returns {string} HTML 內容
         */
        getHTML() {
            try {
                return this.instance?.getHTML() || '';
            } catch (e) {
                log('Vditor getHTML error:', e.message);
                return '';
            }
        },

        /**
         * 在游標位置插入內容
         * @param {string} value - 要插入的內容
         */
        insertValue(value) {
            try {
                this.instance?.insertValue(value);
            } catch (e) {
                log('Vditor insertValue error:', e.message);
            }
        },

        /**
         * 選取指定範圍的文字(字元索引)
         *
         * 安全策略:
         * - 僅在 SV 模式嘗試取得 CodeMirror 並選取(最穩)
         * - IR/WYSIWYG 模式 fallback focus(避免字元索引對應混亂)
         */
        selectRange(start, end) {
            try {
                const mode = this._detectModeFromDOM?.() || null;

                // 只在 sv 嘗試做選取
                if (mode !== 'sv') {
                    this.focus();
                    return false;
                }

                const cm =
                    this.instance?.vditor?.sv?.codemirror ||
                    this.instance?.vditor?.sv?.cm ||
                    this.instance?.vditor?.sv?.editor?.cm ||
                    this.instance?.vditor?.sv?.editor?.codemirror;

                if (!cm) {
                    this.focus();
                    return false;
                }

                const doc = cm.getDoc?.() || cm.doc;
                if (!doc?.posFromIndex || !doc?.setSelection) {
                    this.focus();
                    return false;
                }

                const from = doc.posFromIndex(start);
                const to = doc.posFromIndex(end);
                doc.setSelection(from, to);
                cm.scrollIntoView?.({ from, to }, 100);
                cm.focus?.();

                return true;
            } catch (e) {
                log('Vditor selectRange error:', e?.message || e);
                try { this.focus(); } catch (_) {}
                return false;
            }
        },

        /**
         * 聚焦編輯器
         */
        focus() {
            try {
                this.instance?.focus();
            } catch (e) {
                log('Vditor focus error:', e.message);
            }
        },

        /**
         * 刷新編輯器佈局
         * @param {boolean} force - 是否強制延遲刷新
         */
        refresh(force = false) {
            try {
                if (force) {
                    setTimeout(() => window.dispatchEvent(new Event('resize')), 60);
                } else {
                    window.dispatchEvent(new Event('resize'));
                }
            } catch (e) {
                log('Vditor refresh error:', e.message);
            }
        },

        /**
         * 設定主題
         * @param {string} theme - 'light' 或 'dark'
         */
        setTheme(theme) {
            if (!this.instance) return;
            const isDark = theme === 'dark';
            try {
                this.instance.setTheme(
                    isDark ? 'dark' : 'classic',
                    isDark ? 'dark' : 'light',
                    isDark ? 'dracula' : 'github'
                );
            } catch (e) {
                log('Vditor setTheme error:', e.message);
            }
            this.currentTheme = theme;
        },

        /**
         * 銷毀編輯器
         *
         * 設計意圖:
         * - destroy() 負責整體銷毀流程的編排
         * - 計時器清理責任統一委託給 _uninstallModeSwitchGuard()
         * - 避免重複清理造成的責任不清
         */
        destroy() {
            log('Vditor destroying...');

            // 卸載模式切換保護機制(包含所有計時器和事件監聽器的清理)
            this._uninstallModeSwitchGuard();

            // 銷毀 Vditor 實例
            try {
                this.instance?.destroy();
            } catch (e) {
                log('Vditor destroy error:', e.message);
            }

            // 重置所有狀態
            this.instance = null;
            this.editorDiv = null;
            this.container = null;
            this._lastSVContent = null;
            this._lastSVLength = 0;
            this._lastSVHash = null;
            this._lastMode = null;

            log('Vditor destroyed');
        }
    };

    // ========================================
    // EditorManager 編輯器管理器
    // ========================================

    /**
     * 編輯器管理器
     *
     * 設計意圖:
     * - 提供統一的編輯器操作介面,調用者無需關心具體編輯器類型
     * - 實現切換鎖與隊列機制,防止同時進行多個編輯器切換
     * - 處理 Vditor 安全重建等特殊情況
     *
     * 使用方式:
     * await EditorManager.switchEditor('vditor', container, content, theme, onProgress);
     * const value = EditorManager.getValue();
     * EditorManager.setValue('# Hello');
     */
    const EditorManager = {
        /** @type {string|null} 當前編輯器鍵名 */
        currentEditor: null,

        /** @type {Object|null} 當前適配器實例 */
        currentAdapter: null,

        /** @type {HTMLElement|null} 編輯器容器 */
        container: null,

        /** @type {boolean} 是否正在切換編輯器 */
        _switching: false,

        /** @type {Array} 切換請求隊列 */
        _queue: [],

        /**
         * 切換編輯器
         *
         * 設計意圖:
         * - 使用切換鎖防止同時進行多個切換操作
         * - 使用隊列確保請求不會丟失
         * - 保留當前內容並傳遞給新編輯器
         *
         * @param {string} editorKey - 編輯器鍵名 ('easymde' | 'toastui' | 'cherry' | 'vditor')
         * @param {HTMLElement} container - 編輯器容器元素
         * @param {string} content - 初始內容(可能會被當前編輯器的內容覆蓋)
         * @param {string} theme - 主題 ('light' | 'dark')
         * @param {Function} onProgress - 進度回調函數,接收狀態訊息字串
         * @returns {Promise<Object>} 適配器實例
         * @throws {Error} 載入失敗時拋出錯誤
         */
        async switchEditor(editorKey, container, content, theme, onProgress) {
            // 防止競態:正在切換時加入隊列
            if (this._switching) {
                log('Editor switch queued:', editorKey);
                return new Promise((resolve, reject) => {
                    this._queue.push({
                        editorKey,
                        container,
                        content,
                        theme,
                        onProgress,
                        resolve,
                        reject
                    });
                });
            }

            this._switching = true;
            log('Editor switching to:', editorKey);

            try {
                const adapter = await this._doSwitchEditor(
                    editorKey,
                    container,
                    content,
                    theme,
                    onProgress
                );
                return adapter;
            } catch (e) {
                logError('Editor switch failed:', e);

                // 嘗試恢復到可用狀態
                try {
                    if (this.currentAdapter) {
                        // 保持當前適配器
                        Toast.error(`切換失敗:${e.message}\n已保留當前編輯器`);
                    } else {
                        // 嘗試載入預設編輯器
                        Toast.error(`載入失敗:${e.message}\n正在嘗試載入備用編輯器...`);
                        const fallbackKey = CONFIG.defaultEditor;
                        if (fallbackKey !== editorKey) {
                            try {
                                return await this._doSwitchEditor(
                                    fallbackKey,
                                    container,
                                    content,
                                    theme,
                                    onProgress
                                );
                            } catch (e2) {
                                Toast.error('備用編輯器也載入失敗,請重新整理頁面');
                            }
                        }
                    }
                } catch (recoveryError) {
                    logError('Recovery also failed:', recoveryError);
                }

                throw e;
            } finally {
                this._switching = false;

                // 處理隊列中的下一個請求
                if (this._queue.length > 0) {
                    const next = this._queue.shift();
                    log('Processing queued switch:', next.editorKey);
                    this.switchEditor(
                        next.editorKey,
                        next.container,
                        next.content,
                        next.theme,
                        next.onProgress
                    ).then(next.resolve).catch(next.reject);
                }
            }
        },

        /**
         * 實際執行編輯器切換
         * @private
         */
        async _doSwitchEditor(editorKey, container, content, theme, onProgress) {
            const perfTimer = PerfMonitor.start(`switch-editor-${editorKey}`);
            // 檢查是否為 Vditor 安全重建
            const isVditorSafeReinit = (
                editorKey === 'vditor' &&
                Utils.storage.get(CONFIG.storageKeys.vditorSafeReinitFlag, false)
            );

            // 若為 Vditor 安全重建,優先使用快照內容
            if (isVditorSafeReinit) {
                const snapshotContent = Utils.storage.get(CONFIG.storageKeys.vditorSnapshot, '');
                if (snapshotContent && snapshotContent.trim()) {
                    content = snapshotContent;
                    log('Vditor safe reinit: using snapshot content');
                }
            }

            // 保留當前編輯器的內容(非 Vditor 安全重建時)
            if (this.currentAdapter && !isVditorSafeReinit) {
                try {
                    const oldContent = this.currentAdapter.getValue();
                    if (oldContent && oldContent.trim()) {
                        content = oldContent;
                        log('Preserving current content, length:', oldContent.length);
                    }
                } catch (e) {
                    log('Failed to get current content:', e.message);
                }
            }

            // 銷毀舊的適配器
            if (this.currentAdapter) {
                log('Destroying current adapter:', this.currentEditor);
                try {
                    this.currentAdapter.destroy();
                } catch (e) {
                    logWarn('Adapter destroy error:', e.message);
                }
            }

            this.container = container;
            container.innerHTML = '';

            // 載入編輯器資源
            await Loader.loadEditor(editorKey, onProgress);

            // 取得並初始化適配器
            const adapter = EditorAdapters[editorKey];
            if (!adapter) {
                throw new Error(`No adapter for: ${editorKey}`);
            }

            onProgress?.(`初始化 ${CONFIG.editors[editorKey].name}...`);
            await adapter.init(container, content, theme);

            // 更新狀態
            this.currentEditor = editorKey;
            this.currentAdapter = adapter;
            Utils.storage.set(CONFIG.storageKeys.editor, editorKey);

            // 建立備份(如果有內容)
            if (content && content.trim()) {
                BackupManager.create(content, {
                    editorKey,
                    mode: adapter._detectModeFromDOM?.()
                });
            }

            log('Editor switched successfully:', editorKey);
            perfTimer.end();
            return adapter;
        },

        /**
         * 取得編輯器內容
         * @returns {string} Markdown 內容
         */
        getValue() {
            return this.currentAdapter?.getValue?.() || '';
        },

        /**
         * 設定編輯器內容
         * @param {string} value - Markdown 內容
         */
        setValue(value) {
            this.currentAdapter?.setValue?.(value);
        },

        /**
         * 取得 HTML 輸出
         * @returns {string} HTML 內容
         */
        getHTML() {
            return this.currentAdapter?.getHTML?.() || '';
        },

        /**
         * 在游標位置插入內容
         * @param {string} value - 要插入的內容
         */
        insertValue(value) {
            this.currentAdapter?.insertValue?.(value);
        },

        /**
         * 聚焦編輯器
         */
        focus() {
            this.currentAdapter?.focus?.();
        },

        /**
         * 設定主題
         * @param {string} theme - 'light' 或 'dark'
         */
        setTheme(theme) {
            this.currentAdapter?.setTheme?.(theme);
        },

        /**
         * 刷新編輯器佈局
         * @param {boolean} force - 是否強制延遲刷新
         */
        refresh(force = false) {
            try {
                this.currentAdapter?.refresh?.(force);
            } catch (e) {
                log('Editor refresh error:', e.message);
            }
        },

        /**
         * 銷毀當前編輯器
         */
        destroy() {
            log('EditorManager destroying...');
            try {
                this.currentAdapter?.destroy?.();
            } catch (e) {
                logWarn('Adapter destroy error:', e.message);
            }

            this.currentAdapter = null;
            this.currentEditor = null;
            this.container = null;
        },

        /**
         * 取得當前編輯器資訊
         * @returns {Object|null} 包含 key, config, adapter 的物件
         */
        getCurrentInfo() {
            if (!this.currentEditor) return null;
            return {
                key: this.currentEditor,
                config: CONFIG.editors[this.currentEditor],
                adapter: this.currentAdapter
            };
        },

        /**
         * 檢查編輯器是否就緒
         * @returns {boolean}
         */
        isReady() {
            return !!this.currentAdapter;
        },

        /**
         * 檢查是否正在切換
         * @returns {boolean}
         */
        isSwitching() {
            return this._switching;
        }
    };

    // ========================================
    // SVG 圖標集合
    // ========================================

    /**
     * SVG 圖標集合
     *
     * 設計意圖:
     * - 提供統一的圖標資源
     * - 使用 SVG 確保可縮放和高清顯示
     * - 使用 currentColor 確保圖標顏色跟隨父元素
     *
     * 圖標分類:
     * - 描邊型:使用 stroke,fill="none"
     * - 填充型:使用 fill,無 stroke
     *
     * 修正說明:
     * - 原代碼中部分圖標缺少 fill/stroke 屬性
     * - 導致在某些情況下圖標顯示為黑色或不可見
     * - 現在每個圖標都明確指定正確的屬性
     */
    const Icons = {
        // ===== 填充型圖標 =====

        /** Markdown 標誌(填充型) */
        markdown: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 5h18v14H3V5zm2 2v10h14V7H5zm2.5 2h2l1.5 2 1.5-2h2v6h-2v-3.5L11 14l-1.5-2.5V15h-2V9zm9 0h2v4h1.5L18 16l-1.5-3H15V9z"/></svg>`,

        /** 月亮圖標(填充型) */
        moon: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>`,

        /** 更多選項圖標(填充型 - 三個點) */
        more: `<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>`,

        // ===== 描邊型圖標 =====

        /** 關閉圖標 */
        close: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>`,

        /** 放大圖標 */
        maximize: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/></svg>`,

        /** 縮小圖標 */
        minimize: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 01-2 2H3m18 0h-3a2 2 0 01-2-2V3m0 18v-3a2 2 0 012-2h3M3 16h3a2 2 0 012 2v3"/></svg>`,

        /** 展開圖標 */
        expand: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>`,

        /** 收合圖標 */
        collapse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 14h6v6M14 4h6v6M10 14l-7 7M21 3l-7 7"/></svg>`,

        /** 導出圖標 */
        exportFile: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>`,

        /** 導入圖標 */
        import: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>`,

        /** 檔案圖標 */
        file: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,

        /** 清除圖標 */
        clear: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2M10 11v6M14 11v6"/></svg>`,

        /** 下載圖標 */
        download: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>`,

        /** 複製圖標 */
        copy: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>`,

        /** 代碼圖標 */
        code: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`,

        /** 保存圖標 */
        save: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>`,

        /** 載入中圖標 */
        loading: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>`,

        /** 太陽圖標 */
        sun: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>`,

        /** 還原圖標 */
        restore: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0115.36-6.36L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 01-15.36 6.36L3 16"/><path d="M3 21v-5h5"/></svg>`,

        /** 盾牌圖標 */
        shield: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>`,

        /** 設定圖標 */
        settings: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/></svg>`,

        /** 專注模式圖標 */
        focus: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/></svg>`,

        /** 向上箭頭圖標 */
        arrowUp: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg>`,

        /** 向下箭頭圖標 */
        arrowDown: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12l7 7 7-7"/></svg>`,

        /** 歷史記錄圖標 */
        history: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>`,

        /** 釘選圖標 */
        pin: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l2.4 7.4h7.6l-6 4.6 2.3 7-6.3-4.6-6.3 4.6 2.3-7-6-4.6h7.6z"/></svg>`,

        /** 垃圾桶圖標 */
        trash: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>`,

        /** 眼睛圖標 */
        eye: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>`,

        /** 時鐘圖標 */
        clock: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>`,

        /** 資料庫圖標 */
        database: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>`,

        /** 插槽圖示(層疊方塊,表示多個存檔位置) */
        slots: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>`,

        /** 勾選圖標 */
        check: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`,

        /** 叉號圖標 */
        x: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`,

        /** 資訊圖標 */
        info: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,

        /** 警告圖標 */
        warning: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,

        /** 選單圖標 */
        menu: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg>`,

        /** 外部連結圖標 */
        externalLink: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`,

        /** 編輯圖標 */
        edit: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>`,

        /** 加號圖標 */
        plus: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>`,

        /** 減號圖標 */
        minus: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>`
    };

    // ========================================
    // 主樣式系統
    // ========================================

    /**
     * 樣式管理器
     *
     * 設計意圖(保留):
     * - 以「單一 style 元素」承載整套 UI CSS
     * - 主題切換時替換整段 CSS(可靠、可預期、與頁面 CSS 隔離)
     *
     * 確認的修復/升級(本段落會做):
     * 1) 工具列按鈕溢出:加入 flex-wrap 與溢出處理,避免按鈕掉出視窗(使用者回報)
     * 2) SVG 圖示黑色/不可見:本樣式不再強制 fill:none / stroke:currentColor,
     *    讓每個 SVG 依照 Icons 內的 fill/stroke 自行決定(Icons 已在片段 7 修正)
     */
    const Styles = {
        /** @type {HTMLStyleElement|null} */
        el: null,

        /**
         * 產生主題 CSS
         * @param {string} theme - 'light' | 'dark' | 'auto'(auto 在 Theme 層處理成 light/dark)
         * @returns {string}
         */
        getCSS(theme) {
            const isDark = theme === 'dark';
            const p = CONFIG.prefix;

            // 主題色彩配置(保留原設計:生成整段 CSS)
            const c = isDark ? {
                bg1: '#1a1a2e',
                bg2: '#16213e',
                bg3: '#0f3460',
                text1: '#e8e8e8',
                text2: '#a0a0a0',
                text3: '#6c757d',
                border: '#2d3748',
                accent: '#4facfe',
                accentHover: '#00f2fe',
                accentLight: 'rgba(79,172,254,0.15)',
                header: '#1e1e2e',
                btn: '#2d3748',
                btnHover: '#4a5568',
                danger: '#dc3545',
                dangerHover: '#c82333',
                shadow: '0 25px 50px -12px rgba(0,0,0,0.5)',
                shadowSm: '0 4px 12px rgba(0,0,0,0.3)',
                overlay: 'rgba(0,0,0,0.75)',
                menuBg: '#151526',
                success: '#28a745',
                warning: '#ffc107'
            } : {
                bg1: '#ffffff',
                bg2: '#f8f9fa',
                bg3: '#e9ecef',
                text1: '#212529',
                text2: '#6c757d',
                text3: '#adb5bd',
                border: '#dee2e6',
                accent: '#007bff',
                accentHover: '#0056b3',
                accentLight: 'rgba(0,123,255,0.12)',
                header: '#f1f3f4',
                btn: '#e9ecef',
                btnHover: '#dee2e6',
                danger: '#dc3545',
                dangerHover: '#c82333',
                shadow: '0 25px 50px -12px rgba(0,0,0,0.25)',
                shadowSm: '0 4px 12px rgba(0,0,0,0.1)',
                overlay: 'rgba(0,0,0,0.5)',
                menuBg: '#ffffff',
                success: '#28a745',
                warning: '#ffc107'
            };

            return `
/* ===== CSS Variables 準備(第二階段將完整遷移)===== */
/*
 * 設計說明:
 * 1. 目前仍使用整段 CSS 替換方式進行主題切換
 * 2. 以下變數定義為第二階段遷移做準備
 * 3. 第二階段將改為只切換 root 的類別來切換主題
 */
:root {
    /* 基礎色彩 - 亮色模式 */
    --mme-color-bg-primary: ${isDark ? c.bg1 : c.bg1};
    --mme-color-bg-secondary: ${isDark ? c.bg2 : c.bg2};
    --mme-color-bg-tertiary: ${isDark ? c.bg3 : c.bg3};
    --mme-color-text-primary: ${isDark ? c.text1 : c.text1};
    --mme-color-text-secondary: ${isDark ? c.text2 : c.text2};
    --mme-color-text-muted: ${isDark ? c.text3 : c.text3};
    --mme-color-border: ${isDark ? c.border : c.border};
    --mme-color-accent: ${isDark ? c.accent : c.accent};
    --mme-color-accent-hover: ${isDark ? c.accentHover : c.accentHover};
    --mme-color-accent-light: ${isDark ? c.accentLight : c.accentLight};

    /* 語義化色彩 */
    --mme-color-success: ${c.success};
    --mme-color-warning: ${c.warning};
    --mme-color-danger: ${c.danger};

    /* 陰影 */
    --mme-shadow-lg: ${c.shadow};
    --mme-shadow-sm: ${c.shadowSm};

    /* 圓角 */
    --mme-radius-sm: 6px;
    --mme-radius-md: 8px;
    --mme-radius-lg: 12px;

    /* 過渡 */
    --mme-transition-fast: 0.15s ease;
    --mme-transition-normal: 0.25s ease;
}

/* ===== 基礎重置 ===== */
.${p}overlay *, .${p}overlay *::before, .${p}overlay *::after,
#${p}portal *, #${p}portal *::before, #${p}portal *::after {
    box-sizing: border-box;
}

/* ===== Portal 容器 ===== */
#${p}portal {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    z-index: ${CONFIG.zIndex + 500};
    pointer-events: none;
}
#${p}portal > * { pointer-events: auto; }

/* ===== 遮罩層 ===== */
.${p}overlay {
    position: fixed;
    inset: 0;
    background: ${c.overlay};
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: ${CONFIG.zIndex};
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s ease;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.${p}overlay.${p}active {
    opacity: 1;
    visibility: visible;
}

/* ===== Modal 主視窗 ===== */
.${p}modal {
    position: absolute;
    width: 88vw;
    height: 88vh;
    max-width: 1400px;
    max-height: 900px;
    min-width: 380px;
    min-height: 350px;
    display: flex;
    flex-direction: column;
    background: ${c.bg1};
    border-radius: 12px;
    box-shadow: ${c.shadow};
    overflow: visible;
    transform: scale(0.95);
    transition: transform 0.25s ease;
}
.${p}overlay.${p}active .${p}modal { transform: scale(1); }

.${p}modal.${p}fullscreen {
    width: 100vw !important;
    height: 100vh !important;
    max-width: 100vw !important;
    max-height: 100vh !important;
    border-radius: 0;
    top: 0 !important;
    left: 0 !important;
}

/* 拖曳中 */
.${p}modal.${p}dragging {
    transition: none;
    user-select: none;
}

/* ===== 工具列 ===== */
.${p}toolbar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: ${c.header};
    border-bottom: 1px solid ${c.border};
    user-select: none;
    cursor: move;
    border-radius: 12px 12px 0 0;

    /* 修復:避免窄視窗按鈕掉出 modal(使用者回報) */
    flex-wrap: wrap;
    row-gap: 6px;
    max-height: 96px;          /* 最多兩行 + 捲動 */
    overflow-y: auto;
    overflow-x: hidden;
}
.${p}toolbar-left,
.${p}toolbar-right {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: nowrap;
    min-width: 0;
}
.${p}toolbar-spacer { flex: 1; }

/* 工具列狀態 */
.${p}toolbar-status {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: ${c.text2};
    padding: 0 8px;
    white-space: nowrap;
}
.${p}toolbar-status .${p}sep { color: ${c.text3}; }

/* ===== Mini Slots Bar(工具列迷你插槽列)===== */
.${p}mini-slots {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 0 6px;
    border-left: 1px solid ${c.border};
    margin-left: 6px;
    max-width: 40vw;
    flex-wrap: wrap; /* toolbar 已支援 wrap,這裡也允許 */
}

.${p}mini-slot-btn {
    width: 26px;
    height: 26px;
    border-radius: 6px;
    border: 1px solid ${c.border};
    background: ${c.btn};
    color: ${c.text1};
    font: 12px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-weight: 700;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s ease;
    user-select: none;
}

.${p}mini-slot-btn:hover {
    background: ${c.btnHover};
    border-color: ${c.accent};
}

.${p}mini-slot-btn.${p}has-content {
    background: ${c.accent};
    border-color: ${c.accent};
    color: #fff;
}

.${p}mini-slot-btn.${p}empty {
    opacity: 0.65;
}

.${p}mini-slot-btn:active {
    transform: scale(0.97);
}

/* ===== 編輯器選擇器 ===== */
.${p}editor-select {
    appearance: none;
    padding: 5px 24px 5px 8px;
    border: 1px solid ${c.border};
    border-radius: 6px;
    background: ${c.btn};
    color: ${c.text1};
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    min-width: 120px;
}
.${p}editor-select:hover {
    background: ${c.btnHover};
    border-color: ${c.accent};
}
.${p}editor-select:focus {
    outline: none;
    box-shadow: 0 0 0 2px ${c.accentLight};
    border-color: ${c.accent};
}

/* ===== 按鈕樣式 ===== */
.${p}btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 8px;
    border: 1px solid ${c.border};
    border-radius: 6px;
    background: ${c.btn};
    color: ${c.text1};
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.15s ease;
    white-space: nowrap;
}
.${p}btn:hover {
    background: ${c.btnHover};
    border-color: ${c.accent};
}
.${p}btn:active { transform: scale(0.98); }

/* 重要:不再強制 fill:none / stroke:currentColor
   讓每個 SVG 依照 Icons 內部設定自行決定(避免填充型圖示消失) */
.${p}btn svg,
.${p}icon-btn svg,
.${p}theme-btn svg {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    display: block;
}

/* ===== 圖示按鈕 ===== */
.${p}icon-btn {
    width: 28px;
    height: 28px;
    border: 1px solid ${c.border};
    border-radius: 6px;
    background: ${c.btn};
    color: ${c.text1};
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s ease;
    flex-shrink: 0;
}
.${p}icon-btn:hover {
    background: ${c.btnHover};
    border-color: ${c.accent};
}
.${p}icon-btn:active { transform: scale(0.98); }
.${p}icon-btn.${p}danger:hover {
    background: ${c.danger};
    border-color: ${c.danger};
    color: #fff;
}
.${p}icon-btn.${p}active {
    background: ${c.warning} !important;
    border-color: ${c.warning} !important;
    color: #000 !important;
}

/* ===== 主要按鈕 ===== */
.${p}primary {
    background: ${c.accent} !important;
    border-color: ${c.accent} !important;
    color: #fff !important;
}
.${p}primary:hover {
    background: ${c.accentHover} !important;
    border-color: ${c.accentHover} !important;
}

/* ===== 主題切換按鈕(保留) ===== */
.${p}theme-btn {
    width: 28px;
    height: 28px;
    border: 1px solid ${c.border};
    border-radius: 6px;
    background: ${c.btn};
    color: ${c.text1};
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.15s ease;
}
.${p}theme-btn:hover {
    background: ${c.btnHover};
    border-color: ${c.accent};
}

/* ===== 按鈕外觀模式(由 ToolbarPrefs 套用 class) ===== */
.${p}modal.${p}btn-icon-only .${p}btn span { display: none !important; }
.${p}modal.${p}btn-icon-only .${p}btn { padding: 5px 6px !important; }
.${p}modal.${p}btn-text-only .${p}btn svg { display: none !important; }
.${p}modal.${p}btn-text-only .${p}btn { padding: 5px 10px !important; }
.${p}modal.${p}btn-icon-text .${p}btn svg { display: inline-block; }
.${p}modal.${p}btn-icon-text .${p}btn span { display: inline; }

/* icon-btn 一律只顯示圖示 */
.${p}icon-btn span { display: none !important; }

/* ===== 主體區域 ===== */
.${p}body {
    flex: 1 1 auto;
    min-height: 0;
    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.${p}editor {
    flex: 1;
    width: 100%;
    min-height: 0;
    overflow: hidden;
    position: relative;
}
.${p}editor > * { height: 100% !important; }

/* ===== 載入畫面 ===== */
.${p}loading {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    background: ${c.bg1};
    color: ${c.text2};
    font-size: 14px;
    z-index: 10;
}
.${p}loading.${p}hidden { display: none; }
.${p}spinner {
    width: 44px;
    height: 44px;
    border: 3px solid ${c.border};
    border-top-color: ${c.accent};
    border-radius: 50%;
    animation: ${p}spin 0.8s linear infinite;
}
@keyframes ${p}spin { to { transform: rotate(360deg); } }
.${p}loading-text {
    text-align: center;
    max-width: 320px;
    line-height: 1.5;
}
.${p}loading-error { color: ${c.danger}; margin-top: 8px; }

/* ===== 狀態列 ===== */
.${p}status-bar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 12px;
    background: ${c.header};
    border-top: 1px solid ${c.border};
    font-size: 11px;
    color: ${c.text2};
    border-radius: 0 0 12px 12px;
}
.${p}status-bar .${p}sep { color: ${c.text3}; }
.${p}status-spacer { flex: 1; }
#${p}status-text { color: ${c.text2}; }

/* ===== Portal 下拉選單(更多) ===== */
.${p}menu-panel {
    position: fixed;
    background: ${c.menuBg};
    border: 1px solid ${c.border};
    border-radius: 12px;
    box-shadow: ${c.shadow};
    padding: 10px;
    min-width: 220px;
    max-width: 320px;
    max-height: 70vh;
    overflow-y: auto;
    z-index: ${CONFIG.zIndex + 600};
    display: none;
}
.${p}menu-panel.${p}open { display: block; }
.${p}menu-item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px;
    border-radius: 8px;
    border: none;
    background: transparent;
    color: ${c.text1};
    font-size: 13px;
    cursor: pointer;
    text-align: left;
    transition: background 0.15s;
}
.${p}menu-item:hover { background: ${c.btnHover}; }
.${p}menu-item svg { width: 16px; height: 16px; flex-shrink: 0; display:block; }
.${p}menu-sep { height: 1px; background: ${c.border}; margin: 6px 4px; }
.${p}menu-header {
    padding: 6px 10px;
    font-size: 11px;
    font-weight: 600;
    color: ${c.text3};
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* ===== Portal 面板通用樣式 ===== */
.${p}portal-panel {
    position: fixed;
    background: ${c.bg1};
    border: 1px solid ${c.border};
    border-radius: 12px;
    box-shadow: ${c.shadow};
    z-index: ${CONFIG.zIndex + 600};
    max-height: 80vh;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.${p}portal-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-bottom: 1px solid ${c.border};
    background: ${c.header};
}
.${p}portal-panel-header h3 {
    margin: 0;
    font-size: 14px;
    font-weight: 700;
    color: ${c.text1};
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.${p}portal-panel-header h3 svg {
    width: 18px;
    height: 18px;
    min-width: 18px;
    flex-shrink: 0;
}
.${p}portal-panel-body {
    flex: 1;
    overflow-y: auto;
    padding: 12px;
}
.${p}portal-panel-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid ${c.border};
    background: ${c.header};
}

/* ===== 導入/導出面板(Portal 版) ===== */
.${p}io-panel {
    position: fixed;
    background: ${c.bg1};
    border: 1px solid ${c.border};
    border-radius: 12px;
    box-shadow: ${c.shadow};
    padding: 20px;
    min-width: 260px;
    max-width: 320px;
    z-index: ${CONFIG.zIndex + 600};
}
.${p}io-panel h4 {
    margin: 0 0 12px;
    font-size: 15px;
    font-weight: 700;
    color: ${c.text1};
}
.${p}io-panel .${p}btn {
    justify-content: flex-start;
    width: 100%;
    margin-bottom: 8px;
}
.${p}io-panel .${p}secondary {
    background: transparent;
    border-color: ${c.border};
    color: ${c.text2};
    margin-top: 8px;
}
.${p}io-panel .${p}secondary:hover { background: ${c.btnHover}; }
.${p}io-hint { font-size: 11px; color: ${c.text3}; padding: 4px 0; }
.${p}io-sep { height: 1px; background: ${c.border}; margin: 8px 0; }

/* ===== 設定面板(Portal 版) ===== */
.${p}settings-panel { width: min(550px, 90vw); }
.${p}settings-hint { font-size: 12px; color: ${c.text3}; margin-bottom: 12px; }
.${p}settings-group {
    margin-bottom: 16px;
    padding: 12px;
    background: ${isDark ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)'};
    border-radius: 8px;
    border: 1px solid ${c.border};
}
.${p}settings-group h4 {
    margin: 0 0 10px;
    font-size: 13px;
    font-weight: 600;
    color: ${c.text1};
}
.${p}settings-section { margin-bottom: 16px; }
.${p}settings-section h4 {
    margin: 0 0 8px;
    font-size: 13px;
    font-weight: 600;
    color: ${c.text1};
}
.${p}settings-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: 1px solid ${c.border};
    border-radius: 8px;
    margin-bottom: 6px;
    background: ${isDark ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)'};
}
.${p}settings-row label {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: ${c.text1};
    cursor: pointer;
}
.${p}settings-row input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; }
.${p}settings-mini-btn {
    width: 26px;
    height: 26px;
    border-radius: 6px;
    border: 1px solid ${c.border};
    background: ${c.btn};
    color: ${c.text1};
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s ease;
}
.${p}settings-mini-btn:hover { background: ${c.btnHover}; }
.${p}settings-mini-btn svg { width: 12px; height: 12px; display:block; }
.${p}settings-sub {
    margin-left: 24px;
    padding-left: 12px;
    border-left: 2px solid ${c.border};
}
.${p}select {
    padding: 5px 8px;
    border: 1px solid ${c.border};
    border-radius: 6px;
    background: ${c.btn};
    color: ${c.text1};
    font-size: 12px;
    cursor: pointer;
    min-width: 150px;
}
.${p}select:hover { border-color: ${c.accent}; }
.${p}select:focus {
    outline: none;
    box-shadow: 0 0 0 2px ${c.accentLight};
    border-color: ${c.accent};
}

/* ===== 備份管理面板 ===== */
.${p}backup-panel {
    width: min(600px, 90vw);
    max-height: min(80vh, 600px);
}
.${p}backup-list { display: flex; flex-direction: column; gap: 8px; }
.${p}backup-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    background: ${c.bg2};
    border: 1px solid ${c.border};
    border-radius: 8px;
    transition: all 0.15s ease;
}
.${p}backup-item:hover { border-color: ${c.accent}; background: ${c.accentLight}; }
.${p}backup-item.${p}pinned {
    border-color: ${c.warning};
    background: rgba(255, 193, 7, 0.1);
}
.${p}backup-info { flex: 1; min-width: 0; }
.${p}backup-time {
    font-size: 13px;
    font-weight: 600;
    color: ${c.text1};
    margin-bottom: 2px;
}
.${p}backup-meta {
    font-size: 11px;
    color: ${c.text2};
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}
.${p}backup-actions { display:flex; gap:4px; }
.${p}backup-empty { text-align: center; padding: 40px 20px; color: ${c.text3}; }
.${p}backup-stats {
    display: flex;
    gap: 16px;
    padding: 8px 12px;
    background: ${c.bg2};
    border-radius: 8px;
    margin-bottom: 12px;
    font-size: 12px;
    color: ${c.text2};
}
.${p}backup-stats b { color: ${c.text1}; }
.${p}backup-info-box {
    background: ${isDark ? 'rgba(79,172,254,0.1)' : 'rgba(0,123,255,0.08)'};
    border: 1px solid ${isDark ? 'rgba(79,172,254,0.3)' : 'rgba(0,123,255,0.2)'};
    border-radius: 8px;
    padding: 12px 16px;
    margin-bottom: 16px;
}
.${p}backup-info-title {
    font-weight: 700;
    font-size: 13px;
    color: ${c.accent};
    margin-bottom: 8px;
    display:flex;
    align-items:center;
    gap:6px;
}
.${p}backup-info-title svg { width: 16px; height: 16px; display:block; }
.${p}backup-info-list {
    margin: 0;
    padding-left: 20px;
    font-size: 12px;
    color: ${c.text2};
    line-height: 1.8;
}
.${p}backup-info-list b { color: ${c.text1}; }
.${p}backup-info-warning {
    margin-top: 10px;
    padding: 8px 12px;
    background: ${isDark ? 'rgba(255,193,7,0.15)' : 'rgba(255,193,7,0.1)'};
    border-left: 3px solid ${c.warning};
    border-radius: 4px;
    font-size: 12px;
    color: ${isDark ? '#ffc107' : '#856404'};
}
.${p}backup-age-warning {
    font-size: 10px;
    padding: 2px 6px;
    border-radius: 4px;
    background: ${isDark ? 'rgba(255,193,7,0.2)' : 'rgba(255,193,7,0.15)'};
    color: ${isDark ? '#ffc107' : '#856404'};
    margin-left: 8px;
}
.${p}backup-age-warning.${p}danger {
    background: ${isDark ? 'rgba(220,53,69,0.2)' : 'rgba(220,53,69,0.1)'};
    color: ${c.danger};
}
.${p}pin-badge { font-size: 11px; color: ${c.warning}; margin-left: 6px; }

/* ===== Vditor 下拉選單 z-index 修正 ===== */
.vditor-hint, .vditor-panel--arrow, .vditor-tip {
    z-index: ${CONFIG.zIndex + 100} !important;
}

/* ===== 編輯器適配樣式 ===== */
.${p}editor .vditor { border: none !important; height: 100% !important; }
.${p}editor .cherry { height: 100% !important; border: none !important; }
.${p}editor .toastui-editor-defaultUI { height: 100% !important; }
.${p}editor .EasyMDEContainer {
    height: 100% !important;
    display: flex !important;
    flex-direction: column !important;
}
.${p}editor .EasyMDEContainer .CodeMirror {
    flex: 1 !important;
    height: 0 !important;
    min-height: 0 !important;
}

/* ===== FAB 浮動按鈕 ===== */
.${p}fab {
    position: fixed;
    right: 24px;
    bottom: 24px;
    width: 56px;
    height: 56px;
    border: none;
    border-radius: 50%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: #fff;
    cursor: pointer;
    box-shadow: 0 4px 15px rgba(102, 126, 234, 0.45);
    z-index: 2147483645;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
    touch-action: none;
}
.${p}fab:hover {
    transform: scale(1.08);
    box-shadow: 0 6px 20px rgba(102, 126, 234, 0.55);
}
.${p}fab:active { transform: scale(0.95); }
.${p}fab.${p}dragging { transition: none; cursor: grabbing; }
.${p}fab svg { width: 28px; height: 28px; display:block; }
.${p}fab.${p}loading svg { animation: ${p}spin 0.8s linear infinite; }

/* ===== Tooltip ===== */
.${p}tooltip {
    position: fixed;
    padding: 5px 10px;
    background: ${isDark ? '#4a5568' : '#333'};
    color: #fff;
    font-size: 11px;
    border-radius: 4px;
    white-space: nowrap;
    pointer-events: none;
    z-index: ${CONFIG.zIndex + 800};
    opacity: 0;
    transition: opacity 0.15s;
}

/* ===== Resize 手柄(主視窗) ===== */
.${p}resize-handle { background: transparent; transition: background 0.2s; }
.${p}resize-handle:hover { background: ${c.accentLight}; }
.${p}resize-right:hover,
.${p}resize-left:hover {
    background: linear-gradient(90deg, transparent, ${c.accentLight}, transparent);
}
.${p}resize-top:hover,
.${p}resize-bottom:hover {
    background: linear-gradient(180deg, transparent, ${c.accentLight}, transparent);
}
.${p}resize-corner:hover { background: ${c.accentLight}; }
.${p}resizing { transition: none !important; user-select: none !important; }
.${p}modal.${p}fullscreen .${p}resize-handle { display: none !important; }

/* ===== 快速存檔插槽面板 ===== */
.${p}slots-panel {
    width: min(420px, 90vw);
    max-height: min(70vh, 500px);
    display: flex;
    flex-direction: column;
    padding: 16px;
    background: ${c.bg1};
    border: 1px solid ${c.border};
    border-radius: 12px;
    box-shadow: ${c.shadow};
}

.${p}slots-panel h4 {
    margin: 0 0 12px;
    font-size: 16px;
    font-weight: 700;
    color: ${c.text1};
    display: flex;
    align-items: center;
    gap: 8px;
}

.${p}slots-panel h4 svg {
    width: 20px;
    height: 20px;
}

.${p}slots-hint {
    font-size: 12px;
    color: ${c.text2};
    margin-bottom: 12px;
    padding: 8px 12px;
    background: ${isDark ? 'rgba(79,172,254,0.1)' : 'rgba(0,123,255,0.08)'};
    border-radius: 6px;
    border-left: 3px solid ${c.accent};
}

.${p}slots-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 300px;
    overflow-y: auto;
    padding-right: 4px;
}

.${p}slot-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    background: ${c.bg2};
    border: 1px solid ${c.border};
    border-radius: 8px;
    transition: all 0.15s ease;
}

.${p}slot-row:hover {
    border-color: ${c.accent};
    background: ${c.accentLight};
}

.${p}slot-row.${p}empty {
    opacity: 0.6;
}

.${p}slot-row.${p}empty:hover {
    opacity: 0.8;
}

.${p}slot-number {
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: ${c.btn};
    border: 1px solid ${c.border};
    border-radius: 6px;
    font-size: 13px;
    font-weight: 700;
    color: ${c.text1};
    flex-shrink: 0;
}

.${p}slot-row.${p}has-content .${p}slot-number {
    background: ${c.accent};
    border-color: ${c.accent};
    color: #fff;
}

.${p}slot-info {
    flex: 1;
    min-width: 0;
    overflow: hidden;
}

.${p}slot-label {
    font-size: 13px;
    font-weight: 600;
    color: ${c.text1};
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.${p}slot-meta {
    font-size: 11px;
    color: ${c.text2};
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 2px;
}

.${p}slot-empty-label {
    font-size: 13px;
    color: ${c.text3};
    font-style: italic;
}

.${p}slot-actions {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.${p}slot-actions .${p}icon-btn {
    width: 26px;
    height: 26px;
}

.${p}slot-actions .${p}icon-btn svg {
    width: 12px;
    height: 12px;
}

/* 插槽快捷鍵提示 */
.${p}slot-shortcut {
    font-size: 10px;
    padding: 2px 5px;
    background: ${c.btn};
    border: 1px solid ${c.border};
    border-radius: 4px;
    color: ${c.text3};
    font-family: 'SF Mono', Consolas, monospace;
    margin-left: 4px;
}

/* 插槽設定區塊 */
.${p}slots-settings {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid ${c.border};
}

.${p}slots-stats {
    display: flex;
    gap: 16px;
    padding: 8px 12px;
    background: ${c.bg2};
    border-radius: 6px;
    margin-bottom: 12px;
    font-size: 12px;
    color: ${c.text2};
}

.${p}slots-stats b {
    color: ${c.text1};
}

/* 插槽預覽面板 */
.${p}slot-preview-panel {
    width: min(600px, 90vw);
    max-height: min(70vh, 500px);
}

.${p}slot-preview-content {
    max-height: 300px;
    overflow-y: auto;
    padding: 12px;
    background: ${isDark ? '#1a1a2e' : '#fafafa'};
    border: 1px solid ${c.border};
    border-radius: 6px;
    font-family: 'SF Mono', Consolas, monospace;
    font-size: 13px;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}

/* 插槽標籤編輯 */
.${p}slot-label-input {
    width: 100%;
    padding: 6px 10px;
    border: 1px solid ${c.border};
    border-radius: 6px;
    background: ${c.bg1};
    color: ${c.text1};
    font-size: 13px;
    margin-bottom: 8px;
}

.${p}slot-label-input:focus {
    outline: none;
    border-color: ${c.accent};
    box-shadow: 0 0 0 2px ${c.accentLight};
}

/* 插槽面板底部按鈕區 */
.${p}slots-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid ${c.border};
    gap: 8px;
}

.${p}slots-footer-left,
.${p}slots-footer-right {
    display: flex;
    gap: 8px;
}

/* ===== 拖曳導入功能 ===== */

/* 拖曳覆蓋層 */
.${p}drop-overlay {
    position: fixed;
    inset: 0;
    background: ${isDark ? 'rgba(79, 172, 254, 0.12)' : 'rgba(79, 172, 254, 0.15)'};
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: ${CONFIG.zIndex + 200};
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s ease;
    cursor: copy;
}

.${p}drop-overlay.${p}active {
    opacity: 1;
    visibility: visible;
}

/* 拖曳提示框 */
.${p}drop-hint {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    padding: 48px 64px;
    background: ${c.bg1};
    border: 3px dashed ${c.accent};
    border-radius: 20px;
    box-shadow: ${c.shadow};
    text-align: center;
    pointer-events: none;
    animation: ${p}drop-hint-pulse 2s ease-in-out infinite;
}

@keyframes ${p}drop-hint-pulse {
    0%, 100% {
        border-color: ${c.accent};
        transform: scale(1);
    }
    50% {
        border-color: ${isDark ? '#00f2fe' : '#0056b3'};
        transform: scale(1.02);
    }
}

.${p}drop-icon {
    width: 64px;
    height: 64px;
    color: ${c.accent};
    animation: ${p}drop-icon-bounce 1s ease-in-out infinite;
}

.${p}drop-icon svg {
    width: 100%;
    height: 100%;
}

@keyframes ${p}drop-icon-bounce {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-8px); }
}

.${p}drop-title {
    font-size: 22px;
    font-weight: 700;
    color: ${c.text1};
}

.${p}drop-subtitle {
    font-size: 14px;
    color: ${c.text2};
    line-height: 1.6;
}

/* FAB 拖曳高亮 */
.${p}fab.${p}drag-over {
    transform: scale(1.2) !important;
    box-shadow:
        0 0 0 4px rgba(79, 172, 254, 0.5),
        0 0 20px rgba(79, 172, 254, 0.4),
        0 6px 20px rgba(102, 126, 234, 0.55) !important;
    animation: ${p}fab-drag-pulse 1s ease-in-out infinite !important;
}

@keyframes ${p}fab-drag-pulse {
    0%, 100% {
        box-shadow:
            0 0 0 4px rgba(79, 172, 254, 0.5),
            0 0 20px rgba(79, 172, 254, 0.4),
            0 6px 20px rgba(102, 126, 234, 0.55);
    }
    50% {
        box-shadow:
            0 0 0 8px rgba(79, 172, 254, 0.3),
            0 0 30px rgba(79, 172, 254, 0.5),
            0 6px 20px rgba(102, 126, 234, 0.55);
    }
}

/* Modal 拖曳高亮 */
.${p}modal.${p}drag-over {
    box-shadow:
        ${c.shadow},
        0 0 0 4px rgba(79, 172, 254, 0.5),
        0 0 30px rgba(79, 172, 254, 0.3) !important;
}

.${p}modal.${p}drag-over .${p}body {
    position: relative;
}

.${p}modal.${p}drag-over .${p}body::after {
    content: '';
    position: absolute;
    inset: 0;
    background: ${isDark ? 'rgba(79, 172, 254, 0.08)' : 'rgba(79, 172, 254, 0.05)'};
    border: 2px dashed ${c.accent};
    border-radius: 8px;
    pointer-events: none;
    animation: ${p}modal-drag-pulse 1.5s ease-in-out infinite;
}

@keyframes ${p}modal-drag-pulse {
    0%, 100% { opacity: 0.6; }
    50% { opacity: 1; }
}

/* 拖曳時禁用 Modal 內的 pointer-events(避免干擾) */
.${p}modal.${p}drag-over .${p}editor {
    pointer-events: none;
}

/* ===== 檔案系統設定 ===== */
.${p}fs-status {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    background: ${isDark ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)'};
    border-radius: 8px;
    margin-bottom: 8px;
}

.${p}fs-status-icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.${p}fs-status-icon svg {
    width: 100%;
    height: 100%;
}

.${p}fs-status-text {
    flex: 1;
    font-size: 13px;
    color: ${c.text1};
}

.${p}fs-status-badge {
    padding: 3px 8px;
    border-radius: 4px;
    font-size: 11px;
    font-weight: 600;
}

.${p}fs-status-badge.${p}supported {
    background: rgba(40, 167, 69, 0.15);
    color: ${isDark ? '#5cb85c' : '#28a745'};
}

.${p}fs-status-badge.${p}unsupported {
    background: rgba(220, 53, 69, 0.15);
    color: ${c.danger};
}

.${p}fs-dir-info {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    background: ${isDark ? 'rgba(79,172,254,0.1)' : 'rgba(0,123,255,0.08)'};
    border: 1px solid ${isDark ? 'rgba(79,172,254,0.3)' : 'rgba(0,123,255,0.2)'};
    border-radius: 6px;
    margin-top: 8px;
}

.${p}fs-dir-info svg {
    width: 16px;
    height: 16px;
    color: ${c.accent};
    flex-shrink: 0;
}

.${p}fs-dir-name {
    flex: 1;
    font-size: 13px;
    color: ${c.text1};
    font-weight: 500;
}

.${p}fs-dir-clear {
    padding: 4px 8px;
    font-size: 11px;
    color: ${c.text2};
    background: transparent;
    border: 1px solid ${c.border};
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.15s;
}

.${p}fs-dir-clear:hover {
    background: ${c.danger};
    border-color: ${c.danger};
    color: #fff;
}

/* ===== 快捷鍵面板 ===== */
.${p}shortcuts-panel {
    width: min(500px, 90vw);
    max-height: min(70vh, 550px);
}

.${p}shortcuts-content {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.${p}shortcuts-category {
    background: ${isDark ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)'};
    border: 1px solid ${c.border};
    border-radius: 8px;
    padding: 12px 16px;
}

.${p}shortcuts-category h5 {
    margin: 0 0 10px;
    font-size: 13px;
    font-weight: 700;
    color: ${c.accent};
    display: flex;
    align-items: center;
    gap: 6px;
}

.${p}shortcuts-table {
    width: 100%;
    border-collapse: collapse;
}

.${p}shortcuts-table tr {
    border-bottom: 1px solid ${isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)'};
}

.${p}shortcuts-table tr:last-child {
    border-bottom: none;
}

.${p}shortcuts-table td {
    padding: 8px 0;
    font-size: 13px;
}

.${p}shortcut-key {
    width: 140px;
    font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
    font-size: 12px;
}

.${p}shortcut-key code {
    padding: 3px 8px;
    background: ${c.btn};
    border: 1px solid ${c.border};
    border-radius: 4px;
    color: ${c.text1};
    white-space: nowrap;
}

.${p}shortcut-desc {
    color: ${c.text2};
}

/* ===== 閱讀時間顯示 ===== */
.${p}reading-time {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    color: ${c.text2};
}

.${p}reading-time svg {
    width: 12px;
    height: 12px;
}

/* ===== 設定區塊分隔 ===== */
.${p}settings-divider {
    height: 1px;
    background: ${c.border};
    margin: 16px 0;
}

.${p}settings-note {
    font-size: 11px;
    color: ${c.text3};
    padding: 8px 12px;
    background: ${isDark ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)'};
    border-radius: 6px;
    margin-top: 8px;
    line-height: 1.5;
}

/* ===== 備份匯出/匯入按鈕組 ===== */
.${p}backup-io-group {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 8px;
}

.${p}backup-io-group .${p}btn {
    flex: 1;
    min-width: 120px;
    justify-content: center;
}

/* ===== 備份分頁 ===== */
.${p}backup-pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 12px;
    border-top: 1px solid ${c.border};
    margin-top: 12px;
}

.${p}backup-page-info {
    font-size: 12px;
    color: ${c.text2};
}

.${p}backup-pagination .${p}btn[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ===== 螢幕閱讀器專用 ===== */
.${p}sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* 焦點指示器增強 */
.${p}btn:focus-visible,
.${p}icon-btn:focus-visible,
.${p}editor-select:focus-visible {
    outline: 2px solid ${c.accent};
    outline-offset: 2px;
}

/* 減少動態效果(尊重使用者偏好) */
@media (prefers-reduced-motion: reduce) {
    .${p}overlay,
    .${p}modal,
    .${p}toast,
    .${p}fab,
    .${p}drop-overlay,
    .${p}drop-hint {
        transition: none !important;
        animation: none !important;
    }
}

/* ===== 尋找與取代面板 ===== */
.${p}find-panel {
    position: fixed;
    z-index: ${CONFIG.zIndex + 700};
    background: ${c.bg1};
    border: 1px solid ${c.border};
    border-radius: 8px;
    box-shadow: ${c.shadowSm};
    padding: 12px;
    min-width: 320px;
    max-width: 450px;
    display: none;
    flex-direction: column;
    gap: 8px;
}

.${p}find-row,
.${p}find-replace-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

.${p}find-input {
    flex: 1;
    padding: 6px 10px;
    border: 1px solid ${c.border};
    border-radius: 6px;
    background: ${c.bg2};
    color: ${c.text1};
    font-size: 13px;
    min-width: 0;
}

.${p}find-input:focus {
    outline: none;
    border-color: ${c.accent};
    box-shadow: 0 0 0 2px ${c.accentLight};
}

.${p}find-options {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    font-size: 12px;
}

.${p}find-option {
    display: flex;
    align-items: center;
    gap: 4px;
    color: ${c.text2};
    cursor: pointer;
}

.${p}find-option input {
    margin: 0;
}

.${p}find-option:hover {
    color: ${c.text1};
}

.${p}find-status {
    font-size: 12px;
    color: ${c.text2};
    padding: 4px 0;
}

.${p}btn-sm {
    padding: 4px 10px;
    font-size: 12px;
}

/* ===== 響應式 ===== */
@media (max-width: 1000px) {
    .${p}btn span { display: none; }
    .${p}editor-select { min-width: 130px; }
}
@media (max-width: 760px) {
    .${p}modal {
        width: 100vw !important;
        height: 100vh !important;
        max-width: 100vw !important;
        max-height: 100vh !important;
        border-radius: 0 !important;
    }
    .${p}toolbar { border-radius: 0; }
    .${p}status-bar { border-radius: 0; }
    .${p}editor-select { min-width: 110px; }
}
            `;
        },

        /**
         * 初始化樣式系統
         */
        init() {
            this.el = Utils.addStyle(this.getCSS(Theme.get()), `${CONFIG.prefix}ui-style`);
            Theme.onChange((t) => this.update(t));
        },

        /**
         * 更新主題樣式
         * @param {string} theme
         */
        update(theme) {
            if (this.el) {
                this.el.textContent = this.getCSS(theme);
            }
        }
    };

    // ========================================
    // 工具列偏好設定
    // ========================================

    /**
     * 工具列偏好管理器
     *
     * 設計意圖:
     * - 定義所有可用的工具列按鈕
     * - 管理「顯示/隱藏」「左右區順序」「按鈕外觀」「狀態列設定」
     * - 提供快取機制減少 storage 讀取
     *
     * 快取策略:
     * - 使用 5 秒 TTL 平衡即時性與效能
     * - save() 後主動清除快取確保一致性
     * - 提供手動清除機制供特殊場景使用
     */
    const ToolbarPrefs = {
        /**
         * 效能優化:偏好設定快取
         * 避免過於頻繁地讀取 storage
         */
        _cache: null,
        _cacheTime: 0,
        _cacheTTL: 5000,  // 快取存活時間延長到 5 秒

        /**
         * 清除快取
         *
         * 使用時機:
         * - save() 內部自動調用
         * - 外部強制刷新設定時調用
         */
        clearCache() {
            this._cache = null;
            this._cacheTime = 0;
            if (DEBUG) {
                log('ToolbarPrefs: cache cleared');
            }
        },

        /**
         * 檢查快取是否有效
         * @returns {boolean}
         */
        _isCacheValid() {
            if (!this._cache) return false;
            return (Date.now() - this._cacheTime) < this._cacheTTL;
        },

        /**
         * 所有可用的工具列按鈕定義
         */
        allButtons: {
            // 基本操作
            import: { icon: 'import', label: '導入', group: 'basic', order: 1 },
            export: { icon: 'exportFile', label: '導出', group: 'basic', order: 2 },
            save: { icon: 'save', label: '保存', group: 'basic', order: 3, primary: true },
            clear: { icon: 'clear', label: '清空', group: 'basic', order: 4 },

            // 功能
            backup: { icon: 'database', label: '備份管理', group: 'features', order: 10 },
            focusMode: { icon: 'focus', label: '專注模式', group: 'features', order: 11 },
            settings: { icon: 'settings', label: '偏好設定', group: 'features', order: 12 },
            slots: { icon: 'slots', label: '快速存檔', group: 'features', order: 13, description: '快速存取多份文件' },

            // Vditor 專用
            vditorModeSv: { icon: 'code', label: 'SV 模式', group: 'vditor', order: 20, vditorOnly: true },
            vditorModeIr: { icon: 'eye', label: 'IR 模式', group: 'vditor', order: 21, vditorOnly: true },
            vditorModeWysiwyg: { icon: 'file', label: 'WYSIWYG 模式', group: 'vditor', order: 22, vditorOnly: true },
            vditorRestore: { icon: 'restore', label: '還原快照', group: 'vditor', order: 23, vditorOnly: true },
            vditorDownload: { icon: 'download', label: '下載快照', group: 'vditor', order: 24, vditorOnly: true },
            vditorDiag: { icon: 'shield', label: '診斷報告', group: 'vditor', order: 25, vditorOnly: true },

            // 主題和視窗
            theme: { icon: 'sun', label: '主題', group: 'window', order: 30 },
            maximize: { icon: 'expand', label: '放大', group: 'window', order: 31 },
            more: { icon: 'more', label: '更多', group: 'window', order: 32, alwaysShow: true },
            close: { icon: 'close', label: '關閉', group: 'window', order: 33, alwaysShow: true, danger: true }
        },

        appearanceOptions: ['icon-text', 'icon-only', 'text-only'],

        /**
         * 取得預設偏好設定
         */
        defaultPrefs() {
            return {
                show: {
                    import: true,
                    export: true,
                    save: true,
                    clear: true,

                    backup: false,
                    focusMode: false,
                    settings: false,
                    slots: true,  // 預設顯示快速存檔按鈕

                    vditorModeSv: false,
                    vditorModeIr: false,
                    vditorModeWysiwyg: false,
                    vditorRestore: false,
                    vditorDownload: false,
                    vditorDiag: false,

                    theme: true,
                    maximize: true,

                    more: true,
                    close: true
                },

                orderLeft: ['import', 'export', 'save', 'clear', 'slots', 'backup', 'focusMode', 'settings'],
                orderRight: [
                    'vditorModeSv', 'vditorModeIr', 'vditorModeWysiwyg',
                    'vditorRestore', 'vditorDownload', 'vditorDiag',
                    'theme', 'maximize', 'more', 'close'
                ],

                // 'icon-text' | 'icon-only' | 'text-only'
                buttonAppearance: 'icon-text',

                // 狀態列設定
                statusBar: {
                    enabled: true,
                    position: 'bottom',
                    showWordCount: true,
                    showLineCount: true,
                    showReadingTime: true,
                    showSaveTime: true
                },

                // 專注模式
                focusMode: false
            };
        },

        /**
         * 載入偏好設定(深度合併預設值)
         *
         * 效能優化:使用快取減少 storage 讀取
         * 快取會在設定被修改時主動清除
         *
         * @returns {Object} 完整的偏好設定物件
         */
        load() {
            // 快取有效時直接返回
            if (this._isCacheValid()) {
                return this._cache;
            }

            const now = Date.now();
            const raw = Utils.storage.get(CONFIG.storageKeys.toolbarCfg, null);
            const base = this.defaultPrefs();
            if (!raw) {
                // 快取預設值
                this._cache = base;
                this._cacheTime = now;
                return base;
            }

            const merged = {
                ...base,
                ...raw,
                show: { ...base.show, ...(raw.show || {}) },
                statusBar: { ...base.statusBar, ...(raw.statusBar || {}) }
            };

            // 確保順序陣列包含所有按鈕
            const allLeftBtns = Object.keys(this.allButtons).filter(k =>
                ['basic', 'features'].includes(this.allButtons[k].group)
            );
            const allRightBtns = Object.keys(this.allButtons).filter(k =>
                ['vditor', 'window'].includes(this.allButtons[k].group)
            );

            merged.orderLeft = this._ensureAllItems(merged.orderLeft || [], allLeftBtns);
            merged.orderRight = this._ensureAllItems(merged.orderRight || [], allRightBtns);

            // 驗證外觀選項
            if (!this.appearanceOptions.includes(merged.buttonAppearance)) {
                merged.buttonAppearance = 'icon-text';
            }

            // 更新快取
            this._cache = merged;
            this._cacheTime = now;

            return merged;
        },

        /**
         * 確保陣列包含所有項目
         * @private
         */
        _ensureAllItems(arr, allItems) {
            const result = arr.filter(x => allItems.includes(x));
            for (const item of allItems) {
                if (!result.includes(item)) result.push(item);
            }
            return result;
        },

        /**
         * 儲存偏好設定
         * @param {Object} prefs
         */
        save(prefs) {
            Utils.storage.set(CONFIG.storageKeys.toolbarCfg, prefs);
            // 清除快取,確保下次 load() 會讀取新值
            this.clearCache();
        },

        /**
         * 取得按鈕 HTML 內容(依外觀模式)
         * @param {string} key
         * @param {string} appearance
         * @returns {string}
         */
        getButtonContent(key, appearance) {
            const btn = this.allButtons[key];
            if (!btn) return '';

            const icon = Icons[btn.icon] || '';
            const label = btn.label || '';

            switch (appearance) {
                case 'icon-only':
                    return icon;
                case 'text-only':
                    return `<span>${label}</span>`;
                case 'icon-text':
                default:
                    return `${icon}<span>${label}</span>`;
            }
        },

        /**
         * 套用偏好設定到 Modal
         * @param {Object} modal - Modal 管理器(需具 modal.modal)
         */
        applyToModal(modal) {
            if (!modal?.modal) return;

            const p = CONFIG.prefix;
            const prefs = this.load();
            const isVditor = EditorManager.getCurrentInfo()?.key === 'vditor';

            // 套用專注模式 class(實際專注模式行為由 EnhanceUI/Modal 控制)
            modal.modal.classList.toggle(`${p}focus-mode`, !!prefs.focusMode);

            // 套用按鈕外觀 class
            modal.modal.classList.remove(`${p}btn-icon-only`, `${p}btn-text-only`, `${p}btn-icon-text`);
            modal.modal.classList.add(`${p}btn-${prefs.buttonAppearance}`);

            // 狀態列顯示位置
            this._applyStatusBarSettings(modal, prefs);

            // 按鈕顯示/隱藏 + 內容刷新
            this._applyButtonVisibility(modal, prefs, isVditor);

            // 修復:按鈕順序(原先不會生效)
            this.reorderToolbarButtons(modal, prefs);
        },

        /**
         * 套用狀態列設定
         * @private
         */
        _applyStatusBarSettings(modal, prefs) {
            const p = CONFIG.prefix;
            const statusBar = prefs.statusBar;

            const bottomStatus = modal.modal.querySelector(`#${p}status-bar`);
            const toolbarStatus = modal.modal.querySelector(`#${p}toolbar-status`);

            if (bottomStatus) {
                bottomStatus.style.display = (statusBar.enabled && statusBar.position === 'bottom') ? '' : 'none';
            }
            if (toolbarStatus) {
                toolbarStatus.style.display = (statusBar.enabled && statusBar.position === 'toolbar') ? '' : 'none';
            }
        },

        /**
         * 套用按鈕顯示設定並刷新按鈕內容
         * @private
         */
        _applyButtonVisibility(modal, prefs, isVditor) {
            Object.keys(this.allButtons).forEach(key => {
                const btnDef = this.allButtons[key];
                const btn = modal.modal.querySelector(`[data-action="${key}"]`);
                if (!btn) return;

                // Vditor 專用按鈕
                if (btnDef.vditorOnly && !isVditor) {
                    btn.style.display = 'none';
                    return;
                }

                // 固定顯示
                if (btnDef.alwaysShow) {
                    btn.style.display = '';
                    // 固定顯示按鈕通常是 icon-btn(內容可能由 Modal 自己覆蓋 tooltip 等)
                    btn.innerHTML = Icons[btnDef.icon] || btn.innerHTML;
                    return;
                }

                // 依偏好顯示/隱藏
                btn.style.display = prefs.show[key] ? '' : 'none';

                // 更新按鈕內容(外觀模式變更時需要)
                const content = this.getButtonContent(key, prefs.buttonAppearance);
                if (content) btn.innerHTML = content;
            });
        },

        /**
         * 修復:重新排列工具列按鈕順序(依 prefs.orderLeft / orderRight)
         *
         * 為何這是「確定的修復」:
         * - 舊版只保存順序,但沒有對 DOM 做任何重排,因此使用者感知為「設定不生效」
         *
         * @param {Object} modal
         * @param {Object} prefs
         */
        reorderToolbarButtons(modal, prefs) {
            const p = CONFIG.prefix;
            const left = modal.modal.querySelector(`#${p}toolbar-left`);
            const right = modal.modal.querySelector(`#${p}toolbar-right`);
            if (!left || !right) return;

            const moveInOrder = (parent, order) => {
                order.forEach(key => {
                    const el = parent.querySelector(`[data-action="${key}"]`);
                    if (el) parent.appendChild(el);
                });
            };

            // 左/右兩區按各自順序重排
            moveInOrder(left, prefs.orderLeft || []);
            moveInOrder(right, prefs.orderRight || []);
        }
    };

    // ========================================
    // EnhanceUI:增強 UI(專注模式 / 預覽面板 / FAB 選單)
    // ========================================

    /**
     * EnhanceUI
     *
     * 設計意圖(推測並尊重):
     * - 原團隊把 EnhanceUI 定位為「補強樣式」,而非「掌控流程」
     * - 因此此模組只負責:注入 CSS + 跟隨 Theme 更新
     *
     * 本段確認屬於「修復/升級」的理由:
     * - 使用者回報專注模式/工作情境需要更舒適
     * - 片段 8 已加入 toolbar wrap(避免按鈕溢出),專注模式需兼容該行為
     * - FAB 的「潛力」要擴展,但先以低耦合樣式與框架為主
     */
    const EnhanceUI = {
        styleId: `${CONFIG.prefix}enhance-style`,

        getCSS(theme) {
            const isDark = theme === 'dark';
            const p = CONFIG.prefix;

            const c = isDark ? {
                panel: '#1e1e2e',
                border: '#2d3748',
                text: '#e8e8e8',
                text2: '#a0a0a0',
                accent: '#4facfe',
                btnHover: '#2d3748',
                menuBg: '#151526',
                shadow: '0 25px 50px -12px rgba(0,0,0,0.5)'
            } : {
                panel: '#ffffff',
                border: '#dee2e6',
                text: '#212529',
                text2: '#6c757d',
                accent: '#007bff',
                btnHover: '#f1f3f4',
                menuBg: '#ffffff',
                shadow: '0 25px 50px -12px rgba(0,0,0,0.25)'
            };

            return `
/* ========================================
   EnhanceUI:專注模式
   ======================================== */

/* 專注模式下工具列在底部,預設隱藏
   注意:片段 8 已讓 toolbar 可 wrap 且 max-height 有捲動;
   這裡在 focus-mode 時覆蓋,避免高度限制造成動畫/可用性問題。 */
.${p}modal.${p}focus-mode .${p}toolbar {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    top: auto;

    background: ${c.panel};
    border: none;
    border-top: 1px solid ${c.border};
    border-radius: 0 0 12px 12px;

    padding: 8px 12px;

    /* focus-mode 下工具列可能需要多行 + 捲動,但不要受片段 8 的 max-height 影響 */
    max-height: none !important;
    overflow: visible !important;

    opacity: 0;
    transform: translateY(100%);
    transition: opacity 0.2s ease, transform 0.2s ease;

    z-index: 10;
    cursor: default;
    pointer-events: none;
}

/* 專注模式:工具列可見 */
.${p}modal.${p}focus-mode .${p}toolbar.${p}visible,
.${p}modal.${p}focus-mode .${p}toolbar:focus-within,
.${p}modal.${p}focus-mode .${p}toolbar.${p}has-open-menu {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

/* 專注模式:工具列內部仍維持 flex(避免被隱藏的 display 覆蓋) */
.${p}modal.${p}focus-mode .${p}toolbar-left,
.${p}modal.${p}focus-mode .${p}toolbar-status,
.${p}modal.${p}focus-mode .${p}toolbar-spacer,
.${p}modal.${p}focus-mode .${p}editor-select,
.${p}modal.${p}focus-mode .${p}toolbar-right {
    display: flex !important;
}

/* 專注模式:主體填滿 */
.${p}modal.${p}focus-mode .${p}body {
    height: 100%;
    border-radius: 12px;
}

/* 專注模式提示 */
.${p}modal.${p}focus-mode.${p}show-hint::after {
    content: '專注模式 · 滑鼠移至底部顯示工具列 · 按 Esc 退出';
    position: absolute;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    padding: 8px 20px;
    background: rgba(0,0,0,0.8);
    color: #fff;
    font-size: 12px;
    border-radius: 20px;
    animation: ${p}focus-hint 4s ease forwards;
    pointer-events: none;
    z-index: 100;
}
@keyframes ${p}focus-hint {
    0% { opacity: 0; }
    10% { opacity: 1; }
    80% { opacity: 1; }
    100% { opacity: 0; }
}

/* ========================================
   EnhanceUI:預覽面板(備份預覽等)
   ======================================== */

.${p}preview-panel {
    width: min(700px, 90vw);
    max-height: min(80vh, 600px);
}

.${p}preview-content {
    padding: 16px;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    line-height: 1.6;
    color: ${c.text};
    overflow-y: auto;
    max-height: 420px;
    background: ${isDark ? '#1a1a2e' : '#fafafa'};
    border-radius: 8px;
    border: 1px solid ${c.border};
}
.${p}preview-content pre {
    white-space: pre-wrap;
    word-break: break-word;
    font-family: 'SF Mono', Consolas, monospace;
    font-size: 13px;
}

/* ========================================
   EnhanceUI:FAB 右鍵/長按選單(僅框架,行為由 FAB/Modal 決定)
   ======================================== */

.${p}fab-menu {
    position: fixed;
    min-width: 200px;
    max-width: 260px;
    background: ${c.menuBg};
    border: 1px solid ${c.border};
    border-radius: 12px;
    box-shadow: ${c.shadow};
    padding: 8px;
    z-index: ${CONFIG.zIndex + 900};
    display: none;
}

.${p}fab-menu.${p}open {
    display: block;
}

.${p}fab-menu-item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: none;
    border-radius: 10px;
    background: transparent;
    color: ${c.text};
    cursor: pointer;
    font-size: 13px;
    text-align: left;
}

.${p}fab-menu-item:hover {
    background: ${c.btnHover};
}

.${p}fab-menu-item svg {
    width: 16px;
    height: 16px;
    display: block;
    flex-shrink: 0;
}

.${p}fab-menu-sep {
    height: 1px;
    background: ${c.border};
    margin: 6px 6px;
}
            `;
        },

        apply() {
            Utils.addStyle(this.getCSS(Theme.get()), this.styleId);
            Theme.onChange((t) => {
                Utils.addStyle(this.getCSS(t), this.styleId);
            });
        }
    };

    // ========================================
    // FAB:懸浮按鈕管理器(保留既有行為 + 低耦合擴展框架)
    // ========================================

    /**
     * FAB
     *
     * 設計意圖(推測並尊重):
     * - FAB 是「入口」:讓使用者隨時打開編輯器
     * - 原團隊避免把 FAB 做得太複雜,可能是因為 Modal/設定系統尚在迭代
     *
     * 本段確認屬於「升級」的理由:
     * - 使用者明確要求發揮懸浮鈕潛力
     * - 但仍遵守低耦合:FAB 只負責 UI 與 action 派發,實際功能由 Modal 執行(片段 10)
     */
    const FAB = {
        el: null,

        // 拖曳狀態
        isDragging: false,
        hasMoved: false,
        startPos: { x: 0, y: 0 },
        offset: { x: 0, y: 0 },

        // 選單
        menuEl: null,
        _menuOpen: false,
        _docClickHandler: null,

        // 長按
        _longPressTimer: null,

        // 暫存的頁面選取文字(用於導入選取)
        _pendingSelection: '',

        create() {
            const p = CONFIG.prefix;

            if (this.el) return; // 避免重複 create

            this.el = document.createElement('button');
            this.el.className = `${p}fab`;
            this.el.type = 'button';
            this.el.title = '開啟 Markdown 編輯器 (Alt+M)';
            this.el.innerHTML = Icons.markdown;

            // 還原位置
            const savedPos = Utils.storage.get(CONFIG.storageKeys.buttonPos);
            if (savedPos?.left && savedPos?.top) {
                this.el.style.left = savedPos.left;
                this.el.style.top = savedPos.top;
                this.el.style.right = 'auto';
                this.el.style.bottom = 'auto';
            }

            this.bindEvents();
            document.body.appendChild(this.el);
        },

        /**
         * 建立 FAB 選單(低耦合:只提供入口,實作交由 Modal)
         */
        _ensureMenu() {
            if (this.menuEl) return;

            const p = CONFIG.prefix;
            const menu = document.createElement('div');
            menu.className = `${p}fab-menu`;

            // 選單內容將由 _updateMenuContent() 動態生成
            menu.innerHTML = '';

            // 點擊選單項目
            menu.addEventListener('click', (e) => {
                const item = e.target.closest(`[data-action]`);
                if (!item) return;
                const action = item.getAttribute('data-action');
                this.hideMenu();
                this._dispatch(action);
            });

            // 放到 Portal(避免被頁面 overflow 裁切)
            try {
                Portal.append(menu);
            } catch (e) {
                // 若 Portal 尚未 init,也可直接丟到 body
                document.body.appendChild(menu);
            }

            this.menuEl = menu;

            // 點擊外部關閉
            this._docClickHandler = (e) => {
                if (!this._menuOpen) return;
                if (this.menuEl?.contains(e.target)) return;
                if (this.el?.contains(e.target)) return;
                this.hideMenu();
            };
            document.addEventListener('click', this._docClickHandler, true);
        },

        showMenu(x, y) {
            this._ensureMenu();
            if (!this.menuEl) return;

            const p = CONFIG.prefix;

            // 每次顯示前更新選單內容(動態項目)
            this._updateMenuContent();

            // 先顯示以計算尺寸
            this.menuEl.classList.add(`${p}open`);
            this._menuOpen = true;

            // 初始位置
            const pad = 10;
            const vw = window.innerWidth;
            const vh = window.innerHeight;

            // 先放置
            this.menuEl.style.left = `${x}px`;
            this.menuEl.style.top = `${y}px`;

            // 邊界修正
            const rect = this.menuEl.getBoundingClientRect();
            let left = x;
            let top = y;

            if (left + rect.width > vw - pad) left = vw - rect.width - pad;
            if (top + rect.height > vh - pad) top = vh - rect.height - pad;

            left = Math.max(pad, left);
            top = Math.max(pad, top);

            this.menuEl.style.left = `${left}px`;
            this.menuEl.style.top = `${top}px`;
        },

        hideMenu() {
            if (!this.menuEl) return;
            const p = CONFIG.prefix;
            this.menuEl.classList.remove(`${p}open`);
            this._menuOpen = false;
        },

        /**
         * 更新 FAB 選單內容(動態生成)
         * 根據當前狀態顯示不同選項
         */
        _updateMenuContent() {
            if (!this.menuEl) return;

            const p = CONFIG.prefix;

            // 檢測專注模式狀態
            let inFocusMode = false;
            try {
                const prefs = ToolbarPrefs.load();
                inFocusMode = prefs.focusMode && Modal.isOpen;
            } catch (e) {
                // ToolbarPrefs 可能尚未初始化
            }

            // 檢測 Modal 開啟狀態
            const isModalOpen = Modal?.isOpen || false;

            // 檢測主題
            let isDark = false;
            try {
                isDark = Theme.isDark();
            } catch (e) {
                // Theme 可能尚未初始化
            }

            // 動態生成選單 HTML
            let html = '';

            // 開啟/關閉編輯器
            html += `
                <button class="${p}fab-menu-item" data-action="toggle">
                    ${Icons.edit} <span>${isModalOpen ? '關閉編輯器' : '開啟編輯器'}</span>
                </button>
            `;

            // 專注模式下顯示退出選項
            if (inFocusMode) {
                html += `
                    <button class="${p}fab-menu-item" data-action="exit-focus">
                        ${Icons.collapse} <span>退出專注模式</span>
                    </button>
                `;
            }

            html += `
                <button class="${p}fab-menu-item" data-action="backup">
                    ${Icons.database} <span>備份管理</span>
                </button>
                <button class="${p}fab-menu-item" data-action="settings">
                    ${Icons.settings} <span>偏好設定</span>
                </button>
                <div class="${p}fab-menu-sep"></div>
                <button class="${p}fab-menu-item" data-action="theme">
                    ${isDark ? Icons.sun : Icons.moon} <span>${isDark ? '切換淺色' : '切換深色'}</span>
                </button>
                <button class="${p}fab-menu-item" data-action="import-selection">
                    ${Icons.import} <span>導入選取文字</span>
                </button>
            `;

            this.menuEl.innerHTML = html;
        },

        /**
         * 取得並清空暫存的選取文字
         * @returns {string} 暫存的選取文字
         */
        getPendingSelection() {
            const text = this._pendingSelection;
            this._pendingSelection = '';
            return text;
        },

        /**
         * action 派發(低耦合:盡量透過 Modal 執行)
         */
        _dispatch(action) {
            try {
                // 確保 Modal 已初始化
                if (typeof Modal === 'undefined' || !Modal._inited) {
                    Toast.warning('編輯器正在載入,請稍候...');
                    return;
                }

                switch (action) {
                    case 'toggle':
                        Modal.toggle();
                        return;

                    case 'exit-focus':
                        try {
                            if (Modal?.isOpen) {
                                const prefs = ToolbarPrefs.load();
                                if (prefs.focusMode) {
                                    prefs.focusMode = false;
                                    ToolbarPrefs.save(prefs);
                                    ToolbarPrefs.applyToModal(Modal);
                                    Modal._applyStatusMetricVisibility?.();
                                    Modal.syncThemeButton?.();
                                    Modal.syncMaximizeButton?.();
                                    Toast.info('已退出專注模式');
                                } else {
                                    Toast.info('目前不在專注模式中');
                                }
                            } else {
                                Toast.info('請先開啟編輯器');
                            }
                        } catch (e) {
                            logError('Exit focus mode error:', e);
                            Toast.error('操作失敗');
                        }
                        return;

                    case 'backup':
                        if (Modal.isOpen) {
                            Modal.showBackupPanel?.();
                        } else {
                            Modal.open?.().then(() => {
                                setTimeout(() => Modal.showBackupPanel?.(), 200);
                            }).catch(e => {
                                Toast.error('無法開啟編輯器');
                                log('FAB backup open error:', e);
                            });
                        }
                        return;

                    case 'settings':
                        if (Modal.isOpen) {
                            Modal.showSettingsPanel?.();
                        } else {
                            Modal.open?.().then(() => {
                                setTimeout(() => Modal.showSettingsPanel?.(), 200);
                            }).catch(e => {
                                Toast.error('無法開啟編輯器');
                                log('FAB settings open error:', e);
                            });
                        }
                        return;

                    case 'theme': {
                        const newTheme = Theme.toggle();
                        Modal.syncThemeButton?.();
                        try {
                            EditorManager.setTheme(Theme.get());
                        } catch (e) { /* ignore - 編輯器可能尚未初始化 */ }
                        Toast.info(`已切換到${newTheme === 'dark' ? '深色' : '淺色'}主題`);
                        return;
                    }

                    case 'import-selection': {
                        // 預先檢查是否有選取文字(避免開啟 Modal 後才發現沒內容)
                        const pendingText = this._pendingSelection;
                        if (!pendingText && !Utils.getSelectedText()) {
                            Toast.warning('請先在頁面上選取文字');
                            return;
                        }

                        if (Modal.isOpen) {
                            Modal.importText?.();
                        } else {
                            Modal.open?.().then(() => {
                                setTimeout(() => Modal.importText?.(), 250);
                            }).catch(e => {
                                Toast.error('無法開啟編輯器');
                            });
                        }
                        return;
                    }

                    default:
                        log('FAB unknown action:', action);
                }
            } catch (e) {
                logError('FAB dispatch error:', e?.message || e);
                Toast.error('操作失敗,請重試');
            }
        },

        bindEvents() {
            const p = CONFIG.prefix;

            // ========================================
            // 在任何互動前捕獲頁面選取文字
            // 理由: click/mousedown 導致選取被清除
            // ========================================
            const captureSelection = () => {
                try {
                    const sel = window.getSelection();
                    const text = sel?.toString() || '';
                    if (text.trim()) {
                        this._pendingSelection = text;
                    }
                } catch (e) {
                    // 某些頁面可能限制 selection API
                }
            };

            // 在 mousedown 時捕獲(比 click 更早)
            this.el.addEventListener('mousedown', captureSelection, true);

            // 在 contextmenu 時也捕獲(右鍵選單)
            this.el.addEventListener('contextmenu', (e) => {
                captureSelection();
                // 注意:後續的 contextmenu 處理會在下方
            }, true);

            // click:未拖曳時切換 Modal
            this.el.addEventListener('click', (e) => {
                // 若剛拖曳移動,忽略 click(避免誤觸)
                if (this.hasMoved) return;

                e.preventDefault();
                e.stopPropagation();

                if (typeof Modal !== 'undefined' && Modal?.toggle) {
                    Modal.toggle();
                }
            });

            // 右鍵選單
            this.el.addEventListener('contextmenu', (e) => {
                e.preventDefault();
                e.stopPropagation();
                this.showMenu(e.clientX, e.clientY);
            });

            // 長按(行動裝置)
            this.el.addEventListener('touchstart', (e) => {
                if (e.touches?.length !== 1) return;

                clearTimeout(this._longPressTimer);
                this._longPressTimer = setTimeout(() => {
                    const t = e.touches[0];
                    this.showMenu(t.clientX, t.clientY);
                }, CONFIG.timing.fabLongPressDelay);
            }, { passive: true });

            this.el.addEventListener('touchend', () => {
                clearTimeout(this._longPressTimer);
            }, { passive: true });

            this.el.addEventListener('touchmove', () => {
                clearTimeout(this._longPressTimer);
            }, { passive: true });

            // 拖曳
            const getPos = (e) => {
                if (e.touches?.[0]) {
                    return { x: e.touches[0].clientX, y: e.touches[0].clientY };
                }
                return { x: e.clientX, y: e.clientY };
            };

            const onStart = (e) => {
                // 只允許左鍵拖曳
                if (e.type === 'mousedown' && e.button !== 0) return;

                const rect = this.el.getBoundingClientRect();
                const pos = getPos(e);
                this.offset = { x: pos.x - rect.left, y: pos.y - rect.top };
                this.startPos = pos;
                this.isDragging = true;
                this.hasMoved = false;
                this.el.classList.add(`${p}dragging`);
            };

            const onMove = (e) => {
                if (!this.isDragging) return;

                const pos = getPos(e);
                const dist = Math.hypot(pos.x - this.startPos.x, pos.y - this.startPos.y);

                if (dist > 5) {
                    // 開始判定為拖曳
                    this.hasMoved = true;

                    const x = Utils.clamp(
                        pos.x - this.offset.x,
                        0,
                        window.innerWidth - this.el.offsetWidth
                    );
                    const y = Utils.clamp(
                        pos.y - this.offset.y,
                        0,
                        window.innerHeight - this.el.offsetHeight
                    );

                    this.el.style.left = `${x}px`;
                    this.el.style.top = `${y}px`;
                    this.el.style.right = 'auto';
                    this.el.style.bottom = 'auto';

                    // 防止頁面被拖曳時觸控滾動
                    if (e.cancelable) e.preventDefault();
                }
            };

            const onEnd = () => {
                if (!this.isDragging) return;

                this.el.classList.remove(`${p}dragging`);
                this.isDragging = false;

                if (this.hasMoved) {
                    Utils.storage.set(CONFIG.storageKeys.buttonPos, {
                        left: this.el.style.left,
                        top: this.el.style.top
                    });
                    // 短暫延遲避免 click 立刻觸發
                    setTimeout(() => { this.hasMoved = false; }, 120);
                }
            };

            this.el.addEventListener('mousedown', onStart);
            this.el.addEventListener('touchstart', onStart, { passive: false });

            document.addEventListener('mousemove', onMove);
            document.addEventListener('touchmove', onMove, { passive: false });

            document.addEventListener('mouseup', onEnd);
            document.addEventListener('touchend', onEnd);
        },

        /**
         * 設定 FAB loading 狀態
         * @param {boolean} loading
         */
        setLoading(loading) {
            const p = CONFIG.prefix;
            if (!this.el) return;

            if (loading) {
                this.el.classList.add(`${p}loading`);
                this.el.innerHTML = Icons.loading;
            } else {
                this.el.classList.remove(`${p}loading`);
                this.el.innerHTML = Icons.markdown;
            }
        }
    };

    // ========================================
    // [SEGMENT_10A]
    // Modal 主視窗(Core)
    // ========================================

    const Modal = {
        // ===== DOM refs =====
        container: null,
        overlay: null,
        modal: null,
        toolbar: null,
        editorContainer: null,
        fileInput: null,

        loadingEl: null,
        loadingText: null,

        // 狀態列容器
        statusBar: null,
        toolbarStatus: null,

        // status metrics (bottom)
        wcEl: null,
        lcEl: null,
        rtEl: null,  // 閱讀時間
        saveTimeEl: null,

        // status metrics (toolbar)
        wcToolbarEl: null,
        lcToolbarEl: null,
        rtToolbarEl: null,  // 閱讀時間(工具列)
        saveTimeToolbarEl: null,

        // metric 元素快取(用於 _applyStatusMetricVisibility)
        _statusMetrics: null,

        statusTextEl: null,

        // portal panels
        moreBtn: null,
        morePanel: null,
        importPanel: null,
        exportPanel: null,
        settingsPanel: null,
        backupPanel: null,
        tooltipEl: null,

        // ===== state =====
        isOpen: false,
        isFullscreen: false,
        isDragging: false,
        dragOffset: { x: 0, y: 0 },
        _opening: false,
        _hasOpenMenu: false,

        miniSlotsEl: null,

        // timers
        autoSaveTimer: null,
        wordCountTimer: null,

        // 效能優化:內容 hash 快取(避免重複計算字數)
        _lastContentHash: null,
        _lastWordCountStats: null,

        // resize observer
        _ro: null,

        // background scroll lock
        _savedBodyOverflow: '',
        _savedBodyPaddingRight: '',
        _savedHtmlOverflow: '',

        // init guard
        _inited: false,

        init() {
            if (this._inited) return;
            this._inited = true;

            Portal.init();

            this.createDOM();
            this.createPortalPanels();
            this.bindCoreEvents();

            this.restorePosition();
            this._initModalResize();
            this.initResizeObserver();

            EditorPreviewStyles.inject();

            // Backup auto callback:避免 BackupManager 直接依賴 EditorManager
            // 使用 SafeExecute 確保錯誤被正確處理且不影響其他功能
            BackupManager.setAutoBackupCallback(() => {
                SafeExecute.run(async () => {
                    if (!this.isOpen) return;
                    if (!EditorManager.isReady()) return;

                    const content = EditorManager.getValue();
                    if (!content || !content.trim()) return;

                    const info = EditorManager.getCurrentInfo();

                    // 建立內部備份
                    BackupManager.create(content, {
                        editorKey: info?.key,
                        mode: info?.adapter?._detectModeFromDOM?.()
                    });

                    // 嘗試備份到檔案系統(異步,不阻塞)
                    try {
                        await FileSystemManager.autoBackupToDirectory(content);
                    } catch (fsError) {
                        // 靜默失敗,不打擾使用者
                        log('FileSystem auto backup skipped:', fsError?.message || 'no handle');
                    }
                }, null, 'auto-backup-callback');
            });

            // 套用工具列偏好(含順序/顯示)
            ToolbarPrefs.applyToModal(this);

            // 綁定 Modal 拖曳事件
            try {
                DragDropManager.bindModalDragEvents(this.modal);
            } catch (e) {
                log('Modal: DragDrop binding skipped:', e?.message);
            }

            // 讓 statusBar 的 metrics 顯示控制真正生效
            this._applyStatusMetricVisibility();

            // 同步動態按鈕(尊重 buttonAppearance)
            this.syncThemeButton();
            this.syncMaximizeButton();
            this._bindMiniSlotsBarEventsOnce?.();
            this.updateMiniSlotsBar?.();
        },

        // ----------------------------------------
        // DOM 建立
        // ----------------------------------------

        createDOM() {
            const p = CONFIG.prefix;
            const prefs = ToolbarPrefs.load();

            const savedEditor = Utils.storage.get(CONFIG.storageKeys.editor, CONFIG.defaultEditor);

            const editorOptions = getSortedEditors().map(([key, cfg]) =>
                `<option value="${key}" ${key === savedEditor ? 'selected' : ''}>${cfg.icon} ${cfg.name}</option>`
            ).join('');

            const leftButtons = this._generateToolbarButtons(prefs.orderLeft, prefs);
            const rightButtons = this._generateToolbarButtons(prefs.orderRight, prefs);

            this.container = document.createElement('div');
            this.container.id = `${p}container`;

            this.overlay = document.createElement('div');
            this.overlay.className = `${p}overlay`;

            this.modal = document.createElement('div');
            this.modal.className = `${p}modal ${p}btn-${prefs.buttonAppearance}`;
            this.modal.setAttribute('role', 'dialog');
            this.modal.setAttribute('aria-modal', 'true');
            this.modal.setAttribute('aria-labelledby', `${p}modal-title`);
            this.modal.setAttribute('aria-describedby', `${p}modal-desc`);

            // 為螢幕閱讀器新增隱藏的描述
            const srDesc = document.createElement('div');
            srDesc.id = `${p}modal-desc`;
            srDesc.className = `${p}sr-only`;
            srDesc.textContent = 'Markdown 編輯器視窗。使用 Escape 鍵關閉,使用 Tab 鍵在控制項之間移動。';
            this.modal.appendChild(srDesc);

            // 還原尺寸
            const savedSize = Utils.storage.get(CONFIG.storageKeys.modalSize);
            if (savedSize?.width && savedSize?.height) {
                this.modal.style.width = savedSize.width;
                this.modal.style.height = savedSize.height;
            }

            this.modal.innerHTML = `
                <div class="${p}toolbar" data-action="drag-zone">
                    <select class="${p}editor-select" id="${p}editor-select" data-tooltip="選擇編輯器">
                        ${editorOptions}
                    </select>

                    <div class="${p}mini-slots" id="${p}mini-slots" style="display:none;" aria-label="迷你插槽列"></div>

                    <div class="${p}toolbar-left" id="${p}toolbar-left">
                        ${leftButtons}
                    </div>

                    <div class="${p}toolbar-spacer"></div>

                    <div class="${p}toolbar-status" id="${p}toolbar-status" style="display:${prefs.statusBar.enabled && prefs.statusBar.position === 'toolbar' ? '' : 'none'}">
                        <span class="${p}metric" data-metric="wc"><span id="${p}wc-toolbar">0</span> 字</span>
                        <span class="${p}sep" data-metric="sep-wc">·</span>
                        <span class="${p}metric" data-metric="lc"><span id="${p}lc-toolbar">0</span> 行</span>
                        <span class="${p}sep" data-metric="sep-lc">·</span>
                        <span class="${p}metric ${p}reading-time" data-metric="rt">
                            ${Icons.clock}
                            <span id="${p}reading-time-toolbar">約 1 分鐘</span>
                        </span>
                        <span class="${p}sep" data-metric="sep-rt">·</span>
                        <span class="${p}metric" data-metric="save"><span id="${p}save-time-toolbar">未保存</span></span>
                    </div>

                    <div class="${p}toolbar-right" id="${p}toolbar-right">
                        ${rightButtons}
                    </div>
                </div>

                <div class="${p}body">
                    <div class="${p}loading" id="${p}loading">
                        <div class="${p}spinner"></div>
                        <div class="${p}loading-text" id="${p}loading-text">正在載入編輯器...</div>
                    </div>
                    <div class="${p}editor" id="${p}editor"></div>
                </div>

                <div class="${p}status-bar" id="${p}status-bar" style="display:${prefs.statusBar.enabled && prefs.statusBar.position === 'bottom' ? '' : 'none'}">
                    <span class="${p}metric" data-metric="wc"><span id="${p}wc">0</span> 字</span>
                    <span class="${p}sep" data-metric="sep-wc">·</span>
                    <span class="${p}metric" data-metric="lc"><span id="${p}lc">0</span> 行</span>
                    <span class="${p}sep" data-metric="sep-lc">·</span>
                    <span class="${p}metric ${p}reading-time" data-metric="rt">
                        ${Icons.clock}
                        <span id="${p}reading-time">約 1 分鐘</span>
                    </span>
                    <span class="${p}sep" data-metric="sep-rt">·</span>
                    <span class="${p}metric" data-metric="save"><span id="${p}save-time">未保存</span></span>
                    <span class="${p}status-spacer"></span>
                    <span id="${p}status-text">就緒</span>
                </div>
            `;

            this.fileInput = document.createElement('input');
            this.fileInput.type = 'file';
            this.fileInput.accept = '.md,.txt,.markdown,.mdown,.mkd,.mkdn,.mdwn,.mdtxt,.mdtext,.text';
            this.fileInput.style.display = 'none';

            this.overlay.appendChild(this.modal);
            this.overlay.appendChild(this.fileInput);
            this.container.appendChild(this.overlay);
            document.body.appendChild(this.container);

            // ========================================
            // 元素快取(減少重複 DOM 查詢)
            // ========================================

            // 基礎結構元素
            this.toolbar = this.modal.querySelector(`.${p}toolbar`);
            this.editorContainer = this.modal.querySelector(`#${p}editor`);
            this.loadingEl = this.modal.querySelector(`#${p}loading`);
            this.loadingText = this.modal.querySelector(`#${p}loading-text`);

            // 狀態列容器(底部)
            this.statusBar = this.modal.querySelector(`#${p}status-bar`);
            // 工具列狀態區(工具列內)
            this.toolbarStatus = this.modal.querySelector(`#${p}toolbar-status`);

            // 底部狀態列元素
            this.wcEl = this.modal.querySelector(`#${p}wc`);
            this.lcEl = this.modal.querySelector(`#${p}lc`);
            this.rtEl = this.modal.querySelector(`#${p}reading-time`);
            this.saveTimeEl = this.modal.querySelector(`#${p}save-time`);

            // 工具列狀態元素
            this.wcToolbarEl = this.modal.querySelector(`#${p}wc-toolbar`);
            this.lcToolbarEl = this.modal.querySelector(`#${p}lc-toolbar`);
            this.rtToolbarEl = this.modal.querySelector(`#${p}reading-time-toolbar`);
            this.saveTimeToolbarEl = this.modal.querySelector(`#${p}save-time-toolbar`);

            // 狀態列 metric 元素快取(用於 _applyStatusMetricVisibility)
            // 底部狀態列
            this._statusMetrics = {
                bottom: this.statusBar ? {
                    wc: this.statusBar.querySelector(`[data-metric="wc"]`),
                    lc: this.statusBar.querySelector(`[data-metric="lc"]`),
                    rt: this.statusBar.querySelector(`[data-metric="rt"]`),
                    save: this.statusBar.querySelector(`[data-metric="save"]`),
                    sepWc: this.statusBar.querySelector(`[data-metric="sep-wc"]`),
                    sepLc: this.statusBar.querySelector(`[data-metric="sep-lc"]`),
                    sepRt: this.statusBar.querySelector(`[data-metric="sep-rt"]`)
                } : null,
                // 工具列狀態區
                toolbar: this.toolbarStatus ? {
                    wc: this.toolbarStatus.querySelector(`[data-metric="wc"]`),
                    lc: this.toolbarStatus.querySelector(`[data-metric="lc"]`),
                    rt: this.toolbarStatus.querySelector(`[data-metric="rt"]`),
                    save: this.toolbarStatus.querySelector(`[data-metric="save"]`),
                    sepWc: this.toolbarStatus.querySelector(`[data-metric="sep-wc"]`),
                    sepLc: this.toolbarStatus.querySelector(`[data-metric="sep-lc"]`),
                    sepRt: this.toolbarStatus.querySelector(`[data-metric="sep-rt"]`)
                } : null
            };

            // 其他元素
            this.statusTextEl = this.modal.querySelector(`#${p}status-text`);
            this.moreBtn = this.modal.querySelector(`[data-action="more"]`);
            this.miniSlotsEl = this.modal.querySelector(`#${p}mini-slots`);
        },

        _generateToolbarButtons(order, prefs) {
            const p = CONFIG.prefix;
            const btns = ToolbarPrefs.allButtons;

            return (order || []).map(key => {
                const btn = btns[key];
                if (!btn) return '';

                // 增強的 tooltip 和 aria-label
                let tooltip = btn.label;
                let ariaLabel = btn.label;

                if (key === 'save') {
                    tooltip = '保存草稿(Ctrl+S)';
                    ariaLabel = '保存草稿,快捷鍵 Control 加 S';
                }
                if (key === 'export') {
                    tooltip = '導出(下載/複製)';
                    ariaLabel = '導出選單,提供下載和複製選項';
                }
                if (key === 'backup') {
                    tooltip = '備份管理(歷史版本)';
                    ariaLabel = '開啟備份管理面板,查看歷史版本';
                }
                if (key === 'close') {
                    tooltip = '關閉編輯器(Escape)';
                    ariaLabel = '關閉編輯器,快捷鍵 Escape';
                }

                const content = ToolbarPrefs.getButtonContent(key, prefs.buttonAppearance);
                const classes = [
                    btn.alwaysShow ? `${p}icon-btn` : `${p}btn`,
                    btn.primary ? `${p}primary` : '',
                    btn.danger ? `${p}danger` : ''
                ].filter(Boolean).join(' ');

                const display = (prefs.show?.[key] || btn.alwaysShow) ? '' : 'display:none;';

                return `
                    <button class="${classes}"
                            data-action="${key}"
                            data-tooltip="${Utils.escapeHtml(tooltip)}"
                            aria-label="${Utils.escapeHtml(ariaLabel)}"
                            style="${display}"
                            type="button">
                        ${content}
                    </button>
                `;
            }).join('');
        },

        createPortalPanels() {
            const p = CONFIG.prefix;

            // more menu(10B 會完整渲染互補內容)
            this.morePanel = document.createElement('div');
            this.morePanel.className = `${p}menu-panel`;
            this.morePanel.id = `${p}more-panel`;
            Portal.append(this.morePanel);

            // import panel
            this.importPanel = document.createElement('div');
            this.importPanel.className = `${p}io-panel`;
            this.importPanel.id = `${p}import-panel`;
            this.importPanel.style.display = 'none';
            this.importPanel.setAttribute('role', 'dialog');
            this.importPanel.setAttribute('aria-label', '導入內容');
            this.importPanel.setAttribute('aria-modal', 'true');
            this.importPanel.innerHTML = `
                <h4>導入內容</h4>
                <button class="${p}btn" data-action="import-selection" type="button">
                    ${Icons.import} <span>導入頁面選取文字</span>
                </button>
                <button class="${p}btn" data-action="import-file" type="button">
                    ${Icons.file} <span>導入檔案</span>
                </button>
                <div class="${p}io-hint">支援 .md, .txt, .markdown 等格式</div>
                <button class="${p}btn ${p}secondary" data-action="import-cancel" type="button">取消</button>
            `;
            Portal.append(this.importPanel);

            // export panel
            this.exportPanel = document.createElement('div');
            this.exportPanel.className = `${p}io-panel`;
            this.exportPanel.id = `${p}export-panel`;
            this.exportPanel.style.display = 'none';
            this.exportPanel.setAttribute('role', 'dialog');
            this.exportPanel.setAttribute('aria-label', '導出內容');
            this.exportPanel.setAttribute('aria-modal', 'true');
            this.exportPanel.innerHTML = `
                <h4>導出 / 複製</h4>
                <div class="${p}io-hint">導出:下載到本機;複製:放入剪貼簿</div>
                <button class="${p}btn" data-action="export-md" type="button">
                    ${Icons.download} <span>下載 Markdown (.md)</span>
                </button>
                <button class="${p}btn" data-action="export-txt" type="button">
                    ${Icons.download} <span>下載純文字 (.txt)</span>
                </button>
                <button class="${p}btn" data-action="export-html" type="button">
                    ${Icons.code} <span>下載 HTML (.html)</span>
                </button>
                <button class="${p}btn" data-action="export-pdf" type="button">
                    ${Icons.file} <span>匯出 PDF(列印)</span>
                </button>
                <div class="${p}io-sep"></div>
                <button class="${p}btn" data-action="copy-md" type="button">
                    ${Icons.copy} <span>複製 Markdown</span>
                </button>
                <button class="${p}btn" data-action="copy-html" type="button">
                    ${Icons.copy} <span>複製 HTML</span>
                </button>
                <button class="${p}btn ${p}secondary" data-action="export-cancel" type="button">取消</button>
            `;
            Portal.append(this.exportPanel);

            // slots panel(快速存檔)
            this.slotsPanel = document.createElement('div');
            this.slotsPanel.className = `${p}io-panel ${p}slots-panel`;
            this.slotsPanel.id = `${p}slots-panel`;
            this.slotsPanel.style.display = 'none';
            this.slotsPanel.setAttribute('role', 'dialog');
            this.slotsPanel.setAttribute('aria-label', '快速存檔插槽');
            this.slotsPanel.setAttribute('aria-modal', 'true');
            Portal.append(this.slotsPanel);

            // settings / backup(10B 會 render)
            this.settingsPanel = document.createElement('div');
            this.settingsPanel.className = `${p}portal-panel ${p}settings-panel`;
            this.settingsPanel.id = `${p}settings-panel`;
            this.settingsPanel.style.display = 'none';
            this.settingsPanel.setAttribute('role', 'dialog');
            this.settingsPanel.setAttribute('aria-label', '偏好設定');
            this.settingsPanel.setAttribute('aria-modal', 'true');
            Portal.append(this.settingsPanel);

            this.backupPanel = document.createElement('div');
            this.backupPanel.className = `${p}portal-panel ${p}backup-panel`;
            this.backupPanel.id = `${p}backup-panel`;
            this.backupPanel.style.display = 'none';
            this.backupPanel.setAttribute('role', 'dialog');
            this.backupPanel.setAttribute('aria-label', '備份管理');
            this.backupPanel.setAttribute('aria-modal', 'true');
            Portal.append(this.backupPanel);

            // tooltip(10B 會完整初始化)
            this.tooltipEl = document.createElement('div');
            this.tooltipEl.className = `${p}tooltip`;
            Portal.append(this.tooltipEl);
        },

        // ----------------------------------------
        // Core events
        // ----------------------------------------

        bindCoreEvents() {
            const p = CONFIG.prefix;

            // overlay click to close
            this.overlay.addEventListener('click', (e) => {
                if (e.target === this.overlay) this.close();
            });

            // toolbar actions (delegation)
            this.toolbar.addEventListener('click', (e) => {
                const btn = e.target.closest('[data-action]');
                if (!btn) return;

                const action = btn.getAttribute('data-action');
                if (!action) return;

                // more menu toggle(10B 會覆寫成互補版)
                if (action === 'more') {
                    e.preventDefault();
                    e.stopPropagation();
                    this.toggleMoreMenu();
                    return;
                }

                this.closeAllPanels();
                this._handleAction(action, btn);
            });

            // import panel actions
            this.importPanel.addEventListener('click', (e) => {
                const btn = e.target.closest('[data-action]');
                if (!btn) return;

                const action = btn.getAttribute('data-action');
                switch (action) {
                    case 'import-selection':
                        this.importText();
                        this.hideImportPanel();
                        break;
                    case 'import-file':
                        this.fileInput.click();
                        this.hideImportPanel();
                        break;
                    case 'import-cancel':
                        this.hideImportPanel();
                        break;
                }
            });

            // export panel actions
            this.exportPanel.addEventListener('click', (e) => {
                const btn = e.target.closest('[data-action]');
                if (!btn) return;

                const action = btn.getAttribute('data-action');
                switch (action) {
                    case 'export-md':
                        this.downloadMD();
                        this.hideExportPanel();
                        break;
                    case 'export-html':
                        this.exportHTML();
                        this.hideExportPanel();
                        break;
                    case 'copy-md':
                        this.copyMD();
                        this.hideExportPanel();
                        break;
                    case 'export-txt':
                        this.exportAsText();
                        this.hideExportPanel();
                        break;
                    case 'export-pdf':
                        this.exportAsPDF();
                        this.hideExportPanel();
                        break;
                    case 'copy-html':
                        this.copyHTML();
                        this.hideExportPanel();
                        break;
                    case 'export-cancel':
                        this.hideExportPanel();
                        break;
                }
            });

            // editor select
            this.modal.querySelector(`#${p}editor-select`).addEventListener('change', async (e) => {
                await this.switchEditor(e.target.value);
            });

            // file open
            this.fileInput.addEventListener('change', (e) => this.handleFileOpen(e));

            // drag modal
            this.toolbar.addEventListener('mousedown', (e) => {
                if (e.target.closest('button, select, input')) return;
                this.onDragStart(e);
            });
            document.addEventListener('mousemove', (e) => this.onDragMove(e));
            document.addEventListener('mouseup', () => this.onDragEnd());

            // dblclick toolbar toggle maximize
            this.toolbar.addEventListener('dblclick', (e) => {
                if (e.target.closest('button, select, input')) return;
                this.toggleMaximize();
            });

            // theme sync
            Theme.onChange(() => {
                this.syncThemeButton();
                EditorManager.setTheme(Theme.get());
            });

            // 提前初始化 Tooltip(不等待 10B)
            this._initTooltipEarly();
        },

        /**
         * 提前初始化 Tooltip(Core 版本,10B 會增強)
         */
        _initTooltipEarly() {
            if (this._tooltipEarlyBound) return;
            this._tooltipEarlyBound = true;

            if (!this.tooltipEl) return;

            const showTooltip = (e) => {
                const target = e.target.closest('[data-tooltip]');
                if (!target) return;

                const text = target.getAttribute('data-tooltip');
                if (!text) return;

                this.tooltipEl.textContent = text;
                this.tooltipEl.style.opacity = '0';
                this.tooltipEl.style.visibility = 'visible';

                requestAnimationFrame(() => {
                    const rect = target.getBoundingClientRect();
                    const ttRect = this.tooltipEl.getBoundingClientRect();
                    const isBottomHalf = rect.top > window.innerHeight / 2;

                    let top = isBottomHalf ? rect.top - ttRect.height - 8 : rect.bottom + 8;
                    let left = rect.left + rect.width / 2 - ttRect.width / 2;

                    top = Math.max(5, Math.min(top, window.innerHeight - ttRect.height - 5));
                    left = Math.max(5, Math.min(left, window.innerWidth - ttRect.width - 5));

                    this.tooltipEl.style.top = `${top}px`;
                    this.tooltipEl.style.left = `${left}px`;
                    this.tooltipEl.style.opacity = '1';
                });
            };

            const hideTooltip = () => {
                if (this.tooltipEl) {
                    this.tooltipEl.style.opacity = '0';
                }
            };

            document.addEventListener('mouseover', showTooltip);
            document.addEventListener('mouseout', (e) => {
                if (e.target.closest('[data-tooltip]')) hideTooltip();
            });
        },

        /**
         * action handler(Core 版本已移至 10B)
         * 此處保留空殼以防 10B patch 失敗時的 fallback
         */
        _handleAction(action, anchorEl) {
            // 10B 會覆蓋此方法,這裡只做基本 fallback
            log('_handleAction called before 10B patch:', action);
            switch (action) {
                case 'close':
                    this.close();
                    break;
                default:
                    Toast.warning('模組載入中,請稍候重試');
            }
        },

        // ----------------------------------------
        // Panels (core)
        // ----------------------------------------

        closeAllPanels() {
            const p = CONFIG.prefix;

            if (this.morePanel) this.morePanel.classList.remove(`${p}open`);
            if (this.importPanel) this.importPanel.style.display = 'none';
            if (this.exportPanel) this.exportPanel.style.display = 'none';
            if (this.settingsPanel) this.settingsPanel.style.display = 'none';
            if (this.backupPanel) this.backupPanel.style.display = 'none';
            if (this.slotsPanel) this.slotsPanel.style.display = 'none';

            this._hasOpenMenu = false;
            this.toolbar?.classList.remove(`${p}has-open-menu`);
        },

        _markMenuOpen() {
            const p = CONFIG.prefix;
            this._hasOpenMenu = true;
            this.toolbar?.classList.add(`${p}has-open-menu`);
        },

        toggleMoreMenu() {
            // 10B 會替換成互補邏輯(此處保底顯示一個簡單提示)
            const p = CONFIG.prefix;
            const isOpen = this.morePanel.classList.contains(`${p}open`);

            if (isOpen) {
                this.morePanel.classList.remove(`${p}open`);
                this._hasOpenMenu = false;
                this.toolbar?.classList.remove(`${p}has-open-menu`);
                return;
            }

            this.closeAllPanels();
            this.morePanel.innerHTML = `
                <div class="${p}menu-header">更多</div>
                <div class="${p}menu-item" style="cursor:default;opacity:0.8;font-size:12px;">
                    下一子片段將完成互補選單、偏好設定與備份管理。
                </div>
            `;
            this.morePanel.classList.add(`${p}open`);
            Portal.positionAt(this.morePanel, this.moreBtn, { placement: 'bottom-end' });
            this._markMenuOpen();
        },

        showImportPanel(anchor) {
            this.closeAllPanels();
            this.importPanel.style.display = 'block';
            Portal.positionAt(this.importPanel, anchor || this.moreBtn, { placement: 'bottom-start' });
            this._markMenuOpen();
        },

        hideImportPanel() {
            this.importPanel.style.display = 'none';
            this._hasOpenMenu = false;
            this.toolbar?.classList.remove(`${CONFIG.prefix}has-open-menu`);
        },

        showExportPanel(anchor) {
            this.closeAllPanels();
            this.exportPanel.style.display = 'block';
            Portal.positionAt(this.exportPanel, anchor || this.moreBtn, { placement: 'bottom-start' });
            this._markMenuOpen();
        },

        hideExportPanel() {
            this.exportPanel.style.display = 'none';
            this._hasOpenMenu = false;
            this.toolbar?.classList.remove(`${CONFIG.prefix}has-open-menu`);
        },

        // ----------------------------------------
        // Drag / resize
        // ----------------------------------------

        restorePosition() {
            const savedPos = Utils.storage.get(CONFIG.storageKeys.modalPos);
            if (savedPos?.left && savedPos?.top) {
                this.modal.style.left = savedPos.left;
                this.modal.style.top = savedPos.top;
            }
        },

        onDragStart(e) {
            if (this.isFullscreen) return;

            const rect = this.modal.getBoundingClientRect();
            this.dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
            this.isDragging = true;
            this.modal.classList.add(`${CONFIG.prefix}dragging`);
        },

        onDragMove(e) {
            if (!this.isDragging) return;

            const x = Utils.clamp(
                e.clientX - this.dragOffset.x,
                0,
                window.innerWidth - this.modal.offsetWidth
            );
            const y = Utils.clamp(
                e.clientY - this.dragOffset.y,
                0,
                window.innerHeight - this.modal.offsetHeight
            );

            this.modal.style.left = `${x}px`;
            this.modal.style.top = `${y}px`;
        },

        onDragEnd() {
            if (!this.isDragging) return;

            this.isDragging = false;
            this.modal.classList.remove(`${CONFIG.prefix}dragging`);

            Utils.storage.set(CONFIG.storageKeys.modalPos, {
                left: this.modal.style.left,
                top: this.modal.style.top
            });
        },

        _initModalResize() {
            ResizeManager.enable(this.modal, {
                minWidth: 380,
                minHeight: 350,
                maxWidth: window.innerWidth - 20,
                maxHeight: window.innerHeight - 20,
                edges: ['right', 'bottom', 'corner'],
                onResize: () => EditorManager.refresh(true),
                onResizeEnd: ({ width, height }) => {
                    Utils.storage.set(CONFIG.storageKeys.modalSize, {
                        width: `${Math.round(width)}px`,
                        height: `${Math.round(height)}px`
                    });
                }
            });
        },

        initResizeObserver() {
            if (typeof ResizeObserver === 'undefined') return;

            // 儲存尺寸:使用較長 debounce(減少儲存頻率)
            const saveSize = Utils.debounce(() => {
                if (!this.isOpen || this.isFullscreen) return;

                const rect = this.modal.getBoundingClientRect();
                Utils.storage.set(CONFIG.storageKeys.modalSize, {
                    width: `${Math.round(rect.width)}px`,
                    height: `${Math.round(rect.height)}px`
                });
            }, 500);

            // 刷新編輯器:使用較短 debounce(更及時響應)
            const refreshEditor = Utils.debounce(() => {
                if (!this.isOpen) return;
                EditorManager.refresh(true);
            }, 100);

            const ro = new ResizeObserver(() => {
                saveSize();
                refreshEditor();
            });

            ro.observe(this.modal);
            this._ro = ro;
        },

        // ----------------------------------------
        // Editor ops
        // ----------------------------------------

        async switchEditor(editorKey) {
            const p = CONFIG.prefix;

            this.loadingEl.classList.remove(`${p}hidden`);
            this.loadingText.textContent = '正在載入編輯器...';

            const content = EditorManager.getValue() || Utils.storage.get(CONFIG.storageKeys.content, '');

            try {
                await EditorManager.switchEditor(
                    editorKey,
                    this.editorContainer,
                    content,
                    Theme.get(),
                    (msg) => { this.loadingText.textContent = msg; }
                );

                this.loadingEl.classList.add(`${p}hidden`);
                Toast.success(`已切換到 ${CONFIG.editors[editorKey].name}`);

                setTimeout(() => {
                    EditorManager.focus();
                    EditorManager.refresh(true);

                    // 清除字數統計快取,確保重新計算
                    this._lastContentHash = null;
                    this._lastWordCountStats = null;

                    this.updateWordCount();
                    this.updateSaveTimeLabel();

                    ToolbarPrefs.applyToModal(this);
                    this._applyStatusMetricVisibility();
                    this.syncThemeButton();
                    this.syncMaximizeButton();
                }, 120);

            } catch (err) {
                logError('Switch editor failed:', err);
                this.loadingText.innerHTML = `<div class="${p}loading-error">載入失敗:${Utils.escapeHtml(err.message)}</div>`;
                Toast.error(`載入失敗:${err.message}`, 6000);
            }
        },

        async toggle() {
            if (this.isOpen) this.close();
            else await this.open();
        },

        async open() {
            if (this.isOpen || this._opening) return;
            this._opening = true;

            const p = CONFIG.prefix;
            try { FAB?.setLoading?.(true); } catch (e) {}

            // background scroll lock(確定修復:避免背景滾動)
            this._lockBackgroundScroll();

            this.isOpen = true;
            this.overlay.classList.add(`${p}active`);

            const savedEditor = Utils.storage.get(CONFIG.storageKeys.editor, CONFIG.defaultEditor);
            const select = this.modal.querySelector(`#${p}editor-select`);
            if (select) select.value = savedEditor;

            if (!EditorManager.isReady()) {
                await this.switchEditor(savedEditor);
            } else {
                this.loadingEl.classList.add(`${p}hidden`);
                setTimeout(() => {
                    EditorManager.focus();
                    EditorManager.refresh(true);
                    this.updateWordCount();
                }, 80);
            }

            this.startAutoSave();
            this.startWordCountUpdate();
            BackupManager.startAuto();

            this.updateSaveTimeLabel();
            ToolbarPrefs.applyToModal(this);
            this._applyStatusMetricVisibility();
            this.syncThemeButton();
            this.syncMaximizeButton();
            this._bindMiniSlotsBarEventsOnce();
            this.updateMiniSlotsBar();

            try { FAB?.setLoading?.(false); } catch (e) {}

            // 顯示拖曳導入提示(首次使用時)
            try {
                DragDropManager.showHintIfNeeded();
            } catch (e) {
                // DragDropManager 可能尚未初始化
            }

            this._opening = false;
        },

        close() {
            if (!this.isOpen) return;

            const p = CONFIG.prefix;

            this.isOpen = false;
            this.overlay.classList.remove(`${p}active`);
            this.closeAllPanels();

            // 關閉 FindReplace 面板
            try {
                FindReplace.hide();
            } catch (e) {
                // FindReplace 可能尚未初始化,忽略錯誤
            }

            this.stopAutoSave();
            this.stopWordCountUpdate();
            BackupManager.stopAuto();

            this.saveDraft(true);

            // background scroll unlock
            this._unlockBackgroundScroll();

            // 清除效能優化快取
            this._lastContentHash = null;
            this._lastWordCountStats = null;
        },

        toggleMaximize() {
            const p = CONFIG.prefix;

            this.isFullscreen = !this.isFullscreen;
            this.modal.classList.toggle(`${p}fullscreen`, this.isFullscreen);

            if (this.isFullscreen) {
                this.modal.style.left = '';
                this.modal.style.top = '';
            } else {
                this.restorePosition();
            }

            this.syncMaximizeButton();

            setTimeout(() => {
                window.dispatchEvent(new Event('resize'));
                EditorManager.refresh(true);
            }, 80);
        },

        // EasyMDE safe fullscreen will call this
        toggleFullscreen() {
            this.toggleMaximize();
        },

        // ----------------------------------------
        // background scroll lock helpers
        // ----------------------------------------

        _lockBackgroundScroll() {
            try {
                const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;

                this._savedBodyOverflow = document.body.style.overflow || '';
                this._savedBodyPaddingRight = document.body.style.paddingRight || '';
                this._savedHtmlOverflow = document.documentElement.style.overflow || '';

                document.documentElement.style.overflow = 'hidden';
                document.body.style.overflow = 'hidden';
                if (scrollbarWidth > 0) {
                    document.body.style.paddingRight = `${scrollbarWidth}px`;
                }
            } catch (e) {
                log('lockBackgroundScroll error:', e?.message || e);
            }
        },

        _unlockBackgroundScroll() {
            try {
                document.body.style.overflow = this._savedBodyOverflow || '';
                document.body.style.paddingRight = this._savedBodyPaddingRight || '';
                document.documentElement.style.overflow = this._savedHtmlOverflow || '';
            } catch (e) {
                log('unlockBackgroundScroll error:', e?.message || e);
            }
        },

        // ----------------------------------------
        // import/export/actions
        // ----------------------------------------

        importText() {
            // 優先使用 FAB 暫存的選取,其次才即時取得
            // 理由:點擊 FAB 選單時,頁面選取已被清除
            let text = '';

            try {
                text = FAB.getPendingSelection();
            } catch (e) {
                // FAB 可能尚未初始化
            }

            // 如果暫存為空,嘗試即時取得(可能從 Modal 內部操作)
            if (!text) {
                text = Utils.getSelectedText();
            }

            if (!text || !text.trim()) {
                return Toast.warning('請先在頁面上選取文字');
            }

            if (!EditorManager.isReady()) {
                return Toast.warning('編輯器尚未就緒');
            }

            EditorManager.insertValue(text);
            Toast.success(`已導入選中文字(${text.length} 字元)`);
            this.updateWordCount();
        },

        async handleFileOpen(e) {
            const file = e.target.files?.[0];
            if (!file) return;

            const result = await SafeExecute.run(async () => {
                const content = await Utils.readFile(file);

                // 驗證內容
                if (typeof content !== 'string') {
                    throw new Error('檔案內容格式無效');
                }

                // 設定編輯器內容
                EditorManager.setValue(content);

                // 建立備份
                BackupManager.create(content, {
                    editorKey: EditorManager.currentEditor,
                    manual: true
                });

                return { success: true, filename: file.name, size: content.length };
            }, { success: false }, 'file-open');

            if (result.success) {
                Toast.success(`已開啟:${result.filename}(${result.size} 字元)`);
                this.updateWordCount();
            } else {
                Toast.error('檔案讀取失敗,請確認檔案格式正確', 5000);
            }

            // 清空 input,允許重複選擇相同檔案
            e.target.value = '';
        },

        clearContent() {
            if (!EditorManager.isReady()) return Toast.warning('編輯器尚未就緒');

            if (!confirm('確定要清空所有內容嗎?此操作無法復原。')) return;

            const content = EditorManager.getValue();
            if (content && content.trim()) {
                BackupManager.create(content, { editorKey: EditorManager.currentEditor, manual: true });
            }

            EditorManager.setValue('');
            Utils.storage.set(CONFIG.storageKeys.content, '');

            const info = EditorManager.getCurrentInfo();
            if (info?.key) Utils.clearEditorCache(info.key);

            Toast.info('內容已清空');
            this.updateWordCount();
        },

        exportHTML() {
            if (!EditorManager.isReady()) return Toast.warning('編輯器尚未就緒');

            const html = EditorManager.getHTML();
            const fullHtml = `<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markdown Export</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.7; }
pre { background: #f5f5f5; padding: 16px; overflow-x: auto; border-radius: 6px; }
code { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; }
blockquote { border-left: 4px solid #ddd; margin: 0; padding-left: 16px; color: #666; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f5f5f5; }
</style>
</head>
<body>
${html}
</body>
</html>`;

            const filename = `markdown_${Utils.formatDate()}.html`;
            const ok = Utils.downloadFile(fullHtml, filename, 'text/html;charset=utf-8');
            Toast[ok ? 'success' : 'error'](ok ? 'HTML 導出成功' : '導出失敗');
        },

        /**
         * 匯出為純文字
         */
        exportAsText() {
            if (!EditorManager.isReady()) {
                return Toast.warning('編輯器尚未就緒');
            }

            const content = EditorManager.getValue();
            const filename = `document_${Utils.formatDate()}.txt`;
            const ok = Utils.downloadFile(content, filename, 'text/plain;charset=utf-8');

            Toast[ok ? 'success' : 'error'](ok ? '純文字導出成功' : '導出失敗');
        },

        /**
         * 匯出為 PDF(透過瀏覽器列印)
         */
        exportAsPDF() {
            if (!EditorManager.isReady()) {
                return Toast.warning('編輯器尚未就緒');
            }

            const html = EditorManager.getHTML();
            const title = document.title || 'Markdown Export';

            // 建立列印視窗
            const printWindow = window.open('', '_blank', 'width=800,height=600');

            if (!printWindow) {
                Toast.warning('無法開啟列印視窗\n請檢查是否被瀏覽器封鎖');
                return;
            }

            const printContent = `
        <!DOCTYPE html>
        <html lang="zh-TW">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>${Utils.escapeHtml(title)}</title>
            <style>
                * { box-sizing: border-box; }
                body {
                    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                    max-width: 800px;
                    margin: 0 auto;
                    padding: 40px 20px;
                    line-height: 1.7;
                    color: #333;
                }
                h1, h2, h3, h4, h5, h6 {
                    margin-top: 1.5em;
                    margin-bottom: 0.5em;
                    font-weight: 600;
                    line-height: 1.3;
                }
                h1 { font-size: 2em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
                h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
                h3 { font-size: 1.25em; }
                p { margin: 1em 0; }
                pre {
                    background: #f5f5f5;
                    padding: 16px;
                    overflow-x: auto;
                    border-radius: 4px;
                    font-family: Consolas, Monaco, 'Andale Mono', monospace;
                    font-size: 14px;
                }
                code {
                    background: #f5f5f5;
                    padding: 2px 6px;
                    border-radius: 3px;
                    font-family: Consolas, Monaco, 'Andale Mono', monospace;
                    font-size: 0.9em;
                }
                pre code {
                    background: none;
                    padding: 0;
                }
                blockquote {
                    border-left: 4px solid #ddd;
                    margin: 1em 0;
                    padding: 0.5em 1em;
                    color: #666;
                    background: #f9f9f9;
                }
                table {
                    border-collapse: collapse;
                    width: 100%;
                    margin: 1em 0;
                }
                th, td {
                    border: 1px solid #ddd;
                    padding: 8px 12px;
                    text-align: left;
                }
                th { background: #f5f5f5; }
                img { max-width: 100%; height: auto; }
                a { color: #0366d6; }
                hr { border: none; border-top: 1px solid #eee; margin: 2em 0; }
                ul, ol { padding-left: 2em; }
                li { margin: 0.3em 0; }

                @media print {
                    body { padding: 0; max-width: none; }
                    pre { white-space: pre-wrap; word-wrap: break-word; }
                }
            </style>
        </head>
        <body>
            ${html}
            <script>
                // 自動觸發列印
                window.onload = function() {
                    setTimeout(function() {
                        window.print();
                    }, 500);
                };
            </script>
        </body>
        </html>`;

            printWindow.document.write(printContent);
            printWindow.document.close();

            Toast.info('已開啟列印視窗\n請選擇「儲存為 PDF」');
        },

        downloadMD() {
            if (!EditorManager.isReady()) {
                return Toast.warning('編輯器尚未就緒');
            }

            const result = SafeExecute.wrap(() => {
                const content = EditorManager.getValue();
                if (!content) {
                    Toast.info('目前無內容可下載');
                    return false;
                }

                const filename = `markdown_${Utils.formatDate()}.md`;
                return Utils.downloadFile(content, filename, 'text/markdown;charset=utf-8');
            }, false, 'download-md')();

            if (result) {
                Toast.success('下載成功');
            } else if (result === false) {
                Toast.error('下載失敗,請重試');
            }
        },

        async copyMD() {
            if (!EditorManager.isReady()) {
                return Toast.warning('編輯器尚未就緒');
            }

            const ok = await SafeExecute.run(async () => {
                const content = EditorManager.getValue();
                if (!content) {
                    Toast.info('目前無內容可複製');
                    return false;
                }
                return await Utils.copyToClipboard(content);
            }, false, 'copy-md');

            if (ok) {
                Toast.success('Markdown 已複製到剪貼簿');
            } else if (ok === false) {
                Toast.error('複製失敗,請手動選取複製');
            }
        },

        async copyHTML() {
            if (!EditorManager.isReady()) {
                return Toast.warning('編輯器尚未就緒');
            }

            const ok = await SafeExecute.run(async () => {
                const html = EditorManager.getHTML();
                if (!html) {
                    Toast.info('目前無內容可複製');
                    return false;
                }
                return await Utils.copyToClipboard(html);
            }, false, 'copy-html');

            if (ok) {
                Toast.success('HTML 已複製到剪貼簿');
            } else if (ok === false) {
                Toast.error('複製失敗,請手動選取複製');
            }
        },

        // ----------------------------------------
        // draft/save/metrics
        // ----------------------------------------

        saveDraft(silent = false) {
            if (!EditorManager.isReady()) {
                if (!silent) Toast.warning('編輯器尚未就緒');
                return false;
            }

            try {
                const content = EditorManager.getValue();

                // 檢查內容大小
                let sizeWarning = false;
                try {
                    const bytes = new Blob([content]).size;
                    if (bytes > CONFIG.maxDraftBytes) {
                        const sizeMB = (bytes / 1024 / 1024).toFixed(2);
                        if (!silent) {
                            Toast.warning(`草稿較大(${sizeMB} MB),可能無法完整保存\n建議使用「匯出」功能備份`);
                        }
                        sizeWarning = true;
                    }
                } catch (e) {
                    // 無法計算大小,繼續嘗試保存
                }

                // 嘗試保存
                const ok = Utils.storage.set(CONFIG.storageKeys.content, content);

                if (!ok) {
                    // 儲存失敗,可能是空間不足
                    if (!silent) {
                        Toast.error('保存失敗:儲存空間可能不足\n請清理瀏覽器資料或使用「匯出」功能');
                    }
                    return false;
                }

                const now = Date.now();
                Utils.storage.set(CONFIG.storageKeys.lastSaveTime, now);
                this.updateSaveTimeLabel();

                if (!silent && !sizeWarning) {
                    Toast.success('草稿已保存');
                }

                return true;

            } catch (e) {
                logError('saveDraft error:', e);
                // 記錄更多上下文以便除錯
                if (DEBUG) {
                    console.error('[MME] saveDraft context:', {
                        isOpen: this.isOpen,
                        editorReady: EditorManager.isReady(),
                        currentEditor: EditorManager.currentEditor,
                        timestamp: new Date().toISOString()
                    });
                }
                if (!silent) {
                    Toast.error('保存時發生錯誤:' + (e.message || '未知錯誤'));
                }
                return false;
            }
        },

        updateSaveTimeLabel() {
            const ts = Utils.storage.get(CONFIG.storageKeys.lastSaveTime, null);
            const label = (!ts) ? '未保存' : `保存 ${Utils.formatTime(new Date(ts))}`;

            if (this.saveTimeEl) this.saveTimeEl.textContent = label;
            if (this.saveTimeToolbarEl) this.saveTimeToolbarEl.textContent = label;

            // 每次更新保存時間都重算 metrics 可見性(以便 sep 顯示正確)
            this._applyStatusMetricVisibility();
        },

        /**
         * 更新字數統計
         *
         * 效能優化:
         * 1. 使用 hash 比對,內容無變化時跳過計算
         * 2. 使用快取的 DOM 元素引用
         *
         * 此方法每 3 秒執行一次,優化對長時間使用體驗很重要
         */
        updateWordCount() {
            try {
                const text = EditorManager.getValue() || '';

                // 計算內容 hash
                const hash = Utils.hash32(text);

                // 如果內容未變化,跳過計算和 DOM 更新
                if (hash === this._lastContentHash && this._lastWordCountStats) {
                    // 但仍需套用可見性設定(使用者可能在設定中更改了顯示項目)
                    // 這個操作現在已經優化過了,開銷很小
                    this._applyStatusMetricVisibility();
                    return;
                }

                // 內容有變化,重新計算
                const stats = Utils.countText(text);

                // 更新快取
                this._lastContentHash = hash;
                this._lastWordCountStats = stats;

                // 更新字數(使用快取的元素引用)
                if (this.wcEl) this.wcEl.textContent = stats.charsNoSpace;
                if (this.lcEl) this.lcEl.textContent = stats.lines;

                if (this.wcToolbarEl) this.wcToolbarEl.textContent = stats.charsNoSpace;
                if (this.lcToolbarEl) this.lcToolbarEl.textContent = stats.lines;

                // 更新閱讀時間(使用快取的元素引用)
                const readingTimeText = `約 ${stats.readingTime} 分鐘`;
                if (this.rtEl) this.rtEl.textContent = readingTimeText;
                if (this.rtToolbarEl) this.rtToolbarEl.textContent = readingTimeText;

                this._applyStatusMetricVisibility();

            } catch (e) {
                // 錯誤時清除快取並顯示佔位符
                this._lastContentHash = null;
                this._lastWordCountStats = null;

                if (this.wcEl) this.wcEl.textContent = '--';
                if (this.lcEl) this.lcEl.textContent = '--';
                if (this.wcToolbarEl) this.wcToolbarEl.textContent = '--';
                if (this.lcToolbarEl) this.lcToolbarEl.textContent = '--';
                if (this.rtEl) this.rtEl.textContent = '--';
                if (this.rtToolbarEl) this.rtToolbarEl.textContent = '--';
            }
        },

        /**
         * 套用狀態列 metric 的可見性設定
         *
         * 設計意圖:
         * - 根據使用者偏好設定控制狀態列各項目的顯示/隱藏
         * - 使用快取的元素引用,避免每次都進行 DOM 查詢
         * - 此方法會被頻繁呼叫(updateWordCount、updateSaveTimeLabel 等)
         *
         * 防禦性設計:
         * - 對 _statusMetrics 及其子屬性進行完整的 null 檢查
         * - 在 DEBUG 模式下記錄警告,便於問題追蹤
         */
        _applyStatusMetricVisibility() {
            // 防禦性檢查:確保 _statusMetrics 已初始化
            if (!this._statusMetrics) {
                if (DEBUG) {
                    log('_applyStatusMetricVisibility: _statusMetrics not initialized, skipping');
                }
                return;
            }

            const prefs = ToolbarPrefs.load();
            const sb = prefs.statusBar || {};

            // 計算各項目的可見性(只計算一次,避免重複運算)
            const showWc = !!sb.showWordCount;
            const showLc = !!sb.showLineCount;
            const showRt = sb.showReadingTime !== false; // 預設顯示
            const showSave = !!sb.showSaveTime;

            // 計算分隔符號的可見性(分隔符只在前後項目都顯示時才顯示)
            const showSepWc = showWc && (showLc || showRt || showSave);
            const showSepLc = showLc && (showRt || showSave);
            const showSepRt = showRt && showSave;

            /**
             * 安全地套用可見性到指定的 metric 集合
             * @param {Object|null} metrics - 快取的 metric 元素集合
             * @param {string} location - 位置標識(用於除錯日誌)
             */
            const applyToMetrics = (metrics, location) => {
                if (!metrics) {
                    if (DEBUG) {
                        log(`_applyStatusMetricVisibility: ${location} metrics is null`);
                    }
                    return;
                }

                // 使用安全的樣式設定函數
                const setDisplay = (el, show) => {
                    if (el && el.style) {
                        el.style.display = show ? '' : 'none';
                    }
                };

                // 套用各項目的可見性
                setDisplay(metrics.wc, showWc);
                setDisplay(metrics.lc, showLc);
                setDisplay(metrics.rt, showRt);
                setDisplay(metrics.save, showSave);

                // 套用分隔符的可見性
                setDisplay(metrics.sepWc, showSepWc);
                setDisplay(metrics.sepLc, showSepLc);
                setDisplay(metrics.sepRt, showSepRt);
            };

            // 套用到底部狀態列和工具列狀態區
            applyToMetrics(this._statusMetrics.bottom, 'bottom');
            applyToMetrics(this._statusMetrics.toolbar, 'toolbar');
        },

        startAutoSave() {
            this.stopAutoSave();
            this.autoSaveTimer = setInterval(() => {
                if (this.isOpen && EditorManager.isReady()) this.saveDraft(true);
            }, CONFIG.autoSaveInterval);
        },

        stopAutoSave() {
            if (this.autoSaveTimer) {
                clearInterval(this.autoSaveTimer);
                this.autoSaveTimer = null;
            }
        },

        startWordCountUpdate() {
            this.stopWordCountUpdate();

            // 使用較長的間隔減少 CPU 使用
            this.wordCountTimer = setInterval(() => {
                if (this.isOpen && document.visibilityState === 'visible') {
                    this.updateWordCount();
                }
            }, CONFIG.timing.wordCountUpdateInterval);

            // 同時監聯頁面可見性變化
            this._visibilityHandler = () => {
                if (document.visibilityState === 'visible' && this.isOpen) {
                    this.updateWordCount();
                }
            };
            document.addEventListener('visibilitychange', this._visibilityHandler);
        },

        stopWordCountUpdate() {
            if (this.wordCountTimer) {
                clearInterval(this.wordCountTimer);
                this.wordCountTimer = null;
            }

            if (this._visibilityHandler) {
                document.removeEventListener('visibilitychange', this._visibilityHandler);
                this._visibilityHandler = null;
            }
        },

        // ----------------------------------------
        // Dynamic buttons (尊重 buttonAppearance)
        // ----------------------------------------

        syncThemeButton() {
            const btn = this.modal?.querySelector(`[data-action="theme"]`);
            if (!btn) return;

            const prefs = ToolbarPrefs.load();
            const def = ToolbarPrefs.allButtons.theme;
            const label = def?.label || '主題';

            const icon = Theme.isDark() ? Icons.moon : Icons.sun;
            const tooltip = Theme.isDark() ? '切換淺色' : '切換深色';
            btn.setAttribute('data-tooltip', tooltip);

            switch (prefs.buttonAppearance) {
                case 'icon-only':
                    btn.innerHTML = icon;
                    break;
                case 'text-only':
                    btn.innerHTML = `<span>${Utils.escapeHtml(label)}</span>`;
                    break;
                case 'icon-text':
                default:
                    btn.innerHTML = `${icon}<span>${Utils.escapeHtml(label)}</span>`;
                    break;
            }
        },

        syncMaximizeButton() {
            const btn = this.modal?.querySelector(`[data-action="maximize"]`);
            if (!btn) return;

            const prefs = ToolbarPrefs.load();
            const def = ToolbarPrefs.allButtons.maximize;
            const label = def?.label || '放大';

            const icon = this.isFullscreen ? Icons.collapse : Icons.expand;
            const tooltip = this.isFullscreen ? '還原' : '放大';
            btn.setAttribute('data-tooltip', tooltip);

            switch (prefs.buttonAppearance) {
                case 'icon-only':
                    btn.innerHTML = icon;
                    break;
                case 'text-only':
                    btn.innerHTML = `<span>${Utils.escapeHtml(label)}</span>`;
                    break;
                case 'icon-text':
                default:
                    btn.innerHTML = `${icon}<span>${Utils.escapeHtml(label)}</span>`;
                    break;
            }
        }
    };

    // ========================================
    // [SEGMENT_10B]
    // Modal 主視窗(Advanced: menus/panels/focus/tooltip/shortcuts/vditor-safe)
    // ========================================

    (function patchModal10B() {
        const p = CONFIG.prefix;

        Object.assign(Modal, {
            _seg10BBound: false,
            _tooltipBound: false,
            _outsideClickBound: false,
            _keyBound: false,
            _focusModeBound: false,

            // ============
            // init wrapper
            // ============

            _initAdvancedOnce() {
                if (this._seg10BBound) return;
                this._seg10BBound = true;

                // 1) more menu click delegation
                if (this.morePanel) {
                    this.morePanel.addEventListener('click', (e) => {
                        const item = e.target.closest('[data-action]');
                        if (!item) return;
                        const action = item.getAttribute('data-action');
                        if (!action) return;

                        // 互補選單:點擊後關閉所有面板再執行
                        this.closeAllPanels();
                        this._handleAction(action, this.moreBtn);
                    });
                }

                // 2) click outside closes panels
                this._bindOutsideClickToClosePanels();

                // 3) tooltip
                this._initTooltip();

                // 4) shortcuts
                this._bindKeyboardShortcuts();

                // 5) focus mode control
                this._initFocusModeControl();

                // 6) 初次生成更多選單內容(不強制打開)
                this._updateMoreMenuContent();
            },

            // 重新包裝 init:保留 10A init,並補上 advanced 綁定
            _wrapInit10B() {
                if (this.__initWrapped10B) return;
                this.__initWrapped10B = true;

                const init10A = this.init;
                this.init = function init_10B() {
                    init10A.call(this);
                    this._initAdvancedOnce();
                };
            },

            // ============
            // action handler(補齊失效按鈕)
            // ============

            _handleAction(action, anchorEl) {
                switch (action) {
                    // basic
                    case 'import':
                        this.showImportPanel(anchorEl);
                        break;
                    case 'export':
                        this.showExportPanel(anchorEl);
                        break;
                    case 'save':
                        this.saveDraft(false);
                        break;
                    case 'clear':
                        this.clearContent();
                        break;

                    // features (修復:原先工具列按鈕失效)
                    case 'backup':
                        this.showBackupPanel();
                        break;
                    case 'focusMode':
                        this.toggleFocusMode();
                        break;
                    case 'settings':
                        this.showSettingsPanel();
                        break;
                    case 'slots':
                        this.showSlotsPanel(anchorEl);
                        break;
                    case 'shortcuts':
                        this.showShortcutsPanel();
                        break;

                    // window
                    case 'theme':
                        Theme.toggle();
                        this.syncThemeButton();
                        EditorManager.setTheme(Theme.get());
                        break;
                    case 'maximize':
                        this.toggleMaximize();
                        break;
                    case 'close':
                        this.close();
                        break;

                    // vditor-only (修復:工具列按鈕失效)
                    case 'vditorModeSv':
                        this.handleVditorSafeSwitch('sv');
                        break;
                    case 'vditorModeIr':
                        this.handleVditorSafeSwitch('ir');
                        break;
                    case 'vditorModeWysiwyg':
                        this.handleVditorSafeSwitch('wysiwyg');
                        break;
                    case 'vditorRestore':
                        this.vditorRestoreSnapshot();
                        break;
                    case 'vditorDownload':
                        this.vditorDownloadSnapshot();
                        break;
                    case 'vditorDiag':
                        VditorDiag.printReport();
                        Toast.info('診斷報告已輸出到控制台 (F12)');
                        break;

                    default:
                        log('Unknown action:', action);
                }
            },

            // ============
            // More menu(互補原則)
            // ============

            toggleMoreMenu() {
                const openClass = `${p}open`;
                const isOpen = this.morePanel.classList.contains(openClass);

                if (isOpen) {
                    this.closeAllPanels();
                    return;
                }

                this.closeAllPanels();
                this._updateMoreMenuContent();
                this.morePanel.classList.add(openClass);

                Portal.positionAt(this.morePanel, this.moreBtn, {
                    placement: this._isFocusMode() ? 'top-end' : 'bottom-end'
                });
                this._markMenuOpen();
            },

            _updateMoreMenuContent() {
                if (!this.morePanel) return;

                const prefs = ToolbarPrefs.load();
                const info = EditorManager.getCurrentInfo();
                const isVditor = info?.key === 'vditor';

                const wc = this.wcEl?.textContent || '0';
                const lc = this.lcEl?.textContent || '0';
                const saveTime = this.saveTimeEl?.textContent || '未保存';

                const backupStats = BackupManager.getStats();

                const addHeader = (title) => `<div class="${p}menu-header">${Utils.escapeHtml(title)}</div>`;
                const addSep = () => `<div class="${p}menu-sep"></div>`;
                const addItem = (key) => {
                    const def = ToolbarPrefs.allButtons[key];
                    if (!def) return '';
                    const icon = Icons[def.icon] || '';
                    const label = def.label || key;
                    return `
                        <button class="${p}menu-item" data-action="${key}" type="button">
                            ${icon} <span>${Utils.escapeHtml(label)}</span>
                        </button>
                    `;
                };

                // 狀態區(永遠顯示,資訊型,不算互補功能重複)
                let html = `
                    ${addHeader('狀態')}
                    <div class="${p}menu-item" style="cursor:default;opacity:0.85;font-size:12px;">
                        ${Icons.info} <span>${Utils.escapeHtml(`${wc} 字 · ${lc} 行 · ${saveTime}`)}</span>
                    </div>
                    ${addSep()}
                `;

                // 收集互補項:show=false 的功能才出現於更多選單
                // 並排除 alwaysShow(more/close)
                const hiddenKeys = [];
                for (const [key, def] of Object.entries(ToolbarPrefs.allButtons)) {
                    if (def.alwaysShow) continue;
                    if (def.vditorOnly && !isVditor) continue;

                    if (prefs.show?.[key] === false) hiddenKeys.push(key);
                }

                // 依 group 分組(資料驅動,不寫死 keys 清單,避免漏項)
                const groupOrder = ['basic', 'features', 'vditor', 'window'];
                const groupTitle = {
                    basic: '基本操作',
                    features: '功能',
                    vditor: 'Vditor',
                    window: '視窗'
                };

                for (const g of groupOrder) {
                    const keys = hiddenKeys.filter(k => ToolbarPrefs.allButtons[k]?.group === g);
                    if (!keys.length) continue;

                    html += addHeader(groupTitle[g] || g);
                    for (const k of keys) html += addItem(k);
                    html += addSep();
                }

                // 插槽摘要(資訊型)
                const slotSettings = QuickSlots.getSettings();
                if (slotSettings.enabledCount > 0) {
                    const slotStats = QuickSlots.getStats();
                    html += `
                        ${addHeader('快速存檔')}
                        <div class="${p}menu-item" style="cursor:default;opacity:0.85;font-size:12px;">
                            ${Icons.database} <span>${slotStats.used} / ${slotStats.total} 插槽已使用</span>
                        </div>
                    `;
                }

                // 備份摘要(資訊型)
                html += `
                    ${addHeader('備份摘要')}
                    <div class="${p}menu-item" style="cursor:default;opacity:0.85;font-size:12px;">
                        ${Icons.database} <span>${backupStats.total} 筆備份 · ${backupStats.pinned} 筆釘選</span>
                    </div>
                `;

                // 快捷鍵入口(始終顯示)
                html += `
                    ${addSep()}
                    <button class="${p}menu-item" data-action="shortcuts">
                        ${Icons.info} <span>快捷鍵一覽</span>
                    </button>
                `;

                this.morePanel.innerHTML = html;
            },

            // ============
            // Panels:Import/Export(focus-mode placement 改善)
            // ============

            showImportPanel(anchor) {
                this.closeAllPanels();
                this.importPanel.style.display = 'block';
                Portal.positionAt(this.importPanel, anchor || this.moreBtn, {
                    placement: this._isFocusMode() ? 'top-start' : 'bottom-start'
                });
                this._markMenuOpen();
            },

            showExportPanel(anchor) {
                this.closeAllPanels();
                this.exportPanel.style.display = 'block';
                Portal.positionAt(this.exportPanel, anchor || this.moreBtn, {
                    placement: this._isFocusMode() ? 'top-start' : 'bottom-start'
                });
                this._markMenuOpen();
            },

            // ============
            // Settings panel(偏好設定:工具列/狀態列)
            // ============

            showSettingsPanel() {
                this.closeAllPanels();
                this._renderSettingsPanel();
                this.settingsPanel.style.display = 'flex';
                Portal.positionAt(this.settingsPanel, null, { placement: 'center' });
                this._markMenuOpen();

                const header = this.settingsPanel.querySelector(`.${p}portal-panel-header`);
                if (header) Portal.enableDrag(this.settingsPanel, header);
            },

            hideSettingsPanel() {
                this.settingsPanel.style.display = 'none';
                this._hasOpenMenu = false;
                this.toolbar?.classList.remove(`${p}has-open-menu`);
            },

            _renderSettingsPanel() {
                const p = CONFIG.prefix;
                const prefs = ToolbarPrefs.load();
                const btns = ToolbarPrefs.allButtons;
                const slotSettings = QuickSlots.getSettings();
                const fsStatus = FileSystemManager.getStatus();

                // 定義主題色彩變量(用於模板字串)
                const isDark = Theme.isDark();
                const c = isDark ? {
                    bg1: '#1a1a2e',
                    bg2: '#16213e',
                    text1: '#e8e8e8',
                    text2: '#a0a0a0',
                    text3: '#6c757d',
                    border: '#2d3748',
                    accent: '#4facfe',
                    btn: '#2d3748',
                    btnHover: '#4a5568',
                    danger: '#dc3545',
                    warning: '#ffc107'
                } : {
                    bg1: '#ffffff',
                    bg2: '#f8f9fa',
                    text1: '#212529',
                    text2: '#6c757d',
                    text3: '#adb5bd',
                    border: '#dee2e6',
                    accent: '#007bff',
                    btn: '#e9ecef',
                    btnHover: '#dee2e6',
                    danger: '#dc3545',
                    warning: '#ffc107'
                };

                const groups = {
                    basic: { title: '基本操作', keys: [] },
                    features: { title: '功能', keys: [] },
                    vditor: { title: 'Vditor 專用', keys: [] },
                    window: { title: '視窗控制', keys: [] }
                };
                Object.keys(btns).forEach(key => {
                    const g = btns[key]?.group;
                    if (groups[g]) groups[g].keys.push(key);
                });

                const renderGroup = (groupKey) => {
                    const group = groups[groupKey];
                    if (!group?.keys?.length) return '';

                    const rows = group.keys.map(key => {
                        const def = btns[key];
                        const isAlways = !!def.alwaysShow;
                        const checked = !!prefs.show?.[key] || isAlways;

                        return `
                            <div class="${p}settings-row" data-action="${key}">
                                <label>
                                    <input type="checkbox" data-action="${key}"
                                        ${checked ? 'checked' : ''}
                                        ${isAlways ? 'disabled' : ''}>
                                    <span class="${p}settings-icon">${Icons[def.icon] || ''}</span>
                                    <span>${Utils.escapeHtml(def.label || key)}</span>
                                    ${def.vditorOnly ? `<span class="${p}tag">Vditor</span>` : ''}
                                    ${isAlways ? `<span class="${p}tag ${p}tag-info">固定</span>` : ''}
                                </label>
                                ${!isAlways ? `
                                    <button type="button" class="${p}settings-mini-btn" data-move="up" title="上移">
                                        ${Icons.arrowUp}
                                    </button>
                                    <button type="button" class="${p}settings-mini-btn" data-move="down" title="下移">
                                        ${Icons.arrowDown}
                                    </button>
                                ` : ''}
                            </div>
                        `;
                    }).join('');

                    return `
                        <div class="${p}settings-group" data-group="${groupKey}">
                            <h4>${Utils.escapeHtml(group.title)}</h4>
                            <div class="${p}settings-list">${rows}</div>
                        </div>
                    `;
                };

                this.settingsPanel.innerHTML = `
                    <div class="${p}portal-panel-header">
                        <h3>${Icons.settings} 偏好設定(工具列 / 狀態列)</h3>
                        <button class="${p}icon-btn" data-action="close-settings" type="button" title="關閉">${Icons.close}</button>
                    </div>
                    <div class="${p}portal-panel-body">

                        <div class="${p}settings-section">
                            <h4>按鈕外觀</h4>
                            <div class="${p}settings-row">
                                <label>顯示方式</label>
                                <select id="${p}btn-appearance" class="${p}select">
                                    <option value="icon-text" ${prefs.buttonAppearance === 'icon-text' ? 'selected' : ''}>圖示和文字</option>
                                    <option value="icon-only" ${prefs.buttonAppearance === 'icon-only' ? 'selected' : ''}>僅圖示</option>
                                    <option value="text-only" ${prefs.buttonAppearance === 'text-only' ? 'selected' : ''}>僅文字</option>
                                </select>
                            </div>
                        </div>

                        <div class="${p}settings-section">
                            <h4>狀態列</h4>
                            <div class="${p}settings-row">
                                <label>
                                    <input type="checkbox" id="${p}status-enabled" ${prefs.statusBar.enabled ? 'checked' : ''}>
                                    <span>顯示狀態列</span>
                                </label>
                            </div>
                            <div class="${p}settings-row">
                                <label>顯示位置</label>
                                <select id="${p}status-position" class="${p}select">
                                    <option value="bottom" ${prefs.statusBar.position === 'bottom' ? 'selected' : ''}>底部(獨立狀態列)</option>
                                    <option value="toolbar" ${prefs.statusBar.position === 'toolbar' ? 'selected' : ''}>工具列中</option>
                                </select>
                            </div>
                            <div class="${p}settings-row ${p}settings-sub">
                                <label>
                                    <input type="checkbox" id="${p}status-word" ${prefs.statusBar.showWordCount ? 'checked' : ''}>
                                    <span>字數</span>
                                </label>
                                <label>
                                    <input type="checkbox" id="${p}status-line" ${prefs.statusBar.showLineCount ? 'checked' : ''}>
                                    <span>行數</span>
                                </label>
                                <label>
                                    <input type="checkbox" id="${p}status-reading" ${prefs.statusBar.showReadingTime !== false ? 'checked' : ''}>
                                    <span>閱讀時間</span>
                                </label>
                                <label>
                                    <input type="checkbox" id="${p}status-save" ${prefs.statusBar.showSaveTime ? 'checked' : ''}>
                                    <span>保存時間</span>
                                </label>
                            </div>
                        </div>

                        <div class="${p}settings-section">
                            <h4>快速存檔插槽</h4>
                            <div class="${p}settings-row">
                                <label>啟用插槽數量</label>
                                <select id="${p}slot-count" class="${p}select">
                                    <option value="0" ${slotSettings.enabledCount === 0 ? 'selected' : ''}>停用</option>
                                    <option value="3" ${slotSettings.enabledCount === 3 ? 'selected' : ''}>3 組</option>
                                    <option value="5" ${slotSettings.enabledCount === 5 ? 'selected' : ''}>5 組(預設)</option>
                                    <option value="7" ${slotSettings.enabledCount === 7 ? 'selected' : ''}>7 組</option>
                                    <option value="9" ${slotSettings.enabledCount === 9 ? 'selected' : ''}>9 組(最大)</option>
                                </select>
                            </div>
                            <div class="${p}settings-row">
                                <label>
                                    <input type="checkbox" id="${p}slot-toolbar" ${slotSettings.showInToolbar ? 'checked' : ''}>
                                    <span>在工具列顯示快速存檔按鈕</span>
                                </label>
                            </div>
                            <div class="${p}settings-row">
                                <label>
                                    <input type="checkbox" id="${p}slot-mini-bar" ${slotSettings.showMiniBar ? 'checked' : ''}>
                                    <span>在工具列顯示迷你插槽列(點擊載入 / Shift+點擊儲存)</span>
                                </label>
                            </div>
                            <div class="${p}settings-row ${p}settings-sub">
                                <label>迷你插槽顯示數量</label>
                                <select id="${p}slot-mini-count" class="${p}select">
                                    <option value="3" ${slotSettings.miniBarCount === 3 ? 'selected' : ''}>3</option>
                                    <option value="5" ${slotSettings.miniBarCount === 5 ? 'selected' : ''}>5(建議)</option>
                                    <option value="7" ${slotSettings.miniBarCount === 7 ? 'selected' : ''}>7</option>
                                    <option value="9" ${slotSettings.miniBarCount === 9 ? 'selected' : ''}>9</option>
                                </select>
                            </div>
                            <div class="${p}settings-row">
                                <label>
                                    <input type="checkbox" id="${p}slot-confirm-overwrite" ${slotSettings.confirmBeforeOverwrite ? 'checked' : ''}>
                                    <span>覆蓋插槽前確認</span>
                                </label>
                            </div>
                            <div class="${p}settings-row">
                                <label>
                                    <input type="checkbox" id="${p}slot-confirm-load" ${slotSettings.confirmBeforeLoad ? 'checked' : ''}>
                                    <span>載入插槽前確認(當前有內容時)</span>
                                </label>
                            </div>
                            <div class="${p}settings-row">
                                <label>
                                    <input type="checkbox" id="${p}slot-auto-backup" ${slotSettings.autoBackupBeforeLoad ? 'checked' : ''}>
                                    <span>載入插槽前自動備份當前內容</span>
                                </label>
                            </div>
                        </div>

                        <div class="${p}settings-section">
                            <h4>備份警告設定</h4>
                            <div class="${p}settings-row">
                                <label>
                                    <input type="checkbox" id="${p}backup-size-warning-enabled"
                                        ${BackupManager.getSizeWarningSettings().enabled ? 'checked' : ''}>
                                    <span>備份較大時顯示警告</span>
                                </label>
                            </div>
                            <div class="${p}settings-row ${p}settings-sub" id="${p}backup-size-threshold-row">
                                <label>警告閾值</label>
                                <select id="${p}backup-size-threshold" class="${p}select">
                                    <option value="0.5" ${BackupManager.getSizeWarningSettings().thresholdMB === 0.5 ? 'selected' : ''}>0.5 MB</option>
                                    <option value="1" ${BackupManager.getSizeWarningSettings().thresholdMB === 1 ? 'selected' : ''}>1 MB(預設)</option>
                                    <option value="2" ${BackupManager.getSizeWarningSettings().thresholdMB === 2 ? 'selected' : ''}>2 MB</option>
                                    <option value="3" ${BackupManager.getSizeWarningSettings().thresholdMB === 3 ? 'selected' : ''}>3 MB</option>
                                </select>
                            </div>
                            <div class="${p}settings-note">
                                當備份內容超過設定的大小時,會顯示提示建議您使用「匯出」功能將內容備份到本機。
                                這不會影響備份的執行,只是一個提醒。
                            </div>
                        </div>

                        <div class="${p}settings-section">
                            <h4>檔案系統備份</h4>

                            <!-- API 支援狀態 -->
                            <div class="${p}fs-status">
                                <div class="${p}fs-status-icon">
                                    ${fsStatus.supported ? Icons.check : Icons.x}
                                </div>
                                <div class="${p}fs-status-text">
                                    File System Access API
                                </div>
                                <span class="${p}fs-status-badge ${fsStatus.supported ? p + 'supported' : p + 'unsupported'}">
                                    ${fsStatus.supported ? '支援' : '不支援'}
                                </span>
                            </div>

                            ${fsStatus.supported ? `
                                <!-- 選擇資料夾 -->
                                <div class="${p}settings-row">
                                    <button class="${p}btn" id="${p}fs-select-dir" type="button">
                                        ${Icons.database} <span>選擇備份資料夾</span>
                                    </button>
                                </div>

                                ${fsStatus.directoryName ? `
                                    <div class="${p}fs-dir-info">
                                        ${Icons.database}
                                        <span class="${p}fs-dir-name">${Utils.escapeHtml(fsStatus.directoryName)}</span>
                                        <button class="${p}fs-dir-clear" id="${p}fs-clear-dir" type="button">清除</button>
                                    </div>
                                ` : `
                                    <div class="${p}settings-note">
                                        選擇資料夾後,備份將自動儲存到該位置。<br>
                                        注意:頁面重新載入後需要重新選擇資料夾。
                                    </div>
                                `}

                                ${fsStatus.hasHandle ? `
                                    <!-- 自動備份選項 -->
                                    <div class="${p}settings-row" style="margin-top:8px;">
                                        <label>
                                            <input type="checkbox" id="${p}fs-auto-backup" ${fsStatus.autoBackup ? 'checked' : ''}>
                                            <span>自動備份到資料夾</span>
                                        </label>
                                    </div>
                                ` : ''}
                            ` : `
                                <div class="${p}settings-note">
                                    您的瀏覽器不支援 File System Access API。<br>
                                    請使用 Chrome 或 Edge 86 以上版本,或使用下方的手動匯出/匯入功能。
                                </div>
                            `}

                            <div class="${p}settings-divider"></div>

                            <!-- 手動匯出/匯入 -->
                            <div class="${p}settings-row">
                                <span style="font-size:12px;color:${c.text2};">
                                    手動匯出/匯入所有備份(適用於所有瀏覽器):
                                </span>
                            </div>
                            <div class="${p}backup-io-group">
                                <button class="${p}btn" id="${p}backup-export-all" type="button">
                                    ${Icons.download} <span>匯出備份</span>
                                </button>
                                <button class="${p}btn" id="${p}backup-import-all" type="button">
                                    ${Icons.import} <span>匯入備份</span>
                                </button>
                            </div>

                            <div class="${p}settings-divider"></div>

                            <!-- 快捷鍵一覽 -->
                            <div class="${p}settings-row">
                                <button class="${p}btn" id="${p}show-shortcuts" type="button">
                                    ${Icons.info} <span>查看快捷鍵一覽</span>
                                </button>
                            </div>
                        </div>

                        <div class="${p}settings-section">
                            <h4>工具列按鈕</h4>
                            <p class="${p}settings-hint">原則:工具列與「更多」選單互補。未勾選的按鈕將出現在「更多」選單中。</p>
                            ${renderGroup('basic')}
                            ${renderGroup('features')}
                            ${renderGroup('vditor')}
                            ${renderGroup('window')}
                        </div>

                        <div class="${p}settings-section">
                            <h4>拖曳導入</h4>
                            <div class="${p}settings-row">
                                <label>
                                    <span>支援將文字或檔案拖曳到編輯器按鈕或視窗進行導入</span>
                                </label>
                            </div>
                            <div class="${p}settings-row">
                                <button class="${p}btn" id="${p}reset-dragdrop-hint" type="button">
                                    ${Icons.restore} <span>重置使用提示</span>
                                </button>
                                <span style="font-size:11px;color:${c.text3};margin-left:8px;">
                                    讓拖曳導入提示再次顯示
                                </span>
                            </div>
                        </div>

                    </div>
                    <div class="${p}portal-panel-footer">
                        <button class="${p}btn" data-action="reset-settings" type="button">重置預設</button>
                        <button class="${p}btn ${p}primary" data-action="save-settings" type="button">套用</button>
                    </div>
                `;

                this._bindSettingsPanelEvents();
            },

            _bindSettingsPanelEvents() {
                // close
                this.settingsPanel.querySelector('[data-action="close-settings"]').onclick = () => {
                    this.hideSettingsPanel();
                };

                // reset
                this.settingsPanel.querySelector('[data-action="reset-settings"]').onclick = () => {
                    ToolbarPrefs.save(ToolbarPrefs.defaultPrefs());
                    this._renderSettingsPanel();

                    ToolbarPrefs.applyToModal(this);
                    this._applyStatusMetricVisibility();
                    this.syncThemeButton();
                    this.syncMaximizeButton();

                    // 互補選單:若更多選單開著需刷新
                    if (this.morePanel?.classList.contains(`${p}open`)) {
                        this._updateMoreMenuContent();
                    }

                    Toast.info('已重置為預設配置');
                };

                // save/apply
                this.settingsPanel.querySelector('[data-action="save-settings"]').onclick = () => {
                    this._saveSettingsFromPanel();
                };

                // move up/down
                this.settingsPanel.querySelectorAll(`.${p}settings-mini-btn`).forEach(btn => {
                    btn.onclick = () => {
                        const row = btn.closest(`.${p}settings-row`);
                        const list = row?.parentElement;
                        const move = btn.dataset.move;
                        if (!row || !list) return;

                        if (move === 'up' && row.previousElementSibling) {
                            list.insertBefore(row, row.previousElementSibling);
                        } else if (move === 'down' && row.nextElementSibling) {
                            list.insertBefore(row.nextElementSibling, row);
                        }
                    };
                });

                // 重置拖曳提示
                this.settingsPanel.querySelector(`#${p}reset-dragdrop-hint`)?.addEventListener('click', () => {
                    DragDropManager.resetHintCount();
                    Toast.info('拖曳導入提示已重置');
                });

                // ===== 檔案系統設定 =====

                // 選擇資料夾
                this.settingsPanel.querySelector(`#${p}fs-select-dir`)?.addEventListener('click', async () => {
                    const handle = await FileSystemManager.selectDirectory();
                    if (handle) {
                        // 重新渲染設定面板以更新顯示
                        this._renderSettingsPanel();
                        this._bindSettingsPanelEvents();
                    }
                });

                // 清除資料夾
                this.settingsPanel.querySelector(`#${p}fs-clear-dir`)?.addEventListener('click', () => {
                    FileSystemManager.clearDirectory();
                    // 重新渲染設定面板
                    this._renderSettingsPanel();
                    this._bindSettingsPanelEvents();
                });

                // 自動備份開關
                this.settingsPanel.querySelector(`#${p}fs-auto-backup`)?.addEventListener('change', (e) => {
                    FileSystemManager.saveSettings({ autoBackup: e.target.checked });
                    Toast.info(e.target.checked ? '已啟用自動備份到資料夾' : '已停用自動備份到資料夾');
                });

                // 匯出所有備份
                this.settingsPanel.querySelector(`#${p}backup-export-all`)?.addEventListener('click', () => {
                    const success = BackupManager.downloadAllBackups();
                    if (success) {
                        Toast.success('備份已匯出');
                    } else {
                        Toast.error('匯出失敗');
                    }
                });

                // 匯入備份
                this.settingsPanel.querySelector(`#${p}backup-import-all`)?.addEventListener('click', async () => {
                    const result = await BackupManager.importBackupsFromFile();
                    if (result.success) {
                        Toast.success(result.message);
                        // 如果備份面板開啟,刷新它
                        if (this.backupPanel?.style.display !== 'none') {
                            this._renderBackupPanel();
                        }
                    } else if (result.message !== '已取消' && result.message !== '未選擇檔案') {
                        Toast.error(result.message);
                    }
                });

                // 備份大小警告設定
                const sizeWarningCheckbox = this.settingsPanel.querySelector(`#${p}backup-size-warning-enabled`);
                const sizeThresholdRow = this.settingsPanel.querySelector(`#${p}backup-size-threshold-row`);
                const sizeThresholdSelect = this.settingsPanel.querySelector(`#${p}backup-size-threshold`);

                // 根據開關狀態顯示/隱藏閾值設定
                const updateThresholdVisibility = () => {
                    if (sizeThresholdRow) {
                        sizeThresholdRow.style.opacity = sizeWarningCheckbox?.checked ? '1' : '0.5';
                        if (sizeThresholdSelect) {
                            sizeThresholdSelect.disabled = !sizeWarningCheckbox?.checked;
                        }
                    }
                };

                sizeWarningCheckbox?.addEventListener('change', updateThresholdVisibility);
                updateThresholdVisibility(); // 初始狀態

                // 快捷鍵一覽
                this.settingsPanel.querySelector(`#${p}show-shortcuts`)?.addEventListener('click', () => {
                    this.hideSettingsPanel();
                    this.showShortcutsPanel();
                });

            },

            _saveSettingsFromPanel() {
                const prefs = ToolbarPrefs.load();

                // ===== 儲存插槽設定 =====
                const slotSettings = {
                    enabledCount: parseInt(this.settingsPanel.querySelector(`#${p}slot-count`)?.value) || 5,
                    showInToolbar: !!this.settingsPanel.querySelector(`#${p}slot-toolbar`)?.checked,
                    confirmBeforeOverwrite: !!this.settingsPanel.querySelector(`#${p}slot-confirm-overwrite`)?.checked,
                    confirmBeforeLoad: !!this.settingsPanel.querySelector(`#${p}slot-confirm-load`)?.checked,
                    autoBackupBeforeLoad: !!this.settingsPanel.querySelector(`#${p}slot-auto-backup`)?.checked,

                    // 迷你插槽列
                    showMiniBar: !!this.settingsPanel.querySelector(`#${p}slot-mini-bar`)?.checked,
                    miniBarCount: parseInt(this.settingsPanel.querySelector(`#${p}slot-mini-count`)?.value || '5', 10),
                };
                QuickSlots.saveSettings(slotSettings);

                // 根據插槽設定更新工具列按鈕顯示
                prefs.show.slots = slotSettings.showInToolbar && slotSettings.enabledCount > 0;

                // appearance
                prefs.buttonAppearance = this.settingsPanel.querySelector(`#${p}btn-appearance`)?.value || 'icon-text';

                // status bar
                prefs.statusBar.enabled = !!this.settingsPanel.querySelector(`#${p}status-enabled`)?.checked;
                prefs.statusBar.position = this.settingsPanel.querySelector(`#${p}status-position`)?.value || 'bottom';
                prefs.statusBar.showWordCount = !!this.settingsPanel.querySelector(`#${p}status-word`)?.checked;
                prefs.statusBar.showLineCount = !!this.settingsPanel.querySelector(`#${p}status-line`)?.checked;
                prefs.statusBar.showSaveTime = !!this.settingsPanel.querySelector(`#${p}status-save`)?.checked;
                prefs.statusBar.showReadingTime = !!this.settingsPanel.querySelector(`#${p}status-reading`)?.checked;

                // show/hide
                this.settingsPanel.querySelectorAll(`input[type="checkbox"][data-action]`).forEach(cb => {
                    const action = cb.dataset.action;
                    const def = ToolbarPrefs.allButtons[action];
                    if (!action || !def) return;
                    if (def.alwaysShow) return;
                    prefs.show[action] = !!cb.checked;
                });

                // order arrays (依 panel DOM 順序)
                prefs.orderLeft = Array.from(
                    this.settingsPanel.querySelectorAll(`[data-group="basic"] .${p}settings-row, [data-group="features"] .${p}settings-row`)
                ).map(r => r.getAttribute('data-action')).filter(Boolean);

                prefs.orderRight = Array.from(
                    this.settingsPanel.querySelectorAll(`[data-group="vditor"] .${p}settings-row, [data-group="window"] .${p}settings-row`)
                ).map(r => r.getAttribute('data-action')).filter(Boolean);

                // 儲存備份大小警告設定
                const sizeWarningEnabled = !!this.settingsPanel.querySelector(`#${p}backup-size-warning-enabled`)?.checked;
                const sizeThresholdMB = parseFloat(
                    this.settingsPanel.querySelector(`#${p}backup-size-threshold`)?.value || '1'
                );
                BackupManager.setSizeWarningSettings(sizeWarningEnabled, sizeThresholdMB);

                ToolbarPrefs.save(prefs);

                // apply + 重要:同步動態 icon(避免被 applyToModal 覆蓋後狀態錯亂)
                ToolbarPrefs.applyToModal(this);
                this._applyStatusMetricVisibility();
                this.syncThemeButton();
                this.syncMaximizeButton();
                this.updateMiniSlotsBar();

                // 互補:更多選單若開啟需刷新
                if (this.morePanel?.classList.contains(`${p}open`)) {
                    this._updateMoreMenuContent();
                }

                this.hideSettingsPanel();
                Toast.success('偏好設定已套用');
            },

            // ============
            // Backup panel
            // ============

            showBackupPanel() {
                this.closeAllPanels();

                // 讀取持久化頁碼(避免每次開啟都回到第 1 頁)
                const savedPage = Utils.storage.get(CONFIG.storageKeys.backupPage, 0);
                this._backupCurrentPage = Number.isFinite(savedPage) ? savedPage : 0;

                this._renderBackupPanel();
                this.backupPanel.style.display = 'flex';
                Portal.positionAt(this.backupPanel, null, { placement: 'center' });
                this._markMenuOpen();

                const header = this.backupPanel.querySelector(`.${p}portal-panel-header`);
                if (header) Portal.enableDrag(this.backupPanel, header);
            },

            // ============
            // Shortcuts panel(快捷鍵一覽)
            // ============

            showShortcutsPanel() {
                const p = CONFIG.prefix;

                // 動態建立面板
                let panel = document.getElementById(`${p}shortcuts-panel-instance`);

                if (!panel) {
                    panel = document.createElement('div');
                    panel.id = `${p}shortcuts-panel-instance`;
                    panel.className = `${p}portal-panel ${p}shortcuts-panel`;
                    Portal.append(panel);
                }

                // 生成內容
                let categoriesHtml = '';
                KEYBOARD_SHORTCUTS.forEach(cat => {
                    const itemsHtml = cat.items.map(item => `
                        <tr>
                            <td class="${p}shortcut-key"><code>${Utils.escapeHtml(item.key)}</code></td>
                            <td class="${p}shortcut-desc">${Utils.escapeHtml(item.desc)}</td>
                        </tr>
                    `).join('');

                    categoriesHtml += `
                        <div class="${p}shortcuts-category">
                            <h5>${Utils.escapeHtml(cat.category)}</h5>
                            <table class="${p}shortcuts-table">
                                ${itemsHtml}
                            </table>
                        </div>
                    `;
                });

                panel.innerHTML = `
                    <div class="${p}portal-panel-header">
                        <h3>${Icons.info} 快捷鍵一覽</h3>
                        <button class="${p}icon-btn" data-action="close" type="button" title="關閉">${Icons.close}</button>
                    </div>
                    <div class="${p}portal-panel-body">
                        <div class="${p}shortcuts-content">
                            ${categoriesHtml}
                        </div>
                    </div>
                    <div class="${p}portal-panel-footer">
                        <button class="${p}btn ${p}secondary" data-action="close" type="button">關閉</button>
                    </div>
                `;

                // 顯示面板
                panel.style.display = 'flex';
                Portal.positionAt(panel, null, { placement: 'center' });

                // 綁定關閉事件
                const closeBtns = panel.querySelectorAll('[data-action="close"]');
                closeBtns.forEach(btn => {
                    btn.onclick = () => {
                        panel.style.display = 'none';
                    };
                });

                // 啟用拖曳
                const header = panel.querySelector(`.${p}portal-panel-header`);
                if (header) Portal.enableDrag(panel, header);
            },

            // ============
            // Slots panel(快速存檔)
            // ============

            showSlotsPanel(anchor) {
                this.closeAllPanels();

                // 確保 slotsPanel 的事件委派只綁定一次(避免重渲染後按鈕失效)
                this._ensureSlotsPanelDelegationOnce();

                this._renderSlotsPanel();
                this.slotsPanel.style.display = 'block';
                Portal.positionAt(this.slotsPanel, anchor || this.moreBtn, {
                    placement: this._isFocusMode() ? 'top-start' : 'bottom-start'
                });
                this._markMenuOpen();
            },

            hideSlotsPanel() {
                const p = CONFIG.prefix;  // 確保變量定義
                if (this.slotsPanel) {
                    this.slotsPanel.style.display = 'none';
                }
                this._hasOpenMenu = false;
                this.toolbar?.classList.remove(`${p}has-open-menu`);
            },

            _renderSlotsPanel() {
                const p = CONFIG.prefix;
                const settings = QuickSlots.getSettings();
                const slots = QuickSlots.getAllSlotStatus(true);
                const stats = QuickSlots.getStats();

                // 若未啟用任何插槽
                if (settings.enabledCount === 0) {
                    this.slotsPanel.innerHTML = `
                        <h4>${Icons.slots} 快速存檔</h4>
                        <div class="${p}slots-hint">
                            目前未啟用任何插槽。<br>
                            請在「偏好設定」中調整插槽數量。
                        </div>
                        <button class="${p}btn ${p}secondary" data-action="slots-close" type="button">關閉</button>
                    `;
                    this._bindSlotsPanelEvents();
                    return;
                }

                // 建立插槽列表 HTML
                let listHtml = '';
                slots.forEach(({ slot, isEmpty, meta }) => {
                    const label = meta?.label || `插槽 ${slot}`;
                    const timeStr = meta ? Utils.formatRelativeTime(meta.ts) : '';
                    const charsStr = meta ? `${meta.chars} 字` : '';
                    const linesStr = meta ? `${meta.lines} 行` : '';

                    listHtml += `
                        <div class="${p}slot-row ${isEmpty ? p + 'empty' : p + 'has-content'}" data-slot="${slot}">
                            <div class="${p}slot-number">${slot}</div>
                            <div class="${p}slot-info">
                                ${isEmpty ? `
                                    <div class="${p}slot-empty-label">(空插槽)</div>
                                ` : `
                                    <div class="${p}slot-label">${Utils.escapeHtml(label)}</div>
                                    <div class="${p}slot-meta">
                                        <span>${charsStr}</span>
                                        <span>${linesStr}</span>
                                        <span>${timeStr}</span>
                                    </div>
                                `}
                            </div>
                            <div class="${p}slot-actions">
                                ${!isEmpty ? `
                                    <button class="${p}icon-btn" data-action="slot-preview" data-slot="${slot}" title="預覽內容" type="button">
                                        ${Icons.eye}
                                    </button>
                                    <button class="${p}icon-btn" data-action="slot-load" data-slot="${slot}" title="載入此插槽 (Ctrl+${slot})" type="button">
                                        ${Icons.import}
                                    </button>
                                ` : ''}
                                <button class="${p}icon-btn ${!isEmpty ? '' : p + 'primary'}" data-action="slot-save" data-slot="${slot}" title="儲存到此插槽 (Ctrl+Shift+${slot})" type="button">
                                    ${Icons.save}
                                </button>
                                ${!isEmpty ? `
                                    <button class="${p}icon-btn" data-action="slot-edit-label" data-slot="${slot}" title="編輯標籤" type="button">
                                        ${Icons.edit}
                                    </button>
                                    <button class="${p}icon-btn ${p}danger" data-action="slot-clear" data-slot="${slot}" title="清空此插槽" type="button">
                                        ${Icons.trash}
                                    </button>
                                ` : ''}
                            </div>
                        </div>
                    `;
                });

                this.slotsPanel.innerHTML = `
                    <h4>${Icons.slots} 快速存檔</h4>
                    <div class="${p}slots-hint">
                        💡 快捷鍵:<code>Ctrl+1~9</code> 載入,<code>Ctrl+Shift+1~9</code> 儲存
                    </div>
                    <div class="${p}slots-stats">
                        <span>已使用 <b>${stats.used}</b> / <b>${stats.total}</b> 插槽</span>
                        <span>共 <b>${stats.totalChars}</b> 字</span>
                    </div>
                    <div class="${p}slots-list">
                        ${listHtml}
                    </div>
                    <div class="${p}slots-footer">
                        <div class="${p}slots-footer-left">
                            <button class="${p}btn" data-action="slots-export" type="button" title="匯出所有插槽">
                                ${Icons.download} <span>匯出</span>
                            </button>
                            <button class="${p}btn" data-action="slots-import" type="button" title="匯入插槽">
                                ${Icons.import} <span>匯入</span>
                            </button>
                        </div>
                        <div class="${p}slots-footer-right">
                            <button class="${p}btn ${p}danger" data-action="slots-clear-all" type="button" title="清空所有插槽">
                                ${Icons.trash} <span>全部清空</span>
                            </button>
                            <button class="${p}btn ${p}secondary" data-action="slots-close" type="button">關閉</button>
                        </div>
                    </div>
                `;

                this._bindSlotsPanelEvents();
            },

            _bindSlotsPanelEvents() {
                const p = CONFIG.prefix;
                const self = this;

                // 使用事件委派處理所有按鈕點擊
                // 移除舊的監聽器(避免重複綁定)
                const newPanel = this.slotsPanel.cloneNode(true);
                this.slotsPanel.parentNode?.replaceChild(newPanel, this.slotsPanel);
                this.slotsPanel = newPanel;

                this.slotsPanel.addEventListener('click', (e) => {
                    const actionBtn = e.target.closest('[data-action]');
                    if (!actionBtn) return;

                    const action = actionBtn.getAttribute('data-action');
                    const slot = parseInt(actionBtn.getAttribute('data-slot') || '0');

                    e.preventDefault();
                    e.stopPropagation();

                    switch (action) {
                        case 'slots-close':
                            self.hideSlotsPanel();
                            break;
                        case 'slot-load':
                            if (slot) self._slotLoad(slot);
                            break;
                        case 'slot-save':
                            if (slot) self._slotSave(slot);
                            break;
                        case 'slot-clear':
                            if (slot) self._slotClear(slot);
                            break;
                        case 'slot-preview':
                            if (slot) self._slotPreview(slot);
                            break;
                        case 'slot-edit-label':
                            if (slot) self._slotEditLabel(slot);
                            break;
                        case 'slots-export':
                            self._slotsExport();
                            break;
                        case 'slots-import':
                            self._slotsImport();
                            break;
                        case 'slots-clear-all':
                            self._slotsClearAll();
                            break;
                    }
                });
            },

            /**
             * 載入插槽內容
             */
            _slotLoad(slot) {
                const settings = QuickSlots.getSettings();

                // 檢查插槽是否為空
                if (QuickSlots.isSlotEmpty(slot)) {
                    Toast.warning(`插槽 ${slot} 為空`);
                    return;
                }

                // 檢查當前是否有內容
                const currentContent = EditorManager.getValue();
                const hasCurrentContent = currentContent && currentContent.trim().length > 0;

                if (hasCurrentContent && settings.confirmBeforeLoad) {
                    if (!confirm(`目前編輯器中有內容。\n載入插槽 ${slot} 將會覆蓋當前內容。\n\n確定要繼續嗎?`)) {
                        return;
                    }
                }

                // 自動備份當前內容
                if (hasCurrentContent && settings.autoBackupBeforeLoad) {
                    const info = EditorManager.getCurrentInfo();
                    BackupManager.create(currentContent, {
                        editorKey: info?.key,
                        mode: info?.adapter?._detectModeFromDOM?.(),
                        manual: false
                    });
                    log('QuickSlots: Auto-backed up current content before loading slot', slot);
                }

                // 載入插槽內容
                const result = QuickSlots.loadFromSlot(slot);
                if (result.success) {
                    EditorManager.setValue(result.content);
                    const label = result.meta?.label || `插槽 ${slot}`;
                    Toast.success(`已載入:${label}`);
                    this.updateWordCount();
                    this.hideSlotsPanel();
                } else {
                    Toast.error(result.message);
                }
            },

            /**
             * 儲存到插槽
             */
            _slotSave(slot) {
                if (!EditorManager.isReady()) {
                    Toast.warning('編輯器尚未就緒');
                    return;
                }

                const content = EditorManager.getValue();
                if (!content || !content.trim()) {
                    Toast.warning('目前無內容可儲存');
                    return;
                }

                const settings = QuickSlots.getSettings();
                const existing = QuickSlots.getSlotMeta(slot);

                // 確認覆蓋
                if (existing && settings.confirmBeforeOverwrite) {
                    const existingLabel = existing.label || `插槽 ${slot}`;
                    if (!confirm(`插槽 ${slot}(${existingLabel})已有內容。\n\n確定要覆蓋嗎?`)) {
                        return;
                    }
                }

                // 儲存
                const info = EditorManager.getCurrentInfo();
                const result = QuickSlots.saveToSlot(slot, content, {
                    editorKey: info?.key || null
                });
                if (result.success) {
                    Toast.success(`已儲存到插槽 ${slot}`);
                    this._renderSlotsPanel(); // 刷新面板
                } else {
                    Toast.error(result.message);
                }
            },

            /**
             * 清空插槽
             */
            _slotClear(slot) {
                const meta = QuickSlots.getSlotMeta(slot);
                if (!meta) {
                    Toast.info(`插槽 ${slot} 已經是空的`);
                    return;
                }

                const label = meta.label || `插槽 ${slot}`;
                if (!confirm(`確定要清空「${label}」嗎?\n\n此操作無法復原。`)) {
                    return;
                }

                QuickSlots.clearSlot(slot);
                Toast.info(`已清空插槽 ${slot}`);
                this._renderSlotsPanel(); // 刷新面板
            },

            /**
             * 預覽插槽內容
             */
            _slotPreview(slot) {
                const result = QuickSlots.loadFromSlot(slot);
                if (!result.success) {
                    Toast.error(result.message);
                    return;
                }

                const p = CONFIG.prefix;
                const meta = result.meta;
                const label = meta?.label || `插槽 ${slot}`;
                const timeStr = meta ? Utils.formatRelativeTime(meta.ts) : '';

                const panel = document.createElement('div');
                panel.className = `${p}portal-panel ${p}slot-preview-panel`;
                panel.innerHTML = `
                    <div class="${p}portal-panel-header">
                        <h3>${Icons.eye} 預覽:${Utils.escapeHtml(label)}</h3>
                        <button class="${p}icon-btn" data-action="close" type="button" title="關閉">${Icons.close}</button>
                    </div>
                    <div class="${p}portal-panel-body">
                        <div class="${p}slots-stats" style="margin-bottom:12px;">
                            <span><b>${meta?.chars || 0}</b> 字</span>
                            <span><b>${meta?.lines || 0}</b> 行</span>
                            <span>${timeStr}</span>
                        </div>
                        <div class="${p}slot-preview-content">${Utils.escapeHtml(result.content)}</div>
                    </div>
                    <div class="${p}portal-panel-footer">
                        <button class="${p}btn" data-action="copy" type="button">${Icons.copy} 複製內容</button>
                        <button class="${p}btn ${p}primary" data-action="load" type="button">${Icons.import} 載入此插槽</button>
                    </div>
                `;

                Portal.append(panel);
                panel.style.display = 'flex';
                Portal.positionAt(panel, null, { placement: 'center' });

                const header = panel.querySelector(`.${p}portal-panel-header`);
                if (header) Portal.enableDrag(panel, header);

                // 綁定事件
                panel.querySelector('[data-action="close"]').onclick = () => Portal.remove(panel);

                panel.querySelector('[data-action="copy"]').onclick = async () => {
                    const ok = await Utils.copyToClipboard(result.content);
                    Toast[ok ? 'success' : 'error'](ok ? '已複製內容' : '複製失敗');
                };

                panel.querySelector('[data-action="load"]').onclick = () => {
                    Portal.remove(panel);
                    this._slotLoad(slot);
                };
            },

            /**
             * 編輯插槽標籤
             */
            _slotEditLabel(slot) {
                const meta = QuickSlots.getSlotMeta(slot);
                if (!meta) {
                    Toast.warning(`插槽 ${slot} 為空`);
                    return;
                }

                const currentLabel = meta.label || '';
                const newLabel = prompt(`設定插槽 ${slot} 的標籤:`, currentLabel);

                if (newLabel === null) return; // 使用者取消

                QuickSlots.setSlotLabel(slot, newLabel.trim());
                Toast.success(newLabel.trim() ? `標籤已設為:${newLabel.trim()}` : '標籤已清除');
                this._renderSlotsPanel(); // 刷新面板
            },

            /**
             * 匯出所有插槽
             */
            _slotsExport() {
                const data = QuickSlots.exportAllSlots();
                const json = JSON.stringify(data, null, 2);
                const date = Utils.formatDate();
                const filename = `mme_slots_${date}.json`;

                const ok = Utils.downloadFile(json, filename, 'application/json;charset=utf-8');
                Toast[ok ? 'success' : 'error'](ok ? '插槽已匯出' : '匯出失敗');
            },

            /**
             * 匯入插槽
             */
            _slotsImport() {
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = '.json';
                input.style.display = 'none';

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

                    try {
                        const text = await Utils.readFile(file);
                        const data = JSON.parse(text);

                        const overwrite = confirm(
                            '匯入選項:\n\n' +
                            '點擊「確定」:覆蓋現有的非空插槽\n' +
                            '點擊「取消」:僅匯入到空插槽'
                        );

                        const result = QuickSlots.importSlots(data, { overwrite });

                        if (result.success) {
                            Toast.success(result.message);
                            this._renderSlotsPanel(); // 刷新面板
                        } else {
                            Toast.error(result.message);
                        }
                    } catch (err) {
                        Toast.error('匯入失敗:檔案格式無效');
                        logError('Slots import error:', err);
                    }

                    input.remove();
                };

                document.body.appendChild(input);
                input.click();
            },

            /**
             * 清空所有插槽
             */
            _slotsClearAll() {
                const stats = QuickSlots.getStats();
                if (stats.used === 0) {
                    Toast.info('所有插槽都是空的');
                    return;
                }

                if (!confirm(`確定要清空所有 ${stats.used} 個插槽嗎?\n\n此操作無法復原。`)) {
                    return;
                }

                const count = QuickSlots.clearAllSlots();
                Toast.info(`已清空 ${count} 個插槽`);
                this._renderSlotsPanel(); // 刷新面板
            },

            updateMiniSlotsBar() {
                const p = CONFIG.prefix;
                const el = this.miniSlotsEl;
                if (!el) return;

                const s = QuickSlots.getSettings();

                // 是否顯示:需啟用插槽且 showMiniBar = true
                const enabled = (s.enabledCount > 0) && !!s.showMiniBar;

                if (!enabled) {
                    el.style.display = 'none';
                    el.innerHTML = '';
                    return;
                }

                const count = Math.max(1, Math.min(s.enabledCount, s.miniBarCount || 5));

                // 只渲染 1..count
                const btns = [];
                for (let slot = 1; slot <= count; slot++) {
                    const meta = QuickSlots.getSlotMeta(slot);
                    const isEmpty = !meta;

                    const label = meta?.label || `插槽 ${slot}`;
                    const timeStr = meta ? Utils.formatRelativeTime(meta.ts) : '空插槽';
                    const charsStr = meta ? `${meta.chars || 0} 字` : '';

                    const tooltip = isEmpty
                        ? `插槽 ${slot}(空)\n點擊:載入(無效)\nShift+點擊:儲存`
                        : `${label}\n${charsStr} · ${timeStr}\n點擊:載入\nShift+點擊:儲存(覆蓋)`;

                    btns.push(`
                        <button type="button"
                            class="${p}mini-slot-btn ${isEmpty ? p + 'empty' : p + 'has-content'}"
                            data-slot="${slot}"
                            data-tooltip="${Utils.escapeHtml(tooltip)}"
                            aria-label="${Utils.escapeHtml(`迷你插槽 ${slot}`)}">
                            ${slot}
                        </button>
                    `);
                }

                el.innerHTML = btns.join('');
                el.style.display = 'flex';
            },

            _bindMiniSlotsBarEventsOnce() {
                if (this._miniSlotsBound) return;
                this._miniSlotsBound = true;

                const el = this.miniSlotsEl;
                if (!el) return;

                el.addEventListener('click', (e) => {
                    const btn = e.target.closest('[data-slot]');
                    if (!btn) return;

                    const slot = parseInt(btn.getAttribute('data-slot') || '0', 10);
                    if (!slot) return;

                    // Shift+Click:儲存;Click:載入
                    if (e.shiftKey) {
                        this._slotSave(slot);
                        // 立即更新 mini bar(因為 meta 改變了)
                        this.updateMiniSlotsBar();
                    } else {
                        this._slotLoad(slot);
                        // load 會更新 lastAccess,也更新一下 tooltip
                        this.updateMiniSlotsBar();
                    }
                });
            },

            _ensureSlotsPanelDelegationOnce() {
                if (this._slotsDelegated) return;
                this._slotsDelegated = true;

                if (!this.slotsPanel) return;

                this.slotsPanel.addEventListener('click', (e) => {
                    const btn = e.target.closest('[data-action]');
                    if (!btn) return;

                    const action = btn.getAttribute('data-action');
                    const slot = parseInt(btn.getAttribute('data-slot') || '0', 10);

                    switch (action) {
                        case 'slots-close':
                            this.hideSlotsPanel();
                            return;

                        case 'slot-load':
                            if (slot) this._slotLoad(slot);
                            this.updateMiniSlotsBar();
                            return;

                        case 'slot-save':
                            if (slot) this._slotSave(slot);
                            this.updateMiniSlotsBar();
                            return;

                        case 'slot-clear':
                            if (slot) this._slotClear(slot);
                            this.updateMiniSlotsBar();
                            return;

                        case 'slot-preview':
                            if (slot) this._slotPreview(slot);
                            return;

                        case 'slot-edit-label':
                            if (slot) this._slotEditLabel(slot);
                            this.updateMiniSlotsBar();
                            return;

                        case 'slots-export':
                            this._slotsExport();
                            return;

                        case 'slots-import':
                            this._slotsImport();
                            // 匯入後會 re-render,再更新 mini bar
                            setTimeout(() => this.updateMiniSlotsBar(), 50);
                            return;

                        case 'slots-clear-all':
                            this._slotsClearAll();
                            this.updateMiniSlotsBar();
                            return;
                    }
                });
            },

            hideBackupPanel() {
                this.backupPanel.style.display = 'none';
                this._hasOpenMenu = false;
                this.toolbar?.classList.remove(`${p}has-open-menu`);
            },

            _renderBackupPanel() {
                const p = CONFIG.prefix;
                const allIndex = BackupManager.getIndex();
                const stats = BackupManager.getStats();

                // 分頁設定
                const PAGE_SIZE = CONFIG.backup.pageSize || 20;
                const currentPage = Number.isFinite(this._backupCurrentPage) ? this._backupCurrentPage : 0;
                const totalPages = Math.ceil(allIndex.length / PAGE_SIZE);
                const startIdx = currentPage * PAGE_SIZE;
                const endIdx = Math.min(startIdx + PAGE_SIZE, allIndex.length);
                const index = allIndex.slice(startIdx, endIdx);

                // 吸收 B 版亮點:加入“草稿/備份/快照”定位說明(不改行為,只補 UX)
                const infoBox = `
                    <div class="${p}backup-info-box">
                        <div class="${p}backup-info-title">
                            ${Icons.shield} 備份 / 草稿 / 快照:概念說明
                        </div>
                        <ul class="${p}backup-info-list">
                            <li><b>草稿</b>:您目前的工作內容(「保存」保存到瀏覽器)</li>
                            <li><b>備份</b>:歷史版本(可釘選永久保留,可下載)</li>
                            <li><b>快照</b>:Vditor 專用救援(防模式切換內容異常)</li>
                            <li>📌 <b>釘選</b>的備份永久保留,不會被自動清理</li>
                            <li>⏱️ <b>1 小時內</b>:每 2 分鐘保留一筆</li>
                            <li>📅 <b>24 小時內</b>:每 10 分鐘保留一筆</li>
                            <li>📆 <b>7 天內</b>:每天保留一筆</li>
                            <li>📊 最多保留 <b>${CONFIG.backup.maxBackups}</b> 筆備份</li>
                        </ul>
                        <div class="${p}backup-info-warning">
                            ⚠️ <b>重要</b>:若內容重要,請使用「釘選」或「下載」以避免自動清理導致遺失。
                        </div>
                    </div>
                `;

                // 如果有多頁,新增分頁控制
                let paginationHtml = '';
                if (totalPages > 1) {
                    paginationHtml = `
                        <div class="${p}backup-pagination">
                            <button class="${p}btn" data-action="backup-prev" ${currentPage === 0 ? 'disabled' : ''}>
                                ${Icons.arrowUp} 上一頁
                            </button>
                            <span class="${p}backup-page-info">
                                第 ${currentPage + 1} / ${totalPages} 頁
                                (共 ${allIndex.length} 筆)
                            </span>
                            <button class="${p}btn" data-action="backup-next" ${currentPage >= totalPages - 1 ? 'disabled' : ''}>
                                下一頁 ${Icons.arrowDown}
                            </button>
                        </div>
                    `;
                }

                let listHtml = '';
                if (!index.length) {
                    listHtml = `
                        <div class="${p}backup-empty">
                            ${Icons.database}
                            <p>暫無備份</p>
                            <p style="font-size:12px;">編輯器會定期自動備份(可釘選)</p>
                        </div>
                    `;
                } else {
                    listHtml = index.map(meta => {
                        const time = Utils.formatRelativeTime(meta.ts);
                        const fullTime = new Date(meta.ts).toLocaleString('zh-TW');
                        const editorName = CONFIG.editors[meta.editorKey]?.name || meta.editorKey || '未知';

                        const age = Date.now() - meta.ts;
                        const isOld = age > 86400000;
                        const isVeryOld = age > 604800000;

                        let ageWarning = '';
                        if (!meta.pinned) {
                            if (isVeryOld) ageWarning = `<span class="${p}backup-age-warning ${p}danger">⚠️ 可能即將被刪除</span>`;
                            else if (isOld) ageWarning = `<span class="${p}backup-age-warning">📅 較舊備份</span>`;
                        }

                        return `
                            <div class="${p}backup-item ${meta.pinned ? p + 'pinned' : ''}" data-id="${meta.id}">
                                <div class="${p}backup-info">
                                    <div class="${p}backup-time" title="${Utils.escapeHtml(fullTime)}">
                                        ${Utils.escapeHtml(time)}
                                        ${meta.pinned ? `<span class="${p}pin-badge">📌 已釘選</span>` : ''}
                                        ${ageWarning}
                                    </div>
                                    <div class="${p}backup-meta">
                                        <span>${meta.chars || 0} 字</span>
                                        <span>${meta.lines || 0} 行</span>
                                        <span>${Utils.escapeHtml(editorName)}</span>
                                        ${meta.mode ? `<span>${Utils.escapeHtml(meta.mode)}</span>` : ''}
                                    </div>
                                </div>
                                <div class="${p}backup-actions">
                                    <button class="${p}icon-btn" data-action="preview" type="button" title="預覽內容">${Icons.eye}</button>
                                    <button class="${p}icon-btn" data-action="restore" type="button" title="還原此備份">${Icons.restore}</button>
                                    <button class="${p}icon-btn ${meta.pinned ? p + 'active' : ''}" data-action="pin" type="button" title="${meta.pinned ? '取消釘選' : '釘選(永久保留)'}">${Icons.pin}</button>
                                    <button class="${p}icon-btn" data-action="download" type="button" title="下載備份檔案">${Icons.download}</button>
                                    <button class="${p}icon-btn ${p}danger" data-action="delete" type="button" title="刪除此備份">${Icons.trash}</button>
                                </div>
                            </div>
                        `;
                    }).join('');
                }

                this.backupPanel.innerHTML = `
                    <div class="${p}portal-panel-header">
                        <h3>${Icons.database} 備份管理</h3>
                        <button class="${p}icon-btn" data-action="close-backup" type="button" title="關閉">${Icons.close}</button>
                    </div>
                    <div class="${p}portal-panel-body">
                        ${infoBox}
                        <div class="${p}backup-stats">
                            <span>共 <b>${stats.total}</b> 筆</span>
                            <span>釘選 <b>${stats.pinned}</b> 筆</span>
                            ${stats.oldest ? `<span>最早 ${Utils.escapeHtml(Utils.formatRelativeTime(stats.oldest))}</span>` : ''}
                        </div>
                        <div class="${p}backup-list">${listHtml}</div>
                    </div>
                    <div class="${p}portal-panel-footer">
                        <button class="${p}btn" data-action="backup-now" type="button">${Icons.save} 立即備份</button>
                        <button class="${p}btn" data-action="pin-all" type="button" title="釘選所有備份">${Icons.pin} 全部釘選</button>
                        <button class="${p}btn ${p}danger" data-action="clear-backups" type="button">${Icons.trash} 清除所有</button>
                    </div>
                `;

                // bind header close
                this.backupPanel.querySelector('[data-action="close-backup"]').onclick = () => this.hideBackupPanel();

                // 分頁控制
                this.backupPanel.querySelector('[data-action="backup-prev"]')?.addEventListener('click', () => {
                    if (this._backupCurrentPage > 0) {
                        this._backupCurrentPage--;
                        Utils.storage.set(CONFIG.storageKeys.backupPage, this._backupCurrentPage);
                        this._renderBackupPanel();
                    }
                });

                this.backupPanel.querySelector('[data-action="backup-next"]')?.addEventListener('click', () => {
                    const totalPages = Math.ceil(BackupManager.getIndex().length / (CONFIG.backup.pageSize || 20));
                    if (this._backupCurrentPage < totalPages - 1) {
                        this._backupCurrentPage++;
                        Utils.storage.set(CONFIG.storageKeys.backupPage, this._backupCurrentPage);
                        this._renderBackupPanel();
                    }
                });

                // footer
                this.backupPanel.querySelector('[data-action="backup-now"]').onclick = () => {
                    if (!EditorManager.isReady()) {
                        Toast.warning('編輯器尚未就緒');
                        return;
                    }
                    const content = EditorManager.getValue();
                    const info = EditorManager.getCurrentInfo();
                    const meta = BackupManager.create(content, {
                        editorKey: info?.key,
                        mode: info?.adapter?._detectModeFromDOM?.(),
                        manual: true
                    });
                    if (meta) {
                        Toast.success('已建立備份');
                        this._renderBackupPanel();
                        this._updateMoreMenuContent(); // 內容不一定變,但保持一致
                    } else {
                        Toast.info('內容無變更,無需備份');
                    }
                };

                this.backupPanel.querySelector('[data-action="pin-all"]').onclick = () => {
                    const idx = BackupManager.getIndex();
                    let count = 0;
                    idx.forEach(m => {
                        if (!m.pinned) { m.pinned = true; count++; }
                    });
                    BackupManager.saveIndex(idx);
                    Toast.success(`已釘選 ${count} 筆備份`);
                    this._renderBackupPanel();
                };

                this.backupPanel.querySelector('[data-action="clear-backups"]').onclick = () => {
                    const pinnedCount = BackupManager.getStats().pinned;
                    const msg = pinnedCount > 0
                        ? `確定要清除所有備份嗎?\n\n⚠️ 這將包括 ${pinnedCount} 筆已釘選備份!\n\n此操作無法復原。`
                        : '確定要清除所有備份嗎?此操作無法復原。';

                    if (confirm(msg)) {
                        BackupManager.clearAll();
                        Toast.info('已清除所有備份');
                        this._renderBackupPanel();
                        this._updateMoreMenuContent();
                        Utils.storage.set(CONFIG.storageKeys.backupPage, 0);
                        this._backupCurrentPage = 0;
                    }
                };

                // item actions (event binding per item)
                this.backupPanel.querySelectorAll(`.${p}backup-item`).forEach(item => {
                    const id = item.dataset.id;
                    if (!id) return;

                    item.querySelector('[data-action="preview"]')?.addEventListener('click', () => {
                        this._showBackupPreview(id);
                    });

                    item.querySelector('[data-action="restore"]')?.addEventListener('click', () => {
                        const content = BackupManager.restore(id);
                        if (content) {
                            EditorManager.setValue(content);
                            Toast.success('已還原備份');
                            this.updateWordCount();
                        }
                    });

                    item.querySelector('[data-action="pin"]')?.addEventListener('click', () => {
                        const pinned = BackupManager.togglePin(id);
                        Toast.info(pinned ? '已釘選(永久保留)' : '已取消釘選(可能被自動清理)');
                        this._renderBackupPanel();
                        this._updateMoreMenuContent();
                    });

                    item.querySelector('[data-action="download"]')?.addEventListener('click', () => {
                        const content = BackupManager.getBackup(id);
                        const meta = BackupManager.getIndex().find(m => m.id === id);
                        if (content) {
                            const date = new Date(meta?.ts || Date.now()).toISOString().replace(/[:.]/g, '-').slice(0, 19);
                            Utils.downloadFile(content, `backup_${date}.md`, 'text/markdown;charset=utf-8');
                            Toast.success('已下載備份');
                        }
                    });

                    item.querySelector('[data-action="delete"]')?.addEventListener('click', () => {
                        const meta = BackupManager.getIndex().find(m => m.id === id);
                        const msg = meta?.pinned
                            ? '⚠️ 此備份已被釘選!確定要刪除嗎?'
                            : '確定要刪除此備份嗎?';

                        if (confirm(msg)) {
                            BackupManager.delete(id);
                            Toast.info('已刪除備份');
                            this._renderBackupPanel();
                            this._updateMoreMenuContent();
                        }
                    });
                });
            },

            _showBackupPreview(id) {
                const content = BackupManager.getBackup(id);
                const meta = BackupManager.getIndex().find(m => m.id === id);

                if (!content) {
                    Toast.error('無法讀取備份');
                    return;
                }

                const panel = document.createElement('div');
                panel.className = `${p}portal-panel ${p}preview-panel`;
                panel.innerHTML = `
                    <div class="${p}portal-panel-header">
                        <h3>${Icons.eye} 備份預覽 - ${Utils.escapeHtml(Utils.formatRelativeTime(meta?.ts || Date.now()))}</h3>
                        <button class="${p}icon-btn" data-action="close" type="button" title="關閉">${Icons.close}</button>
                    </div>
                    <div class="${p}portal-panel-body">
                        <div class="${p}preview-content"><pre>${Utils.escapeHtml(content)}</pre></div>
                    </div>
                    <div class="${p}portal-panel-footer">
                        <button class="${p}btn" data-action="copy" type="button">${Icons.copy} 複製內容</button>
                        <button class="${p}btn ${p}primary" data-action="restore" type="button">${Icons.restore} 還原此備份</button>
                    </div>
                `;

                Portal.append(panel);
                panel.style.display = 'flex';
                Portal.positionAt(panel, null, { placement: 'center' });

                const header = panel.querySelector(`.${p}portal-panel-header`);
                if (header) Portal.enableDrag(panel, header);

                panel.querySelector('[data-action="close"]').onclick = () => Portal.remove(panel);

                panel.querySelector('[data-action="copy"]').onclick = async () => {
                    const ok = await Utils.copyToClipboard(content);
                    Toast[ok ? 'success' : 'error'](ok ? '已複製' : '複製失敗');
                };

                panel.querySelector('[data-action="restore"]').onclick = () => {
                    EditorManager.setValue(content);
                    Toast.success('已還原備份');
                    this.updateWordCount();
                    Portal.remove(panel);
                };
            },

            // ============
            // Focus mode (behavior) — CSS in EnhanceUI
            // ============

            toggleFocusMode() {
                const prefs = ToolbarPrefs.load();
                prefs.focusMode = !prefs.focusMode;
                ToolbarPrefs.save(prefs);

                ToolbarPrefs.applyToModal(this);
                this._applyStatusMetricVisibility();
                this.syncThemeButton();
                this.syncMaximizeButton();

                if (prefs.focusMode) {
                    this.modal.classList.add(`${p}show-hint`);
                    setTimeout(() => this.modal.classList.remove(`${p}show-hint`), 4500);
                    Toast.info('已進入專注模式 · 滑鼠移至底部顯示工具列 · 按 Esc 退出', 4000);
                } else {
                    Toast.info('已退出專注模式');
                    // 確保退出時工具列可見
                    this.toolbar?.classList.remove(`${p}visible`);
                    this.toolbar?.classList.remove(`${p}has-open-menu`);
                }

                // 若更多選單開啟,刷新互補內容
                if (this.morePanel?.classList.contains(`${p}open`)) {
                    this._updateMoreMenuContent();
                }
            },

            _isFocusMode() {
                return this.modal?.classList.contains(`${p}focus-mode`);
            },

            _initFocusModeControl() {
                if (this._focusModeBound) return;
                this._focusModeBound = true;

                const TRIGGER_ZONE_HEIGHT = CONFIG.dimensions.focusTriggerZoneHeight;

                this._focusModeMouseHandler = (e) => {
                    if (!this.isOpen) return;

                    const prefs = ToolbarPrefs.load();
                    if (!prefs.focusMode) return;

                    if (this._hasOpenMenu) {
                        this.toolbar.classList.add(`${p}visible`);
                        return;
                    }

                    const modalRect = this.modal.getBoundingClientRect();
                    const distanceFromBottom = modalRect.bottom - e.clientY;

                    if (distanceFromBottom >= 0 && distanceFromBottom <= TRIGGER_ZONE_HEIGHT) {
                        this.toolbar.classList.add(`${p}visible`);
                    } else {
                        const tb = this.toolbar.getBoundingClientRect();
                        const inToolbar = (
                            e.clientY >= tb.top && e.clientY <= tb.bottom &&
                            e.clientX >= tb.left && e.clientX <= tb.right
                        );
                        if (!inToolbar) this.toolbar.classList.remove(`${p}visible`);
                    }
                };

                this._focusModeLeaveHandler = (e) => {
                    if (!this.isOpen) return;

                    const prefs = ToolbarPrefs.load();
                    if (!prefs.focusMode) return;
                    if (this._hasOpenMenu) return;

                    if (!this.modal.contains(e.relatedTarget)) {
                        this.toolbar.classList.remove(`${p}visible`);
                    }
                };

                document.addEventListener('mousemove', this._focusModeMouseHandler);
                this.modal.addEventListener('mouseleave', this._focusModeLeaveHandler);
            },

            /**
             * 初始化 Tooltip 系統
             *
             * 設計意圖:
             * - 為帶有 data-tooltip 屬性的元素顯示提示
             * - 智慧定位:根據元素位置決定 Tooltip 顯示在上方或下方
             * - 防閃爍:使用延遲顯示/隱藏機制
             */
            _initTooltip() {
                if (this._tooltipBound) return;
                this._tooltipBound = true;

                let hideTimer = null;
                let showTimer = null;
                let currentTarget = null;

                const SHOW_DELAY = 150;  // 顯示延遲,避免快速移動時閃爍
                const HIDE_DELAY = 100;  // 隱藏延遲,允許滑鼠移到 tooltip 上
                const PADDING = CONFIG.dimensions?.tooltipPadding || 5;

                /**
                 * 顯示 Tooltip
                 */
                const showTooltip = (e) => {
                    const target = e.target.closest('[data-tooltip]');
                    if (!target) return;

                    // 清除隱藏計時器
                    clearTimeout(hideTimer);

                    // 如果是同一個目標且已經顯示,不需要重新處理
                    if (target === currentTarget && this.tooltipEl.style.opacity === '1') {
                        return;
                    }

                    // 清除之前的顯示計時器
                    clearTimeout(showTimer);

                    const text = target.getAttribute('data-tooltip');
                    if (!text) return;

                    // 延遲顯示,避免快速掃過時閃爍
                    showTimer = setTimeout(() => {
                        currentTarget = target;
                        this.tooltipEl.textContent = text;
                        this.tooltipEl.style.visibility = 'visible';
                        this.tooltipEl.style.opacity = '0';

                        requestAnimationFrame(() => {
                            if (!this.tooltipEl) return;

                            const rect = target.getBoundingClientRect();
                            const ttRect = this.tooltipEl.getBoundingClientRect();
                            const isBottomHalf = rect.top > window.innerHeight / 2;

                            // 計算位置
                            let top = isBottomHalf
                                ? rect.top - ttRect.height - 8
                                : rect.bottom + 8;
                            let left = rect.left + rect.width / 2 - ttRect.width / 2;

                            // 邊界限制
                            top = Math.max(PADDING, Math.min(top, window.innerHeight - ttRect.height - PADDING));
                            left = Math.max(PADDING, Math.min(left, window.innerWidth - ttRect.width - PADDING));

                            this.tooltipEl.style.top = `${top}px`;
                            this.tooltipEl.style.left = `${left}px`;
                            this.tooltipEl.style.opacity = '1';
                        });
                    }, SHOW_DELAY);
                };

                /**
                 * 隱藏 Tooltip
                 */
                const hideTooltip = () => {
                    clearTimeout(showTimer);
                    hideTimer = setTimeout(() => {
                        if (!this.tooltipEl) return;
                        this.tooltipEl.style.opacity = '0';
                        currentTarget = null;
                    }, HIDE_DELAY);
                };

                // 事件綁定
                document.addEventListener('mouseover', showTooltip);
                document.addEventListener('mouseout', (e) => {
                    if (e.target.closest('[data-tooltip]')) {
                        hideTooltip();
                    }
                });

                // 主題變更時更新 Tooltip 背景色
                Theme.onChange((t) => {
                    if (!this.tooltipEl) return;
                    this.tooltipEl.style.background = t === 'dark' ? '#4a5568' : '#333';
                });
            },

            // ============
            // Outside click closes panels
            // ============

            _bindOutsideClickToClosePanels() {
                if (this._outsideClickBound) return;
                this._outsideClickBound = true;

                document.addEventListener('click', (e) => {
                    if (!this.isOpen) return;

                    const isInPanel = [
                        this.morePanel,
                        this.importPanel,
                        this.exportPanel,
                        this.settingsPanel,
                        this.backupPanel,
                        this.slotsPanel,
                    ].some(panel => panel && panel.contains(e.target));

                    const isInToolbar = this.toolbar?.contains(e.target);

                    if (!isInPanel && !isInToolbar) {
                        this.closeAllPanels();
                    }
                }, true);
            },

            // ============
            // Keyboard shortcuts
            // ============

            /**
             * 檢查當前焦點是否位於編輯器的可編輯區域
             *
             * 設計意圖:
             * - 當焦點在編輯器內的文字輸入區域時,應讓編輯器處理搜尋快捷鍵
             * - 這包括 CodeMirror、textarea、contenteditable 元素
             * - 各編輯器使用不同的技術,需要全面檢查
             *
             * @returns {boolean} 是否在編輯器可編輯區域內
             */
            _isFocusInEditorEditableArea() {
                try {
                    const activeEl = document.activeElement;
                    if (!activeEl) return false;

                    // 首先確認焦點在 Modal 的編輯器區域內
                    if (!this.editorContainer?.contains(activeEl)) {
                        return false;
                    }

                    // 檢查 1:焦點在 textarea 內
                    if (activeEl.tagName === 'TEXTAREA') {
                        return true;
                    }

                    // 檢查 2:焦點在 contenteditable 元素內
                    if (activeEl.getAttribute('contenteditable') === 'true') {
                        return true;
                    }

                    // 檢查 3:焦點在 CodeMirror 內(EasyMDE、Cherry 使用)
                    // CodeMirror 的焦點通常在 .CodeMirror-code 或其子元素
                    if (activeEl.closest('.CodeMirror')) {
                        return true;
                    }

                    // 檢查 4:焦點在 Vditor 的編輯區域內
                    // Vditor 有三種模式,各有不同的容器
                    if (activeEl.closest('.vditor-sv') ||
                        activeEl.closest('.vditor-ir') ||
                        activeEl.closest('.vditor-wysiwyg')) {
                        return true;
                    }

                    // 檢查 5:焦點在 Toast UI Editor 的編輯區域內
                    // Toast UI 使用 ProseMirror
                    if (activeEl.closest('.ProseMirror') ||
                        activeEl.closest('.toastui-editor-md-container') ||
                        activeEl.closest('.toastui-editor-ww-container')) {
                        return true;
                    }

                    // 檢查 6:通用的 contenteditable 祖先檢查
                    let parent = activeEl.parentElement;
                    while (parent && parent !== this.editorContainer) {
                        if (parent.getAttribute('contenteditable') === 'true') {
                            return true;
                        }
                        parent = parent.parentElement;
                    }

                    return false;
                } catch (e) {
                    // 發生錯誤時,保守起見返回 false(使用我們的 FindReplace)
                    log('_isFocusInEditorEditableArea error:', e.message);
                    return false;
                }
            },

            _bindKeyboardShortcuts() {
                if (this._keyBound) return;
                this._keyBound = true;

                document.addEventListener('keydown', (e) => {
                    // Alt+M global toggle
                    if (e.altKey && (e.key === 'm' || e.key === 'M')) {
                        e.preventDefault();
                        this.toggle();
                        return;
                    }

                    if (!this.isOpen) return;

                    // Escape: close panels -> exit focus -> close
                    if (e.key === 'Escape') {
                        e.preventDefault();

                        if (this._hasOpenMenu) {
                            this.closeAllPanels();
                            return;
                        }

                        const prefs = ToolbarPrefs.load();
                        if (prefs.focusMode) {
                            prefs.focusMode = false;
                            ToolbarPrefs.save(prefs);
                            ToolbarPrefs.applyToModal(this);
                            this._applyStatusMetricVisibility();
                            this.syncThemeButton();
                            this.syncMaximizeButton();
                            Toast.info('已退出專注模式');
                            return;
                        }

                        this.close();
                        return;
                    }

                    // Ctrl/Cmd+S save
                    if ((e.ctrlKey || e.metaKey) && (e.key === 's' || e.key === 'S')) {
                        e.preventDefault();
                        this.saveDraft(false);
                        return;
                    }

                    // F9 maximize
                    if (e.key === 'F9') {
                        e.preventDefault();
                        this.toggleMaximize();
                        return;
                    }

                    // Ctrl/Cmd+O open file
                    if ((e.ctrlKey || e.metaKey) && (e.key === 'o' || e.key === 'O')) {
                        e.preventDefault();
                        this.fileInput?.click();
                        return;
                    }

                    // Ctrl/Cmd+Shift+C copy markdown
                    if ((e.ctrlKey || e.metaKey) && e.shiftKey && (e.key === 'c' || e.key === 'C')) {
                        e.preventDefault();
                        this.copyMD();
                        return;
                    }

                    // Ctrl+F:尋找
                    // 若焦點在編輯器可編輯區域內,讓編輯器自己處理(尊重編輯器原生體驗)
                    if ((e.ctrlKey || e.metaKey) && !e.shiftKey && (e.key === 'f' || e.key === 'F')) {
                        if (this._isFocusInEditorEditableArea()) {
                            // 不攔截,讓編輯器處理
                            log('Ctrl+F: letting editor handle it');
                            return;
                        }
                        e.preventDefault();
                        FindReplace.show(false);
                        return;
                    }

                    // Ctrl+H:尋找與取代
                    // 若焦點在編輯器可編輯區域內,讓編輯器自己處理
                    if ((e.ctrlKey || e.metaKey) && !e.shiftKey && (e.key === 'h' || e.key === 'H')) {
                        if (this._isFocusInEditorEditableArea()) {
                            // 不攔截,讓編輯器處理
                            log('Ctrl+H: letting editor handle it');
                            return;
                        }
                        e.preventDefault();
                        FindReplace.show(true);
                        return;
                    }

                    // ===== 插槽快捷鍵 =====

                    // Ctrl/Cmd + 1-9: 載入對應插槽
                    if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key >= '1' && e.key <= '9') {
                        const slot = parseInt(e.key);
                        const settings = QuickSlots.getSettings();

                        // 只有在插槽啟用範圍內才響應
                        if (slot <= settings.enabledCount) {
                            e.preventDefault();

                            if (QuickSlots.isSlotEmpty(slot)) {
                                Toast.info(`插槽 ${slot} 為空`);
                            } else {
                                this._slotLoad(slot);
                            }
                        }
                        return;
                    }

                    // Ctrl/Cmd + Shift + 1-9: 儲存到對應插槽
                    if ((e.ctrlKey || e.metaKey) && e.shiftKey && !e.altKey && e.key >= '1' && e.key <= '9') {
                        const slot = parseInt(e.key);
                        const settings = QuickSlots.getSettings();

                        // 只有在插槽啟用範圍內才響應
                        if (slot <= settings.enabledCount) {
                            e.preventDefault();
                            this._slotSave(slot);
                        }
                        return;
                    }
                });
            },

            // ============
            // Vditor safe switch / snapshot actions
            // ============

            async handleVditorSafeSwitch(mode) {
                const info = EditorManager.getCurrentInfo();
                if (info?.key !== 'vditor') {
                    Toast.info('請先切換到 Vditor');
                    return;
                }

                const modeKey = (mode === 'wysiwyg') ? 'wysiwyg' : (mode === 'ir' ? 'ir' : 'sv');
                const currentContent = EditorManager.getValue();
                const adapter = info.adapter;

                // 若目前在 sv,盡量保存 sv 快照(與適配器策略一致)
                try { adapter?._saveSVSnapshot?.('pre-safe-switch'); } catch (e) { /* ignore */ }

                // persist snapshot + draft
                Utils.storage.set(CONFIG.storageKeys.vditorSnapshot, currentContent);
                Utils.storage.set(CONFIG.storageKeys.content, currentContent);

                // create backup (manual)
                BackupManager.create(currentContent, {
                    editorKey: 'vditor',
                    mode: adapter?._detectModeFromDOM?.(),
                    manual: true
                });

                // mode preference + safe reinit
                Utils.storage.set(CONFIG.storageKeys.editorMode, modeKey);
                Utils.storage.set(CONFIG.storageKeys.vditorSafeReinitFlag, true);

                Toast.info(`正在安全切換至 ${modeKey.toUpperCase()} 模式...`, 2000);

                setTimeout(async () => {
                    await this.switchEditor('vditor');

                    setTimeout(() => {
                        const afterContent = EditorManager.getValue();
                        const beforeLen = currentContent.replace(/\s/g, '').length;
                        const afterLen = afterContent.replace(/\s/g, '').length;

                        if (beforeLen > 50 && afterLen < beforeLen * 0.7) {
                            Toast.error(`⚠️ 切換後偵測到內容異常(${beforeLen} → ${afterLen} 字)。正在自動還原...`, 5000);
                            EditorManager.setValue(currentContent);
                        } else {
                            Toast.success(`已切換至 ${modeKey.toUpperCase()} 模式`, 2500);
                        }
                    }, 600);
                }, 120);
            },

            vditorRestoreSnapshot() {
                const info = EditorManager.getCurrentInfo();
                if (info?.key !== 'vditor') return Toast.info('只有在 Vditor 時可用');
                info.adapter?.restoreLastSnapshot?.();
            },

            vditorDownloadSnapshot() {
                const info = EditorManager.getCurrentInfo();
                if (info?.key !== 'vditor') return Toast.info('只有在 Vditor 時可用');

                const ok = info.adapter?.downloadLastSnapshot?.();
                Toast[ok ? 'success' : 'warning'](ok ? '已下載快照' : '沒有可下載快照');
            }
        });

        // 包裝 init(只做一次)
        Modal._wrapInit10B();

        // 如果 Modal 已經初始化,補充調用 advanced 初始化
        if (Modal._inited && !Modal._seg10BBound) {
            Modal._initAdvancedOnce();
        }
    })();

    // ========================================
    // [SEGMENT_10C]
    // Global init / GM menu / unload / error / scheduleInit
    // ========================================

    /**
     * 初始化腳本(單一入口)
     *
     * 設計意圖(尊重原團隊):
     * - 初始化順序集中管理,避免散落到 Modal 或其他模組造成重複與責任不清
     * - userscript 跨站執行,需有 init guard 與 body ready 容錯
     */
    async function init() {
        if (window.__MME_INITED__) return;
        window.__MME_INITED__ = true;

        console.log(`[Multi Markdown Editor] Initializing v${SCRIPT_VERSION}...`);

        try {
            // 某些頁面在 document-idle 下 body 仍可能延後出現
            if (!document.body) {
                try {
                    await Utils.waitFor(() => !!document.body, 2000, 50);
                } catch (e) {
                    // 若仍未出現 body,仍嘗試繼續(避免卡死)
                }
            }

            // 初始化順序:Theme → Styles → Toast → Portal → EnhanceUI → FAB → Modal
            Theme.init();
            Styles.init();
            Toast.init();
            Portal.init();
            EnhanceUI.apply();

            FAB.create();
            DragDropManager.init(); // 初始化拖曳導入
            Modal.init();

            // GM menu / unload / error
            registerMenuCommands();
            setupUnloadProtection();
            setupErrorHandling();

            // 首次使用提示
            if (!Utils.storage.get(CONFIG.storageKeys.welcomed)) {
                setTimeout(() => {
                    Toast.info('按 Alt+M 或點擊右下角按鈕開啟編輯器', 6000);
                    Utils.storage.set(CONFIG.storageKeys.welcomed, true);
                }, 1000);
            }

            // DEBUG:暴露到全域(團隊診斷用)
            if (DEBUG) {
                window.__MME_DEBUG__ = {
                    CONFIG,
                    Utils,
                    Theme,
                    Styles,
                    Portal,
                    Toast,
                    Loader,
                    BackupManager,
                    VditorDiag,
                    PerfMonitor,
                    EditorManager,
                    EditorAdapters,
                    ToolbarPrefs,
                    EnhanceUI,
                    FAB,
                    Modal,

                    // 便捷診斷方法
                    diagnose() {
                        console.group('[MME] 系統診斷');
                        console.log('版本:', SCRIPT_VERSION);
                        console.log('主題:', Theme.get());
                        console.log('編輯器:', EditorManager.currentEditor);
                        console.log('Modal 狀態:', Modal.isOpen ? '開啟' : '關閉');
                        console.log('備份統計:', BackupManager.getStats());
                        console.log('插槽統計:', QuickSlots.getStats());
                        console.log('儲存空間:', Utils.storage.estimateUsage());
                        console.log('效能統計:', PerfMonitor.getReport());
                        console.groupEnd();
                    }
                };
                console.log('[MME] Debug mode enabled. window.__MME_DEBUG__ is available.');
            }

            console.log('[Multi Markdown Editor] Ready!');
        } catch (err) {
            console.error('[Multi Markdown Editor] Init failed:', err);
        }
    }

    /**
     * 註冊 GM 選單命令
     *
     * 設計意圖(尊重原團隊):
     * - 提供 FAB 以外的入口
     * - 提供救援/維護操作
     */
    function registerMenuCommands() {
        try {
            if (typeof GM_registerMenuCommand !== 'function') return;

            GM_registerMenuCommand('🖊️ 開啟/關閉編輯器', () => Modal.toggle());

            GM_registerMenuCommand('🎨 切換主題', () => {
                const newTheme = Theme.toggle();
                Toast.info(`已切換到${newTheme === 'dark' ? '深色' : '淺色'}主題`);
                Modal.syncThemeButton?.();
                EditorManager.setTheme(Theme.get());
            });

            GM_registerMenuCommand('💾 備份管理', () => {
                if (Modal.isOpen) {
                    Modal.showBackupPanel();
                } else {
                    Modal.open().then(() => setTimeout(() => Modal.showBackupPanel(), 300));
                }
            });

            GM_registerMenuCommand('💾 快速存檔插槽', () => {
                if (Modal.isOpen) {
                    Modal.showSlotsPanel();
                } else {
                    Modal.open().then(() => setTimeout(() => Modal.showSlotsPanel(), 300));
                }
            });

            GM_registerMenuCommand('⚙️ 偏好設定(工具列/狀態列)', () => {
                if (Modal.isOpen) {
                    Modal.showSettingsPanel();
                } else {
                    Modal.open().then(() => setTimeout(() => Modal.showSettingsPanel(), 300));
                }
            });

            GM_registerMenuCommand('🧯 Vditor:還原快照', () => {
                const info = EditorManager.getCurrentInfo();
                if (info?.key === 'vditor') info.adapter?.restoreLastSnapshot?.();
                else Toast.info('請先切換到 Vditor');
            });

            GM_registerMenuCommand('⬇️ Vditor:下載快照', () => {
                const info = EditorManager.getCurrentInfo();
                if (info?.key === 'vditor') {
                    const ok = info.adapter?.downloadLastSnapshot?.();
                    Toast[ok ? 'success' : 'warning'](ok ? '已下載快照' : '沒有可下載快照');
                } else {
                    Toast.info('請先切換到 Vditor');
                }
            });

            GM_registerMenuCommand('📊 Vditor:診斷報告(輸出到控制台)', () => {
                VditorDiag.printReport();
                Toast.info('診斷報告已輸出到控制台 (F12)');
            });

            GM_registerMenuCommand('🔄 重置拖曳提示', () => {
                DragDropManager.resetHintCount();
                Toast.info('拖曳導入提示已重置,下次開啟編輯器時會再次顯示');
            });

            GM_registerMenuCommand('📍 重置按鈕位置', () => {
                Utils.storage.remove(CONFIG.storageKeys.buttonPos);
                location.reload();
            });

            GM_registerMenuCommand('⚙️ 重置工具列設定', () => {
                ToolbarPrefs.save(ToolbarPrefs.defaultPrefs());
                if (Modal.isOpen) {
                    ToolbarPrefs.applyToModal(Modal);
                    Modal._applyStatusMetricVisibility?.();
                    Modal.syncThemeButton?.();
                    Modal.syncMaximizeButton?.();
                }
                Toast.info('已重置工具列設定');
            });

            GM_registerMenuCommand('🗑️ 清除所有備份', () => {
                if (confirm('確定要清除所有備份嗎?此操作無法復原。')) {
                    BackupManager.clearAll();
                    Toast.info('已清除所有備份');
                }
            });

            GM_registerMenuCommand('🔄 重置所有設定(不清草稿/備份)', () => {
                if (!confirm('確定要重置所有設定嗎?\n\n這將清除:主題、編輯器偏好、視窗位置、工具列設定等。\n不會清除:草稿與備份。\n\n頁面將重新載入。')) {
                    return;
                }
                const keys = CONFIG.storageKeys;
                [
                    keys.theme,
                    keys.editor,
                    keys.editorMode,
                    keys.buttonPos,
                    keys.modalSize,
                    keys.modalPos,
                    keys.toolbarCfg,
                    keys.focusMode,
                    keys.welcomed,
                    keys.locale
                ].forEach(k => Utils.storage.remove(k));

                Toast.info('設定已重置,頁面將重新載入');
                setTimeout(() => location.reload(), 900);
            });

        } catch (e) {
            console.warn('[MME] Failed to register menu commands:', e);
        }
    }

    /**
     * 頁面卸載保護:離頁/切換分頁時保存草稿,並在離頁時嘗試建立備份
     *
     * 設計意圖(尊重原團隊):
     * - 盡可能保護使用者內容
     * - 不在 beforeunload 做重 UI/互動(避免阻塞)
     */
    function setupUnloadProtection() {
        window.addEventListener('beforeunload', () => {
            try {
                if (Modal.isOpen && EditorManager.isReady()) {
                    const content = EditorManager.getValue();
                    if (content && content.trim()) {
                        Utils.storage.set(CONFIG.storageKeys.content, content);
                        Utils.storage.set(CONFIG.storageKeys.lastSaveTime, Date.now());

                        const info = EditorManager.getCurrentInfo();
                        BackupManager.create(content, {
                            editorKey: info?.key,
                            mode: info?.adapter?._detectModeFromDOM?.(),
                            manual: false
                        });
                    }
                }
            } catch (e) {
                // 避免 beforeunload 中 throw
            }
        });

        document.addEventListener('visibilitychange', () => {
            if (!document.hidden) return;

            try {
                if (Modal.isOpen && EditorManager.isReady()) {
                    const content = EditorManager.getValue();
                    if (content && content.trim()) {
                        Utils.storage.set(CONFIG.storageKeys.content, content);
                        Utils.storage.set(CONFIG.storageKeys.lastSaveTime, Date.now());
                    }
                }
            } catch (e) {
                // ignore
            }
        });
    }

    /**
     * 全域錯誤處理:在“可能與腳本相關”時,做最後保底保存
     *
     * 設計意圖(尊重原團隊):
     * - userscript 跨站運行,頁面本身錯誤很多,不宜過度介入
     * - 但對使用者資料,必要時仍應做最後保護
     */
    function setupErrorHandling() {
        window.addEventListener('error', (e) => {
            try {
                const maybeOurScript =
                    (typeof e?.filename === 'string' && e.filename.includes('userscript')) ||
                    (typeof e?.message === 'string' && e.message.includes('MME'));

                if (maybeOurScript) {
                    console.error('[MME] Uncaught error:', e.error || e.message);
                }

                // 即使不確定是否為本腳本,也做輕量草稿保存(不做 Toast)
                if (Modal.isOpen && EditorManager.isReady()) {
                    const content = EditorManager.getValue();
                    if (content && content.trim()) {
                        Utils.storage.set(CONFIG.storageKeys.content, content);
                        Utils.storage.set(CONFIG.storageKeys.lastSaveTime, Date.now());
                    }
                }
            } catch (saveErr) {
                console.error('[MME] Failed to save on error:', saveErr);
            }
        });

        window.addEventListener('unhandledrejection', (e) => {
            try {
                if (DEBUG) {
                    console.error('[MME] Unhandled promise rejection:', e.reason);
                }
            } catch (err) { /* ignore */ }
        });
    }

    /**
     * 延遲初始化:requestIdleCallback 優先
     */
    function scheduleInit() {
        const doInit = () => init();

        if (typeof requestIdleCallback === 'function') {
            requestIdleCallback(doInit, { timeout: 2000 });
        } else {
            setTimeout(doInit, 100);
        }
    }

    // boot
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        scheduleInit();
    } else {
        document.addEventListener('DOMContentLoaded', scheduleInit);
    }

})();