Twitter / X — Media Copy & Download

Adds one-click copy/download buttons for tweet media & links, and can restore legacy media grid. Includes a download history panel with text bookmarks, custom groups, drag-to-reorder, cross-item lightbox, custom filename templates, avatar /media shortcuts, and dark/light/custom themes. More options in settings.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Twitter / X — Media Copy & Download
// @name:zh-TW   Twitter / X — 媒體複製與下載
// @name:zh-CN   Twitter / X — 媒体复制与下载
// @name:ja      Twitter / X — メディアコピー & ダウンロード
// @name:ko      Twitter / X — 미디어 복사 & 다운로드
// @name:es      Twitter / X — Copiar y Descargar Medios
// @name:pt-BR   Twitter / X — Copiar e Baixar Mídia
// @name:fr      Twitter / X — Copier & Télécharger les Médias
// @name:ru      Twitter / X — Копирование и загрузка медиа
// @namespace    https://greasyfork.org/en/users/1575945-star-tanuki07
// @version      3.1.2.8
// @homepageURL  https://github.com/Startanuki07
// @license      MIT
// @author       Star_tanuki07
// @icon         https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @match        https://twitter.com/*
// @match        https://x.com/*
// @grant        GM_setClipboard
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @connect      twitter.com
// @connect      x.com
// @connect      twimg.com
// @run-at       document-start
// @description      Adds one-click copy/download buttons for tweet media & links, and can restore legacy media grid. Includes a download history panel with text bookmarks, custom groups, drag-to-reorder, cross-item lightbox, custom filename templates, avatar /media shortcuts, and dark/light/custom themes. More options in settings.
// @description:zh-TW 為推文的媒體與連結新增一鍵複製分享連結與下載媒體按鈕,並可還原個人頁面的舊版媒體網格版面。提供下載歷史面板,包含文字書籤、自訂群組、拖曳排序、跨項目燈箱瀏覽、自訂檔名樣板、頭像 /media 捷徑,以及深色/淺色/自訂主題選項。部分進階功能未列出,可於設定面板開啟使用。
// @description:zh-CN 为推文的媒体与链接新增一键复制分享链接与下载媒体按钮,并可还原个人页面的旧版媒体网格版面。提供下载历史面板,包含文字书签、自定义分组、拖拽排序、跨项目灯箱浏览、自定义文件名模板、头像 /media 快捷入口,以及深色/浅色/自定义主题选项。部分进阶功能未列出,可于设置面板开启使用。
// @description:ja    ツイートのメディアとリンクにワンクリックでコピー&ダウンロードボタンを追加し、プロフィールページの旧メディアグリッドレイアウトも復元可能。テキストブックマーク、カスタムグループ、ドラッグ&ドロップでの並べ替え、複数アイテムのライトボックス表示、カスタムファイル名テンプレート、アバターの /media ショートカット、ダーク/ライト/カスタムテーマなどを備えたダウンロード履歴パネルを提供。詳細設定は設定パネルから利用可能。
// @description:ko    트윗의 미디어와 링크에 원클릭 복사 및 다운로드 버튼을 추가하고, 프로필 페이지의 레거시 미디어 그리드 레이아웃도 복원할 수 있습니다. 텍스트 북마크, 사용자 지정 그룹, 드래그 앤 드롭 정렬, 다중 항목 라이트박스 탐색, 사용자 지정 파일명 템플릿, 아바타 /media 바로가기, 다크/라이트/사용자 지정 테마를 포함한 다운로드 기록 패널을 제공합니다. 추가 고급 옵션은 설정 패널에서 이용할 수 있습니다.
// @description:es    Añade botones de copia y descarga con un clic para medios y enlaces de tweets, y permite restaurar la cuadrícula de medios clásica del perfil. Incluye panel de historial con marcadores de texto, grupos personalizados, reordenación por arrastre, lightbox entre elementos, plantillas de nombre de archivo, accesos directos /media desde el avatar y temas oscuro/claro/personalizado.
// @description:pt-BR Adiciona botões de copiar e baixar com um clique para mídias e links de tweets, e permite restaurar a grade de mídia clássica do perfil. Inclui painel de histórico com marcadores de texto, grupos personalizados, reordenação por arrastar, lightbox entre itens, modelos de nome de arquivo, atalhos /media pelo avatar e temas escuro/claro/personalizado.
// @description:fr    Ajoute des boutons de copie et de téléchargement en un clic pour les médias et liens des tweets, et permet de restaurer l'ancienne grille de médias du profil. Inclut un panneau d'historique avec signets texte, groupes personnalisés, réorganisation par glisser-déposer, visionneuse entre éléments, modèles de nom de fichier, raccourcis /media depuis l'avatar et thèmes sombre/clair/personnalisé.
// @description:ru    Добавляет кнопки копирования и скачивания в один клик для медиа и ссылок твитов, а также может восстановить старую сетку медиа в профиле. Панель истории с текстовыми закладками, группами, сортировкой перетаскиванием, лайтбоксом между элементами, именами файлов по шаблону, ярлыками /media из аватара, тёмной/светлой/пользовательской темой. Больше опций в настройках.
// ==/UserScript==

(() => {
    "use strict";
    try {
        if (typeof GM_getValue !== "function") return;
        if (!GM_getValue('app_legacy_media_grid', false)) return;

        const PROFILE_REDESIGN_FLAG = "responsive_web_profile_redesign_enabled";
        const MEDIA_CAROUSEL_FLAG   = "rweb_media_carousel_enabled";

        function _turnOffGridFlags(config) {
            if (!config) return;
            [PROFILE_REDESIGN_FLAG, MEDIA_CAROUSEL_FLAG].forEach((name) => {
                const flag = config[name];
                if (flag && typeof flag === "object") flag.value = false;
            });
        }

        function disableMediaRedesign(state) {
            const fs = state && state.featureSwitch;
            if (!fs) return state;
            _turnOffGridFlags(fs.defaultConfig);
            _turnOffGridFlags(fs.user && fs.user.config);
            return state;
        }

        const _pageWindow = (typeof unsafeWindow !== 'undefined' && unsafeWindow) ? unsafeWindow : window;

        const _stateDescriptor = Object.getOwnPropertyDescriptor(_pageWindow, '__INITIAL_STATE__');
        let _gridState;
        if (_stateDescriptor && 'value' in _stateDescriptor) {
            _gridState = disableMediaRedesign(_stateDescriptor.value);
        }

        Object.defineProperty(_pageWindow, '__INITIAL_STATE__', {
            configurable: true,
            enumerable: true,
            get() { return _gridState; },
            set(value) { _gridState = disableMediaRedesign(value); }
        });
    } catch {
    }
})();

(function () {
    'use strict';

    const _escHtml = s => String(s)
        .replace(/&/g,  '&')
        .replace(/</g,  '&lt;')
        .replace(/>/g,  '&gt;')
        .replace(/"/g,  '&quot;')
        .replace(/'/g,  '&#39;');

    const _log = (tag, msg, ...rest) => {
        try { console.warn(`[${tag}]`, msg, ...rest); } catch (_) {  }
    };

    const GALLERY_PANEL_WIDTH = 212;

    function _convertTweetUrlDomain(tweetUrl) {
        const _dom = GM_getValue(KEY_CLICK_MODE_CUSTOM, false)
            ? GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com') : 'x.com';
        try { const o = new URL(tweetUrl); o.hostname = _dom; return o.toString(); } catch(_) { return tweetUrl; }
    }

    function _buildPrefixedTweetUrl(tweetUrl) {
        const tUrl = _convertTweetUrlDomain(tweetUrl);
        const prefix = GM_getValue(KEY_PREFIX_TEXT, '[text]');
        return `${prefix}(${tUrl})`;
    }

    let _syncCh = null;
    let _startScanInterval = null;
    let scanAndInsert = null;
    let _gridBtnFeedback = null;
    let insertGridMediaButtons = null;
    let _processAvatarBatch = null;
    let insertAvatarMediaButtons = null;
    let _tfRegistry = null;
    const _GROUP_LIST_TRIGGER_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="4" cy="6" r="1.4" fill="currentColor" stroke="none"/><line x1="9" y1="6" x2="21" y2="6"/><circle cx="4" cy="12" r="1.4" fill="currentColor" stroke="none"/><line x1="9" y1="12" x2="21" y2="12"/><circle cx="4" cy="18" r="1.4" fill="currentColor" stroke="none"/><line x1="9" y1="18" x2="21" y2="18"/></svg>';
    const StarPipState = {
        pendingGroupRecordId: null,
        pendingStarPipEl:     null,
        autoHideTimer:        null,
        fanNodes:             [],
        fanOpen:              false,
        selectedIconId:       null,
        escaping:             false,
        pendingIsText:        false,
        textMenuOpen:         false,
        textMenuAnchorEl:     null,
    };

    function _readMonthRecords(ym) {
        try { return JSON.parse(GM_getValue(KEY_HISTORY_PREFIX + ym.replace('.', '_'), '[]')); }
        catch (e) { _log('TMHistory', '月份桶 JSON 解析失敗,視為空陣列', ym, e); return []; }
    }
    function _writeMonthRecords(ym, records) {
        GM_setValue(KEY_HISTORY_PREFIX + ym.replace('.', '_'), JSON.stringify(records));
    }
    function _getHistoryIndex() {
        try { return JSON.parse(GM_getValue(KEY_HISTORY_INDEX, '[]')); }
        catch (e) { _log('TMHistory', '歷史索引 JSON 解析失敗,視為空陣列', e); return []; }
    }
    function _updateHistoryIndex(ym) {
        const idx = _getHistoryIndex();
        if (!idx.includes(ym)) {
            idx.unshift(ym); idx.sort().reverse();
            GM_setValue(KEY_HISTORY_INDEX, JSON.stringify(idx));
        }
    }
    function _loadAllRecords() {
        return _getHistoryIndex().flatMap(ym => {
            try { return _readMonthRecords(ym); }
            catch (e) { _log('TMHistory', '_loadAllRecords 讀取月份失敗,略過', ym, e); return []; }
        });
    }

    function _casMutateMonth(ym, mutateFn, maxRetries = 5) {
        const storageKey = KEY_HISTORY_PREFIX + ym.replace('.', '_');
        let recs, ret;
        for (let attempt = 0; attempt < maxRetries; attempt++) {
            const rawBefore = GM_getValue(storageKey, '[]');
            try { recs = JSON.parse(rawBefore); } catch (_) { recs = []; }
            ret = mutateFn(recs);
            const rawAfter = GM_getValue(storageKey, '[]');
            const isLastAttempt = (attempt === maxRetries - 1);
            if (rawAfter === rawBefore || isLastAttempt) {
                if (rawAfter !== rawBefore) {
                    _log('TMHistory', '_casMutateMonth 重試耗盡,強制寫入最後一次結果(可能覆蓋另一分頁的變更)', ym);
                }
                GM_setValue(storageKey, JSON.stringify(recs));
                return ret;
            }
        }
    }

    function _updateRecord(id, mutateFn) {
        for (const ym of _getHistoryIndex()) {
            const recs = _readMonthRecords(ym);
            const i = recs.findIndex(r => r.id === id);
            if (i === -1) continue;
            return _casMutateMonth(ym, freshRecs => {
                const fi = freshRecs.findIndex(r => r.id === id);
                if (fi === -1) return null;
                mutateFn(freshRecs[fi]);
                return freshRecs[fi];
            });
        }
        return null;
    }

    function _deleteRecordById(id) {
        for (const ym of _getHistoryIndex()) {
            const recs = _readMonthRecords(ym);
            const i = recs.findIndex(r => r.id === id);
            if (i === -1) continue;
            return _casMutateMonth(ym, freshRecs => {
                const fi = freshRecs.findIndex(r => r.id === id);
                if (fi === -1) return null;
                const [record] = freshRecs.splice(fi, 1);
                return { record };
            });
        }
        return null;
    }

    function _insertRecordUndo(record) {
        const ym = record.yyyymm ||
            (record.ts ? new Date(record.ts).toISOString().slice(0, 7).replace('-', '.') : '9999.99');
        _updateHistoryIndex(ym);
        _casMutateMonth(ym, freshRecs => {
            const pos = freshRecs.findIndex(r => r.id <= record.id);
            if (pos === -1) freshRecs.push(record); else freshRecs.splice(pos, 0, record);
        });
    }

    function _batchDeleteRecords(idsToDelete) {
        const deleted = [];
        for (const ym of _getHistoryIndex()) {
            const recs = _readMonthRecords(ym);
            const hasTarget = recs.some(r => idsToDelete.has(r.id) && !r.favorited);
            if (!hasTarget) continue;
            const deletedThisMonth = _casMutateMonth(ym, freshRecs => {
                const thisAttemptDeleted = [];
                const kept = freshRecs.filter(r => {
                    if (!idsToDelete.has(r.id) || r.favorited) return true;
                    thisAttemptDeleted.push(r.tweetId);
                    return false;
                });
                freshRecs.length = 0;
                freshRecs.push(...kept);
                return thisAttemptDeleted;
            });
            if (deletedThisMonth) deleted.push(...deletedThisMonth);
        }
        return deleted;
    }

    function _mutateMatchingRecords(predicate, mutateFn) {
        for (const ym of _getHistoryIndex()) {
            const recs = _readMonthRecords(ym);
            if (!recs.some(predicate)) continue;
            _casMutateMonth(ym, freshRecs => {
                freshRecs.forEach(r => { if (predicate(r)) mutateFn(r); });
            });
        }
    }

    function _mergeImportedRecords(importedArr) {
        const existing = _loadAllRecords();
        const existMap = new Map(existing.map(r => [r.tweetId, r]));
        let addCount = 0;
        importedArr.forEach(r => {
            if (!r.tweetId) return;
            if (!existMap.has(r.tweetId)) addCount++;
            if (!r.yyyymm && r.ts)
                r.yyyymm = new Date(r.ts).toISOString().slice(0, 7).replace('-', '.');
            r.yyyymm = r.yyyymm || '9999.99';
            existMap.set(r.tweetId, r);
        });
        const byMonth = {};
        existMap.forEach(r => { (byMonth[r.yyyymm] = byMonth[r.yyyymm] || []).push(r); });
        Object.entries(byMonth).forEach(([ym, recs]) => {
            recs.sort((a, b) => (b.id || 0) - (a.id || 0));
            _updateHistoryIndex(ym);
            _writeMonthRecords(ym, recs);
        });
        return addCount;
    }

    function _mergeImportedGroups(importedGroups, getFn, saveFn) {
        if (!Array.isArray(importedGroups) || importedGroups.length === 0) return 0;
        const existing = getFn();
        const existIds = new Set(existing.map(g => g.id));
        let addCount = 0;
        importedGroups.forEach(g => {
            if (!g || !g.id || existIds.has(g.id)) return;
            existing.push(g);
            existIds.add(g.id);
            addCount++;
        });
        if (addCount > 0) saveFn(existing);
        return addCount;
    }

    const KEY_PREFIX_TEXT = 'discord_prefix_text';
    const KEY_LANG = 'app_language';
    const KEY_LINK_DOMAIN_CLICK = 'app_link_domain_click';
    const KEY_CLICK_MODE_CUSTOM = 'app_link_click_mode_custom';
    const KEY_DATE_FORMAT = 'app_date_format';
    const KEY_CUSTOM_LANG = 'app_custom_lang_json';
    const KEY_VIDEO_VOLUME = 'app_video_volume';
    const KEY_ONBOARDING_DONE  = 'app_onboarding_done';
    const KEY_FEEDBACK_STYLE   = 'app_feedback_style';
    const KEY_SEEN_FEATURES    = 'app_seen_features';
    const KEY_SP_GROUP_OPEN    = 'app_sp_group_open';
    const KEY_GEAR_DOT_SEEN    = 'app_gear_dot_seen';
    const KEY_HISTORY_RECORDS   = 'app_history_records';
    const KEY_HISTORY_PANEL_POS = 'app_history_panel_pos';
    const KEY_HISTORY_VIEW_MODE = 'app_history_view_mode';
    const KEY_HIST_PINNED       = 'app_hist_pinned';
    const KEY_DOCK_STYLE        = 'app_dock_style';
    const KEY_GROUP_POPUP_STYLE = 'app_group_popup_style';
    const KEY_DOCK_HOVER_DELAY  = 'app_dock_hover_delay';
    const KEY_DOCK_TRIGGER_L    = 'app_dock_trigger_l';
    const KEY_DOCK_TRIGGER_R    = 'app_dock_trigger_r';
    const KEY_DOCK_PERSISTED    = 'app_dock_persisted';
    const KEY_DOCK_REMEMBER_POS = 'app_dock_remember_pos';
    const KEY_GROUP_ON_DOWNLOAD = 'app_group_on_download';
    const KEY_CUSTOM_FILENAME_ENABLED  = 'app_custom_filename_enabled';
    const KEY_CUSTOM_FILENAME_TEMPLATE = 'app_custom_filename_template';
    const CUSTOM_FN_TEXT_MAXLEN = 20;
    const KEY_GROUPS            = 'app_media_groups';
    const KEY_TEXT_GROUPS       = 'app_text_groups';
    const KEY_GROUP_PANEL_CFG   = 'app_group_panel_cfg';
    const KEY_TEXT_BM_CFG       = 'app_text_bm_cfg';
    const KEY_GEAR_VISIBLE      = 'app_gear_visible';
    const KEY_GEAR_DISPLAY_MODE = 'app_gear_display_mode';
    const KEY_GEAR_CORNER       = 'app_gear_corner';
    const KEY_HIST_CLEANUP_NOTIFIED = 'app_hist_cleanup_notified';
    const KEY_CLICK_MODE        = 'app_click_mode';
    const KEY_CUSTOM_MEDIA_ACTIONS = 'app_custom_media_actions';
    const DEFAULT_MEDIA_ACTIONS  = { click: 'copy', longpress: 'copy_prefix', mclick: 'preview', rclick: 'download_all' };
    const KEY_CUSTOM_LINK_ACTIONS = 'app_custom_link_actions';
    const DEFAULT_LINK_ACTIONS    = { click: 'copy_url', longpress: 'copy_prefix_url', mclick: 'none', rclick: 'save_text_bookmark' };
    const KEY_MEDIA_ICON_STYLE  = 'app_media_icon_style';
    const KEY_LINK_ICON_STYLE   = 'app_link_icon_style';
    const KEY_SEARCH_HISTORY    = 'app_search_hist';
    const KEY_VIDEO_SPEED       = 'app_video_speed';
    const KEY_CUSTOM_BEARER     = 'app_custom_bearer';
    const KEY_SCAN_INTERVAL     = 'app_scan_interval';
    const KEY_GRID_MEDIA_BTN    = 'app_grid_media_btn';
    const KEY_AVATAR_MEDIA_BTN  = 'app_avatar_media_btn';
    const KEY_APP_THEME         = 'app_theme';
    const KEY_THEME_SYNC        = 'app_theme_sync';
    const KEY_APP_THEME_HISTORY = 'app_theme_history';
    const KEY_CUSTOM_THEME_ENABLED = 'app_custom_theme_enabled';
    const KEY_CUSTOM_THEME_COLORS  = 'app_custom_theme_colors';

    const KEY_TF_CSS_SIMPLIFY = 'app_tf_css_simplify';
    const KEY_TF_FEED_PRUNE   = 'app_tf_feed_prune';
    const KEY_LEGACY_MEDIA_GRID = 'app_legacy_media_grid';
    const DEFAULT_LEGACY_GRID_CONFIRM_MSG =
        'Legacy Media Grid {state}.<br><br>' +
        '<span style="color:#e0245e"><b>⚠️ Note:</b> If you use Tampermonkey (stable), this feature may not work.</span><br>' +
        'For reliable results, please use <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>, ' +
        '<span style="color:#e0245e"><b>ScriptCat</b></span>, <span style="color:#e0245e"><b>Violentmonkey</b></span>, or other managers.<br><br>' +
        'Reload page now to apply?';
    const KEY_HISTORY_PREFIX    = 'app_history_m_';
    const KEY_HISTORY_INDEX     = 'app_history_index';

    const NEW_FEATURE_IDS = [
        'history_panel',
        'dock_style',
        'sp_feedback_picker',
        'sp_date_picker',
        'sp_dock_picker',
        'sp_trigger_dist',
        'sp_slider_controls',
        'sp_dock_persist',
        'sp_group_on_dl',
        'sp_group_glow_color',
        'sp_group_glow_size',
        'sp_group_label_size',
        'sp_group_icon_size',
        'sp_group_popup_style',
        'sp_group_text_color',
        'sp_corner_position',
        'sp_feedback_pulse',
        'sp_feedback_flash',
        'sp_feedback_slide',
        'right_click_tip',
        'click_mode_menu',
        'custom_media_actions',
        'pt_css_simplify',
        'pt_feed_prune',
        'grid_media_btn',
        'avatar_media_btn',
        'custom_filename_toggle',
        'sp_theme_sync',
        'corner_buttons_modes',
        'sp_media_icon_style',
        'sp_link_icon_style',
        'legacy_media_grid',
    ];

    const DOMAIN_LIST = [
        "vxtwitter.com",
        "fixupx.com",
        "fxtwitter.com",
        "cunnyx.com",
        "fixvx.com",
        "twitter.com",
        "x.com"
    ];

    const TR = {
        'en': {
            langName: 'English',
            menu_domain_click: '🔗 Set "Single-Click" Behavior',
            menu_domain_long: '🔗 Set "Long-Press" Domain',
            menu_prefix: '⚙️ Set Custom Prefix (Discord)',
            menu_lang: '🌐 Change Language',
            menu_help: '📖 Help / Manual',
            menu_corner_btns: '👁 Corner Buttons',
            prompt_prefix: 'Enter custom prefix (e.g., [text]):',
            prompt_lang: 'Select language (enter number):\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: 'Select domain (Enter Number):\n',
            status_default: 'Default (x.com)',
            status_custom: 'Custom',
            btn_tooltip: 'Left Click: Copy Media Links\nMiddle Click: Preview Video / Image Lightbox\nRight Click: Download Files',
            btn_tooltip_menu: 'Click: Open Action Menu\n(Copy / Copy with Prefix / Preview / Download All)',
            sp_grid_media_btn_label: 'Media Grid Buttons',
            sp_grid_media_btn_desc: 'Show copy/preview/download buttons on /media page thumbnails',
            grid_btn_copy_tip: 'Copy media link (long-press to copy with prefix, or menu if multiple)',
            grid_btn_share_tip: 'Copy tweet link (long-press to copy with prefix)',
            grid_btn_preview_tip: 'Preview in lightbox',
            grid_btn_download_tip: 'Download media (long-press for menu)',
            toast_grid_media_copied: 'Link copied!',
            toast_grid_media_saved: 'Media saved!',
            grid_menu_item_image: 'Image {n}',
            grid_menu_item_video: 'Video {n}',
            avatar_media_btn_tip: 'Go to /media page',
            sp_avatar_media_btn_label: 'Avatar Media Shortcut',
            sp_avatar_media_btn_desc: 'Show a subtle badge on timeline avatars to open that user\'s /media page',
            sp_custom_filename_label: 'Custom Filename',
            cfn_input_tip: 'Available tokens: {screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\nNote: {ext} does not include the dot — write .{ext} if you need one. {origName} is the original Twitter CDN filename code (e.g. HP_yBXibUAA2-Qu), without extension, empty if unavailable. {text} is truncated but may still make filenames long.',
            cfn_preview_label: 'Preview',
            cfn_preview_empty: '(empty template — falls back to default format)',
            cfn_reset_toast: '↺ Filename template reset to default',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: 'Handle',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: 'Display name',
            cfn_token_date: '{date}',
            cfn_token_date_desc: 'Date',
            cfn_token_id: '{id}',
            cfn_token_id_desc: 'Tweet ID',
            cfn_token_index: '{index}',
            cfn_token_index_desc: 'Index',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: 'Extension',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: 'Original name',
            cfn_token_text: '{text}',
            cfn_token_text_desc: 'Tweet text',
            cfn_token_text_warn_tip: 'Tweet text is unpredictable in length. Even truncated to 20 characters here, combining with other tokens may still produce an overly long filename.',
            sp_collapseall_expand_tip: 'Expand all groups',
            sp_collapseall_collapse_tip: 'Collapse all groups',
            link_tooltip: 'Click: Copy ',
            link_tooltip_long: '\nLong Press: Copy prefix + ',
            msg_prefix_copied: 'Prefix Copied',
            msg_copied: 'Copied',
            msg_downloaded: 'Downloaded',
            msg_download_failed: '❌ Download failed',
            msg_no_media: '❌ No Media',
            play_btn_tooltip: 'Click: Preview Video in Floating Player',
            btn_switch_to_video: 'Switch to Video',
            btn_switch_to_image: 'Switch to Image',
            msg_no_video: '❌ No Video',
            reload_msg: 'Settings Saved',
            toast_domain_click: '🔗 Single-Click Domain → ',
            toast_domain_long: '🔗 Long-Press Domain → ',
            toast_prefix: '⚙️ Discord Prefix → ',
            toast_date_fmt: '📅 Date Format → ',
            toast_lang_pending: '🌐 Language change will apply after reload.',
            confirm_lang_reload: 'Language changed to {lang}.\nReload page now to apply?',
            corner_modal_subtitle: 'Choose which corner buttons to show.',
            corner_opt_all: 'Show Both',
            corner_opt_all_desc: 'Show the download history and settings buttons together',
            corner_opt_gear: 'Settings Only',
            corner_opt_gear_desc: 'Show only the ⚙️ settings button',
            corner_opt_hist: 'History Only',
            corner_opt_hist_desc: 'Show only the 📋 download history button',
            corner_opt_none: 'Hide Both',
            corner_opt_none_desc: 'Hide both corner buttons (use the userscript menu to reopen)',
            toast_corner_applied: '👁 Corner Buttons → {mode}',
            tp_title: 'Custom Theme',
            tp_enable: 'Enable custom theme',
            tp_enable_hint: 'Applies your custom colors below, overriding the current preset. Leave unchecked to keep editing without affecting your theme.',
            tp_sync: 'Sync Settings ↔ History',
            tp_tab_settings: 'Settings',
            tp_tab_history: 'History',
            tp_quick_label: 'Quick presets',
            tp_field_bg: 'Background',
            tp_field_text: 'Text',
            tp_field_sub: 'Subtext',
            tp_field_header: 'Header',
            tp_field_border: 'Border',
            tp_lock_note: 'Synced with Settings — turn off sync above to edit independently.',
            menu_date_format: '📅 Date Format',
            status_date_asian: 'Asian (YYYY.MM.DD)',
            status_date_western: 'Western (DD.MM.YYYY)',
            help_title: 'Twitter Media Copy Button - Manual',
            help_content: `
                <p><b>🖱️ Media Button (🎞️) — Classic Mode:</b><br>
                • <b>Left Click:</b> Copy media links (images / video URLs).<br>
                • <b>Long Press (0.5s):</b> Copy links with custom prefix (Markdown format, for Discord).<br>
                • <b>Middle Click:</b> Preview — floating video player or image lightbox.<br>
                • <b>Right Click:</b> Force download all media with structured filenames.<br>
                  (Format: <code>[twitter] Name(@ID)_Date_Text_ID.ext</code>)<br>
                • <i>Customize per-slot actions in ⚙️ Settings → 🖱 Click Mode → Customize.</i></p>
                <hr>
                <p><b>🖱️ Media Button (🎞️) — Menu Mode (default):</b><br>
                • <b>Any Click:</b> Opens a popup action menu — Copy / Copy with Prefix / Preview / Download All.<br>
                • Switch via ⚙️ Settings → <b>🖱 Click Mode</b>.</p>
                <hr>
                <p><b>🔗 Link Button (🔗):</b><br>
                • <b>Click:</b> Copy tweet link (default: x.com, or custom click domain).<br>
                • <b>Long Press:</b> Copy with custom prefix + long-press embed domain (e.g. fixupx).</p>
                <hr>
                <p><b>📋 Download History:</b><br>
                • Right-click downloads are automatically logged.<br>
                • Downloaded tweets show a 🟢 badge on the 🎞️ button.<br>
                • Click 📋 (top-right) to browse history: list / thumbnail view, search, export CSV / JSON.</p>
                <hr>
                <p><b>⚙️ Settings Panel:</b><br>
                • Hover the top-right corner → 📋 history / ⚙️ gear button appears.<br>
                • Configure: click mode, click domain, long-press domain, Discord prefix, date format, language, feedback style.</p>
                <hr>
                <p><b>⭐ Groups &amp; ⊞ Gallery:</b><br>
                • <b>Groups:</b> In ⚙️ Settings → <b>⭐ Groups</b>, enable <b>Group on Download</b>. After each right-click download, a ⭐ button briefly appears at the screen edge — hover or click to assign the media to a group via a fan-style picker.<br>
                • <b>Gallery:</b> Inside the floating video player or image lightbox, click the <b>⊞ grid button</b> to open a side panel and browse all media found on the current page.</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ Disclaimer:</b><br>
                Embed domains (e.g. fixupx, vxtwitter) are third-party services unaffiliated with this script.</p>
            `,
            onboard_title: '⚙ Settings Panel',
            onboard_body:  'Hover the top-right corner to reveal the settings button. Click it to quickly manage domains, prefix, language and more — no script manager menu needed.',
            onboard_got_it: 'Got it!',
            onboard_step1_title: '📋 Twitter / X — Media Buttons',
            onboard_step1_body:  'This script adds a Copy button and a Download button directly to every tweet with photos or videos. Hover any media tweet to see them in the action bar.',
            onboard_next: 'Next →',
            menu_feedback_style:    '🔔 Feedback Style',
            status_feedback_toast:  'Toast',
            status_feedback_icon:   'Icon Only',
            status_feedback_pulse:  'Pulse Ring',
            status_feedback_flash:  'Flash Sweep',
            status_feedback_slide:  'Slide Up',
            status_on:  'On',
            status_off: 'Off',
            toast_feedback_style:   '🔔 Feedback Style → ',
            menu_preview:           '👁 Preview',
            menu_click_mode:        '🖱 Click Mode',
            status_click_menu:      'Menu',
            status_click_classic:   'Classic',
            toast_click_mode:       '🖱 Click Mode → ',
            cma_title:                'Customize button actions',
            cma_slot_click:           'Single Click',
            cma_slot_longpress:       'Long Press',
            cma_slot_mclick:          'Middle Click',
            cma_slot_rclick:          'Right Click',
            cma_section_media:        '🎞️ Media Button',
            cma_section_link:         '🔗 Link Button',
            sp_media_icon_style: 'Icon Style',
            sp_media_icon_classic: 'Classic',
            sp_media_icon_play: 'Play',
            sp_link_icon_style: 'Icon Style',
            sp_link_icon_classic: 'Classic',
            sp_link_icon_rounded: 'Rounded',
            sp_link_icon_download: 'Download',
            sp_media_icon_copy: 'Copy',
            sp_link_icon_style_short: 'Link icon',
            sp_media_icon_style_short: 'Media icon',
            sp_link_icon_copy: 'Copy',
            sp_media_icon_download: 'Download',
            cma_action_copy:          'Copy Media URLs',
            cma_action_copy_prefix:   'Copy with Prefix',
            cma_action_preview:       'Preview',
            cma_action_download_all:  'Download All',
            cma_action_copy_url:      'Copy Tweet URL',
            cma_action_save_bookmark: 'Save as Text Bookmark',
            cma_action_none:          'No Action',
            pt_group_title:           '⚡ Performance Tweaks',
            pt_css_simplify_label:    'CSS Simplify',
            pt_css_simplify_desc:     'Remove blur/shadow/gradient globally',
            pt_feed_prune_label:      'Feed Memory Limit',
            pt_feed_prune_desc:       'Freeze off-screen tweets (>70 nodes)',
            
            sp_group_advanced:      '⚙ Advanced',
            sp_backup_settings:     '📤 Backup settings',
            sp_backup_btn:          'Export JSON',
            sp_restore_settings:    '📥 Restore settings',
            sp_restore_btn:         'Choose file…',
            sp_reset_defaults:      '🔄 Reset to defaults',
            sp_reset_confirm:       'Reset all settings to defaults?\n\nThis will clear: copy format, feedback style, dock style, trigger distances, group panel appearance, corner position, button visibility, and list/thumb view mode.\n\nDownload history and groups will NOT be affected.',
            sp_legacy_grid_label:   'Legacy Media Grid',
            sp_legacy_grid_desc:    'Restore the old profile media grid layout. Requires a page reload to apply.',
            confirm_legacy_grid_reload: 'Legacy Media Grid {state}.<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ Note:</b> If you use Tampermonkey (stable), this feature may not work.</span><br>' +
                'For reliable results, please use <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>, ' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>, <span style="color:#e0245e"><b>Violentmonkey</b></span>, or other managers.<br><br>' +
                'Reload page now to apply?',
            btn_legacy_grid_later:      'Later',
            btn_legacy_grid_reload_now: 'Reload now',
            toast_legacy_grid_pending: 'Change saved. Reload the page to apply.',
            sp_bearer_title:        '🔑 Custom Bearer Token',
            sp_bearer_detect:       'Auto-detect',
            sp_bearer_detecting:    'Detecting…',
            sp_bearer_clear:        'Clear',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…  (optional, for 401/403 fallback on Twitter/X)',
            sp_bearer_hint:         'Only used on Twitter / X for media fetch (401/403 fallback). Safe to set on any page — the token is never sent cross-domain.',
            sp_bearer_manual:       'Manual',
            sp_bearer_apply:        'Apply',
            sp_bearer_edit:         'Edit',
            sp_scan_interval:       'Scan Interval',
            
            hist_filter_all:        'All',
            hist_filter_image:      '🖼 Image',
            hist_filter_video:      '🎬 Video',
            hist_filter_text:       '📝 Text',
            hist_sort_newest:       'Newest',
            hist_sort_oldest:       'Oldest',
            hist_sort_author:       'Author A–Z',
            hist_sort_count:        'Most Media',
            hist_search_ph:         '🔍  Search…',
            
            sp_grp_group:           '⭐  Groups',
            sp_grp_on_dl:           'Group on Download',
            sp_grp_popup_style:      'Popup Style',
            sp_grp_popup_style_fan:  'Fan',
            sp_grp_popup_style_list: 'List',
            sp_grp_glow_color:      'Glow Color',
            sp_grp_glow_multi:      'Multi',
            sp_grp_glow_size:       'Glow Size',
            sp_grp_label_color:     'Label Color',
            sp_grp_label_size:      'Label Text Size',
            sp_grp_icon_size:       'Icon Size',
            sp_grp_manage:          'Manage Groups',
            sp_grp_corner:          '📌  Corner Position',
            sp_grp_history:         '🗂  History Panel',
            hist_panel_tooltip:     'Hidden feature: the History Panel has an invisible edge handle on its left & right sides (its look depends on your Dock Style setting below). Hover or click it to auto-hide the panel to the screen edge!',
            sp_grp_dock_style:      'Dock Style',
            sp_grp_hover_delay:     'Hover Delay',
            sp_grp_trigger_l:       'Trigger Distance ◀ Left',
            sp_grp_trigger_r:       'Trigger Distance ▶ Right',
            sp_dock_notch:          'Notch',
            sp_dock_ruler:          'Ruler',
            sp_dock_ghost:          'Ghost',
            sp_dock_pill:           'Pill',
            sp_dock_arrow:          'Arrow',
            sp_dock_dots:           'Dots',
            sp_grp_modal_title:     'Manage Groups',
            sp_grp_modal_new:       '+ New',
            sp_grp_fan_mask:           'Fan Backdrop',
            sp_grp_fan_mask_color:     'Backdrop Color',
            sp_grp_fan_mask_alpha:     'Backdrop Opacity',
            sp_grp_fan_mask_span:      'Backdrop Spread',
            sp_grp_fan_mask_radius:    'Backdrop Size',
            sp_dock_remember_pos:      'Remember Edge Position',
            sp_dock_remember_pos_desc: 'Remembers panel Y position after dragging while peeked',
            sp_theme_sync:             'Sync Panel Themes',
            sp_theme_sync_desc:        'When off, Settings and History panels remember separate dark/light choices',
            sp_dock_reset_pos:         'Reset Panel Position',
            sp_dock_reset_pos_desc:    'Restore panel to default coordinates (dock side kept)',
            sp_dock_reset_btn:         'Reset',
        },
        'zh-TW': {
            langName: '繁體中文',
            menu_domain_click: '🔗 設定「單擊」行為模式',
            menu_domain_long: '🔗 設定「長按」網址域名',
            menu_prefix: '⚙️ 設定 Discord 前綴文字',
            menu_lang: '🌐 切換語言 (Change Language)',
            menu_help: '📖 使用說明書',
            menu_corner_btns: '👁 角落按鈕',
            prompt_prefix: '請輸入自定義前綴(例如 [text]):',
            prompt_lang: '請輸入數字選擇語言:\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: '請輸入數字選擇域名:\n',
            status_default: '預設 (x.com)',
            status_custom: '自定義',
            btn_tooltip: '左鍵:複製媒體連結\n中鍵:預覽影片 / 圖片燈箱\n右鍵:強制下載檔案',
            btn_tooltip_menu: '點擊:開啟操作選單\n(複製 / 帶前綴複製 / 預覽 / 全部下載)',
            sp_grid_media_btn_label: '媒體 Grid 按鈕',
            sp_grid_media_btn_desc: '在 /media 頁面縮圖顯示複製/預覽/下載按鈕',
            grid_btn_copy_tip: '複製媒體連結(長按複製前綴連結,多張則顯示選單)',
            grid_btn_share_tip: '複製貼文連結(長按複製前綴連結)',
            grid_btn_preview_tip: '燈箱預覽',
            grid_btn_download_tip: '下載媒體(長按顯示選單)',
            toast_grid_media_copied: '連結已複製!',
            toast_grid_media_saved: '媒體已儲存!',
            grid_menu_item_image: '圖片 {n}',
            grid_menu_item_video: '影片 {n}',
            avatar_media_btn_tip: '前往 /media 頁面',
            sp_avatar_media_btn_label: '頭像媒體捷徑',
            sp_avatar_media_btn_desc: '在 timeline 頭像旁顯示低調徽章,點擊直達該使用者的 /media 頁面',
            sp_custom_filename_label: '自訂檔名',
            cfn_input_tip: '可用 token:{screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\n注意:{ext} 不含點號——若需要請自行寫成 .{ext}。{origName} 是原始 Twitter CDN 檔名代碼(例如 HP_yBXibUAA2-Qu),不含副檔名,若無法取得則為空。{text} 已截斷,但與其他 token 組合後仍可能產生過長的檔名。',
            cfn_preview_label: '預覽',
            cfn_preview_empty: '(空白樣板——將退回預設格式)',
            cfn_reset_toast: '↺ 檔名樣板已重設為預設值',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: '帳號',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: '顯示名稱',
            cfn_token_date: '{date}',
            cfn_token_date_desc: '日期',
            cfn_token_id: '{id}',
            cfn_token_id_desc: '推文ID',
            cfn_token_index: '{index}',
            cfn_token_index_desc: '序號',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: '副檔名',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: '原始檔名',
            cfn_token_text: '{text}',
            cfn_token_text_desc: '推文內文',
            cfn_token_text_warn_badge: '⚠ 過長',
            cfn_token_text_warn_tip: '推文文字長度難以預測。即使此處已截斷至 20 字元,與其他 token 組合後仍可能產生過長的檔名。',
            sp_collapseall_expand_tip: '展開全部群組',
            sp_collapseall_collapse_tip: '收合全部群組',
            link_tooltip: '點擊:複製 ',
            link_tooltip_long: '\n長按:複製前綴 + ',
            msg_prefix_copied: '前綴已複製',
            msg_copied: '已複製',
            msg_downloaded: '已下載',
            msg_download_failed: '❌ 下載失敗',
            msg_no_media: '❌ 無媒體',
            play_btn_tooltip: '點擊:在浮動播放器中預覽影片',
            btn_switch_to_video: '切換至影片',
            btn_switch_to_image: '切換至圖片',
            msg_no_video: '❌ 無影片',
            reload_msg: '設定已儲存',
            toast_domain_click: '🔗 單擊域名 → ',
            toast_domain_long: '🔗 長按域名 → ',
            toast_prefix: '⚙️ Discord 前綴 → ',
            toast_date_fmt: '📅 日期格式 → ',
            toast_lang_pending: '🌐 語言已變更,重新載入後生效。',
            confirm_lang_reload: '語言已切換為 {lang}。\n立即重新載入頁面以套用?',
            corner_modal_subtitle: '選擇要顯示哪些角落按鈕。',
            corner_opt_all: '全部顯示',
            corner_opt_all_desc: '同時顯示下載紀錄與設定按鈕',
            corner_opt_gear: '只顯示齒輪',
            corner_opt_gear_desc: '只顯示 ⚙️ 設定按鈕',
            corner_opt_hist: '只顯示下載紀錄',
            corner_opt_hist_desc: '只顯示 📋 下載紀錄按鈕',
            corner_opt_none: '全部隱藏',
            corner_opt_none_desc: '隱藏兩顆角落按鈕(可從腳本選單重新開啟)',
            toast_corner_applied: '👁 角落按鈕 → {mode}',
            tp_title: '自訂主題',
            tp_enable: '啟用自訂主題',
            tp_enable_hint: '套用下方自訂顏色,覆蓋目前的配色方案。未勾選時可自由編輯而不影響目前主題。',
            tp_sync: '同步 設定 ↔ 下載紀錄',
            tp_tab_settings: '設定面板',
            tp_tab_history: '下載紀錄',
            tp_quick_label: '快速方案',
            tp_field_bg: '背景',
            tp_field_text: '文字',
            tp_field_sub: '次要文字',
            tp_field_header: '標頭',
            tp_field_border: '邊框',
            tp_lock_note: '已與設定面板同步 — 取消上方同步開關即可獨立編輯。',
            menu_date_format: '📅 日期格式',
            status_date_asian: '亞洲慣用 (YYYY.MM.DD)',
            status_date_western: '歐美慣用 (DD.MM.YYYY)',
            help_title: '推特媒體腳本 — 說明書',
            help_content: `
                <p><b>🖱️ 媒體按鈕 (🎞️) — 經典模式:</b><br>
                • <b>左鍵單擊:</b> 複製推文中所有圖片/影片連結。<br>
                • <b>長按 (0.5秒):</b> 複製含自定義前綴的連結,例如 <code>[text](url)</code>(方便 Discord 嵌入)。<br>
                • <b>中鍵點擊:</b> 開啟浮動影片播放器或圖片燈箱。<br>
                • <b>右鍵點擊:</b> 下載全部媒體,自動生成結構化檔名。<br>
                  (格式:<code>[twitter] 暱稱(@ID)_日期_內文_ID.副檔名</code>)<br>
                • <i>可在 ⚙️ 設定 → 🖱 點擊模式 → 自訂,重新安排各按鍵功能。</i></p>
                <hr>
                <p><b>🖱️ 媒體按鈕 (🎞️) — 選單模式(預設):</b><br>
                • <b>任意點擊:</b> 彈出操作選單(複製 / 含前綴複製 / 預覽 / 下載全部)。<br>
                • 在 ⚙️ 設定 → <b>🖱 點擊模式</b> 切換。</p>
                <hr>
                <p><b>🔗 連結按鈕 (🔗):</b><br>
                • <b>單擊:</b> 複製推文網址(x.com 或自定義單擊域名)。<br>
                • <b>長按:</b> 複製前綴 + 長按域名網址(如 fixupx.com)。</p>
                <hr>
                <p><b>📋 下載履歷:</b><br>
                • 右鍵下載後自動記錄。<br>
                • 滑鼠移至右上角 → 點擊 🕐 開啟履歷面板。<br>
                • 支援列表/縮圖切換、搜尋、Shift 區間選取、批次刪除、CSV/JSON 匯出。</p>
                <hr>
                <p><b>⚙️ 設定面板:</b><br>
                • 將滑鼠移至右上角,顯示齒輪 ⚙️ 與履歷 🕐 按鈕。<br>
                • 點擊 ⚙️ 可設定:點擊模式、單擊域名、長按域名、Discord 前綴、提示風格、日期格式、語言。</p>
                <hr>
                <p><b>⭐ 媒體分組 &amp; ⊞ 頁面相簿:</b><br>
                • <b>分組:</b>在 ⚙️ 設定 → <b>⭐ Groups</b> 中開啟 <b>Group on Download</b>。右鍵下載後,畫面邊角短暫出現 ⭐ 按鈕,hover 或點擊可用扇形選單將媒體歸入群組。<br>
                • <b>頁面相簿:</b>在浮動影片播放器或圖片燈箱中,點擊上方 <b>⊞ 田字鈕</b>,可在側欄瀏覽當前頁面上找到的所有媒體。</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ 免責聲明:</b><br>
                fixupx / vxtwitter 等域名皆為第三方服務,與本腳本無關,請僅使用您信任的域名。</p>
            `,
            onboard_title: '⚙ 設定面板',
            onboard_body:  '將滑鼠移至右上角即可叫出設定按鈕,點擊後可快速管理域名、前綴、語言等設定,無需開啟腳本管理器選單。',
            onboard_got_it: '知道了!',
            onboard_step1_title: '📋 Twitter / X — 媒體按鈕',
            onboard_step1_body:  '本腳本會在所有含有圖片或影片的推文上直接注入「複製連結」與「下載」按鈕,滑鼠移到媒體推文的操作列即可看到它們。',
            onboard_next: '下一步 →',
            menu_feedback_style:    '🔔 提示風格',
            status_feedback_toast:  'Toast 提示',
            status_feedback_icon:   '僅圖示',
            status_feedback_pulse:  '脈衝光環',
            status_feedback_flash:  '閃光掃描',
            status_feedback_slide:  '文字滑入',
            status_on:  '開啟',
            status_off: '關閉',
            toast_feedback_style:   '🔔 提示風格 → ',
            menu_preview:           '👁 預覽',
            menu_click_mode:        '🖱 點擊模式',
            status_click_menu:      '選單',
            status_click_classic:   '經典',
            toast_click_mode:       '🖱 點擊模式 → ',
            cma_title:                '自訂按鈕操作',
            cma_slot_click:           '左鍵單擊',
            cma_slot_longpress:       '左鍵長按',
            cma_slot_mclick:          '中鍵',
            cma_slot_rclick:          '右鍵',
            cma_section_media:        '🎞️ 媒體按鈕',
            cma_section_link:         '🔗 連結按鈕',
            sp_media_icon_style: '圖示樣式',
            sp_media_icon_classic: '經典',
            sp_media_icon_play: '播放',
            sp_link_icon_style: '圖示樣式',
            sp_link_icon_classic: '經典',
            sp_link_icon_rounded: '圓潤',
            sp_link_icon_download: '下載',
            sp_media_icon_copy: '複製',
            sp_link_icon_style_short: '連結圖示',
            sp_media_icon_style_short: '媒體圖示',
            sp_link_icon_copy: '複製',
            sp_media_icon_download: '下載',
            cma_action_copy:          '複製媒體網址',
            cma_action_copy_prefix:   '複製含前綴網址',
            cma_action_preview:       '預覽',
            cma_action_download_all:  '下載全部媒體',
            cma_action_copy_url:      '複製推文網址',
            cma_action_save_bookmark: '另存為文字書籤',
            cma_action_none:          '無動作',
            pt_group_title:           '⚡ 效能微調',
            pt_css_simplify_label:    '簡化樣式',
            pt_css_simplify_desc:     '全域移除模糊/陰影/漸層效果',
            pt_feed_prune_label:      '動態消息記憶體限制',
            pt_feed_prune_desc:       '凍結畫面外推文(超過 70 則)',
            sp_group_advanced:      '⚙ 進階設定',
            sp_backup_settings:     '📤 備份設定',
            sp_backup_btn:          '匯出 JSON',
            sp_restore_settings:    '📥 還原設定',
            sp_restore_btn:         '選擇檔案…',
            sp_reset_defaults:      '🔄 重設為預設值',
            sp_reset_confirm:       '確定要將所有設定重設為預設值嗎?\n\n將清除:複製格式、提示風格、停靠樣式、觸發距離、群組面板外觀、按鈕位置與顯示開關、列表/縮圖檢視模式。\n\n下載履歷與群組不受影響。',
            sp_legacy_grid_label:   '舊版媒體網格',
            sp_legacy_grid_desc:    '還原個人頁面舊版媒體網格排版,切換後需重新整理頁面生效。',
            confirm_legacy_grid_reload: '舊版媒體網格已{state}。<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ 注意:</b>若您使用 Tampermonkey 穩定版,此功能可能無法正常運作。</span><br>' +
                '若要正常使用,請改用 <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>、' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>、<span style="color:#e0245e"><b>Violentmonkey</b></span> 或其他管理器。<br><br>' +
                '立即重新載入頁面以套用?',
            btn_legacy_grid_later:      '稍後',
            btn_legacy_grid_reload_now: '立即重新載入',
            toast_legacy_grid_pending: '設定已儲存,重新整理頁面後生效。',
            sp_bearer_title:        '🔑 自訂 Bearer Token',
            sp_bearer_detect:       '自動偵測',
            sp_bearer_detecting:    '偵測中…',
            sp_bearer_clear:        '清除',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…(可選,作為 401/403 備用 token)',
            sp_bearer_hint:         '僅在 Twitter / X 的媒體擷取時使用(401/403 備用)。可在任何頁面設定,token 不會跨域送出。',
            sp_bearer_manual:       '手動取得',
            sp_bearer_apply:        '套用',
            sp_bearer_edit:         '編輯',
            sp_scan_interval:       '掃描間隔',
            hist_filter_all:        '全部',
            hist_filter_image:      '🖼 圖片',
            hist_filter_video:      '🎬 影片',
            hist_filter_text:       '📝 純文字',
            hist_sort_newest:       '最新',
            hist_sort_oldest:       '最舊',
            hist_sort_author:       '作者 A–Z',
            hist_sort_count:        '媒體最多',
            hist_search_ph:         '🔍  搜尋…',
            sp_grp_group:           '⭐  群組',
            sp_grp_on_dl:           '下載時分組',
            sp_grp_popup_style:      '彈出樣式',
            sp_grp_popup_style_fan:  '扇形',
            sp_grp_popup_style_list: '清單',
            sp_grp_glow_color:      '光暈顏色',
            sp_grp_glow_multi:      '多色',
            sp_grp_glow_size:       '光暈大小',
            sp_grp_label_color:     '標籤顏色',
            sp_grp_label_size:      '標籤文字大小',
            sp_grp_icon_size:       '圖示大小',
            sp_grp_manage:          '管理群組',
            sp_grp_corner:          '📌  按鈕位置',
            sp_grp_history:         '🗂  下載履歷面板',
            hist_panel_tooltip:     '隱藏功能:下載履歷面板的左右兩側邊緣有一個不可見的把手(外觀依下方「停靠樣式」設定而異)。滑鼠懸停或點擊它即可將面板自動隱藏到螢幕邊緣!',
            sp_grp_dock_style:      '停靠樣式',
            sp_grp_hover_delay:     '懸停延遲',
            sp_grp_trigger_l:       '觸發距離 ◀ 左',
            sp_grp_trigger_r:       '觸發距離 ▶ 右',
            sp_dock_notch:          '凹槽',
            sp_dock_ruler:          '尺形',
            sp_dock_ghost:          '隱形',
            sp_dock_pill:           '膠囊',
            sp_dock_arrow:          '箭頭',
            sp_dock_dots:           '點點',
            sp_grp_modal_title:     '管理群組',
            sp_grp_modal_new:       '+ 新增',
            sp_grp_fan_mask:           '扇形背景遮罩',
            sp_grp_fan_mask_color:     '遮罩顏色',
            sp_grp_fan_mask_alpha:     '遮罩透明度',
            sp_grp_fan_mask_span:      '遮罩張角',
            sp_grp_fan_mask_radius:    '遮罩半徑',
            sp_dock_remember_pos:      '記住邊緣位置',
            sp_dock_remember_pos_desc: '拖曳彈出面板後記住其 Y 軸位置',
            sp_theme_sync:             '同步面板主題',
            sp_theme_sync_desc:        '關閉後,設定面板與下載歷史面板可各自記住不同的深色/淺色選擇',
            sp_dock_reset_pos:         '重置面板位置',
            sp_dock_reset_pos_desc:    '還原面板至預設座標(停靠側不變)',
            sp_dock_reset_btn:         '重置',
        },
        'zh-CN': {
            langName: '简体中文',
            menu_domain_click: '🔗 设置“单击”行为模式',
            menu_domain_long: '🔗 设置“长按”网址域名',
            menu_prefix: '⚙️ 设置 Discord 前缀文字',
            menu_lang: '🌐 切换语言 (Change Language)',
            menu_help: '📖 使用说明书',
            menu_corner_btns: '👁 角落按钮',
            prompt_prefix: '请输入自定义前缀(例如 [text]):',
            prompt_lang: '请输入数字选择语言:\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: '请输入数字选择域名:\n',
            status_default: '默认 (x.com)',
            status_custom: '自定义',
            btn_tooltip: '左键:复制媒体链接\n中键:预览视频 / 图片灯箱\n右键:强制下载文件',
            btn_tooltip_menu: '点击:打开操作菜单\n(复制 / 带前缀复制 / 预览 / 全部下载)',
            sp_grid_media_btn_label: '媒体 Grid 按钮',
            sp_grid_media_btn_desc: '在 /media 页面缩图显示复制/预览/下载按钮',
            grid_btn_copy_tip: '复制媒体链接(长按复制前缀链接,多张则显示菜单)',
            grid_btn_share_tip: '复制推文链接(长按复制前缀链接)',
            grid_btn_preview_tip: '灯箱预览',
            grid_btn_download_tip: '下载媒体(长按显示菜单)',
            toast_grid_media_copied: '链接已复制!',
            toast_grid_media_saved: '媒体已保存!',
            grid_menu_item_image: '图片 {n}',
            grid_menu_item_video: '视频 {n}',
            avatar_media_btn_tip: '前往 /media 页面',
            sp_avatar_media_btn_label: '头像媒体快捷方式',
            sp_avatar_media_btn_desc: '在 timeline 头像旁显示低调徽章,点击直达该用户的 /media 页面',
            sp_custom_filename_label: '自定义文件名',
            cfn_input_tip: '可用 token:{screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\n注意:{ext} 不含点号——如需要请自行写成 .{ext}。{origName} 是原始 Twitter CDN 文件名代码(例如 HP_yBXibUAA2-Qu),不含扩展名,若无法获取则为空。{text} 已截断,但与其他 token 组合后仍可能产生过长的文件名。',
            cfn_preview_label: '预览',
            cfn_preview_empty: '(空白模板——将回退到默认格式)',
            cfn_reset_toast: '↺ 文件名模板已重置为默认值',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: '账号',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: '显示名称',
            cfn_token_date: '{date}',
            cfn_token_date_desc: '日期',
            cfn_token_id: '{id}',
            cfn_token_id_desc: '推文ID',
            cfn_token_index: '{index}',
            cfn_token_index_desc: '序号',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: '扩展名',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: '原始文件名',
            cfn_token_text: '{text}',
            cfn_token_text_desc: '推文正文',
            cfn_token_text_warn_badge: '⚠ 过长',
            cfn_token_text_warn_tip: '推文文字长度难以预测。即使此处已截断至 20 字符,与其他 token 组合后仍可能产生过长的文件名。',
            sp_collapseall_expand_tip: '展开全部分组',
            sp_collapseall_collapse_tip: '收起全部分组',
            link_tooltip: '点击:复制 ',
            link_tooltip_long: '\n长按:复制前缀 + ',
            msg_prefix_copied: '前缀已复制',
            msg_copied: '已复制',
            msg_downloaded: '已下载',
            msg_download_failed: '❌ 下载失败',
            msg_no_media: '❌ 无媒体',
            play_btn_tooltip: '点击:在浮动播放器中预览视频',
            btn_switch_to_video: '切换至视频',
            btn_switch_to_image: '切换至图片',
            msg_no_video: '❌ 无视频',
            reload_msg: '设置已保存',
            toast_domain_click: '🔗 单击域名 → ',
            toast_domain_long: '🔗 长按域名 → ',
            toast_prefix: '⚙️ Discord 前缀 → ',
            toast_date_fmt: '📅 日期格式 → ',
            toast_lang_pending: '🌐 语言已变更,重新载入后生效。',
            confirm_lang_reload: '语言已切换为 {lang}。\n立即重新载入页面以应用?',
            corner_modal_subtitle: '选择要显示哪些角落按钮。',
            corner_opt_all: '全部显示',
            corner_opt_all_desc: '同时显示下载记录与设置按钮',
            corner_opt_gear: '只显示齿轮',
            corner_opt_gear_desc: '只显示 ⚙️ 设置按钮',
            corner_opt_hist: '只显示下载记录',
            corner_opt_hist_desc: '只显示 📋 下载记录按钮',
            corner_opt_none: '全部隐藏',
            corner_opt_none_desc: '隐藏两颗角落按钮(可从脚本菜单重新打开)',
            toast_corner_applied: '👁 角落按钮 → {mode}',
            tp_title: '自定义主题',
            tp_enable: '启用自定义主题',
            tp_enable_hint: '应用下方自定义颜色,覆盖当前的配色方案。未勾选时可自由编辑而不影响当前主题。',
            tp_sync: '同步 设置 ↔ 下载记录',
            tp_tab_settings: '设置面板',
            tp_tab_history: '下载记录',
            tp_quick_label: '快速方案',
            tp_field_bg: '背景',
            tp_field_text: '文字',
            tp_field_sub: '次要文字',
            tp_field_header: '标头',
            tp_field_border: '边框',
            tp_lock_note: '已与设置面板同步 — 取消上方同步开关即可独立编辑。',
            menu_date_format: '📅 日期格式',
            status_date_asian: '亚洲惯用 (YYYY.MM.DD)',
            status_date_western: '欧美惯用 (DD.MM.YYYY)',
            help_title: '推特媒体脚本 — 说明书',
            help_content: `
                <p><b>🖱️ 媒体按钮 (🎞️) — 经典模式:</b><br>
                • <b>左键单击:</b> 复制推文中所有图片/视频链接。<br>
                • <b>长按 (0.5秒):</b> 复制含自定义前缀的链接,例如 <code>[text](url)</code>(方便 Discord 嵌入)。<br>
                • <b>中键单击:</b> 打开浮动视频播放器或图片灯箱。<br>
                • <b>右键单击:</b> 下载全部媒体,自动生成结构化文件名。<br>
                  (格式:<code>[twitter] 昵称(@ID)_日期_内文_ID.扩展名</code>)<br>
                • <i>可在 ⚙️ 设置 → 🖱 点击模式 → 自定义,重新排列各按键功能。</i></p>
                <hr>
                <p><b>🖱️ 媒体按钮 (🎞️) — 菜单模式(默认):</b><br>
                • <b>任意点击:</b> 弹出操作菜单(复制 / 含前缀复制 / 预览 / 下载全部)。<br>
                • 在 ⚙️ 设置 → <b>🖱 点击模式</b> 切换。</p>
                <hr>
                <p><b>🔗 链接按钮 (🔗):</b><br>
                • <b>单击:</b> 复制推文链接(x.com 或自定义单击域名)。<br>
                • <b>长按:</b> 复制前缀 + 长按域名链接(如 fixupx.com)。</p>
                <hr>
                <p><b>📋 下载历史:</b><br>
                • 右键下载后自动记录。<br>
                • 鼠标移至右上角 → 点击 🕐 打开历史面板。<br>
                • 支持列表/缩略图切换、搜索、Shift 区间选择、批量删除、CSV/JSON 导出。</p>
                <hr>
                <p><b>⚙️ 设置面板:</b><br>
                • 将鼠标移至右上角,显示齿轮 ⚙️ 与历史 🕐 按钮。<br>
                • 点击 ⚙️ 可设置:点击模式、单击域名、长按域名、Discord 前缀、提示风格、日期格式、语言。</p>
                <hr>
                <p><b>⭐ 媒体分组 &amp; ⊞ 页面相册:</b><br>
                • <b>分组:</b>在 ⚙️ 设置 → <b>⭐ Groups</b> 中开启 <b>Group on Download</b>。右键下载后,屏幕边角短暂出现 ⭐ 按钮,悬停或点击可通过扇形选单将媒体归入分组。<br>
                • <b>页面相册:</b>在浮动视频播放器或图片灯箱中,点击上方 <b>⊞ 田字按钮</b>,可在侧栏浏览当前页面找到的所有媒体。</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ 免责声明:</b><br>
                fixupx / vxtwitter 等域名均为第三方服务,与本脚本无关,请仅使用您信任的域名。</p>
            `,
            onboard_title: '⚙ 设置面板',
            onboard_body:  '将鼠标移到右上角即可呼出设置按钮,点击后可快速管理域名、前缀、语言等设置,无需打开脚本管理器菜单。',
            onboard_got_it: '知道了!',
            onboard_step1_title: '📋 Twitter / X — 媒体按钮',
            onboard_step1_body:  '本脚本会在所有含图片或视频的推文上直接注入「复制链接」与「下载」按钮,鼠标移到媒体推文的操作栏即可看到它们。',
            onboard_next: '下一步 →',
            menu_feedback_style:    '🔔 提示风格',
            status_feedback_toast:  'Toast 提示',
            status_feedback_icon:   '仅图标',
            status_feedback_pulse:  '脉冲光环',
            status_feedback_flash:  '闪光扫描',
            status_feedback_slide:  '文字滑入',
            status_on:  '开启',
            status_off: '关闭',
            toast_feedback_style:   '🔔 提示风格 → ',
            menu_preview:           '👁 预览',
            menu_click_mode:        '🖱 点击模式',
            status_click_menu:      '菜单',
            status_click_classic:   '经典',
            toast_click_mode:       '🖱 点击模式 → ',
            cma_title:                '自定义按钮操作',
            cma_slot_click:           '左键单击',
            cma_slot_longpress:       '左键长按',
            cma_slot_mclick:          '中键',
            cma_slot_rclick:          '右键',
            cma_section_media:        '🎞️ 媒体按钮',
            cma_section_link:         '🔗 链接按钮',
            sp_media_icon_style: '图标样式',
            sp_media_icon_classic: '经典',
            sp_media_icon_play: '播放',
            sp_link_icon_style: '图标样式',
            sp_link_icon_classic: '经典',
            sp_link_icon_rounded: '圆润',
            sp_link_icon_download: '下载',
            sp_media_icon_copy: '复制',
            sp_link_icon_style_short: '链接图标',
            sp_media_icon_style_short: '媒体图标',
            sp_link_icon_copy: '复制',
            sp_media_icon_download: '下载',
            cma_action_copy:          '复制媒体网址',
            cma_action_copy_prefix:   '复制含前缀网址',
            cma_action_preview:       '预览',
            cma_action_download_all:  '下载全部媒体',
            cma_action_copy_url:      '复制推文网址',
            cma_action_save_bookmark: '另存为文字书签',
            cma_action_none:          '无动作',
            pt_group_title:           '⚡ 性能微调',
            pt_css_simplify_label:    '简化样式',
            pt_css_simplify_desc:     '全局移除模糊/阴影/渐变效果',
            pt_feed_prune_label:      '动态消息内存限制',
            pt_feed_prune_desc:       '冻结屏幕外推文(超过 70 条)',
            sp_group_advanced:      '⚙ 高级设置',
            sp_backup_settings:     '📤 备份设置',
            sp_backup_btn:          '导出 JSON',
            sp_restore_settings:    '📥 还原设置',
            sp_restore_btn:         '选择文件…',
            sp_reset_defaults:      '🔄 重置为默认值',
            sp_reset_confirm:       '确定要将所有设置重置为默认值吗?\n\n将清除:复制格式、提示风格、停靠样式、触发距离、分组面板外观、按钮位置与显示开关、列表/缩略图视图模式。\n\n下载历史与分组不受影响。',
            sp_legacy_grid_label:   '旧版媒体网格',
            sp_legacy_grid_desc:    '还原个人主页的旧版媒体网格布局。需要刷新页面才能生效。',
            confirm_legacy_grid_reload: '旧版媒体网格已{state}。<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ 注意:</b>若您使用 Tampermonkey 稳定版,此功能可能无法正常运作。</span><br>' +
                '若要正常使用,请改用 <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>、' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>、<span style="color:#e0245e"><b>Violentmonkey</b></span> 或其他管理器。<br><br>' +
                '是否立即刷新页面以生效?',
            btn_legacy_grid_later:      '稍后',
            btn_legacy_grid_reload_now: '立即刷新',
            toast_legacy_grid_pending: '更改已保存。刷新页面后生效。',
            sp_bearer_title:        '🔑 自定义 Bearer Token',
            sp_bearer_detect:       '自动检测',
            sp_bearer_detecting:    '检测中…',
            sp_bearer_clear:        '清除',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…(可选,作为 401/403 备用 token)',
            sp_bearer_hint:         '仅在 Twitter / X 的媒体获取时使用(401/403 备用)。可在任何页面设置,token 不会跨域发送。',
            sp_bearer_manual:       '手动获取',
            sp_bearer_apply:        '应用',
            sp_bearer_edit:         '编辑',
            sp_scan_interval:       '扫描间隔',
            hist_filter_all:        '全部',
            hist_filter_image:      '🖼 图片',
            hist_filter_video:      '🎬 视频',
            hist_filter_text:       '📝 纯文字',
            hist_sort_newest:       '最新',
            hist_sort_oldest:       '最旧',
            hist_sort_author:       '作者 A–Z',
            hist_sort_count:        '媒体最多',
            hist_search_ph:         '🔍  搜索…',
            sp_grp_group:           '⭐  分组',
            sp_grp_on_dl:           '下载时分组',
            sp_grp_popup_style:      '弹出样式',
            sp_grp_popup_style_fan:  '扇形',
            sp_grp_popup_style_list: '列表',
            sp_grp_glow_color:      '光晕颜色',
            sp_grp_glow_multi:      '多色',
            sp_grp_glow_size:       '光晕大小',
            sp_grp_label_color:     '标签颜色',
            sp_grp_label_size:      '标签文字大小',
            sp_grp_icon_size:       '图标大小',
            sp_grp_manage:          '管理分组',
            sp_grp_corner:          '📌  按钮位置',
            sp_grp_history:         '🗂  下载记录面板',
            hist_panel_tooltip:     '隐藏功能:下载记录面板的左右两侧边缘有一个不可见的把手(外观取决于下方的"停靠样式"设置)。鼠标悬停或点击它即可将面板自动隐藏到屏幕边缘!',
            sp_grp_dock_style:      '停靠样式',
            sp_grp_hover_delay:     '悬停延迟',
            sp_grp_trigger_l:       '触发距离 ◀ 左',
            sp_grp_trigger_r:       '触发距离 ▶ 右',
            sp_dock_notch:          '凹槽',
            sp_dock_ruler:          '标尺',
            sp_dock_ghost:          '隐形',
            sp_dock_pill:           '胶囊',
            sp_dock_arrow:          '箭头',
            sp_dock_dots:           '点点',
            sp_grp_modal_title:     '管理分组',
            sp_grp_modal_new:       '+ 新建',
            sp_grp_fan_mask:           '扇形背景遮罩',
            sp_grp_fan_mask_color:     '遮罩颜色',
            sp_grp_fan_mask_alpha:     '遮罩透明度',
            sp_grp_fan_mask_span:      '遮罩张角',
            sp_grp_fan_mask_radius:    '遮罩半径',
            sp_dock_remember_pos:      '记住边缘位置',
            sp_dock_remember_pos_desc: '拖动弹出面板后记住其 Y 轴位置',
            sp_theme_sync:             '同步面板主题',
            sp_theme_sync_desc:        '关闭后,设置面板与下载历史面板可各自记住不同的深色/浅色选择',
            sp_dock_reset_pos:         '重置面板位置',
            sp_dock_reset_pos_desc:    '还原面板至默认坐标(停靠侧不变)',
            sp_dock_reset_btn:         '重置',
        },
        'ja': {
            langName: '日本語',
            menu_domain_click: '🔗 クリック動作設定',
            menu_domain_long: '🔗 長押しURLドメイン設定',
            menu_prefix: '⚙️ プレフィックス設定 (Discord)',
            menu_lang: '🌐 言語変更 (Change Language)',
            menu_help: '📖 ヘルプ / 説明書',
            menu_corner_btns: '👁 コーナーボタン',
            prompt_prefix: 'カスタムプレフィックスを入力(例: [text]):',
            prompt_lang: '番号を入力して言語を選択:\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: 'ドメインの番号を入力:\n',
            status_default: 'デフォルト (x.com)',
            status_custom: 'カスタム',
            btn_tooltip: '左:メディアリンクをコピー\n中:動画プレビュー / 画像ライトボックス\n右:ファイルをダウンロード',
            btn_tooltip_menu: 'クリック:アクションメニューを開く\n(コピー / プレフィックス付きコピー / プレビュー / 一括ダウンロード)',
            sp_grid_media_btn_label: 'メディア Grid ボタン',
            sp_grid_media_btn_desc: '/media ページのサムネイルにコピー/プレビュー/ダウンロードボタンを表示',
            grid_btn_copy_tip: 'メディアリンクをコピー(長押しでプレフィックス付きコピー、複数の場合はメニュー表示)',
            grid_btn_share_tip: 'ツイートリンクをコピー(長押しでプレフィックス付きコピー)',
            grid_btn_preview_tip: 'ライトボックスでプレビュー',
            grid_btn_download_tip: 'メディアをダウンロード(長押しでメニュー表示)',
            toast_grid_media_copied: 'リンクをコピーしました!',
            toast_grid_media_saved: 'メディアを保存しました!',
            grid_menu_item_image: '画像 {n}',
            grid_menu_item_video: '動画 {n}',
            avatar_media_btn_tip: '/media ページへ移動',
            sp_avatar_media_btn_label: 'アバターメディアショートカット',
            sp_avatar_media_btn_desc: 'タイムラインのアバター横に控えめなバッジを表示し、そのユーザーの /media ページへ移動',
            sp_custom_filename_label: 'カスタムファイル名',
            cfn_input_tip: '使用可能なトークン:{screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\n注:{ext} にはドットが含まれません——必要な場合は .{ext} と記述してください。{origName} は元の Twitter CDN ファイル名コード(例:HP_yBXibUAA2-Qu)で、拡張子を含まず、取得できない場合は空になります。{text} は切り詰められますが、他のトークンと組み合わせるとファイル名が長くなる場合があります。',
            cfn_preview_label: 'プレビュー',
            cfn_preview_empty: '(空のテンプレート——デフォルト形式にフォールバックします)',
            cfn_reset_toast: '↺ ファイル名テンプレートをデフォルトにリセットしました',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: 'アカウント',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: '表示名',
            cfn_token_date: '{date}',
            cfn_token_date_desc: '日付',
            cfn_token_id: '{id}',
            cfn_token_id_desc: 'ツイートID',
            cfn_token_index: '{index}',
            cfn_token_index_desc: '連番',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: '拡張子',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: '元のファイル名',
            cfn_token_text: '{text}',
            cfn_token_text_desc: 'ツイート本文',
            cfn_token_text_warn_badge: '⚠ 長い',
            cfn_token_text_warn_tip: 'ツイート本文の長さは予測できません。ここで20文字に切り詰めても、他のトークンと組み合わせるとファイル名が長くなりすぎる場合があります。',
            sp_collapseall_expand_tip: 'すべてのグループを展開',
            sp_collapseall_collapse_tip: 'すべてのグループを折りたたむ',
            link_tooltip: 'クリック:コピー ',
            link_tooltip_long: '\n長押し:プレフィックス付きコピー ',
            msg_prefix_copied: 'プレフィックス付',
            msg_copied: 'コピー完了',
            msg_downloaded: 'ダウンロード完了',
            msg_download_failed: '❌ ダウンロード失敗',
            msg_no_media: '❌ メディアなし',
            play_btn_tooltip: 'クリック:フローティングプレーヤーで動画を再生',
            btn_switch_to_video: '動画に切り替え',
            btn_switch_to_image: '画像に切り替え',
            msg_no_video: '❌ 動画なし',
            reload_msg: '設定が保存されました',
            toast_domain_click: '🔗 クリックドメイン → ',
            toast_domain_long: '🔗 長押しドメイン → ',
            toast_prefix: '⚙️ Discordプレフィックス → ',
            toast_date_fmt: '📅 日付フォーマット → ',
            toast_lang_pending: '🌐 言語を変更しました。再読み込み後に反映されます。',
            confirm_lang_reload: '言語を {lang} に変更しました。\n今すぐページを再読み込みしますか?',
            corner_modal_subtitle: '表示するコーナーボタンを選択してください。',
            corner_opt_all: '両方表示',
            corner_opt_all_desc: 'ダウンロード履歴と設定ボタンを両方表示',
            corner_opt_gear: '設定のみ',
            corner_opt_gear_desc: '⚙️ 設定ボタンのみ表示',
            corner_opt_hist: '履歴のみ',
            corner_opt_hist_desc: '📋 ダウンロード履歴ボタンのみ表示',
            corner_opt_none: '両方非表示',
            corner_opt_none_desc: '両方のコーナーボタンを非表示(ユーザースクリプトメニューから再表示可能)',
            toast_corner_applied: '👁 コーナーボタン → {mode}',
            tp_title: 'カスタムテーマ',
            tp_enable: 'カスタムテーマを有効化',
            tp_enable_hint: '下記のカスタムカラーを適用し、現在のプリセットを上書きします。チェックを外すと、テーマに影響を与えずに編集できます。',
            tp_sync: '設定 ↔ 履歴 を同期',
            tp_tab_settings: '設定',
            tp_tab_history: '履歴',
            tp_quick_label: 'クイックプリセット',
            tp_field_bg: '背景',
            tp_field_text: 'テキスト',
            tp_field_sub: 'サブテキスト',
            tp_field_header: 'ヘッダー',
            tp_field_border: '枠線',
            tp_lock_note: '設定と同期中 — 上の同期をオフにすると個別に編集できます。',
            menu_date_format: '📅 日付フォーマット',
            status_date_asian: 'アジア式 (YYYY.MM.DD)',
            status_date_western: '欧米式 (DD.MM.YYYY)',
            help_title: 'Twitter メディアスクリプト — マニュアル',
            help_content: `
                <p><b>🖱️ メディアボタン (🎞️) — クラシックモード:</b><br>
                • <b>左クリック:</b> ツイート内の画像/動画URLをすべてコピー。<br>
                • <b>長押し (0.5秒):</b> カスタムプレフィックス付きでコピー(例:<code>[text](url)</code>、Discord向け)。<br>
                • <b>中クリック:</b> フローティング動画プレーヤーまたは画像ライトボックスを開く。<br>
                • <b>右クリック:</b> 全メディアをダウンロード(構造化ファイル名)。<br>
                  (形式:<code>[twitter] 名前(@ID)_日付_本文_ID.拡張子</code>)<br>
                • <i>⚙️ 設定 → 🖱 クリックモード → カスタマイズ で各スロットの動作を変更できます。</i></p>
                <hr>
                <p><b>🖱️ メディアボタン (🎞️) — メニューモード(デフォルト):</b><br>
                • <b>どのクリックでも:</b> アクションメニューを表示(コピー / プレフィックス付きコピー / プレビュー / 全ダウンロード)。<br>
                • ⚙️ 設定 → <b>🖱 クリックモード</b> で切り替え。</p>
                <hr>
                <p><b>🔗 リンクボタン (🔗):</b><br>
                • <b>クリック:</b> ツイートURLをコピー(x.com またはカスタムドメイン)。<br>
                • <b>長押し:</b> プレフィックス + 長押しドメインURLをコピー(例:fixupx.com)。</p>
                <hr>
                <p><b>📋 ダウンロード履歴:</b><br>
                • 右クリックダウンロードは自動記録。<br>
                • 右上にカーソルを合わせ → 🕐 をクリックして履歴パネルを開く。<br>
                • リスト/サムネイル表示、検索、Shift選択一括削除、CSV/JSONエクスポート対応。</p>
                <hr>
                <p><b>⚙️ 設定パネル:</b><br>
                • 右上隅にカーソルを合わせると ⚙️ と 🕐 ボタンが表示される。<br>
                • ⚙️ をクリックして設定:クリックモード、クリックドメイン、長押しドメイン、プレフィックス、通知スタイル、日付形式、言語。</p>
                <hr>
                <p><b>⭐ グループ &amp; ⊞ ギャラリー:</b><br>
                • <b>グループ:</b>⚙️ 設定 → <b>⭐ Groups</b> で <b>Group on Download</b> を有効にすると、右クリックダウンロード後に画面端へ ⭐ が短時間表示されます。ホバーまたはクリックするとファン型の選択メニューが開き、グループに分類できます。<br>
                • <b>ギャラリー:</b>フローティング動画プレーヤーまたは画像ライトボックスで上部の <b>⊞ グリッドボタン</b> をクリックすると、現在のページで見つかったすべてのメディアをサイドパネルで閲覧できます。</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ 免責事項:</b><br>
                fixupx / vxtwitter 等のドメインは第三者サービスであり、このスクリプトとは無関係です。信頼できるドメインのみご使用ください。</p>
            `,
            onboard_title: '⚙ 設定パネル',
            onboard_body:  '右上隅にカーソルを合わせると設定ボタンが現れます。クリックすればスクリプト管理器を開かずにドメイン・プレフィックス・言語などをすばやく管理できます。',
            onboard_got_it: 'わかった!',
            onboard_step1_title: '📋 Twitter / X — メディアボタン',
            onboard_step1_body:  '画像や動画を含むすべてのツイートに「リンクをコピー」と「ダウンロード」ボタンを直接追加します。メディアツイートにカーソルを合わせると操作バーに表示されます。',
            onboard_next: '次へ →',
            menu_feedback_style:    '🔔 フィードバックスタイル',
            status_feedback_toast:  'トースト通知',
            status_feedback_icon:   'アイコンのみ',
            status_feedback_pulse:  'パルスリング',
            status_feedback_flash:  'フラッシュスイープ',
            status_feedback_slide:  'スライドアップ',
            status_on:  'オン',
            status_off: 'オフ',
            toast_feedback_style:   '🔔 フィードバックスタイル → ',
            menu_preview:           '👁 プレビュー',
            menu_click_mode:        '🖱 クリックモード',
            status_click_menu:      'メニュー',
            status_click_classic:   'クラシック',
            toast_click_mode:       '🖱 クリックモード → ',
            cma_title:                'ボタン操作のカスタマイズ',
            cma_slot_click:           'シングルクリック',
            cma_slot_longpress:       '長押し',
            cma_slot_mclick:          '中クリック',
            cma_slot_rclick:          '右クリック',
            cma_section_media:        '🎞️ メディアボタン',
            cma_section_link:         '🔗 リンクボタン',
            sp_media_icon_style: 'アイコンスタイル',
            sp_media_icon_classic: 'クラシック',
            sp_media_icon_play: 'プレイ',
            sp_link_icon_style: 'アイコンスタイル',
            sp_link_icon_classic: 'クラシック',
            sp_link_icon_rounded: 'ラウンド',
            sp_link_icon_download: 'ダウンロード',
            sp_media_icon_copy: 'コピー',
            sp_link_icon_style_short: 'リンクアイコン',
            sp_media_icon_style_short: 'メディアアイコン',
            sp_link_icon_copy: 'コピー',
            sp_media_icon_download: 'ダウンロード',
            cma_action_copy:          'メディアURLをコピー',
            cma_action_copy_prefix:   '接頭辞付きでコピー',
            cma_action_preview:       'プレビュー',
            cma_action_download_all:  'すべてダウンロード',
            cma_action_copy_url:      'ツイートURLをコピー',
            cma_action_save_bookmark: 'テキストブックマークとして保存',
            cma_action_none:          '操作なし',
            pt_group_title:           '⚡ パフォーマンス調整',
            pt_css_simplify_label:    'スタイル簡略化',
            pt_css_simplify_desc:     'ぼかし/影/グラデーションを全体的に削除',
            pt_feed_prune_label:      'フィードメモリ制限',
            pt_feed_prune_desc:       '画面外のツイートを凍結(70件超)',
            sp_group_advanced:      '⚙ 詳細設定',
            sp_backup_settings:     '📤 設定バックアップ',
            sp_backup_btn:          'JSON 書き出し',
            sp_restore_settings:    '📥 設定の復元',
            sp_restore_btn:         'ファイルを選択…',
            sp_reset_defaults:      '🔄 デフォルトに戻す',
            sp_reset_confirm:       'すべての設定をデフォルトに戻しますか?\n\nリセット対象:コピー形式、通知スタイル、ドックスタイル、トリガー距離、グループパネル外観、ボタン位置・表示、リスト/サムネイルモード。\n\nダウンロード履歴とグループは影響を受けません。',
            sp_legacy_grid_label:   '旧メディアグリッド',
            sp_legacy_grid_desc:    'プロフィールページの旧メディアグリッドレイアウトを復元します。適用にはページの再読み込みが必要です。',
            confirm_legacy_grid_reload: '旧メディアグリッドを{state}しました。<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ 注意:</b>Tampermonkey(安定版)をご利用の場合、この機能が正常に動作しない可能性があります。</span><br>' +
                '確実に動作させるには、<span style="color:#e0245e"><b>Tampermonkey Beta</b></span>、' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>、<span style="color:#e0245e"><b>Violentmonkey</b></span> など他の管理ツールをご利用ください。<br><br>' +
                '今すぐページを再読み込みして適用しますか?',
            btn_legacy_grid_later:      '後で',
            btn_legacy_grid_reload_now: '今すぐ再読み込み',
            toast_legacy_grid_pending: '変更を保存しました。ページを再読み込みすると適用されます。',
            sp_bearer_title:        '🔑 カスタム Bearer Token',
            sp_bearer_detect:       '自動検出',
            sp_bearer_detecting:    '検出中…',
            sp_bearer_clear:        'クリア',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…(任意、401/403 フォールバック用)',
            sp_bearer_hint:         'Twitter / X のメディア取得(401/403 フォールバック)でのみ使用します。どのページでも設定可能で、token はクロスドメインに送信されません。',
            sp_bearer_manual:       '手動取得',
            sp_bearer_apply:        '適用',
            sp_bearer_edit:         '編集',
            sp_scan_interval:       'スキャン間隔',
            hist_filter_all:        'すべて',
            hist_filter_image:      '🖼 画像',
            hist_filter_video:      '🎬 動画',
            hist_filter_text:       '📝 テキスト',
            hist_sort_newest:       '新しい順',
            hist_sort_oldest:       '古い順',
            hist_sort_author:       '著者 A–Z',
            hist_sort_count:        'メディア数順',
            hist_search_ph:         '🔍  検索…',
            sp_grp_group:           '⭐  グループ',
            sp_grp_on_dl:           'DL時にグループ分け',
            sp_grp_popup_style:      'ポップアップ様式',
            sp_grp_popup_style_fan:  'ファン型',
            sp_grp_popup_style_list: 'リスト',
            sp_grp_glow_color:      'グロー色',
            sp_grp_glow_multi:      'マルチ',
            sp_grp_glow_size:       'グローサイズ',
            sp_grp_label_color:     'ラベル色',
            sp_grp_label_size:      'ラベル文字サイズ',
            sp_grp_icon_size:       'アイコンサイズ',
            sp_grp_manage:          'グループ管理',
            sp_grp_corner:          '📌  ボタン位置',
            sp_grp_history:         '🗂  履歴パネル',
            hist_panel_tooltip:     '隠し機能:履歴パネルの左右の端に見えないハンドルがあります(見た目は下の「ドックスタイル」設定によって変わります)。ホバーまたはクリックすると、パネルが画面端に自動的に隠れます!',
            sp_grp_dock_style:      'ドックスタイル',
            sp_grp_hover_delay:     'ホバー遅延',
            sp_grp_trigger_l:       'トリガー距離 ◀ 左',
            sp_grp_trigger_r:       'トリガー距離 ▶ 右',
            sp_dock_notch:          'ノッチ',
            sp_dock_ruler:          'ルーラー',
            sp_dock_ghost:          'ゴースト',
            sp_dock_pill:           'ピル',
            sp_dock_arrow:          '矢印',
            sp_dock_dots:           'ドット',
            sp_grp_modal_title:     'グループ管理',
            sp_grp_modal_new:       '+ 新規',
            sp_grp_fan_mask:           '扇形背景マスク',
            sp_grp_fan_mask_color:     'マスクの色',
            sp_grp_fan_mask_alpha:     'マスクの不透明度',
            sp_grp_fan_mask_span:      'マスクの広がり角度',
            sp_grp_fan_mask_radius:    'マスクのサイズ',
            sp_dock_remember_pos:      '端の位置を記憶',
            sp_dock_remember_pos_desc: 'ドラッグ後のパネルのY座標を記憶します',
            sp_theme_sync:             'パネルのテーマを同期',
            sp_theme_sync_desc:        'オフにすると、設定パネルとダウンロード履歴パネルが別々にダーク/ライトを記憶します',
            sp_dock_reset_pos:         'パネル位置をリセット',
            sp_dock_reset_pos_desc:    'パネルを初期座標に戻します(ドック側は維持)',
            sp_dock_reset_btn:         'リセット',
        },
        'ko': {
            langName: '한국어',
            menu_domain_click: '🔗 클릭 동작 설정',
            menu_domain_long: '🔗 길게 누르기 도메인 설정',
            menu_prefix: '⚙️ 접두사 설정 (Discord)',
            menu_lang: '🌐 언어 변경 (Change Language)',
            menu_help: '📖 도움말 / 설명서',
            menu_corner_btns: '👁 코너 버튼',
            prompt_prefix: '사용자 지정 접두사 입력 (예: [text]):',
            prompt_lang: '숫자를 입력하여 언어 선택:\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: '도메인 번호를 선택하세요:\n',
            status_default: '기본 (x.com)',
            status_custom: '사용자 지정',
            btn_tooltip: '왼쪽: 미디어 링크 복사\n가운데: 동영상 미리보기 / 이미지 라이트박스\n오른쪽: 파일 다운로드',
            btn_tooltip_menu: '클릭: 작업 메뉴 열기\n(복사 / 접두사 포함 복사 / 미리보기 / 전체 다운로드)',
            sp_grid_media_btn_label: '미디어 Grid 버튼',
            sp_grid_media_btn_desc: '/media 페이지 썸네일에 복사/미리보기/다운로드 버튼 표시',
            grid_btn_copy_tip: '미디어 링크 복사 (길게 누르면 접두사 포함 복사, 여러 개면 메뉴 표시)',
            grid_btn_share_tip: '트윗 링크 복사 (길게 누르면 접두사 포함 복사)',
            grid_btn_preview_tip: '라이트박스로 미리보기',
            grid_btn_download_tip: '미디어 다운로드 (길게 누르면 메뉴 표시)',
            toast_grid_media_copied: '링크가 복사되었습니다!',
            toast_grid_media_saved: '미디어가 저장되었습니다!',
            grid_menu_item_image: '이미지 {n}',
            grid_menu_item_video: '동영상 {n}',
            avatar_media_btn_tip: '/media 페이지로 이동',
            sp_avatar_media_btn_label: '아바타 미디어 바로가기',
            sp_avatar_media_btn_desc: '타임라인 아바타 옆에 은은한 배지를 표시하여 해당 사용자의 /media 페이지로 이동',
            sp_custom_filename_label: '사용자 지정 파일 이름',
            cfn_input_tip: '사용 가능한 토큰: {screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\n참고: {ext}에는 점이 포함되지 않습니다——필요한 경우 직접 .{ext}로 작성하세요. {origName}은 원본 Twitter CDN 파일명 코드(예: HP_yBXibUAA2-Qu)이며 확장자를 포함하지 않고, 가져올 수 없는 경우 비어 있습니다. {text}는 잘리지만 다른 토큰과 결합하면 파일 이름이 너무 길어질 수 있습니다.',
            cfn_preview_label: '미리보기',
            cfn_preview_empty: '(빈 템플릿——기본 형식으로 대체됩니다)',
            cfn_reset_toast: '↺ 파일명 템플릿이 기본값으로 재설정되었습니다',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: '계정',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: '표시 이름',
            cfn_token_date: '{date}',
            cfn_token_date_desc: '날짜',
            cfn_token_id: '{id}',
            cfn_token_id_desc: '트윗 ID',
            cfn_token_index: '{index}',
            cfn_token_index_desc: '순번',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: '확장자',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: '원본 파일명',
            cfn_token_text: '{text}',
            cfn_token_text_desc: '트윗 본문',
            cfn_token_text_warn_badge: '⚠ 너무 김',
            cfn_token_text_warn_tip: '트윗 텍스트 길이는 예측할 수 없습니다. 여기서 20자로 잘라도 다른 토큰과 결합하면 파일 이름이 너무 길어질 수 있습니다.',
            sp_collapseall_expand_tip: '모든 그룹 펼치기',
            sp_collapseall_collapse_tip: '모든 그룹 접기',
            link_tooltip: '클릭: 복사 ',
            link_tooltip_long: '\n길게 누르기: 접두사 포함 복사 ',
            msg_prefix_copied: '접두사 복사됨',
            msg_copied: '복사 완료',
            msg_downloaded: '다운로드 완료',
            msg_download_failed: '❌ 다운로드 실패',
            msg_no_media: '❌ 미디어 없음',
            play_btn_tooltip: '클릭: 플로팅 플레이어에서 동영상 재생',
            btn_switch_to_video: '동영상으로 전환',
            btn_switch_to_image: '이미지로 전환',
            msg_no_video: '❌ 동영상 없음',
            reload_msg: '설정이 저장되었습니다',
            toast_domain_click: '🔗 클릭 도메인 → ',
            toast_domain_long: '🔗 길게 누르기 도메인 → ',
            toast_prefix: '⚙️ Discord 접두사 → ',
            toast_date_fmt: '📅 날짜 형식 → ',
            toast_lang_pending: '🌐 언어가 변경되었습니다. 새로고침 후 적용됩니다.',
            confirm_lang_reload: '언어가 {lang}(으)로 변경되었습니다.\n지금 페이지를 새로고침하시겠습니까?',
            corner_modal_subtitle: '표시할 코너 버튼을 선택하세요.',
            corner_opt_all: '모두 표시',
            corner_opt_all_desc: '다운로드 기록과 설정 버튼을 함께 표시',
            corner_opt_gear: '설정만',
            corner_opt_gear_desc: '⚙️ 설정 버튼만 표시',
            corner_opt_hist: '기록만',
            corner_opt_hist_desc: '📋 다운로드 기록 버튼만 표시',
            corner_opt_none: '모두 숨기기',
            corner_opt_none_desc: '두 코너 버튼을 모두 숨김 (유저스크립트 메뉴에서 다시 열 수 있음)',
            toast_corner_applied: '👁 코너 버튼 → {mode}',
            tp_title: '커스텀 테마',
            tp_enable: '커스텀 테마 사용',
            tp_enable_hint: '아래 커스텀 색상을 적용하여 현재 프리셋을 덮어씁니다. 선택하지 않으면 테마에 영향을 주지 않고 편집할 수 있습니다.',
            tp_sync: '설정 ↔ 기록 동기화',
            tp_tab_settings: '설정',
            tp_tab_history: '기록',
            tp_quick_label: '빠른 프리셋',
            tp_field_bg: '배경',
            tp_field_text: '텍스트',
            tp_field_sub: '보조 텍스트',
            tp_field_header: '헤더',
            tp_field_border: '테두리',
            tp_lock_note: '설정과 동기화됨 — 위 동기화를 끄면 개별 편집이 가능합니다.',
            menu_date_format: '📅 날짜 형식',
            status_date_asian: '아시아식 (YYYY.MM.DD)',
            status_date_western: '서양식 (DD.MM.YYYY)',
            help_title: '트위터 미디어 스크립트 — 설명서',
            help_content: `
                <p><b>🖱️ 미디어 버튼 (🎞️) — 클래식 모드:</b><br>
                • <b>좌클릭:</b> 트윗 내 모든 이미지/동영상 URL 복사。<br>
                • <b>길게 누르기 (0.5초):</b> 커스텀 접두사 포함 복사(예:<code>[text](url)</code>, Discord용)。<br>
                • <b>중간 클릭:</b> 동영상 플레이어 또는 이미지 라이트박스 열기。<br>
                • <b>우클릭:</b> 모든 미디어 다운로드(구조화된 파일명 자동 생성)。<br>
                  (형식:<code>[twitter] 이름(@ID)_날짜_본문_ID.확장자</code>)<br>
                • <i>⚙️ 설정 → 🖱 클릭 모드 → 사용자 지정 에서 슬롯별 동작을 변경할 수 있습니다。</i></p>
                <hr>
                <p><b>🖱️ 미디어 버튼 (🎞️) — 메뉴 모드(기본값):</b><br>
                • <b>모든 클릭:</b> 액션 메뉴 표시(복사 / 접두사 포함 복사 / 미리보기 / 전체 다운로드)。<br>
                • ⚙️ 설정 → <b>🖱 클릭 모드</b> 에서 전환。</p>
                <hr>
                <p><b>🔗 링크 버튼 (🔗):</b><br>
                • <b>클릭:</b> 트윗 URL 복사(x.com 또는 커스텀 도메인)。<br>
                • <b>길게 누르기:</b> 접두사 + 길게 누르기 도메인 URL 복사(예:fixupx.com)。</p>
                <hr>
                <p><b>📋 다운로드 기록:</b><br>
                • 우클릭 다운로드 후 자동 기록。<br>
                • 오른쪽 상단에 마우스를 올려 → 🕐 클릭으로 기록 패널 열기。<br>
                • 목록/썸네일 보기, 검색, Shift 범위 선택, 일괄 삭제, CSV/JSON 내보내기 지원。</p>
                <hr>
                <p><b>⚙️ 설정 패널:</b><br>
                • 오른쪽 상단에 마우스를 올리면 ⚙️ 와 🕐 버튼이 나타납니다。<br>
                • ⚙️ 클릭으로 설정:클릭 모드, 클릭 도메인, 길게 누르기 도메인, 접두사, 알림 스타일, 날짜 형식, 언어。</p>
                <hr>
                <p><b>⭐ 그룹 &amp; ⊞ 갤러리:</b><br>
                • <b>그룹:</b>⚙️ 설정 → <b>⭐ Groups</b>에서 <b>Group on Download</b>를 활성화하세요. 우클릭 다운로드 후 화면 가장자리에 ⭐ 버튼이 잠깐 표시됩니다. 호버하거나 클릭하면 부채꼴 선택 메뉴가 열려 미디어를 그룹에 분류할 수 있습니다.<br>
                • <b>갤러리:</b>플로팅 동영상 플레이어 또는 이미지 라이트박스에서 상단의 <b>⊞ 그리드 버튼</b>을 클릭하면 현재 페이지의 모든 미디어를 사이드 패널에서 탐색할 수 있습니다.</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ 면책 조항:</b><br>
                fixupx / vxtwitter 등은 본 스크립트와 무관한 제3자 서비스입니다. 신뢰할 수 있는 도메인만 사용하세요。</p>
            `,
            onboard_title: '⚙ 설정 패널',
            onboard_body:  '오른쪽 상단 모서리에 마우스를 올리면 설정 버튼이 나타납니다. 클릭하면 스크립트 관리자 없이 도메인, 접두사, 언어 등을 빠르게 관리할 수 있습니다.',
            onboard_got_it: '알겠어요!',
            onboard_step1_title: '📋 Twitter / X — 미디어 버튼',
            onboard_step1_body:  '이 스크립트는 사진이나 동영상이 포함된 모든 트윗에 링크 복사 및 다운로드 버튼을 직접 추가합니다. 미디어 트윗에 마우스를 올리면 작업 표시줄에서 확인할 수 있습니다.',
            onboard_next: '다음 →',
            menu_feedback_style:    '🔔 피드백 스타일',
            status_feedback_toast:  '토스트',
            status_feedback_icon:   '아이콘만',
            status_feedback_pulse:  '펄스 링',
            status_feedback_flash:  '플래시 스윕',
            status_feedback_slide:  '슬라이드 업',
            status_on:  '켜짐',
            status_off: '꺼짐',
            toast_feedback_style:   '🔔 피드백 스타일 → ',
            menu_preview:           '👁 미리보기',
            menu_click_mode:        '🖱 클릭 모드',
            status_click_menu:      '메뉴',
            status_click_classic:   '클래식',
            toast_click_mode:       '🖱 클릭 모드 → ',
            cma_title:                '버튼 동작 사용자 지정',
            cma_slot_click:           '좌클릭',
            cma_slot_longpress:       '길게 누르기',
            cma_slot_mclick:          '휠클릭',
            cma_slot_rclick:          '우클릭',
            cma_section_media:        '🎞️ 미디어 버튼',
            cma_section_link:         '🔗 링크 버튼',
            sp_media_icon_style: '아이콘 스타일',
            sp_media_icon_classic: '클래식',
            sp_media_icon_play: '재생',
            sp_link_icon_style: '아이콘 스타일',
            sp_link_icon_classic: '클래식',
            sp_link_icon_rounded: '라운드',
            sp_link_icon_download: '다운로드',
            sp_media_icon_copy: '복사',
            sp_link_icon_style_short: '링크 아이콘',
            sp_media_icon_style_short: '미디어 아이콘',
            sp_link_icon_copy: '복사',
            sp_media_icon_download: '다운로드',
            cma_action_copy:          '미디어 URL 복사',
            cma_action_copy_prefix:   '접두사와 함께 복사',
            cma_action_preview:       '미리보기',
            cma_action_download_all:  '전체 다운로드',
            cma_action_copy_url:      '트윗 URL 복사',
            cma_action_save_bookmark: '텍스트 북마크로 저장',
            cma_action_none:          '동작 없음',
            pt_group_title:           '⚡ 성능 조정',
            pt_css_simplify_label:    '스타일 단순화',
            pt_css_simplify_desc:     '블러/그림자/그라데이션 전역 제거',
            pt_feed_prune_label:      '피드 메모리 제한',
            pt_feed_prune_desc:       '화면 밖 트윗 동결 (70개 초과 시)',
            sp_group_advanced:      '⚙ 고급 설정',
            sp_backup_settings:     '📤 설정 백업',
            sp_backup_btn:          'JSON 내보내기',
            sp_restore_settings:    '📥 설정 복원',
            sp_restore_btn:         '파일 선택…',
            sp_reset_defaults:      '🔄 기본값으로 재설정',
            sp_reset_confirm:       '모든 설정을 기본값으로 재설정하시겠습니까?\n\n초기화 항목: 복사 형식, 알림 스타일, 도크 스타일, 트리거 거리, 그룹 패널, 버튼 위치/표시, 목록/썸네일 모드.\n\n다운로드 기록과 그룹은 영향받지 않습니다.',
            sp_legacy_grid_label:   '레거시 미디어 그리드',
            sp_legacy_grid_desc:    '프로필 페이지의 레거시 미디어 그리드 레이아웃을 복원합니다. 적용하려면 페이지를 새로고침해야 합니다.',
            confirm_legacy_grid_reload: '레거시 미디어 그리드가 {state}되었습니다.<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ 참고:</b> Tampermonkey(안정 버전)를 사용 중이라면 이 기능이 정상 작동하지 않을 수 있습니다.</span><br>' +
                '정상적으로 사용하려면 <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>, ' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>, <span style="color:#e0245e"><b>Violentmonkey</b></span> 등 다른 관리자를 사용해 주세요.<br><br>' +
                '지금 페이지를 새로고침하여 적용하시겠습니까?',
            btn_legacy_grid_later:      '나중에',
            btn_legacy_grid_reload_now: '지금 새로고침',
            toast_legacy_grid_pending: '변경 사항이 저장되었습니다. 페이지를 새로고침하면 적용됩니다.',
            sp_bearer_title:        '🔑 커스텀 Bearer Token',
            sp_bearer_detect:       '자동 감지',
            sp_bearer_detecting:    '감지 중…',
            sp_bearer_clear:        '지우기',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…  (선택사항, 401/403 대체용)',
            sp_bearer_hint:         'Twitter / X 미디어 불러오기(401/403 대체)에만 사용됩니다. 어느 페이지에서나 설정 가능하며 token은 교차 도메인으로 전송되지 않습니다.',
            sp_bearer_manual:       '수동 취득',
            sp_bearer_apply:        '적용',
            sp_bearer_edit:         '편집',
            sp_scan_interval:       '스캔 간격',
            hist_filter_all:        '전체',
            hist_filter_image:      '🖼 이미지',
            hist_filter_video:      '🎬 동영상',
            hist_filter_text:       '📝 텍스트',
            hist_sort_newest:       '최신순',
            hist_sort_oldest:       '오래된순',
            hist_sort_author:       '작성자 A–Z',
            hist_sort_count:        '미디어 많은순',
            hist_search_ph:         '🔍  검색…',
            sp_grp_group:           '⭐  그룹',
            sp_grp_on_dl:           '다운로드 시 그룹화',
            sp_grp_popup_style:      '팝업 스타일',
            sp_grp_popup_style_fan:  '부채꼴',
            sp_grp_popup_style_list: '목록',
            sp_grp_glow_color:      '글로우 색상',
            sp_grp_glow_multi:      '멀티',
            sp_grp_glow_size:       '글로우 크기',
            sp_grp_label_color:     '레이블 색상',
            sp_grp_label_size:      '레이블 텍스트 크기',
            sp_grp_icon_size:       '아이콘 크기',
            sp_grp_manage:          '그룹 관리',
            sp_grp_corner:          '📌  버튼 위치',
            sp_grp_history:         '🗂  기록 패널',
            hist_panel_tooltip:     '숨겨진 기능: 기록 패널의 좌우 양쪽 가장자리에 보이지 않는 핸들이 있습니다 (모양은 아래 "도크 스타일" 설정에 따라 다릅니다). 마우스를 올리거나 클릭하면 패널이 화면 가장자리로 자동으로 숨겨집니다!',
            sp_grp_dock_style:      '도크 스타일',
            sp_grp_hover_delay:     '호버 지연',
            sp_grp_trigger_l:       '트리거 거리 ◀ 왼쪽',
            sp_grp_trigger_r:       '트리거 거리 ▶ 오른쪽',
            sp_dock_notch:          '노치',
            sp_dock_ruler:          '룰러',
            sp_dock_ghost:          '고스트',
            sp_dock_pill:           '필',
            sp_dock_arrow:          '화살표',
            sp_dock_dots:           '점',
            sp_grp_modal_title:     '그룹 관리',
            sp_grp_modal_new:       '+ 새로만들기',
            sp_grp_fan_mask:           '부채꼴 배경 마스크',
            sp_grp_fan_mask_color:     '마스크 색상',
            sp_grp_fan_mask_alpha:     '마스크 불투명도',
            sp_grp_fan_mask_span:      '마스크 확산 각도',
            sp_grp_fan_mask_radius:    '마스크 크기',
            sp_dock_remember_pos:      '가장자리 위치 기억',
            sp_dock_remember_pos_desc: '끌어서 이동한 패널의 Y 위치를 기억합니다',
            sp_theme_sync:             '패널 테마 동기화',
            sp_theme_sync_desc:        '끄면 설정 패널과 다운로드 기록 패널이 각각 다른 다크/라이트 상태를 기억합니다',
            sp_dock_reset_pos:         '패널 위치 재설정',
            sp_dock_reset_pos_desc:    '패널을 기본 좌표로 복원합니다 (도크 방향은 유지)',
            sp_dock_reset_btn:         '초기화',
        },
        'es': {
            langName: 'Español',
            menu_domain_click: '🔗 Configurar comportamiento de "clic"',
            menu_domain_long: '🔗 Configurar dominio de "pulsación larga"',
            menu_prefix: '⚙️ Configurar prefijo personalizado (Discord)',
            menu_lang: '🌐 Cambiar idioma (Change Language)',
            menu_help: '📖 Ayuda / Manual',
            menu_corner_btns: '👁 Botones de esquina',
            prompt_prefix: 'Ingrese el prefijo personalizado (ej. [texto]):',
            prompt_lang: 'Ingrese un número para seleccionar el idioma:\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: 'Seleccione el número del dominio:\n',
            status_default: 'Predeterminado (x.com)',
            status_custom: 'Personalizado',
            btn_tooltip: 'Clic izq: Copiar enlaces de medios\nClic central: Ver video / Galería de imágenes\nClic der: Descargar archivos',
            btn_tooltip_menu: 'Clic: Abrir menú de acciones\n(Copiar / Copiar con prefijo / Vista previa / Descargar todo)',
            sp_grid_media_btn_label: 'Botones de Grid de Medios',
            sp_grid_media_btn_desc: 'Mostrar botones de copiar/vista previa/descargar en miniaturas de /media',
            grid_btn_copy_tip: 'Copiar enlace del medio (mantén pulsado para copiar con prefijo, o el menú si hay varios)',
            grid_btn_share_tip: 'Copiar enlace del tweet (mantén pulsado para copiar con prefijo)',
            grid_btn_preview_tip: 'Vista previa en galería',
            grid_btn_download_tip: 'Descargar medio (mantén pulsado para el menú)',
            toast_grid_media_copied: '¡Enlace copiado!',
            toast_grid_media_saved: '¡Medio guardado!',
            grid_menu_item_image: 'Imagen {n}',
            grid_menu_item_video: 'Video {n}',
            avatar_media_btn_tip: 'Ir a la página /media',
            sp_avatar_media_btn_label: 'Atajo de Medios del Avatar',
            sp_avatar_media_btn_desc: 'Mostrar una insignia sutil junto a los avatares para abrir la página /media de ese usuario',
            sp_custom_filename_label: 'Nombre de archivo personalizado',
            cfn_input_tip: 'Tokens disponibles: {screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\nNota: {ext} no incluye el punto — escribe .{ext} si lo necesitas. {origName} es el código de nombre de archivo original de la CDN de Twitter (p. ej., HP_yBXibUAA2-Qu), sin extensión, vacío si no está disponible. {text} se trunca, pero al combinarlo con otros tokens el nombre del archivo puede seguir siendo demasiado largo.',
            cfn_preview_label: 'Vista previa',
            cfn_preview_empty: '(plantilla vacía — se usará el formato predeterminado)',
            cfn_reset_toast: '↺ Plantilla de nombre de archivo restablecida al valor predeterminado',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: 'Usuario',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: 'Nombre',
            cfn_token_date: '{date}',
            cfn_token_date_desc: 'Fecha',
            cfn_token_id: '{id}',
            cfn_token_id_desc: 'ID del tuit',
            cfn_token_index: '{index}',
            cfn_token_index_desc: 'Índice',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: 'Extensión',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: 'Nombre original',
            cfn_token_text: '{text}',
            cfn_token_text_desc: 'Texto del tuit',
            cfn_token_text_warn_badge: '⚠ largo',
            cfn_token_text_warn_tip: 'La longitud del texto del tuit es impredecible. Aunque aquí se trunca a 20 caracteres, al combinarlo con otros tokens el nombre del archivo puede resultar demasiado largo.',
            sp_collapseall_expand_tip: 'Expandir todos los grupos',
            sp_collapseall_collapse_tip: 'Contraer todos los grupos',
            link_tooltip: 'Clic: Copiar ',
            link_tooltip_long: '\nPulsación larga: Copiar prefijo + ',
            msg_prefix_copied: 'Prefijo copiado',
            msg_copied: 'Copiado',
            msg_downloaded: 'Descargado',
            msg_download_failed: '❌ Error al descargar',
            msg_no_media: '❌ Sin medios',
            play_btn_tooltip: 'Clic: Ver video en reproductor flotante',
            btn_switch_to_video: 'Cambiar a vídeo',
            btn_switch_to_image: 'Cambiar a imagen',
            msg_no_video: '❌ Sin video',
            reload_msg: 'Configuración guardada',
            toast_domain_click: '🔗 Dominio de clic → ',
            toast_domain_long: '🔗 Dominio de pulsación larga → ',
            toast_prefix: '⚙️ Prefijo de Discord → ',
            toast_date_fmt: '📅 Formato de fecha → ',
            toast_lang_pending: '🌐 Idioma cambiado. Se aplicará al recargar.',
            confirm_lang_reload: 'Idioma cambiado a {lang}.\n¿Recargar la página ahora?',
            corner_modal_subtitle: 'Elige qué botones de esquina mostrar.',
            corner_opt_all: 'Mostrar ambos',
            corner_opt_all_desc: 'Mostrar el historial de descargas y el botón de ajustes juntos',
            corner_opt_gear: 'Solo ajustes',
            corner_opt_gear_desc: 'Mostrar solo el botón de ajustes ⚙️',
            corner_opt_hist: 'Solo historial',
            corner_opt_hist_desc: 'Mostrar solo el botón de historial de descargas 📋',
            corner_opt_none: 'Ocultar ambos',
            corner_opt_none_desc: 'Ocultar ambos botones de esquina (reábrelos desde el menú del userscript)',
            toast_corner_applied: '👁 Botones de esquina → {mode}',
            tp_title: 'Tema personalizado',
            tp_enable: 'Activar tema personalizado',
            tp_enable_hint: 'Aplica tus colores personalizados de abajo, sobrescribiendo el preset actual. Déjalo sin marcar para seguir editando sin afectar tu tema.',
            tp_sync: 'Sincronizar Ajustes ↔ Historial',
            tp_tab_settings: 'Ajustes',
            tp_tab_history: 'Historial',
            tp_quick_label: 'Preajustes rápidos',
            tp_field_bg: 'Fondo',
            tp_field_text: 'Texto',
            tp_field_sub: 'Texto secundario',
            tp_field_header: 'Encabezado',
            tp_field_border: 'Borde',
            tp_lock_note: 'Sincronizado con Ajustes — desactiva la sincronización arriba para editar de forma independiente.',
            menu_date_format: '📅 Formato de fecha',
            status_date_asian: 'Asiático (YYYY.MM.DD)',
            status_date_western: 'Occidental (DD.MM.YYYY)',
            help_title: 'Botón de copia de medios de Twitter - Manual',
            help_content: `
                <p><b>🖱️ Botón de medios (🎞️) — Modo Clásico:</b><br>
                • <b>Clic izquierdo:</b> Copiar enlaces de imágenes/videos.<br>
                • <b>Pulsación larga (0.5s):</b> Copiar con prefijo personalizado (formato Markdown, para Discord).<br>
                • <b>Clic central:</b> Vista previa——reproductor de video flotante o galería de imágenes.<br>
                • <b>Clic derecho:</b> Descargar todos los medios con nombres de archivo estructurados.<br>
                  (Formato: <code>[twitter] Nombre(@ID)_Fecha_Texto_ID.ext</code>)<br>
                • <i>Personaliza las acciones de cada clic en ⚙️ Ajustes → 🖱 Modo de Clic → Personalizar.</i></p>
                <hr>
                <p><b>🖱️ Botón de medios (🎞️) — Modo Menú (predeterminado):</b><br>
                • <b>Cualquier clic:</b> Abre un menú de acciones (Copiar / Copiar con prefijo / Vista previa / Descargar todo).<br>
                • Cambia en ⚙️ Ajustes → <b>🖱 Modo de Clic</b>.</p>
                <hr>
                <p><b>🔗 Botón de enlace (🔗):</b><br>
                • <b>Clic:</b> Copiar enlace del tweet (predeterminado x.com, o dominio personalizado de clic).<br>
                • <b>Pulsación larga:</b> Copiar con prefijo + dominio de pulsación larga (ej. fixupx).</p>
                <hr>
                <p><b>📋 Historial de descargas:</b><br>
                • Se registra automáticamente tras descargar con clic derecho.<br>
                • Los tweets descargados muestran un 🟢 badge en el botón 🎞️.<br>
                • Clic en 📋 (esquina superior derecha): vista lista/miniaturas, búsqueda, exportar CSV/JSON.</p>
                <hr>
                <p><b>⚙️ Panel de configuración:</b><br>
                • Pasa el ratón por la esquina superior derecha → aparecen 📋 y ⚙️.<br>
                • Configura: modo de clic, dominio de clic, dominio de pulsación larga, prefijo Discord, formato de fecha, idioma, estilo de notificación.</p>
                <hr>
                <p><b>⭐ Grupos &amp; ⊞ Galería:</b><br>
                • <b>Grupos:</b> En ⚙️ Ajustes → <b>⭐ Groups</b>, activa <b>Group on Download</b>. Tras descargar con clic derecho, aparece ⭐ brevemente en el borde de la pantalla — pasa el cursor o haz clic para abrir el selector en abanico y asignar a un grupo.<br>
                • <b>Galería:</b> En el reproductor flotante o el visor de imágenes, haz clic en el <b>botón de cuadrícula ⊞</b> para abrir un panel lateral y explorar todos los medios de la página actual.</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ Aviso legal:</b><br>
                Los dominios de conversión (ej. fixupx, vxtwitter) son servicios de terceros sin relación con este script.</p>
            `,
            onboard_title: '⚙ Panel de Configuración',
            onboard_body:  'Mueve el cursor a la esquina superior derecha para revelar el botón de configuración. Haz clic para gestionar dominios, prefijo, idioma y más sin abrir el administrador de scripts.',
            onboard_got_it: '¡Entendido!',
            onboard_step1_title: '📋 Twitter / X — Botones de Medios',
            onboard_step1_body:  'Este script añade botones de Copiar y Descargar directamente a todos los tweets con fotos o videos. Pasa el cursor sobre cualquier tweet con medios para verlos en la barra de acciones.',
            onboard_next: 'Siguiente →',
            menu_feedback_style:    '🔔 Estilo de Aviso',
            status_feedback_toast:  'Toast',
            status_feedback_icon:   'Solo icono',
            status_feedback_pulse:  'Anillo de Pulso',
            status_feedback_flash:  'Destello Rápido',
            status_feedback_slide:  'Deslizar Texto',
            status_on:  'Activado',
            status_off: 'Desactivado',
            toast_feedback_style:   '🔔 Estilo de Aviso → ',
            menu_preview:           '👁 Vista Previa',
            menu_click_mode:        '🖱 Modo de Clic',
            status_click_menu:      'Menú',
            status_click_classic:   'Clásico',
            toast_click_mode:       '🖱 Modo de Clic → ',
            cma_title:                'Personalizar acciones del botón',
            cma_slot_click:           'Clic simple',
            cma_slot_longpress:       'Pulsación larga',
            cma_slot_mclick:          'Clic central',
            cma_slot_rclick:          'Clic derecho',
            cma_section_media:        '🎞️ Botón de medios',
            cma_section_link:         '🔗 Botón de enlace',
            sp_media_icon_style: 'Estilo de icono',
            sp_media_icon_classic: 'Clásico',
            sp_media_icon_play: 'Reproducir',
            sp_link_icon_style: 'Estilo de icono',
            sp_link_icon_classic: 'Clásico',
            sp_link_icon_rounded: 'Redondeado',
            sp_link_icon_download: 'Descargar',
            sp_media_icon_copy: 'Copiar',
            sp_link_icon_style_short: 'Icono de enlace',
            sp_media_icon_style_short: 'Icono de medios',
            sp_link_icon_copy: 'Copiar',
            sp_media_icon_download: 'Descargar',
            cma_action_copy:          'Copiar URLs de medios',
            cma_action_copy_prefix:   'Copiar con prefijo',
            cma_action_preview:       'Vista previa',
            cma_action_download_all:  'Descargar todo',
            cma_action_copy_url:      'Copiar URL del tuit',
            cma_action_save_bookmark: 'Guardar como marcador de texto',
            cma_action_none:          'Sin acción',
            pt_group_title:           '⚡ Ajustes de rendimiento',
            pt_css_simplify_label:    'Simplificar estilos',
            pt_css_simplify_desc:     'Eliminar difuminado/sombra/degradado globalmente',
            pt_feed_prune_label:      'Límite de memoria del feed',
            pt_feed_prune_desc:       'Congelar tuits fuera de pantalla (>70 nodos)',
            sp_group_advanced:      '⚙ Configuración avanzada',
            sp_backup_settings:     '📤 Copia de seguridad',
            sp_backup_btn:          'Exportar JSON',
            sp_restore_settings:    '📥 Restaurar configuración',
            sp_restore_btn:         'Elegir archivo…',
            sp_reset_defaults:      '🔄 Restablecer valores',
            sp_reset_confirm:       '¿Restablecer toda la configuración?\n\nSe borrará: formato de copia, estilo de notificación, estilo de dock, distancias, apariencia del panel, posición de botones y modo lista/miniaturas.\n\nEl historial y los grupos no se verán afectados.',
            sp_legacy_grid_label:   'Cuadrícula de medios clásica',
            sp_legacy_grid_desc:    'Restaura el diseño clásico de la cuadrícula de medios del perfil. Requiere recargar la página para aplicarse.',
            confirm_legacy_grid_reload: 'Cuadrícula de medios clásica {state}.<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ Nota:</b> Si usas Tampermonkey (versión estable), esta función podría no funcionar.</span><br>' +
                'Para un resultado fiable, usa <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>, ' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>, <span style="color:#e0245e"><b>Violentmonkey</b></span>, u otro gestor.<br><br>' +
                '¿Recargar la página ahora para aplicar los cambios?',
            btn_legacy_grid_later:      'Más tarde',
            btn_legacy_grid_reload_now: 'Recargar ahora',
            toast_legacy_grid_pending: 'Cambio guardado. Recarga la página para aplicarlo.',
            sp_bearer_title:        '🔑 Bearer Token personalizado',
            sp_bearer_detect:       'Detección automática',
            sp_bearer_detecting:    'Detectando…',
            sp_bearer_clear:        'Limpiar',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…  (opcional, para fallback 401/403 en Twitter/X)',
            sp_bearer_hint:         'Solo se usa en Twitter / X para obtener medios (fallback 401/403). Puede configurarse en cualquier página — el token no se envía entre dominios.',
            sp_bearer_manual:       'Manual',
            sp_bearer_apply:        'Aplicar',
            sp_bearer_edit:         'Editar',
            sp_scan_interval:       'Intervalo de escaneo',
            hist_filter_all:        'Todo',
            hist_filter_image:      '🖼 Imagen',
            hist_filter_video:      '🎬 Video',
            hist_filter_text:       '📝 Texto',
            hist_sort_newest:       'Más reciente',
            hist_sort_oldest:       'Más antiguo',
            hist_sort_author:       'Autor A–Z',
            hist_sort_count:        'Más medios',
            hist_search_ph:         '🔍  Buscar…',
            sp_grp_group:           '⭐  Grupos',
            sp_grp_on_dl:           'Agrupar al descargar',
            sp_grp_popup_style:      'Estilo emergente',
            sp_grp_popup_style_fan:  'Abanico',
            sp_grp_popup_style_list: 'Lista',
            sp_grp_glow_color:      'Color de brillo',
            sp_grp_glow_multi:      'Multi',
            sp_grp_glow_size:       'Tamaño del brillo',
            sp_grp_label_color:     'Color de etiqueta',
            sp_grp_label_size:      'Tamaño de texto de etiqueta',
            sp_grp_icon_size:       'Tamaño de icono',
            sp_grp_manage:          'Gestionar grupos',
            sp_grp_corner:          '📌  Posición del botón',
            sp_grp_history:         '🗂  Panel de historial',
            hist_panel_tooltip:     'Función oculta: el Panel de historial tiene un asa invisible en sus bordes izquierdo y derecho (su aspecto depende de tu configuración de "Estilo de dock" abajo). Pasa el cursor o haz clic para ocultar automáticamente el panel en el borde de la pantalla.',
            sp_grp_dock_style:      'Estilo de dock',
            sp_grp_hover_delay:     'Retraso al pasar',
            sp_grp_trigger_l:       'Distancia ◀ Izquierda',
            sp_grp_trigger_r:       'Distancia ▶ Derecha',
            sp_dock_notch:          'Muesca',
            sp_dock_ruler:          'Regla',
            sp_dock_ghost:          'Fantasma',
            sp_dock_pill:           'Píldora',
            sp_dock_arrow:          'Flecha',
            sp_dock_dots:           'Puntos',
            sp_grp_modal_title:     'Gestionar grupos',
            sp_grp_modal_new:       '+ Nuevo',
            sp_grp_fan_mask:           'Fondo en abanico',
            sp_grp_fan_mask_color:     'Color de fondo',
            sp_grp_fan_mask_alpha:     'Opacidad de fondo',
            sp_grp_fan_mask_span:      'Amplitud de fondo',
            sp_grp_fan_mask_radius:    'Tamaño de fondo',
            sp_dock_remember_pos:      'Recordar posición en el borde',
            sp_dock_remember_pos_desc: 'Recuerda la posición Y del panel tras arrastrarlo',
            sp_theme_sync:             'Sincronizar temas de paneles',
            sp_theme_sync_desc:        'Si está desactivado, el panel de ajustes y el de historial recordarán temas claro/oscuro independientes',
            sp_dock_reset_pos:         'Restablecer posición del panel',
            sp_dock_reset_pos_desc:    'Restaura el panel a las coordenadas predeterminadas (mantiene el lado de anclaje)',
            sp_dock_reset_btn:         'Restablecer',
        },
        'pt-BR': {
            langName: 'Português (BR)',
            menu_domain_click: '🔗 Configurar comportamento de "clique"',
            menu_domain_long: '🔗 Configurar domínio de "pressão longa"',
            menu_prefix: '⚙️ Configurar prefixo personalizado (Discord)',
            menu_lang: '🌐 Mudar idioma (Change Language)',
            menu_help: '📖 Ajuda / Manual',
            menu_corner_btns: '👁 Botões de canto',
            prompt_prefix: 'Digite o prefixo personalizado (ex. [texto]):',
            prompt_lang: 'Digite um número para selecionar o idioma:\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: 'Selecione o número do domínio:\n',
            status_default: 'Padrão (x.com)',
            status_custom: 'Personalizado',
            btn_tooltip: 'Clique esq: Copiar links de mídia\nClique do meio: Visualizar vídeo / Galeria de imagens\nClique dir: Baixar arquivos',
            btn_tooltip_menu: 'Clique: Abrir menu de ações\n(Copiar / Copiar com prefixo / Visualizar / Baixar tudo)',
            sp_grid_media_btn_label: 'Botões de Grid de Mídia',
            sp_grid_media_btn_desc: 'Mostrar botões de copiar/visualizar/baixar nas miniaturas de /media',
            grid_btn_copy_tip: 'Copiar link da mídia (pressione e segure para copiar com prefixo, ou o menu se houver vários)',
            grid_btn_share_tip: 'Copiar link do tweet (pressione e segure para copiar com prefixo)',
            grid_btn_preview_tip: 'Visualizar na galeria',
            grid_btn_download_tip: 'Baixar mídia (pressione e segure para o menu)',
            toast_grid_media_copied: 'Link copiado!',
            toast_grid_media_saved: 'Mídia salva!',
            grid_menu_item_image: 'Imagem {n}',
            grid_menu_item_video: 'Vídeo {n}',
            avatar_media_btn_tip: 'Ir para a página /media',
            sp_avatar_media_btn_label: 'Atalho de Mídia do Avatar',
            sp_avatar_media_btn_desc: 'Mostrar um selo discreto ao lado dos avatares para abrir a página /media desse usuário',
            sp_custom_filename_label: 'Nome de arquivo personalizado',
            cfn_input_tip: 'Tokens disponíveis: {screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\nNota: {ext} não inclui o ponto — escreva .{ext} se precisar. {origName} é o código de nome de arquivo original da CDN do Twitter (ex.: HP_yBXibUAA2-Qu), sem extensão, vazio se não estiver disponível. {text} é truncado, mas ao combinar com outros tokens o nome do arquivo ainda pode ficar muito longo.',
            cfn_preview_label: 'Pré-visualização',
            cfn_preview_empty: '(modelo vazio — usará o formato padrão)',
            cfn_reset_toast: '↺ Modelo de nome de arquivo redefinido para o padrão',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: 'Usuário',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: 'Nome',
            cfn_token_date: '{date}',
            cfn_token_date_desc: 'Data',
            cfn_token_id: '{id}',
            cfn_token_id_desc: 'ID do tweet',
            cfn_token_index: '{index}',
            cfn_token_index_desc: 'Índice',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: 'Extensão',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: 'Nome original',
            cfn_token_text: '{text}',
            cfn_token_text_desc: 'Texto do tweet',
            cfn_token_text_warn_badge: '⚠ longo',
            cfn_token_text_warn_tip: 'O comprimento do texto do tweet é imprevisível. Mesmo truncado para 20 caracteres aqui, ao combinar com outros tokens o nome do arquivo ainda pode ficar muito longo.',
            sp_collapseall_expand_tip: 'Expandir todos os grupos',
            sp_collapseall_collapse_tip: 'Recolher todos os grupos',
            link_tooltip: 'Clique: Copiar ',
            link_tooltip_long: '\nPressão longa: Copiar prefixo + ',
            msg_prefix_copied: 'Prefixo copiado',
            msg_copied: 'Copiado',
            msg_downloaded: 'Baixado',
            msg_download_failed: '❌ Falha no download',
            msg_no_media: '❌ Sem mídia',
            play_btn_tooltip: 'Clique: Reproduzir vídeo no player flutuante',
            btn_switch_to_video: 'Trocar para vídeo',
            btn_switch_to_image: 'Trocar para imagem',
            msg_no_video: '❌ Sem vídeo',
            reload_msg: 'Configurações salvas',
            toast_domain_click: '🔗 Domínio de clique → ',
            toast_domain_long: '🔗 Domínio de pressão longa → ',
            toast_prefix: '⚙️ Prefixo do Discord → ',
            toast_date_fmt: '📅 Formato de data → ',
            toast_lang_pending: '🌐 Idioma alterado. Será aplicado ao recarregar.',
            confirm_lang_reload: 'Idioma alterado para {lang}.\nRecarregar a página agora?',
            corner_modal_subtitle: 'Escolha quais botões de canto exibir.',
            corner_opt_all: 'Mostrar ambos',
            corner_opt_all_desc: 'Mostrar o histórico de downloads e o botão de configurações juntos',
            corner_opt_gear: 'Somente configurações',
            corner_opt_gear_desc: 'Mostrar somente o botão de configurações ⚙️',
            corner_opt_hist: 'Somente histórico',
            corner_opt_hist_desc: 'Mostrar somente o botão de histórico de downloads 📋',
            corner_opt_none: 'Ocultar ambos',
            corner_opt_none_desc: 'Ocultar os dois botões de canto (reabra pelo menu do userscript)',
            toast_corner_applied: '👁 Botões de canto → {mode}',
            tp_title: 'Tema personalizado',
            tp_enable: 'Ativar tema personalizado',
            tp_enable_hint: 'Aplica suas cores personalizadas abaixo, substituindo o preset atual. Deixe desmarcado para continuar editando sem afetar seu tema.',
            tp_sync: 'Sincronizar Configurações ↔ Histórico',
            tp_tab_settings: 'Configurações',
            tp_tab_history: 'Histórico',
            tp_quick_label: 'Predefinições rápidas',
            tp_field_bg: 'Plano de fundo',
            tp_field_text: 'Texto',
            tp_field_sub: 'Texto secundário',
            tp_field_header: 'Cabeçalho',
            tp_field_border: 'Borda',
            tp_lock_note: 'Sincronizado com Configurações — desative a sincronização acima para editar de forma independente.',
            menu_date_format: '📅 Formato de data',
            status_date_asian: 'Asiático (YYYY.MM.DD)',
            status_date_western: 'Ocidental (DD.MM.YYYY)',
            help_title: 'Botão de cópia de mídia do Twitter - Manual',
            help_content: `
                <p><b>🖱️ Botão de mídia (🎞️) — Modo Clássico:</b><br>
                • <b>Clique esquerdo:</b> Copiar links de imagens/vídeos.<br>
                • <b>Pressão longa (0.5s):</b> Copiar com prefixo personalizado (formato Markdown, para Discord).<br>
                • <b>Clique do meio:</b> Visualizar——player de vídeo flutuante ou galeria de imagens.<br>
                • <b>Clique direito:</b> Baixar todas as mídias com nomes de arquivo estruturados.<br>
                  (Formato: <code>[twitter] Nome(@ID)_Data_Texto_ID.ext</code>)<br>
                • <i>Personalize as ações de cada clique em ⚙️ Configurações → 🖱 Modo de Clique → Personalizar.</i></p>
                <hr>
                <p><b>🖱️ Botão de mídia (🎞️) — Modo Menu (padrão):</b><br>
                • <b>Qualquer clique:</b> Abre um menu de ações (Copiar / Copiar com prefixo / Visualizar / Baixar tudo).<br>
                • Altere em ⚙️ Configurações → <b>🖱 Modo de Clique</b>.</p>
                <hr>
                <p><b>🔗 Botão de link (🔗):</b><br>
                • <b>Clique:</b> Copiar link do tweet (padrão x.com, ou domínio de clique personalizado).<br>
                • <b>Pressão longa:</b> Copiar com prefixo + domínio de pressão longa (ex. fixupx).</p>
                <hr>
                <p><b>📋 Histórico de downloads:</b><br>
                • Registrado automaticamente após download com clique direito.<br>
                • Tweets baixados mostram 🟢 badge no botão 🎞️.<br>
                • Clique em 📋 (canto superior direito): lista/miniaturas, pesquisa, exportar CSV/JSON.</p>
                <hr>
                <p><b>⚙️ Painel de configurações:</b><br>
                • Passe o mouse pelo canto superior direito → 📋 e ⚙️ aparecem.<br>
                • Configure: modo de clique, domínio de clique, domínio de pressão longa, prefixo Discord, formato de data, idioma, estilo de aviso.</p>
                <hr>
                <p><b>⭐ Grupos &amp; ⊞ Galeria:</b><br>
                • <b>Grupos:</b> Em ⚙️ Configurações → <b>⭐ Groups</b>, ative <b>Group on Download</b>. Após baixar com clique direito, ⭐ aparece brevemente na borda da tela — passe o mouse ou clique para abrir o seletor em leque e atribuir a um grupo.<br>
                • <b>Galeria:</b> No player flutuante ou lightbox de imagens, clique no <b>botão de grade ⊞</b> para abrir um painel lateral e explorar todas as mídias da página atual.</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ Aviso legal:</b><br>
                Os domínios de conversão (ex. fixupx, vxtwitter) são serviços de terceiros sem relação com este script.</p>
            `,
            onboard_title: '⚙ Painel de Configurações',
            onboard_body:  'Passe o mouse no canto superior direito para revelar o botão de configurações. Clique para gerenciar domínios, prefixo, idioma e mais sem abrir o gerenciador de scripts.',
            onboard_got_it: 'Entendi!',
            onboard_step1_title: '📋 Twitter / X — Botões de Mídia',
            onboard_step1_body:  'Este script adiciona botões de Copiar e Baixar diretamente em todos os tweets com fotos ou vídeos. Passe o mouse sobre qualquer tweet com mídia para vê-los na barra de ações.',
            onboard_next: 'Próximo →',
            menu_feedback_style:    '🔔 Estilo de Aviso',
            status_feedback_toast:  'Toast',
            status_feedback_icon:   'Só ícone',
            status_feedback_pulse:  'Anel de Pulso',
            status_feedback_flash:  'Varredura Flash',
            status_feedback_slide:  'Deslizar Texto',
            status_on:  'Ativado',
            status_off: 'Desativado',
            toast_feedback_style:   '🔔 Estilo de Aviso → ',
            menu_preview:           '👁 Pré-visualizar',
            menu_click_mode:        '🖱 Modo de Clique',
            status_click_menu:      'Menu',
            status_click_classic:   'Clássico',
            toast_click_mode:       '🖱 Modo de Clique → ',
            cma_title:                'Personalizar ações do botão',
            cma_slot_click:           'Clique simples',
            cma_slot_longpress:       'Pressão longa',
            cma_slot_mclick:          'Clique do meio',
            cma_slot_rclick:          'Clique direito',
            cma_section_media:        '🎞️ Botão de mídia',
            cma_section_link:         '🔗 Botão de link',
            sp_media_icon_style: 'Estilo do ícone',
            sp_media_icon_classic: 'Clássico',
            sp_media_icon_play: 'Reproduzir',
            sp_link_icon_style: 'Estilo do ícone',
            sp_link_icon_classic: 'Clássico',
            sp_link_icon_rounded: 'Arredondado',
            sp_link_icon_download: 'Baixar',
            sp_media_icon_copy: 'Copiar',
            sp_link_icon_style_short: 'Ícone de link',
            sp_media_icon_style_short: 'Ícone de mídia',
            sp_link_icon_copy: 'Copiar',
            sp_media_icon_download: 'Baixar',
            cma_action_copy:          'Copiar URLs de mídia',
            cma_action_copy_prefix:   'Copiar com prefixo',
            cma_action_preview:       'Pré-visualizar',
            cma_action_download_all:  'Baixar tudo',
            cma_action_copy_url:      'Copiar URL do tweet',
            cma_action_save_bookmark: 'Salvar como marcador de texto',
            cma_action_none:          'Sem ação',
            pt_group_title:           '⚡ Ajustes de desempenho',
            pt_css_simplify_label:    'Simplificar estilos',
            pt_css_simplify_desc:     'Remover desfoque/sombra/gradiente globalmente',
            pt_feed_prune_label:      'Limite de memória do feed',
            pt_feed_prune_desc:       'Congelar tweets fora da tela (>70 nós)',
            sp_group_advanced:      '⚙ Configurações avançadas',
            sp_backup_settings:     '📤 Backup de configurações',
            sp_backup_btn:          'Exportar JSON',
            sp_restore_settings:    '📥 Restaurar configurações',
            sp_restore_btn:         'Escolher arquivo…',
            sp_reset_defaults:      '🔄 Restaurar padrões',
            sp_reset_confirm:       'Restaurar todas as configurações para o padrão?\n\nSerá apagado: formato de cópia, estilo de notificação, estilo de dock, distâncias, aparência do painel, posição dos botões e modo lista/miniaturas.\n\nO histórico de downloads e os grupos não serão afetados.',
            sp_legacy_grid_label:   'Grade de mídia clássica',
            sp_legacy_grid_desc:    'Restaura o layout clássico da grade de mídia do perfil. É necessário recarregar a página para aplicar.',
            confirm_legacy_grid_reload: 'Grade de mídia clássica {state}.<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ Nota:</b> Se você usa o Tampermonkey (versão estável), este recurso pode não funcionar.</span><br>' +
                'Para um resultado confiável, use o <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>, ' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>, <span style="color:#e0245e"><b>Violentmonkey</b></span>, ou outro gerenciador.<br><br>' +
                'Recarregar a página agora para aplicar?',
            btn_legacy_grid_later:      'Mais tarde',
            btn_legacy_grid_reload_now: 'Recarregar agora',
            toast_legacy_grid_pending: 'Alteração salva. Recarregue a página para aplicar.',
            sp_bearer_title:        '🔑 Bearer Token personalizado',
            sp_bearer_detect:       'Detecção automática',
            sp_bearer_detecting:    'Detectando…',
            sp_bearer_clear:        'Limpar',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…  (opcional, para fallback 401/403 no Twitter/X)',
            sp_bearer_hint:         'Usado apenas no Twitter / X para buscar mídia (fallback 401/403). Pode ser configurado em qualquer página — o token não é enviado entre domínios.',
            sp_bearer_manual:       'Manual',
            sp_bearer_apply:        'Aplicar',
            sp_bearer_edit:         'Editar',
            sp_scan_interval:       'Intervalo de varredura',
            hist_filter_all:        'Tudo',
            hist_filter_image:      '🖼 Imagem',
            hist_filter_video:      '🎬 Vídeo',
            hist_filter_text:       '📝 Texto',
            hist_sort_newest:       'Mais recente',
            hist_sort_oldest:       'Mais antigo',
            hist_sort_author:       'Autor A–Z',
            hist_sort_count:        'Mais mídia',
            hist_search_ph:         '🔍  Pesquisar…',
            sp_grp_group:           '⭐  Grupos',
            sp_grp_on_dl:           'Agrupar ao baixar',
            sp_grp_popup_style:      'Estilo pop-up',
            sp_grp_popup_style_fan:  'Leque',
            sp_grp_popup_style_list: 'Lista',
            sp_grp_glow_color:      'Cor do brilho',
            sp_grp_glow_multi:      'Multi',
            sp_grp_glow_size:       'Tamanho do brilho',
            sp_grp_label_color:     'Cor do rótulo',
            sp_grp_label_size:      'Tamanho do texto do rótulo',
            sp_grp_icon_size:       'Tamanho do ícone',
            sp_grp_manage:          'Gerenciar grupos',
            sp_grp_corner:          '📌  Posição do botão',
            sp_grp_history:         '🗂  Painel de histórico',
            hist_panel_tooltip:     'Recurso oculto: o Painel de histórico tem uma alça invisível nas bordas esquerda e direita (a aparência depende da sua configuração de "Estilo de dock" abaixo). Passe o mouse ou clique para ocultar automaticamente o painel na borda da tela.',
            sp_grp_dock_style:      'Estilo do dock',
            sp_grp_hover_delay:     'Atraso ao passar',
            sp_grp_trigger_l:       'Distância ◀ Esquerda',
            sp_grp_trigger_r:       'Distância ▶ Direita',
            sp_dock_notch:          'Entalhe',
            sp_dock_ruler:          'Régua',
            sp_dock_ghost:          'Fantasma',
            sp_dock_pill:           'Pílula',
            sp_dock_arrow:          'Seta',
            sp_dock_dots:           'Pontos',
            sp_grp_modal_title:     'Gerenciar grupos',
            sp_grp_modal_new:       '+ Novo',
            sp_grp_fan_mask:           'Fundo em leque',
            sp_grp_fan_mask_color:     'Cor do fundo',
            sp_grp_fan_mask_alpha:     'Opacidade do fundo',
            sp_grp_fan_mask_span:      'Amplitude do fundo',
            sp_grp_fan_mask_radius:    'Tamanho do fundo',
            sp_dock_remember_pos:      'Lembrar posição na borda',
            sp_dock_remember_pos_desc: 'Lembra a posição Y do painel após arrastá-lo',
            sp_theme_sync:             'Sincronizar temas dos painéis',
            sp_theme_sync_desc:        'Quando desativado, os painéis de Configurações e Histórico lembram temas claro/escuro separados',
            sp_dock_reset_pos:         'Redefinir posição do painel',
            sp_dock_reset_pos_desc:    'Restaura o painel para as coordenadas padrão (lado de ancoragem mantido)',
            sp_dock_reset_btn:         'Redefinir',
        },
        'fr': {
            langName: 'Français',
            menu_domain_click: '🔗 Configurer le comportement "clic"',
            menu_domain_long: '🔗 Configurer le domaine "appui long"',
            menu_prefix: '⚙️ Configurer le préfixe personnalisé (Discord)',
            menu_lang: '🌐 Changer de langue (Change Language)',
            menu_help: '📖 Aide / Manuel',
            menu_corner_btns: '👁 Boutons de coin',
            prompt_prefix: 'Entrez le préfixe personnalisé (ex. [texte]) :',
            prompt_lang: 'Entrez un numéro pour sélectionner la langue :\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: 'Sélectionnez le numéro du domaine :\n',
            status_default: 'Par défaut (x.com)',
            status_custom: 'Personnalisé',
            btn_tooltip: 'Clic gauche : Copier les liens médias\nClic milieu : Aperçu vidéo / Galerie images\nClic droit : Télécharger les fichiers',
            btn_tooltip_menu: 'Clic : Ouvrir le menu d\u2019actions\n(Copier / Copier avec préfixe / Aperçu / Tout télécharger)',
            sp_grid_media_btn_label: 'Boutons Grid Média',
            sp_grid_media_btn_desc: 'Afficher les boutons copier/aperçu/télécharger sur les vignettes de /media',
            grid_btn_copy_tip: 'Copier le lien du m\u00e9dia (appui long pour copier avec pr\u00e9fixe, ou le menu si plusieurs)',
            grid_btn_share_tip: 'Copier le lien du tweet (appui long pour copier avec pr\u00e9fixe)',
            grid_btn_preview_tip: 'Aper\u00e7u dans la galerie',
            grid_btn_download_tip: 'T\u00e9l\u00e9charger le m\u00e9dia (appui long pour le menu)',
            toast_grid_media_copied: 'Lien copi\u00e9\u00a0!',
            toast_grid_media_saved: 'M\u00e9dia enregistr\u00e9\u00a0!',
            grid_menu_item_image: 'Image {n}',
            grid_menu_item_video: 'Vid\u00e9o {n}',
            avatar_media_btn_tip: 'Aller \u00e0 la page /media',
            sp_avatar_media_btn_label: 'Raccourci M\u00e9dia de l\u2019Avatar',
            sp_avatar_media_btn_desc: 'Afficher un badge discret pr\u00e8s des avatars pour ouvrir la page /media de cet utilisateur',
            sp_custom_filename_label: 'Nom de fichier personnalis\u00e9',
            cfn_input_tip: 'Jetons disponibles : {screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\nRemarque : {ext} n\u2019inclut pas le point \u2014 \u00e9crivez .{ext} si n\u00e9cessaire. {origName} est le code de nom de fichier original du CDN Twitter (ex. HP_yBXibUAA2-Qu), sans extension, vide si indisponible. {text} est tronqu\u00e9, mais combin\u00e9 \u00e0 d\u2019autres jetons, le nom de fichier peut rester trop long.',
            cfn_preview_label: 'Aper\u00e7u',
            cfn_preview_empty: '(mod\u00e8le vide \u2014 reviendra au format par d\u00e9faut)',
            cfn_reset_toast: '\u21ba Mod\u00e8le de nom de fichier r\u00e9initialis\u00e9 par d\u00e9faut',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: 'Identifiant',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: 'Nom affich\u00e9',
            cfn_token_date: '{date}',
            cfn_token_date_desc: 'Date',
            cfn_token_id: '{id}',
            cfn_token_id_desc: 'ID du tweet',
            cfn_token_index: '{index}',
            cfn_token_index_desc: 'Index',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: 'Extension',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: 'Nom original',
            cfn_token_text: '{text}',
            cfn_token_text_desc: 'Texte du tweet',
            cfn_token_text_warn_badge: '\u26a0 long',
            cfn_token_text_warn_tip: 'La longueur du texte du tweet est impr\u00e9visible. M\u00eame tronqu\u00e9 \u00e0 20 caract\u00e8res ici, combin\u00e9 \u00e0 d\u2019autres jetons, le nom de fichier peut rester trop long.',
            sp_collapseall_expand_tip: 'D\u00e9velopper tous les groupes',
            sp_collapseall_collapse_tip: 'R\u00e9duire tous les groupes',
            link_tooltip: 'Clic : Copier ',
            link_tooltip_long: '\nAppui long : Copier préfixe + ',
            msg_prefix_copied: 'Préfixe copié',
            msg_copied: 'Copié',
            msg_downloaded: 'Téléchargé',
            msg_download_failed: '❌ Échec du téléchargement',
            msg_no_media: '❌ Aucun média',
            play_btn_tooltip: 'Clic : Lire la vidéo dans le lecteur flottant',
            btn_switch_to_video: 'Basculer vers la vidéo',
            btn_switch_to_image: 'Basculer vers l\'image',
            msg_no_video: '❌ Aucune vidéo',
            reload_msg: 'Paramètres enregistrés',
            toast_domain_click: '🔗 Domaine clic → ',
            toast_domain_long: '🔗 Domaine appui long → ',
            toast_prefix: '⚙️ Préfixe Discord → ',
            toast_date_fmt: '📅 Format de date → ',
            toast_lang_pending: '🌐 Langue modifiée. Sera appliqué au rechargement.',
            confirm_lang_reload: 'Langue changée en {lang}.\nRecharger la page maintenant ?',
            corner_modal_subtitle: 'Choisissez les boutons de coin à afficher.',
            corner_opt_all: 'Afficher les deux',
            corner_opt_all_desc: 'Afficher l\'historique des téléchargements et le bouton des paramètres ensemble',
            corner_opt_gear: 'Paramètres uniquement',
            corner_opt_gear_desc: 'Afficher uniquement le bouton des paramètres ⚙️',
            corner_opt_hist: 'Historique uniquement',
            corner_opt_hist_desc: 'Afficher uniquement le bouton de l\'historique des téléchargements 📋',
            corner_opt_none: 'Masquer les deux',
            corner_opt_none_desc: 'Masquer les deux boutons de coin (rouvrir depuis le menu du userscript)',
            toast_corner_applied: '👁 Boutons de coin → {mode}',
            tp_title: 'Thème personnalisé',
            tp_enable: 'Activer le thème personnalisé',
            tp_enable_hint: 'Applique vos couleurs personnalisées ci-dessous, remplaçant le préréglage actuel. Laissez décoché pour continuer à modifier sans affecter votre thème.',
            tp_sync: 'Synchroniser Paramètres ↔ Historique',
            tp_tab_settings: 'Paramètres',
            tp_tab_history: 'Historique',
            tp_quick_label: 'Préréglages rapides',
            tp_field_bg: 'Arrière-plan',
            tp_field_text: 'Texte',
            tp_field_sub: 'Texte secondaire',
            tp_field_header: 'En-tête',
            tp_field_border: 'Bordure',
            tp_lock_note: 'Synchronisé avec les Paramètres — désactivez la synchronisation ci-dessus pour modifier indépendamment.',
            menu_date_format: '📅 Format de date',
            status_date_asian: 'Asiatique (YYYY.MM.DD)',
            status_date_western: 'Occidental (DD.MM.YYYY)',
            help_title: 'Bouton de copie de médias Twitter - Manuel',
            help_content: `
                <p><b>🖱️ Bouton média (🎞️) — Mode Classique :</b><br>
                • <b>Clic gauche :</b> Copier les liens des images/vidéos.<br>
                • <b>Appui long (0.5s) :</b> Copier avec préfixe personnalisé (format Markdown, pour Discord).<br>
                • <b>Clic central :</b> Aperçu——lecteur vidéo flottant ou galerie d'images.<br>
                • <b>Clic droit :</b> Télécharger tous les médias avec noms de fichiers structurés.<br>
                  (Format : <code>[twitter] Nom(@ID)_Date_Texte_ID.ext</code>)<br>
                • <i>Personnalisez les actions par clic dans ⚙️ Paramètres → 🖱 Mode de Clic → Personnaliser.</i></p>
                <hr>
                <p><b>🖱️ Bouton média (🎞️) — Mode Menu (par défaut) :</b><br>
                • <b>N'importe quel clic :</b> Ouvre un menu d'actions (Copier / Copier avec préfixe / Aperçu / Tout télécharger).<br>
                • Changez dans ⚙️ Paramètres → <b>🖱 Mode de Clic</b>.</p>
                <hr>
                <p><b>🔗 Bouton lien (🔗) :</b><br>
                • <b>Clic :</b> Copier le lien du tweet (x.com par défaut, ou domaine de clic personnalisé).<br>
                • <b>Appui long :</b> Copier avec préfixe + domaine appui long (ex. fixupx).</p>
                <hr>
                <p><b>📋 Historique des téléchargements :</b><br>
                • Enregistré automatiquement après téléchargement par clic droit.<br>
                • Les tweets téléchargés affichent un 🟢 badge sur le bouton 🎞️.<br>
                • Cliquez sur 📋 (coin supérieur droit) : liste/miniatures, recherche, export CSV/JSON.</p>
                <hr>
                <p><b>⚙️ Panneau de paramètres :</b><br>
                • Survolez le coin supérieur droit → 📋 et ⚙️ apparaissent.<br>
                • Configurez : mode de clic, domaine de clic, domaine d'appui long, préfixe Discord, format de date, langue, style de retour.</p>
                <hr>
                <p><b>⭐ Groupes &amp; ⊞ Galerie :</b><br>
                • <b>Groupes :</b> Dans ⚙️ Paramètres → <b>⭐ Groups</b>, activez <b>Group on Download</b>. Après chaque téléchargement par clic droit, ⭐ apparaît brièvement en bordure d'écran — survolez ou cliquez pour ouvrir le sélecteur en éventail et classer le média dans un groupe.<br>
                • <b>Galerie :</b> Dans le lecteur flottant ou le visionneur d'images, cliquez sur le <b>bouton de grille ⊞</b> pour ouvrir un panneau latéral et parcourir tous les médias de la page actuelle.</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ Avertissement :</b><br>
                Les domaines de conversion (ex. fixupx, vxtwitter) sont des services tiers sans lien avec ce script.</p>
            `,
            onboard_title: '⚙ Panneau de Paramètres',
            onboard_body:  'Survolez le coin supérieur droit pour afficher le bouton de paramètres. Cliquez pour gérer les domaines, le préfixe, la langue et plus en utilisant le panneau de paramètres intégré.',
            onboard_got_it: "Compris !",
            onboard_step1_title: '📋 Twitter / X — Boutons Médias',
            onboard_step1_body:  "Ce script ajoute des boutons Copier et Télécharger directement sur tous les tweets avec des photos ou vidéos. Survolez n'importe quel tweet avec des médias pour les voir dans la barre d'actions.",
            onboard_next: 'Suivant →',
            menu_feedback_style:    '🔔 Style de Retour',
            status_feedback_toast:  'Toast',
            status_feedback_icon:   'Icône seul',
            status_feedback_pulse:  'Anneau de Pulse',
            status_feedback_flash:  'Balayage Flash',
            status_feedback_slide:  'Glissement Texte',
            status_on:  'Activé',
            status_off: 'Désactivé',
            toast_feedback_style:   '🔔 Style de Retour → ',
            menu_preview:           '👁 Aperçu',
            menu_click_mode:        '🖱 Mode de Clic',
            status_click_menu:      'Menu',
            status_click_classic:   'Classique',
            toast_click_mode:       '🖱 Mode de Clic → ',
            cma_title:                'Personnaliser les actions du bouton',
            cma_slot_click:           'Clic simple',
            cma_slot_longpress:       'Appui long',
            cma_slot_mclick:          'Clic central',
            cma_slot_rclick:          'Clic droit',
            cma_section_media:        '🎞️ Bouton média',
            cma_section_link:         '🔗 Bouton lien',
            sp_media_icon_style: 'Style d\'icône',
            sp_media_icon_classic: 'Classique',
            sp_media_icon_play: 'Lecture',
            sp_link_icon_style: 'Style d\'icône',
            sp_link_icon_classic: 'Classique',
            sp_link_icon_rounded: 'Arrondi',
            sp_link_icon_download: 'Télécharger',
            sp_media_icon_copy: 'Copier',
            sp_link_icon_style_short: 'Icône de lien',
            sp_media_icon_style_short: 'Icône média',
            sp_link_icon_copy: 'Copier',
            sp_media_icon_download: 'Télécharger',
            cma_action_copy:          'Copier les URL des médias',
            cma_action_copy_prefix:   'Copier avec préfixe',
            cma_action_preview:       'Aperçu',
            cma_action_download_all:  'Tout télécharger',
            cma_action_copy_url:      "Copier l'URL du tweet",
            cma_action_save_bookmark: 'Enregistrer comme signet texte',
            cma_action_none:          'Aucune action',
            pt_group_title:           '⚡ Ajustements de performance',
            pt_css_simplify_label:    'Simplifier les styles',
            pt_css_simplify_desc:     'Supprimer flou/ombre/dégradé globalement',
            pt_feed_prune_label:      'Limite mémoire du fil',
            pt_feed_prune_desc:       'Geler les tweets hors écran (>70 nœuds)',
            sp_group_advanced:      '⚙ Paramètres avancés',
            sp_backup_settings:     '📤 Sauvegarder les paramètres',
            sp_backup_btn:          'Exporter JSON',
            sp_restore_settings:    '📥 Restaurer les paramètres',
            sp_restore_btn:         'Choisir un fichier…',
            sp_reset_defaults:      '🔄 Réinitialiser les paramètres',
            sp_reset_confirm:       'Réinitialiser tous les paramètres aux valeurs par défaut ?\n\nCeci effacera : format de copie, style de notification, style de dock, distances, apparence du panneau, position des boutons et mode liste/vignettes.\n\nL\'historique et les groupes ne seront pas affectés.',
            sp_legacy_grid_label:   'Grille média classique',
            sp_legacy_grid_desc:    'Restaure l\'ancienne mise en page de la grille de médias du profil. Nécessite un rechargement de la page pour s\'appliquer.',
            confirm_legacy_grid_reload: 'Grille média classique {state}.<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ Remarque :</b> si vous utilisez Tampermonkey (stable), cette fonctionnalité peut ne pas fonctionner.</span><br>' +
                'Pour un résultat fiable, utilisez <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>, ' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>, <span style="color:#e0245e"><b>Violentmonkey</b></span>, ou un autre gestionnaire.<br><br>' +
                'Recharger la page maintenant pour appliquer ?',
            btn_legacy_grid_later:      'Plus tard',
            btn_legacy_grid_reload_now: 'Recharger maintenant',
            toast_legacy_grid_pending: 'Modification enregistrée. Rechargez la page pour l\'appliquer.',
            sp_bearer_title:        '🔑 Bearer Token personnalisé',
            sp_bearer_detect:       'Détection automatique',
            sp_bearer_detecting:    'Détection…',
            sp_bearer_clear:        'Effacer',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…  (optionnel, pour le fallback 401/403 sur Twitter/X)',
            sp_bearer_hint:         'Utilisé uniquement sur Twitter / X pour récupérer les médias (fallback 401/403). Configurable sur n\'importe quelle page — le token n\'est pas envoyé entre domaines.',
            sp_bearer_manual:       'Manuel',
            sp_bearer_apply:        'Appliquer',
            sp_bearer_edit:         'Modifier',
            sp_scan_interval:       'Intervalle de scan',
            hist_filter_all:        'Tout',
            hist_filter_image:      '🖼 Image',
            hist_filter_video:      '🎬 Vidéo',
            hist_filter_text:       '📝 Texte',
            hist_sort_newest:       'Plus récent',
            hist_sort_oldest:       'Plus ancien',
            hist_sort_author:       'Auteur A–Z',
            hist_sort_count:        'Plus de médias',
            hist_search_ph:         '🔍  Rechercher…',
            sp_grp_group:           '⭐  Groupes',
            sp_grp_on_dl:           'Grouper au téléchargement',
            sp_grp_popup_style:      'Style de popup',
            sp_grp_popup_style_fan:  'Éventail',
            sp_grp_popup_style_list: 'Liste',
            sp_grp_glow_color:      'Couleur de lueur',
            sp_grp_glow_multi:      'Multi',
            sp_grp_glow_size:       'Taille de lueur',
            sp_grp_label_color:     'Couleur d\'étiquette',
            sp_grp_label_size:      'Taille du texte d\'étiquette',
            sp_grp_icon_size:       'Taille de l\'icône',
            sp_grp_manage:          'Gérer les groupes',
            sp_grp_corner:          '📌  Position du bouton',
            sp_grp_history:         '🗂  Panneau historique',
            hist_panel_tooltip:     "Fonction cachée : le panneau historique possède une poignée invisible sur ses bords gauche et droit (son apparence dépend du réglage « Style du dock » ci-dessous). Survolez ou cliquez pour masquer automatiquement le panneau sur le bord de l'écran !",
            sp_grp_dock_style:      'Style du dock',
            sp_grp_hover_delay:     'Délai au survol',
            sp_grp_trigger_l:       'Distance ◀ Gauche',
            sp_grp_trigger_r:       'Distance ▶ Droite',
            sp_dock_notch:          'Encoche',
            sp_dock_ruler:          'Règle',
            sp_dock_ghost:          'Fantôme',
            sp_dock_pill:           'Pilule',
            sp_dock_arrow:          'Flèche',
            sp_dock_dots:           'Points',
            sp_grp_modal_title:     'Gérer les groupes',
            sp_grp_modal_new:       '+ Nouveau',
            sp_grp_fan_mask:           'Fond en éventail',
            sp_grp_fan_mask_color:     'Couleur du fond',
            sp_grp_fan_mask_alpha:     'Opacité du fond',
            sp_grp_fan_mask_span:      'Étendue du fond',
            sp_grp_fan_mask_radius:    'Taille du fond',
            sp_dock_remember_pos:      'Mémoriser la position au bord',
            sp_dock_remember_pos_desc: 'Mémorise la position Y du panneau après un glisser-déposer',
            sp_theme_sync:             'Synchroniser les thèmes des panneaux',
            sp_theme_sync_desc:        'Si désactivé, les panneaux Paramètres et Historique mémorisent des thèmes clair/sombre distincts',
            sp_dock_reset_pos:         'Réinitialiser la position du panneau',
            sp_dock_reset_pos_desc:    "Restaure le panneau aux coordonnées par défaut (côté d'ancrage conservé)",
            sp_dock_reset_btn:         'Réinitialiser',
        },
        'ru': {
            langName: 'Русский',
            menu_domain_click: '🔗 Настроить поведение «клика»',
            menu_domain_long: '🔗 Настроить домен «долгого нажатия»',
            menu_prefix: '⚙️ Настроить префикс (Discord)',
            menu_lang: '🌐 Изменить язык (Change Language)',
            menu_help: '📖 Справка / Руководство',
            menu_corner_btns: '👁 Угловые кнопки',
            prompt_prefix: 'Введите префикс (например [текст]):',
            prompt_lang: 'Введите номер для выбора языка:\n1. English\n2. 繁體中文\n3. 简体中文\n4. 日本語\n5. 한국어\n6. Español\n7. Português (BR)\n8. Français\n9. Русский\n10. ✏️ Custom Language',
            prompt_domain: 'Выберите номер домена:\n',
            status_default: 'По умолчанию (x.com)',
            status_custom: 'Пользовательский',
            btn_tooltip: 'Левый клик: Копировать медиа-ссылки\nСредний клик: Просмотр видео / Галерея изображений\nПравый клик: Скачать файлы',
            btn_tooltip_menu: 'Клик: Открыть меню действий\n(Копировать / Копировать с префиксом / Просмотр / Скачать всё)',
            sp_grid_media_btn_label: 'Кнопки сетки медиа',
            sp_grid_media_btn_desc: 'Показывать кнопки копирования/просмотра/скачивания на миниатюрах /media',
            grid_btn_copy_tip: 'Копировать ссылку на медиа (долгое нажатие — копия с префиксом, или меню при нескольких)',
            grid_btn_share_tip: 'Копировать ссылку на твит (долгое нажатие — копия с префиксом)',
            grid_btn_preview_tip: 'Просмотр в галерее',
            grid_btn_download_tip: 'Скачать медиа (долгое нажатие — меню)',
            toast_grid_media_copied: 'Ссылка скопирована!',
            toast_grid_media_saved: 'Медиа сохранено!',
            grid_menu_item_image: 'Изображение {n}',
            grid_menu_item_video: 'Видео {n}',
            avatar_media_btn_tip: 'Перейти на страницу /media',
            sp_avatar_media_btn_label: 'Ярлык медиа аватара',
            sp_avatar_media_btn_desc: 'Показывать скромный значок рядом с аватарами для перехода на страницу /media этого пользователя',
            sp_custom_filename_label: 'Пользовательское имя файла',
            cfn_input_tip: 'Доступные токены: {screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\nПримечание: {ext} не включает точку — напишите .{ext}, если нужно. {origName} — это исходный код имени файла Twitter CDN (напр., HP_yBXibUAA2-Qu), без расширения, пусто, если недоступно. {text} обрезается, но в сочетании с другими токенами имя файла всё равно может получиться слишком длинным.',
            cfn_preview_label: 'Предпросмотр',
            cfn_preview_empty: '(пустой шаблон — будет использован формат по умолчанию)',
            cfn_reset_toast: '↺ Шаблон имени файла сброшен к значению по умолчанию',
            cfn_token_screenName: '{screenName}',
            cfn_token_screenName_desc: 'Логин',
            cfn_token_displayName: '{displayName}',
            cfn_token_displayName_desc: 'Отображаемое имя',
            cfn_token_date: '{date}',
            cfn_token_date_desc: 'Дата',
            cfn_token_id: '{id}',
            cfn_token_id_desc: 'ID твита',
            cfn_token_index: '{index}',
            cfn_token_index_desc: 'Индекс',
            cfn_token_ext: '{ext}',
            cfn_token_ext_desc: 'Расширение',
            cfn_token_origName: '{origName}',
            cfn_token_origName_desc: 'Исходное имя',
            cfn_token_text: '{text}',
            cfn_token_text_desc: 'Текст твита',
            cfn_token_text_warn_badge: '⚠ длинно',
            cfn_token_text_warn_tip: 'Длина текста твита непредсказуема. Даже если здесь он обрезан до 20 символов, в сочетании с другими токенами имя файла всё равно может получиться слишком длинным.',
            sp_collapseall_expand_tip: 'Развернуть все группы',
            sp_collapseall_collapse_tip: 'Свернуть все группы',
            link_tooltip: 'Клик: Копировать ',
            link_tooltip_long: '\nДолгое нажатие: Копировать с префиксом ',
            msg_prefix_copied: 'Префикс скопирован',
            msg_copied: 'Скопировано',
            msg_downloaded: 'Загружено',
            msg_download_failed: '❌ Ошибка загрузки',
            msg_no_media: '❌ Нет медиа',
            play_btn_tooltip: 'Клик: Воспроизвести видео во всплывающем плеере',
            btn_switch_to_video: 'Переключить на видео',
            btn_switch_to_image: 'Переключить на изображение',
            msg_no_video: '❌ Нет видео',
            reload_msg: 'Настройки сохранены',
            toast_domain_click: '🔗 Домен клика → ',
            toast_domain_long: '🔗 Домен долгого нажатия → ',
            toast_prefix: '⚙️ Префикс Discord → ',
            toast_date_fmt: '📅 Формат даты → ',
            toast_lang_pending: '🌐 Язык изменён. Применится после перезагрузки.',
            confirm_lang_reload: 'Язык изменён на {lang}.\nПерезагрузить страницу сейчас?',
            corner_modal_subtitle: 'Выберите, какие угловые кнопки показывать.',
            corner_opt_all: 'Показать обе',
            corner_opt_all_desc: 'Показать историю загрузок и кнопку настроек вместе',
            corner_opt_gear: 'Только настройки',
            corner_opt_gear_desc: 'Показать только кнопку настроек ⚙️',
            corner_opt_hist: 'Только история',
            corner_opt_hist_desc: 'Показать только кнопку истории загрузок 📋',
            corner_opt_none: 'Скрыть обе',
            corner_opt_none_desc: 'Скрыть обе угловые кнопки (снова открыть можно из меню userscript)',
            toast_corner_applied: '👁 Угловые кнопки → {mode}',
            tp_title: 'Своя тема',
            tp_enable: 'Включить свою тему',
            tp_enable_hint: 'Применяет ваши цвета ниже, заменяя текущий пресет. Оставьте не отмеченным, чтобы редактировать, не влияя на текущую тему.',
            tp_sync: 'Синхронизировать Настройки ↔ Историю',
            tp_tab_settings: 'Настройки',
            tp_tab_history: 'История',
            tp_quick_label: 'Быстрые пресеты',
            tp_field_bg: 'Фон',
            tp_field_text: 'Текст',
            tp_field_sub: 'Доп. текст',
            tp_field_header: 'Заголовок',
            tp_field_border: 'Граница',
            tp_lock_note: 'Синхронизировано с Настройками — отключите синхронизацию выше для независимого редактирования.',
            menu_date_format: '📅 Формат даты',
            status_date_asian: 'Азиатский (YYYY.MM.DD)',
            status_date_western: 'Западный (DD.MM.YYYY)',
            help_title: 'Кнопка копирования медиа Twitter - Руководство',
            help_content: `
                <p><b>🖱️ Кнопка медиа (🎞️) — Классический режим:</b><br>
                • <b>Левый клик:</b> Копировать ссылки на изображения/видео.<br>
                • <b>Долгое нажатие (0.5с):</b> Копировать с префиксом (формат Markdown, для Discord).<br>
                • <b>Средний клик:</b> Предпросмотр——всплывающий видеоплеер или галерея изображений.<br>
                • <b>Правый клик:</b> Принудительно скачать все медиафайлы со структурированными именами.<br>
                  (Формат: <code>[twitter] Имя(@ID)_Дата_Текст_ID.ext</code>)<br>
                • <i>Настройте действия каждого слота в ⚙️ Настройки → 🖱 Режим клика → Настроить.</i></p>
                <hr>
                <p><b>🖱️ Кнопка медиа (🎞️) — Режим меню (по умолчанию):</b><br>
                • <b>Любой клик:</b> Открывает меню действий (Копировать / Копировать с префиксом / Предпросмотр / Скачать всё).<br>
                • Переключение: ⚙️ Настройки → <b>🖱 Режим клика</b>.</p>
                <hr>
                <p><b>🔗 Кнопка ссылки (🔗):</b><br>
                • <b>Клик:</b> Копировать ссылку на твит (по умолчанию x.com, или пользовательский домен).<br>
                • <b>Долгое нажатие:</b> Копировать с префиксом + домен долгого нажатия (напр. fixupx).</p>
                <hr>
                <p><b>📋 История загрузок:</b><br>
                • Автоматически записывается после правого клика.<br>
                • Скачанные твиты показывают 🟢 значок на кнопке 🎞️.<br>
                • Нажмите 📋 (верхний правый угол): список/миниатюры, поиск, экспорт CSV/JSON.</p>
                <hr>
                <p><b>⚙️ Панель настроек:</b><br>
                • Наведите курсор в правый верхний угол → появятся 📋 и ⚙️.<br>
                • Настройте: режим клика, домен клика, домен долгого нажатия, префикс Discord, формат даты, язык, стиль уведомлений.</p>
                <hr>
                <p><b>⭐ Группы &amp; ⊞ Галерея:</b><br>
                • <b>Группы:</b> В ⚙️ Настройках → <b>⭐ Groups</b> включите <b>Group on Download</b>. После каждой загрузки правой кнопкой мыши у края экрана кратко появляется ⭐ — наведите курсор или нажмите, чтобы открыть веерный выбор и отнести медиа к группе.<br>
                • <b>Галерея:</b> В плавающем видеоплеере или лайтбоксе нажмите <b>кнопку сетки ⊞</b> — откроется боковая панель со всеми медиафайлами текущей страницы.</p>
                <hr>
                <p style="color: #e0245e; font-size: 0.9em;"><b>⚠️ Отказ от ответственности:</b><br>
                Домены конвертации (fixupx, vxtwitter и др.) — сторонние сервисы, не связанные с этим скриптом. Используйте только те домены, которым доверяете.</p>
            `,
            onboard_title: '⚙ Панель настроек',
            onboard_body:  'Наведите курсор в правый верхний угол, чтобы показать кнопку настроек. Нажмите для быстрого управления доменами, префиксом, языком и другими параметрами.',
            onboard_got_it: 'Понятно!',
            onboard_step1_title: '📋 Twitter / X — Кнопки медиа',
            onboard_step1_body:  'Скрипт добавляет кнопки «Копировать» и «Скачать» прямо на все твиты с фотографиями или видео. Наведите курсор на медиатвит, чтобы увидеть их на панели действий.',
            onboard_next: 'Далее →',
            menu_feedback_style:    '🔔 Стиль уведомлений',
            status_feedback_toast:  'Тост',
            status_feedback_icon:   'Только иконка',
            status_feedback_pulse:  'Пульсирующее кольцо',
            status_feedback_flash:  'Вспышка',
            status_feedback_slide:  'Текст снизу',
            status_on:  'Включено',
            status_off: 'Выключено',
            toast_feedback_style:   '🔔 Стиль уведомлений → ',
            menu_preview:           '👁 Предпросмотр',
            menu_click_mode:        '🖱 Режим клика',
            status_click_menu:      'Меню',
            status_click_classic:   'Классика',
            toast_click_mode:       '🖱 Режим клика → ',
            cma_title:                'Настроить действия кнопки',
            cma_slot_click:           'Одиночный клик',
            cma_slot_longpress:       'Долгое нажатие',
            cma_slot_mclick:          'Средний клик',
            cma_slot_rclick:          'Правый клик',
            cma_section_media:        '🎞️ Кнопка медиа',
            cma_section_link:         '🔗 Кнопка ссылки',
            sp_media_icon_style: 'Стиль иконки',
            sp_media_icon_classic: 'Классика',
            sp_media_icon_play: 'Плей',
            sp_link_icon_style: 'Стиль иконки',
            sp_link_icon_classic: 'Классика',
            sp_link_icon_rounded: 'Скруглённый',
            sp_link_icon_download: 'Скачать',
            sp_media_icon_copy: 'Копия',
            sp_link_icon_style_short: 'Значок ссылки',
            sp_media_icon_style_short: 'Значок медиа',
            sp_link_icon_copy: 'Копия',
            sp_media_icon_download: 'Скачать',
            cma_action_copy:          'Скопировать URL медиа',
            cma_action_copy_prefix:   'Скопировать с префиксом',
            cma_action_preview:       'Предпросмотр',
            cma_action_download_all:  'Скачать всё',
            cma_action_copy_url:      'Скопировать URL твита',
            cma_action_save_bookmark: 'Сохранить как текстовую закладку',
            cma_action_none:          'Без действия',
            pt_group_title:           '⚡ Настройки производительности',
            pt_css_simplify_label:    'Упростить стили',
            pt_css_simplify_desc:     'Глобально убрать размытие/тень/градиент',
            pt_feed_prune_label:      'Лимит памяти ленты',
            pt_feed_prune_desc:       'Замораживать твиты за экраном (>70 узлов)',
            sp_group_advanced:      '⚙ Дополнительно',
            sp_backup_settings:     '📤 Резервное копирование',
            sp_backup_btn:          'Экспорт JSON',
            sp_restore_settings:    '📥 Восстановить настройки',
            sp_restore_btn:         'Выбрать файл…',
            sp_reset_defaults:      '🔄 Сбросить настройки',
            sp_reset_confirm:       'Сбросить все настройки до значений по умолчанию?\n\nБудет очищено: формат копирования, стиль уведомлений, стиль панели, расстояния активации, внешний вид, положение кнопок и режим список/миниатюры.\n\nИстория загрузок и группы не затронуты.',
            sp_legacy_grid_label:   'Классическая сетка медиа',
            sp_legacy_grid_desc:    'Восстанавливает старую сетку медиа в профиле. Для применения требуется перезагрузка страницы.',
            confirm_legacy_grid_reload: 'Классическая сетка медиа {state}.<br><br>' +
                '<span style="color:#e0245e"><b>⚠️ Примечание:</b> если вы используете Tampermonkey (стабильная версия), эта функция может не работать.</span><br>' +
                'Для надёжной работы используйте <span style="color:#e0245e"><b>Tampermonkey Beta</b></span>, ' +
                '<span style="color:#e0245e"><b>ScriptCat</b></span>, <span style="color:#e0245e"><b>Violentmonkey</b></span> или другой менеджер.<br><br>' +
                'Перезагрузить страницу сейчас, чтобы применить?',
            btn_legacy_grid_later:      'Позже',
            btn_legacy_grid_reload_now: 'Перезагрузить сейчас',
            toast_legacy_grid_pending: 'Изменение сохранено. Перезагрузите страницу, чтобы применить.',
            sp_bearer_title:        '🔑 Пользовательский Bearer Token',
            sp_bearer_detect:       'Автоопределение',
            sp_bearer_detecting:    'Определение…',
            sp_bearer_clear:        'Очистить',
            sp_bearer_placeholder:  'Bearer AAAAAAAAAA…  (необязательно, для 401/403 на Twitter/X)',
            sp_bearer_hint:         'Используется только на Twitter / X для загрузки медиа (401/403 fallback). Можно настроить на любой странице — token не передаётся между доменами.',
            sp_bearer_manual:       'Вручную',
            sp_bearer_apply:        'Применить',
            sp_bearer_edit:         'Изменить',
            sp_scan_interval:       'Интервал сканирования',
            hist_filter_all:        'Все',
            hist_filter_image:      '🖼 Изображение',
            hist_filter_video:      '🎬 Видео',
            hist_filter_text:       '📝 Текст',
            hist_sort_newest:       'Сначала новые',
            hist_sort_oldest:       'Сначала старые',
            hist_sort_author:       'Автор А–Я',
            hist_sort_count:        'Больше медиа',
            hist_search_ph:         '🔍  Поиск…',
            sp_grp_group:           '⭐  Группы',
            sp_grp_on_dl:           'Группировать при загрузке',
            sp_grp_popup_style:      'Стиль всплывающего меню',
            sp_grp_popup_style_fan:  'Веер',
            sp_grp_popup_style_list: 'Список',
            sp_grp_glow_color:      'Цвет свечения',
            sp_grp_glow_multi:      'Мульти',
            sp_grp_glow_size:       'Размер свечения',
            sp_grp_label_color:     'Цвет подписи',
            sp_grp_label_size:      'Размер текста подписи',
            sp_grp_icon_size:       'Размер значка',
            sp_grp_manage:          'Управление группами',
            sp_grp_corner:          '📌  Позиция кнопки',
            sp_grp_history:         '🗂  Панель истории',
            hist_panel_tooltip:     'Скрытая функция: у Панели истории есть невидимая ручка по левому и правому краям (её вид зависит от настройки "Стиль панели" ниже). Наведите курсор или нажмите, чтобы автоматически скрыть панель у края экрана!',
            sp_grp_dock_style:      'Стиль панели',
            sp_grp_hover_delay:     'Задержка наведения',
            sp_grp_trigger_l:       'Расстояние ◀ Левое',
            sp_grp_trigger_r:       'Расстояние ▶ Правое',
            sp_dock_notch:          'Вырез',
            sp_dock_ruler:          'Линейка',
            sp_dock_ghost:          'Призрак',
            sp_dock_pill:           'Капсула',
            sp_dock_arrow:          'Стрелка',
            sp_dock_dots:           'Точки',
            sp_grp_modal_title:     'Управление группами',
            sp_grp_modal_new:       '+ Создать',
            sp_grp_fan_mask:           'Веерный фон',
            sp_grp_fan_mask_color:     'Цвет фона',
            sp_grp_fan_mask_alpha:     'Непрозрачность фона',
            sp_grp_fan_mask_span:      'Раствор фона',
            sp_grp_fan_mask_radius:    'Размер фона',
            sp_dock_remember_pos:      'Запоминать позицию у края',
            sp_dock_remember_pos_desc: 'Запоминает Y-позицию панели после перетаскивания',
            sp_theme_sync:             'Синхронизировать темы панелей',
            sp_theme_sync_desc:        'Если выключено, панели настроек и истории будут запоминать разные темы (светлая/тёмная) отдельно',
            sp_dock_reset_pos:         'Сбросить позицию панели',
            sp_dock_reset_pos_desc:    'Восстанавливает панель в исходные координаты (сторона стыковки сохраняется)',
            sp_dock_reset_btn:         'Сбросить',
        }
    };

    function getAppLanguage() {
        let lang = GM_getValue(KEY_LANG, null);
        if (!lang) {
            const navLang = navigator.language || 'en';
            if (navLang.toLowerCase().startsWith('zh-cn')) lang = 'zh-CN';
            else if (navLang.toLowerCase().startsWith('zh')) lang = 'zh-TW';
            else if (navLang.toLowerCase().startsWith('ja')) lang = 'ja';
            else if (navLang.toLowerCase().startsWith('ko')) lang = 'ko';
            else if (navLang.toLowerCase().startsWith('pt')) lang = 'pt-BR';
            else if (navLang.toLowerCase().startsWith('fr')) lang = 'fr';
            else if (navLang.toLowerCase().startsWith('ru')) lang = 'ru';
            else if (navLang.toLowerCase().startsWith('es')) lang = 'es';
            else lang = 'en';
            GM_setValue(KEY_LANG, lang);
        }
        if (lang === 'custom') {
            try {
                const raw = GM_getValue(KEY_CUSTOM_LANG, null);
                if (raw) {
                    const parsed = JSON.parse(raw);
                    if (parsed && parsed.langName) {
                        TR['custom'] = parsed;
                        return 'custom';
                    }
                }
            } catch(e) {}
            return 'en';
        }
        return TR[lang] ? lang : 'en';
    }

    const CURRENT_LANG = getAppLanguage();
    const T = TR[CURRENT_LANG];

    let _cachedDateFormat = GM_getValue(KEY_DATE_FORMAT, 'asian');
    function _refreshDateFormatCache() {
        _cachedDateFormat = GM_getValue(KEY_DATE_FORMAT, 'asian');
    }

    let _cachedCustomFilenameEnabled  = GM_getValue(KEY_CUSTOM_FILENAME_ENABLED, false);
    let _cachedCustomFilenameTemplate = GM_getValue(KEY_CUSTOM_FILENAME_TEMPLATE, '');
    function _refreshCustomFilenameCache() {
        _cachedCustomFilenameEnabled  = GM_getValue(KEY_CUSTOM_FILENAME_ENABLED, false);
        _cachedCustomFilenameTemplate = GM_getValue(KEY_CUSTOM_FILENAME_TEMPLATE, '');
    }

    let _cachedClickMode       = GM_getValue(KEY_CLICK_MODE, 'classic');
    let _cachedFeedbackStyle   = GM_getValue(KEY_FEEDBACK_STYLE, 'toast');
    let _cachedClickModeCustom = GM_getValue(KEY_CLICK_MODE_CUSTOM, false);

    function _readSettings() {
        return {
            clickCustom:    GM_getValue(KEY_CLICK_MODE_CUSTOM, false),
            clickDomain:    GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com'),
            prefix:         GM_getValue(KEY_PREFIX_TEXT, '[text]'),
            fmt:            GM_getValue(KEY_DATE_FORMAT, 'asian'),
            fbStyle:        GM_getValue(KEY_FEEDBACK_STYLE, 'toast'),
            dockStyle:      GM_getValue(KEY_DOCK_STYLE, 'ruler'),
            dockHoverDelay: parseInt(GM_getValue(KEY_DOCK_HOVER_DELAY, '500'), 10) || 500,
            dockTriggerL:   parseInt(GM_getValue(KEY_DOCK_TRIGGER_L, '80'), 10) || 80,
            dockTriggerR:   parseInt(GM_getValue(KEY_DOCK_TRIGGER_R, '80'), 10) || 80,
            clickMode:      GM_getValue(KEY_CLICK_MODE, 'classic'),
        };
    }

    function showToast(message, duration = 2500) {
        const existing = document.getElementById('tm-reload-toast');
        if (existing) existing.remove();

        const toast = document.createElement('div');
        toast.id = 'tm-reload-toast';
        toast.style.cssText = `
            position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
            background: #1d9bf0; color: white; padding: 10px 20px;
            border-radius: 16px; box-shadow: 0 8px 16px rgba(0,0,0,0.2);
            font-family: system-ui, -apple-system, sans-serif; font-size: 14px; font-weight: bold;
            z-index: 999999; display: flex; align-items: center; gap: 8px;
            transition: opacity 0.3s ease-in-out; opacity: 0; pointer-events: none;
            white-space: normal; max-width: min(420px, 90vw); word-break: break-word;
            text-align: center;
        `;
        const toastSpan = document.createElement('span');
        toastSpan.textContent = message;
        toast.appendChild(toastSpan);
        document.body.appendChild(toast);

        requestAnimationFrame(() => { toast.style.opacity = '1'; });

        setTimeout(() => {
            if (toast) {
                toast.style.opacity = '0';
                setTimeout(() => toast.remove(), 300);
            }
        }, duration);
    }

    function sanitizeHelpHtml(htmlString, container) {
        const ALLOWED_TAGS = new Set(['P','B','BR','HR','CODE','UL','LI','A','SPAN']);
        const parser = new DOMParser();
        const doc = parser.parseFromString(htmlString, 'text/html');

        function walk(srcNode, destParent) {
            srcNode.childNodes.forEach(child => {
                if (child.nodeType === Node.TEXT_NODE) {
                    destParent.appendChild(document.createTextNode(child.textContent));
                    return;
                }
                if (child.nodeType !== Node.ELEMENT_NODE) return;
                const tag = child.tagName.toUpperCase();
                if (!ALLOWED_TAGS.has(tag)) {
                    destParent.appendChild(document.createTextNode(child.textContent));
                    return;
                }
                const el = document.createElement(tag === 'A' ? 'a' : tag);
                if (tag === 'A') {
                    const href = child.getAttribute('href');
                    if (href && /^https?:\/\//i.test(href)) {
                        el.href = href;
                        el.rel  = 'noopener noreferrer';
                        el.target = '_blank';
                    }
                }
                if ((tag === 'P' || tag === 'SPAN') && child.hasAttribute('style')) {
                    const raw = child.getAttribute('style');
                    const safe = raw.split(';')
                        .filter(rule => /^\s*(color|font-size)\s*:/i.test(rule))
                        .join(';');
                    if (safe) el.setAttribute('style', safe);
                }
                walk(child, el);
                destParent.appendChild(el);
            });
        }

        walk(doc.body, container);
    }

    function showHelpModal() {
        const old = document.getElementById('tm-copy-help-modal');
        if (old) old.remove();

        const curLang = GM_getValue(KEY_LANG, 'en');
        const curT = TR[curLang] || TR['en'];

        const dark = _isDarkTheme();
        const presetKey = _getActiveThemePresetKey();
        const _t = _getActiveThemeColors(presetKey, 'settings');
        const _ex = _getActiveThemeExtras(presetKey, 'settings');
        const C = dark ? {
            overlay: 'rgba(0,0,0,0.82)',
            panel:   _t.bg,
            text:    _t.text,
            border:  _ex.borderB,
            sub:     _t.sub,
        } : {
            overlay: 'rgba(0,0,0,0.7)',
            panel:   _t.bg,
            text:    _t.text,
            border:  _ex.borderB,
            sub:     _t.sub,
        };

        const modal = document.createElement('div');
        modal.id = 'tm-copy-help-modal';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: ${C.overlay}; z-index: 99999;
            display: flex; align-items: center; justify-content: center;
        `;

        const content = document.createElement('div');
        content.style.cssText = `
            background: ${C.panel}; color: ${C.text}; padding: 25px; border-radius: 12px;
            width: 90%; max-width: 500px; box-shadow: 0 4px 15px rgba(0,0,0,0.3);
            font-family: sans-serif; line-height: 1.6; position: relative;
            max-height: 90vh; overflow-y: auto;
        `;

        const title = document.createElement('h2');
        title.textContent = curT.help_title;
        title.style.cssText = `margin-top: 0; border-bottom: 2px solid ${C.border}; padding-bottom: 10px; font-size: 1.2rem; color: ${C.text};`;

        const body = document.createElement('div');
        body.style.cssText = `font-size: 14px; color: ${C.text};`;
        sanitizeHelpHtml(curT.help_content, body);

        if (dark) {
            const style = document.createElement('style');
            style.textContent = `#tm-copy-help-modal code { background: #1e2732; border-radius: 4px; padding: 1px 5px; color: #8b98a5; }`;
            content.appendChild(style);
        }

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.setAttribute('aria-label', 'Close help');
        closeBtn.style.cssText = `
            position: absolute; top: 10px; right: 12px; border: none; background: none;
            width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
            cursor: pointer; color: ${C.sub}; border-radius: 5px;
        `;
        closeBtn.onclick = () => modal.remove();
        modal.onclick = (e) => { if (e.target === modal) modal.remove(); };

        content.appendChild(closeBtn);
        content.appendChild(title);
        content.appendChild(body);
        modal.appendChild(content);
        document.body.appendChild(modal);
    }

    function showLegacyGridConfirmModal(bodyHtml, onConfirm, onCancel) {
        const old = document.getElementById('tm-legacy-grid-confirm-modal');
        if (old) old.remove();

        const curLang = GM_getValue(KEY_LANG, 'en');
        const curT = TR[curLang] || TR['en'];

        const dark = _isDarkTheme();
        const presetKey = _getActiveThemePresetKey();
        const _t = _getActiveThemeColors(presetKey, 'settings');
        const _ex = _getActiveThemeExtras(presetKey, 'settings');
        const C = dark ? {
            overlay: 'rgba(0,0,0,0.82)',
            panel:   _t.bg,
            text:    _t.text,
            border:  _ex.borderB,
            sub:     _t.sub,
        } : {
            overlay: 'rgba(0,0,0,0.7)',
            panel:   _t.bg,
            text:    _t.text,
            border:  _ex.borderB,
            sub:     _t.sub,
        };

        const modal = document.createElement('div');
        modal.id = 'tm-legacy-grid-confirm-modal';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: ${C.overlay}; z-index: 100000;
            display: flex; align-items: center; justify-content: center;
        `;

        const content = document.createElement('div');
        content.style.cssText = `
            background: ${C.panel}; color: ${C.text}; padding: 22px 25px; border-radius: 12px;
            width: 90%; max-width: 440px; box-shadow: 0 4px 15px rgba(0,0,0,0.3);
            font-family: sans-serif; line-height: 1.6; position: relative;
            max-height: 90vh; overflow-y: auto;
        `;

        const title = document.createElement('h2');
        title.textContent = curT.sp_legacy_grid_label || 'Legacy Media Grid';
        title.style.cssText = `margin-top: 0; margin-bottom: 12px; border-bottom: 2px solid ${C.border}; padding-bottom: 10px; font-size: 1.1rem; color: ${C.text};`;

        const body = document.createElement('div');
        body.style.cssText = `font-size: 14px; color: ${C.text};`;
        sanitizeHelpHtml(bodyHtml, body);

        const btnRow = document.createElement('div');
        btnRow.style.cssText = `display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;`;

        const laterBtn = document.createElement('button');
        laterBtn.textContent = curT.btn_legacy_grid_later || 'Later';
        laterBtn.style.cssText = `
            padding: 8px 16px; border-radius: 8px; border: 1px solid ${C.border};
            background: transparent; color: ${C.text}; font-size: 14px; cursor: pointer;
        `;
        let _cancelFired = false;
        const fireCancel = () => {
            if (_cancelFired) return;
            _cancelFired = true;
            modal.remove();
            if (typeof onCancel === 'function') onCancel();
        };

        laterBtn.onclick = () => fireCancel();

        const reloadBtn = document.createElement('button');
        reloadBtn.textContent = curT.btn_legacy_grid_reload_now || 'Reload now';
        reloadBtn.style.cssText = `
            padding: 8px 16px; border-radius: 8px; border: none;
            background: #1d9bf0; color: #fff; font-size: 14px; font-weight: bold; cursor: pointer;
        `;
        reloadBtn.onclick = () => {
            _cancelFired = true;
            modal.remove();
            onConfirm();
        };

        btnRow.appendChild(laterBtn);
        btnRow.appendChild(reloadBtn);

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.setAttribute('aria-label', 'Close');
        closeBtn.style.cssText = `
            position: absolute; top: 10px; right: 12px; border: none; background: none;
            width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
            cursor: pointer; color: ${C.sub}; border-radius: 5px;
        `;
        closeBtn.onclick = () => fireCancel();
        modal.onclick = (e) => { if (e.target === modal) fireCancel(); };

        content.appendChild(closeBtn);
        content.appendChild(title);
        content.appendChild(body);
        content.appendChild(btnRow);
        modal.appendChild(content);
        document.body.appendChild(modal);
    }

    function showLangPickerModal() {
        const old = document.getElementById('tm-lang-picker-modal');
        if (old) old.remove();

        const dark = _isDarkTheme();
        const presetKey = _getActiveThemePresetKey();
        const _t = _getActiveThemeColors(presetKey, 'settings');
        const _ex = _getActiveThemeExtras(presetKey, 'settings');
        const C = dark ? {
            overlay:     'rgba(0,0,0,0.82)',
            panel:       _t.bg,
            text:        _t.text,
            sub:         _t.sub,
            border:      _ex.borderB,
            rowBg:       '#1e2732',
            rowHover:    '#2d3741',
            activeBg:    '#1e3a4f',
            activeBorder:'#1d9bf0',
            activeText:  '#1d9bf0',
            customBg:    '#2b2510',
            customBorder:'#7a5c00',
            customText:  '#f4c430',
            customHover: '#332c10',
        } : {
            overlay:     'rgba(0,0,0,0.72)',
            panel:       _t.bg,
            text:        _t.text,
            sub:         _t.sub,
            border:      _ex.borderB,
            rowBg:       _t.bg,
            rowHover:    _ex.rowHover,
            activeBg:    '#e8f5fe',
            activeBorder:'#1d9bf0',
            activeText:  '#1d9bf0',
            customBg:    '#fffbea',
            customBorder:'#e0a800',
            customText:  '#7a5700',
            customHover: '#fff8d6',
        };

        const LANG_MAP = [
            { code: 'en',    label: 'English' },
            { code: 'zh-TW', label: '繁體中文' },
            { code: 'zh-CN', label: '简体中文' },
            { code: 'ja',    label: '日本語' },
            { code: 'ko',    label: '한국어' },
            { code: 'es',    label: 'Español' },
            { code: 'pt-BR', label: 'Português (BR)' },
            { code: 'fr',    label: 'Français' },
            { code: 'ru',    label: 'Русский' },
        ];

        const currentCode = GM_getValue(KEY_LANG, 'en');

        const modal = document.createElement('div');
        modal.id = 'tm-lang-picker-modal';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: ${C.overlay}; z-index: 999999;
            display: flex; align-items: center; justify-content: center;
            font-family: system-ui, -apple-system, sans-serif;
        `;

        const panel = document.createElement('div');
        panel.style.cssText = `
            background: ${C.panel}; color: ${C.text}; padding: 24px 20px 20px;
            border-radius: 16px; width: 92%; max-width: 420px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.35); position: relative;
        `;

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.style.cssText = `
            position: absolute; top: 12px; right: 14px; border: none; background: none;
            width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
            cursor: pointer; color: ${C.sub}; border-radius: 5px;
        `;
        closeBtn.onclick = () => modal.remove();
        modal.onclick = e => { if (e.target === modal) modal.remove(); };

        const title = document.createElement('h3');
        title.textContent = '🌐 ' + T.menu_lang.replace(/^🌐\s*/, '');
        title.style.cssText = `margin: 0 0 16px; font-size: 1rem; color: ${C.text}; padding-right: 24px;`;

        const list = document.createElement('div');
        list.style.cssText = 'display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px;';

        const applyLang = (code) => {
            modal.remove();
            GM_setValue(KEY_LANG, code);
            GM_deleteValue(KEY_ONBOARDING_DONE);
            const newT = TR[code] || TR['en'];
            const confirmMsg = T.confirm_lang_reload.replace('{lang}', newT.langName);
            if (confirm(confirmMsg)) {
                location.reload();
            } else {
                showToast(newT.toast_lang_pending);
            }
        };

        LANG_MAP.forEach(({ code, label }) => {
            const btn = document.createElement('button');
            const isActive = (code === currentCode);
            btn.textContent = (isActive ? '★ ' : '') + label;
            btn.style.cssText = `
                width: 100%; padding: 9px 14px; border-radius: 9999px; text-align: left;
                border: 2px solid ${isActive ? C.activeBorder : C.border};
                background: ${isActive ? C.activeBg : C.rowBg};
                color: ${isActive ? C.activeText : C.text};
                font-size: 14px; font-weight: ${isActive ? '700' : '400'};
                cursor: pointer; transition: border-color 0.15s, background 0.15s;
            `;
            btn.onmouseenter = () => {
                if (!isActive) { btn.style.borderColor = C.sub; btn.style.background = C.rowHover; }
            };
            btn.onmouseleave = () => {
                if (!isActive) { btn.style.borderColor = C.border; btn.style.background = C.rowBg; }
            };
            btn.onclick = () => applyLang(code);
            list.appendChild(btn);
        });

        const hr = document.createElement('hr');
        hr.style.cssText = `border: none; border-top: 1px solid ${C.border}; margin: 4px 0 10px;`;

        const customBtn = document.createElement('button');
        const hasCustom = !!GM_getValue(KEY_CUSTOM_LANG, null);
        const isCustomActive = (currentCode === 'custom');
        customBtn.textContent = (isCustomActive ? '★ ' : '') + '✏️ Custom Language' + (hasCustom && !isCustomActive ? ' (loaded)' : '');
        customBtn.style.cssText = `
            width: 100%; padding: 9px 14px; border-radius: 9999px; text-align: left;
            border: 2px solid ${isCustomActive ? C.activeBorder : C.customBorder};
            background: ${isCustomActive ? C.activeBg : C.customBg};
            color: ${isCustomActive ? C.activeText : C.customText};
            font-size: 14px; font-weight: ${isCustomActive ? '700' : '500'};
            cursor: pointer; transition: border-color 0.15s, background 0.15s;
        `;
        customBtn.onmouseenter = () => {
            if (!isCustomActive) customBtn.style.background = C.customHover;
        };
        customBtn.onmouseleave = () => {
            if (!isCustomActive) customBtn.style.background = C.customBg;
        };
        customBtn.onclick = () => {
            modal.remove();
            showCustomLangPanel();
        };

        panel.appendChild(closeBtn);
        panel.appendChild(title);
        panel.appendChild(list);
        panel.appendChild(hr);
        panel.appendChild(customBtn);
        modal.appendChild(panel);
        document.body.appendChild(modal);
    }

    function showCornerButtonsModal() {
        const old = document.getElementById('tm-corner-btns-modal');
        if (old) old.remove();

        const dark = _isDarkTheme();
        const presetKey = _getActiveThemePresetKey();
        const _t = _getActiveThemeColors(presetKey, 'settings');
        const _ex = _getActiveThemeExtras(presetKey, 'settings');
        const C = dark ? {
            overlay: 'rgba(0,0,0,0.82)', panel: _t.bg, text: _t.text, sub: _t.sub, border: _ex.borderB,
            rowBg: '#1e2732', rowHover: '#2d3741',
            activeBg: '#1e3a4f', activeBorder: '#1d9bf0', activeText: '#1d9bf0',
        } : {
            overlay: 'rgba(0,0,0,0.72)', panel: _t.bg, text: _t.text, sub: _t.sub, border: _ex.borderB,
            rowBg: _t.bg, rowHover: _ex.rowHover,
            activeBg: '#e8f5fe', activeBorder: '#1d9bf0', activeText: '#1d9bf0',
        };

        const MODE_MAP = [
            { mode: 'all',  label: T.corner_opt_all,  desc: T.corner_opt_all_desc },
            { mode: 'gear', label: T.corner_opt_gear, desc: T.corner_opt_gear_desc },
            { mode: 'hist', label: T.corner_opt_hist, desc: T.corner_opt_hist_desc },
            { mode: 'none', label: T.corner_opt_none, desc: T.corner_opt_none_desc },
        ];
        const currentMode = _getGearDisplayMode();

        const modal = document.createElement('div');
        modal.id = 'tm-corner-btns-modal';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: ${C.overlay}; z-index: 999999;
            display: flex; align-items: center; justify-content: center;
            font-family: system-ui, -apple-system, sans-serif;
        `;

        const panel = document.createElement('div');
        panel.style.cssText = `
            background: ${C.panel}; color: ${C.text}; padding: 24px 20px 20px;
            border-radius: 16px; width: 92%; max-width: 420px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.35); position: relative;
        `;

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.style.cssText = `
            position: absolute; top: 12px; right: 14px; border: none; background: none;
            width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
            cursor: pointer; color: ${C.sub}; border-radius: 5px;
        `;
        closeBtn.onclick = () => modal.remove();
        modal.onclick = e => { if (e.target === modal) modal.remove(); };

        const title = document.createElement('h3');
        title.textContent = '👁 ' + T.menu_corner_btns.replace(/^👁\s*/, '');
        title.style.cssText = `margin: 0 0 4px; font-size: 1rem; color: ${C.text}; padding-right: 24px;`;

        const subtitle = document.createElement('p');
        subtitle.textContent = T.corner_modal_subtitle;
        subtitle.style.cssText = `margin: 0 0 16px; font-size: 12px; color: ${C.sub};`;

        const list = document.createElement('div');
        list.style.cssText = 'display: flex; flex-direction: column; gap: 6px;';

        const applyMode = (mode) => {
            modal.remove();
            _setGearDisplayMode(mode);
            _applyGearDisplayMode();
            showToast(T.toast_corner_applied.replace('{mode}', MODE_MAP.find(m => m.mode === mode).label));
            registerMenus();
        };

        MODE_MAP.forEach(({ mode, label, desc }) => {
            const isActive = (mode === currentMode);
            const btn = document.createElement('button');
            btn.style.cssText = `
                width: 100%; padding: 9px 14px; border-radius: 12px; text-align: left;
                border: 2px solid ${isActive ? C.activeBorder : C.border};
                background: ${isActive ? C.activeBg : C.rowBg};
                cursor: pointer; transition: border-color 0.15s, background 0.15s;
            `;
            const lbl = document.createElement('div');
            lbl.textContent = (isActive ? '★ ' : '') + label;
            lbl.style.cssText = `font-size: 14px; font-weight: ${isActive ? '700' : '400'}; color: ${isActive ? C.activeText : C.text};`;
            const sub = document.createElement('div');
            sub.textContent = desc;
            sub.style.cssText = `font-size: 11px; color: ${C.sub}; margin-top: 2px;`;
            btn.appendChild(lbl);
            btn.appendChild(sub);
            btn.onmouseenter = () => {
                if (!isActive) { btn.style.borderColor = C.sub; btn.style.background = C.rowHover; }
            };
            btn.onmouseleave = () => {
                if (!isActive) { btn.style.borderColor = C.border; btn.style.background = C.rowBg; }
            };
            btn.onclick = () => applyMode(mode);
            list.appendChild(btn);
        });

        panel.appendChild(closeBtn);
        panel.appendChild(title);
        panel.appendChild(subtitle);
        panel.appendChild(list);
        modal.appendChild(panel);
        document.body.appendChild(modal);
    }

    const CUSTOM_LANG_HOW_TO = [
        "Deutsch:       Exportieren → Werte übersetzen → Importieren",
        "Italiano:      Esporta → traduci i valori → Importa",
        "Українська:    Експорт → перекласти значення → Імпорт",
        "ภาษาไทย:       ส่งออก → แปลค่า → นำเข้า",
        "Türkçe:        Dışa aktar → değerleri çevir → İçe aktar",
        "Polski:        Eksportuj → przetłumacz wartości → Importuj",
        "Čeština:       Exportovat → přeložit hodnoty → Importovat",
        "Română:        Exportați → traduceți valorile → Importați",
        "Magyar:        Exportálás → értékek fordítása → Importálás",
        "Ελληνικά:      Εξαγωγή → μετάφραση τιμών → Εισαγωγή",
        "العربية:       تصدير ← ترجمة القيم ← استيراد",
        "עברית:         ייצוא ← תרגום הערכים ← ייבוא",
        "فارسی:         صادر کردن ← ترجمه مقادیر ← وارد کردن",
        "हिन्दी:        निर्यात → मान अनुवाद करें → आयात",
        "বাংলা:         রপ্তানি → মান অনুবাদ করুন → আমদানি",
        "Indonesia:     Ekspor → terjemahkan nilai → Impor",
        "Bahasa Melayu: Eksport → terjemah nilai → Import",
        "Filipino:      I-export → isalin ang mga halaga → I-import",
        "Tiếng Việt:    Xuất → dịch các giá trị → Nhập",
        "Nederlands:    Exporteren → waarden vertalen → Importeren",
        "Svenska:       Exportera → översätt värdena → Importera",
        "Kiswahili:     Hamisha → tafsiri maadili → Ingiza",
    ];

    function buildExportTemplate() {
        const base = Object.assign({}, TR['en']);
        const template = {
            _note: "Translate the VALUES only. Do NOT change the KEYS. Keep {placeholders} like {lang} untouched. Preserve HTML tags and emoji as-is. Set \"langName\" to your language's native name.",
            langName: "My Custom Language",
        };
        Object.keys(base).forEach(key => {
            if (key === 'langName') return;
            const val = base[key];
            template[key] = (typeof val === 'string') ? val.trim() : val;
        });
        return template;
    }

    function showCustomLangPanel() {
        const old = document.getElementById('tm-custom-lang-modal');
        if (old) old.remove();

        const dark = _isDarkTheme();
        const presetKey = _getActiveThemePresetKey();
        const _t = _getActiveThemeColors(presetKey, 'settings');
        const _ex = _getActiveThemeExtras(presetKey, 'settings');
        const C = dark ? {
            overlay:      'rgba(0,0,0,0.82)',
            panel:        _t.bg,
            text:         _t.text,
            sub:          _t.sub,
            border:       _ex.borderB,
            guideBg:      _ex.header,
            guideText:    _t.sub,
            exportBorder: '#1d9bf0',
            exportText:   '#1d9bf0',
            exportBg:     _t.bg,
            exportHover:  '#1e2f3f',
            importBg:     '#1d9bf0',
            importHover:  '#1a8cd8',
            clearBorder:  '#e0245e',
            clearText:    '#e0245e',
            clearBg:      _t.bg,
            clearHover:   '#2a1520',
        } : {
            overlay:      'rgba(0,0,0,0.75)',
            panel:        _t.bg,
            text:         _t.text,
            sub:          _t.sub,
            border:       _ex.borderB,
            guideBg:      _ex.header,
            guideText:    _t.sub,
            exportBorder: '#1d9bf0',
            exportText:   '#1d9bf0',
            exportBg:     _t.bg,
            exportHover:  '#e8f5fe',
            importBg:     '#1d9bf0',
            importHover:  '#1a8cd8',
            clearBorder:  '#e0245e',
            clearText:    '#e0245e',
            clearBg:      _t.bg,
            clearHover:   '#fdf0f2',
        };

        const existingJson = GM_getValue(KEY_CUSTOM_LANG, null);
        const hasCustom = !!existingJson;

        const modal = document.createElement('div');
        modal.id = 'tm-custom-lang-modal';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: ${C.overlay}; z-index: 999999;
            display: flex; align-items: center; justify-content: center;
            font-family: system-ui, -apple-system, sans-serif;
        `;

        const panel = document.createElement('div');
        panel.style.cssText = `
            background: ${C.panel}; color: ${C.text}; padding: 28px 28px 24px;
            border-radius: 16px; width: 95%; max-width: 640px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.35); position: relative;
            max-height: 90vh; overflow-y: auto;
        `;

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.style.cssText = `
            position: absolute; top: 12px; right: 14px; border: none; background: none;
            width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
            cursor: pointer; color: ${C.sub}; border-radius: 5px;
        `;
        closeBtn.onclick = () => modal.remove();
        modal.onclick = e => { if (e.target === modal) modal.remove(); };

        const title = document.createElement('h3');
        title.textContent = '✏️ Custom Language';
        title.style.cssText = `margin: 0 0 6px; font-size: 1.1rem; color: ${C.text};`;

        const statusLine = document.createElement('p');
        statusLine.style.cssText = `margin: 0 0 16px; font-size: 13px; color: ${C.sub};`;

        if (hasCustom) {
            try {
                const parsed = JSON.parse(existingJson);
                statusLine.textContent = '';
                const starText = document.createTextNode('★ Active: ');
                const boldEl   = document.createElement('b');
                boldEl.textContent = parsed.langName || 'Custom';
                statusLine.appendChild(starText);
                statusLine.appendChild(boldEl);
                statusLine.style.color = '#1d9bf0';
            } catch(e) {
                statusLine.textContent = '⚠️ Saved data is corrupted.';
                statusLine.style.color = '#e0245e';
            }
        } else {
            statusLine.textContent = 'No custom language loaded.';
        }

        const hr = document.createElement('hr');
        hr.style.cssText = `border: none; border-top: 1px solid ${C.border}; margin: 0 0 12px;`;

        const guideBox = document.createElement('div');
        guideBox.style.cssText = `
            background: ${C.guideBg}; border: 1px solid ${C.border}; border-radius: 8px;
            padding: 10px 14px; margin-bottom: 16px;
            font-size: 12px; font-family: monospace; line-height: 1.8;
            color: ${C.guideText}; white-space: pre;
        `;
        guideBox.textContent = CUSTOM_LANG_HOW_TO.join('\n');

        const btnRow = document.createElement('div');
        btnRow.style.cssText = 'display: flex; gap: 10px; flex-wrap: wrap;';

        const exportBtn = document.createElement('button');
        exportBtn.textContent = '📤 Export Template';
        exportBtn.style.cssText = `
            flex: 1; min-width: 140px; padding: 10px 16px; border-radius: 9999px;
            border: 2px solid ${C.exportBorder}; background: ${C.exportBg}; color: ${C.exportText};
            font-size: 14px; font-weight: 700; cursor: pointer;
            transition: background 0.15s;
        `;
        exportBtn.onmouseenter = () => { exportBtn.style.background = C.exportHover; };
        exportBtn.onmouseleave = () => { exportBtn.style.background = C.exportBg; };
        exportBtn.onclick = () => {
            const template = buildExportTemplate();
            const jsonStr = JSON.stringify(template, null, 2);
            const blob = new Blob([jsonStr], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'twitter-media-copy-custom-lang.json';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            setTimeout(() => URL.revokeObjectURL(url), 5000);
            showToast('📤 Template exported!');
        };

        const importBtn = document.createElement('button');
        importBtn.textContent = '📥 Import Translation';
        importBtn.style.cssText = `
            flex: 1; min-width: 140px; padding: 10px 16px; border-radius: 9999px;
            border: none; background: ${C.importBg}; color: #fff;
            font-size: 14px; font-weight: 700; cursor: pointer;
            transition: background 0.15s;
        `;
        importBtn.onmouseenter = () => { importBtn.style.background = C.importHover; };
        importBtn.onmouseleave = () => { importBtn.style.background = C.importBg; };
        importBtn.onclick = () => {
            const fileInput = document.createElement('input');
            fileInput.type = 'file';
            fileInput.accept = '.json,application/json';
            fileInput.onchange = e => {
                const file = e.target.files[0];
                if (!file) return;
                const reader = new FileReader();
                reader.onload = ev => {
                    try {
                        const parsed = JSON.parse(ev.target.result);
                        if (!parsed.langName) throw new Error('Missing "langName" field.');
                        const merged = Object.assign({}, TR['en'], parsed);
                        delete merged._note;
                        GM_setValue(KEY_CUSTOM_LANG, JSON.stringify(merged));
                        GM_setValue(KEY_LANG, 'custom');
                        GM_deleteValue(KEY_ONBOARDING_DONE);
                        TR['custom'] = merged;
                        modal.remove();
                        showToast(`✅ Loaded: ${merged.langName}`);
                        if (confirm(`Custom language "${merged.langName}" loaded.\nReload page now to apply?`)) {
                            location.reload();
                        }
                    } catch(err) {
                        alert(`❌ Import failed: ${err.message}\n\nMake sure the file is valid JSON and contains a "langName" field.`);
                    }
                };
                reader.readAsText(file, 'UTF-8');
            };
            document.body.appendChild(fileInput);
            fileInput.click();
            document.body.removeChild(fileInput);
        };

        btnRow.appendChild(exportBtn);
        btnRow.appendChild(importBtn);
        panel.appendChild(closeBtn);
        panel.appendChild(title);
        panel.appendChild(statusLine);
        panel.appendChild(hr);
        panel.appendChild(guideBox);
        panel.appendChild(btnRow);

        if (hasCustom) {
            const clearBtn = document.createElement('button');
            clearBtn.textContent = '🗑️ Clear Custom';
            clearBtn.style.cssText = `
                width: 100%; margin-top: 10px; padding: 9px 16px; border-radius: 9999px;
                border: 2px solid ${C.clearBorder}; background: ${C.clearBg}; color: ${C.clearText};
                font-size: 13px; font-weight: 600; cursor: pointer;
                transition: background 0.15s;
            `;
            clearBtn.onmouseenter = () => { clearBtn.style.background = C.clearHover; };
            clearBtn.onmouseleave = () => { clearBtn.style.background = C.clearBg; };
            clearBtn.onclick = () => {
                if (!confirm('Remove custom language and revert to English?')) return;
                GM_deleteValue(KEY_CUSTOM_LANG);
                GM_setValue(KEY_LANG, 'en');
                delete TR['custom'];
                modal.remove();
                showToast('🗑️ Custom language cleared.');
                if (confirm('Reverted to English.\nReload page now?')) location.reload();
            };
            panel.appendChild(clearBtn);
        }

        modal.appendChild(panel);
        document.body.appendChild(modal);
    }

    function selectDomain(key) {
        let msg = T.prompt_domain;
        DOMAIN_LIST.forEach((d, index) => {
            msg += `${index + 1}. ${d}\n`;
        });
        const input = prompt(msg, "");
        if (input !== null) {
            const index = parseInt(input.trim()) - 1;
            if (!isNaN(index) && DOMAIN_LIST[index]) {
                GM_setValue(key, DOMAIN_LIST[index]);
                return true;
            } else if (input.trim() !== "") {
                const cleanDomain = input.trim();
                if (/^[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}(\.[a-zA-Z0-9\-]{1,63})*\.[a-zA-Z]{2,}$/.test(cleanDomain)) {
                    GM_setValue(key, cleanDomain);
                    return true;
                } else {
                    alert(`Invalid domain: "${cleanDomain}"\nPlease enter a plain domain (e.g. fixupx.com), without http:// or paths.`);
                }
            }
        }
        return false;
    }

    function _getGearDisplayMode() {
        const stored = GM_getValue(KEY_GEAR_DISPLAY_MODE, null);
        if (stored === 'all' || stored === 'gear' || stored === 'hist' || stored === 'none') return stored;
        const legacyVisible = GM_getValue(KEY_GEAR_VISIBLE, true);
        const migrated = legacyVisible ? 'all' : 'none';
        GM_setValue(KEY_GEAR_DISPLAY_MODE, migrated);
        return migrated;
    }
    function _setGearDisplayMode(mode) {
        if (!['all', 'gear', 'hist', 'none'].includes(mode)) return;
        GM_setValue(KEY_GEAR_DISPLAY_MODE, mode);
    }
    
    function _applyGearDisplayMode() {
        const wrapper = document.getElementById('tm-settings-wrapper');
        if (!wrapper) return;
        const mode = _getGearDisplayMode();
        wrapper.style.display = mode === 'none' ? 'none' : '';
        if (mode === 'gear' || mode === 'hist') {
            wrapper.setAttribute('data-solo', mode);
        } else {
            wrapper.removeAttribute('data-solo');
        }
    }

    let menuIds = [];
    function registerMenus() {
        menuIds.forEach(id => GM_unregisterMenuCommand(id));
        menuIds = [];

        const currentPrefix = GM_getValue(KEY_PREFIX_TEXT, '[text]');
        const clickCustom = GM_getValue(KEY_CLICK_MODE_CUSTOM, false);
        const clickDomain = GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com');
        const clickStatusText = clickCustom ? `${T.status_custom} (${clickDomain})` : T.status_default;

        menuIds.push(GM_registerMenuCommand(T.menu_domain_click + ` [${clickStatusText}]`, () => {
            if (!clickCustom) {
                if(selectDomain(KEY_LINK_DOMAIN_CLICK)) {
                    GM_setValue(KEY_CLICK_MODE_CUSTOM, true);
                    const newDomain = GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com');
                    showToast(T.toast_domain_click + newDomain);
                    registerMenus();
                }
            } else {
                GM_setValue(KEY_CLICK_MODE_CUSTOM, false);
                showToast(T.toast_domain_click + 'x.com');
                registerMenus();
            }
        }));

        menuIds.push(GM_registerMenuCommand(T.menu_prefix + ` (${currentPrefix})`, () => {
            const newPrefix = prompt(T.prompt_prefix, currentPrefix);
            if (newPrefix !== null) {
                GM_setValue(KEY_PREFIX_TEXT, newPrefix);
                showToast(T.toast_prefix + (newPrefix || '(empty)'));
                registerMenus();
            }
        }));

        menuIds.push(GM_registerMenuCommand(T.menu_lang + ` [${T.langName}]`, () => {
            showLangPickerModal();
        }));

        const currentFmt = GM_getValue(KEY_DATE_FORMAT, 'asian');
        const fmtStatusText = currentFmt === 'western' ? T.status_date_western : T.status_date_asian;
        menuIds.push(GM_registerMenuCommand(T.menu_date_format + ` [${fmtStatusText}]`, () => {
            const newFmt = currentFmt === 'western' ? 'asian' : 'western';
            GM_setValue(KEY_DATE_FORMAT, newFmt);
            _refreshDateFormatCache();
            const newLabel = newFmt === 'western' ? T.status_date_western : T.status_date_asian;
            showToast(T.toast_date_fmt + newLabel);
            registerMenus();
        }));

        menuIds.push(GM_registerMenuCommand(T.menu_help, showHelpModal));

        menuIds.push(GM_registerMenuCommand('⚙️ Open Settings Panel', () => {
            const wrapper = document.getElementById('tm-settings-wrapper');
            if (!wrapper) return;
            wrapper.style.display = '';
            wrapper.setAttribute('data-open', 'true');
            const panel = document.getElementById('tm-settings-panel');
            if (panel) panel.style.display = 'block';
        }));

        menuIds.push(GM_registerMenuCommand('📋 Open History Panel', () => {
            showHistoryPanel();
        }));

        const _gearModeLabels = { all: T.corner_opt_all, gear: T.corner_opt_gear, hist: T.corner_opt_hist, none: T.corner_opt_none };
        const gearModeText = _gearModeLabels[_getGearDisplayMode()] || _gearModeLabels.all;
        menuIds.push(GM_registerMenuCommand(`${T.menu_corner_btns} [${gearModeText}]`, () => {
            showCornerButtonsModal();
        }));
    }
    registerMenus();

    const _isTwitterDomain = ['twitter.com', 'x.com'].includes(location.hostname);

    const THEME_PRESETS = {
        deep:  { bg: '#16202b', text: '#e7e9ea', sub: '#8b98a5' },
        white: { bg: '#ffffff', text: '#0f1419', sub: '#536471' },
        milk:  { bg: '#fbf8f2', text: '#2b2620', sub: '#7a6f5c' },
    };

    const THEME_PRESET_EXTRAS = {
        deep: {
            header: '#1e2732', borderA: '#2f3336', borderB: '#2f3336',
            rowHover: '#1e2732', inputBg: '#1e2732', groupHdr: '#2f3336',
            thumbBg: '#1e2732', guideBg: '#1e2732', scrollbar: '#2f3336',
        },
        white: {
            header: '#f7f9f9', borderA: '#d8dcdf', borderB: '#eff3f4',
            rowHover: '#f7f9f9', inputBg: '#f7f9f9', groupHdr: '#f7f9f9',
            thumbBg: '#f7f9f9', guideBg: '#f7f9f9', scrollbar: '#eff3f4',
        },
        milk: {
            header: '#f3ede0', borderA: '#e2d9c4', borderB: '#ece4d4',
            rowHover: '#f3ede0', inputBg: '#f3ede0', groupHdr: '#f3ede0',
            thumbBg: '#f3ede0', guideBg: '#f3ede0', scrollbar: '#ece4d4',
        },
    };

    function _isDarkTheme(scope = 'settings') {
        return _getActiveThemePresetKey(scope) === 'deep';
    }

    function _getActiveThemePresetKey(scope = 'settings') {
        const useHistoryKey = scope === 'history' && GM_getValue(KEY_THEME_SYNC, true) === false;
        const key = useHistoryKey ? KEY_APP_THEME_HISTORY : KEY_APP_THEME;
        const stored = GM_getValue(key, null);
        if (stored === 'dark') { GM_setValue(key, 'deep'); return 'deep'; }
        if (stored === 'light') { GM_setValue(key, 'milk'); return 'milk'; }
        if (stored && THEME_PRESETS[stored]) return stored;
        if (useHistoryKey) {
            const globalStored = GM_getValue(KEY_APP_THEME, null);
            if (globalStored === 'dark') return 'deep';
            if (globalStored === 'light') return 'milk';
            if (globalStored && THEME_PRESETS[globalStored]) return globalStored;
        }
        const sysDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        const initial = sysDark ? 'deep' : 'white';
        GM_setValue(KEY_APP_THEME, initial);
        return initial;
    }

    function _toggleAppTheme(scope = 'settings') {
        GM_setValue(KEY_CUSTOM_THEME_ENABLED, false);
        const useHistoryKey = scope === 'history' && GM_getValue(KEY_THEME_SYNC, true) === false;
        const key = useHistoryKey ? KEY_APP_THEME_HISTORY : KEY_APP_THEME;
        const next = _isDarkTheme(scope) ? 'white' : 'deep';
        GM_setValue(key, next);
        return next === 'deep';
    }

    function _setActiveThemePreset(presetKey, scope = 'settings') {
        if (!THEME_PRESETS[presetKey]) return;
        GM_setValue(KEY_CUSTOM_THEME_ENABLED, false);
        const useHistoryKey = scope === 'history' && GM_getValue(KEY_THEME_SYNC, true) === false;
        const key = useHistoryKey ? KEY_APP_THEME_HISTORY : KEY_APP_THEME;
        GM_setValue(key, presetKey);
    }

    function _getActiveThemeColors(presetKey, scope = 'settings') {
        if (GM_getValue(KEY_CUSTOM_THEME_ENABLED, false)) {
            const c = _getCustomThemeColors(scope);
            if (c) return { bg: c.bg, text: c.text, sub: c.sub };
        }
        return { ...(THEME_PRESETS[presetKey] || THEME_PRESETS.white) };
    }

    function _getCustomThemeColors(scope = 'settings') {
        const useHistorySlot = scope === 'history' && GM_getValue(KEY_THEME_SYNC, true) === false;
        const slot = useHistorySlot ? 'history' : 'settings';
        try {
            const all = JSON.parse(GM_getValue(KEY_CUSTOM_THEME_COLORS, '{}'));
            const c = all && all[slot];
            if (c && c.bg && c.text && c.sub && c.header && c.border) return c;
        } catch (_) {  }
        return null;
    }

    function _deriveCustomThemeExtras(header, border) {
        return {
            header, borderA: border, borderB: border,
            rowHover: header, inputBg: header, groupHdr: header,
            thumbBg: header, guideBg: header, scrollbar: border,
        };
    }

    function _getActiveThemeExtras(presetKey, scope = 'settings') {
        if (GM_getValue(KEY_CUSTOM_THEME_ENABLED, false)) {
            const c = _getCustomThemeColors(scope);
            if (c) return _deriveCustomThemeExtras(c.header, c.border);
        }
        return { ...(THEME_PRESET_EXTRAS[presetKey] || THEME_PRESET_EXTRAS.white) };
    }

    function _buildThemePickerFlyout(scope, onSelect) {
        const wrap = document.createElement('span');
        wrap.className = 'tm-theme-picker-wrap';

        const flyout = document.createElement('span');
        flyout.className = 'tm-theme-picker-flyout';

        const customBtn = document.createElement('button');
        customBtn.type = 'button';
        customBtn.className = 'tm-theme-picker-custom-btn' + (GM_getValue(KEY_CUSTOM_THEME_ENABLED, false) ? ' tm-active' : '');
        customBtn.title = T.tp_title || 'Custom Theme';
        customBtn.innerHTML = `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a10 10 0 1 0 0 20c1.1 0 2-.9 2-2 0-.5-.2-1-.5-1.4-.3-.4-.5-.8-.5-1.4 0-1.1.9-2 2-2h2.3a3.7 3.7 0 0 0 3.7-3.7C21 6.5 17 2 12 2z"/><circle cx="7.5" cy="10.5" r="1"/><circle cx="12" cy="7.5" r="1"/><circle cx="16.5" cy="10.5" r="1"/></svg>`;
        customBtn.addEventListener('click', e => {
            e.stopPropagation();
            showThemePaletteModal(scope);
            flyout.classList.remove('tm-force-open');
        });
        flyout.appendChild(customBtn);

        wrap.appendChild(flyout);

        wrap.addEventListener('mouseenter', () => {
            if (wrap._tmCloseTimer) { clearTimeout(wrap._tmCloseTimer); wrap._tmCloseTimer = null; }
            flyout.classList.add('tm-force-open');
        });
        wrap.addEventListener('mouseleave', () => {
            wrap._tmCloseTimer = setTimeout(() => {
                flyout.classList.remove('tm-force-open');
                wrap._tmCloseTimer = null;
            }, 200);
        });

        return wrap;
    }

    function _setCustomThemeColorField(scope, field, hexValue) {
        let all;
        try { all = JSON.parse(GM_getValue(KEY_CUSTOM_THEME_COLORS, '{}')); } catch (_) { all = {}; }
        if (!all || typeof all !== 'object') all = {};
        const slot = scope === 'history' ? 'history' : 'settings';
        if (!all[slot] || typeof all[slot] !== 'object') all[slot] = {};
        all[slot][field] = hexValue;
        GM_setValue(KEY_CUSTOM_THEME_COLORS, JSON.stringify(all));
    }

    function _refreshOpenPanelsForCustomTheme() {
        createSettingsPanel();
        const hp = document.getElementById('tm-hist-panel');
        if (hp) {
            if (typeof hp._tmCleanup === 'function') hp._tmCleanup();
            hp.remove();
            showHistoryPanel();
        }
    }

    function _buildColorSwatchRow(label, initialHex, C, onChange) {
        const row = document.createElement('div');
        row.style.cssText = 'display:flex; align-items:center; justify-content:space-between; padding:8px 0; gap:10px;';

        const lbl = document.createElement('span');
        lbl.textContent = label;
        lbl.style.cssText = `font-size:13px; color:${C.text};`;
        row.appendChild(lbl);

        const swatchWrap = document.createElement('span');
        swatchWrap.style.cssText = 'position:relative; display:inline-flex; align-items:center; gap:8px;';

        const swatch = document.createElement('span');
        swatch.style.cssText = `
            display:inline-block; width:26px; height:26px; border-radius:6px;
            border:2px solid rgba(128,128,128,0.35); background:${initialHex};
            box-shadow:0 1px 3px rgba(0,0,0,0.25); transition: opacity 0.12s ease;
        `;

        const hexLabel = document.createElement('span');
        hexLabel.textContent = initialHex;
        hexLabel.style.cssText = `font-size:11px; font-family:monospace; color:${C.sub}; min-width:56px; transition: opacity 0.12s ease;`;

        const input = document.createElement('input');
        input.type = 'color';
        input.value = initialHex;
        input.style.cssText = 'position:absolute; inset:0; width:26px; height:26px; opacity:0; cursor:pointer; border:none; padding:0;';
        input.addEventListener('input', () => {
            swatch.style.background = input.value;
            hexLabel.textContent = input.value;
            onChange(input.value);
        });

        swatchWrap.appendChild(swatch);
        swatchWrap.appendChild(input);
        swatchWrap.appendChild(hexLabel);
        row.appendChild(swatchWrap);

        return {
            row,
            setDisabled(disabled) {
                input.disabled = disabled;
                input.style.cursor = disabled ? 'default' : 'pointer';
                swatch.style.opacity = disabled ? '0.45' : '1';
                hexLabel.style.opacity = disabled ? '0.45' : '1';
            },
        };
    }

    const QUICK_CUSTOM_PRESETS = {
        milk:   { label: 'Milk',   bg: THEME_PRESETS.milk.bg, text: THEME_PRESETS.milk.text, sub: THEME_PRESETS.milk.sub, header: THEME_PRESET_EXTRAS.milk.header, border: THEME_PRESET_EXTRAS.milk.borderA },
        slate:  { label: 'Slate',  bg: '#eef1f5', text: '#1e293b', sub: '#64748b', header: '#e2e8f0', border: '#cbd5e1' },
        sepia:  { label: 'Sepia',  bg: '#f4ecd8', text: '#4b3621', sub: '#8a7355', header: '#ebe0c7', border: '#d8c7a1' },
        frost:  { label: 'Frost',  bg: '#2b3442', text: '#e8edf3', sub: '#98a7ba', header: '#232b37', border: '#3d4a5c' },
        forest: { label: 'Forest', bg: '#232d27', text: '#e3ede4', sub: '#8faa95', header: '#1c241f', border: '#384a3d' },
        ember:  { label: 'Ember',  bg: '#211611', text: '#f7e4d3', sub: '#c99568', header: '#3a2416', border: '#c2703a' },
        gold:   { label: 'Gold',   bg: '#1a1608', text: '#f5edd0', sub: '#ab9552', header: '#2d260f', border: '#e8c547' },
        cyber:  { label: 'Cyber',  bg: '#0d0b16', text: '#e2e8ff', sub: '#8886b3', header: '#191325', border: '#8b5cf6' },
        volt:   { label: 'Volt',   bg: '#0e0e10', text: '#ececec', sub: '#8a8a90', header: '#1c1c1f', border: '#ef4444' },
    };

    function _applyQuickPreset(scope, presetKey) {
        const preset = QUICK_CUSTOM_PRESETS[presetKey];
        if (!preset) return;
        let all;
        try { all = JSON.parse(GM_getValue(KEY_CUSTOM_THEME_COLORS, '{}')); } catch (_) { all = {}; }
        if (!all || typeof all !== 'object') all = {};
        const slot = scope === 'history' ? 'history' : 'settings';
        all[slot] = { bg: preset.bg, text: preset.text, sub: preset.sub, header: preset.header, border: preset.border };
        GM_setValue(KEY_CUSTOM_THEME_COLORS, JSON.stringify(all));
        GM_setValue(KEY_CUSTOM_THEME_ENABLED, true);
    }

    function showThemePaletteModal(scope = 'settings') {
        const old = document.getElementById('tm-theme-palette-modal');
        if (old) old.remove();

        const dark = _isDarkTheme();
        const presetKey = _getActiveThemePresetKey();
        const _t = _getActiveThemeColors(presetKey, 'settings');
        const _ex = _getActiveThemeExtras(presetKey, 'settings');
        const C = dark
            ? { overlay: 'rgba(0,0,0,0.82)', panel: _t.bg, text: _t.text, sub: _t.sub, border: _ex.borderB }
            : { overlay: 'rgba(0,0,0,0.7)',  panel: _t.bg, text: _t.text, sub: _t.sub, border: _ex.borderB };

        let _settingsForcedOpen = false;
        let _historyForcedOpen = false;
        function _isHistVisible() {
            const hp = document.getElementById('tm-hist-panel');
            if (!hp) return false;
            if (!DockState.side) return true;
            return !!DockState.peeked;
        }
        function _syncPreviewPanel(tab) {
            if (tab === 'settings') {
                const w = document.getElementById('tm-settings-wrapper');
                if (w) {
                    if (w.getAttribute('data-open') !== 'true') {
                        w.setAttribute('data-open', 'true');
                        _settingsForcedOpen = true;
                    }
                } else {
                    createSettingsPanel();
                    const w2 = document.getElementById('tm-settings-wrapper');
                    if (w2) w2.setAttribute('data-open', 'true');
                    _settingsForcedOpen = true;
                }
                if (_historyForcedOpen) {
                    const hp = document.getElementById('tm-hist-panel');
                    if (hp && !GM_getValue(KEY_HIST_PINNED, false)) {
                        if (DockState.side) hp.dispatchEvent(new CustomEvent('tm-hist-force-retract'));
                        else showHistoryPanel();
                    }
                    _historyForcedOpen = false;
                }
            } else {
                if (!_isHistVisible()) {
                    const hp = document.getElementById('tm-hist-panel');
                    if (hp && DockState.side) {
                        hp.dispatchEvent(new CustomEvent('tm-hist-force-peek'));
                    } else {
                        showHistoryPanel();
                    }
                    _historyForcedOpen = true;
                }
                if (_settingsForcedOpen) {
                    const w = document.getElementById('tm-settings-wrapper');
                    if (w) w.setAttribute('data-open', 'false');
                    _settingsForcedOpen = false;
                }
            }
        }

        const FIELDS = [
            ['bg',     T.tp_field_bg     || 'Background'],
            ['text',   T.tp_field_text   || 'Text'],
            ['sub',    T.tp_field_sub    || 'Subtext'],
            ['header', T.tp_field_header || 'Header'],
            ['border', T.tp_field_border || 'Border'],
        ];

        function _readTabColors(tabScope) {
            const c = _getCustomThemeColors(tabScope);
            if (c) return c;
            const base = THEME_PRESETS.milk;
            const ex = THEME_PRESET_EXTRAS.milk;
            return { bg: base.bg, text: base.text, sub: base.sub, header: ex.header, border: ex.borderA };
        }

        let activeTab = scope === 'history' ? 'history' : 'settings';

        const modal = document.createElement('div');
        modal.id = 'tm-theme-palette-modal';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: ${C.overlay}; z-index: 100000;
            display: flex; align-items: center; justify-content: center;
        `;

        const content = document.createElement('div');
        content.style.cssText = `
            background: ${C.panel}; color: ${C.text}; padding: 22px; border-radius: 12px;
            width: 90%; max-width: 380px; box-shadow: 0 4px 15px rgba(0,0,0,0.3);
            font-family: sans-serif; position: relative; max-height: 85vh; overflow-y: auto;
        `;

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.setAttribute('aria-label', 'Close');
        closeBtn.style.cssText = `
            position: absolute; top: 10px; right: 12px; border: none; background: none;
            width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
            cursor: pointer; color: ${C.sub}; border-radius: 5px;
        `;
        function _closePalette() {
            if (_settingsForcedOpen) {
                const w = document.getElementById('tm-settings-wrapper');
                if (w) w.setAttribute('data-open', 'false');
            }
            if (_historyForcedOpen) {
                const hp = document.getElementById('tm-hist-panel');
                if (hp && !GM_getValue(KEY_HIST_PINNED, false)) {
                    if (DockState.side) hp.dispatchEvent(new CustomEvent('tm-hist-force-retract'));
                    else showHistoryPanel();
                }
            }
            modal.remove();
        }
        closeBtn.onclick = _closePalette;
        modal.onclick = (e) => { if (e.target === modal) _closePalette(); };

        const title = document.createElement('h2');
        title.textContent = T.tp_title || 'Custom Theme';
        title.style.cssText = `margin: 0 0 14px; border-bottom: 2px solid ${C.border}; padding-bottom: 10px; font-size: 1.1rem; color: ${C.text};`;

        function _buildToggleRow(labelText, checked, onChange) {
            const r = document.createElement('label');
            r.style.cssText = 'display:flex; align-items:center; justify-content:space-between; padding:6px 0; cursor:pointer; font-size:13px;';
            const s = document.createElement('span');
            s.textContent = labelText;
            s.style.color = C.text;
            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.checked = checked;
            cb.style.cssText = 'width:16px; height:16px; cursor:pointer;';
            cb.addEventListener('change', () => onChange(cb.checked));
            r.appendChild(s);
            r.appendChild(cb);
            return { row: r, input: cb };
        }

        const togglesWrap = document.createElement('div');
        togglesWrap.style.cssText = `border-bottom: 1px solid ${C.border}; padding-bottom: 4px; margin-bottom: 6px;`;
        const enableToggleObj = _buildToggleRow(
            T.tp_enable || 'Enable custom theme',
            GM_getValue(KEY_CUSTOM_THEME_ENABLED, false),
            (checked) => { GM_setValue(KEY_CUSTOM_THEME_ENABLED, checked); _refreshOpenPanelsForCustomTheme(); }
        );
        const enableHint = document.createElement('p');
        enableHint.textContent = T.tp_enable_hint
            || 'Applies your custom colors below, overriding the current preset. Leave unchecked to keep editing without affecting your theme.';
        enableHint.style.cssText = `font-size:11px; color:${C.sub}; margin: 2px 0 6px; line-height:1.4;`;
        const syncToggleObj = _buildToggleRow(
            T.tp_sync || 'Sync Settings \u2194 History',
            GM_getValue(KEY_THEME_SYNC, true),
            (checked) => { GM_setValue(KEY_THEME_SYNC, checked); _renderFields(); _refreshOpenPanelsForCustomTheme(); }
        );
        togglesWrap.appendChild(enableToggleObj.row);
        togglesWrap.appendChild(enableHint);
        togglesWrap.appendChild(syncToggleObj.row);

        const tabBar = document.createElement('div');
        tabBar.style.cssText = 'display:flex; gap:6px; margin: 10px 0 8px;';
        const _tabBtnStyle = (active) => `flex:1; padding:6px 0; border-radius:6px; border:1px solid ${C.border};
            cursor:pointer; font-size:12px; background:${active ? '#1d9bf0' : 'transparent'}; color:${active ? '#fff' : C.text};`;
        const settingsTabBtn = document.createElement('button');
        settingsTabBtn.type = 'button';
        settingsTabBtn.textContent = T.tp_tab_settings || 'Settings';
        const historyTabBtn = document.createElement('button');
        historyTabBtn.type = 'button';
        historyTabBtn.textContent = T.tp_tab_history || 'History';
        tabBar.appendChild(settingsTabBtn);
        tabBar.appendChild(historyTabBtn);

        const quickPicksLabel = document.createElement('div');
        quickPicksLabel.textContent = T.tp_quick_label || 'Quick presets';
        quickPicksLabel.style.cssText = `font-size:11px; color:${C.sub}; margin: 2px 0 6px;`;
        const quickPicksWrap = document.createElement('div');
        quickPicksWrap.style.cssText = 'display:flex; gap:8px; margin-bottom: 4px; flex-wrap: wrap;';
        const _quickBtns = [];
        Object.keys(QUICK_CUSTOM_PRESETS).forEach(pk => {
            const preset = QUICK_CUSTOM_PRESETS[pk];
            const qBtn = document.createElement('button');
            qBtn.type = 'button';
            qBtn.title = preset.label;
            qBtn.style.cssText = `
                width: 26px; height: 26px; border-radius: 50%; cursor: pointer; padding: 0;
                border: 4px solid ${preset.border}; background: ${preset.bg};
                box-shadow: 0 1px 3px rgba(0,0,0,0.2); flex-shrink: 0; transition: transform 0.12s ease, opacity 0.12s ease;
            `;
            qBtn.addEventListener('mouseenter', () => { if (!qBtn.disabled) qBtn.style.transform = 'scale(1.15)'; });
            qBtn.addEventListener('mouseleave', () => { qBtn.style.transform = 'scale(1)'; });
            qBtn.addEventListener('click', () => {
                _applyQuickPreset(activeTab, pk);
                enableToggleObj.input.checked = true;
                _renderFields();
                _refreshOpenPanelsForCustomTheme();
            });
            quickPicksWrap.appendChild(qBtn);
            _quickBtns.push(qBtn);
        });

        const fieldsWrap = document.createElement('div');
        const lockNote = document.createElement('p');
        lockNote.textContent = T.tp_lock_note || 'Synced with Settings — turn off sync above to edit independently.';
        lockNote.style.cssText = `font-size:11px; color:${C.sub}; margin: 8px 0 0; display:none;`;

        let _rows = [];
        function _renderFields() {
            fieldsWrap.innerHTML = '';
            _rows = [];
            const colors = _readTabColors(activeTab);
            FIELDS.forEach(([field, label]) => {
                const swatchRow = _buildColorSwatchRow(label, colors[field] || '#888888', C, (hex) => {
                    _setCustomThemeColorField(activeTab, field, hex);
                    _refreshOpenPanelsForCustomTheme();
                });
                fieldsWrap.appendChild(swatchRow.row);
                _rows.push(swatchRow);
            });
            settingsTabBtn.style.cssText = _tabBtnStyle(activeTab === 'settings');
            historyTabBtn.style.cssText = _tabBtnStyle(activeTab === 'history');
            const locked = activeTab === 'history' && GM_getValue(KEY_THEME_SYNC, true) === true;
            _rows.forEach(r => r.setDisabled(locked));
            _quickBtns.forEach(b => {
                b.disabled = locked;
                b.style.opacity = locked ? '0.4' : '1';
                b.style.cursor = locked ? 'default' : 'pointer';
            });
            lockNote.style.display = locked ? 'block' : 'none';
            _syncPreviewPanel(activeTab);
        }

        settingsTabBtn.onclick = () => { activeTab = 'settings'; _renderFields(); };
        historyTabBtn.onclick  = () => { activeTab = 'history';  _renderFields(); };

        _renderFields();

        content.appendChild(closeBtn);
        content.appendChild(title);
        content.appendChild(togglesWrap);
        content.appendChild(tabBar);
        content.appendChild(quickPicksLabel);
        content.appendChild(quickPicksWrap);
        content.appendChild(fieldsWrap);
        content.appendChild(lockNote);
        modal.appendChild(content);
        document.body.appendChild(modal);
    }

    let _lcRcResizeHandler = null;
    let _spClickAC = null;
    function _applyGearCorner(corner) {
        const wrapper = document.getElementById('tm-settings-wrapper');
        if (!wrapper) return;
        const wMap = {
            tr: { top: '12px',  right: '12px', bottom: 'auto', left: 'auto'  },
            tl: { top: '12px',  right: 'auto', bottom: 'auto', left: '12px'  },
            br: { top: 'auto',  right: '12px', bottom: '12px', left: 'auto'  },
            bl: { top: 'auto',  right: 'auto', bottom: '12px', left: '12px'  },
            tc: { top: '12px',  right: 'auto', bottom: 'auto', left: '50%'   },
            bc: { top: 'auto',  right: 'auto', bottom: '12px', left: '50%'   },
            lc: { top: '50%',   right: 'auto', bottom: 'auto', left: '12px'  },
            rc: { top: '50%',   right: '12px', bottom: 'auto', left: 'auto'  },
            cc: { top: '50%',   right: 'auto', bottom: 'auto', left: '50%'   },
        };
        const wPos = wMap[corner] || wMap['tr'];
        wrapper.style.top    = wPos.top;
        wrapper.style.right  = wPos.right;
        wrapper.style.bottom = wPos.bottom;
        wrapper.style.left   = wPos.left;
        wrapper.style.transform = (corner === 'tc' || corner === 'bc') ? 'translateX(-50%)'
                                : (corner === 'lc' || corner === 'rc') ? 'translateY(-50%)'
                                : (corner === 'cc')                    ? 'translate(-50%, -50%)'
                                : '';

        const panel = document.getElementById('tm-settings-panel');
        if (!panel) return;
        const isBottom = corner === 'br' || corner === 'bl' || corner === 'bc';
        const isLeft   = corner === 'tl' || corner === 'bl' || corner === 'lc';
        const isCenter = corner === 'tc' || corner === 'bc' || corner === 'cc';
        panel.style.top         = isBottom ? 'auto' : 'calc(100% - 21px)';
        panel.style.bottom      = isBottom ? 'calc(100% - 21px)' : 'auto';
        panel.style.right       = isLeft   ? 'auto' : isCenter ? 'auto' : '4px';
        panel.style.left        = isLeft   ? '4px'  : isCenter ? '50%'  : 'auto';
        panel.style.transform = '';
        panel.classList.toggle('tm-panel-hcenter', isCenter);
        panel.classList.toggle('tm-panel-bottom',  isCenter && isBottom);
        if (corner === 'lc') { panel.style.left = '100%'; panel.style.right = 'auto'; }
        if (corner === 'rc') { panel.style.right = '100%'; panel.style.left = 'auto'; }
        panel.style.transformOrigin = (isBottom ? 'bottom' : 'top') + ' ' + (isLeft ? 'left' : isCenter ? 'center' : 'right');

        if (corner === 'lc' || corner === 'rc' || corner === 'cc') {
            const _calcLcRcPos = () => {
                const vpH2    = window.innerHeight;
                const wH2     = wrapper.offsetHeight || 50;
                const pH2     = panel.scrollHeight   || 540;
                const maxH2   = Math.min(pH2, vpH2 - 20);
                const wTVP2   = vpH2 / 2 - wH2 / 2;
                const pTVP2   = Math.max(8, Math.min((vpH2 - maxH2) / 2, vpH2 - maxH2 - 8));
                panel.style.top       = Math.round(pTVP2 - wTVP2) + 'px';
                panel.style.maxHeight = maxH2 + 'px';
                panel.style.overflowY = pH2 > maxH2 ? 'auto' : '';
            };
            _calcLcRcPos();
            if (corner !== 'cc') {
                panel.style.transformOrigin = 'center ' + (corner === 'lc' ? 'left' : 'right');
            }
            const _hdr = panel.querySelector('.tm-sp-header');
            if (_hdr) { _hdr.style.position = 'sticky'; _hdr.style.top = '0'; _hdr.style.zIndex = '2'; }
            if (_lcRcResizeHandler) window.removeEventListener('resize', _lcRcResizeHandler);
            _lcRcResizeHandler = _calcLcRcPos;
            window.addEventListener('resize', _lcRcResizeHandler, { passive: true });
        } else {
            if (_lcRcResizeHandler) {
                window.removeEventListener('resize', _lcRcResizeHandler);
                _lcRcResizeHandler = null;
            }
            const _hdr = panel.querySelector('.tm-sp-header');
            if (_hdr) { _hdr.style.position = ''; _hdr.style.top = ''; _hdr.style.zIndex = ''; }
            panel.style.maxHeight = '';
            panel.style.overflowY = '';
        }
    }

    function _triggerLoadRing() {
        setTimeout(() => {
            const gear = document.getElementById('tm-settings-gear-btn');
            const wrapper = document.getElementById('tm-settings-wrapper');
            const ref = gear ? gear.getBoundingClientRect() : wrapper?.getBoundingClientRect();
            if (!ref) return;
            const cx = ref.left + ref.width  / 2;
            const cy = ref.top  + ref.height / 2;
            if (cx === 0 && cy === 0) return;
            const ring = document.createElement('div');
            ring.className = 'tm-load-ring';
            ring.style.cssText = `left:${Math.round(cx - 26)}px; top:${Math.round(cy - 26)}px;`;
            document.body.appendChild(ring);
            setTimeout(() => ring.remove(), 2000);
        }, 600);
    }

    function _animateWrapperToCorner(corner) {
        const wrapper = document.getElementById('tm-settings-wrapper');
        if (!wrapper) { _applyGearCorner(corner); return; }

        _applyGearCorner(corner);

        const gear = document.getElementById('tm-settings-gear-btn');
        const ref  = gear ? gear.getBoundingClientRect() : wrapper.getBoundingClientRect();
        const cx   = ref.left + ref.width  / 2;
        const cy   = ref.top  + ref.height / 2;

        const pulse = document.createElement('div');
        pulse.className = 'tm-corner-pulse';
        pulse.style.cssText = `left:${Math.round(cx - 38)}px; top:${Math.round(cy - 38)}px;`;

        const RING_TOTAL = 6;
        let doneCount = 0;
        [0, 150, 300, 900, 1050, 1200].forEach(delay => {
            const ring = document.createElement('div');
            ring.className = 'tm-pulse-ring';
            ring.style.animationDelay = delay + 'ms';
            ring.addEventListener('animationend', () => {
                doneCount++;
                if (doneCount === RING_TOTAL) pulse.remove();
            }, { once: true });
            pulse.appendChild(ring);
        });

        document.body.appendChild(pulse);
    }

    function _initSettingsPanel() {
        const _applyGearVisibility = () => {
            _applyGearDisplayMode();
        };
        const _doInit = () => {
            const _isFreshInstall = GM_getValue(KEY_CLICK_MODE, null) === null &&
                !GM_getValue(KEY_ONBOARDING_DONE, false);
            if (_isFreshInstall) {
                GM_setValue(KEY_CLICK_MODE, 'menu');
                GM_setValue(KEY_GROUP_ON_DOWNLOAD, true);
                GM_setValue(KEY_GROUP_POPUP_STYLE, 'list');
                GM_setValue(KEY_DOCK_STYLE, 'arrow');
            }
            createSettingsPanel();
            _applyGearVisibility();
            _applyGearCorner(GM_getValue(KEY_GEAR_CORNER, 'tr'));
            _triggerLoadRing();
            if (_isTwitterDomain) _initStarPip();
            if (GM_getValue(KEY_HIST_PINNED, false)) setTimeout(showHistoryPanel, 800);
        };
        if (document.body) {
            _doInit();
        } else {
            document.addEventListener('DOMContentLoaded', _doInit, { once: true });
        }
    }

    function _initStarPip() {
        if (document.getElementById('tm-star-pip')) return;

        const pip = document.createElement('button');
        pip.id        = 'tm-star-pip';
        pip.className = 'tm-star-pip';
        pip.title     = 'Click: New Group · Hover: pick group';
        _renderStarPipIcon(pip);

        pip.addEventListener('mouseenter', () => {
            clearTimeout(StarPipState.autoHideTimer);
            StarPipState.autoHideTimer = setTimeout(() => {
                if (!StarPipState.fanOpen && !StarPipState.textMenuOpen) hideStarPip();
            }, STAR_AUTO_HIDE_SEC * 1000);
            if (StarPipState.pendingIsText) {
                if (!StarPipState.textMenuOpen) openTextGroupMenu();
                return;
            }
            if (GM_getValue(KEY_GROUP_POPUP_STYLE, 'fan') === 'list') {
                if (!StarPipState.textMenuOpen && getGroups().length) openMediaListMenu();
                return;
            }
            if (!StarPipState.fanOpen && getGroups().length) openGroupFan();
        });
        pip.addEventListener('click', e => {
            e.preventDefault(); e.stopPropagation();
            if (StarPipState.pendingIsText) {
                if (StarPipState.textMenuOpen) closeTextGroupMenu(); else openTextGroupMenu();
            } else if (GM_getValue(KEY_GROUP_POPUP_STYLE, 'fan') === 'list') {
                if (StarPipState.textMenuOpen) closeTextGroupMenu(); else openMediaListMenu();
            } else {
                if (StarPipState.fanOpen) closeGroupFan();
                _runStarEscapeAnim(() => showGroupCreateModal());
            }
        });
        document.body.appendChild(pip);
    }

    function showOnboardingOverlay() {
        if (GM_getValue(KEY_ONBOARDING_DONE, false)) return;

        const wrapperEl = document.getElementById('tm-settings-wrapper');
        const gearEl    = document.getElementById('tm-settings-gear-btn');
        if (!wrapperEl || !gearEl) { setTimeout(showOnboardingOverlay, 400); return; }

        const obStyle = document.createElement('style');
        obStyle.id = 'tm-ob-style';
        obStyle.textContent = `
            @keyframes tm-ob-pulse-ring {
                0%   { transform:translate(-50%,-50%) scale(1);    opacity:0.9; }
                70%  { transform:translate(-50%,-50%) scale(1.55); opacity:0;   }
                100% { transform:translate(-50%,-50%) scale(1.55); opacity:0;   }
            }
            @keyframes tm-ob-fadein { from { opacity:0; } to { opacity:1; } }
            @keyframes tm-ob-card-in {
                from { opacity:0; transform:translateY(10px) scale(0.96); }
                to   { opacity:1; transform:translateY(0)    scale(1);    }
            }
            #tm-ob-overlay {
                position:fixed; inset:0; z-index:999985;
                pointer-events:all;
                animation: tm-ob-fadein 0.45s ease forwards;
            }
            #tm-ob-ring {
                position:fixed; border-radius:50%; pointer-events:none;
                border: 2px solid rgba(29,155,240,0.85);
                animation: tm-ob-pulse-ring 1.7s cubic-bezier(0.215,0.61,0.355,1) infinite;
                z-index:999988;
            }
            #tm-ob-card {
                position:fixed; z-index:999989;
                animation: tm-ob-card-in 0.4s 0.2s cubic-bezier(0.34,1.56,0.64,1) both;
                pointer-events:all;
            }
            #tm-ob-got-it {
                width:100%; padding:9px; border-radius:9999px;
                border:none; background:#1d9bf0; color:#fff;
                font-size:14px; font-weight:700; cursor:pointer;
                text-align:center; display:block; transition:background 0.15s;
            }
            #tm-ob-got-it:hover { background:#1a8cd8; }
            #tm-ob-next {
                width:100%; padding:9px; border-radius:9999px;
                border:none; background:rgba(29,155,240,.15); color:#1d9bf0;
                font-size:14px; font-weight:700; cursor:pointer;
                text-align:center; display:block; margin-bottom:8px;
                transition:background 0.15s;
            }
            #tm-ob-next:hover { background:rgba(29,155,240,.26); }
            .tm-ob-dots { display:flex; gap:5px; justify-content:center; margin-bottom:12px; }
            .tm-ob-dot  { width:6px; height:6px; border-radius:50%; display:inline-block; }
        `;
        document.head.appendChild(obStyle);

        const _obT     = _getActiveThemeColors(_getActiveThemePresetKey());
        const cardBg   = _obT.bg;
        const cardText = _obT.text;
        const cardSub  = _obT.sub;
        const arrowClr = _obT.bg;
        const cardW    = 280;

        let _overlay, _ring, _card;

        const _mkOverlay = (cx, cy, r1, r2) => {
            const el = document.createElement('div');
            el.id = 'tm-ob-overlay';
            el.style.background = cx !== null
                ? `radial-gradient(circle at ${cx}px ${cy}px, transparent ${r1}px, rgba(0,0,0,0.80) ${r2}px)`
                : 'rgba(0,0,0,0.78)';
            return el;
        };

        const _mkRing = (cx, cy, r1) => {
            if (cx === null) return null;
            const el = document.createElement('div');
            el.id = 'tm-ob-ring';
            el.style.cssText = `width:${r1*2}px;height:${r1*2}px;left:${cx}px;top:${cy}px;transform:translate(-50%,-50%);`;
            return el;
        };

        const _mkCard = (left, top, arrowRight) => {
            const el = document.createElement('div');
            el.id = 'tm-ob-card';
            el.style.cssText = `
                width:${cardW}px; left:${left}px; top:${top}px;
                background:${cardBg}; border-radius:14px;
                box-shadow:0 8px 32px rgba(0,0,0,0.32);
                padding:18px 18px 14px;
            `;
            if (arrowRight !== null) {
                const a = document.createElement('div');
                a.style.cssText = `
                    position:absolute; top:-10px; right:${arrowRight}px; width:0; height:0;
                    border-left:10px solid transparent; border-right:10px solid transparent;
                    border-bottom:10px solid ${arrowClr};
                `;
                el.appendChild(a);
            }
            return el;
        };

        const _mkDots = (active) => {
            const d = document.createElement('div');
            d.className = 'tm-ob-dots';
            [0, 1].forEach(i => {
                const s = document.createElement('span');
                s.className = 'tm-ob-dot';
                s.style.background = i === active ? '#1d9bf0' : 'rgba(29,155,240,.3)';
                d.appendChild(s);
            });
            return d;
        };

        const _fadeOut = (els, cb) => {
            els.filter(Boolean).forEach(el => {
                el.style.transition = 'opacity 0.2s ease';
                el.style.opacity = '0';
            });
            setTimeout(cb, 230);
        };

        const _cleanup = () => {
            [_overlay, _ring, _card, obStyle].forEach(el => el?.remove());
            wrapperEl.style.removeProperty('opacity');
            wrapperEl.style.removeProperty('transition');
        };

        function renderStep1() {
            const btn = document.querySelector(`article .${BUTTON_CLASS}`);
            let cx = null, cy = null, r1 = 0, r2 = 0, arrowRight = null;
            let cardLeft, cardTop;

            if (btn) {
                const rect = btn.getBoundingClientRect();
                cx = rect.left + rect.width  / 2;
                cy = rect.top  + rect.height / 2;
                r1 = Math.max(rect.width, rect.height) / 2 + 14;
                r2 = r1 + 20;
                cardLeft = Math.max(8, Math.min(cx - cardW / 2, window.innerWidth - cardW - 8));
                const spaceBelow = window.innerHeight - (cy + r2 + 10);
                cardTop = spaceBelow >= 190 ? cy + r2 + 10
                                             : Math.max(8, cy - r2 - 200);
            } else {
                cardTop  = Math.round(window.innerHeight * 0.30);
                cardLeft = Math.max(8, Math.min(Math.round(window.innerWidth / 2 - cardW / 2),
                                                window.innerWidth - cardW - 8));
            }

            _overlay = _mkOverlay(cx, cy, r1, r2);
            _ring    = _mkRing(cx, cy, r1);
            _card    = _mkCard(cardLeft, cardTop, arrowRight);

            const titleEl = document.createElement('div');
            titleEl.style.cssText = `font-size:15px;font-weight:700;color:${cardText};margin-bottom:8px;`;
            titleEl.textContent = T.onboard_step1_title || '📋 Twitter / X — Media Buttons';

            const bodyEl = document.createElement('div');
            bodyEl.style.cssText = `font-size:13px;color:${cardSub};line-height:1.55;margin-bottom:14px;`;
            bodyEl.textContent = T.onboard_step1_body ||
                'This script adds a Copy button and a Download button directly to every tweet with photos or videos. Hover any media tweet to see them in the action bar.';

            const nextBtn = document.createElement('button');
            nextBtn.id = 'tm-ob-next';
            nextBtn.textContent = T.onboard_next || 'Next →';
            nextBtn.addEventListener('click', e => {
                e.stopPropagation();
                _fadeOut([_overlay, _ring, _card], () => {
                    _overlay?.remove(); _ring?.remove(); _card?.remove();
                    renderStep2();
                });
            });

            _card.appendChild(titleEl);
            _card.appendChild(bodyEl);
            _card.appendChild(_mkDots(0));
            _card.appendChild(nextBtn);

            document.body.appendChild(_overlay);
            if (_ring) document.body.appendChild(_ring);
            document.body.appendChild(_card);
            _overlay.addEventListener('click', () => nextBtn.click());
        }

        function renderStep2() {
            wrapperEl.style.setProperty('opacity', '1', 'important');
            wrapperEl.style.setProperty('transition', 'none', 'important');

            const rect = wrapperEl.getBoundingClientRect();
            const cx = rect.left + rect.width  / 2;
            const cy = rect.top  + rect.height / 2;
            const r1 = Math.ceil(Math.sqrt((rect.width / 2) ** 2 + (rect.height / 2) ** 2)) + 10;
            const r2 = r1 + 20;

            const cardLeft = Math.max(8, Math.min(cx - cardW / 2, window.innerWidth - cardW - 8));
            const cardTop  = cy + r2 + 10;

            _overlay = _mkOverlay(cx, cy, r1, r2);
            _ring    = _mkRing(cx, cy, r1);
            _card    = _mkCard(cardLeft, cardTop, 16);

            const titleEl = document.createElement('div');
            titleEl.style.cssText = `font-size:15px;font-weight:700;color:${cardText};margin-bottom:8px;`;
            titleEl.textContent = T.onboard_title || '⚙ Settings Panel';

            const bodyEl = document.createElement('div');
            bodyEl.style.cssText = `font-size:13px;color:${cardSub};line-height:1.55;margin-bottom:14px;`;
            bodyEl.textContent = T.onboard_body ||
                'Hover the top-right corner to reveal the settings button. Click it to quickly manage domains, prefix, language and more.';

            const gotItBtn = document.createElement('button');
            gotItBtn.id = 'tm-ob-got-it';
            gotItBtn.textContent = T.onboard_got_it || 'Got it!';
            gotItBtn.addEventListener('click', e => {
                e.stopPropagation();
                GM_setValue(KEY_ONBOARDING_DONE, true);
                _fadeOut([_overlay, _ring, _card], () => {
                    _cleanup();
                });
            });

            _card.appendChild(titleEl);
            _card.appendChild(bodyEl);
            _card.appendChild(_mkDots(1));
            _card.appendChild(gotItBtn);

            document.body.appendChild(_overlay);
            document.body.appendChild(_ring);
            document.body.appendChild(_card);
            _overlay.addEventListener('click', () => {
                GM_setValue(KEY_ONBOARDING_DONE, true);
                _fadeOut([_overlay, _ring, _card], () => {
                    _cleanup();
                });
            });
        }

        renderStep1();
    }

    setTimeout(showOnboardingOverlay, 1200);

    if (_isTwitterDomain) {
    const _tf = {};
    _tf.feedPrune = (() => {
        let enabled = false;
        let timerId = null;
        const LIMIT_FEED = 70;
        let prunedSet = new WeakSet();
        const pruneTweet = (el) => {
            if (!el || prunedSet.has(el)) return;
            prunedSet.add(el);
            let h = 0;
            try { h = Math.max(0, Math.floor(el.getBoundingClientRect().height || 0)); } catch (_) {}
            if (!h) h = 800;
            try {
                el.dataset.tfPrevStyle = el.getAttribute('style') || '';
                el.style.contain = 'layout paint style';
                el.style.contentVisibility = 'hidden';
                el.style.containIntrinsicSize = `${h}px`;
                el.style.height = `${h}px`;
                el.style.overflow = 'hidden';
            } catch (_) {}
            try { el.querySelectorAll('video').forEach(v => { try { v.pause(); } catch (_) {} }); } catch (_) {}
        };
        const gcFeed = () => {
            const nodes = document.querySelectorAll('article[role="article"]');
            if (nodes.length <= LIMIT_FEED) return;
            const over = nodes.length - LIMIT_FEED;
            for (let i = 0; i < over; i++) pruneTweet(nodes[i]);
        };
        return {
            get enabled() { return enabled; },
            enable() {
                if (enabled) return;
                timerId = setInterval(gcFeed, 1500);
                enabled = true;
            },
            disable() {
                if (!enabled) return;
                clearInterval(timerId);
                timerId = null;
                document.querySelectorAll('[data-tf-prev-style]').forEach(el => {
                    el.setAttribute('style', el.dataset.tfPrevStyle || '');
                    delete el.dataset.tfPrevStyle;
                });
                prunedSet = new WeakSet();
                enabled = false;
            },
        };
    })();

    _tf.cssSimplify = (() => {
        let enabled = false;
        const STYLE_ID = 'tm-tf-css-simplify';
        const css = `
            
            * { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
            [style*="backdrop-filter"], [style*="filter: blur"], *[style*="blur("] { filter: none !important; }
            html, body { scroll-behavior: auto !important; }

            [data-testid="cellInnerDiv"], [data-testid="sidebarColumn"] {
                content-visibility: auto !important;
                contain: layout paint style !important;
                contain-intrinsic-size: 800px !important;
            }
            article[role="article"] {
                contain: layout paint !important;
            }
            
            [class*="shadow"], [style*="box-shadow"] { box-shadow: none !important; }
            [class*="gradient"], [class*="bg-gradient"], [style*="linear-gradient("] { background-image: none !important; }
            video { transform: none !important; will-change: auto !important; background-color: black !important; }
            * { will-change: auto !important; }
        `;
        return {
            get enabled() { return enabled; },
            enable() {
                if (enabled) return;
                if (!document.getElementById(STYLE_ID)) {
                    const s = document.createElement('style');
                    s.id = STYLE_ID;
                    s.textContent = css;
                    document.head.appendChild(s);
                }
                enabled = true;
            },
            disable() {
                if (!enabled) return;
                document.getElementById(STYLE_ID)?.remove();
                enabled = false;
            },
        };
    })();

    const _restoreTfState = () => {
        if (GM_getValue(KEY_TF_FEED_PRUNE, false))   _tf.feedPrune.enable();
        if (GM_getValue(KEY_TF_CSS_SIMPLIFY, false)) _tf.cssSimplify.enable();
    };
    if (document.head) {
        _restoreTfState();
    } else {
        document.addEventListener('DOMContentLoaded', _restoreTfState, { once: true });
    }

    _tfRegistry = _tf;

    }
    _initSettingsPanel();

    function showDomainPickerModal(key, onSuccess) {
        const old = document.getElementById('tm-domain-picker-modal');
        if (old) old.remove();

        const dark = _isDarkTheme();
        const presetKey = _getActiveThemePresetKey();
        const _t = _getActiveThemeColors(presetKey, 'settings');
        const _ex = _getActiveThemeExtras(presetKey, 'settings');
        const C = dark ? {
            overlay: 'rgba(0,0,0,0.82)', panel: _t.bg, text: _t.text,
            sub: _t.sub, border: _ex.borderA, rowBg: '#1e2732',
            rowHover: '#2d3741', activeBg: '#1e3a4f',
            activeBorder: '#1d9bf0', activeText: '#1d9bf0',
        } : {
            overlay: 'rgba(0,0,0,0.72)', panel: _t.bg, text: _t.text,
            sub: _t.sub, border: _ex.borderA, rowBg: _t.bg,
            rowHover: _ex.header, activeBg: '#e8f5fe',
            activeBorder: '#1d9bf0', activeText: '#1d9bf0',
        };

        const currentVal = GM_getValue(key, 'x.com');

        const modal = document.createElement('div');
        modal.id = 'tm-domain-picker-modal';
        modal.style.cssText = `
            position:fixed;top:0;left:0;width:100%;height:100%;
            background:${C.overlay};z-index:9999999;
            display:flex;align-items:center;justify-content:center;
            font-family:system-ui,-apple-system,sans-serif;
        `;
        const panel = document.createElement('div');
        panel.style.cssText = `
            background:${C.panel};color:${C.text};padding:22px 18px 18px;
            border-radius:16px;width:92%;max-width:360px;
            box-shadow:0 8px 32px rgba(0,0,0,0.35);position:relative;
        `;
        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.style.cssText = `position:absolute;top:12px;right:14px;border:none;
            background:none;width:26px;height:26px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:${C.sub};border-radius:5px;`;
        closeBtn.onclick = () => modal.remove();
        modal.onclick = e => { if (e.target === modal) modal.remove(); };

        const title = document.createElement('h3');
        title.textContent = T.menu_domain_click.replace(/^🔗\s*/, '');
        title.style.cssText = `margin:0 0 14px;font-size:0.95rem;color:${C.text};padding-right:24px;`;

        const list = document.createElement('div');
        list.style.cssText = 'display:flex;flex-direction:column;gap:6px;';

        DOMAIN_LIST.forEach(domain => {
            const isActive = domain === currentVal;
            const btn = document.createElement('button');
            btn.textContent = (isActive ? '★ ' : '') + domain;
            btn.style.cssText = `
                width:100%;padding:8px 14px;border-radius:9999px;text-align:left;
                border:2px solid ${isActive ? C.activeBorder : C.border};
                background:${isActive ? C.activeBg : C.rowBg};
                color:${isActive ? C.activeText : C.text};
                font-size:13px;font-weight:${isActive ? '700' : '400'};
                cursor:pointer;transition:border-color 0.15s,background 0.15s;
            `;
            btn.onmouseenter = () => { if (!isActive) { btn.style.borderColor = C.sub; btn.style.background = C.rowHover; } };
            btn.onmouseleave = () => { if (!isActive) { btn.style.borderColor = C.border; btn.style.background = C.rowBg; } };
            btn.onclick = () => {
                GM_setValue(key, domain);
                modal.remove();
                if (onSuccess) onSuccess(domain);
            };
            list.appendChild(btn);
        });

        panel.appendChild(closeBtn);
        panel.appendChild(title);
        panel.appendChild(list);
        modal.appendChild(panel);
        document.body.appendChild(modal);
    }

    function isFeatureNew(id) {
        if (!NEW_FEATURE_IDS.includes(id)) return false;
        try {
            const seen = JSON.parse(GM_getValue(KEY_SEEN_FEATURES, '[]'));
            return !seen.includes(id);
        } catch(_) { return true; }
    }

    function markFeatureSeen(id) {
        try {
            const seen = JSON.parse(GM_getValue(KEY_SEEN_FEATURES, '[]'));
            if (!seen.includes(id)) {
                seen.push(id);
                GM_setValue(KEY_SEEN_FEATURES, JSON.stringify(seen));
            }
        } catch(e) { console.warn('[TM] markFeatureSeen failed:', e); }
    }

    function createSettingsPanel() {
        try {
        const existingWrapper = document.getElementById('tm-settings-wrapper');
        const _wasOpen  = existingWrapper?.getAttribute('data-open')  === 'true';
        const _prevFocus = existingWrapper?.getAttribute('data-focus') || null;
        if (existingWrapper) existingWrapper.remove();
        const existingStyle = document.getElementById('tm-settings-panel-style');
        if (existingStyle) existingStyle.remove();
        if (_spClickAC) _spClickAC.abort();
        _spClickAC = new AbortController();

        const dark = _isDarkTheme();
        const presetKey = _getActiveThemePresetKey();
        const _t = _getActiveThemeColors(presetKey, 'settings');
        const _ex = _getActiveThemeExtras(presetKey, 'settings');
        const C = dark ? {
            panel:     _t.bg,
            header:    _ex.header,
            text:      _t.text,
            sub:       _t.sub,
            border:    _ex.borderA,
            rowHover:  _ex.header,
            inputBg:   _ex.header,
            badge:     '#1d9bf0',
            gearFg:    _t.text,
            gearBg:    'rgba(255,255,255,0.08)',
            sliderTrackDisabled: 'rgba(255,255,255,.2)',
            valMuted:  _t.sub,
        } : {
            panel:     _t.bg,
            header:    _ex.header,
            text:      _t.text,
            sub:       _t.sub,
            border:    _ex.borderA,
            rowHover:  _ex.header,
            inputBg:   _ex.header,
            badge:     '#1d9bf0',
            gearFg:    _t.sub,
            gearBg:    'rgba(0,0,0,0.06)',
            sliderTrackDisabled: 'rgba(15,20,25,.15)',
            valMuted:  _t.sub,
        };

        const panelStyle = document.createElement('style');
        panelStyle.id = 'tm-settings-panel-style';
        panelStyle.textContent = `
            
            #tm-settings-wrapper {
                position: fixed; top: 12px; right: 12px; z-index: 999990;
                width: 90px; height: 50px;
                opacity: 0;
                transition: opacity 0.3s ease;
            }
            #tm-settings-wrapper:hover, #tm-settings-wrapper[data-open="true"] {
                opacity: 1;
            }
            #tm-settings-wrapper:hover #tm-dismiss-btn,
            #tm-settings-wrapper:hover #tm-refresh-hist-btn {
                opacity: 1 !important;
            }

            #tm-history-btn {
                
                position: absolute; right: 28px; top: 2px;
                width: 44px; height: 44px;
                border-radius: 50%; border: none; background: transparent;
                cursor: pointer; padding: 0;
                display: flex; align-items: center; justify-content: center;
                color: ${C.gearFg};
                z-index: 3; opacity: 1;
                transform: scale(1) translateX(0);
                transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
            }
            #tm-history-btn svg { width: 24px; height: 24px; display: block; }

            #tm-settings-gear-btn {
                
                position: absolute; right: 4px; top: 6px;
                width: 36px; height: 36px;
                border-radius: 50%; border: none; background: transparent;
                cursor: pointer; padding: 0;
                display: flex; align-items: center; justify-content: center;
                color: ${C.gearFg};
                z-index: 1; opacity: 0.5;
                transform: scale(0.9) translateX(0);
                transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
                overflow: hidden;
            }
            
            #tm-settings-gear-btn::before {
                content: ''; position: absolute; inset: 0; border-radius: 50%;
                background: radial-gradient(circle 30px at -6px 18px, rgba(0,0,0,0.08), rgba(0,0,0,0) 55%);
                pointer-events: none; z-index: 0;
            }
            #tm-settings-gear-btn svg { width: 20px; height: 20px; display: block; transition: transform 0.3s ease; position: relative; z-index: 1; }

            @keyframes tm-pulse-ring {
                0%   { transform: scale(0.10); opacity: 0.85; }
                45%  { opacity: 0.5; }
                75%  { opacity: 0; }
                100% { transform: scale(0.88); opacity: 0; }
            }
            
            @keyframes tm-load-ring {
                0%   { transform: scale(0.08); opacity: 0.55; }
                50%  { opacity: 0.28; }
                100% { transform: scale(1.1);  opacity: 0; }
            }
            .tm-load-ring {
                position: fixed; border-radius: 50%;
                width: 52px; height: 52px;
                pointer-events: none; z-index: 9999998;
                border: 1.5px solid #1d9bf0;
                animation: tm-load-ring 0.9s cubic-bezier(0.1,0,0.4,1) infinite;
            }
            .tm-corner-pulse {
                position: fixed; border-radius: 50%;
                width: 76px; height: 76px;
                pointer-events: none; z-index: 9999998;
            }
            .tm-pulse-ring {
                position: absolute; inset: 0; border-radius: 50%;
                border: 2.5px solid #1d9bf0;
                animation: tm-pulse-ring 0.52s cubic-bezier(0.15,0,0.5,1) forwards;
            }

            @keyframes tm-spin-once {
                from { transform: rotate(0deg); }
                to   { transform: rotate(-360deg); }
            }
            #tm-refresh-hist-btn.tm-refreshing svg {
                animation: tm-spin-once 0.6s cubic-bezier(0.4, 0, 0.2, 1) forwards;
            }

            #tm-settings-wrapper[data-focus="hist"] #tm-history-btn {
                transform: scale(1.15);
                background: ${C.gearBg};
            }

            #tm-settings-wrapper[data-focus="gear"] #tm-settings-gear-btn,
            #tm-settings-wrapper[data-open="true"] #tm-settings-gear-btn {
                z-index: 4;
                opacity: 1;
                transform: scale(1.3) translateX(-12px); 
                background: ${C.gearBg};
            }

            #tm-settings-wrapper[data-focus="gear"] #tm-history-btn,
            #tm-settings-wrapper[data-open="true"] #tm-history-btn {
                z-index: 1;
                opacity: 0.35;
                transform: scale(0.75) translateX(26px); 
                background: transparent;
            }

            #tm-settings-wrapper[data-open="true"] #tm-settings-gear-btn svg {
                transform: rotate(90deg);
            }

            #tm-settings-wrapper[data-solo="gear"] #tm-history-btn,
            #tm-settings-wrapper[data-solo="hist"] #tm-settings-gear-btn {
                display: none;
            }
            #tm-settings-wrapper[data-solo="gear"] #tm-settings-gear-btn {
                right: auto; left: 50%;
                transform: translateX(-50%) scale(1);
                opacity: 1;
            }
            #tm-settings-wrapper[data-solo="gear"][data-open="true"] #tm-settings-gear-btn {
                transform: translateX(-50%) scale(1.15);
            }
            #tm-settings-wrapper[data-solo="hist"] #tm-history-btn {
                right: auto; left: 50%;
                transform: translateX(-50%) scale(1.15);
                opacity: 1;
            }

            #tm-settings-panel {
                position: absolute; top: calc(100% - 21px); right: 4px;
                width: 310px; background: ${C.panel};
                border-radius: 14px;
                box-shadow: 0 8px 32px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.10);
                border: 1px solid ${C.border};
                font-family: system-ui, -apple-system, sans-serif;
                
                overflow-y: auto;
                overflow-x: hidden;
                max-height: calc(100vh - 60px);
                transform-origin: top right;
                transform: scale(0.88) translateY(-8px); opacity: 0;
                transition: transform 0.22s cubic-bezier(0.34,1.56,0.64,1), opacity 0.18s ease;
                pointer-events: none;
            }
            #tm-settings-wrapper[data-open="true"] #tm-settings-panel {
                transform: scale(1) translateY(0); opacity: 1;
                pointer-events: all;
            }

            #tm-settings-panel.tm-panel-hcenter {
                transform: translateX(-50%) scale(0.88) translateY(-8px);
            }
            #tm-settings-panel.tm-panel-hcenter.tm-panel-bottom {
                transform: translateX(-50%) scale(0.88) translateY(8px);
            }
            #tm-settings-wrapper[data-open="true"] #tm-settings-panel.tm-panel-hcenter {
                transform: translateX(-50%) scale(1) translateY(0);
            }

            .tm-sp-header { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; padding: 11px 14px 10px; background: ${C.header}; border-bottom: 1px solid ${C.border}; font-size: 12px; font-weight: 700; color: ${C.sub}; letter-spacing: 0.04em; text-transform: uppercase; }
            .tm-sp-header-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            
            .tm-sp-theme-toggle {
                flex-shrink: 0; display: flex; align-items: center; justify-content: center;
                width: 26px; height: 26px; border-radius: 50%; border: none;
                background: ${C.gearBg}; color: ${C.gearFg};
                cursor: pointer; padding: 0; margin-left: 8px;
                transition: background 0.15s, transform 0.15s;
            }
            .tm-sp-theme-toggle:hover { transform: scale(1.08); }
            .tm-sp-theme-toggle:active { transform: scale(0.94); }
            .tm-sp-theme-toggle svg { width: 15px; height: 15px; display: block; }
            
            .tm-theme-picker-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
            .tm-theme-picker-flyout {
                position: absolute; top: 50%; right: calc(100% + 6px);
                transform: translateY(-50%) translateX(6px); transform-origin: right center;
                display: flex; align-items: center; gap: 6px;
                opacity: 0; pointer-events: none;
                transition: opacity 0.15s ease, transform 0.15s ease;
                z-index: 5;
            }
            
            .tm-theme-picker-wrap:hover .tm-theme-picker-flyout,
            .tm-theme-picker-flyout.tm-force-open {
                opacity: 1; pointer-events: auto; transform: translateY(-50%) translateX(0);
            }
            .tm-theme-picker-swatch {
                flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%;
                border: 2px solid rgba(128,128,128,0.35); cursor: pointer; padding: 0;
                transition: transform 0.12s ease, border-color 0.12s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.25);
            }
            .tm-theme-picker-swatch:hover { transform: scale(1.15); }
            .tm-theme-picker-swatch:active { transform: scale(0.92); }
            .tm-theme-picker-swatch.tm-active { border-color: #1d9bf0; }
            .tm-theme-picker-custom-btn {
                
                flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%;
                border: 2px dashed rgba(128,128,128,0.5); cursor: pointer; padding: 0;
                display: flex; align-items: center; justify-content: center;
                background: transparent; color: rgba(128,128,128,0.9);
                transition: transform 0.12s ease, border-color 0.12s ease, color 0.12s ease;
            }
            .tm-theme-picker-custom-btn:hover { transform: scale(1.15); border-color: #1d9bf0; color: #1d9bf0; }
            .tm-theme-picker-custom-btn:active { transform: scale(0.92); }
            .tm-theme-picker-custom-btn.tm-active { border-color: #1d9bf0; border-style: solid; color: #1d9bf0; }
            
            .tm-sp-collapseall-toggle {
                flex-shrink: 0; display: flex; align-items: center; justify-content: center;
                width: 26px; height: 26px; border-radius: 50%; border: none;
                background: ${C.gearBg}; color: ${C.gearFg};
                cursor: pointer; padding: 0; margin-left: 6px;
                transition: background 0.15s, transform 0.15s;
            }
            .tm-sp-collapseall-toggle:hover { transform: scale(1.08); }
            .tm-sp-collapseall-toggle:active { transform: scale(0.94); }
            .tm-sp-collapseall-toggle svg { width: 14px; height: 14px; display: block; }
            
            .tm-sp-group-header {
                padding: 8px 14px 5px;
                font-size: 10px; font-weight: 800; letter-spacing: 0.08em;
                text-transform: uppercase; color: ${C.sub};
                background: ${C.header};
                border-bottom: 1px solid ${C.border};
                user-select: none;
                opacity: 1;
            }
            
            .tm-sp-group-card {
                border: 1px solid ${C.border};
                border-radius: 10px;
                overflow: hidden;
                margin: 8px 8px;
            }
            .tm-sp-group-card:last-of-type { margin-bottom: 4px; }
            .tm-sp-row { display: flex; align-items: center; justify-content: space-between; padding: 9px 14px; gap: 8px; border-bottom: 1px solid ${C.border}; cursor: pointer; transition: background 0.1s; }
            .tm-sp-row:last-child { border-bottom: none; }
            .tm-sp-row:hover { background: ${C.rowHover}; }
            .tm-sp-row-left { display:flex; flex-direction:column; gap:1px; min-width:0; }
            .tm-sp-row-label { font-size: 12px; color: ${C.sub}; white-space: nowrap; }
            .tm-sp-row-value { font-size: 13px; color: ${C.text}; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: color 0.2s, text-shadow 0.2s; }
            
            .tm-sp-val-on  { color: ${dark ? '#38bdf8' : C.text} !important; font-weight: 700; letter-spacing: 0.3px;
                             text-shadow: ${dark ? '0 0 6px rgba(56,189,248,0.85), 0 0 14px rgba(56,189,248,0.45)' : 'none'}; }
            .tm-sp-val-off { color: ${dark ? '#f87171' : C.text} !important; font-weight: 700; letter-spacing: 0.3px;
                             text-shadow: ${dark ? '0 0 6px rgba(248,113,113,0.8), 0 0 14px rgba(248,113,113,0.45)' : 'none'}; }
            .tm-sp-val-option { color: ${dark ? '#a78bfa' : C.valMuted} !important; font-weight: 600; letter-spacing: 0.2px;
                                text-shadow: ${dark ? '0 0 6px rgba(167,139,250,0.75), 0 0 12px rgba(167,139,250,0.35)' : 'none'}; }
            
            .tm-sp-label { font-size: 12px; color: ${C.sub}; white-space: nowrap; }
            .tm-sp-val   { font-size: 13px; color: ${C.text}; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: color 0.2s; }
            .tm-sp-row-picker {  }
            
            .tm-sp-row-desc { font-size: 11px; color: ${C.sub}; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
            .tm-sp-arrow { font-size: 11px; color: ${C.sub}; flex-shrink: 0; margin-left: 4px; opacity: 0.5; }
            
            .tm-sp-picker {
                display: none; flex-direction: column;
                background: ${C.panel};
                border-top: 1px solid ${C.border};
                padding: 6px 10px 8px;
                gap: 4px;
            }
            .tm-sp-picker.open { display: flex; }
            .tm-sp-picker-opt {
                display: flex; align-items: center; gap: 8px;
                padding: 6px 10px; border-radius: 8px; border: none;
                background: transparent; cursor: pointer; text-align: left;
                font-size: 12px; color: ${C.text};
                transition: background 0.1s;
                width: 100%;
            }
            .tm-sp-picker-opt:hover { background: ${C.rowHover}; }
            .tm-sp-picker-opt.active {
                background: rgba(29,155,240,0.10);
                color: #1d9bf0; font-weight: 700;
            }
            .tm-sp-picker-opt .tm-sp-opt-check {
                width: 14px; flex-shrink: 0;
                font-size: 11px; color: #1d9bf0;
            }
            
            .tm-sp-cma-gear {
                margin-left: auto; flex-shrink: 0;
                width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
                border-radius: 6px; cursor: pointer; font-size: 13px;
                background: transparent; border: none; color: ${C.sub};
                transition: background 0.1s;
            }
            .tm-sp-cma-gear:hover { background: ${C.rowHover}; }
            .tm-sp-cma-accordion {
                display: grid;
                grid-template-rows: 0fr;
                background: ${C.panel};
                transition: grid-template-rows 0.25s ease;
            }
            .tm-sp-cma-accordion.open { grid-template-rows: 1fr; }
            .tm-sp-cma-accordion-inner {
                
                overflow: hidden; min-height: 0;
            }
            .tm-sp-cma-accordion-content {
                
                display: flex; flex-direction: column; gap: 8px;
                padding: 8px 14px 10px;
                border-top: 1px solid ${C.border};
            }
            .tm-sp-cma-section {
                font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
                color: ${C.sub}; margin-top: 4px; padding-top: 8px; border-top: 1px solid ${C.border};
            }
            .tm-sp-cma-section:first-child { margin-top: 0; padding-top: 0; border-top: none; }
            .tm-sp-cma-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
            .tm-sp-cma-label { font-size: 12px; color: ${C.sub}; white-space: nowrap; }
            .tm-sp-cma-select {
                background: ${C.rowHover}; color: ${C.text}; border: 1px solid ${C.border};
                border-radius: 6px; padding: 3px 7px; font: 12px system-ui; cursor: pointer;
                max-width: 170px;
            }
            
            .tm-sp-group-header {
                display: flex; align-items: center; gap: 5px;
                cursor: pointer;
                transition: opacity 0.15s;
            }
            .tm-sp-group-header:hover { opacity: 1 !important; }
            .tm-sp-group-chevron {
                margin-left: auto; flex-shrink: 0;
                opacity: 0.5;
                transition: transform 0.2s ease;
                display: inline-flex; align-items: center;
            }
            .tm-sp-group-header.collapsed .tm-sp-group-chevron {
                transform: rotate(-90deg);
            }
            .tm-sp-group-body {
                overflow: hidden;
                max-height: 2000px;
                transition: max-height 0.35s cubic-bezier(0.4,0,0.2,1),
                            opacity 0.2s ease;
                opacity: 1;
            }
            .tm-sp-group-body.collapsed {
                max-height: 0;
                opacity: 0;
                pointer-events: none;
            }
            
            .tm-tf-group-header {
                display: flex; align-items: center; justify-content: space-between;
                padding: 8px 14px 7px;
                border-top: 1px solid ${C.border};
                border-bottom: 1px solid ${C.border};
                cursor: pointer;
                font-size: 11px; font-weight: 700; letter-spacing: 0.4px;
                color: ${C.sub};
                background: ${C.header};
                transition: background 0.1s, opacity 0.1s;
                user-select: none;
            }
            .tm-tf-group-header:hover { opacity: 0.85; }
            .tm-tf-group-header-chevron {
                margin-left: auto; flex-shrink: 0; opacity: 0.5;
                transition: transform 0.2s ease;
                display: inline-flex; align-items: center;
            }
            .tm-tf-group-body {
                overflow: hidden;
                max-height: 1200px;
                transition: max-height 0.3s cubic-bezier(0.4,0,0.2,1), opacity 0.2s ease;
                opacity: 1;
            }
            .tm-tf-group-body.collapsed {
                max-height: 0; opacity: 0; pointer-events: none;
            }
            
            .tm-tf-row {
                display: flex; align-items: center; justify-content: space-between;
                padding: 8px 14px; gap: 8px;
                border-bottom: 1px solid ${C.border};
                cursor: pointer; transition: background 0.1s;
            }
            .tm-tf-row:last-child { border-bottom: none; }
            .tm-tf-row:hover { background: ${C.rowHover}; }
            .tm-tf-row-left { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
            .tm-tf-row-name { font-size: 12px; color: ${C.sub}; white-space: nowrap; }
            .tm-tf-row-desc { font-size: 11px; color: ${C.sub}; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
            .tm-tf-row-right { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
            
            .tm-tf-val-on {
                font-size: 12px; font-weight: 700; letter-spacing: 0.3px;
                color: ${dark ? '#38bdf8' : C.text};
                text-shadow: ${dark ? '0 0 6px rgba(56,189,248,0.85), 0 0 14px rgba(56,189,248,0.45)' : 'none'};
                transition: color 0.2s, text-shadow 0.2s;
            }
            
            .tm-tf-val-off {
                font-size: 12px; font-weight: 700; letter-spacing: 0.3px;
                color: ${dark ? '#f87171' : C.text};
                text-shadow: ${dark ? '0 0 6px rgba(248,113,113,0.8), 0 0 14px rgba(248,113,113,0.45)' : 'none'};
                transition: color 0.2s, text-shadow 0.2s;
            }
            .tm-tf-arrow { font-size: 11px; color: ${C.sub}; opacity: 0.5; flex-shrink: 0; }
            
            .tm-sp-help-badge {
                display: inline-flex; align-items: center; justify-content: center;
                width: 13px; height: 13px; flex-shrink: 0;
                opacity: 0.45; cursor: help;
                transition: opacity 0.15s;
            }
            .tm-sp-help-badge:hover { opacity: 0.9; }
            .tm-sp-help-badge svg { width: 13px; height: 13px; }
            
            .tm-sp-slider-row {
                padding: 7px 14px 8px;
                border-bottom: 1px solid ${C.border};
                display: flex; flex-direction: column; gap: 4px;
            }
            .tm-sp-slider-header {
                display: flex; align-items: center; justify-content: space-between;
            }
            .tm-sp-slider-label { font-size: 12px; color: ${C.sub}; }
            .tm-sp-slider-value {
                font-size: 12px; font-weight: 600; color: #1d9bf0;
                min-width: 40px; text-align: right;
            }
            .tm-sp-slider {
                -webkit-appearance: none; appearance: none;
                width: 100%; height: 3px; border-radius: 2px;
                background: ${C.border}; outline: none; cursor: pointer;
                accent-color: #1d9bf0;
            }
            .tm-sp-slider::-webkit-slider-thumb {
                -webkit-appearance: none; appearance: none;
                width: 14px; height: 14px; border-radius: 50%;
                background: #1d9bf0; cursor: pointer;
                box-shadow: 0 1px 4px rgba(29,155,240,0.4);
                transition: box-shadow 0.15s;
            }
            .tm-sp-slider::-webkit-slider-thumb:hover {
                box-shadow: 0 0 0 4px rgba(29,155,240,0.18);
            }
            .tm-sp-slider::-moz-range-thumb {
                width: 14px; height: 14px; border-radius: 50%; border: none;
                background: #1d9bf0; cursor: pointer;
            }
            
            .tm-sp-disabled-child {
                opacity: 0.32;
                pointer-events: none;
                cursor: default;
                user-select: none;
                transition: opacity 0.18s ease;
            }
            .tm-sp-disabled-child .tm-sp-slider {
                cursor: default;
                accent-color: ${C.sliderTrackDisabled};
            }
            
            .tm-sp-reset-btn {
                display: inline-flex; align-items: center; justify-content: center;
                width: 18px; height: 18px; border-radius: 50%;
                border: none; background: transparent;
                cursor: pointer; padding: 0; flex-shrink: 0;
                color: ${C.sub};
                opacity: 0; pointer-events: none;
                transition: opacity 0.15s, color 0.15s, background 0.15s, transform 0.22s;
            }
            .tm-sp-reset-btn.is-modified {
                opacity: 0.55; pointer-events: auto;
            }
            .tm-sp-reset-btn:hover {
                opacity: 1 !important; color: #1d9bf0;
                background: rgba(29,155,240,0.12);
                transform: rotate(-40deg);
            }
            .tm-sp-reset-btn:active { transform: rotate(-160deg); }
            .tm-sp-disabled-child .tm-sp-reset-btn {
                pointer-events: none;
            }
            
            #tm-dock-spotlight {
                position: fixed; inset: 0; z-index: 9999990;
                pointer-events: all;
                animation: tm-spotlight-fadein 0.22s ease;
            }
            @keyframes tm-spotlight-fadein {
                from { opacity: 0; }
                to   { opacity: 1; }
            }
            #tm-dock-spotlight-canvas {
                position: absolute; inset: 0;
                pointer-events: none;
            }
            .tm-dock-spotlight-step {
                position: absolute;
                background: rgba(29,155,240,0.12);
                border: 2px solid rgba(29,155,240,0.75);
                border-radius: 8px;
                pointer-events: none;
                animation: tm-spotlight-pulse 1.8s ease-in-out infinite;
            }
            @keyframes tm-spotlight-pulse {
                0%,100% { box-shadow: 0 0 0 0 rgba(29,155,240,0.4); }
                50%      { box-shadow: 0 0 0 6px rgba(29,155,240,0); }
            }
            .tm-dock-spotlight-label {
                position: absolute;
                background: rgba(15,20,25,0.92);
                color: #e7e9ea;
                font-size: 11px; font-weight: 600;
                padding: 5px 10px; border-radius: 6px;
                white-space: nowrap;
                pointer-events: none;
                border: 1px solid rgba(29,155,240,0.35);
            }
            #tm-dock-spotlight-dismiss {
                position: absolute; bottom: 32px; left: 50%;
                transform: translateX(-50%);
                padding: 8px 22px; border-radius: 99px; border: none;
                background: #1d9bf0; color: #fff;
                font-size: 13px; font-weight: 700; cursor: pointer;
                box-shadow: 0 4px 16px rgba(29,155,240,0.4);
                transition: background 0.15s;
            }
            #tm-dock-spotlight-dismiss:hover { background: #1a8cd8; }

            @keyframes tm-sp-new-pulse {
                0%, 100% { box-shadow: 0 0 0 0   rgba(29,155,240,0.55); }
                50%       { box-shadow: 0 0 0 4px rgba(29,155,240,0);    }
            }
            .tm-sp-new-badge { font-size: 9px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; padding: 2px 6px; border-radius: 9999px; flex-shrink: 0; background: #1d9bf0; color: #fff; animation: tm-sp-new-pulse 1.8s ease-in-out infinite; margin-right: 2px; }

            .tm-fb-demo {
                flex-shrink: 0;
                display: inline-flex; align-items: center; justify-content: center;
                width: 52px; height: 22px;
                pointer-events: none;
                overflow: visible;
            }
            
            .tm-fb-demo-toast-bubble {
                display: inline-flex; align-items: center; gap: 3px;
                padding: 2px 6px; border-radius: 99px;
                background: rgba(29,155,240,0.13); color: #1d9bf0;
                font-size: 9px; font-weight: 700; white-space: nowrap;
                border: 1px solid rgba(29,155,240,0.28);
                opacity: 0;
                transform: translateY(6px) scale(0.85);
            }
            .tm-fb-playing.tm-fb-demo-toast .tm-fb-demo-toast-bubble {
                animation: tm-fb-toast-in 0.55s cubic-bezier(0.34,1.56,0.64,1) forwards;
            }
            @keyframes tm-fb-toast-in {
                0%   { opacity: 0; transform: translateY(6px) scale(0.85); }
                55%  { opacity: 1; transform: translateY(0)   scale(1);    }
                80%  { opacity: 1; transform: translateY(0)   scale(1);    }
                100% { opacity: 0; transform: translateY(-3px) scale(0.9); }
            }
            
            .tm-fb-demo-icon-wrap {
                display: inline-flex; align-items: center; justify-content: center;
                width: 22px; height: 22px; border-radius: 50%;
                background: rgba(29,155,240,0.10);
                border: 1.5px solid rgba(29,155,240,0.25);
                color: #1d9bf0;
                opacity: 0;
                transform: scale(0.4);
            }
            .tm-fb-playing.tm-fb-demo-icon .tm-fb-demo-icon-wrap {
                animation: tm-fb-icon-pop 0.6s cubic-bezier(0.34,1.56,0.64,1) forwards;
            }
            .tm-fb-demo-icon-svg {
                stroke: #1d9bf0;
            }
            @keyframes tm-fb-icon-pop {
                0%   { opacity: 0; transform: scale(0.3) rotate(-15deg); }
                55%  { opacity: 1; transform: scale(1.15) rotate(3deg); }
                75%  { opacity: 1; transform: scale(0.95) rotate(0deg); }
                85%  { opacity: 1; transform: scale(1) rotate(0deg);    }
                100% { opacity: 0; transform: scale(1) rotate(0deg);    }
            }
            
            .tm-fb-demo-pulse-wrap {
                position: relative;
                width: 22px; height: 22px;
                display: inline-flex; align-items: center; justify-content: center;
            }
            .tm-fb-demo-pulse-ring {
                position: absolute; inset: -1px; border-radius: 50%;
                border: 1.5px solid rgba(29,155,240,0.7);
                opacity: 0; transform: scale(0.9);
            }
            .tm-fb-playing.tm-fb-demo-pulse .tm-fb-demo-pulse-ring:nth-child(1) {
                animation: tm-fb-pulse-ring 1.0s ease-out 0.05s forwards;
            }
            .tm-fb-playing.tm-fb-demo-pulse .tm-fb-demo-pulse-ring:nth-child(2) {
                animation: tm-fb-pulse-ring 1.0s ease-out 0.28s forwards;
            }
            .tm-fb-playing.tm-fb-demo-pulse .tm-fb-demo-pulse-ring:nth-child(3) {
                animation: tm-fb-pulse-ring 1.0s ease-out 0.52s forwards;
            }
            @keyframes tm-fb-pulse-ring {
                0%   { opacity: 0.8; transform: scale(0.9); }
                100% { opacity: 0;   transform: scale(2.4); }
            }
            .tm-fb-demo-pulse-icon {
                width: 20px; height: 20px; border-radius: 50%;
                background: rgba(29,155,240,0.10);
                border: 1.5px solid rgba(29,155,240,0.25);
                display: flex; align-items: center; justify-content: center;
                color: #1d9bf0;
                z-index: 1; position: relative;
            }
            .tm-fb-playing.tm-fb-demo-pulse .tm-fb-demo-pulse-icon {
                animation: tm-fb-pulse-icon-flash 0.35s ease 0.05s forwards;
            }
            @keyframes tm-fb-pulse-icon-flash {
                0%   { background: rgba(29,155,240,0.10); }
                40%  { background: rgba(29,155,240,0.42); }
                100% { background: rgba(29,155,240,0.10); }
            }

            .tm-fb-demo-flash-box {
                width: 34px; height: 18px; border-radius: 4px;
                background: rgba(29,155,240,0.08);
                border: 1px solid rgba(29,155,240,0.20);
                display: flex; align-items: center; justify-content: center;
                overflow: hidden; position: relative;
            }
            .tm-fb-demo-flash-sweep {
                position: absolute; inset: 0;
                background: linear-gradient(90deg, transparent 0%, rgba(29,155,240,0.55) 50%, transparent 100%);
                transform: translateX(-100%); opacity: 0;
            }
            .tm-fb-demo-flash-check {
                color: #1d9bf0; font-size: 10px;
                position: relative; z-index: 1; opacity: 0;
            }
            .tm-fb-playing.tm-fb-demo-flash .tm-fb-demo-flash-check {
                animation: tm-fb-flash-fade 1.1s ease forwards;
            }
            .tm-fb-playing.tm-fb-demo-flash .tm-fb-demo-flash-sweep {
                animation: tm-fb-flash-sweep 0.55s ease 0.08s forwards;
            }
            @keyframes tm-fb-flash-sweep {
                0%   { transform: translateX(-100%); opacity: 1; }
                100% { transform: translateX(110%);  opacity: 1; }
            }
            @keyframes tm-fb-flash-fade {
                0%   { opacity: 0; }
                15%  { opacity: 1; }
                75%  { opacity: 1; }
                100% { opacity: 0; }
            }

            .tm-fb-demo-slide-clip {
                overflow: hidden; height: 16px;
                display: flex; align-items: center;
            }
            .tm-fb-demo-slide-text {
                font-size: 9px; font-weight: 700; color: ${C.sub};
                white-space: nowrap;
                transform: translateY(14px); opacity: 0;
            }
            .tm-fb-playing.tm-fb-demo-slide .tm-fb-demo-slide-text {
                animation: tm-fb-slide-up 1.1s cubic-bezier(0.34,1.3,0.64,1) forwards;
            }
            @keyframes tm-fb-slide-up {
                0%   { transform: translateY(14px); opacity: 0; }
                28%  { transform: translateY(0);     opacity: 1; }
                72%  { transform: translateY(0);     opacity: 1; }
                100% { transform: translateY(-8px);  opacity: 0; }
            }

            @keyframes tm-fb-btn-pulse {
                0%   { box-shadow: 0 0 0 0   rgba(29,155,240,0.65), 0 0 0 0   rgba(29,155,240,0.3); }
                55%  { box-shadow: 0 0 0 7px rgba(29,155,240,0),    0 0 0 14px rgba(29,155,240,0); }
                100% { box-shadow: 0 0 0 0   rgba(29,155,240,0),    0 0 0 0   rgba(29,155,240,0); }
            }
            .tm-anim-pulse {
                border-radius: 50%;
                animation: tm-fb-btn-pulse 0.62s ease-out forwards;
            }
            
            @keyframes tm-fb-btn-flash {
                0%   { background: transparent; }
                25%  { background: rgba(29,155,240,0.28); }
                100% { background: transparent; }
            }
            .tm-anim-flash {
                border-radius: 4px;
                animation: tm-fb-btn-flash 0.55s ease forwards;
            }
            
            @keyframes tm-anim-slide-up-text {
                0%   { opacity: 0; transform: translateY(5px); }
                28%  { opacity: 1; transform: translateY(0);   }
                72%  { opacity: 1; transform: translateY(0);   }
                100% { opacity: 0; transform: translateY(-4px); }
            }
            .tm-anim-pop-text.tm-slide {
                animation: tm-anim-slide-up-text 0.9s cubic-bezier(0.34,1.2,0.64,1) forwards;
            }

            @keyframes tm-float-new-pulse {
                0%, 100% { box-shadow: 0 0 0 0   rgba(249, 24, 128, 0.6); }
                50%       { box-shadow: 0 0 0 4px rgba(249, 24, 128, 0);    }
            }
            .tm-float-new-badge {
                position: absolute; top: -2px; right: -4px;
                font-size: 8px; font-weight: 800; letter-spacing: 0.04em;
                text-transform: uppercase; padding: 2px 4px;
                border-radius: 4px; background: #f91880; color: #fff;
                animation: tm-float-new-pulse 1.8s ease-in-out infinite;
                pointer-events: none; z-index: 5;
            }

            .tm-gear-notify-dot {
                position: absolute; top: 5px; right: 5px;
                width: 7px; height: 7px; border-radius: 50%;
                background: #ff6b35;
                border: 1.5px solid var(--tm-gear-dot-border, #16202b);
                pointer-events: none; z-index: 6;
                animation: tm-dot-notify-pulse 2.2s ease-in-out infinite;
            }
            @keyframes tm-dot-notify-pulse {
                0%,100% { box-shadow: 0 0 0 0 rgba(255,107,53,0.55); }
                50%     { box-shadow: 0 0 0 4px rgba(255,107,53,0); }
            }

            #tm-settings-wrapper[data-absorb="true"] {
                opacity: 1 !important;
                
            }
            
            #tm-settings-wrapper[data-absorb="true"] #tm-history-btn {
                z-index: 3 !important;
                opacity: 1 !important;
            }
            #tm-settings-wrapper[data-absorb="true"] #tm-settings-gear-btn {
                opacity: 0.35 !important;
                transform: scale(0.88) translateX(0) !important;
                z-index: 1 !important;
            }
            @keyframes tm-hist-absorb-bounce {
                0%   { transform: scale(1)    translateX(0); filter: none; }
                18%  { transform: scale(1.42) translateX(0); filter: drop-shadow(0 0 10px rgba(29,155,240,0.95)); }
                38%  { transform: scale(0.88) translateX(0); filter: drop-shadow(0 0  5px rgba(29,155,240,0.5)); }
                58%  { transform: scale(1.18) translateX(0); filter: drop-shadow(0 0  7px rgba(29,155,240,0.7)); }
                75%  { transform: scale(0.95) translateX(0); filter: none; }
                100% { transform: scale(1)    translateX(0); filter: none; }
            }
            
            #tm-history-btn.tm-absorbing {
                animation: tm-hist-absorb-bounce 0.75s cubic-bezier(0.36,0.07,0.19,0.97) forwards;
                transition: none !important;
            }

            .tm-star-pip {
                position: fixed;
                width: 22px; height: 22px;
                border-radius: 50%; border: none;
                background: transparent;
                display: flex; align-items: center; justify-content: center;
                cursor: pointer; z-index: 99996;
                box-shadow: none; outline: none; pointer-events: none;
                transform: scale(0); opacity: 0;
                transition: transform .38s cubic-bezier(.34,1.56,.64,1), opacity .25s ease;
                font-size: 16px; line-height: 1;
                filter: drop-shadow(0 1px 2px rgba(0,0,0,.5));
            }
            .tm-star-pip.tm-popped { transform: scale(1); opacity: 1; pointer-events: all; }
            .tm-star-pip:hover     { transform: scale(1.35) !important; }
            
            .tm-star-pip-svg {
                display: flex; align-items: center; justify-content: center;
                width: 16px; height: 16px; color: #e8eaf0;
            }
            .tm-star-pip-svg svg { width: 100%; height: 100%; }
            .tm-star-pip.tm-escaping,
            .tm-star-pip.tm-escaping:hover { transform: scale(0.7) translate(-6px, 2px) !important; pointer-events: none !important; }
            
            .tm-star-pip-badge {
                position: absolute; top: -4px; right: -4px;
                width: 9px; height: 9px; border-radius: 50%;
                background: #ffd400; color: #1a1a1a;
                font-size: 8px; line-height: 9px; font-weight: 700;
                text-align: center;
                box-shadow: 0 0 0 1.5px rgba(0,0,0,.55);
                pointer-events: none;
                transform-origin: center center;
                animation: tm-star-badge-retract .4s ease-in 1s forwards;
            }
            @keyframes tm-star-badge-retract {
                0%   { opacity: 1; transform: scale(1)    translate(0, 0); }
                100% { opacity: 0; transform: scale(0.15) translate(-9px, 9px); }
            }
            
            .tm-fan-node {
                position: fixed;
                width: 36px; height: 36px;
                border-radius: 50%;
                display: flex; align-items: center; justify-content: center;
                cursor: pointer; z-index: 99994; pointer-events: none;
            }
            .tm-fan-node.tm-spawned { pointer-events: all; }
            .tm-fan-node-inner {
                width: 32px; height: 32px; border-radius: 50%;
                display: flex; align-items: center; justify-content: center;
                position: relative;
                background: transparent;
                transition: transform .18s cubic-bezier(.34,1.56,.64,1);
            }
            .tm-fan-node:hover .tm-fan-node-inner { transform: scale(1.15); }
            .tm-fan-glow {
                position: absolute; inset: -6px; border-radius: 50%;
                opacity: 0; transition: opacity .3s; pointer-events: none;
            }
            .tm-fan-node.tm-spawned .tm-fan-glow { opacity: 1; }
            .tm-fan-icon { font-size: 15px; line-height: 1; position: relative; z-index: 2; }
            .tm-fan-count { display: none; }  
            .tm-fan-label {
                position: absolute; bottom: -1px; left: 50%;
                transform: translateX(-50%);
                font-size: 9px; color: rgba(255,255,255,.7);
                white-space: nowrap; pointer-events: none;
                opacity: 0; transition: opacity .25s .12s;
                text-shadow: 0 1px 4px rgba(0,0,0,.9);
            }
            .tm-fan-node.tm-spawned .tm-fan-label { opacity: 1; }  
            
            #tm-fan-mask {
                position: fixed; pointer-events: none;
                z-index: 99992;
                transition: opacity .22s ease;
                transform-origin: center center;
            }
            #tm-fan-mask.tm-fan-mask-hidden { opacity: 0 !important; }
            
            .tm-ripple-ring {
                position: fixed; border-radius: 50%; pointer-events: none;
                border: 1px solid rgba(255,210,50,.45);
                animation: tm-ring-out .52s cubic-bezier(.2,.6,.4,1) forwards;
                z-index: 99993;
            }
            @keyframes tm-ring-out {
                0%   { transform: scale(1)   translate(-50%,-50%); opacity: .85; }
                100% { transform: scale(3.8) translate(-50%,-50%); opacity: 0; }
            }
            
            @keyframes tm-float-a { 0%,100%{transform:translate(0,0)} 33%{transform:translate(2.2px,-2.8px)} 66%{transform:translate(-1.8px,2.1px)} }
            @keyframes tm-float-b { 0%,100%{transform:translate(0,0)} 28%{transform:translate(-2.5px,-2.0px)} 68%{transform:translate(2.8px,1.8px)} }
            @keyframes tm-float-c { 0%,100%{transform:translate(0,0)} 42%{transform:translate(1.9px,2.6px)} 74%{transform:translate(-2.2px,-1.8px)} }
            @keyframes tm-float-d { 0%,100%{transform:translate(0,0)} 22%{transform:translate(2.6px,1.5px)} 62%{transform:translate(-1.5px,-2.8px)} }
            @keyframes tm-float-e { 0%,100%{transform:translate(0,0)} 36%{transform:translate(-2.8px,1.2px)} 71%{transform:translate(1.6px,-2.4px)} }
            .tm-float-a { animation: tm-float-a 3.4s ease-in-out infinite; }
            .tm-float-b { animation: tm-float-b 4.1s ease-in-out infinite .6s; }
            .tm-float-c { animation: tm-float-c 3.8s ease-in-out infinite 1.1s; }
            .tm-float-d { animation: tm-float-d 4.5s ease-in-out infinite .3s; }
            .tm-float-e { animation: tm-float-e 3.6s ease-in-out infinite 1.8s; }
            
            .tm-group-modal-overlay {
                position: fixed; inset: 0;
                background: rgba(0,0,0,.65);
                z-index: 99998;
                display: flex; align-items: center; justify-content: center;
                opacity: 0; pointer-events: none;
                transition: opacity .2s;
            }
            .tm-group-modal-overlay.tm-show {
                opacity: 1; pointer-events: all;
            }
            .tm-group-modal-box {
                background: #1a1f2e;
                border: 0.5px solid rgba(255,255,255,.13);
                border-radius: 14px; padding: 20px 22px; width: 380px;
                max-height: calc(100dvh - 48px);
                overflow: hidden;
                display: flex; flex-direction: column;
                transform: scale(.88) translateY(10px);
                transition: transform .26s cubic-bezier(.34,1.56,.64,1);
            }
            .tm-group-modal-overlay.tm-show .tm-group-modal-box {
                transform: scale(1) translateY(0);
            }
            .tm-group-modal-title {
                font-size: 14px; color: rgba(255,255,255,.92);
                margin-bottom: 14px; font-weight: 600;
                letter-spacing: .01em;
            }
            .tm-group-modal-input {
                width: 100%;
                background: rgba(255,255,255,.08);
                border: 1px solid rgba(255,255,255,.18);
                border-radius: 8px; padding: 8px 11px;
                font-size: 13px; color: #e7e9ea; outline: none;
                transition: border-color .15s;
                box-sizing: border-box;
                font-family: inherit;
            }
            .tm-group-modal-input:focus { border-color: rgba(29,155,240,.7); }
            .tm-group-modal-btns {
                display: flex; gap: 8px; margin-top: 16px;
            }
            .tm-group-modal-btn {
                flex: 1; padding: 8px 0; border-radius: 8px; border: none;
                font-size: 13px; font-weight: 500; cursor: pointer;
                transition: background .15s, opacity .15s;
                font-family: inherit; letter-spacing: .01em;
            }
            .tm-group-modal-btn.tm-confirm {
                background: #1d9bf0; color: #fff;
            }
            .tm-group-modal-btn.tm-confirm:hover { background: #1a8cd8; }
            .tm-group-modal-btn.tm-skip {
                background: rgba(255,255,255,.09);
                color: rgba(255,255,255,.65);
                border: 1px solid rgba(255,255,255,.12);
            }
            .tm-group-modal-btn.tm-skip:hover { background: rgba(255,255,255,.14); }

            #tm-text-group-menu {
                position: fixed;
                min-width: 168px;
                background: #1a1f2e;
                border: 0.5px solid rgba(255,255,255,.15);
                border-radius: 10px;
                box-shadow: 0 8px 28px rgba(0,0,0,.55), 0 2px 8px rgba(0,0,0,.35);
                z-index: 99997;
                overflow: hidden;
                opacity: 0;
                pointer-events: none;
                transform: translateX(8px) scale(.96);
                transform-origin: right center;
                transition: opacity .18s ease, transform .2s cubic-bezier(.34,1.4,.64,1);
            }
            #tm-text-group-menu.tm-tgm-open {
                opacity: 1;
                pointer-events: all;
                transform: translateX(0) scale(1);
            }
            .tm-tgm-header {
                font-size: 9.5px;
                color: rgba(255,255,255,.38);
                letter-spacing: .07em;
                text-transform: uppercase;
                padding: 9px 13px 6px;
                border-bottom: 1px solid rgba(255,255,255,.08);
                user-select: none;
            }
            .tm-tgm-list {
                max-height: 320px;
                overflow-y: auto;
                overflow-x: hidden;
                scrollbar-width: thin;
                scrollbar-color: rgba(255,255,255,.15) transparent;
            }
            .tm-tgm-item-wrap {
                display: flex;
                align-items: center;
            }
            .tm-tgm-drag-handle {
                flex-shrink: 0;
                width: 14px;
                display: flex; align-items: center; justify-content: center;
                color: rgba(255,255,255,.15);
                cursor: grab;
                transition: color .12s;
                user-select: none;
            }
            .tm-tgm-item-wrap:hover .tm-tgm-drag-handle { color: rgba(255,255,255,.45); }
            .tm-tgm-item {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 8px 13px 8px 2px;
                font-size: 12.5px;
                color: rgba(255,255,255,.82);
                cursor: pointer;
                transition: background .1s;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                font-family: inherit;
                border: none;
                background: transparent;
                width: 100%;
                text-align: left;
            }
            .tm-tgm-item:hover { background: rgba(255,255,255,.07); }
            .tm-tgm-item.tm-tgm-selected { color: #1d9bf0; }
            .tm-tgm-item-icon {
                width: 16px; height: 16px;
                flex-shrink: 0;
                display: flex; align-items: center; justify-content: center;
            }
            .tm-tgm-item-icon svg { width: 16px; height: 16px; }
            .tm-tgm-item-dot {
                width: 7px; height: 7px;
                border-radius: 50%;
                border: 1.5px solid rgba(255,255,255,.35);
                flex-shrink: 0;
                transition: background .12s, border-color .12s;
            }
            .tm-tgm-item.tm-tgm-selected .tm-tgm-item-dot {
                background: #1d9bf0;
                border-color: #1d9bf0;
            }
            .tm-tgm-divider {
                height: 1px;
                background: rgba(255,255,255,.08);
                margin: 3px 0;
            }
            .tm-tgm-new {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 8px 13px 9px;
                font-size: 12px;
                color: rgba(255,255,255,.55);
                cursor: pointer;
                transition: color .1s, background .1s;
                font-family: inherit;
                border: none;
                background: transparent;
                width: 100%;
                text-align: left;
            }
            .tm-tgm-new:hover { background: rgba(255,255,255,.06); color: rgba(255,255,255,.8); }
        `;
        document.head.appendChild(panelStyle);

        const wrapper = document.createElement('div');
        wrapper.id = 'tm-settings-wrapper';
        wrapper.setAttribute('data-focus', 'hist');
        wrapper.setAttribute('data-open', 'false');

        let focusTimer  = null;
        let currentFocus = 'hist';
        let _gearLock   = false;
        let _gearLockTimer = null;
        let _leaveTimer = null;
        let _sideStreak = 0;
        let _lastSide   = null;

        wrapper.addEventListener('mouseenter', () => {
            if (_leaveTimer) { clearTimeout(_leaveTimer); _leaveTimer = null; }
        });

        wrapper.addEventListener('mousemove', (e) => {
            if (_leaveTimer) { clearTimeout(_leaveTimer); _leaveTimer = null; }

            if (wrapper.getAttribute('data-open') === 'true') return;
            const rect = wrapper.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;
            const HIST_CX = 40, HIST_CY = 24, GEAR_CX = 68, GEAR_CY = 24;
            const _WEIGHT_HIST = 0.4;
            const distHist = Math.hypot(x - HIST_CX, y - HIST_CY) * _WEIGHT_HIST;
            const distGear = Math.hypot(x - GEAR_CX, y - GEAR_CY);
            const targetFocus = distGear < distHist ? 'gear' : 'hist';

            const _SIDE_STREAK_MIN = 2;
            if (targetFocus !== _lastSide) {
                _lastSide = targetFocus;
                _sideStreak = 1;
            } else {
                _sideStreak++;
            }
            if (_sideStreak < _SIDE_STREAK_MIN) return;

            if (_gearLock && targetFocus === 'hist') {
                if (focusTimer) { clearTimeout(focusTimer); focusTimer = null; }
                return;
            }

            if (targetFocus !== currentFocus) {
                if (!focusTimer) {
                    focusTimer = setTimeout(() => {
                        currentFocus = targetFocus;
                        wrapper.setAttribute('data-focus', currentFocus);
                        focusTimer = null;

                        if (currentFocus === 'gear') {
                            _gearLock = true;
                            if (_gearLockTimer) clearTimeout(_gearLockTimer);
                            _gearLockTimer = setTimeout(() => { _gearLock = false; }, 800);
                        }
                    }, 150);
                }
            } else {
                if (focusTimer) { clearTimeout(focusTimer); focusTimer = null; }
            }
        });

        wrapper.addEventListener('mouseleave', () => {
            if (wrapper.getAttribute('data-open') === 'true') return;
            if (focusTimer) { clearTimeout(focusTimer); focusTimer = null; }
            if (_leaveTimer) clearTimeout(_leaveTimer);
            _leaveTimer = setTimeout(() => {
                _leaveTimer = null;
                if (_gearLockTimer) { clearTimeout(_gearLockTimer); _gearLockTimer = null; }
                _gearLock    = false;
                currentFocus = 'hist';
                wrapper.setAttribute('data-focus', 'hist');
                _sideStreak = 0;
                _lastSide   = null;
            }, 400);
        });

        const dismissBtn = document.createElement('button');
        dismissBtn.id = 'tm-dismiss-btn';
        dismissBtn.title = 'Hide buttons (reload to restore)';
        dismissBtn.innerHTML = '<svg viewBox="0 0 10 10" width="7" height="7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="1" y1="1" x2="9" y2="9"/><line x1="9" y1="1" x2="1" y2="9"/></svg>';
        dismissBtn.style.cssText = `
            position:absolute; left:0; top:0;
            width:14px; height:14px;
            border-radius:50%; border:none;
            background:rgba(255,255,255,.12);
            color:rgba(255,255,255,.4);
            cursor:pointer; padding:0;
            display:flex; align-items:center; justify-content:center;
            opacity:0; transition:opacity .2s;
            z-index:10;
        `;
        dismissBtn.addEventListener('click', e => {
            e.stopPropagation();
            wrapper.setAttribute('data-open', 'false');
            wrapper.style.display = 'none';
        });

        const SVG_GEAR = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`;
        const gearBtn = document.createElement('button');
        gearBtn.id = 'tm-settings-gear-btn';
        gearBtn.innerHTML = SVG_GEAR;
        gearBtn.title = '⚙️ Twitter Media Script Settings';
        gearBtn.setAttribute('aria-label', 'Open settings');

        const _seenCount    = GM_getValue(KEY_GEAR_DOT_SEEN, 0);
        const _hasUnseenFeature = _seenCount < NEW_FEATURE_IDS.length
            && NEW_FEATURE_IDS.some(id => isFeatureNew(id));
        if (_hasUnseenFeature) {
            const dot = document.createElement('span');
            dot.className = 'tm-gear-notify-dot';
            const _dotBorderMap = { deep: '#16202b', white: '#f7f9f9', milk: '#f3ede0' };
            dot.style.setProperty('--tm-gear-dot-border', _dotBorderMap[_getActiveThemePresetKey()] || '#f7f9f9');
            gearBtn.appendChild(dot);
        }

        const SVG_HISTORY = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15.5 15.5"/><path d="M5 5l2.5 2.5" opacity="0.5"/></svg>`;
        const histBtn = document.createElement('button');
        histBtn.id = 'tm-history-btn';
        histBtn.innerHTML = SVG_HISTORY;
        histBtn.title = '📋 Download History';
        histBtn.setAttribute('aria-label', 'Open download history');

        if (isFeatureNew('history_panel')) {
            const floatBadge = document.createElement('span');
            floatBadge.className = 'tm-float-new-badge';
            floatBadge.textContent = 'NEW';
            histBtn.appendChild(floatBadge);
        }

        histBtn.addEventListener('click', e => {
            e.stopPropagation();

            if (isFeatureNew('history_panel')) {
                markFeatureSeen('history_panel');
                const badge = histBtn.querySelector('.tm-float-new-badge');
                if (badge) badge.remove();
            }

            wrapper.setAttribute('data-open', 'false');
            showHistoryPanel();
        });

        const panel = document.createElement('div');
        panel.id = 'tm-settings-panel';

        let _lastEggBubbleAC = null;
        let _lastCfnMenuAC = null;
        function buildContent() {
            if (_lastEggBubbleAC) { _lastEggBubbleAC.abort(); _lastEggBubbleAC = null; }
            if (_lastCfnMenuAC) { _lastCfnMenuAC.abort(); _lastCfnMenuAC = null; }
            document.getElementById('tm-egg-bubble-el')?.remove();
            document.getElementById('tm-cfn-menu-el')?.remove();
            panel.innerHTML = '';

            const s = _readSettings();
            const { clickCustom, clickDomain, prefix, fmt, fbStyle: _fbStyle,
                    dockStyle, dockHoverDelay, dockTriggerL, dockTriggerR } = s;

            const header = document.createElement('div');
            header.className = 'tm-sp-header';
            const headerTitle = document.createElement('span');
            headerTitle.className = 'tm-sp-header-title';
            headerTitle.textContent = T.onboard_title || '⚙ Media Script Settings';
            header.appendChild(headerTitle);

            const themeToggleBtn = document.createElement('button');
            themeToggleBtn.type = 'button';
            themeToggleBtn.className = 'tm-sp-theme-toggle';
            themeToggleBtn.title = dark ? 'Switch to light theme' : 'Switch to dark theme';
            themeToggleBtn.innerHTML = dark
                ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
                : '<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"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
            themeToggleBtn.addEventListener('click', e => {
                e.stopPropagation();
                _toggleAppTheme();
                createSettingsPanel();
            });

            const themePickerWrap = _buildThemePickerFlyout('settings', presetKey => {
                _setActiveThemePreset(presetKey, 'settings');
                createSettingsPanel();
            });
            themePickerWrap.appendChild(themeToggleBtn);

            const collapseAllBtn = document.createElement('button');
            collapseAllBtn.type = 'button';
            collapseAllBtn.className = 'tm-sp-collapseall-toggle';
            header.appendChild(themePickerWrap);
            header.appendChild(collapseAllBtn);
            panel.appendChild(header);

            const makeRow = (label, value, onClick, featureId = null) => {
                const row = document.createElement('div');
                row.className = 'tm-sp-row';
                const left = document.createElement('div');
                left.className = 'tm-sp-row-left';
                const lbl = document.createElement('span');
                lbl.className = 'tm-sp-row-label';
                lbl.textContent = label;
                const val = document.createElement('span');
                val.className = 'tm-sp-row-value';
                const getVal = typeof value === 'function' ? value : () => value;
                const _colorOnOff = (strVal) => {
                    const on  = T.status_on  || 'On';
                    const off = T.status_off || 'Off';
                    val.classList.remove('tm-sp-val-on', 'tm-sp-val-off');
                    if      (strVal === on)  val.classList.add('tm-sp-val-on');
                    else if (strVal === off) val.classList.add('tm-sp-val-off');
                };
                val.textContent = getVal();
                _colorOnOff(val.textContent);
                left.appendChild(lbl);
                left.appendChild(val);
                row.appendChild(left);
                if (featureId && isFeatureNew(featureId)) {
                    const badge = document.createElement('span');
                    badge.className = 'tm-sp-new-badge';
                    badge.textContent = 'NEW';
                    row.appendChild(badge);
                }
                const arrow = document.createElement('span');
                arrow.className = 'tm-sp-arrow';
                arrow.textContent = '›';
                row.appendChild(arrow);
                row.addEventListener('click', () => {
                    if (featureId) markFeatureSeen(featureId);
                    onClick();
                    val.textContent = getVal();
                    _colorOnOff(val.textContent);
                });
                return row;
            };

            const SVG_RESET = `<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>`;

            const _makeResetBtn = (onReset) => {
                const btn = document.createElement('button');
                btn.type = 'button';
                btn.className = 'tm-sp-reset-btn';
                btn.title = 'Reset to default';
                btn.innerHTML = SVG_RESET;
                btn.addEventListener('click', (e) => { e.stopPropagation(); onReset(); });
                return btn;
            };

            const _spGroupRegistry = [];

            const makeGroup = (label, defaultOpen = true, tooltip = null, onOpen = null) => {
                const SVG_CHEVRON = `<svg viewBox="0 0 10 10" width="9" height="9" fill="currentColor"><path d="M1 3l4 4 4-4z"/></svg>`;
                const SVG_HELP    = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6.5"/><path d="M6 6.2C6 5.1 6.9 4.2 8 4.2s2 .9 2 2c0 1-1 1.5-2 2v.6"/><circle cx="8" cy="11.2" r=".6" fill="currentColor" stroke="none"/></svg>`;

                let openState = defaultOpen;
                try {
                    const saved = JSON.parse(GM_getValue(KEY_SP_GROUP_OPEN, '{}'));
                    if (label in saved) openState = saved[label];
                } catch (e) { console.warn('[TM] makeGroup read openState failed:', e); }

                const g = document.createElement('div');
                g.className = 'tm-sp-group-header' + (openState ? '' : ' collapsed');

                const labelSpan = document.createElement('span');
                labelSpan.textContent = label;
                g.appendChild(labelSpan);

                if (tooltip) {
                    const helpBadge = document.createElement('span');
                    helpBadge.className = 'tm-sp-help-badge';
                    helpBadge.innerHTML = SVG_HELP;
                    helpBadge.title = tooltip;
                    g.appendChild(helpBadge);
                }

                const chevron = document.createElement('span');
                chevron.className = 'tm-sp-group-chevron';
                chevron.innerHTML = SVG_CHEVRON;
                if (openState) chevron.style.transform = 'rotate(0deg)';
                else           chevron.style.transform = 'rotate(-90deg)';
                g.appendChild(chevron);

                const body = document.createElement('div');
                body.className = 'tm-sp-group-body' + (openState ? '' : ' collapsed');

                g.addEventListener('click', (e) => {
                    if (e.target.closest('.tm-sp-help-badge')) return;
                    const isCollapsed = body.classList.toggle('collapsed');
                    g.classList.toggle('collapsed', isCollapsed);
                    chevron.style.transform = isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)';
                    if (!isCollapsed && onOpen) onOpen();
                    try {
                        const saved = JSON.parse(GM_getValue(KEY_SP_GROUP_OPEN, '{}'));
                        saved[label] = !isCollapsed;
                        GM_setValue(KEY_SP_GROUP_OPEN, JSON.stringify(saved));
                    } catch (e) { console.warn('[TM] makeGroup write openState failed:', e); }
                    if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                });

                const card = document.createElement('div');
                card.className = 'tm-sp-group-card';
                card.appendChild(g);
                card.appendChild(body);
                panel.appendChild(card);
                _spGroupRegistry.push({ header: g, body, chevron, label, card });
                return {
                    body,
                    append: (el) => body.appendChild(el),
                };
            };

            const makePickerRow = (label, options, currentVal, onSelect, featureId = null, defaultVal = undefined) => {
                const wrap = document.createElement('div');
                wrap.style.cssText = `border-bottom: 1px solid ${C.border};`;

                const row = document.createElement('div');
                row.className = 'tm-sp-row';
                row.style.borderBottom = 'none';

                const left = document.createElement('div');
                left.className = 'tm-sp-row-left';
                const lbl = document.createElement('span');
                lbl.className = 'tm-sp-row-label';
                lbl.textContent = label;
                const val = document.createElement('span');
                val.className = 'tm-sp-row-value';
                val.classList.toggle('tm-sp-val-option', defaultVal !== undefined && currentVal !== defaultVal);
                val.textContent = (options.find(o => o.value === currentVal) || options[0]).label;
                left.appendChild(lbl);
                left.appendChild(val);
                row.appendChild(left);

                if (featureId && isFeatureNew(featureId)) {
                    const badge = document.createElement('span');
                    badge.className = 'tm-sp-new-badge';
                    badge.textContent = 'NEW';
                    row.appendChild(badge);
                }

                const arrow = document.createElement('span');
                arrow.className = 'tm-sp-arrow';
                arrow.textContent = '›';
                arrow.style.cssText = 'transition: transform 0.18s ease;';
                row.appendChild(arrow);

                const picker = document.createElement('div');
                picker.className = 'tm-sp-picker';

                options.forEach(opt => {
                    const btn = document.createElement('button');
                    btn.className = 'tm-sp-picker-opt' + (opt.value === currentVal ? ' active' : '');
                    const check = document.createElement('span');
                    check.className = 'tm-sp-opt-check';
                    check.textContent = opt.value === currentVal ? '✓' : '';
                    const txt = document.createElement('span');
                    txt.textContent = opt.label;
                    btn.appendChild(check);
                    btn.appendChild(txt);
                    btn.addEventListener('click', (e) => {
                        e.stopPropagation();
                        if (featureId) markFeatureSeen(featureId);
                        onSelect(opt.value);
                        val.textContent = opt.label;
                        val.classList.toggle('tm-sp-val-option', defaultVal !== undefined && opt.value !== defaultVal);
                        picker.querySelectorAll('.tm-sp-picker-opt').forEach(b => {
                            b.classList.remove('active');
                            b.querySelector('.tm-sp-opt-check').textContent = '';
                        });
                        btn.classList.add('active');
                        check.textContent = '✓';
                        picker.classList.remove('open');
                        arrow.style.transform = '';
                    });
                    picker.appendChild(btn);
                });

                row.addEventListener('click', (e) => {
                    e.stopPropagation();
                    const isOpen = picker.classList.toggle('open');
                    arrow.style.transform = isOpen ? 'rotate(90deg)' : '';
                    if (featureId && isOpen) markFeatureSeen(featureId);
                    if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                });

                wrap.addEventListener('click', e => e.stopPropagation());

                wrap.appendChild(row);
                wrap.appendChild(picker);
                return wrap;
            };

            const makeDockStylePickerRow = (label, options, currentVal, onSelect, featureId = null, defaultVal = undefined, svgW = 28, svgH = 52) => {
                const _optBgIdle  = dark ? 'rgba(255,255,255,0.04)' : 'rgba(15,20,25,0.04)';
                const _optOutIdle = dark ? '1px solid rgba(255,255,255,0.08)' : '1px solid rgba(15,20,25,0.12)';
                const _optFgIdle  = dark ? 'rgba(255,255,255,0.55)' : '#7a6f5c';

                const wrap = document.createElement('div');
                wrap.className = 'tm-sp-picker-wrap';

                const row = document.createElement('div');
                row.className = 'tm-sp-row tm-sp-row-picker';

                const lbl = document.createElement('span');
                lbl.className = 'tm-sp-label';
                lbl.textContent = label;
                row.appendChild(lbl);

                const val = document.createElement('span');
                val.className = 'tm-sp-val';
                val.classList.toggle('tm-sp-val-option', defaultVal !== undefined && currentVal !== defaultVal);
                val.textContent = options.find(o => o.value === currentVal)?.label ?? currentVal;
                row.appendChild(val);

                if (featureId && isFeatureNew(featureId)) {
                    const badge = document.createElement('span');
                    badge.className = 'tm-sp-new-badge';
                    badge.textContent = 'NEW';
                    row.appendChild(badge);
                }

                const arrow = document.createElement('span');
                arrow.className = 'tm-sp-arrow';
                arrow.textContent = '›';
                arrow.style.cssText = 'transition: transform 0.18s ease;';
                row.appendChild(arrow);

                const picker = document.createElement('div');
                picker.className = 'tm-sp-picker';
                picker.style.cssText = 'display:none; grid-template-columns:1fr 1fr; gap:6px; padding:10px 12px;';

                options.forEach(opt => {
                    const btn = document.createElement('button');
                    btn.type = 'button';
                    btn.style.cssText = `
                        display:flex; flex-direction:column; align-items:center; gap:5px;
                        padding:8px 6px; border-radius:8px; border:none; cursor:pointer;
                        background:${opt.value === currentVal ? 'rgba(29,155,240,0.12)' : _optBgIdle};
                        outline:${opt.value === currentVal ? '1.5px solid rgba(29,155,240,0.5)' : _optOutIdle};
                        transition:background 0.15s, outline 0.15s;
                    `;

                    const tmpDiv = document.createElement('div');
                    tmpDiv.innerHTML = opt.svg;
                    const svgEl = tmpDiv.querySelector('svg');
                    if (svgEl) {
                        svgEl.setAttribute('width', String(svgW));
                        svgEl.setAttribute('height', String(svgH));
                        btn.appendChild(svgEl);
                    }

                    const nameLbl = document.createElement('span');
                    nameLbl.textContent = opt.label;
                    nameLbl.style.cssText = `font-size:10px; color:${opt.value === currentVal ? '#1d9bf0' : _optFgIdle}; font-weight:${opt.value === currentVal ? '600' : '400'};`;
                    btn.appendChild(nameLbl);

                    btn.addEventListener('click', e => {
                        e.stopPropagation();
                        if (featureId) markFeatureSeen(featureId);
                        onSelect(opt.value);
                        val.textContent = opt.label;
                        val.classList.toggle('tm-sp-val-option', defaultVal !== undefined && opt.value !== defaultVal);
                        picker.querySelectorAll('button').forEach(b => {
                            b.style.background = _optBgIdle;
                            b.style.outline = _optOutIdle;
                            b.querySelector('span:last-child').style.color = _optFgIdle;
                            b.querySelector('span:last-child').style.fontWeight = '400';
                        });
                        btn.style.background = 'rgba(29,155,240,0.12)';
                        btn.style.outline = '1.5px solid rgba(29,155,240,0.5)';
                        nameLbl.style.color = '#1d9bf0';
                        nameLbl.style.fontWeight = '600';
                        picker.classList.remove('open');
                        picker.style.display = 'none';
                        arrow.style.transform = '';
                        if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                    });
                    picker.appendChild(btn);
                });

                row.addEventListener('click', e => {
                    e.stopPropagation();
                    const isOpen = picker.style.display === 'grid';
                    picker.style.display = isOpen ? 'none' : 'grid';
                    arrow.style.transform = isOpen ? '' : 'rotate(90deg)';
                    if (featureId && !isOpen) markFeatureSeen(featureId);
                    if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                });

                wrap.addEventListener('click', e => e.stopPropagation());
                wrap.appendChild(row);
                wrap.appendChild(picker);
                return wrap;
            };

            const makeIconStyleRow = (label, options, currentVal, onSelect, featureId = null) => {
                const wrap = document.createElement('div');
                wrap.style.cssText = 'padding: 8px 16px 10px;';

                const lbl = document.createElement('div');
                lbl.className = 'tm-sp-label';
                lbl.style.cssText = 'font-size: 12px; margin-bottom: 6px; display: flex; align-items: center; gap: 6px;';
                lbl.textContent = label;
                if (featureId && isFeatureNew(featureId)) {
                    const badge = document.createElement('span');
                    badge.className = 'tm-sp-new-badge';
                    badge.textContent = 'NEW';
                    lbl.appendChild(badge);
                }
                wrap.appendChild(lbl);

                const row = document.createElement('div');
                row.style.cssText = 'display: flex; gap: 6px;';

                const _optBgIdle  = dark ? 'rgba(255,255,255,0.04)' : 'rgba(15,20,25,0.04)';
                const _optOutIdle = dark ? '1px solid rgba(255,255,255,0.08)' : '1px solid rgba(15,20,25,0.12)';
                const _optFgIdle  = dark ? 'rgba(255,255,255,0.55)' : '#7a6f5c';

                options.forEach(opt => {
                    const btn = document.createElement('button');
                    btn.type = 'button';
                    const isActive = opt.value === currentVal;
                    btn.style.cssText = `
                        flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
                        padding: 6px 4px; border-radius: 8px; border: none; cursor: pointer; min-width: 0;
                        background: ${isActive ? 'rgba(29,155,240,0.12)' : _optBgIdle};
                        outline: ${isActive ? '1.5px solid rgba(29,155,240,0.5)' : _optOutIdle};
                        transition: background 0.15s, outline 0.15s;
                    `;

                    const tmpDiv = document.createElement('div');
                    tmpDiv.innerHTML = opt.svg;
                    const svgEl = tmpDiv.querySelector('svg');
                    if (svgEl) {
                        svgEl.setAttribute('width', '18');
                        svgEl.setAttribute('height', '18');
                        svgEl.style.color = isActive ? '#1d9bf0' : _optFgIdle;
                        btn.appendChild(svgEl);
                    }

                    const nameLbl = document.createElement('span');
                    nameLbl.textContent = opt.label;
                    nameLbl.style.cssText = `font-size: 10px; color: ${isActive ? '#1d9bf0' : _optFgIdle}; font-weight: ${isActive ? '600' : '400'};`;
                    btn.appendChild(nameLbl);

                    btn.addEventListener('click', e => {
                        e.stopPropagation();
                        if (featureId) markFeatureSeen(featureId);
                        onSelect(opt.value);
                    });
                    row.appendChild(btn);
                });

                wrap.appendChild(row);
                return wrap;
            };

            const makeFeedbackPickerRow = (label, options, currentVal, onSelect, featureId = null, defaultVal = undefined) => {
                const wrap = document.createElement('div');
                wrap.style.cssText = `border-bottom: 1px solid ${C.border};`;

                const row = document.createElement('div');
                row.className = 'tm-sp-row';
                row.style.borderBottom = 'none';

                const left = document.createElement('div');
                left.className = 'tm-sp-row-left';
                const lbl = document.createElement('span');
                lbl.className = 'tm-sp-row-label';
                lbl.textContent = label;
                const val = document.createElement('span');
                val.className = 'tm-sp-row-value';
                val.classList.toggle('tm-sp-val-option', defaultVal !== undefined && currentVal !== defaultVal);
                val.textContent = (options.find(o => o.value === currentVal) || options[0]).label;
                left.appendChild(lbl);
                left.appendChild(val);
                row.appendChild(left);

                if (featureId && isFeatureNew(featureId)) {
                    const badge = document.createElement('span');
                    badge.className = 'tm-sp-new-badge';
                    badge.textContent = 'NEW';
                    row.appendChild(badge);
                }

                const arrow = document.createElement('span');
                arrow.className = 'tm-sp-arrow';
                arrow.textContent = '›';
                arrow.style.cssText = 'transition: transform 0.18s ease;';
                row.appendChild(arrow);

                const picker = document.createElement('div');
                picker.className = 'tm-sp-picker';

                const makeDemoEl = (value) => {
                    const demo = document.createElement('span');
                    demo.className = 'tm-fb-demo tm-fb-demo-' + value;
                    demo.setAttribute('aria-hidden', 'true');

                    if (value === 'toast') {
                        demo.innerHTML = `
                            <span class="tm-fb-demo-toast-bubble">
                                <svg viewBox="0 0 14 14" width="9" height="9" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><polyline points="2,7 5.5,10.5 12,3.5"/></svg>
                                OK
                            </span>`;
                    } else if (value === 'icon') {
                        demo.innerHTML = `
                            <span class="tm-fb-demo-icon-wrap">
                                <svg class="tm-fb-demo-icon-svg" viewBox="0 0 14 14" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><polyline points="2,7 5.5,10.5 12,3.5"/></svg>
                            </span>`;
                    } else if (value === 'pulse') {
                        demo.innerHTML = `
                            <span class="tm-fb-demo-pulse-wrap">
                                <span class="tm-fb-demo-pulse-ring"></span>
                                <span class="tm-fb-demo-pulse-ring"></span>
                                <span class="tm-fb-demo-pulse-ring"></span>
                                <span class="tm-fb-demo-pulse-icon">
                                    <svg viewBox="0 0 14 14" width="9" height="9" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><polyline points="2,7 5.5,10.5 12,3.5"/></svg>
                                </span>
                            </span>`;
                    } else if (value === 'flash') {
                        demo.innerHTML = `
                            <span class="tm-fb-demo-flash-box">
                                <span class="tm-fb-demo-flash-sweep"></span>
                                <span class="tm-fb-demo-flash-check">
                                    <svg viewBox="0 0 14 14" width="9" height="9" fill="none" stroke="#1d9bf0" stroke-width="2.4" stroke-linecap="round"><polyline points="2,7 5.5,10.5 12,3.5"/></svg>
                                </span>
                            </span>`;
                    } else if (value === 'slide') {
                        demo.innerHTML = `
                            <span class="tm-fb-demo-slide-clip">
                                <span class="tm-fb-demo-slide-text">Copied</span>
                            </span>`;
                    }
                    return demo;
                };

                options.forEach(opt => {
                    const btn = document.createElement('button');
                    btn.className = 'tm-sp-picker-opt' + (opt.value === currentVal ? ' active' : '');

                    const check = document.createElement('span');
                    check.className = 'tm-sp-opt-check';
                    check.textContent = opt.value === currentVal ? '✓' : '';

                    const txt = document.createElement('span');
                    txt.style.cssText = 'flex: 1;';
                    txt.textContent = opt.label;

                    const demoEl = makeDemoEl(opt.value);

                    btn.appendChild(check);
                    btn.appendChild(txt);
                    if (opt.featureId && isFeatureNew(opt.featureId)) {
                        const optBadge = document.createElement('span');
                        optBadge.className = 'tm-sp-new-badge';
                        optBadge.textContent = 'NEW';
                        btn.appendChild(optBadge);
                    }
                    btn.appendChild(demoEl);

                    btn.addEventListener('click', (e) => {
                        e.stopPropagation();
                        if (featureId) markFeatureSeen(featureId);
                        if (opt.featureId) markFeatureSeen(opt.featureId);
                        onSelect(opt.value);
                        val.textContent = opt.label;
                        val.classList.toggle('tm-sp-val-option', defaultVal !== undefined && opt.value !== defaultVal);
                        picker.querySelectorAll('.tm-sp-picker-opt').forEach(b => {
                            b.classList.remove('active');
                            b.querySelector('.tm-sp-opt-check').textContent = '';
                        });
                        btn.classList.add('active');
                        check.textContent = '✓';
                        picker.classList.remove('open');
                        arrow.style.transform = '';
                    });

                    btn.addEventListener('mouseenter', () => {
                        const d = btn.querySelector('.tm-fb-demo');
                        if (!d) return;
                        d.classList.remove('tm-fb-playing');
                        void d.offsetWidth;
                        d.classList.add('tm-fb-playing');
                    });

                    picker.appendChild(btn);
                });

                row.addEventListener('click', (e) => {
                    e.stopPropagation();
                    const isOpen = picker.classList.toggle('open');
                    arrow.style.transform = isOpen ? 'rotate(90deg)' : '';
                    if (featureId && isOpen) markFeatureSeen(featureId);
                    if (isOpen) {
                        picker.querySelectorAll('.tm-fb-demo').forEach(d => {
                            d.classList.remove('tm-fb-playing');
                            void d.offsetWidth;
                            d.classList.add('tm-fb-playing');
                        });
                    }
                    if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                });

                wrap.addEventListener('click', e => e.stopPropagation());
                wrap.appendChild(row);
                wrap.appendChild(picker);
                return wrap;
            };

            const makeSliderRow = (label, value, min, max, step, unit, onChange, onCommit, featureId = null, defaultVal = undefined) => {
                const wrap = document.createElement('div');
                wrap.className = 'tm-sp-slider-row';

                const hdr = document.createElement('div');
                hdr.className = 'tm-sp-slider-header';

                const lbl = document.createElement('span');
                lbl.className = 'tm-sp-slider-label';
                lbl.textContent = label;

                const valDisplay = document.createElement('span');
                valDisplay.className = 'tm-sp-slider-value';
                valDisplay.textContent = value + ' ' + unit;

                if (featureId && isFeatureNew(featureId)) {
                    const badge = document.createElement('span');
                    badge.className = 'tm-sp-new-badge';
                    badge.textContent = 'NEW';
                    hdr.appendChild(lbl);
                    hdr.appendChild(badge);
                } else {
                    hdr.appendChild(lbl);
                }

                const rightGrp = document.createElement('div');
                rightGrp.style.cssText = 'display:flex;align-items:center;gap:3px;flex-shrink:0;';
                rightGrp.appendChild(valDisplay);

                let _resetBtn = null;
                if (defaultVal !== undefined) {
                    _resetBtn = _makeResetBtn(() => {
                        slider.value = String(defaultVal);
                        valDisplay.textContent = defaultVal + ' ' + unit;
                        if (onChange) onChange(defaultVal);
                        if (onCommit) onCommit(defaultVal);
                        if (featureId) markFeatureSeen(featureId);
                        _resetBtn.classList.toggle('is-modified', false);
                    });
                    _resetBtn.classList.toggle('is-modified', value !== defaultVal);
                    rightGrp.appendChild(_resetBtn);
                }
                hdr.appendChild(rightGrp);

                const slider = document.createElement('input');
                slider.type  = 'range';
                slider.className = 'tm-sp-slider';
                slider.min   = String(min);
                slider.max   = String(max);
                slider.step  = String(step);
                slider.value = String(value);

                slider.addEventListener('input', () => {
                    const n = parseInt(slider.value, 10);
                    valDisplay.textContent = n + ' ' + unit;
                    if (onChange) onChange(n);
                    if (_resetBtn) _resetBtn.classList.toggle('is-modified', n !== defaultVal);
                });
                slider.addEventListener('change', () => {
                    const n = parseInt(slider.value, 10);
                    if (onCommit) onCommit(n);
                    if (featureId) markFeatureSeen(featureId);
                });
                slider.addEventListener('click', e => e.stopPropagation());

                wrap.addEventListener('click', e => e.stopPropagation());
                wrap.appendChild(hdr);
                wrap.appendChild(slider);
                return wrap;
            };

            const showDockSpotlight = () => {
                if (GM_getValue('app_dock_spotlight_done', false)) return;
                if (document.getElementById('tm-dock-spotlight')) return;

                const histPanel = document.getElementById('tm-hist-panel');
                if (!histPanel) {
                    showHistoryPanel();
                    setTimeout(showDockSpotlight, 450);
                    return;
                }

                const trigL = histPanel.querySelector('.tm-dock-trigger.left');
                const trigR = histPanel.querySelector('.tm-dock-trigger.right');
                if (!trigL && !trigR) {
                    setTimeout(showDockSpotlight, 200);
                    return;
                }

                GM_setValue('app_dock_spotlight_done', true);

                const overlay = document.createElement('div');
                overlay.id = 'tm-dock-spotlight';

                const canvas = document.createElement('canvas');
                canvas.id = 'tm-dock-spotlight-canvas';
                canvas.width  = window.innerWidth;
                canvas.height = window.innerHeight;
                const ctx = canvas.getContext('2d');
                ctx.fillStyle = 'rgba(0,0,0,0.62)';
                ctx.fillRect(0, 0, canvas.width, canvas.height);

                const pr = histPanel.getBoundingClientRect();
                ctx.clearRect(pr.left - 4, pr.top - 4, pr.width + 8, pr.height + 8);

                overlay.appendChild(canvas);

                const addHighlight = (el, labelText, labelSide) => {
                    if (!el) return;
                    const r = el.getBoundingClientRect();
                    const pad = 6;
                    const box = document.createElement('div');
                    box.className = 'tm-dock-spotlight-step';
                    box.style.cssText = [
                        'left:'   + (r.left   - pad) + 'px',
                        'top:'    + (r.top    - pad) + 'px',
                        'width:'  + (r.width  + pad * 2) + 'px',
                        'height:' + (r.height + pad * 2) + 'px',
                    ].join(';');
                    overlay.appendChild(box);

                    const lbl = document.createElement('div');
                    lbl.className = 'tm-dock-spotlight-label';
                    lbl.textContent = labelText;
                    lbl.style.cssText = labelSide === 'left'
                        ? 'left:' + (r.right + 10) + 'px; top:' + (r.top + r.height/2 - 12) + 'px;'
                        : 'right:' + (window.innerWidth - r.left + 10) + 'px; top:' + (r.top + r.height/2 - 12) + 'px;';
                    overlay.appendChild(lbl);
                };

                addHighlight(trigL, '① Click to dock left →', 'left');
                addHighlight(trigR, '← Click to dock right ②', 'right');

                const dismissBtn = document.createElement('button');
                dismissBtn.id = 'tm-dock-spotlight-dismiss';
                dismissBtn.textContent = 'Got it!';
                dismissBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    overlay.remove();
                });
                overlay.appendChild(dismissBtn);

                overlay.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (e.target === overlay || e.target === canvas) overlay.remove();
                });

                document.body.appendChild(overlay);
            };

            const langLabel = T.menu_lang.replace(/^🌐\s*/, '').replace(/\s*\(Change Language\)/i, '').trim();
            const grpLang = makeGroup('🌐  ' + langLabel, true);
            grpLang.append(makeRow(langLabel, T.langName, () => {
                showLangPickerModal();
            }));

            const grpLink = makeGroup('🔗  Link', true);

            const clickVal = clickCustom ? clickDomain : 'x.com (default)';
            const clickLabel = T.menu_domain_click ? T.menu_domain_click.replace(/^🔗\s*/, '') : 'Single-Click Domain';
            grpLink.append(makeRow(clickLabel, clickVal, () => {
                if (!clickCustom) {
                    showDomainPickerModal(KEY_LINK_DOMAIN_CLICK, dom => {
                        GM_setValue(KEY_CLICK_MODE_CUSTOM, true);
                        showToast(T.toast_domain_click + dom);
                        registerMenus(); buildContent();
                    });
                } else {
                    GM_setValue(KEY_CLICK_MODE_CUSTOM, false);
                    showToast(T.toast_domain_click + 'x.com');
                    registerMenus(); buildContent();
                }
            }));

            const prefixLabel = T.menu_prefix ? T.menu_prefix.replace(/^⚙️\s*/, '') : 'Discord Prefix';
            grpLink.append(makeRow(prefixLabel, prefix || '(empty)', () => {
                const newPrefix = prompt(T.prompt_prefix, prefix);
                if (newPrefix !== null) {
                    GM_setValue(KEY_PREFIX_TEXT, newPrefix);
                    showToast(T.toast_prefix + (newPrefix || '(empty)'));
                    registerMenus(); buildContent();
                }
            }));

            const grpMedia = makeGroup('🎞  Media', true);

            const clickModeOpts = [
                { value: 'menu',    label: T.status_click_menu    || 'Menu',    featureId: 'click_mode_menu' },
                { value: 'classic', label: T.status_click_classic || 'Classic' },
            ];
            const clickModeRow = makePickerRow(T.menu_click_mode || '🖱 Click Mode', clickModeOpts, GM_getValue(KEY_CLICK_MODE, 'classic'), (newMode) => {
                GM_setValue(KEY_CLICK_MODE, newMode);
                const modeLabel = newMode === 'menu'
                    ? (T.status_click_menu    || 'Menu')
                    : (T.status_click_classic || 'Classic');
                const toastMsg = (T.toast_click_mode || '🖱 Click Mode → ') + modeLabel;
                buildContent();
                showToast(toastMsg);
            }, null, 'classic');
            grpMedia.append(clickModeRow);

            (() => {
                const picker = clickModeRow.querySelector('.tm-sp-picker');
                const classicOpt = picker?.children[1];
                if (!classicOpt) return;

                const tail = document.createElement('span');
                tail.style.cssText = 'margin-left:auto; display:flex; align-items:center; gap:4px;';

                if (isFeatureNew('custom_media_actions')) {
                    const badge = document.createElement('span');
                    badge.className = 'tm-sp-new-badge';
                    badge.textContent = 'NEW';
                    tail.appendChild(badge);
                }

                const gear = document.createElement('span');
                gear.className = 'tm-sp-cma-gear';
                gear.textContent = '⚙️';
                gear.title = T.cma_title || 'Customize button actions';
                gear.setAttribute('role', 'button');
                gear.setAttribute('aria-label', T.cma_title || 'Customize button actions');
                gear.tabIndex = 0;
                tail.appendChild(gear);
                classicOpt.appendChild(tail);

                const accordion = document.createElement('div');
                accordion.className = 'tm-sp-cma-accordion';
                const accordionInner = document.createElement('div');
                accordionInner.className = 'tm-sp-cma-accordion-inner';
                const accordionContent = document.createElement('div');
                accordionContent.className = 'tm-sp-cma-accordion-content';
                accordionInner.appendChild(accordionContent);
                accordion.appendChild(accordionInner);

                const SLOTS = [
                    { key: 'click',     label: T.cma_slot_click     || 'Single Click' },
                    { key: 'longpress', label: T.cma_slot_longpress || 'Long Press' },
                    { key: 'mclick',    label: T.cma_slot_mclick     || 'Middle Click' },
                    { key: 'rclick',    label: T.cma_slot_rclick     || 'Right Click' },
                ];

                const _buildActionSection = (headerLabel, gmKey, defaults, actionLabels) => {
                    const header = document.createElement('div');
                    header.className = 'tm-sp-cma-section';
                    header.textContent = headerLabel;
                    accordionContent.appendChild(header);

                    let current;
                    try {
                        current = { ...defaults, ...JSON.parse(GM_getValue(gmKey, '{}')) };
                    } catch (_) { current = { ...defaults }; }

                    const selects = {};
                    SLOTS.forEach(({ key, label }) => {
                        const row = document.createElement('div');
                        row.className = 'tm-sp-cma-row';

                        const lbl = document.createElement('span');
                        lbl.className = 'tm-sp-cma-label';
                        lbl.textContent = label;

                        const sel = document.createElement('select');
                        sel.className = 'tm-sp-cma-select';
                        Object.entries(actionLabels).forEach(([val, txt]) => {
                            const o = document.createElement('option');
                            o.value = val; o.textContent = txt;
                            sel.appendChild(o);
                        });
                        sel.value = current[key];

                        selects[key] = sel;
                        row.appendChild(lbl);
                        row.appendChild(sel);
                        accordionContent.appendChild(row);
                    });

                    Object.entries(selects).forEach(([key, sel]) => {
                        sel.addEventListener('change', () => {
                            const newVal = sel.value;
                            const oldVal = current[key];
                            if (newVal === oldVal) return;
                            const otherKey = Object.keys(current).find(k => k !== key && current[k] === newVal);
                            current[key] = newVal;
                            if (otherKey) {
                                current[otherKey] = oldVal;
                                selects[otherKey].value = oldVal;
                            }
                            GM_setValue(gmKey, JSON.stringify(current));
                        });
                    });
                };

                _buildActionSection(
                    T.cma_section_media || '🎞️ Media Button',
                    KEY_CUSTOM_MEDIA_ACTIONS,
                    DEFAULT_MEDIA_ACTIONS,
                    {
                        copy:         T.cma_action_copy        || 'Copy Media URLs',
                        copy_prefix:  T.cma_action_copy_prefix  || 'Copy with Prefix',
                        preview:      T.cma_action_preview      || 'Preview',
                        download_all: T.cma_action_download_all || 'Download All',
                    }
                );

                _buildActionSection(
                    T.cma_section_link || '🔗 Link Button',
                    KEY_CUSTOM_LINK_ACTIONS,
                    DEFAULT_LINK_ACTIONS,
                    {
                        copy_url:           T.cma_action_copy_url     || 'Copy Tweet URL',
                        copy_prefix_url:    T.cma_action_copy_prefix   || 'Copy with Prefix',
                        save_text_bookmark: T.cma_action_save_bookmark || 'Save as Text Bookmark',
                        none:               T.cma_action_none          || 'No Action',
                    }
                );

                const _toggleAccordion = (e) => {
                    e.stopPropagation();
                    const isOpen = accordion.classList.toggle('open');
                    if (isOpen) markFeatureSeen('custom_media_actions');
                    tail.querySelector('.tm-sp-new-badge')?.remove();
                    if (_cmaCollapseTimer) { clearTimeout(_cmaCollapseTimer); _cmaCollapseTimer = null; }
                    if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                };
                gear.addEventListener('click', _toggleAccordion);
                gear.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); _toggleAccordion(e); }
                });

                clickModeRow.appendChild(accordion);

                clickModeRow.querySelector('.tm-sp-row')?.addEventListener('click', () => {
                    if (!picker.classList.contains('open')) accordion.classList.remove('open');
                });

                let _cmaCollapseTimer = null;
                clickModeRow.addEventListener('mouseleave', () => {
                    if (!accordion.classList.contains('open')) return;
                    _cmaCollapseTimer = setTimeout(() => {
                        _cmaCollapseTimer = null;
                        accordion.classList.remove('open');
                        if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                    }, 600);
                });
                clickModeRow.addEventListener('mouseenter', () => {
                    if (_cmaCollapseTimer) { clearTimeout(_cmaCollapseTimer); _cmaCollapseTimer = null; }
                });
            })();

            const scanIntervalOpts = [500, 1000, 1500, 2500, 5000].map(ms => ({
                value: String(ms),
                label: `${ms}ms`,
            }));
            const scanIntervalRow = makePickerRow(
                `🔗🎞️ ${T.sp_scan_interval || 'Scan Interval'}`,
                scanIntervalOpts,
                String(parseInt(GM_getValue(KEY_SCAN_INTERVAL, '1500'), 10) || 1500),
                (newVal) => {
                    GM_setValue(KEY_SCAN_INTERVAL, newVal);
                    if (_startScanInterval) _startScanInterval();
                },
                null, '1500'
            );
            grpMedia.append(scanIntervalRow);

            const linkIconOpts = [
                { value: 'classic', label: T.sp_link_icon_classic || 'Classic',
                  svg: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8 12a4 4 0 0 0 5.66 0l2-2a4 4 0 0 0-5.66-5.66l-1 1"/><path d="M12 8a4 4 0 0 0-5.66 0l-2 2a4 4 0 0 0 5.66 5.66l1-1"/></svg>` },
                { value: 'rounded', label: T.sp_link_icon_rounded || 'Rounded',
                  svg: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 13.5L7.2 15.3a2.8 2.8 0 0 1-4-4L5 9.5"/><path d="M11 6.5l1.8-1.8a2.8 2.8 0 0 1 4 4L15 10.5"/></svg>` },
                { value: 'copy', label: T.sp_link_icon_copy || 'Copy',
                  svg: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="7" width="10" height="10" rx="2"/><path d="M4 13V5a2 2 0 0 1 2-2h8"/></svg>` },
            ];
            grpMedia.append(makeIconStyleRow(
                T.sp_link_icon_style_short || 'Link icon',
                linkIconOpts,
                GM_getValue(KEY_LINK_ICON_STYLE, 'classic'),
                (next) => {
                    GM_setValue(KEY_LINK_ICON_STYLE, next);
                    const label = linkIconOpts.find(o => o.value === next)?.label ?? next;
                    showToast((T.sp_link_icon_style || 'Icon Style') + ' → ' + label);
                    buildContent();
                },
                'sp_link_icon_style'
            ));

            const mediaIconOpts = [
                { value: 'classic', label: T.sp_media_icon_classic || 'Classic',
                  svg: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="16" height="12" rx="2"/><line x1="2" y1="7" x2="18" y2="7"/><line x1="2" y1="13" x2="18" y2="13"/><line x1="6" y1="4" x2="6" y2="7"/><line x1="10" y1="4" x2="10" y2="7"/><line x1="14" y1="4" x2="14" y2="7"/><line x1="6" y1="13" x2="6" y2="16"/><line x1="10" y1="13" x2="10" y2="16"/><line x1="14" y1="13" x2="14" y2="16"/></svg>` },
                { value: 'play', label: T.sp_media_icon_play || 'Play',
                  svg: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="14" height="14" rx="2.5"/><path d="M8 7.3v5.4l4.6-2.7z" fill="currentColor" stroke="none"/></svg>` },
                { value: 'download', label: T.sp_media_icon_download || 'Download',
                  svg: `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3v9M6.5 9L10 12.5 13.5 9"/><path d="M4 15.5h12"/></svg>` },
            ];
            grpMedia.append(makeIconStyleRow(
                T.sp_media_icon_style_short || 'Media icon',
                mediaIconOpts,
                GM_getValue(KEY_MEDIA_ICON_STYLE, 'classic'),
                (next) => {
                    GM_setValue(KEY_MEDIA_ICON_STYLE, next);
                    const label = mediaIconOpts.find(o => o.value === next)?.label ?? next;
                    showToast((T.sp_media_icon_style || 'Icon Style') + ' → ' + label);
                    buildContent();
                },
                'sp_media_icon_style'
            ));

            const fbOpts = [
                { value: 'toast',  label: T.status_feedback_toast  || 'Toast' },
                { value: 'icon',   label: T.status_feedback_icon   || 'Icon Only' },
                { value: 'pulse',  label: T.status_feedback_pulse  || 'Pulse Ring',   featureId: 'sp_feedback_pulse' },
                { value: 'flash',  label: T.status_feedback_flash  || 'Flash Sweep',  featureId: 'sp_feedback_flash' },
                { value: 'slide',  label: T.status_feedback_slide  || 'Slide Up',     featureId: 'sp_feedback_slide' },
            ];
            const fbLabel = T.menu_feedback_style ? T.menu_feedback_style.replace(/^🔔\s*/, '') : 'Feedback Style';
            const fbWrap = makeFeedbackPickerRow(fbLabel, fbOpts, _fbStyle, (newFb) => {
                GM_setValue(KEY_FEEDBACK_STYLE, newFb);
                const chosen = fbOpts.find(o => o.value === newFb);
                showToast((T.toast_feedback_style || '🔔 Feedback Style → ') + (chosen ? chosen.label : newFb));
                buildContent();
            }, 'sp_feedback_picker', 'toast');

            grpMedia.append(fbWrap);

            const fmtOpts = [
                { value: 'asian',   label: (T.status_date_asian   || 'Asian (YYYY.MM.DD)') },
                { value: 'western', label: (T.status_date_western || 'Western (DD.MM.YYYY)') },
            ];
            const fmtLabel = T.menu_date_format ? T.menu_date_format.replace(/^📅\s*/, '') : 'Date Format';
            grpMedia.append(makePickerRow(fmtLabel, fmtOpts, fmt, (newFmt) => {
                GM_setValue(KEY_DATE_FORMAT, newFmt);
                _refreshDateFormatCache();
                const chosen = fmtOpts.find(o => o.value === newFmt);
                showToast(T.toast_date_fmt + (chosen ? chosen.label : newFmt));
                registerMenus(); buildContent();
            }, 'sp_date_picker', 'asian'));

            const gridBtnRow = makeRow(
                T.sp_grid_media_btn_label || 'Media Grid Buttons',
                () => GM_getValue(KEY_GRID_MEDIA_BTN, true) ? (T.status_on || 'On') : (T.status_off || 'Off'),
                () => {
                    const next = !GM_getValue(KEY_GRID_MEDIA_BTN, true);
                    GM_setValue(KEY_GRID_MEDIA_BTN, next);
                    showToast((T.sp_grid_media_btn_label || 'Media Grid Buttons') + ' → ' + (next ? (T.status_on || 'On') : (T.status_off || 'Off')));
                    if (!next) document.querySelectorAll('.tm-grid-btn-layer').forEach(el => el.remove());
                },
                'grid_media_btn'
            );
            const gridBtnIcon = document.createElement('span');
            gridBtnIcon.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:-2px;margin-right:6px;"><rect x="3" y="3" width="6" height="6" rx="1"/><rect x="9.5" y="3" width="6" height="6" rx="1"/><rect x="16" y="3" width="5" height="6" rx="1"/><rect x="3" y="9.5" width="6" height="6" rx="1"/><rect x="9.5" y="9.5" width="6" height="6" rx="1"/><rect x="16" y="9.5" width="5" height="6" rx="1"/><rect x="3" y="16" width="6" height="5" rx="1"/><rect x="9.5" y="16" width="6" height="5" rx="1"/><rect x="16" y="16" width="5" height="5" rx="1"/></svg>';
            gridBtnRow.querySelector('.tm-sp-row-label')?.prepend(gridBtnIcon);
            grpMedia.append(gridBtnRow);

            const avatarBtnRow = makeRow(
                T.sp_avatar_media_btn_label || 'Avatar Media Shortcut',
                () => GM_getValue(KEY_AVATAR_MEDIA_BTN, true) ? (T.status_on || 'On') : (T.status_off || 'Off'),
                () => {
                    const next = !GM_getValue(KEY_AVATAR_MEDIA_BTN, true);
                    GM_setValue(KEY_AVATAR_MEDIA_BTN, next);
                    showToast((T.sp_avatar_media_btn_label || 'Avatar Media Shortcut') + ' → ' + (next ? (T.status_on || 'On') : (T.status_off || 'Off')));
                    if (!next) document.querySelectorAll('.tm-avatar-media-btn').forEach(el => el.remove());
                },
                'avatar_media_btn'
            );
            const avatarBtnIcon = document.createElement('span');
            avatarBtnIcon.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px;"><polygon points="12,2 2,7 12,12 22,7"/><polyline points="2,12 12,17 22,12"/><polyline points="2,17 12,22 22,17"/></svg>';
            avatarBtnRow.querySelector('.tm-sp-row-label')?.prepend(avatarBtnIcon);
            grpMedia.append(avatarBtnRow);

            const CUSTOM_FN_DEFAULT_TEMPLATE = '[twitter] {displayName}(@{screenName})_{date}_{id}_{index}.{ext}';
            const CUSTOM_FN_TOKENS = [
                { labelKey: 'cfn_token_screenName',  label: T.cfn_token_screenName  || '{screenName}',  token: '{screenName}',  desc: T.cfn_token_screenName_desc  || '' },
                { labelKey: 'cfn_token_displayName', label: T.cfn_token_displayName || '{displayName}', token: '{displayName}', desc: T.cfn_token_displayName_desc || '' },
                { labelKey: 'cfn_token_date',        label: T.cfn_token_date        || '{date}',        token: '{date}',        desc: T.cfn_token_date_desc        || '' },
                { labelKey: 'cfn_token_id',          label: T.cfn_token_id          || '{id}',          token: '{id}',          desc: T.cfn_token_id_desc          || '' },
                { labelKey: 'cfn_token_index',       label: T.cfn_token_index       || '{index}',       token: '{index}',       desc: T.cfn_token_index_desc       || '' },
                { labelKey: 'cfn_token_ext',         label: T.cfn_token_ext         || '{ext}',         token: '{ext}',         desc: T.cfn_token_ext_desc         || '' },
                { labelKey: 'cfn_token_origName', label: T.cfn_token_origName || '{origName}', token: '{origName}', desc: T.cfn_token_origName_desc || '' },
                { labelKey: 'cfn_token_text', label: T.cfn_token_text || '{text}', token: '{text}', desc: T.cfn_token_text_desc || '', warn: true },
            ];

            const customFnRow = makeRow(
                T.sp_custom_filename_label || 'Custom Filename',
                () => GM_getValue(KEY_CUSTOM_FILENAME_ENABLED, false) ? (T.status_on || 'On') : (T.status_off || 'Off'),
                () => {
                    const next = !GM_getValue(KEY_CUSTOM_FILENAME_ENABLED, false);
                    GM_setValue(KEY_CUSTOM_FILENAME_ENABLED, next);
                    _refreshCustomFilenameCache();
                    showToast((T.sp_custom_filename_label || 'Custom Filename') + ' → ' + (next ? (T.status_on || 'On') : (T.status_off || 'Off')));
                    _syncCustomFilenameChildDisabled();
                },
                'custom_filename_toggle'
            );
            grpMedia.append(customFnRow);

            const customFnWrap = (() => {
                const wrap = document.createElement('div');
                wrap.style.cssText = `padding:8px 12px;border-bottom:1px solid ${C.border};position:relative;`;
                wrap.addEventListener('click', e => e.stopPropagation());

                const editRow = document.createElement('div');
                editRow.style.cssText = 'position:relative;';

                const tplInput = document.createElement('input');
                tplInput.type = 'text';
                tplInput.spellcheck = false;
                tplInput.autocomplete = 'off';
                tplInput.value = GM_getValue(KEY_CUSTOM_FILENAME_TEMPLATE, '') || CUSTOM_FN_DEFAULT_TEMPLATE;
                tplInput.style.cssText = `
                    display: block; width: 100%; box-sizing: border-box;
                    font-size: 12px; font-family: ui-monospace, monospace;
                    padding: 6px 30px 6px 8px; border-radius: 6px; border: 1px solid ${C.border};
                    background: ${C.panel}; color: ${C.text}; outline: none;
                `;
                tplInput.title = T.cfn_input_tip || 'Available tokens: {screenName} {displayName} {date} {id} {index} {ext} {origName} {text}\nNote: {ext} does not include the dot — write .{ext} if you need one. {origName} is the original Twitter CDN filename code (e.g. HP_yBXibUAA2-Qu), without extension, empty if unavailable. {text} is truncated but may still make filenames long.';

                const resetBtn = _makeResetBtn(() => {
                    tplInput.value = CUSTOM_FN_DEFAULT_TEMPLATE;
                    GM_setValue(KEY_CUSTOM_FILENAME_TEMPLATE, CUSTOM_FN_DEFAULT_TEMPLATE);
                    _refreshCustomFilenameCache();
                    _updateCfnPreview();
                    _updateCfnResetState();
                    showToast(T.cfn_reset_toast || '↺ Filename template reset to default');
                });
                resetBtn.style.position = 'absolute';
                resetBtn.style.right = '6px';
                resetBtn.style.top = '50%';
                resetBtn.style.transform = 'translateY(-50%)';
                editRow.appendChild(tplInput);
                editRow.appendChild(resetBtn);
                wrap.appendChild(editRow);

                const _updateCfnResetState = () => {
                    resetBtn.classList.toggle('is-modified', tplInput.value !== CUSTOM_FN_DEFAULT_TEMPLATE);
                };
                _updateCfnResetState();

                const previewLabel = document.createElement('div');
                previewLabel.style.cssText = `font-size:10px;color:${C.sub};margin-top:7px;letter-spacing:.03em;text-transform:uppercase;`;
                previewLabel.textContent = T.cfn_preview_label || 'Preview';
                wrap.appendChild(previewLabel);

                const previewBox = document.createElement('div');
                previewBox.style.cssText = `
                    font-size: 12px; font-family: ui-monospace, monospace; margin-top: 4px;
                    padding: 6px 8px; border-radius: 6px; background: ${C.rowHover || C.border};
                    color: ${C.text}; word-break: break-all; line-height: 1.5;
                `;
                wrap.appendChild(previewBox);

                const _CFN_SAMPLE = { screenName: 'example', displayName: 'Example User', date: '2026.07.14', id: '1234567890123456789', index: 1, ext: 'mp4', origName: 'HP_yBXibUAA2-Qu', text: sanitizeForFilename('This is a sample tweet content for preview', CUSTOM_FN_TEXT_MAXLEN) };
                const _updateCfnPreview = () => {
                    const tpl = tplInput.value;
                    if (!tpl) { previewBox.textContent = T.cfn_preview_empty || '(empty template — falls back to default format)'; return; }
                    let rendered = tpl
                        .replace(/\{screenName\}/g,  _CFN_SAMPLE.screenName)
                        .replace(/\{displayName\}/g, _CFN_SAMPLE.displayName)
                        .replace(/\{date\}/g,        _CFN_SAMPLE.date)
                        .replace(/\{id\}/g,          _CFN_SAMPLE.id)
                        .replace(/\{index\}/g,       String(_CFN_SAMPLE.index))
                        .replace(/\{ext\}/g,         _CFN_SAMPLE.ext)
                        .replace(/\{origName\}/g,    _CFN_SAMPLE.origName)
                        .replace(/\{text\}/g,        _CFN_SAMPLE.text);
                    rendered = rendered.replace(/[\\/]/g, '_');
                    previewBox.textContent = rendered;
                };
                _updateCfnPreview();

                tplInput.addEventListener('input', () => {
                    GM_setValue(KEY_CUSTOM_FILENAME_TEMPLATE, tplInput.value);
                    _refreshCustomFilenameCache();
                    _updateCfnPreview();
                    _updateCfnResetState();
                });

                const menu = document.createElement('div');
                menu.id = 'tm-cfn-menu-el';
                menu.style.cssText = `
                    display: none; position: fixed; z-index: 9999999;
                    background: ${C.panel}; border: 1px solid ${C.border}; border-radius: 8px;
                    padding: 5px; box-shadow: 0 4px 16px rgba(0,0,0,0.25);
                    min-width: 150px; max-width: 220px;
                `;
                document.body.appendChild(menu);

                const _insertCfnToken = (token) => {
                    const start = tplInput.selectionStart ?? tplInput.value.length;
                    const end = tplInput.selectionEnd ?? tplInput.value.length;
                    tplInput.focus();
                    tplInput.setSelectionRange(start, end);
                    let inserted = false;
                    try { inserted = document.execCommand('insertText', false, token); } catch (_) { inserted = false; }
                    if (!inserted) {
                        const cur = tplInput.value;
                        const nextVal = cur.slice(0, start) + token + cur.slice(end);
                        tplInput.value = nextVal;
                        const caret = start + token.length;
                        tplInput.setSelectionRange(caret, caret);
                    }
                    GM_setValue(KEY_CUSTOM_FILENAME_TEMPLATE, tplInput.value);
                    _refreshCustomFilenameCache();
                    _updateCfnPreview();
                    _updateCfnResetState();
                };

                CUSTOM_FN_TOKENS.forEach(t => {
                    const item = document.createElement('button');
                    item.type = 'button';
                    item.style.cssText = `
                        display: flex; align-items: center; justify-content: space-between; gap: 6px;
                        width: 100%; text-align: left;
                        padding: 7px 9px; font-size: 12px; font-family: ui-monospace, monospace;
                        color: ${C.text}; background: transparent; border: none; border-radius: 5px;
                        cursor: pointer;
                    `;
                    const labelSpan = document.createElement('span');
                    labelSpan.textContent = t.label;
                    labelSpan.style.cssText = 'flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
                    item.appendChild(labelSpan);
                    if (t.desc) {
                        const descSpan = document.createElement('span');
                        descSpan.textContent = t.desc;
                        descSpan.style.cssText = `flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;font-size:11px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:${C.sub};`;
                        item.appendChild(descSpan);
                    }
                    if (t.warn) {
                        const warnBadge = document.createElement('span');
                        warnBadge.textContent = T.cfn_token_text_warn_badge || '⚠ long';
                        warnBadge.title = T.cfn_token_text_warn_tip || `Tweet text is unpredictable in length. Even truncated to ${CUSTOM_FN_TEXT_MAXLEN} characters here, combining with other tokens may still produce an overly long filename.`;
                        warnBadge.style.cssText = `
                            flex-shrink: 0; font-size: 9px; line-height: 1; padding: 3px 5px;
                            border-radius: 4px; background: rgba(255,159,10,0.16); color: #ff9f0a;
                            white-space: nowrap;
                        `;
                        item.appendChild(warnBadge);
                    }
                    item.addEventListener('mouseenter', () => { item.style.background = C.rowHover || C.border; });
                    item.addEventListener('mouseleave', () => { item.style.background = 'transparent'; });
                    item.addEventListener('mousedown', (e) => {
                        e.preventDefault();
                        _insertCfnToken(t.token);
                    });
                    menu.appendChild(item);
                });

                const _positionCfnMenu = () => {
                    const r = tplInput.getBoundingClientRect();
                    menu.style.display = 'block';
                    requestAnimationFrame(() => {
                        const mw = menu.offsetWidth;
                        const leftPos = r.left - mw - 8;
                        if (leftPos < 8) {
                            menu.style.left = Math.min(r.right + 8, window.innerWidth - mw - 8) + 'px';
                        } else {
                            menu.style.left = leftPos + 'px';
                        }
                        const mh = menu.offsetHeight;
                        let topPos = r.top;
                        if (topPos + mh > window.innerHeight - 8) topPos = window.innerHeight - mh - 8;
                        if (topPos < 8) topPos = 8;
                        menu.style.top = topPos + 'px';
                    });
                };
                const _hideCfnMenu = () => { menu.style.display = 'none'; };

                tplInput.addEventListener('focus', _positionCfnMenu);
                const _cfnMenuAC = new AbortController();
                _lastCfnMenuAC = _cfnMenuAC;
                document.addEventListener('click', (e) => {
                    if (!e.target.closest('#tm-cfn-menu-el') && e.target !== tplInput) {
                        _hideCfnMenu();
                    }
                }, { capture: true, passive: true, signal: _cfnMenuAC.signal });

                return wrap;
            })();
            grpMedia.append(customFnWrap);

            const _syncCustomFilenameChildDisabled = () => {
                const isOn = GM_getValue(KEY_CUSTOM_FILENAME_ENABLED, false);
                customFnWrap.classList.toggle('tm-sp-disabled-child', !isOn);
            };
            _syncCustomFilenameChildDisabled();

            const grpGroups = makeGroup(T.sp_grp_group || '⭐  Groups', true);

            const _grpSub    = dark ? 'rgba(255,255,255,.7)'  : '#7a6f5c';
            const _grpDiv    = dark ? 'rgba(255,255,255,.06)' : 'rgba(15,20,25,.08)';
            const _grpBtnBg  = dark ? 'rgba(255,255,255,.06)' : 'rgba(15,20,25,.05)';
            const _grpBtnBgH = dark ? 'rgba(255,255,255,.12)' : 'rgba(15,20,25,.09)';
            const _grpBtnFg  = dark ? 'rgba(255,255,255,.75)' : '#2b2620';
            const _grpBtnBd  = dark ? 'rgba(255,255,255,.15)' : 'rgba(15,20,25,.18)';
            const _grpBtnBdH = dark ? 'rgba(255,255,255,.3)'  : 'rgba(15,20,25,.32)';
            const _grpSelRing = dark ? 'rgba(255,255,255,.8)' : 'rgba(15,20,25,.65)';

            const groupOnDlRow = makeRow(
                T.sp_grp_on_dl || 'Group on Download',
                () => GM_getValue(KEY_GROUP_ON_DOWNLOAD, false) ? (T.status_on || 'On') : (T.status_off || 'Off'),
                () => {
                    const next = !GM_getValue(KEY_GROUP_ON_DOWNLOAD, false);
                    GM_setValue(KEY_GROUP_ON_DOWNLOAD, next);
                    showToast((T.sp_grp_on_dl || 'Group on Download') + ' → ' + (next ? (T.status_on || 'On') : (T.status_off || 'Off')));
                    _syncGroupChildrenDisabled();
                },
                'sp_group_on_dl'
            );
            grpGroups.append(groupOnDlRow);

            const _popupSvgFaint  = dark ? 'rgba(255,255,255,0.35)' : 'rgba(15,20,25,0.32)';
            const _popupSvgLine   = dark ? 'rgba(255,255,255,0.18)' : 'rgba(15,20,25,0.18)';
            const _popupSvgBg     = dark ? 'rgba(255,255,255,0.04)' : 'rgba(15,20,25,0.04)';
            const _popupSvgBorder = dark ? 'rgba(255,255,255,0.12)' : 'rgba(15,20,25,0.15)';
            const _popupSvgDot    = dark ? 'rgba(255,255,255,0.35)' : 'rgba(15,20,25,0.32)';
            const _popupSvgBar    = dark ? 'rgba(255,255,255,0.3)'  : 'rgba(15,20,25,0.28)';

            const popupStyleRow = makeDockStylePickerRow(
                T.sp_grp_popup_style || 'Popup Style',
                [
                    {
                        value: 'fan', label: T.sp_grp_popup_style_fan || 'Fan',
                        svg: `<svg viewBox="0 0 36 36" fill="none"><circle cx="18" cy="30" r="2.2" fill="rgba(29,155,240,0.7)"/><circle cx="7" cy="14" r="2.5" fill="${_popupSvgFaint}"/><circle cx="15" cy="7" r="2.5" fill="${_popupSvgFaint}"/><circle cx="24" cy="7" r="2.5" fill="${_popupSvgFaint}"/><circle cx="30" cy="15" r="2.5" fill="${_popupSvgFaint}"/><line x1="18" y1="30" x2="7" y2="14" stroke="${_popupSvgLine}" stroke-width="1"/><line x1="18" y1="30" x2="15" y2="7" stroke="${_popupSvgLine}" stroke-width="1"/><line x1="18" y1="30" x2="24" y2="7" stroke="${_popupSvgLine}" stroke-width="1"/><line x1="18" y1="30" x2="30" y2="15" stroke="${_popupSvgLine}" stroke-width="1"/></svg>`
                    },
                    {
                        value: 'list', label: T.sp_grp_popup_style_list || 'List',
                        svg: `<svg viewBox="0 0 36 36" fill="none"><rect x="5" y="6" width="26" height="24" rx="3" fill="${_popupSvgBg}" stroke="${_popupSvgBorder}" stroke-width="1"/><circle cx="10" cy="12" r="1.3" fill="${_popupSvgDot}"/><rect x="14" y="11" width="13" height="1.6" rx="0.8" fill="${_popupSvgBar}"/><circle cx="10" cy="18" r="1.3" fill="rgba(29,155,240,0.7)"/><rect x="14" y="17" width="13" height="1.6" rx="0.8" fill="rgba(29,155,240,0.6)"/><circle cx="10" cy="24" r="1.3" fill="${_popupSvgDot}"/><rect x="14" y="23" width="9" height="1.6" rx="0.8" fill="${_popupSvgBar}"/></svg>`
                    }
                ],
                GM_getValue(KEY_GROUP_POPUP_STYLE, 'fan'),
                (val) => {
                    GM_setValue(KEY_GROUP_POPUP_STYLE, val);
                    showToast((T.sp_grp_popup_style || 'Popup Style') + ' → ' + (val === 'list' ? (T.sp_grp_popup_style_list || 'List') : (T.sp_grp_popup_style_fan || 'Fan')));
                    _syncGroupChildrenDisabled();
                },
                'sp_group_popup_style', 'list',
                36, 36
            );
            grpGroups.append(popupStyleRow);

            const _grpCfgRaw  = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
            const _grpGlowClr = _grpCfgRaw.glowColor || 'multi';
            const _grpGlowSz  = Number(_grpCfgRaw.glowSize  ?? 12);
            const _grpTxtClr  = _grpCfgRaw.textColor || 'white';
            const _grpLabelSz = Number(_grpCfgRaw.labelSize ?? 9);
            const _grpIconSz  = Number(_grpCfgRaw.iconSize  ?? 18);

            const glowColorRow = (() => {
                const wrap = document.createElement('div');
                wrap.style.cssText = `padding:8px 12px;border-bottom:1px solid ${_grpDiv}`;
                wrap.addEventListener('click', e => e.stopPropagation());

                const labelRow = document.createElement('div');
                labelRow.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:7px';
                const lbl = document.createElement('span');
                lbl.style.cssText = `font-size:12px;color:${_grpSub}`;
                lbl.textContent   = T.sp_grp_glow_color || 'Glow Color';

                const multiToggle = document.createElement('button');
                multiToggle.type = 'button';
                const isMulti = _grpGlowClr === 'multi';
                multiToggle.style.cssText = `
                    padding:2px 8px;border-radius:99px;font-size:10px;
                    cursor:pointer;font-family:inherit;line-height:1.4;
                    background:${isMulti ? 'rgba(29,155,240,.7)' : 'transparent'};
                    color:${isMulti ? '#fff' : _grpBtnFg};
                    border:${isMulti ? '1px solid transparent' : `1px solid ${_grpBtnBd}`};
                    transition:background .12s,color .12s,border-color .12s;
                `;
                multiToggle.textContent = T.sp_grp_glow_multi || 'Multi';
                multiToggle.title = 'Use individual color per group';
                multiToggle.addEventListener('click', () => {
                    const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
                    const next = cfg.glowColor !== 'multi' ? 'multi' : '#1d9bf0';
                    cfg.glowColor = next;
                    GM_setValue(KEY_GROUP_PANEL_CFG, JSON.stringify(cfg));
                    multiToggle.style.background    = next === 'multi' ? 'rgba(29,155,240,.7)' : 'transparent';
                    multiToggle.style.color         = next === 'multi' ? '#fff' : _grpBtnFg;
                    multiToggle.style.borderColor   = next === 'multi' ? 'transparent' : _grpBtnBd;
                    showToast((T.sp_grp_glow_color || 'Glow Color') + ' → ' + (next === 'multi' ? (T.sp_grp_glow_multi || 'Multi') : next));
                });

                labelRow.appendChild(lbl);
                labelRow.appendChild(multiToggle);

                const colorRow = document.createElement('div');
                colorRow.style.cssText = 'display:flex;align-items:center;gap:5px;flex-wrap:wrap';

                const presets = ['#1d9bf0','#ffd700','#ff6b6b','#7bed9f','#a29bfe','#ff9f43','#ffffff'];
                const curHex  = (_grpGlowClr !== 'multi' && _grpGlowClr) ? _grpGlowClr : '#1d9bf0';

                const colorInput = document.createElement('input');
                colorInput.type  = 'color';
                colorInput.value = curHex;
                colorInput.style.cssText = `
                    width:24px;height:24px;border:none;border-radius:50%;
                    padding:0;cursor:pointer;background:transparent;
                    flex-shrink:0;outline:none;
                `;
                colorInput.title = 'Custom color';

                const saveColor = (hex) => {
                    const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
                    cfg.glowColor = hex;
                    GM_setValue(KEY_GROUP_PANEL_CFG, JSON.stringify(cfg));
                    multiToggle.style.background = _grpBtnBg;
                    multiToggle.style.color      = _grpBtnFg;
                };

                colorInput.addEventListener('input',  () => saveColor(colorInput.value));
                colorInput.addEventListener('change', () => saveColor(colorInput.value));

                presets.forEach(hex => {
                    const swatch = document.createElement('button');
                    swatch.type  = 'button';
                    swatch.style.cssText = `
                        width:18px;height:18px;border-radius:50%;border:2px solid ${hex === curHex ? _grpSelRing : 'transparent'};
                        background:${hex};cursor:pointer;flex-shrink:0;padding:0;
                        transition:border-color .1s,transform .1s;
                        box-shadow: inset 0 0 0 1px rgba(0,0,0,.15);
                    `;
                    swatch.addEventListener('mouseover', () => swatch.style.transform = 'scale(1.2)');
                    swatch.addEventListener('mouseout',  () => swatch.style.transform = '');
                    swatch.addEventListener('click', () => {
                        colorInput.value = hex;
                        saveColor(hex);
                        colorRow.querySelectorAll('button[data-swatch]').forEach(s => s.style.borderColor = 'transparent');
                        swatch.style.borderColor = _grpSelRing;
                    });
                    swatch.dataset.swatch = hex;
                    colorRow.appendChild(swatch);
                });

                colorRow.appendChild(colorInput);
                wrap.appendChild(labelRow);
                wrap.appendChild(colorRow);
                return wrap;
            })();
            grpGroups.append(glowColorRow);

            grpGroups.append(makeSliderRow(
                T.sp_grp_glow_size || 'Glow Size', _grpGlowSz, 4, 60, 2, 'px',
                null,
                (n) => {
                    const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
                    cfg.glowSize = n;
                    GM_setValue(KEY_GROUP_PANEL_CFG, JSON.stringify(cfg));
                },
                'sp_group_glow_size', 12
            ));

            grpGroups.append(makeSliderRow(
                T.sp_grp_label_size || 'Label Text Size', _grpLabelSz, 7, 16, 1, 'px',
                null,
                (n) => {
                    const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
                    cfg.labelSize = n;
                    GM_setValue(KEY_GROUP_PANEL_CFG, JSON.stringify(cfg));
                },
                'sp_group_label_size', 9
            ));

            grpGroups.append(makeSliderRow(
                T.sp_grp_icon_size || 'Icon Size', _grpIconSz, 14, 28, 1, 'px',
                null,
                (n) => {
                    const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
                    cfg.iconSize = n;
                    GM_setValue(KEY_GROUP_PANEL_CFG, JSON.stringify(cfg));
                },
                'sp_group_icon_size', 18
            ));

            const labelColorRow = (() => {
                const wrap = document.createElement('div');
                wrap.style.cssText = `padding:8px 12px;border-bottom:1px solid ${_grpDiv}`;
                wrap.addEventListener('click', e => e.stopPropagation());

                const topRow = document.createElement('div');
                topRow.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:7px';
                const lbl = document.createElement('span');
                lbl.style.cssText = `font-size:12px;color:${_grpSub}`;
                lbl.textContent   = T.sp_grp_label_color || 'Label Color';
                topRow.appendChild(lbl);

                const colorRow = document.createElement('div');
                colorRow.style.cssText = 'display:flex;align-items:center;gap:5px;flex-wrap:wrap';

                const txtPresets = ['#ffffff','#ffd700','#1d9bf0','#aaaaaa','#ff9f43','#7bed9f','#f368e0'];
                const curTxt = (_grpTxtClr && !['white','yellow','blue','gray'].includes(_grpTxtClr))
                    ? _grpTxtClr
                    : { white:'#ffffff', yellow:'#ffd700', blue:'#1d9bf0', gray:'#aaaaaa' }[_grpTxtClr] || '#ffffff';

                const txtInput = document.createElement('input');
                txtInput.type  = 'color';
                txtInput.value = curTxt;
                txtInput.style.cssText = 'width:24px;height:24px;border:none;border-radius:50%;padding:0;cursor:pointer;background:transparent;flex-shrink:0;outline:none';
                txtInput.title = 'Custom label color';

                const saveTxt = (hex) => {
                    const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
                    cfg.textColor = hex;
                    GM_setValue(KEY_GROUP_PANEL_CFG, JSON.stringify(cfg));
                };
                txtInput.addEventListener('input',  () => saveTxt(txtInput.value));
                txtInput.addEventListener('change', () => saveTxt(txtInput.value));

                txtPresets.forEach(hex => {
                    const sw = document.createElement('button');
                    sw.type  = 'button';
                    sw.style.cssText = `
                        width:18px;height:18px;border-radius:50%;
                        border:2px solid ${hex === curTxt ? _grpSelRing : 'transparent'};
                        background:${hex};cursor:pointer;flex-shrink:0;padding:0;
                        transition:border-color .1s,transform .1s;
                        box-shadow: inset 0 0 0 1px rgba(0,0,0,.15);
                    `;
                    sw.addEventListener('mouseover', () => sw.style.transform = 'scale(1.2)');
                    sw.addEventListener('mouseout',  () => sw.style.transform = '');
                    sw.addEventListener('click', () => {
                        txtInput.value = hex;
                        saveTxt(hex);
                        colorRow.querySelectorAll('button[data-swatch]').forEach(s => s.style.borderColor = 'transparent');
                        sw.style.borderColor = _grpSelRing;
                    });
                    sw.dataset.swatch = hex;
                    colorRow.appendChild(sw);
                });
                colorRow.appendChild(txtInput);
                wrap.appendChild(topRow);
                wrap.appendChild(colorRow);
                return wrap;
            })();
            grpGroups.append(labelColorRow);

            const _maskCfg        = _grpCfgRaw;
            const _maskEnabled    = _maskCfg.fanMaskEnabled !== false;
            const _maskColorHex   = _maskCfg.fanMaskColor   || '#000000';
            const _maskAlpha      = Number(_maskCfg.fanMaskAlpha   ?? 0.45);
            const _maskSpanDeg    = Number(_maskCfg.fanMaskSpanDeg ?? 160);
            const _maskRadius     = Number(_maskCfg.fanMaskRadius  ?? 180);

            const _saveMaskCfg = (patch) => {
                const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
                Object.assign(cfg, patch);
                GM_setValue(KEY_GROUP_PANEL_CFG, JSON.stringify(cfg));
            };

            const fanMaskRow = makeRow(
                T.sp_grp_fan_mask || 'Fan Backdrop',
                () => (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })().fanMaskEnabled === false
                    ? (T.status_off || 'Off') : (T.status_on || 'On'),
                () => {
                    const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
                    const next = cfg.fanMaskEnabled === false ? true : false;
                    _saveMaskCfg({ fanMaskEnabled: next });
                    showToast((T.sp_grp_fan_mask || 'Fan Backdrop') + ' → ' + (next ? (T.status_on || 'On') : (T.status_off || 'Off')));
                }
            );
            grpGroups.append(fanMaskRow);

            const fanMaskColorRow = (() => {
                const wrap = document.createElement('div');
                wrap.style.cssText = `padding:8px 12px;border-bottom:1px solid ${_grpDiv}`;
                wrap.addEventListener('click', e => e.stopPropagation());

                const topRow = document.createElement('div');
                topRow.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:7px';
                const lbl = document.createElement('span');
                lbl.style.cssText = `font-size:12px;color:${_grpSub}`;
                lbl.textContent = T.sp_grp_fan_mask_color || 'Backdrop Color';
                topRow.appendChild(lbl);

                const colorRow = document.createElement('div');
                colorRow.style.cssText = 'display:flex;align-items:center;gap:5px;flex-wrap:wrap';

                const maskPresets = ['#000000','#0f172a','#1e2732','#1d9bf0','#7c3aed','#dc2626','#ffffff'];
                const curMaskHex = _maskColorHex;

                const maskColorInput = document.createElement('input');
                maskColorInput.type  = 'color';
                maskColorInput.value = curMaskHex;
                maskColorInput.style.cssText = 'width:24px;height:24px;border:none;border-radius:50%;padding:0;cursor:pointer;background:transparent;flex-shrink:0;outline:none';
                maskColorInput.title = 'Custom backdrop color';

                const saveMaskColor = (hex) => _saveMaskCfg({ fanMaskColor: hex });
                maskColorInput.addEventListener('input',  () => saveMaskColor(maskColorInput.value));
                maskColorInput.addEventListener('change', () => saveMaskColor(maskColorInput.value));

                maskPresets.forEach(hex => {
                    const sw = document.createElement('button');
                    sw.type  = 'button';
                    sw.style.cssText = `
                        width:18px;height:18px;border-radius:50%;
                        border:2px solid ${hex === curMaskHex ? _grpSelRing : 'transparent'};
                        background:${hex};cursor:pointer;flex-shrink:0;padding:0;
                        transition:border-color .1s,transform .1s;
                        box-shadow: inset 0 0 0 1px rgba(0,0,0,.15);
                    `;
                    sw.addEventListener('mouseover', () => sw.style.transform = 'scale(1.2)');
                    sw.addEventListener('mouseout',  () => sw.style.transform = '');
                    sw.addEventListener('click', () => {
                        maskColorInput.value = hex;
                        saveMaskColor(hex);
                        colorRow.querySelectorAll('button[data-swatch]').forEach(s => s.style.borderColor = 'transparent');
                        sw.style.borderColor = _grpSelRing;
                    });
                    sw.dataset.swatch = hex;
                    colorRow.appendChild(sw);
                });
                colorRow.appendChild(maskColorInput);
                wrap.appendChild(topRow);
                wrap.appendChild(colorRow);
                return wrap;
            })();
            grpGroups.append(fanMaskColorRow);

            grpGroups.append(makeSliderRow(
                T.sp_grp_fan_mask_alpha || 'Backdrop Opacity', Math.round(_maskAlpha * 100), 5, 100, 5, '%',
                null,
                (n) => { _saveMaskCfg({ fanMaskAlpha: n / 100 }); },
                null, 45
            ));

            grpGroups.append(makeSliderRow(
                T.sp_grp_fan_mask_span || 'Backdrop Spread', _maskSpanDeg, 20, 340, 10, '°',
                null,
                (n) => { _saveMaskCfg({ fanMaskSpanDeg: n }); },
                null, 160
            ));

            grpGroups.append(makeSliderRow(
                T.sp_grp_fan_mask_radius || 'Backdrop Size', _maskRadius, 60, 360, 10, 'px',
                null,
                (n) => { _saveMaskCfg({ fanMaskRadius: n }); },
                null, 180
            ));

            const grpBtnRow = document.createElement('div');
            grpBtnRow.style.cssText = 'padding:6px 12px 10px';
            grpBtnRow.addEventListener('click', e => e.stopPropagation());

            const manageBtn = document.createElement('button');
            manageBtn.type = 'button';
            manageBtn.textContent = T.sp_grp_manage || 'Manage Groups';
            manageBtn.style.cssText = `
                width:100%;padding:8px 0;border-radius:8px;
                border:1px solid ${_grpBtnBd};
                background:${_grpBtnBg};
                color:${_grpBtnFg};
                font-size:12px;font-weight:500;cursor:pointer;
                font-family:inherit;text-align:center;line-height:1;
                transition:background .12s,border-color .12s;
            `;
            manageBtn.addEventListener('mouseover', () => { manageBtn.style.background=_grpBtnBgH; manageBtn.style.borderColor=_grpBtnBdH; });
            manageBtn.addEventListener('mouseout',  () => { manageBtn.style.background=_grpBtnBg; manageBtn.style.borderColor=_grpBtnBd; });
            manageBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                showGroupManagerModal();
            });
            grpBtnRow.appendChild(manageBtn);
            grpGroups.append(grpBtnRow);

            const textBmColorRow = (() => {
                const wrap = document.createElement('div');
                wrap.style.cssText = `padding:8px 12px;border-bottom:1px solid ${_grpDiv}`;
                wrap.addEventListener('click', e => e.stopPropagation());

                const topRow = document.createElement('div');
                topRow.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:7px';
                const lbl = document.createElement('span');
                lbl.style.cssText = `font-size:12px;color:${_grpSub}`;
                lbl.textContent = '📝 Text Bookmark — Content Color';
                topRow.appendChild(lbl);

                const colorRow = document.createElement('div');
                colorRow.style.cssText = 'display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin-top:2px';

                const _tbmCfgRaw = (() => { try { return JSON.parse(GM_getValue(KEY_TEXT_BM_CFG, '{}')); } catch(_) { return {}; } })();
                const curTbmColor = _tbmCfgRaw.textContentColor || '#ffd700';

                const tbmPresets = ['#ffd700','#ffffff','#1d9bf0','#ff9f43','#7bed9f','#a29bfe','#ff6b6b'];
                const tbmInput = document.createElement('input');
                tbmInput.type  = 'color';
                tbmInput.value = curTbmColor;
                tbmInput.style.cssText = 'width:24px;height:24px;border:none;border-radius:50%;padding:0;cursor:pointer;background:transparent;flex-shrink:0;outline:none';
                tbmInput.title = 'Custom text color';

                const saveTbmColor = (hex) => {
                    const cfg = (() => { try { return JSON.parse(GM_getValue(KEY_TEXT_BM_CFG, '{}')); } catch(_) { return {}; } })();
                    cfg.textContentColor = hex;
                    GM_setValue(KEY_TEXT_BM_CFG, JSON.stringify(cfg));
                };
                tbmInput.addEventListener('input',  () => saveTbmColor(tbmInput.value));
                tbmInput.addEventListener('change', () => saveTbmColor(tbmInput.value));

                tbmPresets.forEach(hex => {
                    const sw = document.createElement('button');
                    sw.type = 'button';
                    sw.style.cssText = `
                        width:18px;height:18px;border-radius:50%;
                        border:2px solid ${hex === curTbmColor ? _grpSelRing : 'transparent'};
                        background:${hex};cursor:pointer;flex-shrink:0;padding:0;
                        transition:border-color .1s,transform .1s;
                        box-shadow:inset 0 0 0 1px rgba(0,0,0,.2);
                    `;
                    sw.addEventListener('mouseover', () => sw.style.transform = 'scale(1.2)');
                    sw.addEventListener('mouseout',  () => sw.style.transform = '');
                    sw.addEventListener('click', () => {
                        tbmInput.value = hex;
                        saveTbmColor(hex);
                        colorRow.querySelectorAll('button[data-tbm-swatch]').forEach(s => s.style.borderColor = 'transparent');
                        sw.style.borderColor = _grpSelRing;
                        showToast('Text Bookmark Content Color → ' + hex);
                    });
                    sw.dataset.tbmSwatch = hex;
                    colorRow.appendChild(sw);
                });
                colorRow.appendChild(tbmInput);
                wrap.appendChild(topRow);
                wrap.appendChild(colorRow);
                return wrap;
            })();
            grpGroups.append(textBmColorRow);

            const _fanOnlyChildren = [glowColorRow, labelColorRow, fanMaskRow, fanMaskColorRow];
            const _mainToggleOnlyChildren = [popupStyleRow, grpBtnRow];
            const _getGroupSliders = () => Array.from(grpGroups.body.querySelectorAll('.tm-sp-slider-row'));
            const _syncGroupChildrenDisabled = () => {
                const isOn = GM_getValue(KEY_GROUP_ON_DOWNLOAD, false);
                const isListStyle = GM_getValue(KEY_GROUP_POPUP_STYLE, 'fan') === 'list';
                const fanDisabled = !isOn || isListStyle;
                [..._fanOnlyChildren, ..._getGroupSliders()].forEach(el => {
                    el.classList.toggle('tm-sp-disabled-child', fanDisabled);
                });
                _mainToggleOnlyChildren.forEach(el => {
                    el.classList.toggle('tm-sp-disabled-child', !isOn);
                });
            };
            _syncGroupChildrenDisabled();

            const grpCorner = makeGroup(T.sp_grp_corner || '📌  Corner Position', true, null, () => {
                markFeatureSeen('sp_corner_position');
            });

            if (isFeatureNew('sp_corner_position')) {
                const grpCornerHeader = _spGroupRegistry[_spGroupRegistry.length - 1]?.header || null;
                if (grpCornerHeader) {
                    const newBadge = document.createElement('span');
                    newBadge.className = 'tm-sp-new-badge';
                    newBadge.textContent = 'NEW';
                    newBadge.style.marginLeft = '6px';
                    const chevron = grpCornerHeader.querySelector('.tm-sp-group-chevron');
                    grpCornerHeader.insertBefore(newBadge, chevron);
                }
            }

            const _curCorner = GM_getValue(KEY_GEAR_CORNER, 'tr');
            const cornerRow = document.createElement('div');
            cornerRow.style.cssText = `padding: 10px 12px 12px; display: flex; justify-content: center;`;

            const cornerGrid = document.createElement('div');
            cornerGrid.style.cssText = `
                display: grid; grid-template-columns: 1fr 1fr 1fr;
                gap: 6px; width: 210px;
            `;

            const CORNERS = [
                { value: 'tl', label: '↖ Top Left',      row: 1, col: 1, dotH: 'flex-start', dotV: 'flex-start' },
                { value: 'tc', label: '↑ Top Center',     row: 1, col: 2, dotH: 'center',      dotV: 'flex-start' },
                { value: 'tr', label: '↗ Top Right',      row: 1, col: 3, dotH: 'flex-end',    dotV: 'flex-start' },
                { value: 'lc', label: '← Left Center',    row: 2, col: 1, dotH: 'flex-start', dotV: 'center'      },
                { value: 'rc', label: '→ Right Center',   row: 2, col: 3, dotH: 'flex-end',    dotV: 'center'      },
                { value: 'bl', label: '↙ Bottom Left',    row: 3, col: 1, dotH: 'flex-start', dotV: 'flex-end'    },
                { value: 'bc', label: '↓ Bottom Center',  row: 3, col: 2, dotH: 'center',      dotV: 'flex-end'    },
                { value: 'br', label: '↘ Bottom Right',   row: 3, col: 3, dotH: 'flex-end',    dotV: 'flex-end'    },
            ];

            let _centerClicks = 0;

            const _eggBubbleAC = new AbortController();
            _lastEggBubbleAC = _eggBubbleAC;
            const _eggBubble = (() => {
                const el = document.createElement('div');
                el.id = 'tm-egg-bubble-el';
                el.style.cssText = `
                    display: none; position: fixed; z-index: 9999999;
                    background: #fff; color: #111;
                    border: 2.5px solid #111; border-radius: 12px;
                    padding: 10px 14px; font-size: 12px; font-weight: 700;
                    font-family: system-ui, sans-serif; line-height: 1.6;
                    white-space: pre-line; pointer-events: none;
                    box-shadow: 3px 3px 0 #111;
                    max-width: 240px;
                    animation: none;
                `;
                const tail = document.createElement('div');
                tail.style.cssText = `
                    position: absolute; top: 100%; left: 50%;
                    transform: translateX(-50%);
                    width: 0; height: 0;
                    border-left: 9px solid transparent;
                    border-right: 9px solid transparent;
                    border-top: 10px solid #111;
                `;
                const tailInner = document.createElement('div');
                tailInner.style.cssText = `
                    position: absolute; top: -12px; left: 50%;
                    transform: translateX(-50%);
                    width: 0; height: 0;
                    border-left: 7px solid transparent;
                    border-right: 7px solid transparent;
                    border-top: 9px solid #fff;
                `;
                tail.appendChild(tailInner);
                el.appendChild(tail);
                document.body.appendChild(el);
                return el;
            })();

            const _positionBubble = (anchorEl) => {
                const r = anchorEl.getBoundingClientRect();
                _eggBubble.style.left = (r.left + r.width / 2) + 'px';
                _eggBubble.style.transform = 'translateX(-50%)';
                _eggBubble.style.top = (r.top - 10) + 'px';
                requestAnimationFrame(() => {
                    const bh = _eggBubble.offsetHeight;
                    const topAbove = r.top - bh - 10;
                    if (topAbove < 8) {
                        _eggBubble.style.top = (r.bottom + 10) + 'px';
                        const tail = _eggBubble.lastChild;
                        tail.style.top = '-10px';
                        tail.style.bottom = 'auto';
                        tail.style.borderTop = 'none';
                        tail.style.borderBottom = '10px solid #111';
                        tail.firstChild.style.top = '3px';
                        tail.firstChild.style.borderTop = 'none';
                        tail.firstChild.style.borderBottom = '9px solid #fff';
                    } else {
                        _eggBubble.style.top = topAbove + 'px';
                        const tail = _eggBubble.lastChild;
                        tail.style.top = '100%';
                        tail.style.bottom = 'auto';
                        tail.style.borderTop = '10px solid #111';
                        tail.style.borderBottom = 'none';
                        tail.firstChild.style.top = '-12px';
                        tail.firstChild.style.borderTop = '9px solid #fff';
                        tail.firstChild.style.borderBottom = 'none';
                    }
                });
            };

            const _showEggBubble = (text, anchorEl) => {
                const tail = _eggBubble.lastChild;
                _eggBubble.textContent = text;
                _eggBubble.appendChild(tail);
                _eggBubble.style.display = 'block';
                _positionBubble(anchorEl);
            };

            const _hideEggBubble = () => { _eggBubble.style.display = 'none'; };

            document.addEventListener('click', (e) => {
                if (!e.target.closest('#tm-settings-wrapper')) _hideEggBubble();
            }, { capture: true, passive: true, signal: _eggBubbleAC.signal });

            for (let r = 1; r <= 3; r++) {
                for (let cl = 1; cl <= 3; cl++) {
                    if (r === 2 && cl === 2) {
                        const eggCell = document.createElement('button');
                        eggCell.type = 'button';
                        eggCell.title = '·';
                        eggCell.dataset.cornerValue = 'cc';
                        eggCell.style.cssText = `
                            width: 64px; height: 44px; border-radius: 8px; cursor: pointer;
                            border: 1.5px solid ${C.border}; background: transparent;
                            position: relative; display: flex;
                            align-items: center; justify-content: center;
                            padding: 6px; transition: border-color .12s, background .12s;
                            font-family: inherit;
                        `;
                        const eggDot = document.createElement('div');
                        eggDot.style.cssText = `
                            width: 6px; height: 6px; border-radius: 50%;
                            background: ${C.sub}; opacity: 0.35;
                        `;
                        eggCell.appendChild(eggDot);

                        eggCell.addEventListener('click', (e) => {
                            e.stopPropagation();
                            _centerClicks++;

                            if (_centerClicks === 1) {
                                let blinks = 0;
                                const blink = () => {
                                    cornerGrid.style.opacity = cornerGrid.style.opacity === '0.15' ? '1' : '0.15';
                                    blinks++;
                                    if (blinks < 6) setTimeout(blink, 75);
                                    else cornerGrid.style.opacity = '1';
                                };
                                blink();
                                setTimeout(() => _showEggBubble('Are you sure? 🤔', eggCell), 500);

                            } else if (_centerClicks === 2) {
                                _showEggBubble(
                                    "Okay, I admit it.\nTop / Bottom / Left / Right Center?\nCompletely made up — just to fill the grid. 😅\n\nClick again if you really mean it.",
                                    eggCell
                                );

                            } else if (_centerClicks >= 3) {
                                _animateWrapperToCorner('cc');
                                wrapper.setAttribute('data-open', 'false');
                                _centerClicks = 999;
                                const _gearBtn = document.getElementById('tm-settings-gear-btn');
                                setTimeout(() => {
                                    const anchor = _gearBtn || eggCell;
                                    _showEggBubble("See? You and I both messed this up.\nCongrats on finding it! 🎉", anchor);

                                    setTimeout(() => {
                                        if (!document.getElementById('tm-egg-anim-style')) {
                                            const s = document.createElement('style');
                                            s.id = 'tm-egg-anim-style';
                                            s.textContent = `
                                                @keyframes tm-egg-ptcl {
                                                    0%   { opacity: 1; transform: translate(0, 0) scale(1) rotate(var(--ptcl-rot, 0deg)); }
                                                    60%  { opacity: 0.75; }
                                                    100% { opacity: 0; transform: translate(var(--ptcl-dx), var(--ptcl-dy)) scale(0.15) rotate(var(--ptcl-rot, 0deg)); }
                                                }
                                            `;
                                            document.head.appendChild(s);
                                        }

                                        const wRect  = wrapper.getBoundingClientRect();
                                        const cx     = Math.round(wRect.left + wRect.width  / 2);
                                        const cy     = Math.round(wRect.top  + wRect.height / 2);
                                        const N      = 10;
                                        const COLORS = ['#1d9bf0', '#f59e0b', '#e2e8f0', '#f472b6', '#a78bfa'];
                                        const ptcls  = [];

                                        for (let i = 0; i < N; i++) {
                                            const angle = (i / N) * Math.PI * 2;
                                            const dist  = 55 + (i % 3) * 18;
                                            const dx    = Math.round(Math.cos(angle) * dist);
                                            const dy    = Math.round(Math.sin(angle) * dist);
                                            const size  = 4 + (i % 3);
                                            const color = COLORS[i % COLORS.length];
                                            const dur   = 950 + (i % 3) * 80;
                                            const delay = i * 30;
                                            const rot = i % 2 !== 0 ? '45deg' : '0deg';

                                            const p = document.createElement('div');
                                            p.style.cssText = `
                                                position:fixed; pointer-events:none; z-index:9999991;
                                                left:${cx - Math.floor(size / 2)}px;
                                                top:${cy  - Math.floor(size / 2)}px;
                                                width:${size}px; height:${size}px;
                                                background:${color};
                                                border-radius:${i % 2 === 0 ? '50%' : '1.5px'};
                                                --ptcl-dx:${dx}px; --ptcl-dy:${dy}px; --ptcl-rot:${rot};
                                                animation: tm-egg-ptcl ${dur}ms ${delay}ms ease-out forwards;
                                            `;
                                            document.body.appendChild(p);
                                            ptcls.push(p);
                                        }

                                        setTimeout(() => ptcls.forEach(p => p.remove()), 1500);

                                    }, 400);

                                    setTimeout(() => {
                                        _hideEggBubble();
                                        eggCell.style.visibility = 'hidden';
                                        eggCell.style.pointerEvents = 'none';
                                    }, 3500);
                                }, 120);
                            }
                        });

                        cornerGrid.appendChild(eggCell);
                        continue;
                    }
                    const c = CORNERS.find(x => x.row === r && x.col === cl);
                    if (!c) continue;
                    const cell = document.createElement('button');
                    cell.type = 'button';
                    cell.title = c.label;
                    const isActive = _curCorner === c.value;
                    cell.style.cssText = `
                        width: 64px; height: 44px; border-radius: 8px; cursor: pointer;
                        border: 1.5px solid ${isActive ? '#1d9bf0' : C.border};
                        background: ${isActive ? 'rgba(29,155,240,.12)' : 'transparent'};
                        position: relative; display: flex;
                        align-items: ${c.dotV}; justify-content: ${c.dotH};
                        padding: 6px; transition: border-color .12s, background .12s;
                        font-family: inherit;
                    `;
                    const dot = document.createElement('div');
                    dot.style.cssText = `
                        width: 10px; height: 10px; border-radius: 50%;
                        background: ${isActive ? '#1d9bf0' : C.sub};
                        transition: background .12s;
                    `;
                    cell.appendChild(dot);
                    cell.addEventListener('click', () => {
                        GM_setValue(KEY_GEAR_CORNER, c.value);
                        _animateWrapperToCorner(c.value);
                        wrapper.setAttribute('data-open', 'false');
                        showToast('📌 Corner → ' + c.label);
                        cornerGrid.querySelectorAll('button').forEach(b => {
                            const bv = b.dataset.cornerValue;
                            const bActive = bv === c.value;
                            b.style.border = `1.5px solid ${bActive ? '#1d9bf0' : C.border}`;
                            b.style.background = bActive ? 'rgba(29,155,240,.12)' : 'transparent';
                            const bDot = b.querySelector('div');
                            if (bDot) bDot.style.background = bActive ? '#1d9bf0' : C.sub;
                        });
                    });
                    cell.dataset.cornerValue = c.value;
                    cornerGrid.appendChild(cell);
                }
            }

            cornerRow.appendChild(cornerGrid);
            grpCorner.append(cornerRow);

            const HIST_TOOLTIP = T.hist_panel_tooltip ||
                'Hidden feature: The history panel has invisible dock triggers (⋮) on its left & right edges. Hover or click the ⋮ button to auto-hide the panel to the screen edge!';
            const grpHist = makeGroup(T.sp_grp_history || '🗂  History Panel', false, HIST_TOOLTIP, showDockSpotlight);

            const _DS = {
                bg:    dark ? 'rgba(255,255,255,0.06)' : 'rgba(15,20,25,0.05)',
                strip: dark ? 'rgba(255,255,255,0.12)' : 'rgba(15,20,25,0.09)',
                blue:  'rgba(29,155,240,0.7)',
                blueL: 'rgba(29,155,240,0.35)',
                sub:   dark ? 'rgba(255,255,255,0.25)' : 'rgba(15,20,25,0.22)',
            };
            const _DS_ghostBorder = dark ? 'rgba(255,255,255,0.10)' : 'rgba(15,20,25,0.15)';
            const _DS_ghostLine   = dark ? 'rgba(255,255,255,0.08)' : 'rgba(15,20,25,0.12)';
            const dockStyleOpts = [
                { value: 'notch', label: T.sp_dock_notch || 'Notch',
                  svg: `<svg viewBox="0 0 28 52" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="9" y="0" width="10" height="52" rx="1" fill="${_DS.bg}"/><rect x="12" y="20" width="4" height="12" rx="2" fill="${_DS.blue}"/></svg>` },
                { value: 'ruler', label: T.sp_dock_ruler || 'Ruler',
                  svg: `<svg viewBox="0 0 28 52" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="9" y="0" width="10" height="52" rx="1" fill="${_DS.strip}"/><line x1="9" y1="4"  x2="19" y2="4"  stroke="${_DS.blue}"  stroke-width="1.5"/><line x1="9" y1="10" x2="15" y2="10" stroke="${_DS.blueL}" stroke-width="0.8"/><line x1="9" y1="16" x2="15" y2="16" stroke="${_DS.blueL}" stroke-width="0.8"/><line x1="9" y1="22" x2="19" y2="22" stroke="${_DS.blue}"  stroke-width="1.5"/><line x1="9" y1="28" x2="15" y2="28" stroke="${_DS.blueL}" stroke-width="0.8"/><line x1="9" y1="34" x2="15" y2="34" stroke="${_DS.blueL}" stroke-width="0.8"/><line x1="9" y1="40" x2="19" y2="40" stroke="${_DS.blue}"  stroke-width="1.5"/><line x1="9" y1="46" x2="15" y2="46" stroke="${_DS.blueL}" stroke-width="0.8"/></svg>` },
                { value: 'ghost', label: T.sp_dock_ghost || 'Ghost',
                  svg: `<svg viewBox="0 0 28 52" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="0" width="8" height="52" rx="1" fill="none" stroke="${_DS_ghostBorder}" stroke-width="1" stroke-dasharray="3 4"/><line x1="10" y1="7"  x2="18" y2="7"  stroke="${_DS_ghostLine}" stroke-width="0.8"/><line x1="10" y1="14" x2="18" y2="14" stroke="${_DS_ghostLine}" stroke-width="0.8"/><line x1="10" y1="21" x2="18" y2="21" stroke="${_DS_ghostLine}" stroke-width="0.8"/><line x1="10" y1="28" x2="18" y2="28" stroke="${_DS_ghostLine}" stroke-width="0.8"/><line x1="10" y1="35" x2="18" y2="35" stroke="${_DS_ghostLine}" stroke-width="0.8"/><line x1="10" y1="42" x2="18" y2="42" stroke="${_DS_ghostLine}" stroke-width="0.8"/></svg>` },
                { value: 'pill',  label: T.sp_dock_pill  || 'Pill',
                  svg: `<svg viewBox="0 0 28 52" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="11" y="0" width="6" height="52" rx="0" fill="${_DS.bg}"/><rect x="11" y="16" width="6" height="20" rx="3" fill="${_DS.blue}"/></svg>` },
                { value: 'arrow', label: T.sp_dock_arrow || 'Arrow',
                  svg: `<svg viewBox="0 0 28 52" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="0" width="8" height="52" rx="1" fill="${_DS.bg}"/><path d="M12 21 L17 26 L12 31" stroke="${_DS.blue}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>` },
                { value: 'dots',  label: T.sp_dock_dots  || 'Dots',
                  svg: `<svg viewBox="0 0 28 52" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="10" y="0" width="8" height="52" rx="1" fill="${_DS.bg}"/><circle cx="14" cy="19" r="1.8" fill="${_DS.sub}"/><circle cx="14" cy="26" r="1.8" fill="${_DS.blue}"/><circle cx="14" cy="33" r="1.8" fill="${_DS.sub}"/></svg>` },
            ];
            grpHist.append(makeDockStylePickerRow(T.sp_grp_dock_style || 'Dock Style', dockStyleOpts, dockStyle, (next) => {
                GM_setValue(KEY_DOCK_STYLE, next);
                showToast('🗂 ' + (T.sp_grp_dock_style || 'Dock Style') + ' → ' + (dockStyleOpts.find(o => o.value === next)?.label ?? next));
                const curTab = document.getElementById('tm-hist-dock-tab');
                if (curTab) {
                    const sideClass = [...curTab.classList].find(c => c.startsWith('side-')) ?? '';
                    curTab.className = 'style-' + next + (sideClass ? ' ' + sideClass : '');
                    curTab.innerHTML = '';
                    if (next === 'dots') {
                        for (let i = 0; i < 3; i++) {
                            const d = document.createElement('span');
                            d.className = 'tm-dock-dot';
                            curTab.appendChild(d);
                        }
                    }
                }
            }, 'sp_dock_picker', 'arrow'));

            grpHist.append(makeSliderRow(
                T.sp_grp_hover_delay || 'Hover Delay', dockHoverDelay, 100, 3000, 50, 'ms',
                null,
                (n) => { GM_setValue(KEY_DOCK_HOVER_DELAY, String(n)); showToast('⏱ ' + (T.sp_grp_hover_delay || 'Hover Delay') + ' → ' + n + ' ms'); },
                'sp_slider_controls', 500
            ));

            grpHist.append(makeSliderRow(
                T.sp_grp_trigger_l || 'Trigger Distance ◀ Left', dockTriggerL, 20, 120, 5, 'px',
                (n) => {
                    const hotzone = document.querySelector('#tm-hist-dock-tab.side-left .tm-dock-hotzone');
                    if (hotzone) hotzone.style.width = n + 'px';
                },
                (n) => { GM_setValue(KEY_DOCK_TRIGGER_L, String(n)); },
                'sp_slider_controls', 80
            ));

            grpHist.append(makeSliderRow(
                T.sp_grp_trigger_r || 'Trigger Distance ▶ Right', dockTriggerR, 20, 120, 5, 'px',
                (n) => {
                    const hotzone = document.querySelector('#tm-hist-dock-tab.side-right .tm-dock-hotzone');
                    if (hotzone) hotzone.style.width = n + 'px';
                },
                (n) => { GM_setValue(KEY_DOCK_TRIGGER_R, String(n)); },
                'sp_slider_controls', 80
            ));

            {
                const _remRow = makeRow(
                    T.sp_dock_remember_pos || 'Remember Edge Position',
                    () => GM_getValue(KEY_DOCK_REMEMBER_POS, false) ? (T.status_on || 'On') : (T.status_off || 'Off'),
                    () => {
                        const next = !GM_getValue(KEY_DOCK_REMEMBER_POS, false);
                        GM_setValue(KEY_DOCK_REMEMBER_POS, next);
                        showToast('📌 ' + (T.sp_dock_remember_pos || 'Remember Edge Position') + ' → ' + (next ? (T.status_on || 'On') : (T.status_off || 'Off')));
                    },
                    'sp_dock_remember_pos'
                );
                const _remDesc = document.createElement('span');
                _remDesc.className = 'tm-sp-row-desc';
                _remDesc.textContent = T.sp_dock_remember_pos_desc || 'Remembers panel Y position after dragging while peeked';
                _remRow.querySelector('.tm-sp-row-left').appendChild(_remDesc);
                grpHist.append(_remRow);
            }

            {
                const _rstRow = makeRow(
                    T.sp_dock_reset_pos || 'Reset Panel Position',
                    () => T.sp_dock_reset_btn || 'Reset',
                    () => {
                        GM_deleteValue(KEY_HISTORY_PANEL_POS);
                        const existPanel = document.getElementById('tm-hist-panel');
                        if (existPanel) existPanel.dispatchEvent(new CustomEvent('tm-hist-reset-pos'));
                        showToast('↺ ' + (T.sp_dock_reset_pos || 'Reset Panel Position'));
                    },
                    'sp_dock_reset_pos'
                );
                const _rstDesc = document.createElement('span');
                _rstDesc.className = 'tm-sp-row-desc';
                _rstDesc.textContent = T.sp_dock_reset_pos_desc || 'Restore panel to default coordinates (dock side kept)';
                _rstRow.querySelector('.tm-sp-row-left').appendChild(_rstDesc);
                grpHist.append(_rstRow);
            }

            const grpAdv = makeGroup(T.sp_group_advanced || '⚙ Advanced', false, null);

            const ALL_SETTING_KEYS = [
                KEY_PREFIX_TEXT, KEY_LANG, KEY_LINK_DOMAIN_CLICK, KEY_CLICK_MODE_CUSTOM,
                KEY_DATE_FORMAT, KEY_CUSTOM_LANG, KEY_VIDEO_VOLUME, KEY_VIDEO_SPEED,
                KEY_FEEDBACK_STYLE, KEY_CLICK_MODE, KEY_GEAR_DISPLAY_MODE, KEY_GEAR_CORNER,
                KEY_DOCK_STYLE, KEY_DOCK_HOVER_DELAY, KEY_DOCK_TRIGGER_L, KEY_DOCK_TRIGGER_R,
                KEY_DOCK_PERSISTED, KEY_DOCK_REMEMBER_POS, KEY_GROUP_ON_DOWNLOAD, KEY_SCAN_INTERVAL,
                KEY_CUSTOM_MEDIA_ACTIONS, KEY_CUSTOM_LINK_ACTIONS, KEY_GROUP_PANEL_CFG,
                KEY_TF_CSS_SIMPLIFY, KEY_TF_FEED_PRUNE, KEY_GRID_MEDIA_BTN,
                KEY_AVATAR_MEDIA_BTN, KEY_CUSTOM_FILENAME_ENABLED, KEY_CUSTOM_FILENAME_TEMPLATE,
                KEY_GROUP_POPUP_STYLE, KEY_TEXT_BM_CFG,
                KEY_LEGACY_MEDIA_GRID,
                KEY_APP_THEME, KEY_APP_THEME_HISTORY, KEY_THEME_SYNC,
                KEY_CUSTOM_THEME_ENABLED, KEY_CUSTOM_THEME_COLORS,
                KEY_LINK_ICON_STYLE, KEY_MEDIA_ICON_STYLE, KEY_SP_GROUP_OPEN,
            ];
            function _exportSettings() {
                const snap = {};
                ALL_SETTING_KEYS.forEach(k => { const v = GM_getValue(k, null); if (v !== null) snap[k] = v; });
                const blob = new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' });
                const a = Object.assign(document.createElement('a'), {
                    href: URL.createObjectURL(blob),
                    download: `tm-settings-${new Date().toISOString().slice(0, 10)}.json`,
                });
                a.click(); URL.revokeObjectURL(a.href);
            }
            function _importSettings(json) {
                try {
                    const data = JSON.parse(json);
                    const valid = new Set(ALL_SETTING_KEYS);
                    Object.entries(data).forEach(([k, v]) => { if (valid.has(k)) GM_setValue(k, v); });
                    showToast('✅ Settings imported.', 3000);
                    createSettingsPanel();
                } catch (_) { showToast('❌ Invalid settings file.', 3000); }
            }
            const _ROW_CSS = `display:flex;align-items:center;padding:8px 14px;gap:8px;font:13px system-ui;color:${C.text};`;
            const _BTN_CSS = `padding:4px 10px;border-radius:6px;border:1px solid ${C.border};background:${C.inputBg};color:${C.text};cursor:pointer;font:12px system-ui;`;

            const backupRow = document.createElement('div');
            backupRow.style.cssText = _ROW_CSS;
            const backupLabel = document.createElement('span');
            backupLabel.textContent = T.sp_backup_settings || '📤 Backup settings';
            backupLabel.style.flex = '1';
            const backupBtn = document.createElement('button');
            backupBtn.textContent = T.sp_backup_btn || 'Export JSON';
            backupBtn.style.cssText = _BTN_CSS;
            backupBtn.onclick = _exportSettings;
            backupRow.append(backupLabel, backupBtn);
            grpAdv.append(backupRow);

            const importRow = document.createElement('div');
            importRow.style.cssText = _ROW_CSS;
            const importLabel = document.createElement('span');
            importLabel.textContent = T.sp_restore_settings || '📥 Restore settings';
            importLabel.style.flex = '1';
            const importFileInput = document.createElement('input');
            importFileInput.type = 'file';
            importFileInput.accept = '.json,application/json';
            importFileInput.style.display = 'none';
            importFileInput.addEventListener('change', () => {
                const file = importFileInput.files?.[0];
                if (!file) return;
                const reader = new FileReader();
                reader.onload = e2 => _importSettings(e2.target.result);
                reader.readAsText(file);
                importFileInput.value = '';
            });
            const importBtn = document.createElement('button');
            importBtn.textContent = T.sp_restore_btn || 'Choose file…';
            importBtn.style.cssText = _BTN_CSS;
            importBtn.onclick = () => importFileInput.click();
            importRow.append(importLabel, importBtn, importFileInput);
            grpAdv.append(importRow);

            const resetRow = makeRow(T.sp_reset_defaults || '🔄 Reset to defaults', '', () => {
                const confirmed = confirm(T.sp_reset_confirm ||
                    'Reset all settings to defaults?\n\n' +
                    'This will clear: copy format, feedback style, dock style, trigger distances, ' +
                    'group panel appearance, corner position, button visibility, and list/thumb view mode.\n\n' +
                    'Download history and groups will NOT be affected.'
                );
                if (!confirmed) return;
                const RESET_KEYS = [
                    KEY_PREFIX_TEXT, KEY_LINK_DOMAIN_CLICK, KEY_CLICK_MODE_CUSTOM,
                    KEY_CLICK_MODE,
                    KEY_DATE_FORMAT, KEY_FEEDBACK_STYLE,
                    KEY_DOCK_STYLE, KEY_DOCK_HOVER_DELAY,
                    KEY_DOCK_TRIGGER_L, KEY_DOCK_TRIGGER_R, KEY_DOCK_PERSISTED,
                    KEY_GEAR_CORNER, KEY_GEAR_DISPLAY_MODE,
                    KEY_GROUP_ON_DOWNLOAD, KEY_GROUP_PANEL_CFG,
                    KEY_SP_GROUP_OPEN, KEY_SEEN_FEATURES,
                    KEY_HISTORY_VIEW_MODE,
                    KEY_SCAN_INTERVAL,
                ];
                RESET_KEYS.forEach(k => GM_deleteValue(k));
                buildContent();
                showToast('✅ Settings reset to defaults.');
            });
            resetRow.style.opacity = '0.7';
            grpAdv.append(resetRow);

            const bearerGroupTitle = T.sp_bearer_title || '🔑 Custom Bearer Token';
            const bearerGroupHeader = document.createElement('div');
            bearerGroupHeader.className = 'tm-tf-group-header';
            const bearerGroupLabel = document.createElement('span');
            bearerGroupLabel.textContent = bearerGroupTitle;
            bearerGroupHeader.appendChild(bearerGroupLabel);
            const bearerGroupChevron = document.createElement('span');
            bearerGroupChevron.className = 'tm-tf-group-header-chevron';
            bearerGroupChevron.innerHTML = `<svg viewBox="0 0 10 10" width="9" height="9" fill="currentColor"><path d="M1 3l4 4 4-4z"/></svg>`;
            bearerGroupHeader.appendChild(bearerGroupChevron);

            const bearerGroupBody = document.createElement('div');
            bearerGroupBody.className = 'tm-tf-group-body';

            let bearerGroupOpen = false;
            try {
                const saved = JSON.parse(GM_getValue(KEY_SP_GROUP_OPEN, '{}'));
                if (bearerGroupTitle in saved) bearerGroupOpen = saved[bearerGroupTitle];
            } catch (_) {}
            if (!bearerGroupOpen) {
                bearerGroupBody.classList.add('collapsed');
                bearerGroupChevron.style.transform = 'rotate(-90deg)';
            } else {
                bearerGroupChevron.style.transform = 'rotate(0deg)';
            }
            bearerGroupHeader.addEventListener('click', () => {
                const isCollapsed = bearerGroupBody.classList.toggle('collapsed');
                bearerGroupChevron.style.transform = isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)';
                try {
                    const saved = JSON.parse(GM_getValue(KEY_SP_GROUP_OPEN, '{}'));
                    saved[bearerGroupTitle] = !isCollapsed;
                    GM_setValue(KEY_SP_GROUP_OPEN, JSON.stringify(saved));
                } catch (_) {}
                if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
            });

            const bearerRow = document.createElement('div');
            bearerRow.style.cssText = `padding:8px 14px;font:13px system-ui;color:${C.text};`;
            const bearerTitleRow = document.createElement('div');
            bearerTitleRow.style.cssText = 'margin-bottom:5px;';
            const bearerLabelRow = document.createElement('div');
            bearerLabelRow.style.cssText = 'display:flex;align-items:center;justify-content:flex-end;gap:6px;margin-bottom:4px;';
            const bearerHelpBtn = document.createElement('button');
            bearerHelpBtn.title = 'What is this?';
            bearerHelpBtn.innerHTML = `<svg viewBox="0 0 16 16" width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg">
                <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.4"/>
                <path d="M6.5 6.4C6.5 5.51 7.17 4.8 8 4.8s1.5.71 1.5 1.6c0 .78-.52 1.24-1.03 1.6C8.2 8.2 8 8.48 8 8.85" stroke="currentColor" stroke-width="1.25" stroke-linecap="round"/>
                <circle cx="8" cy="10.75" r="0.8" fill="currentColor"/>
            </svg>`;
            bearerHelpBtn.style.cssText = `flex-shrink:0;background:none;border:none;padding:2px;cursor:pointer;color:${C.sub};display:flex;align-items:center;border-radius:50%;transition:color 0.12s;`;
            bearerLabelRow.append(bearerHelpBtn);
            const bearerBtnRow = document.createElement('div');
            bearerBtnRow.style.cssText = 'display:flex;align-items:center;gap:4px;';
            bearerHelpBtn.onmouseenter = () => bearerHelpBtn.style.color = C.text;
            bearerHelpBtn.onmouseleave = () => bearerHelpBtn.style.color = C.sub;

            const bearerPopover = document.createElement('div');
            bearerPopover.style.cssText = `
                display:grid; grid-template-rows:0fr;
                transition:grid-template-rows 0.22s ease, opacity 0.18s ease;
                opacity:0; overflow:hidden; margin-top:0;
            `;
            const bearerPopoverInner = document.createElement('div');
            bearerPopoverInner.style.cssText = `
                min-height:0; overflow:hidden;
            `;
            const bearerPopoverContent = document.createElement('div');
            bearerPopoverContent.style.cssText = `
                background:${C.inputBg}; border:1px solid ${C.border};
                border-radius:8px; padding:8px 10px; margin-top:5px; margin-bottom:2px;
                font:11px system-ui; color:${C.sub}; line-height:1.6;
            `;
            const _bHint = T.sp_bearer_hint || 'Only used on Twitter / X for media fetch (401/403 fallback). Safe to set on any page — the token is never sent cross-domain.';
            const _bManual = T.sp_bearer_manual || 'Manual';
            bearerPopoverContent.innerHTML =
                _escHtml(_bHint) + '<br>' +
                `<b style="color:${C.text}">${_escHtml(_bManual)}:</b> Twitter DevTools → Network → ` +
                `any <code style="font-family:monospace;color:${C.text}">/i/api/</code> request → ` +
                `<code style="font-family:monospace;color:${C.text}">Authorization</code> header.`;
            bearerPopoverInner.appendChild(bearerPopoverContent);
            bearerPopover.appendChild(bearerPopoverInner);

            let _bearerHoverTimer = null;
            let _bearerPopOpen = false;
            const _openBearerPop = () => {
                clearTimeout(_bearerHoverTimer);
                _bearerPopOpen = true;
                bearerPopover.style.gridTemplateRows = '1fr';
                bearerPopover.style.opacity = '1';
                bearerPopover.style.marginTop = '0';
                bearerHelpBtn.style.color = '#1d9bf0';
            };
            const _closeBearerPop = (immediate = false) => {
                clearTimeout(_bearerHoverTimer);
                if (immediate) {
                    _bearerPopOpen = false;
                    bearerPopover.style.gridTemplateRows = '0fr';
                    bearerPopover.style.opacity = '0';
                    bearerHelpBtn.style.color = C.sub;
                } else {
                    _bearerHoverTimer = setTimeout(() => {
                        _bearerPopOpen = false;
                        bearerPopover.style.gridTemplateRows = '0fr';
                        bearerPopover.style.opacity = '0';
                        bearerHelpBtn.style.color = C.sub;
                    }, 600);
                }
            };
            bearerHelpBtn.addEventListener('click', () => {
                _bearerPopOpen ? _closeBearerPop(true) : _openBearerPop();
            });
            bearerRow.addEventListener('mouseenter', () => clearTimeout(_bearerHoverTimer));
            bearerRow.addEventListener('mouseleave', () => {
                if (_bearerPopOpen) _closeBearerPop(false);
            });

            const bearerDetectBtn = document.createElement('button');
            bearerDetectBtn.textContent = T.sp_bearer_detect || 'Auto-detect';
            bearerDetectBtn.title = T.sp_bearer_detect || 'Auto-detect';
            bearerDetectBtn.style.cssText = `padding:3px 9px;border-radius:5px;border:1px solid ${C.border};background:${C.inputBg};color:${_isTwitterDomain ? C.text : C.sub};cursor:pointer;font:11px system-ui;`;

            const bearerApplyBtn = document.createElement('button');
            const bearerEditBtn  = document.createElement('button');
            const _applyLabel = T.sp_bearer_apply || 'Apply';
            const _editLabel  = T.sp_bearer_edit  || 'Edit';
            const _btnBase = `padding:3px 9px;border-radius:5px;border:1px solid ${C.border};background:${C.inputBg};cursor:pointer;font:11px system-ui;`;
            bearerApplyBtn.textContent = _applyLabel;
            bearerApplyBtn.style.cssText = _btnBase + `color:${C.text};`;
            bearerEditBtn.textContent  = _editLabel;
            bearerEditBtn.style.cssText = _btnBase + `color:#1d9bf0;display:none;`;

            const bearerClearBtn = document.createElement('button');
            bearerClearBtn.textContent = T.sp_bearer_clear || 'Clear';
            bearerClearBtn.style.cssText = `padding:3px 9px;border-radius:5px;border:1px solid ${C.border};background:${C.inputBg};color:${_isTwitterDomain ? C.text : C.sub};cursor:pointer;font:11px system-ui;`;
            bearerBtnRow.append(bearerDetectBtn, bearerApplyBtn, bearerEditBtn, bearerClearBtn);
            bearerTitleRow.append(bearerLabelRow, bearerBtnRow);

            const bearerTa = document.createElement('textarea');
            bearerTa.rows = 3;
            bearerTa.placeholder = T.sp_bearer_placeholder || 'Bearer AAAAAAAAAA…  (optional, for 401/403 fallback on Twitter/X)';
            bearerTa.style.cssText = `
                width:100%; box-sizing:border-box;
                background:${C.inputBg}; color:${C.text};
                border:1px solid ${C.border}; border-radius:6px;
                padding:5px 8px; font:11px monospace;
                resize:none; height:58px; overflow-y:auto;
                transition:opacity 0.15s;
            `;
            let _bearerRealVal = GM_getValue(KEY_CUSTOM_BEARER, '');
            bearerTa.value = _bearerRealVal;
            bearerTa.addEventListener('change', () => {
                if (!bearerTa.readOnly) _bearerRealVal = bearerTa.value.trim();
                GM_setValue(KEY_CUSTOM_BEARER, _bearerRealVal);
            });

            let _bearerLocked = _bearerRealVal.length > 0;
            const _syncBearerLock = () => {
                bearerTa.readOnly = _bearerLocked;
                bearerTa.style.opacity  = _bearerLocked ? '0.75' : '1';
                bearerTa.style.cursor   = _bearerLocked ? 'default' : 'text';
                bearerApplyBtn.style.display = _bearerLocked ? 'none' : '';
                bearerEditBtn.style.display  = _bearerLocked ? ''     : 'none';
                bearerTa.value = _bearerLocked
                    ? '*'.repeat(Math.min(_bearerRealVal.length, 48))
                    : _bearerRealVal;
            };
            _syncBearerLock();

            bearerApplyBtn.addEventListener('click', () => {
                _bearerRealVal = bearerTa.value.trim();
                GM_setValue(KEY_CUSTOM_BEARER, _bearerRealVal);
                _bearerLocked = true;
                _syncBearerLock();
                if (_bearerRealVal) showToast('✅ Bearer Token saved.', 2500);
            });
            bearerEditBtn.addEventListener('click', () => {
                _bearerLocked = false;
                _syncBearerLock();
                bearerTa.focus();
                bearerTa.setSelectionRange(bearerTa.value.length, bearerTa.value.length);
            });

            bearerDetectBtn.addEventListener('click', async () => {
                if (!_isTwitterDomain) {
                    showToast('⚠️ Open Twitter / X first, then try auto-detect.', 3500);
                    return;
                }
                const _origTxt = bearerDetectBtn.textContent;
                bearerDetectBtn.textContent = T.sp_bearer_detecting || 'Detecting…';
                bearerDetectBtn.disabled = true;
                try {
                    let found = null;
                    for (const script of document.querySelectorAll('script[src*="main."]')) {
                        if (!script.src) continue;
                        const resp = await fetch(script.src);
                        if (!resp.ok) continue;
                        const text = await resp.text();
                        const m = text.match(/Bearer (AAAAAAA[A-Za-z0-9%_-]{20,})/);
                        if (m) { found = 'Bearer ' + m[1]; break; }
                    }
                    if (found) {
                        _bearerRealVal = found.trim();
                        GM_setValue(KEY_CUSTOM_BEARER, _bearerRealVal);
                        _bearerLocked = true;
                        _syncBearerLock();
                        showToast('✅ Bearer Token detected and saved.', 3000);
                    } else {
                        showToast('⚠️ Not found in page scripts. Use DevTools method (click ❓).', 4000);
                    }
                } catch (err) {
                    showToast('❌ Detection failed: ' + err.message, 3000);
                } finally {
                    bearerDetectBtn.textContent = _origTxt;
                    bearerDetectBtn.disabled = false;
                }
            });
            bearerClearBtn.onclick = () => {
                GM_deleteValue(KEY_CUSTOM_BEARER);
                _bearerRealVal = '';
                bearerTa.value = '';
                _bearerLocked = false;
                _syncBearerLock();
                showToast('🔑 Custom Bearer Token cleared.');
            };
            bearerRow.append(bearerTitleRow, bearerPopover, bearerTa);
            bearerGroupBody.appendChild(bearerRow);
            if (_isTwitterDomain) {
                grpAdv.append(bearerGroupHeader);
                grpAdv.append(bearerGroupBody);
            }

            if (_tfRegistry) {
                const SVG_CHEVRON_TF = `<svg viewBox="0 0 10 10" width="9" height="9" fill="currentColor"><path d="M1 3l4 4 4-4z"/></svg>`;

                const tfGroupHeader = document.createElement('div');
                tfGroupHeader.className = 'tm-tf-group-header';

                const tfGroupTitle = T.pt_group_title || '⚡ Performance Tweaks';
                const tfGroupLabel = document.createElement('span');
                tfGroupLabel.textContent = tfGroupTitle;
                tfGroupHeader.appendChild(tfGroupLabel);

                const tfGroupChevron = document.createElement('span');
                tfGroupChevron.className = 'tm-tf-group-header-chevron';
                tfGroupChevron.innerHTML = SVG_CHEVRON_TF;
                tfGroupHeader.appendChild(tfGroupChevron);

                const tfGroupBody = document.createElement('div');
                tfGroupBody.className = 'tm-tf-group-body';

                const _TF_GROUP_KEY = tfGroupTitle;
                let tfGroupOpen = true;
                try {
                    const saved = JSON.parse(GM_getValue(KEY_SP_GROUP_OPEN, '{}'));
                    if (_TF_GROUP_KEY in saved) tfGroupOpen = saved[_TF_GROUP_KEY];
                } catch (_) {}
                if (!tfGroupOpen) {
                    tfGroupBody.classList.add('collapsed');
                    tfGroupChevron.style.transform = 'rotate(-90deg)';
                } else {
                    tfGroupChevron.style.transform = 'rotate(0deg)';
                }

                tfGroupHeader.addEventListener('click', () => {
                    const isCollapsed = tfGroupBody.classList.toggle('collapsed');
                    tfGroupChevron.style.transform = isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)';
                    try {
                        const saved = JSON.parse(GM_getValue(KEY_SP_GROUP_OPEN, '{}'));
                        saved[_TF_GROUP_KEY] = !isCollapsed;
                        GM_setValue(KEY_SP_GROUP_OPEN, JSON.stringify(saved));
                    } catch (_) {}
                    if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                });

                grpAdv.append(tfGroupHeader);
                grpAdv.append(tfGroupBody);

                const _tfItems = [
                    [T.pt_css_simplify_label || 'CSS Simplify',      T.pt_css_simplify_desc || 'Remove blur/shadow/gradient globally', KEY_TF_CSS_SIMPLIFY, 'cssSimplify', 'pt_css_simplify'],
                    [T.pt_feed_prune_label   || 'Feed Memory Limit', T.pt_feed_prune_desc   || 'Freeze off-screen tweets (>70 nodes)', KEY_TF_FEED_PRUNE,   'feedPrune',   'pt_feed_prune'],
                ];

                const _makeTfToggleRow = (label, desc, key, tfKey, featureId) => {
                    const row = document.createElement('div');
                    row.className = 'tm-tf-row';

                    const left = document.createElement('div');
                    left.className = 'tm-tf-row-left';

                    const nameEl = document.createElement('span');
                    nameEl.className = 'tm-tf-row-name';
                    nameEl.textContent = label;

                    const descEl = document.createElement('span');
                    descEl.className = 'tm-tf-row-desc';
                    descEl.textContent = desc;

                    left.appendChild(nameEl);
                    left.appendChild(descEl);
                    row.appendChild(left);

                    if (featureId && isFeatureNew(featureId)) {
                        const badge = document.createElement('span');
                        badge.className = 'tm-sp-new-badge';
                        badge.textContent = 'NEW';
                        row.appendChild(badge);
                    }

                    const right = document.createElement('div');
                    right.className = 'tm-tf-row-right';

                    const valEl = document.createElement('span');
                    const updateVal = () => {
                        const on = GM_getValue(key, false);
                        valEl.className = on ? 'tm-tf-val-on' : 'tm-tf-val-off';
                        valEl.textContent = on ? (T.status_on || 'On') : (T.status_off || 'Off');
                    };
                    updateVal();

                    const arrow = document.createElement('span');
                    arrow.className = 'tm-tf-arrow';
                    arrow.textContent = '›';

                    right.appendChild(valEl);
                    right.appendChild(arrow);
                    row.appendChild(right);

                    row.addEventListener('click', () => {
                        if (featureId) markFeatureSeen(featureId);
                        const next = !GM_getValue(key, false);
                        GM_setValue(key, next);
                        if (next) _tfRegistry[tfKey].enable();
                        else      _tfRegistry[tfKey].disable();
                        updateVal();
                        showToast(label + ' → ' + (next ? (T.status_on || 'On') : (T.status_off || 'Off')));
                    });
                    return row;
                };

                _tfItems.forEach(([label, desc, key, tfKey, featureId]) => {
                    tfGroupBody.appendChild(_makeTfToggleRow(label, desc, key, tfKey, featureId));
                });

                const _makeLegacyGridRow = () => {
                    const row = document.createElement('div');
                    row.className = 'tm-tf-row';

                    const left = document.createElement('div');
                    left.className = 'tm-tf-row-left';
                    const nameEl = document.createElement('span');
                    nameEl.className = 'tm-tf-row-name';
                    nameEl.textContent = T.sp_legacy_grid_label || 'Legacy Media Grid';
                    const descEl = document.createElement('span');
                    descEl.className = 'tm-tf-row-desc';
                    descEl.textContent = T.sp_legacy_grid_desc || 'Restore the old profile media grid layout. Requires a page reload to apply.';
                    left.appendChild(nameEl);
                    left.appendChild(descEl);
                    row.appendChild(left);

                    if (isFeatureNew('legacy_media_grid')) {
                        const badge = document.createElement('span');
                        badge.className = 'tm-sp-new-badge';
                        badge.textContent = 'NEW';
                        row.appendChild(badge);
                    }

                    const right = document.createElement('div');
                    right.className = 'tm-tf-row-right';
                    const valEl = document.createElement('span');
                    const updateVal = () => {
                        const on = GM_getValue(KEY_LEGACY_MEDIA_GRID, false);
                        valEl.className = on ? 'tm-tf-val-on' : 'tm-tf-val-off';
                        valEl.textContent = on ? (T.status_on || 'On') : (T.status_off || 'Off');
                    };
                    updateVal();
                    const arrow = document.createElement('span');
                    arrow.className = 'tm-tf-arrow';
                    arrow.textContent = '›';
                    right.appendChild(valEl);
                    right.appendChild(arrow);
                    row.appendChild(right);

                    row.addEventListener('click', () => {
                        markFeatureSeen('legacy_media_grid');
                        const next = !GM_getValue(KEY_LEGACY_MEDIA_GRID, false);
                        GM_setValue(KEY_LEGACY_MEDIA_GRID, next);
                        updateVal();
                        const stateText = next ? (T.status_on || 'On') : (T.status_off || 'Off');
                        const confirmMsg = (T.confirm_legacy_grid_reload || DEFAULT_LEGACY_GRID_CONFIRM_MSG)
                            .replace('{state}', stateText);
                        showLegacyGridConfirmModal(
                            confirmMsg,
                            () => location.reload(),
                            () => showToast(T.toast_legacy_grid_pending || 'Change saved. Reload the page to apply.')
                        );
                    });
                    return row;
                };
                tfGroupBody.appendChild(_makeLegacyGridRow());
            }

            const SVG_CHEVRONS_OUT = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 8l5-5 5 5"/><path d="M7 16l5 5 5-5"/></svg>';
            const SVG_CHEVRONS_IN  = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10l5-5 5 5"/><path d="M7 14l5 5 5-5"/></svg>';

            const _isAllGroupsOpen = () => _spGroupRegistry.every(gr => !gr.body.classList.contains('collapsed'));

            const _updateCollapseAllBtnIcon = () => {
                const allOpen = _isAllGroupsOpen();
                collapseAllBtn.innerHTML = allOpen ? SVG_CHEVRONS_IN : SVG_CHEVRONS_OUT;
                collapseAllBtn.title = allOpen
                    ? (T.sp_collapseall_collapse_tip || 'Collapse all groups')
                    : (T.sp_collapseall_expand_tip   || 'Expand all groups');
            };

            const _setAllGroupsOpen = (open) => {
                let saved = {};
                try { saved = JSON.parse(GM_getValue(KEY_SP_GROUP_OPEN, '{}')); } catch (e) { console.warn('[TM] collapseAll read openState failed:', e); }
                _spGroupRegistry.forEach(({ header: gh, body: gb, chevron: gc, label: gl }) => {
                    gb.classList.toggle('collapsed', !open);
                    gh.classList.toggle('collapsed', !open);
                    gc.style.transform = open ? 'rotate(0deg)' : 'rotate(-90deg)';
                    saved[gl] = open;
                });
                try { GM_setValue(KEY_SP_GROUP_OPEN, JSON.stringify(saved)); } catch (e) { console.warn('[TM] collapseAll write openState failed:', e); }
                if (_lcRcResizeHandler) requestAnimationFrame(_lcRcResizeHandler);
                _updateCollapseAllBtnIcon();
            };

            collapseAllBtn.addEventListener('click', e => {
                e.stopPropagation();
                _setAllGroupsOpen(!_isAllGroupsOpen());
            });
            _updateCollapseAllBtnIcon();

            const helpLabel = T.menu_help ? T.menu_help.replace(/^📖\s*/, '') : 'Help / Manual';
            const helpRow = makeRow('📖 ' + helpLabel, '', () => {
                showHelpModal();
            });
            helpRow.style.borderTop = `1px solid ${C.border}`;
            panel.appendChild(helpRow);
        }

        buildContent();

        gearBtn.addEventListener('click', e => {
            e.stopPropagation();
            const isOpen = wrapper.getAttribute('data-open') === 'true';
            wrapper.setAttribute('data-open', String(!isOpen));
            if (!isOpen) {
                gearBtn.querySelector('.tm-gear-notify-dot')?.remove();
                GM_setValue(KEY_GEAR_DOT_SEEN, NEW_FEATURE_IDS.length);
            }
        });

        document.addEventListener('click', e => {
            if (wrapper.contains(e.target)) return;
            if (e.target.closest('#tm-group-modal-overlay'))  return;
            if (e.target.closest('#tm-group-mgr-overlay'))    return;
            if (e.target.closest('.tm-sp-picker'))            return;
            if (e.target.closest('#tm-cfn-menu-el'))          return;
            if (e.target.closest('#tm-theme-palette-modal'))  return;
            wrapper.setAttribute('data-open', 'false');
        }, { signal: _spClickAC.signal });

        wrapper.appendChild(dismissBtn);
        wrapper.appendChild(histBtn);

        if (!_isTwitterDomain) {
            const SVG_REFRESH = `<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4.93"/></svg>`;
            const refreshBtn = document.createElement('button');
            refreshBtn.id    = 'tm-refresh-hist-btn';
            refreshBtn.title = 'Refresh Download History';
            refreshBtn.innerHTML = SVG_REFRESH;
            refreshBtn.style.cssText = `
                position:absolute; right:0; top:0;
                width:20px; height:20px;
                border-radius:50%; border:none;
                background:rgba(255,255,255,.12);
                color:rgba(255,255,255,.4);
                cursor:pointer; padding:0;
                display:flex; align-items:center; justify-content:center;
                opacity:0; transition:opacity .2s, color .15s;
                z-index:10;
            `;
            refreshBtn.onmouseenter = () => refreshBtn.style.color = 'rgba(255,255,255,.85)';
            refreshBtn.onmouseleave = () => refreshBtn.style.color = 'rgba(255,255,255,.4)';

            refreshBtn.addEventListener('click', e => {
                e.stopPropagation();

                refreshBtn.classList.remove('tm-refreshing');
                void refreshBtn.offsetWidth;
                refreshBtn.classList.add('tm-refreshing');
                setTimeout(() => refreshBtn.classList.remove('tm-refreshing'), 650);

                const panel = document.getElementById('tm-hist-panel');
                if (panel) {
                    panel.dispatchEvent(new CustomEvent('tm-hist-refresh'));
                    const count = _loadAllRecords().length;
                    showToast(`🔄 History refreshed · ${count} record${count !== 1 ? 's' : ''}`);
                } else {
                    showHistoryPanel();
                }
            });

            wrapper.appendChild(refreshBtn);
        }

        wrapper.appendChild(gearBtn);
        wrapper.appendChild(panel);
        document.body.appendChild(wrapper);

        if (_wasOpen) {
            wrapper.setAttribute('data-open', 'true');
            wrapper.style.transform = '';
        }
        if (_prevFocus) wrapper.setAttribute('data-focus', _prevFocus);
        } catch (e) {
            console.error('[TM] createSettingsPanel failed, settings panel unavailable this cycle:', e);
            document.getElementById('tm-settings-panel-style')?.remove();
            document.getElementById('tm-settings-wrapper')?.remove();
        }
    }

    const _downloadedIds = (() => {
        const s = new Set();
        try {
            _getHistoryIndex().slice(0, 6).forEach(ym => {
                _readMonthRecords(ym).forEach(r => { if (r.tweetId) s.add(r.tweetId); });
            });
        } catch (_) {}
        return s;
    })();

    let _historyUndoBuffer = null;
    let _historyUndoTimer  = null;

    const DockState = {
        side:         null,
        tabEl:        null,
        hoverTimer:   null,
        peeked:       false,
        retractTimer: null,
        snapshot:     null,
    };
    let _dialogOpenGlobal       = false;

    (function _restorePersistedDock() {
        const persisted = GM_getValue(KEY_DOCK_PERSISTED, '');
        if (persisted === 'left' || persisted === 'right') {
            DockState.side = persisted;
            requestAnimationFrame(() => {
                showHistoryPanel();
            });
        }
    })();

    function _getTweetIdFromArticle(article) {
        for (const lk of article.querySelectorAll('a[href*="/status/"]')) {
            const m = lk.getAttribute('href')?.match(/\/status\/(\d+)/);
            if (m) return m[1];
        }
        return null;
    }

    function _applyHistoryBadge(btn, skipAnim = false) {
        if (!btn || btn.querySelector('.tm-media-dl-dot')) return;
        const badge = document.createElement('span');
        badge.className = 'tm-media-dl-dot';
        badge.style.cssText = `
            position: absolute; top: -3px; right: -3px;
            width: 8px; height: 8px; border-radius: 50%;
            padding: 0; margin: 0; box-sizing: border-box;
            background: #00ba7c; pointer-events: none;
            box-shadow: 0 0 0 2px rgba(0,0,0,0.65);
            ${skipAnim ? '' : 'animation: tm-pop-bounce 0.35s cubic-bezier(0.175,0.885,0.32,1.275) both;'}
            z-index: 10;
        `;
        btn.appendChild(badge);
    }
    function _removeHistoryBadge(btn) {
        if (!btn) return;
        btn.querySelector('.tm-media-dl-dot')?.remove();
        btn.removeAttribute('data-tm-badge-shown');
    }

    window.addEventListener('scroll', e => {
        const fromMenuList = e.target?.nodeType === 1 && e.target.closest?.('.tm-tgm-list');
        if (!StarPipState.fanOpen) hideStarPip();
        if (StarPipState.textMenuOpen && !fromMenuList) closeTextGroupMenu();
    }, { passive: true, capture: true });
    document.addEventListener('visibilitychange', () => { if (document.hidden) { if (StarPipState.fanOpen) closeGroupFan(); if (StarPipState.textMenuOpen) closeTextGroupMenu(); hideStarPip(); } });

    let _navScanTimer1 = null;
    let _navScanTimer2 = null;
    function _scheduleNavScan() {
        if (!scanAndInsert) return;
        clearTimeout(_navScanTimer1);
        clearTimeout(_navScanTimer2);
        _navScanTimer1 = setTimeout(scanAndInsert, 50);
        _navScanTimer2 = setTimeout(scanAndInsert, 500);
    }
    window.addEventListener('popstate', _scheduleNavScan, { passive: true });

    (() => {
        const _wrap = fn => function(...args) {
            const r = fn.apply(this, args);
            try {
                hideStarPip?.();
                _scheduleNavScan();
                if (GM_getValue(KEY_HIST_PINNED, false) && !document.getElementById('tm-hist-panel')) {
                    setTimeout(() => {
                        try {
                            if (GM_getValue(KEY_HIST_PINNED, false) && !document.getElementById('tm-hist-panel')) {
                                showHistoryPanel();
                            }
                        } catch (e) { _log('TMNav', 'pinned history panel reopen failed', e); }
                    }, 300);
                }
            } catch (e) {
                _log('TMNav', 'pushState/replaceState wrapper side-effect failed', e);
            }
            return r;
        };
        history.pushState    = _wrap(history.pushState);
        history.replaceState = _wrap(history.replaceState);
    })();

    function getGroups() {
        try { return JSON.parse(GM_getValue(KEY_GROUPS, '[]')); } catch (_) { return []; }
    }

    function saveGroups(arr) {
        GM_setValue(KEY_GROUPS, JSON.stringify(arr));
    }

    function getTextGroups() {
        try { return JSON.parse(GM_getValue(KEY_TEXT_GROUPS, '[]')); } catch (_) { return []; }
    }

    function saveTextGroups(arr) {
        GM_setValue(KEY_TEXT_GROUPS, JSON.stringify(arr));
    }

    function createGroup(name, icon = '📁', glow = 'rgba(80,160,240,.5)') {
        const groups = getGroups();
        const group = {
            id:        'g_' + Date.now(),
            name:      name.slice(0, 24),
            icon,
            glow,
            createdAt: Date.now(),
        };
        groups.push(group);
        saveGroups(groups);
        return group;
    }

    function createTextGroup(name, icon = '📝', glow = 'rgba(120,200,120,.5)') {
        const groups = getTextGroups();
        const group = {
            id:        'txt_' + Date.now(),
            name:      name.slice(0, 24),
            icon,
            glow,
            createdAt: Date.now(),
        };
        groups.push(group);
        saveTextGroups(groups);
        return group;
    }

    function deleteGroup(groupId) {
        const groups = getGroups().filter(g => g.id !== groupId);
        saveGroups(groups);
        _mutateMatchingRecords(r => r.groupId === groupId, r => { delete r.groupId; });
        GM_deleteValue('app_group_unread_' + groupId);
    }

    function deleteTextGroup(groupId) {
        const groups = getTextGroups().filter(g => g.id !== groupId);
        saveTextGroups(groups);
        _mutateMatchingRecords(r => r.groupId === groupId, r => { delete r.groupId; });
        GM_deleteValue('app_group_unread_' + groupId);
    }

    function assignGroup(recordId, groupId) {
        try {
            const updated = _updateRecord(recordId, rec => {
                if (groupId === null) delete rec.groupId;
                else rec.groupId = groupId;
            });
            if (!updated) return;
            if (groupId !== null) {
                GM_setValue('app_group_unread_' + groupId, true);
            }
            const existPanel = document.getElementById('tm-hist-panel');
            if (existPanel) existPanel.dispatchEvent(new CustomEvent('tm-hist-refresh'));
        } catch (e) { console.error('[TMGroup] assignGroup error:', e); }
    }

    const STAR_AUTO_HIDE_SEC = 8;
    const STAR_FLOAT_CLS     = ['tm-float-a','tm-float-b','tm-float-c','tm-float-d','tm-float-e'];
    const STAR_GLOW_COLORS   = [
        'rgba(80,200,180,.28)', 'rgba(160,100,240,.28)', 'rgba(240,160,80,.28)',
        'rgba(240,100,100,.28)', 'rgba(80,160,240,.28)',  'rgba(200,200,80,.28)',
        'rgba(180,80,200,.28)', 'rgba(100,200,120,.28)',  'rgba(240,120,160,.28)',
        'rgba(220,160,60,.28)',
    ];

    function _renderStarPipIcon(pip) {
        if (StarPipState.pendingIsText) {
            pip.innerHTML = '<span class="tm-star-pip-glyph">📁</span>';
            return;
        }
        if (GM_getValue(KEY_GROUP_POPUP_STYLE, 'fan') === 'list') {
            pip.innerHTML = `<span class="tm-star-pip-glyph tm-star-pip-svg">${_GROUP_LIST_TRIGGER_SVG}</span>`;
            return;
        }
        pip.innerHTML = '<span class="tm-star-pip-glyph">⭐</span><span class="tm-star-pip-badge">+</span>';
    }

    function popStarPip(mediaBtnEl) {
        const pip = document.getElementById('tm-star-pip');
        if (!pip) return;

        _renderStarPipIcon(pip);
        pip.title = StarPipState.pendingIsText ? 'Group this text bookmark' : 'Click: New Group · Hover: pick group';

        if (mediaBtnEl) {
            const r = mediaBtnEl.getBoundingClientRect();
            pip.style.left = (r.right + 2) + 'px';
            pip.style.top  = (r.top   - 9) + 'px';
        }

        pip.classList.add('tm-popped');

        clearTimeout(StarPipState.autoHideTimer);
        StarPipState.autoHideTimer = setTimeout(() => {
            if (!StarPipState.fanOpen && !StarPipState.textMenuOpen) hideStarPip();
        }, STAR_AUTO_HIDE_SEC * 1000);
    }

    function hideStarPip() {
        const pip = document.getElementById('tm-star-pip');
        if (!pip) return;
        pip.classList.remove('tm-popped');
        clearTimeout(StarPipState.autoHideTimer);
        if (GM_getValue(KEY_GROUP_POPUP_STYLE, 'fan') === 'list') {
            pip.innerHTML = `<span class="tm-star-pip-glyph tm-star-pip-svg">${_GROUP_LIST_TRIGGER_SVG}</span>`;
        } else {
            pip.innerHTML = '<span class="tm-star-pip-glyph">⭐</span><span class="tm-star-pip-badge">+</span>';
        }
        pip.title = 'Click: New Group · Hover: pick group';
    }

    function _layerCfg(n) {
        if (n <= 5)  return [{c:n,  r:62}];
        if (n <= 12) return [{c:Math.min(5,n), r:60}, {c:n-Math.min(5,n), r:108}];
        const l1=5, l2=Math.min(7,n-5);
        return [{c:l1,r:60},{c:l2,r:108},{c:n-l1-l2,r:155}];
    }

    function _fanPositions(n, cx, cy) {
        const pos = [];
        const spanDeg = n > 10 ? 80 : 70;
        _layerCfg(n).forEach(({c, r}) => {
            const startDeg = -spanDeg, endDeg = spanDeg;
            const range = endDeg - startDeg;
            for (let i = 0; i < c; i++) {
                const deg = c === 1 ? 0 : startDeg + range * i / (c - 1);
                const rad = deg * Math.PI / 180;
                const horizBoost = Math.max(0, 1 - Math.abs(deg) / 30) * 14;
                const rr = r + horizBoost;
                pos.push({ x: cx + rr * Math.cos(rad) - 16, y: cy + rr * Math.sin(rad) - 16 });
            }
        });
        return pos;
    }

    function _spawnRipple(cx, cy) {
        const r = document.createElement('div');
        r.className = 'tm-ripple-ring';
        const sz = 18;
        r.style.cssText = `width:${sz}px;height:${sz}px;left:${cx}px;top:${cy}px`;
        document.body.appendChild(r);
        setTimeout(() => r.remove(), 560);
    }

    function _getStarPipPos() {
        const pip = document.getElementById('tm-star-pip');
        if (!pip) return { cx: 0, cy: 0 };
        const r = pip.getBoundingClientRect();
        return { cx: r.left + r.width / 2, cy: r.top + r.height / 2 };
    }

    function _buildFanDom() {
        StarPipState.fanNodes.forEach(n => n.remove());
        StarPipState.fanNodes = [];

        const _cfg      = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
        const _glowClr  = _cfg.glowColor || 'multi';
        const _glowSz   = Number(_cfg.glowSize  ?? 12);
        const _txtClrMap = { white: 'rgba(255,255,255,.7)', yellow: 'rgba(255,220,60,.85)', blue: 'rgba(29,155,240,.9)', gray: 'rgba(180,180,180,.65)' };
        const _isSafeColor = v => typeof v === 'string' && (
            /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3}([0-9a-fA-F]{2})?)?$/.test(v.trim()) ||
            /^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*[\d.]+\s*)?\)$/.test(v.trim())
        );
        const _txtClr   = _cfg.textColor
            ? (_txtClrMap[_cfg.textColor] || (_isSafeColor(_cfg.textColor) ? _cfg.textColor : _txtClrMap.white))
            : _txtClrMap.white;
        const _glowPx   = Math.max(4, Math.min(60, _glowSz));
        const _labelPx  = Math.max(7, Math.min(16, Number(_cfg.labelSize ?? 9)));
        const _iconPx   = Math.max(14, Math.min(28, Number(_cfg.iconSize ?? 18)));

        const groups = StarPipState.pendingIsText ? getTextGroups() : getGroups();
        groups.forEach((g, i) => {
            const _rawGlow = _glowClr === 'multi'
                ? (g.glow || STAR_GLOW_COLORS[i % STAR_GLOW_COLORS.length])
                : _glowClr;
            const _safeGlow = _isSafeColor(_rawGlow) ? _rawGlow : STAR_GLOW_COLORS[i % STAR_GLOW_COLORS.length];
            const baseGlow = _glowClr === 'multi' ? _safeGlow : (_safeGlow + '85');
            const half = Math.round(_glowPx / 2);

            const ic       = _resolveGroupIcon(g.icon);
            const iconHtml = `<div style="width:${_iconPx}px;height:${_iconPx}px;color:${ic.color};flex-shrink:0">${ic.svg}</div>`;

            const el = document.createElement('div');
            el.className = 'tm-fan-node ' + STAR_FLOAT_CLS[i % STAR_FLOAT_CLS.length];
            el.style.cssText = 'left:-300px;top:-300px;opacity:0';
            el.innerHTML = `
                <div class="tm-fan-node-inner">
                    <div class="tm-fan-glow" style="background:radial-gradient(circle,${baseGlow} 0%,transparent 68%);inset:-${half}px"></div>
                    ${iconHtml}
                </div>
                <span class="tm-fan-label" style="color:${_txtClr};font-size:${_labelPx}px">${_escHtml(g.name)}</span>`;
            el.addEventListener('click', () => _onFanGroupClick(g.id, g.name));
            document.body.appendChild(el);
            StarPipState.fanNodes.push(el);
        });
    }

    function _runStarEscapeAnim(callback) {
        const pip = document.getElementById('tm-star-pip');
        if (!pip || StarPipState.escaping) { if (!pip) callback?.(); return; }
        StarPipState.escaping = true;

        clearTimeout(StarPipState.autoHideTimer);

        pip.classList.add('tm-escaping');

        const directions = [
            { dx:  220, dy: -180, rot:  340 },
            { dx:  180, dy:  200, rot: -320 },
            { dx:  260, dy:  -60, rot:  380 },
            { dx:  200, dy:  140, rot: -360 },
        ];
        const dir = directions[Math.floor(Math.random() * directions.length)];

        pip.style.transition = 'transform .12s cubic-bezier(.4,0,.2,1)';
        pip.style.transform  = 'scale(0.7) translate(-6px, 2px)';

        setTimeout(() => {
            pip.style.transition = 'transform .42s cubic-bezier(.2,0,.8,1), opacity .38s ease .08s';
            pip.style.transform  = `translate(${dir.dx}px, ${dir.dy}px) rotate(${dir.rot}deg) scale(0.15)`;
            pip.style.opacity    = '0';
        }, 130);

        setTimeout(() => {
            pip.style.transition = 'none';
            pip.style.transform  = '';
            pip.style.opacity    = '';
            pip.classList.remove('tm-popped', 'tm-escaping');
            StarPipState.escaping = false;
            callback?.();
        }, 600);
    }

    function _showFanMask(cx, cy) {
        document.getElementById('tm-fan-mask')?.remove();

        const _cfg = (() => { try { return JSON.parse(GM_getValue(KEY_GROUP_PANEL_CFG, '{}')); } catch(_) { return {}; } })();
        const _maskEnabled  = _cfg.fanMaskEnabled !== false;
        if (!_maskEnabled) return;

        const _maskColor    = _cfg.fanMaskColor   || '#000000';
        const _maskAlpha    = Number(_cfg.fanMaskAlpha   ?? 0.45);
        const _spanDeg      = Number(_cfg.fanMaskSpanDeg ?? 160);
        const _radius       = Number(_cfg.fanMaskRadius  ?? 180);

        const _isSafeColor = v => typeof v === 'string' && /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(v.trim());
        const safeColor  = _isSafeColor(_maskColor) ? _maskColor : '#000000';
        const safeAlpha  = Math.max(0, Math.min(1, _maskAlpha));
        const safeSpan   = Math.max(10, Math.min(360, _spanDeg));
        const safeRadius = Math.max(40, Math.min(400, _radius));

        const halfSpan = safeSpan / 2;
        const startRad = (-halfSpan) * Math.PI / 180;
        const endRad   =   halfSpan  * Math.PI / 180;
        const x1 = cx + safeRadius * Math.cos(startRad);
        const y1 = cy + safeRadius * Math.sin(startRad);
        const x2 = cx + safeRadius * Math.cos(endRad);
        const y2 = cy + safeRadius * Math.sin(endRad);
        const largeArc = safeSpan >= 180 ? 1 : 0;

        const svgNS = 'http://www.w3.org/2000/svg';
        const svg = document.createElementNS(svgNS, 'svg');
        svg.id = 'tm-fan-mask';
        svg.setAttribute('width',  String(window.innerWidth));
        svg.setAttribute('height', String(window.innerHeight));
        svg.style.cssText = `position:fixed;top:0;left:0;pointer-events:none;z-index:99992;opacity:0;transition:opacity .22s ease;`;

        const path = document.createElementNS(svgNS, 'path');
        path.setAttribute('d', `M ${cx} ${cy} L ${x1} ${y1} A ${safeRadius} ${safeRadius} 0 ${largeArc} 1 ${x2} ${y2} Z`);
        path.setAttribute('fill', safeColor);
        path.setAttribute('opacity', String(safeAlpha));

        svg.appendChild(path);
        document.body.appendChild(svg);

        requestAnimationFrame(() => { svg.style.opacity = '1'; });
    }

    function _hideFanMask() {
        const mask = document.getElementById('tm-fan-mask');
        if (mask) mask.classList.add('tm-fan-mask-hidden');
    }

    function openGroupFan() {
        if (StarPipState.pendingIsText) { openTextGroupMenu(); return; }
        const _activeGroups = getGroups();
        if (!_activeGroups.length) {
            _runStarEscapeAnim(() => showGroupCreateModal());
            return;
        }

        StarPipState.fanOpen = true;
        _buildFanDom();
        const { cx, cy } = _getStarPipPos();
        const groupNodeCount = StarPipState.fanNodes.length;
        const positions = _fanPositions(groupNodeCount, cx, cy);
        const pip = document.getElementById('tm-star-pip');
        if (pip) pip.classList.add('tm-lit');

        _showFanMask(cx, cy);

        _spawnRipple(cx, cy);
        setTimeout(() => _spawnRipple(cx, cy), 110);

        StarPipState.fanNodes.forEach((node, i) => {
            const pos = positions[i];
            node.style.cssText = `left:${cx-16}px;top:${cy-16}px;opacity:0;transition:none`;
            node.classList.remove('tm-spawned');
            const delay = 20 + i * 46;
            setTimeout(() => {
                node.style.transition = `opacity .26s ease ${delay}ms, left .42s cubic-bezier(.34,1.56,.64,1) ${delay}ms, top .42s cubic-bezier(.34,1.56,.64,1) ${delay}ms`;
                node.style.left    = pos.x + 'px';
                node.style.top     = pos.y + 'px';
                node.style.opacity = '1';
                setTimeout(() => node.classList.add('tm-spawned'), delay + 380);
            }, 10);
        });
    }

    function closeGroupFan() {
        StarPipState.fanOpen = false;
        const { cx, cy } = _getStarPipPos();
        const pip = document.getElementById('tm-star-pip');
        if (pip) pip.classList.remove('tm-lit');
        _hideFanMask();
        StarPipState.fanNodes.forEach(node => {
            node.classList.remove('tm-spawned');
            node.style.transition = 'opacity .15s ease, left .22s cubic-bezier(.6,0,.2,1), top .22s cubic-bezier(.6,0,.2,1)';
            node.style.left    = (cx - 16) + 'px';
            node.style.top     = (cy - 16) + 'px';
            node.style.opacity = '0';
        });
        setTimeout(() => {
            StarPipState.fanNodes.forEach(n => n.remove());
            StarPipState.fanNodes = [];
            document.getElementById('tm-fan-mask')?.remove();
        }, 300);
    }

    function _onFanGroupClick(groupId, groupName) {
        assignGroup(StarPipState.pendingGroupRecordId, groupId);
        StarPipState.pendingGroupRecordId = null;
        StarPipState.pendingIsText        = false;
        showToast(`⭐ → ${groupName}`);
        closeGroupFan();
        _runStarEscapeAnim(() => hideStarPip());
    }

    function openTextGroupMenu(anchorEl, renderFn) {
        let menu = document.getElementById('tm-text-group-menu');
        if (!menu) {
            menu = document.createElement('div');
            menu.id = 'tm-text-group-menu';
            menu.setAttribute('role', 'menu');
            document.body.appendChild(menu);

            menu.addEventListener('mouseleave', () => {
                setTimeout(() => {
                    const pip = document.getElementById('tm-star-pip');
                    if (!pip?.matches(':hover') && !StarPipState.textMenuAnchorEl?.matches(':hover') && !menu.matches(':hover')) {
                        closeTextGroupMenu();
                    }
                }, 160);
            });
        }

        StarPipState.textMenuAnchorEl = anchorEl || document.getElementById('tm-star-pip');

        (renderFn || _renderTextGroupMenu)(menu);

        _positionTextGroupMenu(menu);

        StarPipState.textMenuOpen = true;
        requestAnimationFrame(() => menu.classList.add('tm-tgm-open'));
    }

    function openMediaListMenu(anchorEl) {
        openTextGroupMenu(anchorEl, _renderMediaListMenu);
    }

    function closeTextGroupMenu() {
        const menu = document.getElementById('tm-text-group-menu');
        if (!menu) return;
        StarPipState.textMenuOpen = false;
        StarPipState.textMenuAnchorEl = null;
        menu.classList.remove('tm-tgm-open');
    }

    function _positionTextGroupMenu(menu) {
        const anchor = StarPipState.textMenuAnchorEl || document.getElementById('tm-star-pip');
        if (!anchor) return;
        const r   = anchor.getBoundingClientRect();
        menu.style.top   = '';
        menu.style.left  = '';
        menu.style.visibility = 'hidden';
        menu.style.display    = 'block';
        const mh = menu.offsetHeight || 200;
        menu.style.visibility = '';
        menu.style.display    = '';

        const mw    = Math.max(menu.offsetWidth || 168, 168);
        const right = r.left - 1;
        const left  = Math.max(4, right - mw);
        let top = r.top + r.height / 2 - mh / 2 - 30;
        top = Math.max(8, Math.min(top, window.innerHeight - mh - 8));

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

    function _renderGroupMenu(menu, groups, toastPrefix, saveFn = saveGroups) {
        menu.innerHTML = '';

        const header = document.createElement('div');
        header.className = 'tm-tgm-header';
        header.textContent = 'Select Group';
        menu.appendChild(header);

        const list = document.createElement('div');
        list.className = 'tm-tgm-list';
        const ITEM_H = 32;
        const visibleCount = Math.min(groups.length || 1, 15);
        list.style.maxHeight = (visibleCount * ITEM_H) + 'px';
        list.addEventListener('wheel', e => {
            e.preventDefault();
            e.stopPropagation();
            const max = list.scrollHeight - list.clientHeight;
            list.scrollTop = Math.max(0, Math.min(max, list.scrollTop + e.deltaY));
        }, { passive: false });

        let _dragSrcIdx = -1;

        if (groups.length) {
            groups.forEach((g, gIdx) => {
                const wrapper = document.createElement('div');
                wrapper.className = 'tm-tgm-item-wrap';
                wrapper.draggable = false;
                wrapper.dataset.groupIdx = gIdx;

                wrapper.addEventListener('dragstart', e => {
                    _dragSrcIdx = gIdx;
                    wrapper.style.opacity = '0.45';
                    e.dataTransfer.effectAllowed = 'move';
                });
                wrapper.addEventListener('dragend', () => {
                    wrapper.style.opacity = '1';
                    list.querySelectorAll('.tm-tgm-item-wrap').forEach(el => {
                        el.style.borderTop = ''; el.style.borderBottom = '';
                    });
                });
                wrapper.addEventListener('dragover', e => {
                    e.preventDefault();
                    e.dataTransfer.dropEffect = 'move';
                    const rect = wrapper.getBoundingClientRect();
                    const mid  = rect.top + rect.height / 2;
                    list.querySelectorAll('.tm-tgm-item-wrap').forEach(el => {
                        el.style.borderTop = ''; el.style.borderBottom = '';
                    });
                    if (e.clientY < mid) wrapper.style.borderTop    = '2px solid rgba(29,155,240,.7)';
                    else                 wrapper.style.borderBottom = '2px solid rgba(29,155,240,.7)';
                });
                wrapper.addEventListener('dragleave', () => {
                    wrapper.style.borderTop = ''; wrapper.style.borderBottom = '';
                });
                wrapper.addEventListener('drop', e => {
                    e.preventDefault();
                    wrapper.style.borderTop = ''; wrapper.style.borderBottom = '';
                    const destIdx = parseInt(wrapper.dataset.groupIdx);
                    if (_dragSrcIdx === -1 || _dragSrcIdx === destIdx) return;
                    const rect = wrapper.getBoundingClientRect();
                    const insertAfter = e.clientY >= rect.top + rect.height / 2;
                    const arr = groups.slice();
                    const [moved] = arr.splice(_dragSrcIdx, 1);
                    const newDest = insertAfter
                        ? (_dragSrcIdx < destIdx ? destIdx : destIdx + 1)
                        : (_dragSrcIdx < destIdx ? destIdx - 1 : destIdx);
                    arr.splice(Math.max(0, Math.min(arr.length, newDest)), 0, moved);
                    (saveFn)(arr);
                    _dragSrcIdx = -1;
                    _renderGroupMenu(menu, arr, toastPrefix, saveFn);
                });

                const dragHandle = document.createElement('span');
                dragHandle.className = 'tm-tgm-drag-handle';
                dragHandle.title = 'Drag to reorder';
                dragHandle.innerHTML = '<svg viewBox="0 0 10 16" width="8" height="13" fill="currentColor"><circle cx="3" cy="3" r="1.2"/><circle cx="7" cy="3" r="1.2"/><circle cx="3" cy="8" r="1.2"/><circle cx="7" cy="8" r="1.2"/><circle cx="3" cy="13" r="1.2"/><circle cx="7" cy="13" r="1.2"/></svg>';
                dragHandle.addEventListener('mousedown', () => { wrapper.draggable = true; });
                wrapper.addEventListener('mousedown', e => {
                    if (!e.target.closest('.tm-tgm-drag-handle')) wrapper.draggable = false;
                });

                const btn = document.createElement('button');
                btn.type      = 'button';
                btn.className = 'tm-tgm-item';
                btn.setAttribute('role', 'menuitem');

                const ic     = _resolveGroupIcon(g.icon);
                const iconEl = document.createElement('span');
                iconEl.className   = 'tm-tgm-item-icon';
                iconEl.style.color = ic.color;
                iconEl.innerHTML   = ic.svg;

                const label = document.createElement('span');
                label.textContent = g.name;
                label.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1';

                btn.appendChild(iconEl);
                btn.appendChild(label);
                wrapper.appendChild(dragHandle);
                wrapper.appendChild(btn);
                list.appendChild(wrapper);

                btn.addEventListener('click', () => {
                    assignGroup(StarPipState.pendingGroupRecordId, g.id);
                    StarPipState.pendingGroupRecordId = null;
                    StarPipState.pendingIsText        = false;
                    showToast(`${toastPrefix} → ${g.name}`);
                    closeTextGroupMenu();
                    _runStarEscapeAnim(() => hideStarPip());
                });
            });
        } else {
            const empty = document.createElement('div');
            empty.style.cssText = 'padding:10px 13px 6px;font-size:11.5px;color:rgba(255,255,255,.32);font-style:italic';
            empty.textContent = 'No groups yet';
            list.appendChild(empty);
        }

        menu.appendChild(list);

        const div = document.createElement('div');
        div.className = 'tm-tgm-divider';
        menu.appendChild(div);

        const newBtn = document.createElement('button');
        newBtn.type      = 'button';
        newBtn.className = 'tm-tgm-new';
        newBtn.setAttribute('role', 'menuitem');
        newBtn.innerHTML = '<span style="font-size:14px;line-height:1;opacity:.7">+</span><span>New Group</span>';
        newBtn.addEventListener('click', () => {
            closeTextGroupMenu();
            _runStarEscapeAnim(() => showGroupCreateModal());
        });
        menu.appendChild(newBtn);
    }

    function _renderTextGroupMenu(menu) {
        _renderGroupMenu(menu, getTextGroups(), '📁', saveTextGroups);
    }

    function _renderMediaListMenu(menu) {
        _renderGroupMenu(menu, getGroups(), '🗂', saveGroups);
    }

    document.addEventListener('click', e => {
        if (StarPipState.textMenuOpen &&
            !e.target.closest('#tm-text-group-menu') &&
            !e.target.closest('#tm-star-pip') &&
            !(StarPipState.textMenuAnchorEl && StarPipState.textMenuAnchorEl.contains?.(e.target))) {
            closeTextGroupMenu();
        }
        if (!StarPipState.fanOpen) return;
        if (e.target.closest('.tm-fan-node') ||
            e.target.closest('#tm-star-pip')  ||
            e.target.closest('.tm-group-modal-overlay')) return;
        closeGroupFan();
    }, true);

    const _GROUP_SVG_ICONS = [
        { div: '📷  Media & Creation' },
        { id:'photo',     label:'Photo',     color:'#ffb74d', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="2" y="6" width="20" height="15" rx="2"/><circle cx="12" cy="13.5" r="3"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/></svg>' },
        { id:'video',     label:'Video',     color:'#ff8a65', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="2" y="4" width="15" height="16" rx="2"/><path d="M17 8l5-3v14l-5-3V8z"/></svg>' },
        { id:'music',     label:'Music',     color:'#f48fb1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>' },
        { id:'art',       label:'Art',       color:'#ce93d8', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><path d="M8 12a4 4 0 0 1 4-4 4 4 0 0 1 4 4"/><circle cx="8.5" cy="9" r=".8" fill="currentColor"/><circle cx="15.5" cy="9" r=".8" fill="currentColor"/></svg>' },
        { id:'palette',   label:'Palette',   color:'#f06292', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 2C6.48 2 2 6.48 2 12c0 5.52 4.48 10 10 10 1.1 0 2-.9 2-2v-.5c0-.55-.22-1.05-.59-1.41-.36-.36-.59-.86-.59-1.41 0-1.1.9-2 2-2h2c3.31 0 6-2.69 6-6 0-4.97-4.48-8.58-9-8.59z"/><circle cx="6.5" cy="11.5" r="1.5" fill="currentColor" stroke="none"/><circle cx="9.5" cy="7.5" r="1.5" fill="currentColor" stroke="none"/><circle cx="14.5" cy="7.5" r="1.5" fill="currentColor" stroke="none"/><circle cx="17.5" cy="11.5" r="1.5" fill="currentColor" stroke="none"/></svg>' },
        { id:'film',      label:'Film',      color:'#ef9a9a', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="2" y="4" width="20" height="16" rx="2"/><line x1="7" y1="4" x2="7" y2="20"/><line x1="17" y1="4" x2="17" y2="20"/><line x1="2" y1="9" x2="7" y2="9"/><line x1="17" y1="9" x2="22" y2="9"/><line x1="2" y1="15" x2="7" y2="15"/><line x1="17" y1="15" x2="22" y2="15"/></svg>' },
        { id:'mic',       label:'Mic',       color:'#b39ddb', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="9" y="2" width="6" height="11" rx="3"/><path d="M19 10a7 7 0 0 1-14 0"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="8" y1="22" x2="16" y2="22"/></svg>' },
        { id:'headphone', label:'Audio',     color:'#90caf9', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M3 18v-6a9 9 0 0 1 18 0v6"/><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"/></svg>' },
        { id:'podcast',   label:'Podcast',   color:'#4dd0e1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="9" width="4" height="6"/><rect x="10" y="5" width="4" height="14"/><rect x="17" y="12" width="4" height="3"/><path d="M1 21h22"/></svg>' },
        { id:'design',    label:'Design',    color:'#f06292', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z"/></svg>' },
        { div: '🎮  Entertainment' },
        { id:'game',      label:'Game',      color:'#80cbc4', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="2" y="7" width="20" height="14" rx="3"/><path d="M7 11v4M5 13h4"/><circle cx="16.5" cy="12" r=".8" fill="currentColor"/><circle cx="18.5" cy="14" r=".8" fill="currentColor"/></svg>' },
        { id:'book',      label:'Book',      color:'#ffcc80', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>' },
        { id:'star',      label:'Fav',       color:'#fff176', svg:'<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>' },
        { id:'magic',     label:'Magic',     color:'#b39ddb', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M15 4V2M15 16v-2M8 9h2M20 9h2M17.8 11.8L19 13M17.8 6.2L19 5M12.2 6.2L11 5M12.2 11.8L11 13"/><path d="M3 21l9-9"/><circle cx="15" cy="9" r="3"/></svg>' },
        { id:'puzzle',    label:'Puzzle',    color:'#80deea', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M19 10h-1a2 2 0 1 1 0-4h1V4H4v6h1a2 2 0 1 1 0 4H4v6h15v-3a2 2 0 1 1 4 0v-7h-4z"/></svg>' },
        { id:'coffee',    label:'Coffee',    color:'#bcaaa4', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"/><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"/><line x1="6" y1="2" x2="6" y2="4"/><line x1="10" y1="2" x2="10" y2="4"/><line x1="14" y1="2" x2="14" y2="4"/></svg>' },
        { id:'moon',      label:'Night',     color:'#90caf9', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>' },
        { id:'gift',      label:'Gift',      color:'#f48fb1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><polyline points="20 12 20 22 4 22 4 12"/><rect x="2" y="7" width="20" height="5"/><line x1="12" y1="22" x2="12" y2="7"/><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"/><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"/></svg>' },
        { id:'dice',      label:'Dice',      color:'#80deea', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8" rx="1.5"/><rect x="13" y="13" width="8" height="8" rx="1.5"/><circle cx="6" cy="6" r=".8" fill="currentColor" stroke="none"/><circle cx="8" cy="8" r=".8" fill="currentColor" stroke="none"/><circle cx="16" cy="16" r=".8" fill="currentColor" stroke="none"/><circle cx="18" cy="18" r=".8" fill="currentColor" stroke="none"/><circle cx="16" cy="20" r=".8" fill="currentColor" stroke="none"/></svg>' },
        { id:'ticket',    label:'Ticket',    color:'#ffd54f', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10a2 2 0 0 0 0 4v3a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-3a2 2 0 0 1 0-4V7a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1z"/><line x1="10" y1="6" x2="10" y2="18" stroke-dasharray="2 2"/></svg>' },
        { div: '🌍  Travel & Lifestyle' },
        { id:'travel',    label:'Travel',    color:'#4dd0e1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="10" r="3"/><path d="M12 2a8 8 0 0 1 8 8c0 5.5-8 13-8 13S4 15.5 4 10a8 8 0 0 1 8-8z"/></svg>' },
        { id:'plane',     label:'Plane',     color:'#4dd0e1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16l-9-9-9 9"/><path d="M3 8l4.5 4.5L12 8l4.5 4.5L21 8"/><line x1="12" y1="3" x2="12" y2="8"/></svg>' },
        { id:'globe',     label:'Globe',     color:'#80deea', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>' },
        { id:'map',       label:'Map',       color:'#a5d6a7', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>' },
        { id:'food',      label:'Food',      color:'#a5d6a7', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"/><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"/><line x1="6" y1="2" x2="6" y2="8"/><line x1="10" y1="2" x2="10" y2="8"/><line x1="14" y1="2" x2="14" y2="8"/></svg>' },
        { id:'fashion',   label:'Fashion',   color:'#f8bbd0', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M20.38 3.46L16 2a4 4 0 0 1-8 0L3.62 3.46a2 2 0 0 0-1.34 2.23l.58 3.57a1 1 0 0 0 .99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2V10h2.15a1 1 0 0 0 .99-.84l.58-3.57a2 2 0 0 0-1.34-2.23z"/></svg>' },
        { id:'home',      label:'Home',      color:'#ffb74d', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>' },
        { id:'mountain',  label:'Mountain',  color:'#a5d6a7', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 21l4-13 4 13"/><path d="M3 21l6.5-16 2 5"/><path d="M22 21H2"/></svg>' },
        { id:'tent',      label:'Camping',   color:'#ffb74d', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l7 5v13H5V8z"/><path d="M12 3v9l5 3"/></svg>' },
        { id:'car',       label:'Car',       color:'#90caf9', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 13l2-6a2 2 0 0 1 2-1h10a2 2 0 0 1 2 1l2 6"/><rect x="2" y="13" width="20" height="6" rx="1.5"/><circle cx="7" cy="19" r="1.5"/><circle cx="17" cy="19" r="1.5"/></svg>' },
        { div: '💪  Sport & Health' },
        { id:'sport',     label:'Sport',     color:'#ef9a9a', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><path d="M4.93 4.93c2.34 2.34 3.07 5.71 2.07 8.71M19.07 4.93c-2.34 2.34-3.07 5.71-2.07 8.71M4.93 19.07c2.34-2.34 5.71-3.07 8.71-2.07M19.07 19.07c-2.34-2.34-5.71-3.07-8.71-2.07"/></svg>' },
        { id:'run',       label:'Run',       color:'#ffb74d', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="13" cy="4" r="2"/><path d="M7 21l2-6 3 3 3-8 3 3"/><path d="M5 12l2-3 4 1 2-4"/></svg>' },
        { id:'gym',       label:'Gym',       color:'#ef9a9a', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="6.5" y1="12" x2="17.5" y2="12"/><rect x="2" y="9" width="4.5" height="6" rx="1.5"/><rect x="17.5" y="9" width="4.5" height="6" rx="1.5"/><line x1="6.5" y1="10.5" x2="6.5" y2="13.5"/><line x1="17.5" y1="10.5" x2="17.5" y2="13.5"/></svg>' },
        { id:'bike',      label:'Bike',      color:'#a5d6a7', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="5.5" cy="17.5" r="3.5"/><circle cx="18.5" cy="17.5" r="3.5"/><path d="M8.5 17.5l3-8h5.5"/><path d="M15 9.5l3 8"/><circle cx="15" cy="5.5" r="1.5"/></svg>' },
        { id:'health',    label:'Health',    color:'#ef9a9a', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>' },
        { id:'pet',       label:'Pet',       color:'#ffab91', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="14" r="6"/><circle cx="7" cy="6" r="2"/><circle cx="17" cy="6" r="2"/><circle cx="4" cy="11" r="1.5"/><circle cx="20" cy="11" r="1.5"/></svg>' },
        { id:'swim',      label:'Swim',      color:'#4dd0e1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 17c1.5-1.5 3-1.5 4.5 0s3 1.5 4.5 0 3-1.5 4.5 0 3 1.5 4.5 0"/><path d="M2 21c1.5-1.5 3-1.5 4.5 0s3 1.5 4.5 0 3-1.5 4.5 0 3 1.5 4.5 0"/><circle cx="17" cy="6" r="2.5"/><path d="M13.5 14L8 9 4 12"/></svg>' },
        { id:'basketball', label:'Basketball', color:'#ff8a65', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 3v18M3 12h18"/><path d="M5.5 5.5c2 2 4 2.5 6.5 2.5s4.5-.5 6.5-2.5M5.5 18.5c2-2 4-2.5 6.5-2.5s4.5.5 6.5 2.5"/></svg>' },
        { div: '💼  Work & Knowledge' },
        { id:'work',      label:'Work',      color:'#b0bec5', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/><line x1="12" y1="12" x2="12" y2="12.01"/></svg>' },
        { id:'tech',      label:'Tech',      color:'#90caf9', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>' },
        { id:'brain',     label:'Brain',     color:'#ce93d8', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3a3 3 0 0 1 6 0"/><path d="M12 3v3"/><path d="M6.6 5A5 5 0 0 0 4 9.5c0 1.8.8 3.4 2 4.5v4a1 1 0 0 0 1 1h2v-3h2v3h2a1 1 0 0 0 1-1v-4a6 6 0 0 0 2-4.5A5 5 0 0 0 17.4 5"/><path d="M9 18h6"/></svg>' },
        { id:'news',      label:'News',      color:'#b0bec5', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 0-2 2zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/><line x1="18" y1="2" x2="18" y2="22"/><line x1="8" y1="10" x2="14" y2="10"/><line x1="8" y1="14" x2="14" y2="14"/></svg>' },
        { id:'finance',   label:'Finance',   color:'#a5d6a7', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="12" y1="2" x2="12" y2="22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>' },
        { id:'clock',     label:'Clock',     color:'#b0bec5', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>' },
        { id:'chat',      label:'Chat',      color:'#80cbc4', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>' },
        { id:'lock',      label:'Private',   color:'#ef9a9a', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>' },
        { id:'calendar',  label:'Calendar',  color:'#b0bec5', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="16" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="16" y1="2" x2="16" y2="6"/><circle cx="8" cy="14" r="1" fill="currentColor" stroke="none"/><circle cx="12" cy="14" r="1" fill="currentColor" stroke="none"/><circle cx="16" cy="14" r="1" fill="currentColor" stroke="none"/></svg>' },
        { id:'folder',    label:'Folder',    color:'#ffcc80', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>' },
        { div: '✨  Special' },
        { id:'nature',    label:'Nature',    color:'#c5e1a5', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 22V12"/><path d="M5 12c0-4 3-7 7-7s7 3 7 7c0 2.5-1.5 4.5-4 5.5"/><path d="M3 18c0-2.5 2-4 4.5-4C9 14 10 15.5 12 16"/></svg>' },
        { id:'flower',    label:'Flower',    color:'#f48fb1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><path d="M12 2a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0 14a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm10-7a3 3 0 0 1 0 6 3 3 0 0 1-3-3 3 3 0 0 1 3-3zM2 12a3 3 0 0 1 3-3 3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3zm12.24-5.76a3 3 0 0 1 4.24 0 3 3 0 0 1 0 4.24 3 3 0 0 1-4.24 0 3 3 0 0 1 0-4.24zm-8.48 0a3 3 0 0 1 4.24 0 3 3 0 0 1 0 4.24 3 3 0 0 1-4.24 0 3 3 0 0 1 0-4.24zm0 8.48a3 3 0 0 1 4.24 0 3 3 0 0 1 0 4.24 3 3 0 0 1-4.24 0 3 3 0 0 1 0-4.24zm8.48 0a3 3 0 0 1 4.24 0 3 3 0 0 1 0 4.24 3 3 0 0 1-4.24 0 3 3 0 0 1 0-4.24z"/></svg>' },
        { id:'fire',      label:'Fire',      color:'#ff7043', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2c0 0-4 4-4 9a4 4 0 0 0 8 0c0-2-1-4-1-4s-1 2-3 2c-1 0-2-1-2-2 0-2 2-5 2-5z"/><path d="M12 22c-3.31 0-6-2.69-6-6 0-2.5 1.5-4.5 1.5-4.5S9 13 12 13s4.5-1.5 4.5-1.5S18 13.5 18 16c0 3.31-2.69 6-6 6z"/></svg>' },
        { id:'sun',       label:'Sun',       color:'#ffd54f', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>' },
        { id:'award',     label:'Award',     color:'#ffd54f', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="8" r="6"/><path d="M15.477 12.89L17 22l-5-3-5 3 1.523-9.11"/></svg>' },
        { id:'bell',      label:'Alert',     color:'#ffcc80', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>' },
        { id:'diamond',   label:'VIP',       color:'#4dd0e1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3h12l4 6-10 13L2 9z"/><line x1="2" y1="9" x2="22" y2="9"/><line x1="12" y1="3" x2="2" y2="9"/><line x1="12" y1="3" x2="22" y2="9"/><line x1="12" y1="9" x2="12" y2="22"/></svg>' },
        { id:'heart',     label:'Heart',     color:'#f48fb1', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1-1.1a5.5 5.5 0 1 0-7.8 7.8L12 21.2l8.8-8.8a5.5 5.5 0 0 0 0-7.8z"/></svg>' },
        { id:'rocket',    label:'Rocket',    color:'#ce93d8', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2c3 2 5 6 5 10 0 2-1 4-1 4l-4 3-4-3s-1-2-1-4c0-4 2-8 5-10z"/><circle cx="12" cy="9" r="1.6"/><path d="M8 15l-3 2 1-4M16 15l3 2-1-4"/></svg>' },
        { id:'crown',     label:'Crown',     color:'#ffd700', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8l4 3 5-7 5 7 4-3-2 11H5z"/><line x1="5" y1="21" x2="19" y2="21"/></svg>' },
    ];
    const _GROUP_ICON_FLAT = _GROUP_SVG_ICONS.filter(x => !x.div);

    function _resolveGroupIcon(iconId) {
        const ic = _GROUP_ICON_FLAT.find(x => x.id === iconId);
        if (ic) return { type: 'svg', color: ic.color, svg: ic.svg, label: ic.label };
        const fb = _GROUP_ICON_FLAT[0];
        return { type: 'svg', color: fb.color, svg: fb.svg, label: fb.label };
    }

    function showGroupCreateModal() {
        const old = document.getElementById('tm-group-modal-overlay');
        if (old) old.remove();
        StarPipState.selectedIconId = null;

        const overlay = document.createElement('div');
        overlay.id        = 'tm-group-modal-overlay';
        overlay.className = 'tm-group-modal-overlay';

        const box = document.createElement('div');
        box.className = 'tm-group-modal-box';

        const title = document.createElement('div');
        title.className   = 'tm-group-modal-title';
        title.textContent = 'New Group';

        const input = document.createElement('input');
        input.className   = 'tm-group-modal-input';
        input.placeholder = 'Group name…';
        input.maxLength   = 24;

        const iconLabel = document.createElement('div');
        iconLabel.style.cssText = 'font-size:9px;color:rgba(255,255,255,.35);margin:10px 0 5px;letter-spacing:.07em;text-transform:uppercase';
        iconLabel.textContent = 'Icon';

        const iconGrid = document.createElement('div');
        iconGrid.style.cssText = 'display:grid;grid-template-columns:repeat(6,1fr);gap:6px;max-height:380px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;padding-right:2px';

        _GROUP_SVG_ICONS.forEach(ic => {
            if (ic.div) {
                const divEl = document.createElement('div');
                divEl.style.cssText = `
                    grid-column:1/-1;font-size:9px;color:rgba(255,255,255,.32);
                    letter-spacing:.06em;text-transform:uppercase;
                    padding:7px 0 3px;margin-top:2px;
                    border-top:1px solid rgba(255,255,255,.08);
                    pointer-events:none;
                `;
                divEl.textContent = ic.div;
                iconGrid.appendChild(divEl);
                return;
            }
            const cell = document.createElement('button');
            cell.type = 'button';
            cell.dataset.iconId = ic.id;
            const isSel = ic.id === StarPipState.selectedIconId;
            cell.style.cssText = `
                display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
                padding:8px 3px;border-radius:8px;border:1.5px solid ${isSel ? ic.color : 'transparent'};
                background:${isSel ? 'rgba(255,255,255,.07)' : 'transparent'};
                cursor:pointer;transition:all .12s;font-family:inherit;
            `;

            const iconWrap = document.createElement('div');
            iconWrap.style.cssText = `width:24px;height:24px;color:${ic.color};flex-shrink:0`;
            iconWrap.innerHTML = ic.svg;
            const svgEl = iconWrap.querySelector('svg');
            if (svgEl) { svgEl.style.width = '24px'; svgEl.style.height = '24px'; }

            const iconLbl = document.createElement('span');
            iconLbl.style.cssText = 'font-size:9px;color:rgba(255,255,255,.45);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:56px';
            iconLbl.textContent = ic.label;

            cell.appendChild(iconWrap);
            cell.appendChild(iconLbl);

            cell.addEventListener('click', () => {
                StarPipState.selectedIconId = ic.id;
                iconGrid.querySelectorAll('button[data-icon-id]').forEach(b => {
                    const bid = b.dataset.iconId;
                    const bic = _GROUP_ICON_FLAT.find(x => x.id === bid);
                    b.style.border = `1.5px solid ${bid === ic.id ? (bic?.color ?? ic.color) : 'transparent'}`;
                    b.style.background = bid === ic.id ? 'rgba(255,255,255,.07)' : 'transparent';
                });
            });

            iconGrid.appendChild(cell);
        });

        const btns = document.createElement('div');
        btns.style.cssText = 'display:flex;gap:8px;margin-top:16px';

        const skipBtn = document.createElement('button');
        skipBtn.type = 'button';
        skipBtn.style.cssText = `
            flex:1;padding:9px 0;border-radius:8px;
            border:1px solid rgba(255,255,255,.15);
            background:rgba(255,255,255,.06);
            color:rgba(255,255,255,.6);
            font-size:13px;font-weight:500;cursor:pointer;
            font-family:inherit;text-align:center;line-height:1;
            transition:background .12s;
        `;
        skipBtn.textContent = 'Skip';
        skipBtn.addEventListener('mouseover', () => skipBtn.style.background = 'rgba(255,255,255,.12)');
        skipBtn.addEventListener('mouseout',  () => skipBtn.style.background = 'rgba(255,255,255,.06)');
        skipBtn.addEventListener('click', () => _closeGroupModal(false, null));

        const confirmBtn = document.createElement('button');
        confirmBtn.type = 'button';
        confirmBtn.style.cssText = `
            flex:1;padding:9px 0;border-radius:8px;
            border:none;background:#1d9bf0;
            color:#fff;font-size:13px;font-weight:500;cursor:pointer;
            font-family:inherit;text-align:center;line-height:1;
            transition:background .12s;
        `;
        confirmBtn.textContent = 'Create';
        confirmBtn.addEventListener('mouseover', () => confirmBtn.style.background = '#1a8cd8');
        confirmBtn.addEventListener('mouseout',  () => confirmBtn.style.background = '#1d9bf0');
        confirmBtn.addEventListener('click', () => _closeGroupModal(true, input.value.trim()));

        btns.appendChild(skipBtn);
        btns.appendChild(confirmBtn);

        box.appendChild(title);
        box.appendChild(input);
        box.appendChild(iconLabel);
        box.appendChild(iconGrid);
        box.appendChild(btns);
        overlay.appendChild(box);
        document.body.appendChild(overlay);

        input.addEventListener('keydown', e => {
            if (e.key === 'Enter')  _closeGroupModal(true, input.value.trim());
            if (e.key === 'Escape') _closeGroupModal(false, null);
        });
        overlay.addEventListener('click', e => {
            if (e.target === overlay) _closeGroupModal(false, null);
        });
        requestAnimationFrame(() => {
            overlay.classList.add('tm-show');
            setTimeout(() => input.focus(), 200);
        });
    }

    function _closeGroupModal(confirm, name) {
        const overlay = document.getElementById('tm-group-modal-overlay');
        if (!overlay) return;
        overlay.classList.remove('tm-show');
        setTimeout(() => overlay.remove(), 220);

        if (!confirm || !name) {
            hideStarPip();
            return;
        }

        const ic      = (StarPipState.selectedIconId && _GROUP_ICON_FLAT.find(x => x.id === StarPipState.selectedIconId)) || _GROUP_ICON_FLAT[0];
        const _baseLookup = StarPipState.pendingIsText ? getTextGroups() : getGroups();
        const glowIdx = _baseLookup.length % STAR_GLOW_COLORS.length;
        const group   = StarPipState.pendingIsText
            ? createTextGroup(name, ic.id, STAR_GLOW_COLORS[glowIdx])
            : createGroup(name, ic.id, STAR_GLOW_COLORS[glowIdx]);
        if (StarPipState.pendingGroupRecordId !== null) {
            assignGroup(StarPipState.pendingGroupRecordId, group.id);
            StarPipState.pendingGroupRecordId = null;
        }
        StarPipState.pendingIsText = false;
        showToast(`⭐ Created「${ic.label} · ${name}」`);

        setTimeout(() => {
            if (document.getElementById('tm-star-pip')?.classList.contains('tm-popped')) {
                openGroupFan();
            }
        }, 300);
    }

    function showGroupManagerModal() {
        const old = document.getElementById('tm-group-mgr-overlay');
        if (old) old.remove();

        const overlay = document.createElement('div');
        overlay.id        = 'tm-group-mgr-overlay';
        overlay.className = 'tm-group-modal-overlay';

        const box = document.createElement('div');
        box.className = 'tm-group-modal-box';
        box.style.cssText += ';width:280px;max-width:92vw';

        const titleRow = document.createElement('div');
        titleRow.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:14px';

        const title = document.createElement('div');
        title.className   = 'tm-group-modal-title';
        title.style.margin = '0';
        title.textContent = T.sp_grp_modal_title || 'Manage Groups';

        const newBtn = document.createElement('button');
        newBtn.type = 'button';
        newBtn.textContent = T.sp_grp_modal_new || '+ New';
        newBtn.style.cssText = `
            padding:5px 10px;border-radius:7px;border:none;
            background:#1d9bf0;color:#fff;
            font-size:11px;font-weight:600;cursor:pointer;
            font-family:inherit;line-height:1;transition:background .12s;
        `;
        newBtn.addEventListener('mouseover', () => newBtn.style.background = '#1a8cd8');
        newBtn.addEventListener('mouseout',  () => newBtn.style.background = '#1d9bf0');
        newBtn.addEventListener('click', () => {
            overlay.classList.remove('tm-show');
            setTimeout(() => { overlay.remove(); showGroupCreateModal(); }, 180);
        });

        titleRow.appendChild(title);
        titleRow.appendChild(newBtn);

        const list = document.createElement('div');
        list.style.cssText = 'flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:6px;margin-bottom:14px;scrollbar-width:thin';

        let _openIconPicker = null;

        function buildIconPicker(g, iconWrap, onPick) {
            const picker = document.createElement('div');
            picker.style.cssText = `
                display:grid;grid-template-columns:repeat(6,1fr);gap:4px;
                padding:8px;background:rgba(20,25,40,.98);
                border-radius:8px;border:1px solid rgba(255,255,255,.1);
                margin-top:4px;
            `;
            _GROUP_ICON_FLAT.forEach(ic => {
                const cell = document.createElement('button');
                cell.type = 'button';
                cell.style.cssText = `
                    display:flex;flex-direction:column;align-items:center;gap:2px;
                    padding:5px 2px;border-radius:6px;border:1.5px solid ${ic.id === g.icon ? ic.color : 'transparent'};
                    background:${ic.id === g.icon ? 'rgba(255,255,255,.07)' : 'transparent'};
                    cursor:pointer;transition:all .1s;font-family:inherit;
                `;
                const svg = document.createElement('div');
                svg.style.cssText = `width:18px;height:18px;color:${ic.color}`;
                svg.innerHTML = ic.svg;
                const svgEl = svg.querySelector('svg');
                if (svgEl) { svgEl.style.width = '18px'; svgEl.style.height = '18px'; }
                const lbl = document.createElement('span');
                lbl.style.cssText = 'font-size:7px;color:rgba(255,255,255,.4);white-space:nowrap;overflow:hidden;max-width:38px;text-overflow:ellipsis';
                lbl.textContent = ic.label;
                cell.appendChild(svg);
                cell.appendChild(lbl);
                cell.addEventListener('click', () => {
                    onPick(ic);
                    picker.remove();
                    _openIconPicker = null;
                });
                picker.appendChild(cell);
            });
            return picker;
        }

        function rebuildList() {
            list.innerHTML = '';
            _openIconPicker = null;
            const groups = getGroups();

            if (!groups.length) {
                const empty = document.createElement('div');
                empty.style.cssText = 'font-size:12px;color:rgba(255,255,255,.35);text-align:center;padding:16px 0';
                empty.textContent = 'No groups yet — click + New to create one';
                list.appendChild(empty);
                return;
            }

            let _dragSrcIdx = -1;

            const _groupCountMap = _loadAllRecords().reduce((m, r) => {
                if (r.groupId) m.set(r.groupId, (m.get(r.groupId) || 0) + 1);
                return m;
            }, new Map());

            groups.forEach((g, gIdx) => {
                const wrapper = document.createElement('div');
                wrapper.style.cssText = 'display:flex;flex-direction:column;gap:0';
                wrapper.draggable = true;
                wrapper.dataset.groupId  = g.id;
                wrapper.dataset.groupIdx = gIdx;

                wrapper.addEventListener('dragstart', e => {
                    _dragSrcIdx = gIdx;
                    wrapper.style.opacity = '0.45';
                    e.dataTransfer.effectAllowed = 'move';
                });
                wrapper.addEventListener('dragend', () => {
                    wrapper.style.opacity = '1';
                    list.querySelectorAll('[data-group-id]').forEach(el => {
                        el.style.borderTop    = '';
                        el.style.borderBottom = '';
                    });
                });
                wrapper.addEventListener('dragover', e => {
                    e.preventDefault();
                    e.dataTransfer.dropEffect = 'move';
                    const rect = wrapper.getBoundingClientRect();
                    const mid  = rect.top + rect.height / 2;
                    list.querySelectorAll('[data-group-id]').forEach(el => {
                        el.style.borderTop = ''; el.style.borderBottom = '';
                    });
                    if (e.clientY < mid) wrapper.style.borderTop    = '2px solid rgba(29,155,240,.7)';
                    else                 wrapper.style.borderBottom = '2px solid rgba(29,155,240,.7)';
                });
                wrapper.addEventListener('dragleave', () => {
                    wrapper.style.borderTop = ''; wrapper.style.borderBottom = '';
                });
                wrapper.addEventListener('drop', e => {
                    e.preventDefault();
                    wrapper.style.borderTop = ''; wrapper.style.borderBottom = '';
                    const destIdx = parseInt(wrapper.dataset.groupIdx);
                    if (_dragSrcIdx === -1 || _dragSrcIdx === destIdx) return;
                    const rect = wrapper.getBoundingClientRect();
                    const insertAfter = e.clientY >= rect.top + rect.height / 2;
                    const arr  = getGroups();
                    const [moved] = arr.splice(_dragSrcIdx, 1);
                    const newDest  = insertAfter
                        ? (_dragSrcIdx < destIdx ? destIdx : destIdx + 1)
                        : (_dragSrcIdx < destIdx ? destIdx - 1 : destIdx);
                    arr.splice(Math.max(0, Math.min(arr.length, newDest)), 0, moved);
                    saveGroups(arr);
                    _dragSrcIdx = -1;
                    rebuildList();
                });

                const row = document.createElement('div');
                row.style.cssText = 'display:flex;align-items:center;gap:8px;background:rgba(255,255,255,.05);border-radius:8px;padding:7px 8px';

                const dragHandle = document.createElement('span');
                dragHandle.title = 'Drag to reorder';
                dragHandle.innerHTML = '<svg viewBox="0 0 10 16" width="8" height="13" fill="currentColor"><circle cx="3" cy="3" r="1.2"/><circle cx="7" cy="3" r="1.2"/><circle cx="3" cy="8" r="1.2"/><circle cx="7" cy="8" r="1.2"/><circle cx="3" cy="13" r="1.2"/><circle cx="7" cy="13" r="1.2"/></svg>';
                dragHandle.style.cssText = 'flex-shrink:0;color:rgba(255,255,255,.2);cursor:grab;display:flex;align-items:center;transition:color .12s;user-select:none';
                dragHandle.addEventListener('mouseover', () => dragHandle.style.color = 'rgba(255,255,255,.5)');
                dragHandle.addEventListener('mouseout',  () => dragHandle.style.color = 'rgba(255,255,255,.2)');
                dragHandle.addEventListener('mousedown', () => { wrapper.draggable = true; });
                row.addEventListener('mousedown', e => {
                    if (!e.target.closest('[title="Drag to reorder"]')) wrapper.draggable = false;
                });

                const iconWrap = document.createElement('button');
                iconWrap.type = 'button';
                iconWrap.title = 'Change icon';
                iconWrap.style.cssText = `
                    width:22px;height:22px;flex-shrink:0;border:none;
                    background:transparent;cursor:pointer;padding:0;border-radius:4px;
                    transition:background .12s;display:flex;align-items:center;justify-content:center;
                `;
                const ic = _resolveGroupIcon(g.icon);
                iconWrap.style.color = ic.color;
                iconWrap.innerHTML = ic.svg;
                iconWrap.addEventListener('mouseover', () => iconWrap.style.background = 'rgba(255,255,255,.1)');
                iconWrap.addEventListener('mouseout',  () => iconWrap.style.background = 'transparent');
                iconWrap.addEventListener('click', () => {
                    if (_openIconPicker) { _openIconPicker.remove(); _openIconPicker = null; }
                    const picker = buildIconPicker(g, iconWrap, (newIc) => {
                        const arr = getGroups();
                        const idx = arr.findIndex(x => x.id === g.id);
                        if (idx > -1) { arr[idx].icon = newIc.id; saveGroups(arr); g.icon = newIc.id; }
                        const resolved = _resolveGroupIcon(newIc.id);
                        iconWrap.style.color = resolved.color;
                        iconWrap.innerHTML = resolved.svg;
                        showToast(`Icon → ${resolved.label}`);
                    });
                    wrapper.appendChild(picker);
                    _openIconPicker = picker;
                });

                const nameInput = document.createElement('input');
                nameInput.value     = g.name;
                nameInput.maxLength = 24;
                nameInput.readOnly  = true;
                nameInput.style.cssText = 'flex:1;min-width:0;background:transparent;border:none;border-bottom:1px solid transparent;outline:none;color:rgba(255,255,255,.88);font-size:12px;font-family:inherit;padding:2px 0;transition:border-color .12s;overflow:hidden;text-overflow:ellipsis;cursor:default';
                nameInput.addEventListener('blur', () => {
                    const n = nameInput.value.trim();
                    if (!n) { nameInput.value = g.name; }
                    else if (n !== g.name) {
                        const arr = getGroups();
                        const idx = arr.findIndex(x => x.id === g.id);
                        if (idx > -1) { arr[idx].name = n; saveGroups(arr); g.name = n; showToast(`Renamed → ${n}`); }
                    }
                    nameInput.readOnly = true;
                    nameInput.style.borderColor = 'transparent';
                    nameInput.style.cursor = 'default';
                    editBtn.style.opacity = '0';
                });
                nameInput.addEventListener('keydown', e => {
                    if (e.key === 'Enter')  nameInput.blur();
                    if (e.key === 'Escape') { nameInput.value = g.name; nameInput.blur(); }
                });

                const editBtn = document.createElement('button');
                editBtn.type = 'button';
                editBtn.title = 'Rename';
                editBtn.style.cssText = 'background:transparent;border:none;color:rgba(255,255,255,.3);cursor:pointer;flex-shrink:0;padding:0 2px;line-height:1;transition:color .12s,opacity .15s;opacity:0';
                editBtn.innerHTML = '<svg viewBox="0 0 14 14" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 1.5l3 3L4 13H1v-3L9.5 1.5z"/></svg>';
                editBtn.addEventListener('mouseover', () => editBtn.style.color = 'rgba(255,255,255,.7)');
                editBtn.addEventListener('mouseout',  () => editBtn.style.color = 'rgba(255,255,255,.3)');
                editBtn.addEventListener('click', () => {
                    nameInput.readOnly = false;
                    nameInput.style.borderBottom = '1px solid rgba(29,155,240,.6)';
                    nameInput.style.cursor = 'text';
                    nameInput.focus();
                    nameInput.select();
                });
                row.addEventListener('mouseenter', () => { editBtn.style.opacity = '1'; });
                row.addEventListener('mouseleave', () => { if (nameInput.readOnly) editBtn.style.opacity = '0'; });

                const cnt = _groupCountMap.get(g.id) || 0;
                const cntBadge = document.createElement('span');
                cntBadge.style.cssText = 'font-size:10px;color:rgba(255,255,255,.3);flex-shrink:0;white-space:nowrap';
                cntBadge.textContent = cnt ? `${cnt} items` : '';

                const delBtn = document.createElement('button');
                delBtn.type  = 'button';
                delBtn.title = 'Delete group';
                delBtn.style.cssText = 'background:transparent;border:none;color:rgba(255,80,80,.55);font-size:14px;cursor:pointer;flex-shrink:0;padding:0 2px;line-height:1;transition:color .12s';
                delBtn.innerHTML = '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>';
                delBtn.addEventListener('mouseover', () => delBtn.style.color = 'rgba(255,80,80,.9)');
                delBtn.addEventListener('mouseout',  () => delBtn.style.color = 'rgba(255,80,80,.55)');
                delBtn.addEventListener('click', () => {
                    if (cnt > 0 && !confirm(`Delete「${g.name}」? This will ungroup ${cnt} item(s).`)) return;
                    if (cnt === 0 && !confirm(`Delete「${g.name}」?`)) return;
                    deleteGroup(g.id);
                    rebuildList();
                    showToast(`Deleted「${g.name}」`);
                });

                row.appendChild(dragHandle);
                row.appendChild(iconWrap);
                row.appendChild(nameInput);
                row.appendChild(editBtn);
                row.appendChild(cntBadge);
                row.appendChild(delBtn);
                wrapper.appendChild(row);
                list.appendChild(wrapper);
            });
        }
        rebuildList();

        const _closeModal = () => {
            overlay.classList.remove('tm-show');
            setTimeout(() => overlay.remove(), 220);
            const existPanel = document.getElementById('tm-hist-panel');
            if (existPanel) existPanel.dispatchEvent(new CustomEvent('tm-hist-refresh'));
        };

        const doneBtn = document.createElement('button');
        doneBtn.type = 'button';
        doneBtn.style.cssText = `
            width:100%;padding:9px 0;border-radius:8px;border:none;
            background:#1d9bf0;color:#fff;
            font-size:13px;font-weight:500;cursor:pointer;
            font-family:inherit;text-align:center;line-height:1;
            transition:background .12s;
        `;
        doneBtn.textContent = 'Done';
        doneBtn.addEventListener('mouseover', () => doneBtn.style.background = '#1a8cd8');
        doneBtn.addEventListener('mouseout',  () => doneBtn.style.background = '#1d9bf0');
        doneBtn.addEventListener('click', _closeModal);

        box.appendChild(titleRow);
        box.appendChild(list);
        box.appendChild(doneBtn);
        overlay.appendChild(box);
        document.body.appendChild(overlay);

        overlay.addEventListener('click', e => {
            if (e.target === overlay) _closeModal();
        });

        requestAnimationFrame(() => overlay.classList.add('tm-show'));
    }

    (function _migrateHistoryIfNeeded() {
        const legacy = GM_getValue(KEY_HISTORY_RECORDS, null);
        if (!legacy) return;
        try {
            const records = JSON.parse(legacy);
            if (!records.length) { GM_deleteValue(KEY_HISTORY_RECORDS); return; }

            const byMonth = {};
            records.forEach(r => {
                const ym = r.yyyymm ||
                    (r.ts ? new Date(r.ts).toISOString().slice(0, 7).replace('-', '.') : '9999.99');
                (byMonth[ym] = byMonth[ym] || []).push(r);
            });

            const idx = Object.keys(byMonth).sort().reverse();
            idx.forEach(ym => _writeMonthRecords(ym, byMonth[ym]));
            GM_setValue(KEY_HISTORY_INDEX, JSON.stringify(idx));
            GM_deleteValue(KEY_HISTORY_RECORDS);
        } catch (e) { console.error('[TMHist] Migration failed (keeping legacy key):', e); }
    })();

    function recordHistory(info, urls, mediaBtn) {
        try {
            const thumbUrls = urls.filter(u => !u.includes('.mp4'));
            const hasVideo  = urls.some(u => u.includes('.mp4'));

            if (thumbUrls.length === 0 && info.videoThumb) {
                thumbUrls.push(info.videoThumb);
            }

            const _now    = new Date();
            const _yy     = _now.getFullYear();
            const _mm     = String(_now.getMonth() + 1).padStart(2, '0');
            const yyyymm  = `${_yy}.${_mm}`;

            const record = {
                id:          Date.now(),
                ts:          Date.now(),
                yyyymm,
                tweetId:     info.id,
                tweetUrl:    `https://x.com/${info.screenName}/status/${info.id}`,
                tweetDate:   info.date,
                downloadDate: `${_yy}-${_mm}-${String(_now.getDate()).padStart(2,'0')}`,
                screenName:  info.screenName,
                displayName: info.displayName,
                text:        (info.text || '').slice(0, 80),
                thumbUrls,
                mediaUrls:   urls,
                hasVideo,
                count:       urls.length,
            };

            const ym = record.yyyymm;
            let monthRecs = _readMonthRecords(ym);
            const _oldRecord = monthRecs.find(r => r.tweetId === info.id);
            if (_oldRecord?.favorited) record.favorited = true;
            monthRecs = monthRecs.filter(r => r.tweetId !== info.id);
            monthRecs.unshift(record);
            _updateHistoryIndex(ym);
            _writeMonthRecords(ym, monthRecs);

            if (!GM_getValue(KEY_HIST_CLEANUP_NOTIFIED, false)) {
                const _idx = _getHistoryIndex();
                const _roughCount = _idx.length * 100;
                if (_roughCount >= 2000) {
                    const _totalCount = _idx.reduce((sum, ym2) => sum + _readMonthRecords(ym2).length, 0);
                    if (_totalCount >= 2000) {
                        GM_setValue(KEY_HIST_CLEANUP_NOTIFIED, true);
                        setTimeout(() => showToast(
                            '📋 You have 2000+ download records. ' +
                            'Consider exporting a backup (📤 in the history panel) and deleting old entries ' +
                            'to keep storage healthy and panel loading fast.',
                            8000
                        ), 500);
                    }
                }
            }

            if (GM_getValue(KEY_GROUP_ON_DOWNLOAD, false)) {
                StarPipState.pendingIsText        = false;
                StarPipState.pendingGroupRecordId = record.id;
                setTimeout(() => popStarPip(mediaBtn || null), 80);
            }

            _downloadedIds.add(info.id);
            _syncCh?.postMessage({ type: 'dl', tweetId: info.id });

            document.querySelectorAll(`article a[href*="/status/${info.id}"]`).forEach(a => {
                const art = a.closest('article');
                if (art) {
                    const targetBtn = art.querySelector('.force-media-copy-btn');
                    if (targetBtn) _applyHistoryBadge(targetBtn);
                }
            });

            const existPanel = document.getElementById('tm-hist-panel');
            if (existPanel) existPanel.dispatchEvent(new CustomEvent('tm-hist-refresh'));
        } catch (e) { console.error('[TMHist] recordHistory error:', e); }
    }

    function _createMediaRenderer(deps) {
        const {
            body, selectedIds, collapsedGroups, dark, delSelBtn,
            getFiltered, getRecords, _invalidateRecordsCache, _fmtGroupLabel,
            render, _openHistMediaLightbox, _injectHistCrossNav,
            getEditMode, getQuery, getAnchorIdx, setAnchorIdx,
        } = deps;

        function renderList(records) {
            const editMode = getEditMode();
            if (!records.length) { _renderEmpty(); return; }

            const groupCounts = {};
            records.forEach(r => { groupCounts[r.yyyymm] = (groupCounts[r.yyyymm] || 0) + 1; });

            let lastGroup = null;
            let _cbShiftDown = false;

            const frag = document.createDocumentFragment();

            records.forEach((rec, idx) => {
                if (rec.yyyymm !== lastGroup) {
                    lastGroup = rec.yyyymm;
                    const isCollapsed = collapsedGroups.has(rec.yyyymm);
                    const gh = document.createElement('div');
                    gh.className = 'tm-hist-group-header' + (isCollapsed ? ' tm-collapsed' : '');
                    gh.dataset.yyyymm = rec.yyyymm;

                    const chevron = document.createElement('span');
                    chevron.className = 'tm-hist-group-chevron';
                    chevron.innerHTML = `<svg viewBox="0 0 10 10" width="8" height="8" fill="currentColor"><path d="M1 3l4 4 4-4z"/></svg>`;

                    const label = document.createElement('span');
                    label.textContent = _fmtGroupLabel(rec.yyyymm);

                    const countEl = document.createElement('span');
                    countEl.className = 'tm-hist-group-count';
                    countEl.textContent = `${groupCounts[rec.yyyymm]}`;

                    gh.appendChild(chevron);
                    gh.appendChild(label);
                    gh.appendChild(countEl);

                    gh.addEventListener('click', () => {
                        if (collapsedGroups.has(rec.yyyymm)) collapsedGroups.delete(rec.yyyymm);
                        else collapsedGroups.add(rec.yyyymm);
                        render();
                    });
                    frag.appendChild(gh);
                }

                if (collapsedGroups.has(rec.yyyymm)) return;

                const row = document.createElement('div');
                row.className = 'tm-hist-row' + (selectedIds.has(rec.id) ? ' selected' : '');
                row.dataset.id = rec.id;
                row.dataset.idx = idx;

                if (editMode) {
                    if (!rec.favorited) {
                        const cb = document.createElement('input');
                        cb.type = 'checkbox';
                        cb.className = 'tm-hist-cb';
                        cb.checked = selectedIds.has(rec.id);
                        cb.addEventListener('mousedown', e => { _cbShiftDown = e.shiftKey; });
                        cb.addEventListener('change', e => {
                            e.stopPropagation();
                            _handleCheckbox(rec.id, idx, _cbShiftDown);
                            _cbShiftDown = false;
                        });
                        row.appendChild(cb);
                    } else {
                        const lock = document.createElement('span');
                        lock.className = 'tm-hist-cb';
                        lock.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;opacity:0.35;font-size:10px;';
                        lock.textContent = '♥';
                        row.appendChild(lock);
                    }
                }

                const thumbWrap = document.createElement('div');
                thumbWrap.className = 'tm-hist-thumb-wrap';
                if (rec.thumbUrls && rec.thumbUrls.length > 0) {
                    const img = document.createElement('img');
                    img.src = _thumbUrl(rec.thumbUrls[0]);
                    img.loading = 'lazy';
                    img.alt = '';
                    img.decode().catch(() => {});
                    thumbWrap.appendChild(img);
                    thumbWrap.classList.add('tm-has-thumb');
                    thumbWrap.addEventListener('mouseenter', (e) => _showZoom(rec.thumbUrls[0], e));
                    thumbWrap.addEventListener('mousemove',  (e) => _moveZoom(e));
                    thumbWrap.addEventListener('mouseleave', _hideZoom);
                    thumbWrap.addEventListener('click', (e) => {
                        e.stopPropagation();
                        _hideZoom();
                        _openHistMediaLightbox(rec, 0, thumbWrap.querySelector('img'));
                        _injectHistCrossNav(rec);
                    });
                } else if (rec.textOnly) {
                    if (rec.avatarUrl) {
                        const avImg = document.createElement('img');
                        avImg.src = rec.avatarUrl;
                        avImg.loading = 'lazy';
                        avImg.alt = '';
                        avImg.style.cssText = 'width:44px;height:44px;border-radius:50%;object-fit:cover;flex-shrink:0;';
                        avImg.decode().catch(() => {});
                        thumbWrap.style.cssText += ';display:flex;align-items:center;justify-content:center;background:rgba(29,155,240,.06);';
                        thumbWrap.appendChild(avImg);
                    } else {
                        const vi = document.createElement('div');
                        vi.className = 'tm-hist-video-icon';
                        vi.style.color = 'rgba(29,155,240,.5)';
                        vi.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="14" y2="17"/></svg>`;
                        thumbWrap.appendChild(vi);
                    }
                } else {
                    const vi = document.createElement('div');
                    vi.className = 'tm-hist-video-icon';
                    vi.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M15 10l4.55-2.28A1 1 0 0 1 21 8.65v6.7a1 1 0 0 1-1.45.93L15 14"/><rect x="2" y="7" width="13" height="10" rx="2"/></svg>`;
                    thumbWrap.appendChild(vi);
                }
                row.appendChild(thumbWrap);

                const info = document.createElement('div');
                info.className = 'tm-hist-info';

                const author = document.createElement('div');
                author.className = 'tm-hist-author';
                author.textContent = rec.displayName || rec.screenName;
                const handle = document.createElement('span');
                handle.className = 'tm-hist-handle';
                handle.textContent = `@${rec.screenName}`;
                author.appendChild(handle);

                const textEl = document.createElement('div');
                textEl.className = 'tm-hist-text';
                textEl.textContent = rec.text || '(no caption)';

                const dateEl = document.createElement('div');
                dateEl.className = 'tm-hist-date';
                if (rec.downloadDate) {
                    const [dy, dm, dd] = rec.downloadDate.split('-');
                    if (_cachedDateFormat === 'western') {
                        const MON = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
                        dateEl.textContent = `${parseInt(dd, 10)} ${MON[parseInt(dm, 10)] || dm} ${dy}`;
                    } else {
                        dateEl.textContent = `${dy}.${dm}.${dd}`;
                    }
                }

                const urlEl = document.createElement('div');
                urlEl.className = 'tm-hist-url';
                if (rec.tweetUrl) {
                    urlEl.textContent = rec.tweetUrl;
                    urlEl.title = rec.tweetUrl + '\nClick to navigate to tweet';
                    urlEl.addEventListener('click', (e) => {
                        e.stopPropagation();
                    try {
                        const path = new URL(rec.tweetUrl).pathname;
                        history.pushState({ tmNav: true }, '', path);
                        window.dispatchEvent(new Event('popstate'));
                    } catch (_) { window.open(rec.tweetUrl, '_blank'); }
                });
                }

                info.appendChild(author);
                info.appendChild(textEl);

                const metaEl = document.createElement('div');
                metaEl.className = 'tm-hist-meta';
                if (rec.downloadDate) metaEl.appendChild(dateEl);
                if (rec.downloadDate && rec.tweetUrl) {
                    const sep = document.createElement('span');
                    sep.className = 'tm-hist-meta-sep';
                    sep.textContent = '·';
                    metaEl.appendChild(sep);
                }
                if (rec.tweetUrl) metaEl.appendChild(urlEl);
                if (metaEl.hasChildNodes()) info.appendChild(metaEl);

                row.appendChild(info);

                const acts = document.createElement('div');
                acts.className = 'tm-hist-actions';

                const SVG_JUMP    = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M10 2h4v4"/><path d="M7 9L14 2"/><path d="M12 10v4H2V4h4"/></svg>`;
                const SVG_DEL     = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><polyline points="2,4 4,4 14,4"/><path d="M13 4l-.9 9H3.9L3 4"/><path d="M6.5 7v4M9.5 7v4"/><path d="M5.5 4V2.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5V4"/></svg>`;
                const SVG_COPY    = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="5" width="9" height="10" rx="1.5"/><path d="M3 11H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v1"/></svg>`;
                const SVG_HEART_EMPTY = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13.5S1.5 9.5 1.5 5.5A3.5 3.5 0 0 1 8 3.207 3.5 3.5 0 0 1 14.5 5.5C14.5 9.5 8 13.5 8 13.5z"/></svg>`;
                const SVG_HEART_FULL  = `<svg viewBox="0 0 16 16" fill="currentColor" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13.5S1.5 9.5 1.5 5.5A3.5 3.5 0 0 1 8 3.207 3.5 3.5 0 0 1 14.5 5.5C14.5 9.5 8 13.5 8 13.5z"/></svg>`;

                const copyBtn = document.createElement('button');
                copyBtn.className = 'tm-hist-act-btn tm-copy-btn';
                copyBtn.innerHTML = SVG_COPY;
                copyBtn.title = 'Click: Copy tweet URL\nHold: Copy with prefix';

                const _getMediaUrls = () => {
                    const _dom = GM_getValue(KEY_CLICK_MODE_CUSTOM, false)
                        ? GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com') : 'x.com';
                    const _toUrl = u => {
                        try {
                            const o = new URL(u);
                            if (o.hostname.includes('twimg.com') || u.includes('.mp4')) return u;
                            o.hostname = _dom;
                            return o.toString();
                        }
                        catch(_) { return u; }
                    };
                    return _toUrl(rec.tweetUrl);
                };

                let _cpTimer = null;

                copyBtn.addEventListener('mousedown', e => {
                    if (e.button !== 0) return;
                    e.stopPropagation();
                    _cpTimer = setTimeout(() => {
                        _cpTimer = null;
                        GM_setClipboard(_buildPrefixedTweetUrl(rec.tweetUrl));
                        copyBtn.style.color = '#f59e0b';
                        setTimeout(() => { copyBtn.style.color = ''; }, 900);
                    }, 500);
                });

                copyBtn.addEventListener('mouseup', e => {
                    if (_cpTimer) {
                        clearTimeout(_cpTimer); _cpTimer = null;
                        GM_setClipboard(_getMediaUrls());
                        copyBtn.style.color = '#1d9bf0';
                        setTimeout(() => { copyBtn.style.color = ''; }, 800);
                    }
                });

                copyBtn.addEventListener('mouseleave', () => { if (_cpTimer) { clearTimeout(_cpTimer); _cpTimer = null; } });
                copyBtn.addEventListener('click', e => e.stopPropagation());

                const jmpBtn = document.createElement('button');
                jmpBtn.className = 'tm-hist-act-btn';
                jmpBtn.innerHTML = SVG_JUMP;
                jmpBtn.title = 'Open tweet';
                jmpBtn.addEventListener('click', (e) => { e.stopPropagation(); window.open(rec.tweetUrl, '_blank'); });

                const favBtn = document.createElement('button');
                const isFav = !!rec.favorited;
                favBtn.className = 'tm-hist-act-btn tm-fav-btn' + (isFav ? ' tm-fav-active' : '');
                favBtn.innerHTML = isFav ? SVG_HEART_FULL : SVG_HEART_EMPTY;
                favBtn.title = isFav ? 'Unfavorite' : 'Favorite';
                if (editMode) {
                    favBtn.style.opacity = '0.3';
                    favBtn.style.pointerEvents = 'none';
                }
                favBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    const updated = _updateRecord(rec.id, r => { r.favorited = !r.favorited; });
                    if (!updated) return;
                    const nowFav = updated.favorited;
                    favBtn.innerHTML = nowFav ? SVG_HEART_FULL : SVG_HEART_EMPTY;
                    favBtn.title = nowFav ? 'Unfavorite' : 'Favorite';
                    favBtn.classList.toggle('tm-fav-active', nowFav);
                    rec.favorited = nowFav;
                });

                const delBtn = document.createElement('button');
                delBtn.className = 'tm-hist-act-btn danger';
                delBtn.innerHTML = SVG_DEL;
                delBtn.title = 'Delete';
                delBtn.addEventListener('click', (e) => { e.stopPropagation(); _deleteOne(rec.id, idx, favBtn); });

                const SVG_DLOAD = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M8 2v8"/><path d="M5 7l3 3 3-3"/><path d="M2 13h12"/></svg>`;
                const dlBtn = document.createElement('button');
                dlBtn.className = 'tm-hist-act-btn';
                dlBtn.innerHTML = SVG_DLOAD;
                const hasMedia = rec.mediaUrls && rec.mediaUrls.length > 0;
                dlBtn.title = hasMedia ? `Re-download (${rec.mediaUrls.length} file${rec.mediaUrls.length > 1 ? 's' : ''})` : 'Open tweet to re-download';
                dlBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (!hasMedia) { window.open(rec.tweetUrl, '_blank'); return; }
                    const safeDisplay = sanitizeForFilename(rec.displayName || rec.screenName || 'unknown');
                    const safeScreen  = sanitizeForFilename(rec.screenName  || 'unknown');
                    const datePart    = rec.tweetDate || rec.downloadDate || 'unknown';
                    const textPart    = rec.text ? `_${sanitizeForFilename(rec.text, 40)}` : '';
                    const safeTextForToken = sanitizeForFilename(rec.text || '', CUSTOM_FN_TEXT_MAXLEN);
                    const idPart      = rec.tweetId || rec.id;
                    rec.mediaUrls.forEach((url, i) => {
                        let ext = 'jpg';
                        if (url.includes('.mp4')) {
                            ext = 'mp4';
                        } else {
                            try {
                                const fmtParam = new URL(url).searchParams.get('format');
                                ext = fmtParam || url.match(/\.(\w{3,4})(?:\?|$)/)?.[1] || 'jpg';
                            } catch (_) {}
                        }
                        const fname = buildCustomFilename(
                            { screenName: safeScreen, displayName: safeDisplay, date: datePart, id: idPart, index: i + 1, ext, text: safeTextForToken, origName: _extractOrigMediaName(url) },
                            () => `[twitter] ${safeDisplay}(@${safeScreen})_${datePart}${textPart}_${idPart}_${i + 1}.${ext}`
                        );
                        setTimeout(() => forceDownloadBlob(url, fname), i * 600);
                    });
                });

                acts.appendChild(copyBtn);
                acts.appendChild(favBtn);
                acts.appendChild(jmpBtn);
                if (!rec.textOnly) acts.appendChild(dlBtn);
                acts.appendChild(delBtn);
                row.appendChild(acts);

                if (editMode && !rec.favorited) {
                    row.style.cursor = 'pointer';
                    row.addEventListener('click', (e) => {
                        if (e.target.classList.contains('tm-hist-cb')) return;
                        _handleCheckbox(rec.id, idx, e.shiftKey);
                    });
                }

                frag.appendChild(row);
            });
            body.appendChild(frag);
        }

        function renderThumb(records) {
            const editMode = getEditMode();
            if (!records.length) { _renderEmpty(); return; }
            const grid = document.createElement('div');
            grid.id = 'tm-hist-thumb-grid';

            const frag = document.createDocumentFragment();

            records.forEach((rec, idx) => {
                const cell = document.createElement('div');
                cell.className = 'tm-hist-grid-cell';
                cell.title = `${rec.displayName} @${rec.screenName}`;

                if (rec.thumbUrls && rec.thumbUrls.length > 0) {
                    const img = document.createElement('img');
                    img.src = _thumbUrl(rec.thumbUrls[0]);
                    img.loading = 'lazy';
                    img.alt = '';
                    img.decode().catch(() => {});
                    cell.appendChild(img);
                } else if (rec.textOnly) {
                    const tc = document.createElement('div');
                    tc.style.cssText = 'width:100%;height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:6px;padding:10px;box-sizing:border-box;background:rgba(29,155,240,.08)';
                    if (rec.avatarUrl) {
                        const avImg = document.createElement('img');
                        avImg.src = rec.avatarUrl;
                        avImg.loading = 'lazy';
                        avImg.alt = '';
                        avImg.style.cssText = 'width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0;';
                        avImg.decode().catch(() => {});
                        tc.appendChild(avImg);
                    } else {
                        const tcIcon = document.createElement('div');
                        tcIcon.innerHTML = `<svg viewBox="0 0 20 20" width="20" height="20" fill="none" stroke="rgba(29,155,240,.6)" stroke-width="1.6" stroke-linecap="round"><line x1="3" y1="5" x2="17" y2="5"/><line x1="3" y1="9" x2="17" y2="9"/><line x1="3" y1="13" x2="12" y2="13"/></svg>`;
                        tc.appendChild(tcIcon);
                    }
                    const tcText = document.createElement('div');
                    tcText.style.cssText = `font-size:10px;color:${dark ? 'rgba(255,255,255,.5)' : 'rgba(15,20,25,.5)'};text-align:center;line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;word-break:break-all`;
                    tcText.textContent = rec.text || '';
                    tc.appendChild(tcText);
                    cell.appendChild(tc);
                } else {
                    const ni = document.createElement('div');
                    ni.className = 'tm-hist-grid-nothumb';
                    ni.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"><path d="M15 10l4.55-2.28A1 1 0 0 1 21 8.65v6.7a1 1 0 0 1-1.45.93L15 14"/><rect x="2" y="7" width="13" height="10" rx="2"/></svg>`;
                    cell.appendChild(ni);
                }

                const overlay = document.createElement('div');
                overlay.className = 'tm-hist-grid-overlay';
                const govAuthor = document.createElement('div');
                govAuthor.className = 'gov-author';
                govAuthor.textContent = rec.displayName || rec.screenName;
                const govText = document.createElement('div');
                govText.className = 'gov-text';
                govText.textContent = rec.text || '';
                overlay.appendChild(govAuthor);
                overlay.appendChild(govText);
                cell.appendChild(overlay);

                const SVG_COPY_SM = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="5" width="9" height="10" rx="1.5"/><path d="M3 11H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v1"/></svg>`;
                const gridCopyBtn = document.createElement('button');
                gridCopyBtn.className = 'tm-grid-copy-btn';
                gridCopyBtn.innerHTML = SVG_COPY_SM;
                gridCopyBtn.title = 'Click: Copy tweet URL\nHold: Copy with prefix';

                const _gcGetUrls = () => {
                    const _dom = GM_getValue(KEY_CLICK_MODE_CUSTOM, false)
                        ? GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com') : 'x.com';
                    const _toUrl = u => {
                        try {
                            const o = new URL(u);
                            if (o.hostname.includes('twimg.com') || u.includes('.mp4')) return u;
                            o.hostname = _dom;
                            return o.toString();
                        }
                        catch(_) { return u; }
                    };
                    return _toUrl(rec.tweetUrl);
                };

                let _gcTimer = null;
                let _gcPressing = false;
                gridCopyBtn.addEventListener('mousedown', e => {
                    if (e.button !== 0) return;
                    e.stopPropagation();
                    _gcPressing = true;
                    _gcTimer = setTimeout(() => {
                        _gcTimer = null;
                        _gcPressing = false;
                        GM_setClipboard(_buildPrefixedTweetUrl(rec.tweetUrl));
                        gridCopyBtn.style.background = 'rgba(245,158,11,0.9)';
                        setTimeout(() => { gridCopyBtn.style.background = ''; }, 800);
                    }, 500);
                });
                gridCopyBtn.addEventListener('mouseup', e => {
                    _gcPressing = false;
                    if (_gcTimer) {
                        clearTimeout(_gcTimer); _gcTimer = null;
                        GM_setClipboard(_gcGetUrls());
                        gridCopyBtn.style.background = 'rgba(29,155,240,0.9)';
                        setTimeout(() => { gridCopyBtn.style.background = ''; }, 700);
                    }
                });
                gridCopyBtn.addEventListener('mouseleave', () => { if (_gcTimer && !_gcPressing) { clearTimeout(_gcTimer); _gcTimer = null; } });
                gridCopyBtn.addEventListener('click', e => e.stopPropagation());
                cell.appendChild(gridCopyBtn);

                const SVG_DL_SM = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M8 2v8"/><path d="M5 7l3 3 3-3"/><path d="M2 13h12"/></svg>`;
                const gridDlBtn = document.createElement('button');
                gridDlBtn.className = 'tm-grid-dl-btn';
                gridDlBtn.innerHTML = SVG_DL_SM;
                const _gcHasMedia = rec.mediaUrls && rec.mediaUrls.length > 0;
                gridDlBtn.title = _gcHasMedia
                    ? `Re-download (${rec.mediaUrls.length} file${rec.mediaUrls.length > 1 ? 's' : ''})`
                    : 'Open tweet to re-download';
                gridDlBtn.addEventListener('click', e => {
                    e.stopPropagation();
                    if (!_gcHasMedia) { window.open(rec.tweetUrl, '_blank'); return; }
                    const safeDisplay = sanitizeForFilename(rec.displayName || rec.screenName || 'unknown');
                    const safeScreen  = sanitizeForFilename(rec.screenName  || 'unknown');
                    const datePart    = rec.tweetDate || rec.downloadDate || 'unknown';
                    const textPart    = rec.text ? `_${sanitizeForFilename(rec.text, 40)}` : '';
                    const safeTextForToken = sanitizeForFilename(rec.text || '', CUSTOM_FN_TEXT_MAXLEN);
                    const idPart      = rec.tweetId || rec.id;
                    rec.mediaUrls.forEach((url, i) => {
                        let ext = 'jpg';
                        if (url.includes('.mp4')) {
                            ext = 'mp4';
                        } else {
                            try {
                                const fmtParam = new URL(url).searchParams.get('format');
                                ext = fmtParam || url.match(/\.(\w{3,4})(?:\?|$)/)?.[1] || 'jpg';
                            } catch (_) {}
                        }
                        const fname = buildCustomFilename(
                            { screenName: safeScreen, displayName: safeDisplay, date: datePart, id: idPart, index: i + 1, ext, text: safeTextForToken, origName: _extractOrigMediaName(url) },
                            () => `[twitter] ${safeDisplay}(@${safeScreen})_${datePart}${textPart}_${idPart}_${i + 1}.${ext}`
                        );
                        setTimeout(() => forceDownloadBlob(url, fname), i * 600);
                    });
                });
                if (!rec.textOnly) cell.appendChild(gridDlBtn);

                const SVG_GOTO_SM = `<svg viewBox="0 0 14 14" fill="none" stroke="white" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10 L10 4"/><path d="M6.5 4h3.5v3.5"/></svg>`;
                const gridGotoBtn = document.createElement('button');
                gridGotoBtn.className = 'tm-grid-goto-btn';
                gridGotoBtn.innerHTML = SVG_GOTO_SM;
                gridGotoBtn.title = 'Go to tweet';
                gridGotoBtn.addEventListener('click', e => {
                    e.stopPropagation();
                    if (_isTwitterDomain) {
                        try {
                            const path = new URL(rec.tweetUrl).pathname;
                            history.pushState({ tmNav: true }, '', path);
                            window.dispatchEvent(new Event('popstate'));
                        } catch (_) { window.open(rec.tweetUrl, '_blank'); }
                    } else {
                        window.open(rec.tweetUrl, '_blank');
                    }
                });
                cell.appendChild(gridGotoBtn);

                const _GFB_HEART_EMPTY = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13.5S1.5 9.5 1.5 5.5A3.5 3.5 0 0 1 8 3.207 3.5 3.5 0 0 1 14.5 5.5C14.5 9.5 8 13.5 8 13.5z"/></svg>`;
                const _GFB_HEART_FULL  = `<svg viewBox="0 0 16 16" fill="currentColor" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13.5S1.5 9.5 1.5 5.5A3.5 3.5 0 0 1 8 3.207 3.5 3.5 0 0 1 14.5 5.5C14.5 9.5 8 13.5 8 13.5z"/></svg>`;
                const gridFavBtn = document.createElement('button');
                gridFavBtn.className = 'tm-grid-fav-btn' + (rec.favorited ? ' tm-fav-active' : '');
                gridFavBtn.innerHTML = rec.favorited ? _GFB_HEART_FULL : _GFB_HEART_EMPTY;
                gridFavBtn.title = rec.favorited ? 'Unfavorite' : 'Favorite';
                gridFavBtn.setAttribute('aria-label', rec.favorited ? 'Unfavorite' : 'Favorite');
                gridFavBtn.addEventListener('click', e => {
                    e.stopPropagation();
                    const updated = _updateRecord(rec.id, r => { r.favorited = !r.favorited; });
                    if (!updated) return;
                    const nowFav = updated.favorited;
                    gridFavBtn.innerHTML = nowFav ? _GFB_HEART_FULL : _GFB_HEART_EMPTY;
                    gridFavBtn.title = nowFav ? 'Unfavorite' : 'Favorite';
                    gridFavBtn.setAttribute('aria-label', nowFav ? 'Unfavorite' : 'Favorite');
                    gridFavBtn.classList.toggle('tm-fav-active', nowFav);
                    if (editMode) cell.classList.toggle('tm-grid-fav-lock', nowFav);
                    rec.favorited = nowFav;
                });
                cell.appendChild(gridFavBtn);

                if (editMode) {
                    if (rec.favorited) {
                        cell.classList.add('tm-grid-fav-lock');
                        cell.style.cursor = 'default';
                    } else {
                        if (selectedIds.has(rec.id)) cell.classList.add('selected');
                        cell.style.cursor = 'pointer';
                    }
                }

                cell.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (editMode) {
                        if (rec.favorited) return;
                        _handleCheckbox(rec.id, idx, e.shiftKey);
                    } else {
                        _openHistMediaLightbox(rec, 0, cell.querySelector('img'));
                        _injectHistCrossNav(rec);
                    }
                });

                cell.addEventListener('contextmenu', e => {
                    e.preventDefault(); e.stopPropagation();
                    document.getElementById('tm-thumb-ctx')?.remove();
                    _dialogOpenGlobal = true;
                    const menu = document.createElement('div');
                    menu.id = 'tm-thumb-ctx';
                    menu.style.cssText = `
                        position:fixed;left:${e.clientX}px;top:${e.clientY}px;
                        background:#1a1f2e;border:0.5px solid rgba(255,255,255,.15);
                        border-radius:10px;padding:4px;min-width:160px;
                        z-index:999999;box-shadow:0 8px 32px rgba(0,0,0,.6);
                        font-size:13px;
                    `;
                    const mkItem = (svgOrEmoji, label, onClick, danger = false) => {
                        const item = document.createElement('button');
                        item.type = 'button';
                        item.style.cssText = `display:flex;align-items:center;gap:9px;width:100%;padding:7px 12px;background:transparent;border:none;border-radius:7px;color:${danger ? 'rgba(255,100,100,.9)' : 'rgba(255,255,255,.88)'};cursor:pointer;font-size:13px;font-family:inherit;text-align:left;transition:background .1s`;
                        item.addEventListener('mouseover', () => item.style.background = danger ? 'rgba(255,80,80,.1)' : 'rgba(255,255,255,.08)');
                        item.addEventListener('mouseout',  () => item.style.background = 'transparent');
                        const iconEl = document.createElement('span');
                        iconEl.style.cssText = 'width:16px;height:16px;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:inherit;opacity:0.8';
                        if (svgOrEmoji.trim().startsWith('<svg')) {
                            iconEl.innerHTML = svgOrEmoji;
                            iconEl.querySelector('svg')?.setAttribute('width', '14');
                            iconEl.querySelector('svg')?.setAttribute('height', '14');
                        } else {
                            iconEl.style.fontSize = '14px';
                            iconEl.textContent = svgOrEmoji;
                        }
                        const labelEl = document.createElement('span');
                        labelEl.textContent = label;
                        item.appendChild(iconEl); item.appendChild(labelEl);
                        item.addEventListener('click', () => { menu.remove(); _dialogOpenGlobal = false; onClick(); });
                        return item;
                    };

                    const CTX_FAV_ON  = `<svg viewBox="0 0 16 16" fill="currentColor"  stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13.5S1.5 9.5 1.5 5.5A3.5 3.5 0 0 1 8 3.207 3.5 3.5 0 0 1 14.5 5.5C14.5 9.5 8 13.5 8 13.5z"/></svg>`;
                    const CTX_FAV_OFF = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13.5S1.5 9.5 1.5 5.5A3.5 3.5 0 0 1 8 3.207 3.5 3.5 0 0 1 14.5 5.5C14.5 9.5 8 13.5 8 13.5z"/></svg>`;
                    const CTX_COPY    = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="5" width="9" height="10" rx="1.5"/><path d="M3 11H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v1"/></svg>`;
                    const CTX_OPEN    = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M10 2h4v4"/><path d="M7 9L14 2"/><path d="M12 10v4H2V4h4"/></svg>`;
                    const CTX_DEL     = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><polyline points="2,4 4,4 14,4"/><path d="M13 4l-.9 9H3.9L3 4"/><path d="M6.5 7v4M9.5 7v4"/><path d="M5.5 4V2.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5V4"/></svg>`;

                    menu.appendChild(mkItem(CTX_COPY, 'Copy media URL(s)', () => {
                        const _dom = GM_getValue(KEY_CLICK_MODE_CUSTOM, false)
                            ? GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com') : 'x.com';
                        const _toUrl = u => {
                            try {
                                const o = new URL(u);
                                if (o.hostname.includes('twimg.com') || u.includes('.mp4')) return u;
                                o.hostname = _dom;
                                return o.toString();
                            }
                            catch(_) { return u; }
                        };
                        const urls = (rec.mediaUrls || []).length
                            ? rec.mediaUrls.map(_toUrl).join('\n')
                            : _toUrl(rec.tweetUrl);
                        GM_setClipboard(urls);
                    }));
                    const CTX_PREFIX = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="4" width="10" height="8" rx="1.2"/><path d="M11 6h2a2 2 0 0 1 0 4h-2"/></svg>`;
                    menu.appendChild(mkItem(CTX_PREFIX, 'Copy with prefix', () => {
                        GM_setClipboard(_buildPrefixedTweetUrl(rec.tweetUrl));
                    }));
                    const isFav = !!rec.favorited;
                    menu.appendChild(mkItem(isFav ? CTX_FAV_ON : CTX_FAV_OFF, isFav ? 'Unfavorite' : 'Favorite', () => {
                        const updated = _updateRecord(rec.id, r => { r.favorited = !r.favorited; });
                        if (!updated) return;
                        rec.favorited = updated.favorited;
                        render();
                    }));
                    menu.appendChild(mkItem(CTX_OPEN, 'Open in new tab', () => {
                        window.open(rec.tweetUrl, '_blank');
                    }));
                    const CTX_VIEW = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><rect x="1" y="3" width="14" height="10" rx="1.5"/><circle cx="5.5" cy="7.5" r="1.5"/><path d="M9 10l2-2.5 2.5 2.5"/></svg>`;
                    menu.appendChild(mkItem(CTX_VIEW, 'View media', () => {
                        _openHistMediaLightbox(rec, 0, null);
                        _injectHistCrossNav(rec);
                    }));
                    const CTX_DLOAD = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M8 2v8"/><path d="M5 7l3 3 3-3"/><path d="M2 13h12"/></svg>`;
                    const hasMedia = rec.mediaUrls && rec.mediaUrls.length > 0;
                    menu.appendChild(mkItem(CTX_DLOAD, hasMedia ? 'Re-download' : 'Open tweet to re-download', () => {
                        if (!hasMedia) { window.open(rec.tweetUrl, '_blank'); return; }
                        const safeDisplay = sanitizeForFilename(rec.displayName || rec.screenName || 'unknown');
                        const safeScreen  = sanitizeForFilename(rec.screenName  || 'unknown');
                        const datePart    = rec.tweetDate || rec.downloadDate || 'unknown';
                        const textPart    = rec.text ? `_${sanitizeForFilename(rec.text, 40)}` : '';
                        const safeTextForToken = sanitizeForFilename(rec.text || '', CUSTOM_FN_TEXT_MAXLEN);
                        const idPart      = rec.tweetId || rec.id;
                        rec.mediaUrls.forEach((url, i) => {
                            let ext = 'jpg';
                            if (url.includes('.mp4')) {
                                ext = 'mp4';
                            } else {
                                try {
                                    const fmtParam = new URL(url).searchParams.get('format');
                                    ext = fmtParam || url.match(/\.(\w{3,4})(?:\?|$)/)?.[1] || 'jpg';
                                } catch (_) {}
                            }
                            const fname = buildCustomFilename(
                                { screenName: safeScreen, displayName: safeDisplay, date: datePart, id: idPart, index: i + 1, ext, text: safeTextForToken, origName: _extractOrigMediaName(url) },
                                () => `[twitter] ${safeDisplay}(@${safeScreen})_${datePart}${textPart}_${idPart}_${i + 1}.${ext}`
                            );
                            setTimeout(() => forceDownloadBlob(url, fname), i * 600);
                        });
                    }));
                    const sep = document.createElement('div');
                    sep.style.cssText = 'height:0.5px;background:rgba(255,255,255,.1);margin:3px 8px';
                    menu.appendChild(sep);
                    menu.appendChild(mkItem(CTX_DEL, 'Delete', () => {
                        _deleteOne(rec.id, records.indexOf(rec), cell);
                    }, true));

                    document.body.appendChild(menu);
                    requestAnimationFrame(() => {
                        const r = menu.getBoundingClientRect();
                        if (r.right  > window.innerWidth)  menu.style.left = (e.clientX - r.width)  + 'px';
                        if (r.bottom > window.innerHeight)  menu.style.top  = (e.clientY - r.height) + 'px';
                    });
                    const close = ev => {
                        if (!menu.contains(ev.target)) {
                            menu.remove();
                            _dialogOpenGlobal = false;
                            document.removeEventListener('mousedown', close, true);
                        }
                    };
                    document.addEventListener('mousedown', close, true);
                });
                frag.appendChild(cell);
            });

            grid.appendChild(frag);
            body.appendChild(grid);
        }

        function _renderEmpty() {
            const query = getQuery();
            const em = document.createElement('div');
            em.className = 'tm-hist-empty';
            em.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`;
            const msg = document.createElement('div');
            msg.textContent = query ? 'No results matching search.' : 'No download history yet.\nRight-click 🎞️ to download & record.';
            msg.style.whiteSpace = 'pre-line';
            em.appendChild(msg);
            body.appendChild(em);
        }

        function _toggleTextRowSel(id, row, cb) {
            const allRecs = getFiltered(getRecords());
            const target = allRecs.find(r => r.id === id);
            if (target && target.favorited) return;
            if (selectedIds.has(id)) {
                selectedIds.delete(id);
                row.classList.remove('selected');
                if (cb) cb.checked = false;
            } else {
                selectedIds.add(id);
                row.classList.add('selected');
                if (cb) cb.checked = true;
            }
            delSelBtn.textContent = `Delete selected (${selectedIds.size})`;
        }

        function _handleCheckbox(id, idx, shiftKey) {
            const allRecords = getFiltered(getRecords());
            const target = allRecords.find(r => r.id === id);
            if (target && target.favorited) return;
            if (shiftKey && getAnchorIdx() >= 0) {
                const lo = Math.min(getAnchorIdx(), idx);
                const hi = Math.max(getAnchorIdx(), idx);
                for (let i = lo; i <= hi; i++) {
                    if (allRecords[i] && !allRecords[i].favorited) selectedIds.add(allRecords[i].id);
                }
            } else {
                if (selectedIds.has(id)) selectedIds.delete(id);
                else { selectedIds.add(id); setAnchorIdx(idx); }
            }
            render();
        }

        function _deleteOne(id, idx, hintEl = null) {
            let records = getRecords();
            const record = records.find(r => r.id === id);
            if (!record) return;
            if (record.favorited) {
                const favBtn = hintEl?.classList?.contains('tm-grid-fav-btn') ? hintEl
                    : hintEl?.classList?.contains('tm-hist-act-btn') ? hintEl
                    : hintEl?.querySelector?.('.tm-grid-fav-btn')
                    ?? null;
                if (favBtn) {
                    favBtn.classList.remove('tm-fav-shake', 'tm-grid-fav-shake');
                    void favBtn.offsetWidth;
                    const shakeClass = favBtn.classList.contains('tm-grid-fav-btn')
                        ? 'tm-grid-fav-shake' : 'tm-fav-shake';
                    favBtn.classList.add(shakeClass);
                    favBtn.addEventListener('animationend', () => {
                        favBtn.classList.remove(shakeClass);
                    }, { once: true });
                }
                return;
            }
            const deleted = _deleteRecordById(id);
            if (!deleted) return;
            _downloadedIds.delete(deleted.record.tweetId);

            if (_historyUndoTimer) clearTimeout(_historyUndoTimer);
            _historyUndoBuffer = { record: deleted.record, index: idx };
            _invalidateRecordsCache();
            render();

            const ut = document.createElement('div');
            ut.style.cssText = `
                position:fixed; bottom:24px; left:50%; transform:translateX(-50%);
                background:rgba(15,20,25,0.92); color:#fff; padding:8px 16px;
                border-radius:99px; font-size:12px; font-family:system-ui;
                display:flex; align-items:center; gap:10px;
                z-index:9999999; box-shadow:0 4px 16px rgba(0,0,0,0.3);
                animation:tm-toast-rise 5s forwards;
            `;
            ut.id = 'tm-hist-undo-toast';
            const msg = document.createElement('span');
            msg.textContent = 'Record deleted';
            const undoBtn = document.createElement('button');
            undoBtn.textContent = 'Undo';
            undoBtn.style.cssText = `background:none;border:none;color:#1d9bf0;cursor:pointer;font-weight:700;font-size:12px;padding:0;`;
            undoBtn.addEventListener('click', () => {
                if (_historyUndoBuffer) {
                    _insertRecordUndo(_historyUndoBuffer.record);
                    _downloadedIds.add(_historyUndoBuffer.record.tweetId);
                    _historyUndoBuffer = null;
                    ut.remove();
                    clearTimeout(_historyUndoTimer);
                    _invalidateRecordsCache();
                    render();
                }
            });
            ut.appendChild(msg);
            ut.appendChild(undoBtn);
            document.getElementById('tm-hist-undo-toast')?.remove();
            document.body.appendChild(ut);
            _historyUndoTimer = setTimeout(() => { ut.remove(); _historyUndoBuffer = null; }, 5000);
        }

        function _thumbUrl(url) {
            try {
                if (url.includes('pbs.twimg.com') && url.includes('/media/')) {
                    const u = new URL(url);
                    u.searchParams.set('name', 'small');
                    return u.toString();
                }
            } catch (_) {}
            return url;
        }

        function _showZoom(url, e) {
            let z = document.getElementById('tm-hist-zoom');
            if (!z) { z = document.createElement('div'); z.id = 'tm-hist-zoom'; document.body.appendChild(z); }
            z.innerHTML = '';
            const img = document.createElement('img');
            img.src = _thumbUrl(url).replace('name=small', 'name=medium');
            img.alt = '';
            z.appendChild(img);
            _moveZoom(e);
        }
        let _zoomRafId = null;
        function _moveZoom(e) {
            if (_zoomRafId) return;
            const cx = e.clientX, cy = e.clientY;
            _zoomRafId = requestAnimationFrame(() => {
                _zoomRafId = null;
                const z = document.getElementById('tm-hist-zoom');
                if (!z) return;
                const w = 200, h = 200, margin = 14;
                let left = cx - w - margin;
                let top  = cy - h / 2;
                if (left < 4) left = cx + margin;
                if (top < 4)  top  = 4;
                if (top + h > window.innerHeight - 4) top = window.innerHeight - h - 4;
                z.style.cssText = `position:fixed;z-index:9999999;width:${w}px;height:${h}px;left:${left}px;top:${top}px;border-radius:8px;overflow:hidden;pointer-events:none;box-shadow:0 8px 24px rgba(0,0,0,0.4);border:2px solid rgba(255,255,255,0.2);`;
            });
        }
        function _hideZoom() {
            if (_zoomRafId) { cancelAnimationFrame(_zoomRafId); _zoomRafId = null; }
            document.getElementById('tm-hist-zoom')?.remove();
        }

        return {
            renderList, renderThumb, renderEmpty: _renderEmpty,
            handleCheckbox: _handleCheckbox, toggleTextRowSel: _toggleTextRowSel,
            deleteOne: _deleteOne, thumbUrl: _thumbUrl,
            showZoom: _showZoom, hideZoom: _hideZoom, moveZoom: _moveZoom,
        };
    }

    function _createDockController(deps) {
        const { panel, render, getPinned } = deps;

        function _savePos() {
            const r = panel.getBoundingClientRect();
            const inDockedPeek = DockState.side && DockState.peeked;
            const remember = GM_getValue(KEY_DOCK_REMEMBER_POS, false);
            let pos = { x: r.left, y: r.top, w: r.width, h: r.height };
            if (inDockedPeek && !remember) {
                try {
                    const old = JSON.parse(GM_getValue(KEY_HISTORY_PANEL_POS, 'null'));
                    if (old) { pos.x = old.x; pos.y = old.y; }
                } catch (_) {}
            }
            GM_setValue(KEY_HISTORY_PANEL_POS, JSON.stringify(pos));
            if (inDockedPeek) {
                if (remember) {
                    DockState.snapshot = { left: r.left, top: r.top, width: r.width, height: r.height };
                } else if (DockState.snapshot) {
                    DockState.snapshot.width  = r.width;
                    DockState.snapshot.height = r.height;
                }
            }
        }

        function _dockTabGeometry() {
            const snap = DockState.snapshot;
            if (!snap) {
                const r = panel.getBoundingClientRect();
                const h   = Math.min(r.height * 0.55, 200);
                const top = r.top + (r.height - h) / 2;
                return { h, top };
            }
            const h   = Math.min(snap.height * 0.55, 200);
            const top = snap.top + (snap.height - h) / 2;
            return { h, top };
        }

        function _buildDockTab(side) {
            const style = GM_getValue(KEY_DOCK_STYLE, 'ruler');
            const { h, top } = _dockTabGeometry();

            const triggerKey  = side === 'left' ? KEY_DOCK_TRIGGER_L : KEY_DOCK_TRIGGER_R;
            const triggerDist = Math.max(6, parseInt(GM_getValue(triggerKey, '80'), 10) || 80);

            const tab = document.createElement('div');
            tab.id = 'tm-hist-dock-tab';
            tab.className = 'style-' + style + ' side-' + side;
            tab.style.cssText = [
                'top:'    + top + 'px',
                'height:' + h   + 'px',
                side === 'left' ? 'left:0' : 'right:0',
            ].join(';');
            if (style === 'dots') {
                for (let i = 0; i < 3; i++) {
                    const d = document.createElement('span');
                    d.className = 'tm-dock-dot';
                    tab.appendChild(d);
                }
            }

            const hotzone = document.createElement('div');
            hotzone.className = 'tm-dock-hotzone';
            hotzone.style.cssText = [
                'position:absolute',
                'top:0',
                'height:100%',
                'width:' + triggerDist + 'px',
                side === 'left' ? 'left:0' : 'right:0',
                'cursor:pointer',
                'z-index:1',
            ].join(';');

            const _onHotEnter = () => {
                clearTimeout(DockState.hoverTimer);
                const delay = parseInt(GM_getValue(KEY_DOCK_HOVER_DELAY, '500'), 10) || 500;
                DockState.hoverTimer = setTimeout(() => _peekOut(), delay);
            };
            const _onHotLeave = () => {
                clearTimeout(DockState.hoverTimer);
                DockState.hoverTimer = null;
            };

            hotzone.addEventListener('mouseenter', _onHotEnter);
            hotzone.addEventListener('mouseleave', _onHotLeave);
            tab.addEventListener('mouseenter', _onHotEnter);
            tab.addEventListener('mouseleave', _onHotLeave);

            let _tabHoldTimer   = null;
            let _tabHoldRAF     = null;
            let _tabHoldElapsed = 0;
            const TAB_HOLD_MS   = 2000;

            const _tabTick = () => {
                _tabHoldElapsed += 16;
                const pct = Math.min(_tabHoldElapsed / TAB_HOLD_MS, 1);
                tab.style.opacity = (0.4 + pct * 0.6).toFixed(2);
                if (pct < 1) _tabHoldRAF = requestAnimationFrame(_tabTick);
            };

            const _cancelTabHold = () => {
                if (_tabHoldTimer) { clearTimeout(_tabHoldTimer); _tabHoldTimer = null; }
                if (_tabHoldRAF)   { cancelAnimationFrame(_tabHoldRAF); _tabHoldRAF = null; }
                _tabHoldElapsed = 0;
                tab.style.opacity = '';
            };

            tab.addEventListener('mousedown', (e) => {
                if (e.button !== 0) return;
                e.stopPropagation();
                _tabHoldElapsed = 0;
                _tabHoldTimer = setTimeout(() => {
                    cancelAnimationFrame(_tabHoldRAF);
                    _tabHoldRAF  = null;
                    _tabHoldTimer = null;
                    tab.style.opacity = '';
                    _forceResetDock();
                }, TAB_HOLD_MS);
                _tabHoldRAF = requestAnimationFrame(_tabTick);
            });
            tab.addEventListener('mouseup',    _cancelTabHold);
            tab.addEventListener('mouseleave',  _cancelTabHold);

            tab.appendChild(hotzone);
            return tab;
        }

        function _dock(side) {
            if (DockState.side) return;
            DockState.peeked = false;

            const r   = panel.getBoundingClientRect();
            const vpW = window.innerWidth;

            DockState.snapshot = { left: r.left, top: r.top, width: r.width, height: r.height };
            DockState.side = side;

            GM_setValue(KEY_DOCK_PERSISTED, side);

            const PEEK = 6;
            const OFFSET_LEFT  =  15;
            const OFFSET_RIGHT = -15;

            const peekLeft = side === 'left'
                ? OFFSET_LEFT
                : (vpW - r.width + OFFSET_RIGHT);
            panel.style.left = peekLeft + 'px';

            panel.style.transform = `translateX(${r.left - peekLeft}px)`;
            panel.offsetWidth;

            panel.classList.add('tm-animating');
            panel.addEventListener('transitionend', function _rm(e) {
                if (e.propertyName !== 'transform') return;
                panel.classList.remove('tm-animating');
                panel.removeEventListener('transitionend', _rm);
            });

            const offX = side === 'left'
                ? -(OFFSET_LEFT  + r.width - PEEK)
                :  (r.width      - PEEK - OFFSET_RIGHT);
            panel.style.transform = `translateX(${offX}px)`;
            panel.classList.add('tm-docked');

            if (DockState.tabEl) DockState.tabEl.remove();
            DockState.tabEl = _buildDockTab(side);
            document.body.appendChild(DockState.tabEl);
        }

        function _peekOut() {
            if (!DockState.side || DockState.peeked) return;
            DockState.peeked = true;

            clearTimeout(DockState.retractTimer);
            DockState.retractTimer = null;

            const snap = DockState.snapshot;
            const vpH  = window.innerHeight;

            if (snap) {
                const safeTop = Math.max(0, Math.min(snap.top, vpH - snap.height - 10));
                panel.style.top = safeTop + 'px';
            }

            panel.classList.add('tm-animating');
            panel.addEventListener('transitionend', function _rm(e) {
                if (e.propertyName !== 'transform') return;
                panel.classList.remove('tm-animating');
                panel.removeEventListener('transitionend', _rm);
            });

            panel.style.transform = 'translateX(0)';
            panel.classList.remove('tm-docked');

            if (DockState.tabEl) DockState.tabEl.style.pointerEvents = 'none';

            let bridge = document.getElementById('tm-hover-bridge');
            if (!bridge) {
                bridge = document.createElement('div');
                bridge.id = 'tm-hover-bridge';
                bridge.style.cssText = 'position:absolute; top:0; bottom:0; width:15px; background:transparent; z-index:-1;';
                panel.appendChild(bridge);
            }
            if (DockState.side === 'left') {
                bridge.style.left = '-10px';
                bridge.style.right = '';
            } else {
                bridge.style.right = '-10px';
                bridge.style.left = '';
            }

            const body = document.getElementById('tm-hist-body');
            if (body && !body.hasChildNodes()) render();
        }

        function _retract() {
            if (!DockState.side || !DockState.peeked) return;
            const _pinned = getPinned();
            if (_pinned) return;
            DockState.peeked = false;

            const PEEK         = 6;
            const OFFSET_LEFT  =  15;
            const OFFSET_RIGHT = -15;
            const pw = panel.offsetWidth;
            const offX = DockState.side === 'left'
                ? -(OFFSET_LEFT  + pw - PEEK)
                :  (pw           - PEEK - OFFSET_RIGHT);

            panel.classList.add('tm-animating');
            panel.addEventListener('transitionend', function _rm(e) {
                if (e.propertyName !== 'transform') return;
                panel.classList.remove('tm-animating');
                panel.removeEventListener('transitionend', _rm);
            });

            panel.style.transform = `translateX(${offX}px)`;
            panel.classList.add('tm-docked');

            if (DockState.tabEl) DockState.tabEl.style.pointerEvents = '';
        }

        function _exitDockMode() {
            clearTimeout(DockState.hoverTimer);
            clearTimeout(DockState.retractTimer);
            DockState.hoverTimer   = null;
            DockState.retractTimer = null;

            const snap = DockState.snapshot;
            if (snap) {
                const safeLeft = Math.max(0, Math.min(snap.left, window.innerWidth  - snap.width));
                const safeTop  = Math.max(0, Math.min(snap.top,  window.innerHeight - 60));
                panel.style.transition = 'none';
                panel.style.left = safeLeft + 'px';
                panel.style.top  = safeTop  + 'px';
                panel.style.transform = '';
                panel.offsetWidth;
                panel.style.transition = '';
            }
            panel.classList.remove('tm-docked');
            DockState.side     = null;
            DockState.peeked   = false;
            DockState.snapshot = null;

            GM_setValue(KEY_DOCK_PERSISTED, '');

            if (DockState.tabEl) {
                DockState.tabEl.remove();
                DockState.tabEl = null;
            }
        }

        function _forceResetDock() {
            clearTimeout(DockState.hoverTimer);
            clearTimeout(DockState.retractTimer);
            DockState.hoverTimer   = null;
            DockState.retractTimer = null;
            DockState.side         = null;
            DockState.peeked       = false;
            DockState.snapshot     = null;
            if (DockState.tabEl) { DockState.tabEl.remove(); DockState.tabEl = null; }
            GM_setValue(KEY_DOCK_PERSISTED, '');

            panel.classList.remove('tm-docked');
            const pw = panel.offsetWidth  || 390;
            const ph = panel.offsetHeight || 540;
            panel.style.transition = 'none';
            panel.style.left = Math.round((window.innerWidth  - pw) / 2) + 'px';
            panel.style.top  = Math.round((window.innerHeight - ph) / 4) + 'px';
            panel.style.transform = '';
            panel.offsetWidth;
            panel.style.transition = '';
            render();
            showToast('🔓 Dock reset — panel restored');
        }

        return {
            savePos: _savePos,
            dockTabGeometry: _dockTabGeometry,
            buildDockTab: _buildDockTab,
            dock: _dock,
            peekOut: _peekOut,
            retract: _retract,
            exitDockMode: _exitDockMode,
            forceResetDock: _forceResetDock,
        };
    }

    function _createSearchHistoryController(deps) {
        const { render, getSearchInput, getSearchDrop, setQuery } = deps;
        let _searchDropVisible = false;

        const _SRCH_HIST_MAX = 30;
        function _loadSearchHist() {
            try { return JSON.parse(GM_getValue(KEY_SEARCH_HISTORY, '[]')); } catch (_) { return []; }
        }
        function _saveSearchHist(h) { GM_setValue(KEY_SEARCH_HISTORY, JSON.stringify(h)); }
        function _addSearchHist(text) {
            if (!text || text.length < 2) return;
            let h = _loadSearchHist().filter(e => e.text.toLowerCase() !== text.toLowerCase());
            h.unshift({ text, pinned: false, ts: Date.now() });
            const pinned   = h.filter(e => e.pinned);
            const unpinned = h.filter(e => !e.pinned).slice(0, _SRCH_HIST_MAX);
            _saveSearchHist([...pinned, ...unpinned]);
        }
        function _getSortedHist() {
            const h = _loadSearchHist();
            return [
                ...h.filter(e => e.pinned).sort((a, b) => a.ts - b.ts),
                ...h.filter(e => !e.pinned).sort((a, b) => b.ts - a.ts),
            ];
        }
        const _SPIN_HOLLOW = `<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="1.8" width="6" height="5.6" rx="1"/><line x1="8" y1="7.4" x2="8" y2="10.8"/><path d="M5.7 10.8h4.6l-2.3 3.4z"/></svg>`;
        const _SPIN_FILL   = `<svg viewBox="0 0 16 16" width="13" height="13" fill="currentColor" stroke="currentColor" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="1.8" width="6" height="5.6" rx="1"/><path d="M8 7.4v3.4" stroke-width="1.8" fill="none"/><path d="M5.7 10.8h4.6l-2.3 3.4z"/></svg>`;
        function _buildSearchHistRow(item) {
            const row = document.createElement('div');
            row.className = 'tm-search-hist-item';
            row.dataset.histKey = item.text;
            const pinBtn = document.createElement('button');
            pinBtn.className = 'tm-search-pin-btn' + (item.pinned ? ' pinned' : '');
            pinBtn.title = item.pinned ? 'Unpin' : 'Pin to top';
            pinBtn.innerHTML = item.pinned ? _SPIN_FILL : _SPIN_HOLLOW;
            pinBtn.addEventListener('click', e => {
                e.stopPropagation();
                const h2 = _loadSearchHist();
                const entry = h2.find(e2 => e2.text === item.text);
                if (entry) {
                    entry.pinned = !entry.pinned;
                    if (entry.pinned) entry.ts = Date.now();
                    _saveSearchHist(h2);
                }
                _renderSearchDrop();
            });
            const textEl = document.createElement('span');
            textEl.className = 'tm-search-hist-text';
            textEl.textContent = item.text;
            const delBtn = document.createElement('button');
            delBtn.className = 'tm-search-del-btn';
            delBtn.title = 'Remove';
            delBtn.textContent = '✕';
            delBtn.addEventListener('click', e => {
                e.stopPropagation();
                const remaining = _loadSearchHist().filter(e2 => e2.text !== item.text);
                _saveSearchHist(remaining);
                _renderSearchDrop();
            });
            row.addEventListener('mousedown', e => {
                if (pinBtn.contains(e.target) || delBtn.contains(e.target)) return;
                e.preventDefault();
                getSearchInput().value = item.text;
                setQuery(item.text); render();
                _hideSearchDrop();
            });
            row.appendChild(pinBtn); row.appendChild(textEl); row.appendChild(delBtn);
            return row;
        }
        function _renderSearchDrop() {
            const _searchDrop = getSearchDrop();
            if (!_searchDrop) return;
            const hist = _getSortedHist();
            if (!hist.length) { _hideSearchDrop(); return; }

            const existingMap = new Map();
            _searchDrop.querySelectorAll('.tm-search-hist-item[data-hist-key]')
                .forEach(el => existingMap.set(el.dataset.histKey, el));

            const fragment = document.createDocumentFragment();
            hist.forEach(item => {
                let row = existingMap.get(item.text);
                existingMap.delete(item.text);
                if (!row) {
                    row = _buildSearchHistRow(item);
                } else {
                    const pinBtn = row.querySelector('.tm-search-pin-btn');
                    if (pinBtn) {
                        pinBtn.className = 'tm-search-pin-btn' + (item.pinned ? ' pinned' : '');
                        pinBtn.title     = item.pinned ? 'Unpin' : 'Pin to top';
                        pinBtn.innerHTML = item.pinned ? _SPIN_FILL : _SPIN_HOLLOW;
                    }
                }
                fragment.appendChild(row);
            });
            existingMap.forEach(el => el.remove());
            const unpinCnt = hist.filter(e => !e.pinned).length;
            if (unpinCnt > 0) {
                const footer = document.createElement('div');
                footer.className = 'tm-search-drop-footer';
                const clearBtn = document.createElement('button');
                clearBtn.className = 'tm-search-clear-all';
                clearBtn.textContent = `Clear history (${unpinCnt})`;
                clearBtn.addEventListener('mousedown', e => {
                    e.preventDefault();
                    _saveSearchHist(_loadSearchHist().filter(e2 => e2.pinned));
                    _renderSearchDrop();
                });
                footer.appendChild(clearBtn);
                fragment.appendChild(footer);
            }
            _searchDrop.appendChild(fragment);
        }
        function _showSearchDrop() {
            const _searchDrop = getSearchDrop();
            if (_searchDropVisible || !_getSortedHist().length) return;
            _searchDropVisible = true;
            _searchDrop.classList.remove('closing');
            _searchDrop.style.display = 'block';
            requestAnimationFrame(() => _searchDrop.style.transform = '');
            _renderSearchDrop();
        }
        function _hideSearchDrop() {
            const _searchDrop = getSearchDrop();
            if (!_searchDrop || !_searchDropVisible) return;
            _searchDropVisible = false;
            _searchDrop.classList.add('closing');
            const onEnd = () => {
                if (_searchDropVisible) return;
                _searchDrop.classList.remove('closing');
                _searchDrop.style.display = 'none';
            };
            _searchDrop.addEventListener('transitionend', onEnd, { once: true });
        }

        return {
            load: _loadSearchHist,
            save: _saveSearchHist,
            add: _addSearchHist,
            getSorted: _getSortedHist,
            buildRow: _buildSearchHistRow,
            renderDrop: _renderSearchDrop,
            show: _showSearchDrop,
            hide: _hideSearchDrop,
        };
    }

    function _createTextRenderer(deps) {
        const {
            dark, C, selectedIds, body, render,
            _textPinnedAuthors, _savePinnedAuthors, _textAuthorCollapsed, _textAuthorAutoCollapsedApplied,
            getEditMode, getRenderEmpty, getToggleTextRowSel, getDeleteOne,
            getScrollTarget, setScrollTarget, setViewMode,
        } = deps;

        function _getTextBmContentColor() {
            try {
                const cfg = JSON.parse(GM_getValue(KEY_TEXT_BM_CFG, '{}'));
                return cfg.textContentColor || '#ffd700';
            } catch(_) { return '#ffd700'; }
        }

        function _showTextPreview(rec) {
            const existing = document.getElementById('tm-text-preview-overlay');
            if (existing) existing.remove();

            _dialogOpenGlobal = true;

            const _closePreview = () => {
                overlay.remove();
                _dialogOpenGlobal = false;
            };

            const overlay = document.createElement('div');
            overlay.id = 'tm-text-preview-overlay';
            overlay.style.cssText = `
                position:fixed;inset:0;z-index:99999999;
                background:rgba(0,0,0,.72);display:flex;align-items:center;justify-content:center;
                backdrop-filter:blur(4px);cursor:pointer;
            `;

            const card = document.createElement('div');
            card.style.cssText = `
                background:#192734;border-radius:14px;padding:24px 28px;
                max-width:520px;width:calc(100vw - 48px);max-height:80vh;
                overflow-y:auto;cursor:default;
                border:1px solid rgba(255,255,255,.12);
                box-shadow:0 24px 64px rgba(0,0,0,.7);
            `;
            card.addEventListener('click', e => e.stopPropagation());

            const authorRow = document.createElement('div');
            authorRow.style.cssText = 'display:flex;align-items:center;gap:10px;margin-bottom:14px';
            if (rec.avatarUrl) {
                const av = document.createElement('img');
                av.src = rec.avatarUrl; av.alt = ''; av.loading = 'lazy';
                av.style.cssText = 'width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0';
                authorRow.appendChild(av);
            }
            const authorMeta = document.createElement('div');
            authorMeta.style.cssText = 'display:flex;flex-direction:column;gap:2px;min-width:0';
            const dname = document.createElement('div');
            dname.textContent = rec.displayName || rec.screenName;
            dname.style.cssText = 'font-size:14px;font-weight:700;color:#e7e9ea;white-space:nowrap;overflow:hidden;text-overflow:ellipsis';
            const sname = document.createElement('div');
            sname.textContent = '@' + (rec.screenName || '');
            sname.style.cssText = 'font-size:12px;color:rgba(255,255,255,.45)';
            authorMeta.appendChild(dname); authorMeta.appendChild(sname);
            authorRow.appendChild(authorMeta);
            card.appendChild(authorRow);

            const hr = document.createElement('div');
            hr.style.cssText = 'border-top:1px solid rgba(255,255,255,.1);margin-bottom:16px';
            card.appendChild(hr);

            const textEl = document.createElement('div');
            textEl.textContent = rec.text || '(no text)';
            textEl.style.cssText = `
                font-size:18px;line-height:1.7;color:${_getTextBmContentColor()};
                white-space:pre-wrap;word-break:break-word;margin-bottom:16px;
            `;
            card.appendChild(textEl);

            const meta = document.createElement('div');
            meta.style.cssText = 'display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:4px';
            if (rec.downloadDate) {
                const d = document.createElement('span');
                d.textContent = rec.downloadDate;
                d.style.cssText = 'font-size:12px;color:rgba(255,255,255,.35)';
                meta.appendChild(d);
            }
            if (rec.tweetUrl) {
                const link = document.createElement('a');
                link.href = rec.tweetUrl; link.target = '_blank'; link.rel = 'noopener noreferrer';
                link.textContent = 'Open tweet ↗';
                link.style.cssText = 'font-size:12px;color:#1d9bf0;text-decoration:none';
                link.addEventListener('click', e => e.stopPropagation());
                meta.appendChild(link);
            }
            card.appendChild(meta);

            const closeBtn = document.createElement('button');
            closeBtn.textContent = '✕';
            closeBtn.style.cssText = `
                position:absolute;top:14px;right:14px;
                background:rgba(255,255,255,.12);border:none;color:rgba(255,255,255,.7);
                width:28px;height:28px;border-radius:50%;cursor:pointer;
                font-size:13px;display:flex;align-items:center;justify-content:center;
                transition:background .12s;
            `;
            closeBtn.addEventListener('mouseover', () => closeBtn.style.background = 'rgba(255,255,255,.22)');
            closeBtn.addEventListener('mouseout',  () => closeBtn.style.background = 'rgba(255,255,255,.12)');
            closeBtn.addEventListener('click', () => _closePreview());
            card.style.position = 'relative';
            card.appendChild(closeBtn);

            overlay.appendChild(card);
            overlay.addEventListener('click', () => _closePreview());
            const _onEsc = (e) => { if (e.key === 'Escape') { _closePreview(); document.removeEventListener('keydown', _onEsc); } };
            document.addEventListener('keydown', _onEsc);
            document.body.appendChild(overlay);
        }

        function renderTextAuthorList(records) {
            const editMode = getEditMode();
            const _renderEmpty = getRenderEmpty();
            if (!records.length) { _renderEmpty(); return; }
            const frag = document.createDocumentFragment();

            const authorMap = new Map();
            records.forEach(r => {
                const key = r.screenName || '';
                if (!authorMap.has(key)) {
                    authorMap.set(key, {
                        recs: [], maxTs: 0,
                        displayName: r.displayName || key,
                        avatarUrl: r.avatarUrl || '',
                        screenName: key,
                    });
                }
                const entry = authorMap.get(key);
                entry.recs.push(r);
                if ((r.ts || 0) > entry.maxTs) entry.maxTs = r.ts || 0;
            });

            const authors = Array.from(authorMap.values())
                .sort((a, b) => b.maxTs - a.maxTs);

            const SVG_HOME = `<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 7L8 2l6 5"/><path d="M4 7v6h3v-3h2v3h3V7"/></svg>`;
            const SVG_JUMP = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M10 2h4v4"/><path d="M7 9L14 2"/><path d="M12 10v4H2V4h4"/></svg>`;
            const SVG_DEL  = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><polyline points="2,4 4,4 14,4"/><path d="M13 4l-.9 9H3.9L3 4"/><path d="M6.5 7v4M9.5 7v4"/><path d="M5.5 4V2.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5V4"/></svg>`;
            const SVG_HEART_EMPTY = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13.5S1.5 9.5 1.5 5.5A3.5 3.5 0 0 1 8 3.207 3.5 3.5 0 0 1 14.5 5.5C14.5 9.5 8 13.5 8 13.5z"/></svg>`;
            const SVG_HEART_FULL  = `<svg viewBox="0 0 16 16" fill="currentColor" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 13.5S1.5 9.5 1.5 5.5A3.5 3.5 0 0 1 8 3.207 3.5 3.5 0 0 1 14.5 5.5C14.5 9.5 8 13.5 8 13.5z"/></svg>`;
            const SVG_CHEV_DOWN   = `<svg viewBox="0 0 10 10" width="10" height="10" fill="currentColor"><path d="M1 3l4 4 4-4z"/></svg>`;
            const SVG_CHEV_RIGHT  = `<svg viewBox="0 0 10 10" width="10" height="10" fill="currentColor"><path d="M3 1l4 4-4 4z"/></svg>`;

            const textColor = _getTextBmContentColor();
            let globalItemIdx = 0;

            const _TEXT_AUTOCOLLAPSE_THRESHOLD = 30;

            authors.forEach(({ screenName, displayName, avatarUrl, recs }) => {
                if (!_textAuthorAutoCollapsedApplied.has(screenName)) {
                    _textAuthorAutoCollapsedApplied.add(screenName);
                    if (recs.length > _TEXT_AUTOCOLLAPSE_THRESHOLD) {
                        _textAuthorCollapsed.add(screenName);
                    }
                }
                const isCollapsed = _textAuthorCollapsed.has(screenName);

                const header = document.createElement('div');
                header.className = 'tm-hist-group-header';
                header.dataset.authorHandle = screenName;
                const _hdrBgIdle = dark ? 'rgba(255,255,255,.04)' : 'rgba(15,20,25,.035)';
                header.style.cssText = `display:flex;align-items:center;gap:8px;padding:8px 10px 8px 8px;cursor:pointer;border-radius:8px;margin:2px 0;background:${_hdrBgIdle};transition:background .12s`;
                header.addEventListener('mouseover', () => header.style.background = 'rgba(29,155,240,.1)');
                header.addEventListener('mouseout',  () => header.style.background = _hdrBgIdle);

                if (avatarUrl) {
                    const av = document.createElement('img');
                    av.src = avatarUrl; av.alt = ''; av.loading = 'lazy';
                    av.style.cssText = `width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0;border:2px solid ${dark ? 'rgba(255,255,255,.12)' : 'rgba(15,20,25,.14)'}`;
                    av.decode && av.decode().catch(() => {});
                    header.appendChild(av);
                } else {
                    const avFb = document.createElement('div');
                    avFb.style.cssText = `width:40px;height:40px;border-radius:50%;background:rgba(29,155,240,.25);flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:18px;color:${dark ? 'rgba(255,255,255,.5)' : '#ffffff'};flex-shrink:0`;
                    avFb.textContent = (displayName || screenName).charAt(0).toUpperCase();
                    header.appendChild(avFb);
                }

                const authorInfo = document.createElement('div');
                authorInfo.style.cssText = 'flex:1;min-width:0';
                const dname = document.createElement('div');
                dname.textContent = displayName;
                dname.style.cssText = `font-size:13px;font-weight:700;color:${C.text};white-space:nowrap;overflow:hidden;text-overflow:ellipsis`;
                const sname = document.createElement('div');
                sname.style.cssText = 'display:flex;align-items:center;gap:5px;margin-top:1px';
                const snameText = document.createElement('span');
                snameText.textContent = '@' + screenName;
                snameText.style.cssText = `font-size:11px;color:${dark ? 'rgba(255,255,255,.4)' : 'rgba(15,20,25,.42)'}`;
                sname.appendChild(snameText);
                const homeLink = document.createElement('a');
                homeLink.href = `https://x.com/${screenName}`;
                homeLink.target = '_blank'; homeLink.rel = 'noopener noreferrer';
                homeLink.innerHTML = SVG_HOME;
                const _homeLinkIdle = dark ? 'rgba(255,255,255,.3)' : 'rgba(15,20,25,.32)';
                homeLink.style.cssText = `display:inline-flex;align-items:center;color:${_homeLinkIdle};transition:color .12s;flex-shrink:0`;
                homeLink.title = `@${screenName} profile`;
                homeLink.addEventListener('click', e => e.stopPropagation());
                homeLink.addEventListener('mouseover', () => homeLink.style.color = '#1d9bf0');
                homeLink.addEventListener('mouseout',  () => homeLink.style.color = _homeLinkIdle);
                sname.appendChild(homeLink);
                authorInfo.appendChild(dname); authorInfo.appendChild(sname);
                header.appendChild(authorInfo);

                const countBdg = document.createElement('span');
                countBdg.textContent = `${recs.length}`;
                countBdg.style.cssText = `font-size:10px;color:${dark ? 'rgba(255,255,255,.4)' : 'rgba(15,20,25,.42)'};background:${dark ? 'rgba(255,255,255,.09)' : 'rgba(15,20,25,.07)'};border-radius:99px;padding:1px 7px;flex-shrink:0`;
                header.appendChild(countBdg);

                const chev = document.createElement('span');
                chev.style.cssText = `color:${dark ? 'rgba(255,255,255,.3)' : 'rgba(15,20,25,.32)'};flex-shrink:0;line-height:0`;
                chev.innerHTML = isCollapsed ? SVG_CHEV_RIGHT : SVG_CHEV_DOWN;
                header.appendChild(chev);

                header.addEventListener('click', () => {
                    if (_textAuthorCollapsed.has(screenName)) _textAuthorCollapsed.delete(screenName);
                    else _textAuthorCollapsed.add(screenName);
                    render();
                });

                frag.appendChild(header);

                if (isCollapsed) return;

                const sortedRecs = recs.slice().sort((a, b) => (b.ts || 0) - (a.ts || 0));
                let _cbShiftDown = false;
                sortedRecs.forEach(rec => {
                    const row = document.createElement('div');
                    row.className = 'tm-hist-row tm-text-author-item' + (selectedIds.has(rec.id) ? ' selected' : '');
                    row.dataset.id = rec.id;
                    if (globalItemIdx < 8) row.style.animationDelay = `${globalItemIdx * 30}ms`;
                    globalItemIdx++;
                    row.style.paddingLeft = '56px';
                    row.title = editMode ? '' : 'Click to preview full text';

                    if (editMode) {
                        if (!rec.favorited) {
                            const cb = document.createElement('input');
                            cb.type = 'checkbox';
                            cb.className = 'tm-hist-cb';
                            cb.checked = selectedIds.has(rec.id);
                            cb.addEventListener('mousedown', e => { _cbShiftDown = e.shiftKey; });
                            cb.addEventListener('change', e => {
                                e.stopPropagation();
                                getToggleTextRowSel()(rec.id, row, cb);
                                _cbShiftDown = false;
                            });
                            row.appendChild(cb);
                        } else {
                            const lock = document.createElement('span');
                            lock.className = 'tm-hist-cb';
                            lock.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;opacity:0.35;font-size:10px;';
                            lock.textContent = '♥';
                            row.appendChild(lock);
                        }
                        row.style.cursor = rec.favorited ? 'default' : 'pointer';
                    }

                    const textEl = document.createElement('div');
                    textEl.className = 'tm-hist-text';
                    textEl.textContent = rec.text || '(no caption)';
                    textEl.style.cssText = `
                        color:${textColor};font-size:13px;line-height:1.55;
                        display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
                        overflow:hidden;word-break:break-word;flex:1;min-width:0;
                    `;

                    const dateEl = document.createElement('span');
                    dateEl.className = 'tm-hist-date';
                    if (rec.downloadDate) {
                        const [dy, dm, dd] = rec.downloadDate.split('-');
                        if (_cachedDateFormat === 'western') {
                            const MON = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
                            dateEl.textContent = `${parseInt(dd, 10)} ${MON[parseInt(dm, 10)] || dm} ${dy}`;
                        } else {
                            dateEl.textContent = `${dy}.${dm}.${dd}`;
                        }
                    }

                    const infoLine = document.createElement('div');
                    infoLine.style.cssText = 'display:flex;align-items:flex-start;gap:8px;flex:1;min-width:0';
                    if (rec.downloadDate) {
                        const dateSep = document.createElement('span');
                        dateSep.style.cssText = `font-size:11px;color:${dark ? 'rgba(255,255,255,.28)' : 'rgba(15,20,25,.4)'};white-space:nowrap;padding-top:1px;flex-shrink:0`;
                        dateSep.textContent = dateEl.textContent;
                        infoLine.appendChild(dateSep);
                    }
                    infoLine.appendChild(textEl);
                    row.appendChild(infoLine);

                    const acts = document.createElement('div');
                    acts.className = 'tm-hist-actions';

                    const jmpBtn = document.createElement('button');
                    jmpBtn.className = 'tm-hist-act-btn'; jmpBtn.innerHTML = SVG_JUMP;
                    jmpBtn.title = 'Open tweet';
                    jmpBtn.addEventListener('click', e => { e.stopPropagation(); window.open(rec.tweetUrl, '_blank'); });

                    const favBtn = document.createElement('button');
                    const isFav = !!rec.favorited;
                    favBtn.className = 'tm-hist-act-btn tm-fav-btn' + (isFav ? ' tm-fav-active' : '');
                    favBtn.innerHTML = isFav ? SVG_HEART_FULL : SVG_HEART_EMPTY;
                    favBtn.title = isFav ? 'Unfavorite' : 'Favorite';
                    favBtn.addEventListener('click', e => {
                        e.stopPropagation();
                        const updated = _updateRecord(rec.id, r => { r.favorited = !r.favorited; });
                        if (!updated) return;
                        const nowFav = updated.favorited;
                        favBtn.innerHTML = nowFav ? SVG_HEART_FULL : SVG_HEART_EMPTY;
                        favBtn.title = nowFav ? 'Unfavorite' : 'Favorite';
                        favBtn.classList.toggle('tm-fav-active', nowFav);
                        rec.favorited = nowFav;
                    });

                    const delBtn = document.createElement('button');
                    delBtn.className = 'tm-hist-act-btn danger'; delBtn.innerHTML = SVG_DEL;
                    delBtn.title = 'Delete';
                    delBtn.addEventListener('click', e => { e.stopPropagation(); getDeleteOne()(rec.id, 0, favBtn); });

                    acts.appendChild(favBtn); acts.appendChild(jmpBtn); acts.appendChild(delBtn);
                    row.appendChild(acts);

                    row.addEventListener('click', (e) => {
                        if (e.target.classList.contains('tm-hist-act-btn') ||
                            e.target.closest('.tm-hist-actions')) return;
                        if (editMode) {
                            if (e.target.classList.contains('tm-hist-cb')) return;
                            if (!rec.favorited) {
                                const cb = row.querySelector('input[type="checkbox"].tm-hist-cb');
                                getToggleTextRowSel()(rec.id, row, cb);
                            }
                            return;
                        }
                        _showTextPreview(rec);
                    });

                    frag.appendChild(row);
                });
            });

            body.appendChild(frag);

            if (getScrollTarget()) {
                const target = getScrollTarget();
                setScrollTarget(null);
                requestAnimationFrame(() => {
                    const el = body.querySelector(`[data-author-handle="${CSS.escape(target)}"]`);
                    if (el) el.scrollIntoView({ block: 'start', behavior: 'smooth' });
                });
            }
        }

        function renderTextAuthorThumb(records) {
            const _renderEmpty = getRenderEmpty();
            if (!records.length) { _renderEmpty(); return; }

            const authorMap = new Map();
            records.forEach(r => {
                const key = r.screenName || '';
                if (!authorMap.has(key) || (r.ts || 0) > authorMap.get(key).maxTs) {
                    authorMap.set(key, {
                        screenName: key,
                        displayName: r.displayName || key,
                        avatarUrl:   r.avatarUrl   || '',
                        maxTs: r.ts || 0,
                        recCount: 0,
                    });
                }
                authorMap.get(key).recCount++;
            });

            const authors = Array.from(authorMap.values()).sort((a, b) => {
                const ap = _textPinnedAuthors.has(a.screenName) ? 0 : 1;
                const bp = _textPinnedAuthors.has(b.screenName) ? 0 : 1;
                if (ap !== bp) return ap - bp;
                return b.maxTs - a.maxTs;
            });

            const grid = document.createElement('div');
            grid.id = 'tm-hist-thumb-grid';
            const frag = document.createDocumentFragment();

            authors.forEach(({ screenName, displayName, avatarUrl, recCount }) => {
                const isPinned = _textPinnedAuthors.has(screenName);

                const cell = document.createElement('div');
                cell.className = 'tm-hist-grid-cell tm-text-author-cell';
                cell.title = `${displayName} @${screenName} — ${recCount} bookmark${recCount > 1 ? 's' : ''}`;

                const inner = document.createElement('div');
                inner.style.cssText = 'width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:6px;padding:14px 6px 10px;box-sizing:border-box;background:rgba(29,155,240,.08)';

                if (avatarUrl) {
                    const av = document.createElement('img');
                    av.src = avatarUrl; av.alt = ''; av.loading = 'lazy';
                    av.style.cssText = `width:40px;height:40px;border-radius:50%;object-fit:cover;border:2px solid ${dark ? 'rgba(255,255,255,.18)' : 'rgba(15,20,25,.14)'};flex-shrink:0`;
                    av.decode && av.decode().catch(() => {});
                    inner.appendChild(av);
                } else {
                    const avFb = document.createElement('div');
                    avFb.style.cssText = `width:40px;height:40px;border-radius:50%;background:rgba(29,155,240,.3);display:flex;align-items:center;justify-content:center;font-size:18px;color:${dark ? 'rgba(255,255,255,.6)' : '#ffffff'};flex-shrink:0`;
                    avFb.textContent = (displayName || screenName).charAt(0).toUpperCase();
                    inner.appendChild(avFb);
                }

                const nameEl = document.createElement('div');
                nameEl.style.cssText = `font-size:11px;font-weight:700;color:${C.text};text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;width:100%;padding:0 6px;box-sizing:border-box;line-height:1.3`;
                nameEl.textContent = displayName;

                const handleEl = document.createElement('div');
                handleEl.style.cssText = `font-size:10px;color:${dark ? 'rgba(255,255,255,.4)' : 'rgba(15,20,25,.42)'};text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;width:100%;padding:0 6px;box-sizing:border-box;margin-top:-2px`;
                handleEl.textContent = '@' + screenName;

                const cntEl = document.createElement('div');
                cntEl.style.cssText = `font-size:9px;color:${dark ? 'rgba(255,255,255,.28)' : 'rgba(15,20,25,.4)'};background:${dark ? 'rgba(255,255,255,.07)' : 'rgba(15,20,25,.06)'};border-radius:99px;padding:1px 6px;margin-top:1px;flex-shrink:0`;
                cntEl.textContent = `${recCount} post${recCount > 1 ? 's' : ''}`;

                inner.appendChild(nameEl); inner.appendChild(handleEl); inner.appendChild(cntEl);
                cell.appendChild(inner);

                const pinBtn = document.createElement('button');
                pinBtn.className = 'tm-text-pin-btn';
                pinBtn.textContent = '📌';
                pinBtn.title = isPinned ? 'Unpin (remove from top)' : 'Pin to top';
                pinBtn.style.opacity = isPinned ? '1' : '0.3';
                pinBtn.addEventListener('mouseover', () => {
                    pinBtn.style.opacity = '1';
                    pinBtn.style.background = 'rgba(0,0,0,.7)';
                });
                pinBtn.addEventListener('mouseout', () => {
                    const stillPinned = _textPinnedAuthors.has(screenName);
                    pinBtn.style.opacity = stillPinned ? '1' : '0.3';
                    pinBtn.style.background = 'rgba(0,0,0,.5)';
                });
                pinBtn.addEventListener('click', e => {
                    e.stopPropagation();
                    const nowPinned = !_textPinnedAuthors.has(screenName);
                    if (nowPinned) _textPinnedAuthors.add(screenName);
                    else           _textPinnedAuthors.delete(screenName);
                    _savePinnedAuthors();
                    pinBtn.title   = nowPinned ? 'Unpin (remove from top)' : 'Pin to top';
                    pinBtn.style.opacity = nowPinned ? '1' : '0.3';
                    render();
                });
                cell.appendChild(pinBtn);

                cell.addEventListener('click', (e) => {
                    if (e.target.closest('button')) return;
                    setScrollTarget(screenName);
                    setViewMode('list');
                    GM_setValue(KEY_HISTORY_VIEW_MODE, 'list');
                    render();
                });

                frag.appendChild(cell);
            });

            grid.appendChild(frag);
            body.appendChild(grid);
        }

        return {
            getTextBmContentColor: _getTextBmContentColor,
            showTextPreview: _showTextPreview,
            renderAuthorList: renderTextAuthorList,
            renderAuthorThumb: renderTextAuthorThumb,
        };
    }

    function _createGroupTabsBuilder(deps) {
        const { groupTabBar, C, render, getMediaFilter, getActiveGroupId, setActiveGroupId } = deps;
        let _gtabExpanded = false;

        function buildGroupTabs() {
            groupTabBar.innerHTML = '';
            const mediaFilter = getMediaFilter();
            const activeGroupId = getActiveGroupId();
            const isTextTab = mediaFilter === 'text';
            const groups = isTextTab ? getTextGroups() : getGroups();
            groupTabBar.style.display = 'flex';

            const SVG_LEFT  = `<svg viewBox="0 0 10 10" fill="currentColor"><path d="M6.5 2L3.5 5l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/></svg>`;
            const SVG_RIGHT = `<svg viewBox="0 0 10 10" fill="currentColor"><path d="M3.5 2L6.5 5l-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/></svg>`;

            const btnAddGroup = document.createElement('button');
            btnAddGroup.type = 'button';
            btnAddGroup.title = 'New group';
            btnAddGroup.style.cssText = `flex-shrink:0;width:26px;height:26px;border-radius:7px;border:none;background:transparent;color:${C.sub};cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;transition:background .12s,color .12s;margin:0 3px 0 2px`;
            btnAddGroup.innerHTML = '<svg viewBox="0 0 14 14" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="7" y1="2" x2="7" y2="12"/><line x1="2" y1="7" x2="12" y2="7"/></svg>';
            btnAddGroup.addEventListener('mouseover', () => { btnAddGroup.style.background = 'rgba(29,155,240,.18)'; btnAddGroup.style.color = '#1d9bf0'; });
            btnAddGroup.addEventListener('mouseout',  () => { btnAddGroup.style.background = 'transparent'; btnAddGroup.style.color = C.sub; });
            btnAddGroup.addEventListener('click', e => {
                e.stopPropagation();
                StarPipState.pendingIsText = isTextTab;
                showGroupCreateModal();
            });

            const btnLeft = document.createElement('button');
            btnLeft.className = 'tm-gtab-scroll-btn left';
            btnLeft.innerHTML = SVG_LEFT;
            btnLeft.title = 'Scroll left';
            btnLeft.addEventListener('click', (e) => {
                e.stopPropagation();
                scrollArea.scrollBy({ left: -120, behavior: 'smooth' });
            });

            const scrollArea = document.createElement('div');
            scrollArea.id = 'tm-group-tab-scroll';

            const btnRight = document.createElement('button');
            btnRight.className = 'tm-gtab-scroll-btn right visible';
            btnRight.innerHTML = SVG_RIGHT;
            btnRight.title = 'Show all groups';
            btnRight.addEventListener('click', (e) => {
                e.stopPropagation();
                toggleExpandState();
            });

            const _commitReorder = () => {
                const orderedIds = Array.from(scrollArea.querySelectorAll('.tm-gtab-pill[data-reorderable="1"]'))
                    .map(p => p.dataset.groupId);
                const currentGroups = isTextTab ? getTextGroups() : getGroups();
                const byId = new Map(currentGroups.map(g => [g.id, g]));
                const reordered = orderedIds.map(id => byId.get(id)).filter(Boolean);
                if (reordered.length !== currentGroups.length) {
                    const reorderedSet = new Set(reordered.map(g => g.id));
                    currentGroups.forEach(g => { if (!reorderedSet.has(g.id)) reordered.push(g); });
                }
                if (isTextTab) saveTextGroups(reordered); else saveGroups(reordered);
                buildGroupTabs();
            };

            const initDragHandle = (handle, pill) => {
                handle.addEventListener('click', e => e.stopPropagation());
                handle.addEventListener('pointerdown', (e) => {
                    if (e.button !== 0) return;
                    e.preventDefault();
                    e.stopPropagation();
                    const pointerId = e.pointerId;

                    const rect = pill.getBoundingClientRect();
                    const offsetX = e.clientX - rect.left;
                    const offsetY = e.clientY - rect.top;
                    const ghost = pill.cloneNode(true);
                    ghost.classList.add('tm-gtab-ghost');
                    ghost.style.width  = rect.width + 'px';
                    ghost.style.height = rect.height + 'px';
                    ghost.style.left   = rect.left + 'px';
                    ghost.style.top    = rect.top + 'px';
                    document.body.appendChild(ghost);
                    document.body.classList.add('tm-gtab-drag-active');

                    pill.classList.add('tm-gtab-dragging');

                    const onMove = (ev) => {
                        if (ev.pointerId !== pointerId) return;
                        ghost.style.left = (ev.clientX - offsetX) + 'px';
                        ghost.style.top  = (ev.clientY - offsetY) + 'px';
                        const el = document.elementFromPoint(ev.clientX, ev.clientY);
                        const targetPill = el?.closest('.tm-gtab-pill[data-reorderable="1"]');
                        if (targetPill && targetPill !== pill) {
                            const trect = targetPill.getBoundingClientRect();
                            const before = ev.clientX < trect.left + trect.width / 2;
                            targetPill.parentNode.insertBefore(pill, before ? targetPill : targetPill.nextSibling);
                        }
                    };
                    const onUp = (ev) => {
                        if (ev.pointerId !== pointerId) return;
                        document.removeEventListener('pointermove', onMove);
                        document.removeEventListener('pointerup', onUp);
                        document.removeEventListener('pointercancel', onUp);
                        ghost.remove();
                        document.body.classList.remove('tm-gtab-drag-active');
                        _commitReorder();
                    };
                    document.addEventListener('pointermove', onMove);
                    document.addEventListener('pointerup', onUp);
                    document.addEventListener('pointercancel', onUp);
                });
            };

            const makePill = (iconHtml, label, value, reorderable = false) => {
                const pill = document.createElement('button');
                pill.type = 'button';
                const isActive =
                    (value === '__all__'       && (activeGroupId === null || activeGroupId === '__all__')) ||
                    (value === '__ungrouped__' && activeGroupId === '__ungrouped__') ||
                    (value !== '__all__' && value !== '__ungrouped__' && activeGroupId === value);
                pill.className = 'tm-gtab-pill' + (isActive ? ' active' : '');
                if (reorderable) {
                    pill.dataset.groupId = value;
                    pill.setAttribute('data-reorderable', '1');
                    const handle = document.createElement('span');
                    handle.className = 'tm-gtab-drag-handle';
                    handle.innerHTML = SVG_GRIP;
                    handle.title = 'Drag to reorder';
                    pill.appendChild(handle);
                    initDragHandle(handle, pill);
                }
                const iconSpan = document.createElement('span');
                iconSpan.innerHTML = iconHtml;
                iconSpan.style.cssText = 'display:inline-flex;align-items:center;flex-shrink:0';
                pill.appendChild(iconSpan);
                if (label) {
                    const txtSpan = document.createElement('span');
                    txtSpan.textContent = label;
                    pill.appendChild(txtSpan);
                }
                if (value !== '__ungrouped__' && value !== '__all__' && value !== '__favorites__' && GM_getValue('app_group_unread_' + value, false)) {
                    const dot = document.createElement('span');
                    dot.className = 'tm-gtab-dot';
                    pill.appendChild(dot);
                }
                pill.addEventListener('click', (e) => {
                    e.stopPropagation();
                    setActiveGroupId(value === '__all__' ? null : value);
                    if (value !== '__ungrouped__' && value !== '__all__' && value !== '__favorites__') {
                        GM_deleteValue('app_group_unread_' + value);
                        pill.querySelector('.tm-gtab-dot')?.remove();
                    }
                    scrollArea.querySelectorAll('.tm-gtab-pill').forEach(p => p.classList.remove('active'));
                    pill.classList.add('active');
                    pill.scrollIntoView({ block: 'nearest', inline: 'nearest' });
                    render();
                });

                if (value !== '__ungrouped__' && value !== '__all__' && value !== '__favorites__') {
                    pill.addEventListener('contextmenu', e => {
                        e.preventDefault(); e.stopPropagation();
                        document.getElementById('tm-pill-ctx')?.remove();
                        _dialogOpenGlobal = true;
                        const menu = document.createElement('div');
                        menu.id = 'tm-pill-ctx';
                        menu.style.cssText = `
                            position:fixed;left:${e.clientX}px;top:${e.clientY}px;
                            background:#1a1f2e;border:0.5px solid rgba(255,255,255,.15);
                            border-radius:10px;padding:4px;min-width:150px;
                            z-index:999999;box-shadow:0 8px 32px rgba(0,0,0,.6);
                            font-size:13px;
                        `;
                        const mkItem = (icon, label, color, onClick) => {
                            const item = document.createElement('button');
                            item.type = 'button';
                            item.style.cssText = `display:flex;align-items:center;gap:9px;width:100%;padding:8px 12px;background:transparent;border:none;border-radius:7px;color:${color || 'rgba(255,255,255,.88)'};cursor:pointer;font-size:13px;font-family:inherit;text-align:left;transition:background .1s`;
                            item.addEventListener('mouseover', () => item.style.background = 'rgba(255,255,255,.08)');
                            item.addEventListener('mouseout',  () => item.style.background = 'transparent');
                            const iconEl = document.createElement('span');
                            iconEl.style.cssText = 'font-size:15px;flex-shrink:0;line-height:1';
                            iconEl.textContent = icon;
                            const labelEl = document.createElement('span');
                            labelEl.textContent = label;
                            item.appendChild(iconEl); item.appendChild(labelEl);
                            item.addEventListener('click', () => { menu.remove(); _dialogOpenGlobal = false; onClick(); });
                            return item;
                        };
                        menu.appendChild(mkItem('✏️', 'Rename', null, () => {
                            _dialogOpenGlobal = true;
                            const newName = prompt('Rename group:', label);
                            _dialogOpenGlobal = false;
                            if (!newName || !newName.trim()) return;
                            const arr = isTextTab ? getTextGroups() : getGroups();
                            const idx = arr.findIndex(x => x.id === value);
                            if (idx > -1) {
                                arr[idx].name = newName.trim();
                                isTextTab ? saveTextGroups(arr) : saveGroups(arr);
                            }
                            render();
                        }));
                        const sep = document.createElement('div');
                        sep.style.cssText = 'height:0.5px;background:rgba(255,255,255,.1);margin:3px 8px';
                        menu.appendChild(sep);
                        menu.appendChild(mkItem('🗑️', 'Delete group', 'rgba(255,100,100,.9)', () => {
                            _dialogOpenGlobal = true;
                            const ok = confirm(`Delete group「${label}」? Records will be ungrouped.`);
                            _dialogOpenGlobal = false;
                            if (!ok) return;
                            if (isTextTab) deleteTextGroup(value);
                            else deleteGroup(value);
                            if (getActiveGroupId() === value) setActiveGroupId(null);
                            render();
                        }));
                        document.body.appendChild(menu);
                        requestAnimationFrame(() => {
                            const r = menu.getBoundingClientRect();
                            if (r.right  > window.innerWidth)  menu.style.left = (e.clientX - r.width)  + 'px';
                            if (r.bottom > window.innerHeight)  menu.style.top  = (e.clientY - r.height) + 'px';
                        });
                        const close = ev => {
                            if (!menu.contains(ev.target)) {
                                menu.remove();
                                _dialogOpenGlobal = false;
                                document.removeEventListener('mousedown', close, true);
                            }
                        };
                        document.addEventListener('mousedown', close, true);
                    });
                }
                return pill;
            };

            const SVG_GRIP = `<svg viewBox="0 0 10 16" width="8" height="12" fill="currentColor"><circle cx="2" cy="2" r="1.3"/><circle cx="8" cy="2" r="1.3"/><circle cx="2" cy="8" r="1.3"/><circle cx="8" cy="8" r="1.3"/><circle cx="2" cy="14" r="1.3"/><circle cx="8" cy="14" r="1.3"/></svg>`;

            const SVG_ALL = `<svg viewBox="0 0 14 14" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg>`;
            scrollArea.appendChild(makePill(SVG_ALL, 'All', '__all__'));

            const SVG_HEART = `<svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none"><path d="M16.697 5.5c-1.222-.06-2.679.51-3.89 2.16l-.805 1.09-.806-1.09C9.984 6.01 8.526 5.44 7.304 5.5c-1.243.07-2.349.78-2.91 1.91-.552 1.12-.633 2.78.479 4.82 1.074 1.97 3.257 4.27 7.129 6.61 3.87-2.34 6.052-4.64 7.126-6.61 1.111-2.04 1.03-3.7.477-4.82-.561-1.13-1.666-1.84-2.908-1.91z"/></svg>`;
            scrollArea.appendChild(makePill(SVG_HEART, '', '__favorites__'));

            groups.forEach(g => {
                const ic = _resolveGroupIcon(g.icon);
                const iconHtml = `<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">${ic.svg.match(/<svg[^>]*>([\s\S]*?)<\/svg>/)?.[1] || ''}</svg>`;
                scrollArea.appendChild(makePill(iconHtml, g.name, g.id, true));
            });

            const SVG_DASH = `<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="2" y1="6" x2="10" y2="6"/></svg>`;
            scrollArea.appendChild(makePill(SVG_DASH, 'Ungrouped', '__ungrouped__'));

            groupTabBar.appendChild(btnAddGroup);
            groupTabBar.appendChild(btnLeft);
            groupTabBar.appendChild(scrollArea);
            groupTabBar.appendChild(btnRight);

            const syncArrows = () => {
                const canLeft = scrollArea.scrollLeft > 2;
                btnLeft.classList.toggle('visible', canLeft && !_gtabExpanded);
            };
            scrollArea.addEventListener('scroll', syncArrows, { passive: true });
            if (window.ResizeObserver) {
                const ro = new ResizeObserver(syncArrows);
                ro.observe(scrollArea);
            }

            const applyExpandStateInstant = (expanded) => {
                groupTabBar.classList.toggle('tm-gtab-expanded', expanded);
                scrollArea.classList.toggle('tm-gtab-expanded', expanded);
                btnRight.classList.toggle('tm-gtab-expanded', expanded);
                btnRight.title = expanded ? 'Collapse groups' : 'Show all groups';
                syncArrows();
            };

            let _gtabAnimating = false;
            const toggleExpandState = () => {
                if (_gtabAnimating) return;
                const expanded = !_gtabExpanded;
                _gtabExpanded = expanded;
                btnRight.classList.toggle('tm-gtab-expanded', expanded);
                btnRight.title = expanded ? 'Collapse groups' : 'Show all groups';
                syncArrows();

                const startHeight = scrollArea.getBoundingClientRect().height;
                scrollArea.style.overflow = 'hidden';
                _gtabAnimating = true;

                if (expanded) {
                    groupTabBar.classList.add('tm-gtab-expanded');
                    scrollArea.classList.add('tm-gtab-expanded');
                    const endHeight = Math.min(scrollArea.scrollHeight, 120);
                    scrollArea.animate(
                        [{ height: startHeight + 'px' }, { height: endHeight + 'px' }],
                        { duration: 200, easing: 'ease' }
                    ).onfinish = () => {
                        scrollArea.style.overflow = '';
                        scrollArea.style.height = '';
                        _gtabAnimating = false;
                    };
                } else {
                    scrollArea.animate(
                        [{ height: startHeight + 'px' }, { height: '36px' }],
                        { duration: 200, easing: 'ease' }
                    ).onfinish = () => {
                        groupTabBar.classList.remove('tm-gtab-expanded');
                        scrollArea.classList.remove('tm-gtab-expanded');
                        scrollArea.style.overflow = '';
                        scrollArea.style.height = '';
                        syncArrows();
                        _gtabAnimating = false;
                    };
                }
            };
            applyExpandStateInstant(_gtabExpanded);

            requestAnimationFrame(syncArrows);
        }

        return { buildGroupTabs };
    }

    function _createCrossNavController(deps) {
        const { _openHistMediaLightbox, getNavRecords } = deps;
        let _crossKeyHandlerRef = null;

        function _getCrossNavNeighbor(recId, dir) {
            if (getNavRecords().length < 2) return null;
            const i = getNavRecords().findIndex(r => r.id === recId);
            if (i === -1) return null;
            const n = getNavRecords().length;
            return getNavRecords()[(i + dir + n) % n];
        }

        function _removeCrossNavButtons() {
            document.getElementById('tm-hcn-prev')?.remove();
            document.getElementById('tm-hcn-next')?.remove();
            if (_crossKeyHandlerRef) {
                document.removeEventListener('keydown', _crossKeyHandlerRef);
                _crossKeyHandlerRef = null;
            }
        }

        const _HCN_MODAL_IDS = ['tm-image-lightbox', 'tm-floating-video-modal', 'tm-thumb-lb-backdrop'];

        function _injectHistCrossNav(rec) {
            _removeCrossNavButtons();
            if (getNavRecords().length < 2) return;
            let tries = 0;
            const poll = () => {
                const modal = _HCN_MODAL_IDS.map(id => document.getElementById(id)).find(Boolean);
                if (modal) { _buildCrossNavButtons(rec, modal); return; }
                if (++tries < 20) setTimeout(poll, 50);
            };
            poll();
        }

        function _buildCrossNavButtons(rec, modal) {
            if (document.getElementById('tm-hcn-prev')) return;

            modal.querySelectorAll('.tm-nav-fade-btn').forEach(btn => {
                if (btn.style.left  === '20px') btn.style.left  = '76px';
                if (btn.style.right === '20px') btn.style.right = '76px';
            });

            const BTN_BASE = `
                position: fixed; top: 50%; transform: translateY(-50%);
                background: rgba(0,0,0,0.55); backdrop-filter: blur(4px);
                color: white; border: none;
                width: 40px; height: 40px; border-radius: 50%;
                cursor: pointer; display: flex; align-items: center; justify-content: center;
                transition: background 0.2s; z-index: 10000020;
            `;
            const SVG_DPREV = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><polyline points="18,18 10,12 18,6"/><polyline points="11,18 3,12 11,6"/></svg>`;
            const SVG_DNEXT = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><polyline points="6,18 14,12 6,6"/><polyline points="13,18 21,12 13,6"/></svg>`;

            const _go = (dir) => {
                const neighbor = _getCrossNavNeighbor(rec.id, dir);
                if (!neighbor) return;
                _removeCrossNavButtons();
                document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
                setTimeout(() => {
                    _openHistMediaLightbox(neighbor, 0, null);
                    _injectHistCrossNav(neighbor);
                }, 340);
            };

            const prevBtn = document.createElement('button');
            prevBtn.id = 'tm-hcn-prev';
            prevBtn.title = 'Previous record in history ([)';
            prevBtn.setAttribute('aria-label', 'Previous record in history');
            prevBtn.innerHTML = SVG_DPREV;
            prevBtn.style.cssText = BTN_BASE + 'left: 20px;';
            prevBtn.onmouseenter = () => prevBtn.style.background = 'rgba(255,255,255,0.25)';
            prevBtn.onmouseleave = () => prevBtn.style.background = 'rgba(0,0,0,0.55)';
            prevBtn.onclick = (e) => { e.stopPropagation(); _go(-1); };

            const nextBtn = document.createElement('button');
            nextBtn.id = 'tm-hcn-next';
            nextBtn.title = 'Next record in history (])';
            nextBtn.setAttribute('aria-label', 'Next record in history');
            nextBtn.innerHTML = SVG_DNEXT;
            nextBtn.style.cssText = BTN_BASE + 'right: 20px;';
            nextBtn.onmouseenter = () => nextBtn.style.background = 'rgba(255,255,255,0.25)';
            nextBtn.onmouseleave = () => nextBtn.style.background = 'rgba(0,0,0,0.55)';
            nextBtn.onclick = (e) => { e.stopPropagation(); _go(1); };

            document.body.appendChild(prevBtn);
            document.body.appendChild(nextBtn);

            _crossKeyHandlerRef = (e) => {
                if (e.key === '[') { e.preventDefault(); _go(-1); }
                if (e.key === ']') { e.preventDefault(); _go(1); }
            };
            document.addEventListener('keydown', _crossKeyHandlerRef);

            const _watchClose = new MutationObserver(() => {
                const stillOpen = _HCN_MODAL_IDS.some(id => document.getElementById(id));
                if (!stillOpen) { _removeCrossNavButtons(); _watchClose.disconnect(); }
            });
            _watchClose.observe(document.body, { childList: true });
        }

        return {
            getNeighbor: _getCrossNavNeighbor,
            removeCrossNavButtons: _removeCrossNavButtons,
            injectCrossNav: _injectHistCrossNav,
            buildCrossNavButtons: _buildCrossNavButtons,
        };
    }

    function _createImportExportController(deps) {
        const { getRecords, btnExp, render, _invalidateRecordsCache } = deps;
        let _expMenu = null;

        function _exportCSV() {
            const csvCell = v => {
                const s = String(v == null ? '' : v);
                return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
            };
            const records = getRecords();
            const groupNameMap = new Map(
                [...getGroups(), ...getTextGroups()].map(g => [g.id, g.name])
            );
            const header  = 'tweetId,tweetUrl,date,screenName,displayName,text,mediaUrls,groupId,groupName,favorited\n';
            const rows    = records.map(r => [
                r.tweetId,
                r.tweetUrl,
                r.tweetDate,
                r.screenName,
                csvCell(r.displayName),
                csvCell(r.text),
                csvCell((r.mediaUrls || []).join(' ')),
                r.groupId || '',
                csvCell(r.groupId ? (groupNameMap.get(r.groupId) || '') : ''),
                r.favorited ? '1' : '0',
            ].join(',')).join('\n');
            _download('history.csv', header + rows, 'text/csv');
        }
        function _exportJSON() {
            const payload = {
                __tm_export_version: 1,
                records: getRecords(),
                groups: getGroups(),
                textGroups: getTextGroups(),
            };
            _download('history.json', JSON.stringify(payload, null, 2), 'application/json');
        }
        function _download(filename, content, type) {
            const blob = new Blob([content], { type });
            const url  = URL.createObjectURL(blob);
            const a    = document.createElement('a');
            a.href = url; a.download = filename;
            document.body.appendChild(a); a.click(); document.body.removeChild(a);
            setTimeout(() => URL.revokeObjectURL(url), 5000);
        }

        function _importJSON() {
            const input = document.createElement('input');
            input.type   = 'file';
            input.accept = '.json,application/json';
            input.style.display = 'none';
            document.body.appendChild(input);

            input.addEventListener('change', () => {
                const file = input.files?.[0];
                if (!file) { input.remove(); return; }
                const reader = new FileReader();
                reader.onload = (e) => {
                    showToast('⏳ Importing…', 60000);
                    setTimeout(() => {
                    try {
                        const parsed = JSON.parse(e.target.result);

                        let imported, importedGroups, importedTextGroups;
                        if (Array.isArray(parsed)) {
                            imported = parsed;
                            importedGroups = importedTextGroups = [];
                        } else if (parsed && Array.isArray(parsed.records)) {
                            imported           = parsed.records;
                            importedGroups     = Array.isArray(parsed.groups) ? parsed.groups : [];
                            importedTextGroups = Array.isArray(parsed.textGroups) ? parsed.textGroups : [];
                        } else {
                            throw new Error('Not a valid history export');
                        }

                        const groupAddCount = _mergeImportedGroups(importedGroups, getGroups, saveGroups)
                            + _mergeImportedGroups(importedTextGroups, getTextGroups, saveTextGroups);

                        const addCount = _mergeImportedRecords(imported);
                        imported.forEach(r => { if (r.tweetId) _downloadedIds.add(r.tweetId); });
                        _invalidateRecordsCache();
                        showToast(groupAddCount > 0
                            ? `✅ Imported ${addCount} record(s), ${groupAddCount} group(s)`
                            : `✅ Imported ${addCount} new record(s)`);
                        render();
                    } catch (err) {
                        showToast(`❌ Import failed: ${err.message}`);
                    } finally {
                        input.remove();
                    }
                    }, 0);
                };
                reader.readAsText(file);
            });

            input.click();
        }

        function _showExpMenu() {
            const existing = document.getElementById('tm-exp-menu');
            if (existing) { existing.remove(); _expMenu = null; return; }

            const menu = document.createElement('div');
            menu.id = 'tm-exp-menu';
            _expMenu = menu;

            const btnRect = btnExp.getBoundingClientRect();
            const menuWidth = 152;
            const menuLeft  = Math.max(4, btnRect.right - menuWidth);
            menu.style.cssText = `
                position:fixed;
                left:${menuLeft}px;
                top:${btnRect.bottom + 6}px;
                background:rgba(22,32,43,.97);
                border:1px solid rgba(255,255,255,.12);
                border-radius:10px;
                padding:4px;
                z-index:999999;
                min-width:${menuWidth}px;
                box-shadow:0 6px 20px rgba(0,0,0,.5);
                animation:tm-exp-menu-in .15s cubic-bezier(.34,1.56,.64,1);
            `;

            if (!document.getElementById('tm-exp-menu-style')) {
                const s = document.createElement('style');
                s.id = 'tm-exp-menu-style';
                s.textContent = `@keyframes tm-exp-menu-in{from{opacity:0;transform:scale(.88) translateY(-4px)}to{opacity:1;transform:scale(1) translateY(0)}}`;
                document.head.appendChild(s);
            }

            const mkItem = (icon, label, onClick) => {
                const item = document.createElement('button');
                item.type = 'button';
                item.style.cssText = `
                    display:flex;align-items:center;gap:8px;
                    width:100%;padding:7px 10px;border:none;
                    background:transparent;border-radius:7px;
                    color:rgba(255,255,255,.8);font-size:12px;
                    cursor:pointer;font-family:inherit;text-align:left;
                    transition:background .1s;white-space:nowrap;
                `;
                item.innerHTML = `<span style="opacity:.65;display:flex;align-items:center">${icon}</span><span>${label}</span>`;
                item.addEventListener('mouseover', () => item.style.background = 'rgba(255,255,255,.09)');
                item.addEventListener('mouseout',  () => item.style.background = 'transparent');
                item.addEventListener('click', (e) => {
                    e.stopPropagation();
                    menu.remove(); _expMenu = null;
                    document.removeEventListener('click', closeMenu, true);
                    onClick();
                });
                return item;
            };

            const SVG_CSV  = `<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><rect x="2" y="1" width="12" height="14" rx="1.5"/><line x1="5" y1="5" x2="11" y2="5"/><line x1="5" y1="8" x2="11" y2="8"/><line x1="5" y1="11" x2="8" y2="11"/></svg>`;
            const SVG_JSON = `<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M5 2C3.5 2 3 3 3 4v2c0 1-.5 1.5-1 2 .5.5 1 1 1 2v2c0 1 .5 2 2 2"/><path d="M11 2c1.5 0 2 1 2 2v2c0 1 .5 1.5 1 2-.5.5-1 1-1 2v2c0 1-.5 2-2 2"/></svg>`;
            const SVG_IMP  = `<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M8 10V2M5 7l3 3 3-3"/><path d="M2 12h12"/></svg>`;

            const divider = document.createElement('div');
            divider.style.cssText = 'height:1px;background:rgba(255,255,255,.08);margin:3px 4px';

            menu.appendChild(mkItem(SVG_CSV,  'Export CSV',  _exportCSV));
            menu.appendChild(mkItem(SVG_JSON, 'Export JSON', _exportJSON));
            menu.appendChild(divider);
            menu.appendChild(mkItem(SVG_IMP,  'Import JSON', _importJSON));

            document.body.appendChild(menu);

            const closeMenu = (e) => {
                if (menu.contains(e.target) || e.target === btnExp) return;
                menu.remove(); _expMenu = null;
            };
            setTimeout(() => document.addEventListener('click', closeMenu, { capture: true, once: true }), 80);
        }

        return {
            exportCSV: _exportCSV,
            exportJSON: _exportJSON,
            importJSON: _importJSON,
            showExportMenu: _showExpMenu,
        };
    }

    function showHistoryPanel() {
        let _pinned = GM_getValue(KEY_HIST_PINNED, false);
        let _pinCloseAlertCount = 0;

        const existing = document.getElementById('tm-hist-panel');
        if (existing) {
            if (DockState.side) {
                existing.dispatchEvent(new CustomEvent('tm-hist-toggle-peek'));
                return;
            }
            if (GM_getValue(KEY_HIST_PINNED, false)) return;

            if (typeof existing._tmCleanup === 'function') existing._tmCleanup();
            if (_historyUndoTimer) { clearTimeout(_historyUndoTimer); _historyUndoTimer = null; _historyUndoBuffer = null; }
            existing.remove();
            _cleanZoom();
            return;
        }

        const dark = _isDarkTheme('history');
        const presetKey = _getActiveThemePresetKey('history');
        const _t = _getActiveThemeColors(presetKey, 'history');
        const _ex = _getActiveThemeExtras(presetKey, 'history');
        const C = dark ? {
            bg: _t.bg, header: _ex.header, text: _t.text, sub: _t.sub,
            border: _ex.borderA, rowHover: _ex.header, inputBg: _ex.header,
            groupHdr: _ex.header, groupTxt: _t.sub,
            thumbBg: _ex.header, danger: '#e0245e', dangerHover: '#c01e4e',
            badgeNew: '#1d9bf0', scrollbar: _ex.borderB,
        } : {
            bg: _t.bg, header: _ex.header, text: _t.text, sub: _t.sub,
            border: _ex.borderA, rowHover: _ex.header, inputBg: _ex.header,
            groupHdr: _ex.header, groupTxt: _t.sub,
            thumbBg: _ex.header, danger: '#e0245e', dangerHover: '#c01e4e',
            badgeNew: '#1d9bf0', scrollbar: _ex.borderB,
        };

        let pos = {
            x: Math.max(8, Math.min(Math.round(window.innerWidth * 0.55 - 195), window.innerWidth - 398)),
            y: 60, w: 390, h: 540,
        };
        try {
            const saved = JSON.parse(GM_getValue(KEY_HISTORY_PANEL_POS, 'null'));
            if (saved && typeof saved.x === 'number') {
                pos = {
                    x: Math.min(saved.x, window.innerWidth  - 300),
                    y: Math.min(saved.y, window.innerHeight - 200),
                    w: Math.max(300, Math.min(saved.w || 390, 680)),
                    h: Math.max(280, Math.min(saved.h || 540, window.innerHeight - 20)),
                };
            }
        } catch (_) {}

        let viewMode  = GM_getValue(KEY_HISTORY_VIEW_MODE, 'list');
        let editMode  = false;
        let query     = '';
        let activeGroupId = null;
        let mediaFilter = 'all';
        let sortMode = 'newest';
        const selectedIds    = new Set();
        const collapsedGroups = new Set();
        let anchorIdx          = -1;
        let _navRecords        = [];
        let _crossKeyHandlerRef = null;
        const _textAuthorCollapsed = new Set();
        const _textAuthorAutoCollapsedApplied = new Set();
        let _textAuthorScrollTarget = null;
        const _loadTbmCfg = () => { try { return JSON.parse(GM_getValue(KEY_TEXT_BM_CFG, '{}')); } catch(_) { return {}; } };
        const _textPinnedAuthors = new Set((_loadTbmCfg().pinnedAuthors) || []);
        const _savePinnedAuthors = () => {
            const cfg = _loadTbmCfg();
            cfg.pinnedAuthors = Array.from(_textPinnedAuthors);
            GM_setValue(KEY_TEXT_BM_CFG, JSON.stringify(cfg));
        };
        let _renderLimit   = 80;
        let _lastFilterKey = '';
        let _groupTabsDirty  = true;
        let _lastTabFilter   = null;
        let _lastTabActiveId = null;
        let _loadSentinelObserver = null;
        let _searchRenderTimer    = null;
        let _searchDrop        = null;

        let _recordsCache = null;
        function _invalidateRecordsCache() { _recordsCache = null; }

        const searchHistCtl = _createSearchHistoryController({
            render,
            getSearchInput: () => searchInput,
            getSearchDrop: () => _searchDrop,
            setQuery: (v) => { query = v; },
        });
        const _loadSearchHist      = searchHistCtl.load;
        const _saveSearchHist      = searchHistCtl.save;
        const _addSearchHist       = searchHistCtl.add;
        const _getSortedHist       = searchHistCtl.getSorted;
        const _buildSearchHistRow  = searchHistCtl.buildRow;
        const _renderSearchDrop    = searchHistCtl.renderDrop;
        const _showSearchDrop      = searchHistCtl.show;
        const _hideSearchDrop      = searchHistCtl.hide;

        let histStyleEl = document.getElementById('tm-hist-style');
        if (!histStyleEl) {
            histStyleEl = document.createElement('style');
            histStyleEl.id = 'tm-hist-style';
            document.head.appendChild(histStyleEl);
        }
        histStyleEl.textContent = `
            #tm-hist-panel {
                position: fixed; z-index: 999992; 
                font-family: system-ui, -apple-system, sans-serif;
                display: flex; flex-direction: column;
                background: ${C.bg}; border: 1px solid ${C.border};
                border-radius: 14px;
                box-shadow: 0 12px 40px rgba(0,0,0,0.22), 0 2px 8px rgba(0,0,0,0.10);
                overflow: hidden;
                min-width: 300px; min-height: 280px;
            }
            
            .tm-hist-theme-fade { transition: opacity 0.16s ease; }
            #tm-hist-titlebar {
                display: flex; align-items: center; gap: 6px;
                padding: 9px 12px; cursor: grab;
                background: ${C.header}; border-bottom: 1px solid ${C.border};
                user-select: none; flex-shrink: 0;
            }

            @keyframes tm-pin-on {
                0%   { transform: rotate(-45deg) scale(0.7); opacity: 0; }
                60%  { transform: rotate(8deg)   scale(1.15); opacity: 1; }
                100% { transform: rotate(0deg)   scale(1);   opacity: 1; }
            }
            @keyframes tm-pin-off {
                0%   { transform: rotate(0deg)   scale(1);   opacity: 1; }
                40%  { transform: rotate(-15deg) scale(0.85); }
                100% { transform: rotate(45deg)  scale(0.7); opacity: 0.4; }
            }
            
            @keyframes tm-pin-alert {
                0%   { transform: scale(1);    opacity: 0.85; color: #1d9bf0; }
                18%  { transform: scale(1.35); opacity: 1;    color: #f4212e; background: rgba(244,33,46,.15); }
                36%  { transform: scale(0.88); opacity: 0.7;  color: #1d9bf0; background: transparent; }
                54%  { transform: scale(1.22); opacity: 1;    color: #f4212e; background: rgba(244,33,46,.12); }
                72%  { transform: scale(0.92); opacity: 0.75; color: #1d9bf0; background: transparent; }
                88%  { transform: scale(1.08); opacity: 0.95; color: #f4212e; background: rgba(244,33,46,.08); }
                100% { transform: scale(1);    opacity: 0.85; color: #1d9bf0; background: transparent; }
            }
            #tm-hist-btn-pin {
                background: transparent; border: none; cursor: pointer;
                display: inline-flex; align-items: center; justify-content: center;
                width: 24px; height: 24px; border-radius: 6px; padding: 0;
                color: ${C.sub}; opacity: 0.45;
                transition: opacity 0.18s, background 0.15s, color 0.15s;
                flex-shrink: 0;
            }
            #tm-hist-btn-pin:hover { opacity: 1; background: ${C.rowHover}; }
            #tm-hist-btn-pin.pinned {
                opacity: 0.85; color: #1d9bf0;
                animation: tm-pin-on 0.38s cubic-bezier(0.34,1.56,0.64,1) forwards;
            }
            #tm-hist-btn-pin.unpinning {
                animation: tm-pin-off 0.28s ease forwards;
            }
            #tm-hist-btn-pin.pin-alert {
                animation: tm-pin-alert 1.5s ease forwards;
            }
            
            @keyframes tm-pin-egg-2x-in {
                0%   { transform: scale(1); }
                100% { transform: scale(2); }
            }
            @keyframes tm-pin-egg-2x-breathe {
                0%, 100% { transform: scale(2); }
                50%      { transform: scale(2.12); }
            }
            @keyframes tm-pin-egg-mushroom {
                0%   { transform: scale(1)   rotate(0deg); }
                18%  { transform: scale(2.6) rotate(-6deg); }
                28%  { transform: scale(2.6) rotate(6deg); }
                38%  { transform: scale(2.6) rotate(-5deg); }
                48%  { transform: scale(2.6) rotate(4deg); }
                58%  { transform: scale(2.6) rotate(-4deg); }
                68%  { transform: scale(2.6) rotate(3deg); }
                78%  { transform: scale(2.6) rotate(-2deg); }
                90%  { transform: scale(2.6) rotate(0deg); }
                100% { transform: scale(2.6) rotate(0deg); }
            }
            #tm-hist-btn-pin.pin-egg-2x {
                position: relative; z-index: 5;
                
                animation: tm-pin-egg-2x-in 0.4s cubic-bezier(0.34,1.56,0.64,1) forwards,
                           tm-pin-egg-2x-breathe 1.6s ease-in-out 0.4s infinite;
            }
            #tm-hist-btn-pin.pin-egg-mushroom {
                position: relative; z-index: 5;
                font-size: 15px; line-height: 1;
                animation: tm-pin-egg-mushroom 1.1s ease-in-out forwards;
            }
            
            #tm-hist-panel.tm-pinned .tm-dock-trigger {
                opacity: 0.15 !important; pointer-events: none !important; cursor: default !important;
            }
            #tm-hist-titlebar:active { cursor: grabbing; }
            
            .tm-hist-title {
                flex: 1; min-width: 0;
                display: flex; align-items: center;
            }
            .tm-hist-badge {
                display: inline-flex; align-items: center; gap: 5px;
                padding: 3px 10px 3px 7px; border-radius: 6px;
                border: 1px solid ${dark ? '#8a6d3f' : '#d9d0bd'};
                background: ${dark
                    ? 'linear-gradient(180deg, #23180d 0%, #14100a 100%)'
                    : 'linear-gradient(180deg, #faf7f0 0%, #f2ecdd 100%)'};
                white-space: nowrap; overflow: hidden;
                box-shadow: inset 0 1px 0 ${dark ? 'rgba(201,168,118,.14)' : 'rgba(255,255,255,.7)'};
            }
            .tm-hist-badge-icon {
                display: inline-flex; align-items: center; flex-shrink: 0;
                color: ${dark ? '#d9b876' : '#3d3527'};
            }
            .tm-hist-badge-text {
                font-size: 12px; font-weight: 700; letter-spacing: .3px;
                color: ${dark ? '#e8c988' : '#3d3527'};
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
            }
            .tm-hist-icon-btn {
                width: 26px; height: 26px; border-radius: 6px; border: none;
                background: transparent; cursor: pointer;
                display: flex; align-items: center; justify-content: center;
                color: ${C.sub}; transition: background 0.1s, color 0.1s;
                flex-shrink: 0;
            }
            .tm-hist-icon-btn:hover { background: ${C.rowHover}; color: ${C.text}; }
            .tm-hist-icon-btn svg { width: 14px; height: 14px; pointer-events: none; }
            .tm-hist-icon-btn.active { color: #1d9bf0; }
            
            .tm-hist-theme-toggle:hover { transform: scale(1.08); }
            .tm-hist-theme-toggle:active { transform: scale(0.92); }
            .tm-hist-theme-toggle svg { width: 14px; height: 14px; }
            
            .tm-theme-picker-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
            .tm-theme-picker-flyout {
                position: absolute; top: 50%; right: calc(100% + 6px);
                transform: translateY(-50%) translateX(6px); transform-origin: right center;
                display: flex; align-items: center; gap: 6px;
                opacity: 0; pointer-events: none;
                transition: opacity 0.15s ease, transform 0.15s ease;
                z-index: 5;
            }
            
            .tm-theme-picker-wrap:hover .tm-theme-picker-flyout,
            .tm-theme-picker-flyout.tm-force-open {
                opacity: 1; pointer-events: auto; transform: translateY(-50%) translateX(0);
            }
            .tm-theme-picker-swatch {
                flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%;
                border: 2px solid rgba(128,128,128,0.35); cursor: pointer; padding: 0;
                transition: transform 0.12s ease, border-color 0.12s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.25);
            }
            .tm-theme-picker-swatch:hover { transform: scale(1.15); }
            .tm-theme-picker-swatch:active { transform: scale(0.92); }
            .tm-theme-picker-swatch.tm-active { border-color: #1d9bf0; }
            .tm-theme-picker-custom-btn {
                flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%;
                border: 2px dashed rgba(128,128,128,0.5); cursor: pointer; padding: 0;
                display: flex; align-items: center; justify-content: center;
                background: transparent; color: rgba(128,128,128,0.9);
                transition: transform 0.12s ease, border-color 0.12s ease, color 0.12s ease;
            }
            .tm-theme-picker-custom-btn:hover { transform: scale(1.15); border-color: #1d9bf0; color: #1d9bf0; }
            .tm-theme-picker-custom-btn:active { transform: scale(0.92); }
            .tm-theme-picker-custom-btn.tm-active { border-color: #1d9bf0; border-style: solid; color: #1d9bf0; }
            .tm-hist-searchbar {
                
                flex: 1; min-width: 120px;
                position: relative; z-index: 3;
            }
            #tm-hist-search {
                width: 100%; padding: 4px 10px; border-radius: 99px;
                border: 1px solid ${C.border}; background: ${C.inputBg};
                color: ${C.text}; font-size: 12px;
                outline: none; box-sizing: border-box;
            }
            #tm-hist-search::placeholder { color: ${C.sub}; }
            
            .tm-search-drop {
                position: absolute; top: 100%; left: 0; right: 0;
                background: ${C.bg}; border: 1px solid ${C.border};
                border-top: none; border-radius: 0 0 12px 12px; z-index: 100;
                max-height: 224px; overflow-y: auto;
                box-shadow: 0 6px 18px rgba(0,0,0,0.24);
                scrollbar-width: thin; scrollbar-color: ${C.scrollbar} transparent;
                
                transform-origin: top center;
                transition: opacity 0.18s ease, transform 0.18s ease;
                opacity: 1; transform: scaleY(1);
            }
            
            .tm-search-drop.closing {
                opacity: 0; transform: scaleY(0.88);
                pointer-events: none;
            }
            .tm-search-hist-item {
                display: flex; align-items: center; gap: 6px;
                padding: 6px 8px; cursor: pointer;
                font-size: 12px; color: ${C.text};
                transition: background 0.08s;
            }
            .tm-search-hist-item:hover { background: ${C.rowHover}; }
            
            .tm-search-pin-btn {
                flex-shrink: 0; background: none; border: none; cursor: pointer;
                color: ${C.sub}; padding: 2px 3px; border-radius: 4px;
                display: flex; align-items: center; justify-content: center;
                transition: color 0.12s; line-height: 0;
            }
            .tm-search-pin-btn:hover { color: ${C.text}; }
            .tm-search-pin-btn.pinned { color: #1d9bf0; }
            .tm-search-hist-text {
                flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
                min-width: 0;
            }
            
            .tm-search-del-btn {
                flex-shrink: 0; background: none; border: none; cursor: pointer;
                color: ${C.sub}; padding: 2px 5px; border-radius: 4px;
                font-size: 10px; line-height: 1;
                opacity: 0; transition: opacity 0.1s, color 0.1s;
            }
            .tm-search-hist-item:hover .tm-search-del-btn { opacity: 1; }
            .tm-search-del-btn:hover { color: ${C.danger}; }
            
            .tm-search-drop-footer {
                display: flex; justify-content: flex-end;
                padding: 4px 8px 6px; border-top: 1px solid ${C.border};
            }
            .tm-search-clear-all {
                background: none; border: none; cursor: pointer;
                font-size: 11px; color: ${C.sub}; padding: 3px 7px;
                border-radius: 4px; transition: color 0.1s, background 0.08s;
            }
            .tm-search-clear-all:hover { color: ${C.danger}; background: ${C.rowHover}; }
            #tm-hist-body {
                flex: 1; overflow-y: auto; overflow-x: hidden;
                scrollbar-width: thin; scrollbar-color: ${C.scrollbar} transparent;
                
                contain: layout style paint;
            }
            
            #tm-hist-body::-webkit-scrollbar { width: 6px; }
            #tm-hist-body::-webkit-scrollbar-track { background: transparent; }
            #tm-hist-body::-webkit-scrollbar-thumb {
                background: ${C.scrollbar}; border-radius: 3px;
            }
            .tm-hist-group-header {
                position: sticky; top: 0; z-index: 2;
                padding: 5px 12px 4px;
                background: ${C.groupHdr}; border-bottom: 1px solid ${C.border};
                font-size: 11px; font-weight: 700; color: ${C.groupTxt};
                letter-spacing: 0.03em;
                cursor: pointer; user-select: none;
                display: flex; align-items: center; gap: 5px;
            }
            .tm-hist-group-header:hover { background: ${C.rowHover}; }
            .tm-hist-group-chevron {
                opacity: 0.55; flex-shrink: 0;
                display: inline-flex; align-items: center;
                transition: transform 0.18s ease;
            }
            .tm-hist-group-header.tm-collapsed .tm-hist-group-chevron {
                transform: rotate(-90deg);
            }
            .tm-hist-group-count {
                margin-left: auto; font-size: 10px; font-weight: 400;
                opacity: 0.5; padding-right: 2px;
            }
            
            .tm-hist-sel-all-btn {
                padding: 4px 10px; border-radius: 99px;
                border: 1px solid ${C.border}; background: transparent;
                color: ${C.sub}; font-size: 11px; cursor: pointer;
                transition: background 0.1s, color 0.1s;
                white-space: nowrap; flex-shrink: 0;
            }
            .tm-hist-sel-all-btn:hover { background: ${C.rowHover}; color: ${C.text}; }
            
            #tm-hist-gh-footer {
                flex-shrink: 0; box-sizing: border-box;
                height: 26px;
                border-top: 1px solid ${C.border};
                display: flex; align-items: center; justify-content: center;
                background: ${C.header};
            }
            #tm-hist-gh-footer a {
                font-size: 10px; color: ${C.sub}; text-decoration: none;
                opacity: 0.38; letter-spacing: 0.02em;
                font-family: system-ui, -apple-system, sans-serif;
                transition: opacity 0.18s;
                pointer-events: all;
            }
            #tm-hist-gh-footer a:hover { opacity: 0.85; text-decoration: underline; }
            .tm-hist-row {
                display: flex; align-items: flex-start; gap: 10px;
                padding: 8px 12px; border-bottom: 1px solid ${C.border};
                transition: background 0.08s;
                position: relative;
                
                contain: layout paint;
                
                content-visibility: auto;
                contain-intrinsic-size: auto 66px;
            }
            .tm-hist-row:hover { background: ${C.rowHover}; }
            .tm-hist-row.selected { background: rgba(29,155,240,0.08); }
            .tm-hist-row.selected::before {
                content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
                background: #1d9bf0; border-radius: 0 2px 2px 0;
            }
            
            #tm-hist-body.tm-scrolling .tm-hist-row { transition: none !important; }
            #tm-hist-body.tm-scrolling .tm-hist-row:hover { background: inherit; }
            .tm-hist-cb { flex-shrink: 0; margin-top: 3px; cursor: pointer; }
            .tm-hist-thumb-wrap {
                width: 44px; height: 44px; border-radius: 6px;
                overflow: hidden; flex-shrink: 0;
                background: ${C.thumbBg}; border: 1px solid ${C.border};
                display: flex; align-items: center; justify-content: center;
                position: relative; cursor: pointer;
            }
            .tm-hist-thumb-wrap img {
                width: 100%; height: 100%; object-fit: cover; display: block;
            }
            .tm-hist-thumb-wrap .tm-hist-video-icon { color: ${C.sub}; }
            .tm-hist-thumb-wrap .tm-hist-video-icon svg { width: 20px; height: 20px; }
            .tm-hist-info { flex: 1; min-width: 0; }
            .tm-hist-author {
                font-size: 12px; font-weight: 600; color: ${C.text};
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
            }
            .tm-hist-handle {
                font-size: 11px; color: ${C.sub}; margin-left: 4px; font-weight: 400;
            }
            .tm-hist-text {
                font-size: 11px; color: ${C.sub}; margin: 2px 0;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
                line-height: 1.4;
            }
            .tm-hist-date {
                font-size: 10px; color: ${C.sub}; opacity: 0.6;
                white-space: nowrap;
                font-variant-numeric: tabular-nums; flex-shrink: 0;
            }
            .tm-hist-meta {
                display: flex; align-items: center; gap: 5px;
                margin: 1px 0; min-width: 0;
            }
            .tm-hist-meta-sep {
                font-size: 10px; color: ${C.sub}; opacity: 0.4;
                flex-shrink: 0; line-height: 1;
            }
            .tm-hist-url {
                font-size: 10px; color: #1d9bf0;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
                cursor: pointer; min-width: 0; flex: 1;
            }
            .tm-hist-url:hover { text-decoration: underline; }
            .tm-hist-actions {
                display: flex; flex-direction: row; gap: 0px;
                flex-shrink: 0; align-items: center;
            }
            .tm-hist-act-btn {
                width: 22px; height: 22px; border-radius: 5px; border: none;
                background: transparent; cursor: pointer;
                display: flex; align-items: center; justify-content: center;
                color: ${C.sub}; transition: background 0.1s, color 0.1s;
            }
            .tm-hist-act-btn:hover { background: ${C.rowHover}; color: ${C.text}; }
            .tm-hist-act-btn.danger:hover { color: ${C.danger}; }
            .tm-hist-act-btn svg { width: 12px; height: 12px; pointer-events: none; }
            
            .tm-hist-act-btn.tm-fav-active { color: #e0245e; }
            .tm-hist-act-btn.tm-fav-btn:hover { color: #e0245e; }
            .tm-hist-act-btn.tm-fav-btn svg { width: 15px; height: 15px; }
            .tm-hist-act-btn.tm-copy-btn svg { width: 12px; height: 12px; }

            @keyframes tm-fav-shake {
                0%   { transform: scale(1)    rotate(0deg);   color: #e0245e; }
                15%  { transform: scale(1.45) rotate(-18deg); color: #ff4477; }
                35%  { transform: scale(1.3)  rotate( 16deg); color: #e0245e; }
                55%  { transform: scale(1.4)  rotate(-12deg); color: #ff4477; }
                75%  { transform: scale(1.25) rotate(  8deg); color: #e0245e; }
                90%  { transform: scale(1.1)  rotate( -4deg); }
                100% { transform: scale(1)    rotate(0deg);   color: #e0245e; }
            }
            .tm-fav-shake {
                animation: tm-fav-shake 0.52s cubic-bezier(.36,.07,.19,.97) forwards;
                pointer-events: none;
            }

            @keyframes tm-grid-fav-pop-off {
                0%   { transform: scale(1);    opacity: 1; }
                18%  { transform: scale(1.55); opacity: 1; }
                55%  { transform: scale(0.55); opacity: 0.7; }
                100% { transform: scale(0);    opacity: 0; }
            }
            .tm-grid-fav-shake {
                animation: tm-grid-fav-pop-off 0.38s cubic-bezier(.55,0,.75,.5) forwards !important;
                opacity: 1 !important;
                pointer-events: none !important;
            }
            
            .tm-hist-grid-cell .tm-grid-copy-btn {
                position: absolute; top: 5px; right: 5px;
                width: 24px; height: 24px; border-radius: 6px; border: none;
                background: rgba(0,0,0,0.38); backdrop-filter: blur(4px);
                color: rgba(255,255,255,0.75); cursor: pointer;
                display: flex; align-items: center; justify-content: center;
                opacity: 0; transition: opacity 0.15s, background 0.1s;
                z-index: 3; pointer-events: none;
            }
            .tm-hist-grid-cell .tm-grid-copy-btn svg { width: 12px; height: 12px; pointer-events: none; }
            .tm-hist-grid-cell:hover .tm-grid-copy-btn {
                opacity: 1; pointer-events: auto;
            }
            .tm-hist-grid-cell .tm-grid-copy-btn:hover { background: rgba(29,155,240,0.75); }
            
            .tm-hist-grid-cell.selected .tm-grid-copy-btn,
            .tm-hist-grid-cell.tm-grid-fav-lock .tm-grid-copy-btn { display: none; }
            
            .tm-hist-grid-cell .tm-grid-dl-btn {
                position: absolute; top: 33px; right: 5px;
                width: 24px; height: 24px; border-radius: 6px; border: none;
                background: rgba(0,0,0,0.38); backdrop-filter: blur(4px);
                color: rgba(255,255,255,0.75); cursor: pointer;
                display: flex; align-items: center; justify-content: center;
                opacity: 0; transition: opacity 0.15s, background 0.1s;
                z-index: 3; pointer-events: none;
            }
            .tm-hist-grid-cell .tm-grid-dl-btn svg { width: 12px; height: 12px; pointer-events: none; }
            .tm-hist-grid-cell:hover .tm-grid-dl-btn { opacity: 1; pointer-events: auto; }
            .tm-hist-grid-cell .tm-grid-dl-btn:hover { background: rgba(16,185,129,0.75); }
            .tm-hist-grid-cell.selected .tm-grid-dl-btn,
            .tm-hist-grid-cell.tm-grid-fav-lock .tm-grid-dl-btn { display: none; }

            .tm-hist-grid-cell .tm-grid-goto-btn {
                position: absolute; bottom: 0; right: 0;
                width: 30px; height: 30px;
                clip-path: polygon(100% 0, 100% 100%, 0 100%);
                background: rgba(29,155,240,0.65); backdrop-filter: blur(2px);
                border: none; cursor: pointer;
                display: flex; align-items: flex-end; justify-content: flex-end;
                padding: 4px;
                opacity: 0; transition: opacity 0.15s, background 0.1s;
                z-index: 3; pointer-events: none;
            }
            .tm-hist-grid-cell .tm-grid-goto-btn svg { width: 10px; height: 10px; pointer-events: none; }
            .tm-hist-grid-cell:hover .tm-grid-goto-btn { opacity: 1; pointer-events: auto; }
            .tm-hist-grid-cell .tm-grid-goto-btn:hover { background: rgba(29,155,240,0.9); }
            .tm-hist-grid-cell.selected .tm-grid-goto-btn,
            .tm-hist-grid-cell.tm-grid-fav-lock .tm-grid-goto-btn { display: none; }
            
            #tm-hist-thumb-grid {
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
                gap: 3px; padding: 8px;
            }
            .tm-hist-grid-cell {
                aspect-ratio: 1; border-radius: 6px; overflow: hidden;
                position: relative; cursor: pointer;
                background: ${C.thumbBg};
                
                contain: layout paint;
                
                content-visibility: auto;
                contain-intrinsic-size: auto 90px;
            }
            .tm-hist-grid-cell img {
                width: 100%; height: 100%; object-fit: cover; display: block;
            }
            .tm-hist-grid-cell .tm-hist-grid-overlay {
                position: absolute; inset: 0;
                background: linear-gradient(to top, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0) 55%);
                opacity: 0; transition: opacity 0.18s;
                display: flex; flex-direction: column; justify-content: flex-end;
                padding: 6px;
            }
            .tm-hist-grid-cell:hover .tm-hist-grid-overlay { opacity: 1; }
            .tm-hist-grid-overlay .gov-author {
                font-size: 11px; font-weight: 700; color: #fff;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
            }
            .tm-hist-grid-overlay .gov-text {
                font-size: 10px; color: rgba(255,255,255,0.82);
                overflow: hidden; text-overflow: ellipsis;
                display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                line-height: 1.3; margin-top: 2px;
            }
            .tm-hist-grid-cell .tm-hist-grid-nothumb {
                width: 100%; height: 100%; display: flex; align-items: center;
                justify-content: center; color: ${C.sub};
            }
            .tm-hist-grid-cell .tm-hist-grid-nothumb svg { width: 28px; height: 28px; }
            
            .tm-hist-grid-cell.selected {
                outline: 2.5px solid rgba(29,155,240,0.9);
                outline-offset: -2px;
            }
            .tm-hist-grid-cell.selected::after {
                content: '';
                position: absolute; top: 6px; right: 6px;
                width: 18px; height: 18px;
                background: rgba(29,155,240,1) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='2,6 5,9 10,3'/%3E%3C/svg%3E") center/12px no-repeat;
                border-radius: 50%;
                pointer-events: none; z-index: 2;
            }
            
            .tm-hist-grid-cell.tm-grid-fav-lock::after {
                content: '';
                
            }
            
            .tm-grid-fav-btn {
                position: absolute; top: 4px; left: 4px; z-index: 4;
                width: 22px; height: 22px; border-radius: 50%;
                border: none; cursor: pointer;
                background: rgba(0,0,0,0.45);
                display: flex; align-items: center; justify-content: center;
                opacity: 0; pointer-events: none;
                transition: opacity 0.15s, background 0.15s;
                color: rgba(255,255,255,0.85);
            }
            .tm-grid-fav-btn svg { width: 12px; height: 12px; pointer-events: none; }
            .tm-hist-grid-cell:hover .tm-grid-fav-btn {
                opacity: 1; pointer-events: auto;
            }
            
            .tm-grid-fav-btn.tm-fav-active {
                opacity: 1; pointer-events: auto;
                background: rgba(224,36,94,0.75); color: #fff;
            }
            .tm-grid-fav-btn.tm-fav-active:hover { background: rgba(224,36,94,0.95); }
            .tm-grid-fav-btn:not(.tm-fav-active):hover { background: rgba(0,0,0,0.65); }
            
            #tm-thumb-lb-backdrop {
                position: fixed; inset: 0; z-index: 10000010;
                background: rgba(0,0,0,0); backdrop-filter: blur(0px);
                display: flex; align-items: center; justify-content: center;
                transition: background 0.22s ease, backdrop-filter 0.22s ease;
                cursor: zoom-out;
            }
            #tm-thumb-lb-backdrop.tm-lb-visible {
                background: rgba(0,0,0,0.88);
                backdrop-filter: blur(6px);
            }
            #tm-thumb-lb-img {
                max-width: 92vw; max-height: 88vh;
                border-radius: 10px;
                box-shadow: 0 24px 80px rgba(0,0,0,0.7);
                object-fit: contain; display: block;
                transform-origin: var(--lb-ox, 50%) var(--lb-oy, 50%);
                transform: scale(var(--lb-scale-from, 0.18)) translate(var(--lb-tx, 0px), var(--lb-ty, 0px));
                opacity: 0;
                transition: transform 0.32s cubic-bezier(0.22,1,0.36,1),
                            opacity   0.22s ease;
                cursor: default;
            }
            #tm-thumb-lb-img.tm-lb-visible {
                transform: scale(1) translate(0,0);
                opacity: 1;
            }
            #tm-thumb-lb-nav {
                position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%);
                display: flex; align-items: center; gap: 12px;
                z-index: 10000011; pointer-events: none;
                opacity: 0; transition: opacity 0.2s;
            }
            #tm-thumb-lb-nav.tm-lb-visible { opacity: 1; pointer-events: auto; }
            .tm-lb-nav-btn {
                width: 36px; height: 36px; border-radius: 50%; border: none;
                background: rgba(255,255,255,0.15); backdrop-filter: blur(8px);
                color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center;
                transition: background 0.15s; font-size: 18px; line-height: 1;
            }
            .tm-lb-nav-btn:hover { background: rgba(255,255,255,0.28); }
            .tm-lb-nav-btn:disabled { opacity: 0.3; cursor: default; }
            #tm-lb-counter {
                font-size: 12px; color: rgba(255,255,255,0.7);
                font-family: system-ui, -apple-system, sans-serif;
                min-width: 40px; text-align: center;
            }
            #tm-thumb-lb-close {
                position: fixed; top: 18px; right: 22px; z-index: 10000012;
                width: 36px; height: 36px; border-radius: 50%; border: none;
                background: rgba(255,255,255,0.15); backdrop-filter: blur(8px);
                color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center;
                opacity: 0; transition: opacity 0.2s, background 0.15s;
                pointer-events: none;
            }
            #tm-thumb-lb-close.tm-lb-visible { opacity: 1; pointer-events: auto; }
            #tm-thumb-lb-close:hover { background: rgba(255,255,255,0.28); }
            
            .tm-hist-thumb-wrap.tm-has-thumb { cursor: zoom-in; }
            
            #tm-hist-footer {
                border-top: 1px solid ${C.border}; padding: 7px 12px;
                display: flex; align-items: center; gap: 8px;
                background: ${C.header}; flex-shrink: 0;
            }
            #tm-hist-footer.hidden { display: none; }
            .tm-hist-del-sel-btn {
                padding: 5px 12px; border-radius: 99px; border: none;
                background: ${C.danger}; color: #fff; font-size: 12px;
                font-weight: 600; cursor: pointer; transition: background 0.1s;
            }
            .tm-hist-del-sel-btn:hover { background: ${C.dangerHover}; }
            .tm-hist-cancel-edit {
                padding: 5px 12px; border-radius: 99px;
                border: 1px solid ${C.border}; background: transparent;
                color: ${C.text}; font-size: 12px; cursor: pointer;
            }
            
            #tm-hist-resize {
                position: absolute; bottom: 0; right: 0;
                width: 14px; height: 14px; cursor: se-resize;
                opacity: 0.4;
            }
            #tm-hist-resize:hover { opacity: 0.8; }

            .tm-dock-trigger {
                position: absolute; top: 50%; transform: translateY(-50%);
                width: 10px; height: 40px;
                background: transparent;
                border: none; padding: 0; cursor: pointer;
                display: flex; align-items: center; justify-content: center;
                opacity: 0;
                transition: opacity 0.25s ease;
                z-index: 5;
                
                overflow: visible;
            }
            #tm-hist-panel:hover .tm-dock-trigger { opacity: 1; }
            .tm-dock-trigger.left  { left: -1px; }
            .tm-dock-trigger.right { right: -1px; }

            .tm-dock-trigger svg {
                width: 6px; height: 28px;
                opacity: 0.18;
                transition: opacity 0.2s ease, filter 0.2s ease;
                pointer-events: none;
            }
            .tm-dock-trigger:hover svg {
                opacity: 0.55;
                filter: drop-shadow(0 0 2px rgba(29,155,240,0.7));
            }

            #tm-hist-panel {
                transition: transform 0.38s cubic-bezier(0.4,0,0.2,1),
                            opacity 0.28s ease;
                
                contain: layout style;
            }
            
            #tm-hist-panel.tm-animating { will-change: transform; }
            #tm-hist-panel.tm-docked { opacity: 0.0; pointer-events: none; }
            #tm-hist-panel.tm-docked .tm-dock-trigger { opacity: 1 !important; pointer-events: all; }

            #tm-hist-dock-tab {
                position: fixed; z-index: 999979;
                width: 6px;
                border-radius: 3px;
                pointer-events: all;
                cursor: pointer;
                
                overflow: visible;
                transition: opacity 0.22s ease;
            }
            #tm-hist-dock-tab:hover { width: 9px; transition: width 0.18s ease, opacity 0.22s ease; }

            #tm-hist-dock-tab.style-ruler {
                background: ${C.border};
            }
            #tm-hist-dock-tab.style-ruler::before {
                content: '';
                position: absolute; inset: 0;
                background: repeating-linear-gradient(
                    to bottom,
                    transparent       0px,
                    transparent       7px,
                    rgba(29,155,240,0.45) 7px,
                    rgba(29,155,240,0.45) 8px
                );
            }
            #tm-hist-dock-tab.style-ruler::after {
                content: '';
                position: absolute; inset: 0;
                background: repeating-linear-gradient(
                    to bottom,
                    transparent       0px,
                    transparent       23px,
                    rgba(29,155,240,0.85) 23px,
                    rgba(29,155,240,0.85) 25px
                );
                box-shadow: 0 0 3px rgba(29,155,240,0.4);
            }

            #tm-hist-dock-tab.style-ghost {
                background: repeating-linear-gradient(
                    to bottom,
                    transparent           0px,
                    transparent           3px,
                    rgba(128,128,128,0.22) 3px,
                    rgba(128,128,128,0.22) 4px
                );
                border-radius: 3px;
            }
            #tm-hist-dock-tab.style-ghost:hover {
                background: repeating-linear-gradient(
                    to bottom,
                    transparent           0px,
                    transparent           3px,
                    rgba(29,155,240,0.38)  3px,
                    rgba(29,155,240,0.38)  4px
                );
            }

            #tm-hist-dock-tab.style-notch {
                background: transparent;
                display: flex; align-items: center; justify-content: center;
            }
            #tm-hist-dock-tab.style-notch::before {
                content: '';
                width: 2px;
                height: 28px;
                border-radius: 1px;
                background: ${C.border};
                box-shadow: inset 0 0 0 1px rgba(29,155,240,0.2);
                transition: background 0.2s, box-shadow 0.2s;
            }
            #tm-hist-dock-tab.style-notch:hover::before {
                background: rgba(29,155,240,0.45);
                box-shadow: 0 0 4px rgba(29,155,240,0.5);
            }

            #tm-hist-dock-tab.style-pill {
                background: transparent;
                display: flex; align-items: center; justify-content: center;
            }
            #tm-hist-dock-tab.style-pill::before {
                content: '';
                width: 4px; height: 36px; border-radius: 2px;
                background: rgba(29,155,240,0.55);
                transition: height 0.2s, background 0.2s, box-shadow 0.2s;
            }
            #tm-hist-dock-tab.style-pill:hover::before {
                height: 52px;
                background: rgba(29,155,240,0.85);
                box-shadow: 0 0 8px rgba(29,155,240,0.5);
            }

            #tm-hist-dock-tab.style-arrow {
                background: transparent;
                display: flex; align-items: center; justify-content: center;
            }
            #tm-hist-dock-tab.style-arrow::before {
                content: '';
                width: 0; height: 0;
                border-top: 7px solid transparent;
                border-bottom: 7px solid transparent;
                opacity: 0.35;
                transition: opacity 0.2s, filter 0.2s;
            }
            
            #tm-hist-dock-tab.style-arrow.side-left::before  { border-left:  8px solid ${C.text}; }
            #tm-hist-dock-tab.style-arrow.side-right::before { border-right: 8px solid ${C.text}; }
            #tm-hist-dock-tab.style-arrow:hover::before {
                opacity: 1;
                filter: drop-shadow(0 0 3px rgba(29,155,240,0.6));
                border-left-color:  #1d9bf0;
                border-right-color: #1d9bf0;
            }

            #tm-hist-dock-tab.style-dots {
                background: transparent;
                display: flex; flex-direction: column;
                align-items: center; justify-content: center; gap: 6px;
            }
            #tm-hist-dock-tab.style-dots::before,
            #tm-hist-dock-tab.style-dots::after {
                display: none; 
            }
            .tm-dock-dot {
                width: 3px; height: 3px; border-radius: 50%;
                background: ${C.sub}; opacity: 0.45;
                transition: background 0.18s, opacity 0.18s, transform 0.18s;
            }
            #tm-hist-dock-tab.style-dots:hover .tm-dock-dot {
                background: #1d9bf0; opacity: 1;
            }
            #tm-hist-dock-tab.style-dots:hover .tm-dock-dot:nth-child(2) {
                transform: scale(1.4);
            }
            
            .tm-hist-empty {
                display: flex; flex-direction: column; align-items: center;
                justify-content: center; padding: 48px 24px;
                color: ${C.sub}; font-size: 13px; line-height: 1.6; gap: 12px; text-align: center;
            }
            .tm-hist-empty svg { width: 34px; height: 34px; opacity: 0.35; }
            
            #tm-hist-zoom {
                position: fixed; z-index: 999999;
                width: 200px; height: 200px; border-radius: 8px;
                overflow: hidden; pointer-events: none;
                box-shadow: 0 8px 24px rgba(0,0,0,0.4);
                border: 2px solid rgba(255,255,255,0.2);
            }
            #tm-hist-zoom img { width: 100%; height: 100%; object-fit: cover; }
            
            #tm-group-tab-bar {
                position: relative;
                display: flex; align-items: center;
                padding: 0; gap: 0;
                border-bottom: 1px solid ${C.border};
                flex-shrink: 0; overflow: hidden;
                background: ${C.header};
            }
            
            #tm-group-tab-bar.tm-gtab-expanded { align-items: flex-start; }
            #tm-group-tab-scroll {
                display: flex; align-items: center; gap: 4px;
                padding: 5px 8px;
                overflow-x: auto; overflow-y: hidden;
                scrollbar-width: none; flex: 1; min-width: 0;
                scroll-behavior: smooth;
                max-height: 36px;
            }
            #tm-group-tab-scroll::-webkit-scrollbar { display: none; }
            
            #tm-group-tab-scroll.tm-gtab-expanded {
                flex-wrap: wrap;
                overflow-x: hidden; overflow-y: auto;
                max-height: 120px;
                align-content: flex-start;
            }
            .tm-gtab-pill {
                display: inline-flex; align-items: center; gap: 4px;
                padding: 3px 9px 3px 7px;
                border-radius: 99px; border: 1px solid transparent;
                font-size: 11px; font-weight: 500;
                color: ${C.sub}; white-space: nowrap;
                cursor: pointer; flex-shrink: 0;
                background: transparent;
                font-family: inherit; line-height: 1.4;
                transition: background .12s, border-color .12s, color .12s, opacity .12s;
            }
            .tm-gtab-pill:hover {
                background: ${C.rowHover};
                color: ${C.text};
                border-color: ${C.border};
            }
            .tm-gtab-pill.active {
                background: rgba(29,155,240,.14);
                border-color: rgba(29,155,240,.45);
                color: #1d9bf0;
            }
            .tm-gtab-pill svg {
                width: 12px; height: 12px;
                flex-shrink: 0; pointer-events: none;
            }
            .tm-gtab-scroll-btn {
                flex-shrink: 0; width: 22px; height: 100%;
                border: none; background: transparent;
                cursor: pointer; display: none;
                align-items: center; justify-content: center;
                color: ${C.sub}; padding: 0;
                transition: background .1s, color .1s, transform .2s ease;
                z-index: 2;
            }
            .tm-gtab-scroll-btn:hover { background: ${C.rowHover}; color: ${C.text}; }
            .tm-gtab-scroll-btn.visible { display: flex; }
            .tm-gtab-scroll-btn svg { width: 10px; height: 10px; pointer-events: none; }
            .tm-gtab-scroll-btn.left {
                border-right: 1px solid ${C.border};
            }
            .tm-gtab-scroll-btn.right {
                border-left: 1px solid ${C.border};
            }
            
            #tm-group-tab-bar.tm-gtab-expanded .tm-gtab-scroll-btn.right {
                height: 26px; transform: rotate(90deg);
            }
            
            .tm-gtab-drag-handle {
                display: none;
                align-items: center; justify-content: center;
                width: 14px; flex-shrink: 0;
                cursor: grab; touch-action: none;
                color: ${C.sub}; opacity: .55;
            }
            .tm-gtab-drag-handle:hover { opacity: 1; }
            #tm-group-tab-scroll.tm-gtab-expanded .tm-gtab-drag-handle { display: inline-flex; }
            
            .tm-gtab-pill.tm-gtab-dragging {
                opacity: .35;
                user-select: none;
            }
            
            .tm-gtab-ghost {
                position: fixed;
                pointer-events: none;
                z-index: 999999;
                opacity: 1;
                transform: scale(1.06);
                box-shadow: 0 6px 18px rgba(0,0,0,.5);
                transition: none;
            }
            
            body.tm-gtab-drag-active, body.tm-gtab-drag-active * {
                cursor: grabbing !important;
            }
            
            .tm-gtab-dot {
                width: 6px; height: 6px;
                border-radius: 50%;
                background: #fbbf24;
                flex-shrink: 0;
                box-shadow: 0 0 4px rgba(251,191,36,0.7);
                animation: tm-dot-pop 0.25s cubic-bezier(0.34,1.56,0.64,1);
            }
            @keyframes tm-dot-pop {
                from { transform: scale(0); opacity: 0; }
                to   { transform: scale(1); opacity: 1; }
            }

            .tm-hist-grid-cell.tm-text-author-cell {
                aspect-ratio: unset !important;
                min-height: 130px;
                height: 130px;
                cursor: pointer;
            }

            .tm-text-pin-btn {
                position: absolute;
                top: 5px; right: 5px;
                width: 22px; height: 22px;
                border-radius: 50%;
                background: rgba(0,0,0,.5);
                border: none; cursor: pointer;
                display: flex; align-items: center; justify-content: center;
                z-index: 5;
                font-size: 13px; line-height: 1;
                transition: opacity .15s, background .12s;
                pointer-events: auto;
            }

            @keyframes tm-text-row-in {
                from { opacity: 0; transform: translateY(-5px); }
                to   { opacity: 1; transform: translateY(0); }
            }
            .tm-text-author-item {
                animation: tm-text-row-in 0.16s ease-out both;
            }
        `;

        const panel = document.createElement('div');
        panel.id = 'tm-hist-panel';
        panel.setAttribute('data-ss-preserve', '');
        panel.style.cssText = `left:${pos.x}px; top:${pos.y}px; width:${pos.w}px; height:${pos.h}px;`;

        if (DockState.side && !_pinned) {
            panel.style.opacity = '0';
            panel.style.transition = 'none';
        }

        const titlebar = document.createElement('div');
        titlebar.id = 'tm-hist-titlebar';

        const titleIcon = document.createElement('span');
        titleIcon.className = 'tm-hist-badge-icon';
        titleIcon.innerHTML = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><path d="M8 1.2 13.2 3v4.1c0 3.4-2.2 5.9-5.2 7.1-3-1.2-5.2-3.7-5.2-7.1V3L8 1.2z"/><path d="M8 5.4 8.9 7.2 10.9 7.5 9.45 8.9 9.8 10.9 8 9.95 6.2 10.9 6.55 8.9 5.1 7.5 7.1 7.2 8 5.4z" fill="currentColor" stroke="none"/></svg>`;

        const titleText = document.createElement('span');
        titleText.className = 'tm-hist-badge-text';
        titleText.textContent = 'History';

        const titleBadge = document.createElement('span');
        titleBadge.className = 'tm-hist-badge';
        titleBadge.title = 'Download History';
        titleBadge.appendChild(titleIcon);
        titleBadge.appendChild(titleText);

        const titleEl = document.createElement('span');
        titleEl.className = 'tm-hist-title';
        titleEl.appendChild(titleBadge);

        const themeToggleBtn = document.createElement('button');
        themeToggleBtn.className = 'tm-hist-icon-btn tm-hist-theme-toggle';
        themeToggleBtn.type = 'button';
        themeToggleBtn.title = dark ? 'Switch to light theme' : 'Switch to dark theme';
        themeToggleBtn.innerHTML = dark
            ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
            : '<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"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';

        function _applyHistoryThemeWithFade(applyFn) {
            applyFn();
            const FADE_MS = 160;
            panel.classList.add('tm-hist-theme-fade');
            panel.style.opacity = '0';
            const _wasDocked = !!DockState.side;
            setTimeout(() => {
                if (typeof panel._tmCleanup === 'function') panel._tmCleanup();
                if (_historyUndoTimer) { clearTimeout(_historyUndoTimer); _historyUndoTimer = null; _historyUndoBuffer = null; }
                panel.remove();
                _cleanZoom();
                showHistoryPanel();
                if (_wasDocked) return;
                const fresh = document.getElementById('tm-hist-panel');
                if (fresh) {
                    fresh.style.opacity = '0';
                    void fresh.offsetWidth;
                    fresh.classList.add('tm-hist-theme-fade');
                    fresh.style.opacity = '1';
                    fresh.addEventListener('transitionend', function _rmFade(ev) {
                        if (ev.propertyName !== 'opacity') return;
                        fresh.classList.remove('tm-hist-theme-fade');
                        fresh.style.opacity = '';
                        fresh.removeEventListener('transitionend', _rmFade);
                    });
                }
            }, FADE_MS);
        }

        themeToggleBtn.addEventListener('click', e => {
            e.stopPropagation();
            _applyHistoryThemeWithFade(() => _toggleAppTheme('history'));
        });

        const themePickerWrap = _buildThemePickerFlyout('history', presetKey => {
            _applyHistoryThemeWithFade(() => _setActiveThemePreset(presetKey, 'history'));
        });
        themePickerWrap.appendChild(themeToggleBtn);

        const SVG_LIST  = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><line x1="1" y1="4" x2="15" y2="4"/><line x1="1" y1="8" x2="15" y2="8"/><line x1="1" y1="12" x2="15" y2="12"/></svg>`;
        const SVG_GRID  = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="6" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/><rect x="9" y="9" width="6" height="6" rx="1"/></svg>`;
        const SVG_EDIT  = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M11 2l3 3-8 8H3v-3L11 2z"/></svg>`;
        const SVG_EXP   = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M8 2v8M5 7l3 4 3-4"/><line x1="2" y1="13" x2="14" y2="13"/></svg>`;
        const SVG_CLOSE = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;

        const btnViewToggle = _mkIconBtn(viewMode === 'list' ? SVG_GRID : SVG_LIST,
            viewMode === 'list' ? 'Switch to Thumbnail' : 'Switch to List');
        const btnEdit  = _mkIconBtn(SVG_EDIT,  'Edit mode');
        const btnExp   = _mkIconBtn(SVG_EXP,   'Export');
        const btnClose = _mkIconBtn(SVG_CLOSE, 'Close');

        const SVG_PIN = `<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="10" cy="5" r="3.2"/>
            <circle cx="8.7" cy="3.7" r="0.55" fill="currentColor" stroke="none" opacity="0.6"/>
            <line x1="7.6" y1="7.3" x2="3.6" y2="14.4"/>
        </svg>`;
        const SVG_PIN_FILLED = `<svg viewBox="0 0 16 16" width="15" height="15" fill="currentColor" stroke="currentColor" stroke-width="0.3" stroke-linecap="round" stroke-linejoin="round">
            <circle cx="10" cy="5" r="3.2"/>
            <circle cx="8.7" cy="3.7" r="0.6" fill="${C.bg}" stroke="none"/>
            <path d="M7.4 7.05 L8.3 7.6 L3.6 14.4 Z"/>
            <line x1="1.6" y1="13.5" x2="5.6" y2="14.7" stroke-width="1.1" opacity="0.55"/>
        </svg>`;

        const btnPin = document.createElement('button');
        btnPin.id = 'tm-hist-btn-pin';
        btnPin.type = 'button';
        btnPin.innerHTML = _pinned ? SVG_PIN_FILLED : SVG_PIN;
        btnPin.title = _pinned ? 'Unpin panel (currently persistent across navigation)' : 'Pin panel (keep visible across SPA navigation)';
        if (_pinned) {
            btnPin.classList.add('pinned');
            panel.classList.add('tm-pinned');
        }

        btnPin.addEventListener('click', e => {
            e.stopPropagation();
            _pinned = !_pinned;
            GM_setValue(KEY_HIST_PINNED, _pinned);
            if (_pinned) {
                btnPin.classList.remove('unpinning');
                void btnPin.offsetWidth;
                btnPin.classList.add('pinned');
                btnPin.innerHTML = SVG_PIN_FILLED;
                btnPin.title = 'Unpin panel (currently persistent across navigation)';
                panel.classList.add('tm-pinned');
            } else {
                _pinCloseAlertCount = 0;
                btnPin.classList.remove('pin-alert', 'pin-egg-2x', 'pin-egg-mushroom');
                btnPin.classList.remove('pinned');
                btnPin.classList.add('unpinning');
                btnPin.innerHTML = SVG_PIN;
                btnPin.title = 'Pin panel (keep visible across SPA navigation)';
                panel.classList.remove('tm-pinned');
                btnPin.addEventListener('animationend', () => btnPin.classList.remove('unpinning'), { once: true });
            }
        });

        titlebar.appendChild(titleEl);
        titlebar.appendChild(themePickerWrap);
        titlebar.appendChild(btnPin);
        const sortSel = document.createElement('select');
        sortSel.id = 'tm-hist-sort';
        sortSel.title = 'Sort order';
        sortSel.style.cssText = `
            background:${C.inputBg};color:${C.text};border:1px solid ${C.border};
            border-radius:6px;padding:3px 7px;font:13px system-ui;cursor:pointer;margin-left:4px;
            max-width:110px;
        `;
        [
            ['newest', T.hist_sort_newest || 'Newest'],
            ['oldest', T.hist_sort_oldest || 'Oldest'],
            ['author', T.hist_sort_author || 'Author A–Z'],
            ['count',  T.hist_sort_count  || 'Most Media'],
        ].forEach(([v, label]) => {
                const o = document.createElement('option');
                o.value = v; o.textContent = label; sortSel.appendChild(o);
            });
        sortSel.value = sortMode;
        sortSel.addEventListener('change', () => { sortMode = sortSel.value; render(); });
        titlebar.appendChild(sortSel);
        titlebar.appendChild(btnViewToggle);
        titlebar.appendChild(btnEdit);
        titlebar.appendChild(btnExp);
        titlebar.appendChild(btnClose);
        panel.appendChild(titlebar);

        const searchBar = document.createElement('div');
        searchBar.className = 'tm-hist-searchbar';
        const searchInput = document.createElement('input');
        searchInput.id = 'tm-hist-search';
        searchInput.type = 'search';
        searchInput.placeholder = T.hist_search_ph || '🔍  Search…';
        _searchDrop = document.createElement('div');
        _searchDrop.className = 'tm-search-drop';
        _searchDrop.style.display = 'none';
        searchBar.appendChild(searchInput);
        searchBar.appendChild(_searchDrop);

        const filterBarEl = document.createElement('div');
        filterBarEl.id = 'tm-hist-filter-bar';
        filterBarEl.style.cssText = `
            display:flex; align-items:center; gap:6px;
            padding:5px 8px 5px 12px; flex-shrink:0;
            border-bottom:1px solid ${C.border};
        `;
        const FILTER_OPTS = [
            ['all',   T.hist_filter_all   || 'All'],
            ['image', T.hist_filter_image || '🖼 Image'],
            ['video', T.hist_filter_video || '🎬 Video'],
            ['text',  T.hist_filter_text  || '📝 Text'],
        ];
        const _pillEls = {};
        FILTER_OPTS.forEach(([val, label]) => {
            const pill = document.createElement('button');
            pill.textContent = label;
            pill.style.cssText = `
                flex-shrink:0; padding:3px 10px; border-radius:9999px; border:none; cursor:pointer;
                font:600 12px system-ui; transition:background 0.15s,color 0.15s; white-space:nowrap;
                background:${val === 'all' ? C.badgeNew : 'transparent'};
                color:${val === 'all' ? '#fff' : C.sub};
            `;
            pill.addEventListener('click', () => {
                mediaFilter = val;
                activeGroupId = null;
                FILTER_OPTS.forEach(([v]) => {
                    _pillEls[v].style.background = v === val ? C.badgeNew : 'transparent';
                    _pillEls[v].style.color = v === val ? '#fff' : C.sub;
                });
                render();
            });
            _pillEls[val] = pill;
            filterBarEl.appendChild(pill);
        });
        filterBarEl.appendChild(searchBar);
        panel.appendChild(filterBarEl);

        const groupTabBar = document.createElement('div');
        groupTabBar.id = 'tm-group-tab-bar';

        const groupTabsBuilder = _createGroupTabsBuilder({
            groupTabBar, C, render,
            getMediaFilter: () => mediaFilter,
            getActiveGroupId: () => activeGroupId,
            setActiveGroupId: (v) => { activeGroupId = v; },
        });
        const buildGroupTabs = groupTabsBuilder.buildGroupTabs;

        buildGroupTabs();
        panel.appendChild(groupTabBar);
        const body = document.createElement('div');
        body.id = 'tm-hist-body';
        panel.appendChild(body);

        let _histScrollTimer = null;
        body.addEventListener('scroll', () => {
            body.classList.add('tm-scrolling');
            clearTimeout(_histScrollTimer);
            _histScrollTimer = setTimeout(() => body.classList.remove('tm-scrolling'), 150);
        }, { passive: true });

        const footer = document.createElement('div');
        footer.id = 'tm-hist-footer';
        footer.className = 'hidden';

        const selAllBtn = document.createElement('button');
        selAllBtn.className = 'tm-hist-sel-all-btn';
        selAllBtn.textContent = 'Select All';

        const delSelBtn = document.createElement('button');
        delSelBtn.className = 'tm-hist-del-sel-btn';

        const cancelEditBtn = document.createElement('button');
        cancelEditBtn.className = 'tm-hist-cancel-edit';
        cancelEditBtn.textContent = 'Cancel';

        footer.appendChild(selAllBtn);
        footer.appendChild(delSelBtn);
        footer.appendChild(cancelEditBtn);
        panel.appendChild(footer);

        const ghFooterEl = document.createElement('div');
        ghFooterEl.id = 'tm-hist-gh-footer';
        const ghLink = document.createElement('a');
        ghLink.href    = 'https://github.com/Startanuki07/Twitter-X-Media-Copy-Download';
        ghLink.target  = '_blank';
        ghLink.rel     = 'noopener noreferrer';
        ghLink.textContent = '★ Star on GitHub';
        ghFooterEl.appendChild(ghLink);
        panel.appendChild(ghFooterEl);

        const resizeHandle = document.createElement('div');
        resizeHandle.id = 'tm-hist-resize';
        resizeHandle.innerHTML = `<svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="2" y1="12" x2="12" y2="12"/><line x1="7" y1="12" x2="12" y2="7"/><line x1="12" y1="2" x2="12" y2="12"/></svg>`;
        panel.appendChild(resizeHandle);

        const SVG_NOTCH = `<svg viewBox="0 0 6 28" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="2" y="6"  width="2" height="2" rx="1" fill="currentColor"/>
  <rect x="2" y="13" width="2" height="2" rx="1" fill="currentColor"/>
  <rect x="2" y="20" width="2" height="2" rx="1" fill="currentColor"/>
</svg>`;
        const dockTriggerL = document.createElement('button');
        dockTriggerL.className = 'tm-dock-trigger left';
        dockTriggerL.title = 'Dock panel to left edge';
        dockTriggerL.innerHTML = SVG_NOTCH;
        panel.appendChild(dockTriggerL);
        const dockTriggerR = document.createElement('button');
        dockTriggerR.className = 'tm-dock-trigger right';
        dockTriggerR.title = 'Dock panel to right edge';
        dockTriggerR.innerHTML = SVG_NOTCH;
        panel.appendChild(dockTriggerR);

        document.body.appendChild(panel);

        function getRecords() {
            return _recordsCache ?? (_recordsCache = _loadAllRecords());
        }

        function getFiltered(records) {
            let result = records;
            if (activeGroupId === '__favorites__') {
                result = result.filter(r => r.favorited === true);
            } else if (activeGroupId === '__ungrouped__') {
                result = result.filter(r => !r.groupId);
            } else if (activeGroupId) {
                result = result.filter(r => r.groupId === activeGroupId);
            }
            if (query) {
                const q = query.toLowerCase();
                result = result.filter(r =>
                    r.displayName?.toLowerCase().includes(q) ||
                    r.screenName?.toLowerCase().includes(q) ||
                    r.text?.toLowerCase().includes(q)
                );
            }
            if (mediaFilter === 'all') {
                result = result.filter(r => !r.textOnly);
            } else if (mediaFilter === 'text') {
                result = result.filter(r => r.textOnly === true);
            } else if (mediaFilter === 'video') {
                result = result.filter(r => r.hasVideo && !r.textOnly);
            } else {
                result = result.filter(r => !r.hasVideo && !r.textOnly);
            }
            return result;
        }

        function _fmtGroupLabel(yyyymm) {
            const p = yyyymm.split('.');
            if (p.length !== 2) return yyyymm;
            const y = p[0];
            const m = parseInt(p[1], 10);
            if (_cachedDateFormat === 'western') {
                const names = ['','January','February','March','April','May','June',
                               'July','August','September','October','November','December'];
                return `${names[m] || p[1]} ${y}`;
            }
            return `${y}年 ${m}月`;
        }

        function render() {
            const records  = getRecords();
            const _sorters = {
                newest: (a, b) => b.ts - a.ts,
                oldest: (a, b) => a.ts - b.ts,
                author: (a, b) => (a.screenName || '').localeCompare(b.screenName || ''),
                count:  (a, b) => (b.count || 0) - (a.count || 0),
            };
            records.sort(_sorters[sortMode] || _sorters.newest);
            const filtered = getFiltered(records);
            searchInput.placeholder = `${T.hist_search_ph || '🔍  Search…'}  All ${records.length} entries`;
            delSelBtn.textContent = `Delete selected (${selectedIds.size})`;

            const _visibleRecs = filtered.filter(r => !collapsedGroups.has(r.yyyymm));
            _navRecords = _visibleRecs;
            const visibleIds = _visibleRecs.map(r => r.id);
            const allSelected = visibleIds.length > 0 && visibleIds.every(id => selectedIds.has(id));
            selAllBtn.textContent = allSelected ? 'Deselect All' : 'Select All';

            const _curFilterKey = `${mediaFilter}|${activeGroupId}|${sortMode}|${query}|${viewMode}`;
            const _filterChanged = _curFilterKey !== _lastFilterKey;
            if (_filterChanged) { _renderLimit = 80; _lastFilterKey = _curFilterKey; }

            const _savedScrollTop = _filterChanged ? 0 : body.scrollTop;

            if (_loadSentinelObserver) { _loadSentinelObserver.disconnect(); _loadSentinelObserver = null; }

            body.innerHTML = '';

            if (_groupTabsDirty || _lastTabFilter !== mediaFilter || _lastTabActiveId !== activeGroupId) {
                buildGroupTabs();
                _groupTabsDirty  = false;
                _lastTabFilter   = mediaFilter;
                _lastTabActiveId = activeGroupId;
            }

            const page = filtered.slice(0, _renderLimit);
            if (mediaFilter === 'text') {
                if (viewMode === 'list') renderTextAuthorList(filtered);
                else renderTextAuthorThumb(filtered);
            } else {
                if (viewMode === 'list') renderList(page);
                else renderThumb(page);
            }

            if (mediaFilter !== 'text' && filtered.length > _renderLimit) {
                if (!document.getElementById('tm-sentinel-css')) {
                    const _ss = document.createElement('style');
                    _ss.id  = 'tm-sentinel-css';
                    _ss.textContent = '@keyframes tm-sentinel-pulse{0%,100%{opacity:.2;transform:scale(.65)}50%{opacity:.6;transform:scale(1)}}';
                    document.head.appendChild(_ss);
                }
                const sentinel = document.createElement('div');
                sentinel.style.cssText = 'height:44px;display:flex;align-items:center;justify-content:center;gap:7px;';
                for (let _di = 0; _di < 3; _di++) {
                    const _dot = document.createElement('span');
                    _dot.style.cssText = `display:block;width:5px;height:5px;border-radius:50%;background:${dark ? 'rgba(255,255,255,.28)' : 'rgba(15,20,25,.3)'};animation:tm-sentinel-pulse 1.1s ease-in-out ${_di * 0.17}s infinite;`;
                    sentinel.appendChild(_dot);
                }
                _loadSentinelObserver = new IntersectionObserver(entries => {
                    if (!entries[0].isIntersecting) return;
                    _loadSentinelObserver.disconnect();
                    _loadSentinelObserver = null;
                    _renderLimit += 80;
                    render();
                }, { root: body, threshold: 0.1 });
                _loadSentinelObserver.observe(sentinel);
                body.appendChild(sentinel);
            }

            if (_savedScrollTop > 0) body.scrollTop = _savedScrollTop;

            btnViewToggle.innerHTML = viewMode === 'list' ? SVG_GRID : SVG_LIST;
            btnViewToggle.title     = viewMode === 'list' ? 'Switch to Thumbnail' : 'Switch to List';
            btnViewToggle.classList.toggle('active', true);
        }

        const textRenderer = _createTextRenderer({
            dark, C, selectedIds, body, render,
            _textPinnedAuthors, _savePinnedAuthors, _textAuthorCollapsed, _textAuthorAutoCollapsedApplied,
            getEditMode: () => editMode,
            getRenderEmpty: () => _renderEmpty,
            getToggleTextRowSel: () => _toggleTextRowSel,
            getDeleteOne: () => _deleteOne,
            getScrollTarget: () => _textAuthorScrollTarget,
            setScrollTarget: (v) => { _textAuthorScrollTarget = v; },
            setViewMode: (v) => { viewMode = v; },
        });
        const _getTextBmContentColor = textRenderer.getTextBmContentColor;
        const _showTextPreview       = textRenderer.showTextPreview;
        const renderTextAuthorList   = textRenderer.renderAuthorList;
        const renderTextAuthorThumb  = textRenderer.renderAuthorThumb;

        const crossNavCtl = _createCrossNavController({
            _openHistMediaLightbox,
            getNavRecords: () => _navRecords,
        });
        const _getCrossNavNeighbor    = crossNavCtl.getNeighbor;
        const _removeCrossNavButtons  = crossNavCtl.removeCrossNavButtons;
        const _injectHistCrossNav     = crossNavCtl.injectCrossNav;
        const _buildCrossNavButtons   = crossNavCtl.buildCrossNavButtons;

        const mediaRenderer = _createMediaRenderer({
            body, selectedIds, collapsedGroups, dark, delSelBtn,
            getFiltered, getRecords, _invalidateRecordsCache, _fmtGroupLabel,
            render, _openHistMediaLightbox, _injectHistCrossNav,
            getEditMode: () => editMode,
            getQuery: () => query,
            getAnchorIdx: () => anchorIdx,
            setAnchorIdx: (v) => { anchorIdx = v; },
        });
        const renderList         = mediaRenderer.renderList;
        const renderThumb        = mediaRenderer.renderThumb;
        const _renderEmpty       = mediaRenderer.renderEmpty;
        const _handleCheckbox    = mediaRenderer.handleCheckbox;
        const _toggleTextRowSel  = mediaRenderer.toggleTextRowSel;
        const _deleteOne         = mediaRenderer.deleteOne;
        const _thumbUrl          = mediaRenderer.thumbUrl;
        const _showZoom          = mediaRenderer.showZoom;
        const _hideZoom          = mediaRenderer.hideZoom;
        const _moveZoom          = mediaRenderer.moveZoom;

        function _openHistMediaLightbox(rec, startIdx, originEl) {
            const media = rec.mediaUrls && rec.mediaUrls.length > 0 ? rec.mediaUrls : null;
            _dialogOpenGlobal = true;
            if (media) {
                const imgUrls = media.filter(u => !u.includes('.mp4'));
                const vidUrls = media.filter(u =>  u.includes('.mp4'));

                const _launchPlayer = async (fallbackVids, fallbackImgs) => {
                    let freshVids = fallbackVids;
                    if (!_isTwitterDomain) {
                        if (fallbackImgs && fallbackImgs.length > 0) {
                            showImageLightbox(fallbackImgs, null, false, {
                                screenName: rec.screenName, displayName: rec.displayName,
                                date: rec.tweetDate || rec.downloadDate, id: rec.tweetId || rec.id, text: rec.text,
                            });
                            requestAnimationFrame(() => {
                                const gb = document.getElementById('tm-lb-gallery-btn');
                                if (gb) gb.style.display = 'none';
                            });
                        } else {
                            _openThumbLightbox(rec.thumbUrls || [], startIdx, originEl);
                        }
                        return;
                    }
                    if (rec.tweetId && fallbackVids.length > 0) {
                        try {
                            const apiData = await fetchTweetMediaFromAPI(rec.tweetId);
                            if (apiData && apiData.videos && apiData.videos.length > 0) {
                                freshVids = apiData.videos;
                            }
                        } catch (_) {  }
                    }
                    if (fallbackImgs && fallbackImgs.length > 0) {
                        showImageLightbox(fallbackImgs, freshVids.length > 0 ? freshVids : null, false, {
                            screenName: rec.screenName, displayName: rec.displayName,
                            date: rec.tweetDate || rec.downloadDate, id: rec.tweetId || rec.id, text: rec.text,
                        });
                        requestAnimationFrame(() => {
                            const gb = document.getElementById('tm-lb-gallery-btn');
                            if (gb) gb.style.display = 'none';
                        });
                    } else if (freshVids.length > 0) {
                        showFloatingVideoPlayer(freshVids, 0);
                        const _vpObs = new MutationObserver(() => {
                            if (!document.getElementById('tm-floating-video-modal')) {
                                _dialogOpenGlobal = false;
                                _vpObs.disconnect();
                            }
                        });
                        _vpObs.observe(document.body, { childList: true });
                    } else {
                        _openThumbLightbox(rec.thumbUrls || [], startIdx, originEl);
                    }
                };

                if (imgUrls.length > 0) {
                    _launchPlayer(vidUrls, imgUrls);
                } else if (vidUrls.length > 0) {
                    _launchPlayer(vidUrls, null);
                } else {
                    _openThumbLightbox(rec.thumbUrls || [], startIdx, originEl);
                }
            } else {
                _openThumbLightbox(rec.thumbUrls || [], startIdx, originEl);
            }
        }

        function _openThumbLightbox(urls, startIdx, originEl) {
            document.getElementById('tm-thumb-lb-backdrop')?.remove();

            _dialogOpenGlobal = true;

            const allUrls = (urls || []).map(u => {
                try {
                    if (u.includes('pbs.twimg.com') && u.includes('/media/')) {
                        const obj = new URL(u);
                        obj.searchParams.set('name', 'large');
                        return obj.toString();
                    }
                } catch (_) {}
                return u;
            });
            if (!allUrls.length) return;

            let idx = Math.max(0, Math.min(startIdx, allUrls.length - 1));

            const vpW = window.innerWidth, vpH = window.innerHeight;

            function _calcOrigin(rect) {
                if (!rect) return { ox: '50%', oy: '50%', tx: '0px', ty: '0px', scale: 0.18 };
                const cx = rect.left + rect.width  / 2;
                const cy = rect.top  + rect.height / 2;
                const finalW = Math.min(vpW * 0.92, vpH * 0.88 * 1.5);
                const finalH = finalW / 1.5;
                const imgCx = vpW / 2, imgCy = vpH / 2;
                const scaleFrom = Math.max(rect.width / finalW, 0.06);
                const ox = ((cx - imgCx) / finalW * 100 + 50).toFixed(2) + '%';
                const oy = ((cy - imgCy) / finalH * 100 + 50).toFixed(2) + '%';
                return { ox, oy, tx: '0px', ty: '0px', scale: scaleFrom };
            }

            const backdrop = document.createElement('div');
            backdrop.id = 'tm-thumb-lb-backdrop';

            const imgEl = document.createElement('img');
            imgEl.id = 'tm-thumb-lb-img';
            imgEl.alt = '';

            const closeBtn = document.createElement('button');
            closeBtn.id = 'tm-thumb-lb-close';
            closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
            closeBtn.title = 'Close';
            closeBtn.setAttribute('aria-label', 'Close preview');

            const nav = document.createElement('div');
            nav.id = 'tm-thumb-lb-nav';

            const prevBtn = document.createElement('button');
            prevBtn.className = 'tm-nav-fade-btn';
            prevBtn.innerHTML = `<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><polyline points="10,3 5,8 10,13"/></svg>`;
            prevBtn.title = 'Previous';

            const counter = document.createElement('span');
            counter.id = 'tm-lb-counter';

            const nextBtn = document.createElement('button');
            nextBtn.className = 'tm-nav-fade-btn';
            nextBtn.innerHTML = `<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><polyline points="6,3 11,8 6,13"/></svg>`;
            nextBtn.title = 'Next';

            nav.appendChild(prevBtn);
            nav.appendChild(counter);
            nav.appendChild(nextBtn);
            backdrop.appendChild(imgEl);
            document.body.appendChild(backdrop);
            document.body.appendChild(closeBtn);
            document.body.appendChild(nav);

            function _showIdx(newIdx, fromEl) {
                idx = Math.max(0, Math.min(newIdx, allUrls.length - 1));
                counter.textContent = allUrls.length > 1 ? `${idx + 1} / ${allUrls.length}` : '';
                prevBtn.disabled = (idx === 0);
                nextBtn.disabled = (idx === allUrls.length - 1);
                nav.style.display = allUrls.length > 1 ? '' : 'none';

                const o = _calcOrigin(fromEl ? fromEl.getBoundingClientRect() : null);
                imgEl.style.setProperty('--lb-ox', o.ox);
                imgEl.style.setProperty('--lb-oy', o.oy);
                imgEl.style.setProperty('--lb-scale-from', o.scale);
                imgEl.classList.remove('tm-lb-visible');

                imgEl.src = allUrls[idx];
                imgEl.onload = () => {
                    requestAnimationFrame(() => {
                        imgEl.classList.add('tm-lb-visible');
                    });
                };
                if (imgEl.complete && imgEl.naturalWidth) {
                    requestAnimationFrame(() => imgEl.classList.add('tm-lb-visible'));
                }
            }

            function _close() {
                backdrop.classList.remove('tm-lb-visible');
                closeBtn.classList.remove('tm-lb-visible');
                nav.classList.remove('tm-lb-visible');
                imgEl.classList.remove('tm-lb-visible');
                setTimeout(() => {
                    backdrop.remove();
                    closeBtn.remove();
                    nav.remove();
                    _dialogOpenGlobal = false;
                }, 320);
                document.removeEventListener('keydown', _onKey);
            }

            function _onKey(e) {
                if (e.key === 'Escape') _close();
                if (e.key === 'ArrowLeft'  && idx > 0)                  _showIdx(idx - 1, null);
                if (e.key === 'ArrowRight' && idx < allUrls.length - 1) _showIdx(idx + 1, null);
            }

            backdrop.addEventListener('click', (e) => { if (e.target === backdrop) _close(); });
            closeBtn.addEventListener('click', _close);
            prevBtn.addEventListener('click', (e) => { e.stopPropagation(); _showIdx(idx - 1, null); });
            nextBtn.addEventListener('click', (e) => { e.stopPropagation(); _showIdx(idx + 1, null); });
            imgEl.addEventListener('click', (e) => e.stopPropagation());
            document.addEventListener('keydown', _onKey);

            _showIdx(startIdx, originEl);
            requestAnimationFrame(() => {
                backdrop.classList.add('tm-lb-visible');
                closeBtn.classList.add('tm-lb-visible');
                if (allUrls.length > 1) nav.classList.add('tm-lb-visible');
            });
        }

        const importExportCtl = _createImportExportController({ getRecords, btnExp, render, _invalidateRecordsCache });
        const _exportCSV    = importExportCtl.exportCSV;
        const _exportJSON   = importExportCtl.exportJSON;
        const _importJSON   = importExportCtl.importJSON;
        const _showExpMenu  = importExportCtl.showExportMenu;

        function _mkIconBtn(svg, title) {
            const b = document.createElement('button');
            b.className = 'tm-hist-icon-btn';
            b.innerHTML = svg;
            b.title = title;
            return b;
        }

        btnViewToggle.addEventListener('click', () => {
            viewMode = viewMode === 'list' ? 'thumb' : 'list';
            GM_setValue(KEY_HISTORY_VIEW_MODE, viewMode);
            render();
        });
        btnClose.addEventListener('click', () => {
            if (_pinned) {
                btnPin.classList.remove('pin-alert', 'pin-egg-2x', 'pin-egg-mushroom');
                void btnPin.offsetWidth;
                _pinCloseAlertCount++;

                if (_pinCloseAlertCount === 1) {
                    btnPin.classList.add('pin-alert');
                    btnPin.addEventListener('animationend', () => btnPin.classList.remove('pin-alert'), { once: true });
                } else if (_pinCloseAlertCount === 2) {
                    btnPin.classList.add('pin-egg-2x');
                } else {
                    btnPin.innerHTML = '🍄';
                    btnPin.classList.add('pin-egg-mushroom');
                }
                return;
            }

            if (DockState.side) {
                _retract();
                return;
            }

            if (typeof panel._tmCleanup === 'function') panel._tmCleanup();
            if (_historyUndoTimer) { clearTimeout(_historyUndoTimer); _historyUndoTimer = null; _historyUndoBuffer = null; }
            panel.remove();
            _cleanZoom();
        });

        btnEdit.addEventListener('click', () => {
            editMode = !editMode;
            selectedIds.clear(); anchorIdx = -1;
            footer.classList.toggle('hidden', !editMode);
            btnEdit.classList.toggle('active', editMode);
            render();
        });

        selAllBtn.addEventListener('click', () => {
            const filtered = getFiltered(getRecords());
            const visibleIds = filtered
                .filter(r => !collapsedGroups.has(r.yyyymm) && !r.favorited)
                .map(r => r.id);
            const allSelected = visibleIds.length > 0 && visibleIds.every(id => selectedIds.has(id));
            if (allSelected) {
                visibleIds.forEach(id => selectedIds.delete(id));
            } else {
                visibleIds.forEach(id => selectedIds.add(id));
                const firstVisible = filtered.find(r => !collapsedGroups.has(r.yyyymm) && !r.favorited);
                if (firstVisible) {
                    anchorIdx = filtered.indexOf(firstVisible);
                }
            }
            render();
        });

        delSelBtn.addEventListener('click', () => {
            if (!selectedIds.size) return;
            const deletedTweetIds = _batchDeleteRecords(selectedIds);
            const deletedSet = new Set(deletedTweetIds);
            deletedTweetIds.forEach(tid => _downloadedIds.delete(tid));
            document.querySelectorAll('article[role="article"]').forEach(art => {
                const tid = _getTweetIdFromArticle(art);
                if (!tid || !deletedSet.has(tid)) return;
                const btn = art.querySelector('.' + BUTTON_CLASS);
                if (btn) _removeHistoryBadge(btn);
            });
            selectedIds.clear(); anchorIdx = -1;
            _invalidateRecordsCache();
            render();
        });

        cancelEditBtn.addEventListener('click', () => {
            editMode = false; selectedIds.clear(); anchorIdx = -1;
            footer.classList.add('hidden'); btnEdit.classList.remove('active');
            render();
        });

        btnExp.addEventListener('click', (e) => {
            e.stopPropagation();
            _showExpMenu();
        });
        btnExp.title = 'Export / Import';

        searchInput.addEventListener('input', () => {
            query = searchInput.value.trim();
            clearTimeout(_searchRenderTimer);
            _searchRenderTimer = setTimeout(render, 150);
            if (query) _hideSearchDrop();
            else        _showSearchDrop();
        });
        searchInput.addEventListener('focus', () => {
            if (!query) _showSearchDrop();
        });
        searchInput.addEventListener('blur', () => {
            setTimeout(() => {
                const text = searchInput.value.trim();
                if (text.length >= 2) _addSearchHist(text);
                _hideSearchDrop();
            }, 160);
        });
        searchInput.addEventListener('keydown', e => {
            if (e.key === 'Enter') {
                const text = searchInput.value.trim();
                if (text) _addSearchHist(text);
                _hideSearchDrop();
            }
            if (e.key === 'Escape') {
                searchInput.value = ''; query = ''; render();
                _hideSearchDrop(); searchInput.blur();
            }
        });
        _searchDrop.addEventListener('mouseleave', e => {
            const bar = _searchDrop.parentElement;
            if (bar && bar.contains(e.relatedTarget)) return;
            _hideSearchDrop();
        });

        panel.addEventListener('tm-hist-refresh', () => {
            _invalidateRecordsCache();
            _groupTabsDirty = true;
            render();
        });

        panel.addEventListener('tm-hist-toggle-peek', () => {
            if (DockState.peeked) _retract();
            else _peekOut();
        });

        panel.addEventListener('tm-hist-force-peek', () => {
            if (DockState.side && !DockState.peeked) _peekOut();
        });
        panel.addEventListener('tm-hist-force-retract', () => {
            if (DockState.side && DockState.peeked) _retract();
        });

        panel.addEventListener('tm-hist-reset-pos', () => {
            const defX = Math.max(8, Math.min(Math.round(window.innerWidth * 0.55 - 195), window.innerWidth - 398));
            const defY = 60, defW = 390, defH = 540;
            panel.style.transition = 'none';
            panel.style.width  = defW + 'px';
            panel.style.height = defH + 'px';
            if (DockState.side) {
                DockState.snapshot = { left: defX, top: defY, width: defW, height: defH };
                if (DockState.peeked) panel.style.top = defY + 'px';
                if (DockState.tabEl) {
                    const { h, top } = _dockTabGeometry();
                    DockState.tabEl.style.height = h   + 'px';
                    DockState.tabEl.style.top    = top + 'px';
                }
            } else {
                panel.style.left = defX + 'px';
                panel.style.top  = defY + 'px';
            }
            panel.offsetWidth;
            panel.style.transition = '';
        });

        const _panelAC = new AbortController();
        const _acSignal = { signal: _panelAC.signal };
        panel._tmCleanup = () => {
            _panelAC.abort();
            if (_loadSentinelObserver) { _loadSentinelObserver.disconnect(); _loadSentinelObserver = null; }
        };

        let _dragging = false, _dx = 0, _dy = 0;
        titlebar.addEventListener('mousedown', (e) => {
            if (e.button !== 0 || e.target.classList.contains('tm-hist-icon-btn') || e.target.tagName === 'SELECT') return;
            _dragging = true;
            _dx = e.clientX - panel.getBoundingClientRect().left;
            _dy = e.clientY - panel.getBoundingClientRect().top;
            e.preventDefault();
        });
        document.addEventListener('mousemove', (e) => {
            if (!_dragging) return;
            let nx = e.clientX - _dx;
            let ny = e.clientY - _dy;
            nx = Math.max(0, Math.min(nx, window.innerWidth  - panel.offsetWidth));
            ny = Math.max(0, Math.min(ny, window.innerHeight - 60));
            panel.style.left = nx + 'px';
            panel.style.top  = ny + 'px';
        }, _acSignal);
        document.addEventListener('mouseup', () => {
            if (!_dragging) return;
            _dragging = false;
            _savePos();
        }, _acSignal);

        let _resizing = false, _rsx = 0, _rsy = 0, _rsw = 0, _rsh = 0;
        resizeHandle.addEventListener('mousedown', (e) => {
            _resizing = true;
            _rsx = e.clientX; _rsy = e.clientY;
            _rsw = panel.offsetWidth; _rsh = panel.offsetHeight;
            e.preventDefault(); e.stopPropagation();
        });
        document.addEventListener('mousemove', (e) => {
            if (!_resizing) return;
            const nw = Math.max(300, Math.min(_rsw + (e.clientX - _rsx), 680));
            const nh = Math.max(280, Math.min(_rsh + (e.clientY - _rsy), window.innerHeight - 20));
            panel.style.width  = nw + 'px';
            panel.style.height = nh + 'px';
        }, _acSignal);
        document.addEventListener('mouseup', () => {
            if (!_resizing) return;
            _resizing = false;
            _savePos();
        }, _acSignal);

        const dockCtl = _createDockController({
            panel, render,
            getPinned: () => _pinned,
        });
        const _savePos          = dockCtl.savePos;

        panel.addEventListener('click', e => e.stopPropagation());

        const _dockTabGeometry = dockCtl.dockTabGeometry;
        const _buildDockTab    = dockCtl.buildDockTab;
        const _dock            = dockCtl.dock;
        const _peekOut         = dockCtl.peekOut;
        const _retract         = dockCtl.retract;
        const _exitDockMode    = dockCtl.exitDockMode;
        const _forceResetDock  = dockCtl.forceResetDock;

        dockTriggerL.addEventListener('click', (e) => {
            e.stopPropagation();
            if (DockState.side === 'left') {
                _exitDockMode();
            } else if (DockState.side === 'right') {
                showToast('⚠️ Undock right side first before docking left');
            } else {
                _dock('left');
            }
        });
        dockTriggerR.addEventListener('click', (e) => {
            e.stopPropagation();
            if (DockState.side === 'right') {
                _exitDockMode();
            } else if (DockState.side === 'left') {
                showToast('⚠️ Undock left side first before docking right');
            } else {
                _dock('right');
            }
        });

        panel.addEventListener('mouseleave', () => {
            if (!DockState.side || !DockState.peeked) return;
            if (_dialogOpenGlobal) return;
            if (_pinned) return;
            clearTimeout(DockState.retractTimer);
            DockState.retractTimer = setTimeout(() => _retract(), 480);
        });
        panel.addEventListener('mouseenter', () => {
            if (!DockState.side) return;
            clearTimeout(DockState.retractTimer);
            DockState.retractTimer = null;
        });

        const _origCleanup = panel._tmCleanup;
        panel._tmCleanup = () => {
            _origCleanup();
            clearTimeout(DockState.hoverTimer);
            clearTimeout(DockState.retractTimer);
            DockState.hoverTimer   = null;
            DockState.retractTimer = null;
            if (DockState.tabEl) { DockState.tabEl.remove(); DockState.tabEl = null; }
        };

        if (DockState.side && !_pinned) {
            requestAnimationFrame(() => {
                const side = DockState.side;
                DockState.side = null;
                _dock(side);
                requestAnimationFrame(() => {
                    panel.style.transition = '';
                    panel.style.opacity = '';
                });
            });
        } else if (DockState.side && _pinned) {
            requestAnimationFrame(() => {
                const side = DockState.side;
                DockState.side = null;
                _dock(side);
                requestAnimationFrame(() => {
                    panel.style.transition = '';
                    panel.style.opacity = '';
                    _peekOut();
                });
            });
        }

        render();
    }

    function _cleanZoom() { document.getElementById('tm-hist-zoom')?.remove(); }

    function fireMeteor(fromEl) {
        const histBtn = document.getElementById('tm-history-btn');
        if (!histBtn || !fromEl) return;

        const fromRect = fromEl.getBoundingClientRect();
        const toRect   = histBtn.getBoundingClientRect();

        const fromX = fromRect.left + fromRect.width  / 2;
        const fromY = fromRect.top  + fromRect.height / 2;
        const toX   = toRect.left   + toRect.width    / 2;
        const toY   = toRect.top    + toRect.height   / 2;

        const dx    = toX - fromX;
        const dy    = toY - fromY;
        const dist  = Math.sqrt(dx * dx + dy * dy);
        const angle = Math.atan2(dy, dx) * 180 / Math.PI;
        const tailLen = Math.min(Math.max(dist * 0.24, 16), 40);

        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            left: ${fromX}px; top: ${fromY}px;
            width: 0; height: 0;
            pointer-events: none;
            z-index: 99999999;
        `;

        const tail = document.createElement('div');
        tail.style.cssText = `
            position: absolute;
            width: ${tailLen * 1.2}px; height: 6px;
            border-radius: 9999px;
            background: linear-gradient(90deg,
                rgba(255,255,255,0)     0%,
                rgba(255,220,100,0.15) 30%,
                rgba(255,200,80,0.3)   70%,
                rgba(255,240,150,0.25)100%);
            box-shadow: 0 0 4px 1px rgba(255,200,100,0.3);
            filter: drop-shadow(0 0 3px rgba(255,200,100,0.2));
            transform: translateX(-100%) translateY(-50%) rotate(${angle}deg);
            transform-origin: 100% 50%;
        `;

        const tailGlow = document.createElement('div');
        tailGlow.style.cssText = `
            position: absolute;
            width: ${tailLen}px; height: 10px;
            border-radius: 9999px;
            background: linear-gradient(90deg,
                rgba(255,240,200,0)    0%,
                rgba(255,220,150,0.15) 50%,
                rgba(255,240,200,0)  100%);
            filter: blur(1.5px);
            transform: translateX(-100%) translateY(-50%) rotate(${angle}deg);
            transform-origin: 100% 50%;
            opacity: 0.4;
        `;

        const dot = document.createElement('div');
        dot.style.cssText = `
            position: absolute;
            width: 9px; height: 9px; border-radius: 50%;
            background: radial-gradient(circle at 40% 40%, #ffffdd, #ffeed4);
            box-shadow: 0 0 5px 2px rgba(255,220,150,0.4),
                        0 0 10px 4px rgba(255,200,100,0.15);
            transform: translate(-50%, -50%);
        `;

        const particles = [];
        for (let i = 0; i < 4; i++) {
            const particle = document.createElement('div');
            const offsetX = (Math.random() - 0.5) * tailLen * 0.4;
            const offsetY = (Math.random() - 0.5) * 8;
            const size = 1.5 + Math.random() * 2;
            particle.style.cssText = `
                position: absolute;
                width: ${size}px; height: ${size}px; border-radius: 50%;
                background: rgba(255, 240, 180, ${0.3 + Math.random() * 0.3});
                left: ${offsetX}px; top: ${offsetY}px;
                filter: blur(0.5px);
                box-shadow: 0 0 ${size + 1}px rgba(255,220,150,0.3);
            `;
            particles.push(particle);
        }

        container.appendChild(tailGlow);
        container.appendChild(tail);
        particles.forEach(p => container.appendChild(p));
        container.appendChild(dot);
        document.body.appendChild(container);

        const DURATION = 680;

        const anim = container.animate([
            { transform: 'translate(0px, 0px)',           opacity: 0,   offset: 0    },
            { transform: 'translate(0px, 0px)',           opacity: 1,   offset: 0.04 },
            { transform: `translate(${dx}px, ${dy}px)`,  opacity: 1,   offset: 0.88 },
            { transform: `translate(${dx}px, ${dy}px)`,  opacity: 0,   offset: 1    },
        ], {
            duration: DURATION,
            easing:   'cubic-bezier(0.28, 0.0, 0.72, 1.0)',
            fill:     'forwards',
        });

        anim.onfinish = () => {
            container.remove();
            _triggerHistAbsorb();
        };
    }

    let _absorbTimer = null;
    function _triggerHistAbsorb() {
        const wrapper = document.getElementById('tm-settings-wrapper');
        const histBtn = document.getElementById('tm-history-btn');
        if (!wrapper || !histBtn) return;

        if (_absorbTimer) clearTimeout(_absorbTimer);

        wrapper.setAttribute('data-focus', 'hist');
        wrapper.setAttribute('data-absorb', 'true');

        histBtn.classList.remove('tm-absorbing');
        void histBtn.offsetWidth;
        histBtn.classList.add('tm-absorbing');

        _absorbTimer = setTimeout(() => {
            wrapper.removeAttribute('data-absorb');
            histBtn.classList.remove('tm-absorbing');
            _absorbTimer = null;
        }, 2500);
    }

    const BUTTON_CLASS = 'force-media-copy-btn';

    const style = document.createElement('style');
    style.textContent = `
        .${BUTTON_CLASS}, .custom-copy-icon {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            background: transparent;
            border: none;
            font-size: 11px;
            box-sizing: border-box;
            opacity: 0.75;
            cursor: pointer;
            margin-left: 4px;
            transition: opacity 0.2s, filter 0.2s, color 0.15s;
            color: #536471;
        }
        
        .custom-copy-icon {
            margin-left: auto;
        }
        @media (prefers-color-scheme: dark) {
            .${BUTTON_CLASS}, .custom-copy-icon { color: #71767b; }
        }
        .${BUTTON_CLASS} svg, .custom-copy-icon svg {
            width: 20px;
            height: 20px;
            display: block;
            overflow: visible;
            flex-shrink: 0;
        }
        .${BUTTON_CLASS}:hover { opacity: 1.0; color: #1d9bf0; }
        .custom-copy-icon:hover {
            opacity: 1.0;
            color: #1d9bf0;
            filter: drop-shadow(0 0 4px rgba(29,155,240,0.4));
        }
    `;
    const _appendHeadStyles = () => {
        document.head.appendChild(style);
        document.head.appendChild(_toastStyle);
    };

    const _toastStyle = document.createElement('style');
    _toastStyle.textContent = `
        @keyframes tm-toast-rise {
            0%   { opacity: 0; transform: translate(-50%, -100%) scale(0.85); }
            15%  { opacity: 1; transform: translate(-50%, -118%) scale(1);    }
            70%  { opacity: 1; transform: translate(-50%, -135%) scale(1);    }
            100% { opacity: 0; transform: translate(-50%, -152%) scale(0.95); }
        }
        .tm-action-toast {
            position: fixed; white-space: nowrap; pointer-events: none;
            transform: translate(-50%, -100%);
            background: rgba(29,155,240,0.92); color: #fff;
            font: 600 11px/1 system-ui,-apple-system,sans-serif;
            padding: 4px 9px; border-radius: 9999px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.25); z-index: 9999999;
            animation: tm-toast-rise 1.6s cubic-bezier(0.22,1,0.36,1) forwards;
        }
        .tm-action-toast.warn  { background: rgba(255,140,0,0.92); }
        .tm-action-toast.error { background: rgba(224,36,94,0.92); }
        @keyframes tm-spin { to { transform: rotate(360deg); } }
        .tm-dl-ring { display:inline-flex; align-items:center; justify-content:center;
            width:20px; height:20px; flex-shrink:0; }
        .tm-dl-ring svg { overflow:visible; }
        .tm-dl-ring .tm-bg { stroke: rgba(128,128,128,0.28); }
        .tm-dl-ring .tm-fg { stroke: currentColor;
            transition: stroke-dashoffset 0.15s linear;
            transform-origin: 10px 10px; }
        .tm-dl-ring.indeterminate .tm-fg {
            animation: tm-spin 0.85s linear infinite; }
    `;

    if (document.head) {
        _appendHeadStyles();
    } else {
        document.addEventListener('DOMContentLoaded', _appendHeadStyles, { once: true });
    }

    let _activeMenuCloseFn = null;

    function _showActionMenu(anchorEl, items) {
        const existing = document.getElementById('tm-action-menu');
        if (existing) {
            const prev = existing._anchorEl;
            _activeMenuCloseFn?.();
            _activeMenuCloseFn = null;
            if (prev === anchorEl) return () => {};
        }

        const dark = window.matchMedia('(prefers-color-scheme: dark)').matches
                  || document.documentElement.style.colorScheme === 'dark'
                  || document.documentElement.getAttribute('data-color-scheme') === 'dark';
        const bg     = dark ? '#1e2732' : '#fbf8f2';
        const border = dark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.12)';
        const text   = dark ? 'rgba(255,255,255,0.92)' : '#2b2620';
        const hover  = dark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)';
        const shadow = dark ? '0 4px 20px rgba(0,0,0,0.6)' : '0 4px 20px rgba(0,0,0,0.16)';

        if (!document.getElementById('tm-action-menu-style')) {
            const s = document.createElement('style');
            s.id = 'tm-action-menu-style';
            s.textContent = `
                #tm-action-menu {
                    position: fixed; z-index: 9999998;
                    border-radius: 12px; overflow: hidden;
                    min-width: 180px;
                    opacity: 0; transform: translateY(4px) scale(0.97);
                    transform-origin: bottom center;
                    transition: opacity 0.14s ease, transform 0.14s ease;
                    pointer-events: none;
                }
                #tm-action-menu.tm-menu-in {
                    opacity: 1; transform: translateY(0) scale(1);
                    pointer-events: auto;
                }
                .tm-menu-item {
                    display: flex; align-items: center; gap: 10px;
                    padding: 10px 15px; cursor: pointer;
                    font: 13px/1.4 system-ui, -apple-system, sans-serif;
                    font-weight: 500; user-select: none;
                    transition: background 0.1s;
                    white-space: nowrap;
                }
                .tm-menu-item:hover { background: var(--tm-menu-hover); }
                .tm-menu-item.danger { color: #f4212e; }
                .tm-menu-item-icon { width: 16px; height: 16px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; opacity: 0.85; }
                .tm-menu-item-icon svg { width: 14px; height: 14px; display: block; overflow: visible; flex-shrink: 0; }
                .tm-menu-divider { height: 1px; margin: 4px 0; background: var(--tm-menu-border); }
                
                @keyframes tm-icon-pop {
                    0%   { transform: scale(0.7); opacity: 0.5; }
                    55%  { transform: scale(1.22); }
                    80%  { transform: scale(0.95); }
                    100% { transform: scale(1);    opacity: 1;  }
                }
                @keyframes tm-check-draw {
                    0%   { stroke-dashoffset: 28; opacity: 0.55; }
                    65%  { stroke-dashoffset: 0;  opacity: 1;    }
                    100% { stroke-dashoffset: 0;  opacity: 1;    }
                }
                @keyframes tm-arrow-drop {
                    0%   { transform: translateY(-5px); opacity: 0;   }
                    55%  { transform: translateY(2px);  opacity: 1;   }
                    80%  { transform: translateY(-1px);               }
                    100% { transform: translateY(0);    opacity: 1;   }
                }
                
                .tm-menu-item--copied .tm-menu-item-icon {
                    animation: tm-icon-pop 0.38s cubic-bezier(0.34,1.56,0.64,1) both;
                }
                .tm-menu-item--copied .tm-menu-item-icon svg path,
                .tm-menu-item--copied .tm-menu-item-icon svg polyline,
                .tm-menu-item--copied .tm-menu-item-icon svg line {
                    stroke-dasharray: 28; stroke-dashoffset: 28;
                    animation: tm-check-draw 0.42s ease forwards;
                }
                
                .tm-menu-item--downloaded .tm-menu-item-icon {
                    animation: tm-icon-pop 0.36s cubic-bezier(0.34,1.56,0.64,1) both;
                }
                .tm-menu-item--downloaded .tm-menu-item-icon svg path {
                    animation: tm-arrow-drop 0.34s ease forwards;
                }
                
                .tm-menu-item--prefix { opacity: 0.55; font-size: 12.5px; }
                .tm-menu-item--prefix:hover { opacity: 0.82; background: var(--tm-menu-hover); }
                
                .tm-menu-item--preview {
                    border-left: 2.5px solid #1d9bf0;
                    background: rgba(29,155,240,0.08);
                    padding-left: 12.5px;
                }
                .tm-menu-item--preview:hover { background: rgba(29,155,240,0.16); }
                
                #tm-action-menu-bridge {
                    position: fixed; z-index: 9999997;
                    background: transparent; pointer-events: auto;
                }
            `;
            document.head.appendChild(s);
        }

        const menu = document.createElement('div');
        menu.id = 'tm-action-menu';
        menu._anchorEl = anchorEl;
        menu.style.setProperty('--tm-menu-hover', hover);
        menu.style.setProperty('--tm-menu-border', border);
        menu.style.background = bg;
        menu.style.border = `1px solid ${border}`;
        menu.style.boxShadow = shadow;

        items.forEach((item, idx) => {
            if (item === 'divider') {
                const d = document.createElement('div');
                d.className = 'tm-menu-divider';
                menu.appendChild(d);
                return;
            }
            const row = document.createElement('div');
            row.className = 'tm-menu-item' + (item.danger ? ' danger' : '') + (item.itemClass ? ' ' + item.itemClass : '');
            row.setAttribute('role', 'menuitem');
            row.style.color = item.danger ? '#f4212e' : text;
            if (item.icon) {
                const ic = document.createElement('span');
                ic.className = 'tm-menu-item-icon';
                if (item.icon.trimStart().startsWith('<svg')) {
                    ic.innerHTML = item.icon;
                } else {
                    ic.textContent = item.icon;
                }
                row.appendChild(ic);
            }
            const lb = document.createElement('span');
            lb.textContent = item.label;
            row.appendChild(lb);
            row.addEventListener('click', e => {
                e.stopPropagation(); e.preventDefault();
                close();
                item.action();
            });
            menu.appendChild(row);
        });

        const _position = () => {
            const r = anchorEl.getBoundingClientRect();
            const mW = menu.offsetWidth || 190;
            const _realItemCount = items.filter(i => i !== 'divider').length;
            const _dividerCount  = items.length - _realItemCount;
            const mH = menu.offsetHeight || _realItemCount * 40 + _dividerCount * 9 + 16;
            const GAP = 6;
            let left = r.right - mW;
            if (left < 8) left = r.left;
            left = Math.max(8, Math.min(left, window.innerWidth - mW - 8));
            let top = r.top - mH - GAP;
            if (top < 8) top = r.bottom + GAP;
            menu.style.left = left + 'px';
            menu.style.top  = top + 'px';

            const bridge = document.getElementById('tm-action-menu-bridge') || (() => {
                const b = document.createElement('div');
                b.id = 'tm-action-menu-bridge';
                document.body.appendChild(b);
                return b;
            })();
            bridge._anchorEl = anchorEl;
            const menuAbove = top < r.top;
            const bTop = menuAbove ? (top + mH) : r.bottom;
            const bBot = menuAbove ? r.top      : top;
            bridge.style.left   = (r.left - 4) + 'px';
            bridge.style.top    = bTop + 'px';
            bridge.style.width  = (r.width + 8) + 'px';
            bridge.style.height = Math.max(0, bBot - bTop) + 'px';
        };

        document.body.appendChild(menu);
        requestAnimationFrame(() => {
            _position();
            menu.classList.add('tm-menu-in');
        });

        const _acMenu = new AbortController();

        const close = () => {
            menu.classList.remove('tm-menu-in');
            document.getElementById('tm-action-menu-bridge')?.remove();
            setTimeout(() => menu.remove(), 150);
            _acMenu.abort();
            if (_activeMenuCloseFn === close) _activeMenuCloseFn = null;
        };

        _activeMenuCloseFn = close;

        document.addEventListener('click', e => {
            if (!menu.contains(e.target) && e.target !== anchorEl) close();
        }, { capture: true, signal: _acMenu.signal });

        document.addEventListener('keydown', e => {
            if (e.key === 'Escape') close();
        }, { signal: _acMenu.signal });

        return close;
    }

    function _bindMenuHover(anchorEl, getItems) {
        let _hoverTimer = null;
        let _closeTimer = null;
        let _closeFn    = null;

        const _openMenu = () => {
            clearTimeout(_closeTimer);
            const existing = document.getElementById('tm-action-menu');
            if (existing && existing._anchorEl !== anchorEl) {
                existing._anchorEl = null;
                _activeMenuCloseFn?.();
            }
            if (document.getElementById('tm-action-menu')?._anchorEl === anchorEl) return;
            _closeFn = _showActionMenu(anchorEl, getItems());

            _attachMenuEvents();
        };
        const _scheduleClose = () => {
            _closeTimer = setTimeout(() => {
                const m = document.getElementById('tm-action-menu');
                if (m?._anchorEl === anchorEl) _closeFn?.();
            }, 200);
        };
        const _cancelClose = () => clearTimeout(_closeTimer);

        let _menuEvtAC = null;
        const _attachMenuEvents = () => {
            _menuEvtAC?.abort();
            _menuEvtAC = new AbortController();
            const _ms = { signal: _menuEvtAC.signal };

            const _attachBridge = () => {
                const bridge = document.getElementById('tm-action-menu-bridge');
                if (!bridge || bridge._anchorEl !== anchorEl) return;
                bridge.addEventListener('mouseover', () => {
                    if (document.getElementById('tm-action-menu-bridge')?._anchorEl === anchorEl)
                        _cancelClose();
                }, _ms);
                bridge.addEventListener('mouseout', e => {
                    const b = document.getElementById('tm-action-menu-bridge');
                    if (!b || b._anchorEl !== anchorEl) return;
                    if (!b.contains(e.relatedTarget) && !anchorEl.contains(e.relatedTarget))
                        _scheduleClose();
                }, _ms);
            };
            _attachBridge();
            requestAnimationFrame(_attachBridge);

            const _attachMenu = () => {
                const m = document.getElementById('tm-action-menu');
                if (!m || m._anchorEl !== anchorEl) return;
                m.addEventListener('mouseover', () => {
                    if (document.getElementById('tm-action-menu')?._anchorEl === anchorEl)
                        _cancelClose();
                }, _ms);
                m.addEventListener('mouseout', e => {
                    const mn = document.getElementById('tm-action-menu');
                    if (!mn || mn._anchorEl !== anchorEl) return;
                    if (!mn.contains(e.relatedTarget) && !anchorEl.contains(e.relatedTarget))
                        _scheduleClose();
                }, _ms);
            };
            _attachMenu();
            requestAnimationFrame(_attachMenu);

            const _cleanupObs = new MutationObserver(() => {
                if (!document.getElementById('tm-action-menu')) {
                    _menuEvtAC?.abort();
                    _menuEvtAC = null;
                    _cleanupObs.disconnect();
                }
            });
            _cleanupObs.observe(document.body, { childList: true, subtree: true });
        };

        const _ac = new AbortController();
        const sig = { signal: _ac.signal };

        anchorEl.addEventListener('mouseenter', () => {
            clearTimeout(_hoverTimer);
            _hoverTimer = setTimeout(_openMenu, 420);
        }, sig);
        anchorEl.addEventListener('mouseleave', () => {
            clearTimeout(_hoverTimer);
            _scheduleClose();
        }, sig);

        return _ac;
    }

    function _bindMenuClick(anchorEl, getItems) {
        const _ac = new AbortController();
        anchorEl.addEventListener('click', e => {
            e.preventDefault(); e.stopPropagation();
            _showActionMenu(anchorEl, getItems());
        }, { signal: _ac.signal });
        return _ac;
    }

    function createProgressRing() {
        const R = 8, C = +(2 * Math.PI * R).toFixed(4);
        const el = document.createElement('span');
        el.className = 'tm-dl-ring indeterminate';
        el.innerHTML = `<svg viewBox="0 0 20 20" width="20" height="20">
            <circle class="tm-bg" cx="10" cy="10" r="${R}" fill="none" stroke-width="2.5"/>
            <circle class="tm-fg" cx="10" cy="10" r="${R}" fill="none" stroke-width="2.5"
                stroke-dasharray="${C}" stroke-dashoffset="${C}"
                transform="rotate(-90 10 10)"/></svg>`;
        const fg = el.querySelector('.tm-fg');
        const update = (pct) => {
            if (pct === null) {
                el.classList.add('indeterminate');
                fg.style.strokeDashoffset = C;
            } else {
                el.classList.remove('indeterminate');
                fg.style.strokeDashoffset = C * (1 - Math.max(0, Math.min(1, pct / 100)));
            }
        };
        return { el, update, remove: () => el.remove() };
    }

    async function forceDownloadBlob(url, filename, onProgress) {
        try {
            const resp = await unsafeWindow.fetch(url, { credentials: 'omit' });
            if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
            const contentLength = parseInt(resp.headers.get('content-length') || '0', 10);
            const reader = resp.body.getReader();
            const chunks = [];
            let received = 0;
            if (onProgress) onProgress(contentLength > 0 ? 0 : null);
            while (true) {
                const { value, done } = await reader.read();
                if (done) break;
                chunks.push(value);
                received += value.length;
                if (onProgress && contentLength > 0) {
                    onProgress(Math.round(received / contentLength * 100));
                }
            }
            if (onProgress) onProgress(100);
            const blob = new Blob(chunks, { type: resp.headers.get('content-type') || 'application/octet-stream' });
            const blobUrl = (window.URL || window.webkitURL).createObjectURL(blob);
            try {
                const tag = document.createElement('a');
                tag.href = blobUrl; tag.download = filename;
                document.body.appendChild(tag); tag.click(); document.body.removeChild(tag);
            } finally {
                setTimeout(() => (window.URL || window.webkitURL).revokeObjectURL(blobUrl), 8000);
            }
            return;
        } catch (fetchErr) {
            console.warn('[MediaDL] fetch stream failed, falling back to GM_xmlhttpRequest:', fetchErr);
        }

        await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET", url: url, responseType: "blob",
                timeout: 15000,
                onload: function(response) {
                    if (response.status === 200) {
                        const blob = response.response;
                        const urlCreator = window.URL || window.webkitURL;
                        const blobUrl = urlCreator.createObjectURL(blob);
                        try {
                            const tag = document.createElement('a');
                            tag.href = blobUrl;
                            tag.download = filename;
                            document.body.appendChild(tag);
                            tag.click();
                            document.body.removeChild(tag);
                        } finally {
                            setTimeout(() => urlCreator.revokeObjectURL(blobUrl), 8000);
                        }
                        resolve();
                    } else {
                        reject(new Error(`GM fallback HTTP ${response.status}`));
                    }
                },
                ontimeout: function() {
                    console.warn('[MediaDL] GM fallback timed out (15s), falling back to new tab');
                    const tag = document.createElement('a');
                    tag.href = url; tag.download = filename; tag.target = '_blank';
                    document.body.appendChild(tag); tag.click(); document.body.removeChild(tag);
                    resolve();
                },
                onerror: function(err) {
                    console.error('[MediaDL] GM fallback also failed:', err);
                    const tag = document.createElement('a');
                    tag.href = url; tag.download = filename; tag.target = '_blank';
                    document.body.appendChild(tag); tag.click(); document.body.removeChild(tag);
                    resolve();
                }
            });
        });
    }

    function showFloatingVideoPlayer(videoUrls, startIndex = 0, imageUrls = null, openGallery = false, tweetInfo = null) {
        document.querySelectorAll('video, audio').forEach(media => {
            if (!media.paused) media.pause();
        });

        const oldModal = document.getElementById('tm-floating-video-modal');
        if (oldModal) oldModal.remove();

        const total = videoUrls.length;
        let currentIndex = Math.max(0, Math.min(startIndex, total - 1));

        if (!document.getElementById('tm-vp-style')) {
            const s = document.createElement('style');
            s.id = 'tm-vp-style';
            s.textContent = `
                #tm-floating-video-modal {
                    opacity: 0;
                    transition: opacity 0.22s ease;
                }
                #tm-floating-video-modal.tm-vp-visible {
                    opacity: 1;
                }
                #tm-floating-video-modal video {
                    opacity: 0;
                    transform: scale(0.88) translateZ(0);
                    transition: opacity 0.30s ease 0.10s,
                                transform 0.38s cubic-bezier(0.22,1,0.36,1) 0.08s;
                    will-change: transform, opacity;
                }
                #tm-floating-video-modal.tm-vp-visible video {
                    opacity: 1;
                    transform: scale(1) translateZ(0);
                }
            `;
            document.head.appendChild(s);
        }

        const modal = document.createElement('div');
        modal.id = 'tm-floating-video-modal';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.95); z-index: 9999999;
            display: flex; align-items: center; justify-content: center;
            overscroll-behavior: contain;
        `;
        modal.addEventListener('wheel', e => { e.preventDefault(); e.stopPropagation(); }, { passive: false });
        modal.addEventListener('touchmove', e => e.preventDefault(), { passive: false });
        const video = document.createElement('video');
        video.controls = true;
        video.autoplay = true;
        video.volume = Math.max(0, Math.min(1, parseFloat(GM_getValue(KEY_VIDEO_VOLUME, '1')) || 1));
        video.addEventListener('volumechange', () => {
            GM_setValue(KEY_VIDEO_VOLUME, String(video.volume));
        });
        video.style.cssText = `
            max-width: 88%; max-height: 88%;
            border-radius: 8px; box-shadow: 0 10px 50px rgba(0,0,0,0.8);
            background: #000; outline: none;
        `;

        const counter = document.createElement('div');
        counter.style.cssText = `
            position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
            background: rgba(0,0,0,0.72);
            color: rgba(255,255,255,0.85); padding: 4px 14px;
            border-radius: 9999px; font: 13px/1.5 system-ui, sans-serif;
            z-index: 3; pointer-events: none; white-space: nowrap;
            display: ${total > 1 ? 'block' : 'none'};
        `;

        const NAV_BASE = `
            position: absolute; top: 50%; transform: translateY(-50%);
            background: rgba(0,0,0,0.55); backdrop-filter: blur(4px);
            color: white; border: none;
            width: 46px; height: 46px; border-radius: 50%;
            cursor: pointer; display: flex; align-items: center; justify-content: center;
            transition: background 0.2s, opacity 0.2s; z-index: 3;
        `;
        const SVG_PREV = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15,18 9,12 15,6"/></svg>`;
        const SVG_NEXT = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9,18 15,12 9,6"/></svg>`;

        const prevBtn = document.createElement('button');
        prevBtn.className = 'tm-nav-fade-btn';
        prevBtn.innerHTML = SVG_PREV;
        prevBtn.style.cssText = NAV_BASE + 'left: 20px;';
        prevBtn.onmouseenter = () => prevBtn.style.background = 'rgba(255,255,255,0.25)';
        prevBtn.onmouseleave = () => prevBtn.style.background = 'rgba(0,0,0,0.55)';

        const nextBtn = document.createElement('button');
        nextBtn.className = 'tm-nav-fade-btn';
        nextBtn.innerHTML = SVG_NEXT;
        nextBtn.style.cssText = NAV_BASE + 'right: 20px;';
        nextBtn.onmouseenter = () => nextBtn.style.background = 'rgba(255,255,255,0.25)';
        nextBtn.onmouseleave = () => nextBtn.style.background = 'rgba(0,0,0,0.55)';

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.setAttribute('aria-label', 'Close video player');
        closeBtn.style.cssText = `
            position: absolute; top: 20px; right: 25px;
            background: rgba(0,0,0,0.6); color: white; border: none;
            width: 40px; height: 40px; border-radius: 50%;
            cursor: pointer; display: flex; align-items: center; justify-content: center;
            transition: background 0.2s, right 0.25s ease; z-index: 4;
        `;
        closeBtn.onmouseenter = () => closeBtn.style.background = 'rgba(255,255,255,0.3)';
        closeBtn.onmouseleave = () => closeBtn.style.background = 'rgba(0,0,0,0.6)';

        const viewImgBtn = imageUrls && imageUrls.length ? document.createElement('button') : null;
        if (viewImgBtn) {
            viewImgBtn.title = T.btn_switch_to_image || 'Switch to Image';
            viewImgBtn.innerHTML = `<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5" fill="currentColor" stroke="none"/><polyline points="21,15 16,10 5,21"/></svg>`;
            viewImgBtn.style.cssText = `
                position: absolute; top: 20px; right: 75px;
                background: rgba(29,155,240,0.85); color: white; border: none;
                width: 40px; height: 40px; border-radius: 50%;
                cursor: pointer; display: flex; align-items: center; justify-content: center;
                transition: background 0.2s, right 0.25s ease; z-index: 4;
            `;
            viewImgBtn.onmouseenter = () => viewImgBtn.style.background = 'rgba(29,155,240,1)';
            viewImgBtn.onmouseleave = () => viewImgBtn.style.background = 'rgba(29,155,240,0.85)';
            viewImgBtn.onclick = (e) => {
                e.stopPropagation();
                closeModal();
                showImageLightbox(imageUrls, videoUrls, false, tweetInfo);
            };
        }

        const vpDownloadBtn = document.createElement('button');
        vpDownloadBtn.title = 'Download video';
        vpDownloadBtn.setAttribute('aria-label', 'Download video');
        vpDownloadBtn.innerHTML = `<svg width="16" height="16" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3v10M6 9l4 4 4-4"/><line x1="3" y1="17" x2="17" y2="17"/></svg>`;
        vpDownloadBtn.style.cssText = `
            position: absolute; top: 20px;
            background: rgba(0,0,0,0.6); color: white; border: none;
            width: 40px; height: 40px; border-radius: 50%;
            cursor: pointer; display: flex; align-items: center; justify-content: center;
            transition: background 0.2s, right 0.25s ease; z-index: 4;
        `;
        vpDownloadBtn.onmouseenter = () => vpDownloadBtn.style.background = 'rgba(255,255,255,0.3)';
        vpDownloadBtn.onmouseleave = () => vpDownloadBtn.style.background = 'rgba(0,0,0,0.6)';
        vpDownloadBtn.onclick = async e => {
            e.stopPropagation();
            const original = vpDownloadBtn.innerHTML;
            vpDownloadBtn.innerHTML = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9" stroke-dasharray="42" stroke-dashoffset="14"/></svg>';
            try {
                await forceDownloadBlob(videoUrls[currentIndex], _lbFilename(videoUrls[currentIndex], currentIndex + 1, tweetInfo));
                vpDownloadBtn.innerHTML = `<svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4,10 8,14 16,6"/></svg>`;
            } catch (_) {
                showToast(T.msg_download_failed || '❌ Download failed');
                vpDownloadBtn.innerHTML = original;
            } finally {
                setTimeout(() => { vpDownloadBtn.innerHTML = original; }, 1200);
            }
        };

        function updatePlayer() {
            video.src = videoUrls[currentIndex];
            video.play().catch(() => {});
            video.playbackRate = parseFloat(_speed) || 1;
            video.loop = _loop;
            if (total > 1) {
                counter.textContent = `${currentIndex + 1} / ${total}`;
                prevBtn.style.opacity      = '1';
                nextBtn.style.opacity      = '1';
                prevBtn.style.pointerEvents = 'auto';
                nextBtn.style.pointerEvents = 'auto';
            }
        }

        prevBtn.onclick = e => {
            e.stopPropagation();
            currentIndex = currentIndex > 0 ? currentIndex - 1 : total - 1;
            updatePlayer();
        };
        nextBtn.onclick = e => {
            e.stopPropagation();
            currentIndex = currentIndex < total - 1 ? currentIndex + 1 : 0;
            updatePlayer();
        };

        let _vpGalleryOpen  = false;
        let _vpGalleryItems = null;
        let vpGalleryBtn   = null;
        let vpGalleryPanel = null;

        video.onclick = e => {
            e.stopPropagation();
            if (_vpGalleryItems !== null && vpGalleryBtn && vpGalleryPanel) {
                _vpGalleryOpen = !_vpGalleryOpen;
                vpGalleryBtn.style.background = _vpGalleryOpen ? 'rgba(29,155,240,0.75)' : 'rgba(0,0,0,0.6)';
                vpGalleryPanel.classList.toggle('tm-gp-open', _vpGalleryOpen);
            } else {
                video.paused ? video.play().catch(() => {}) : video.pause();
            }
        };

        const closeModal = () => {
            modal.classList.remove('tm-vp-visible');
            video.pause();
            setTimeout(() => {
                modal.remove();
            }, 220);
            document.removeEventListener('keydown', keyHandler);
        };

        closeBtn.onclick = closeModal;
        modal.onclick = (e) => {
            if (_vpGalleryOpen) return;
            const hit = e.composedPath().some(el => el !== modal && el instanceof Element && (
                el.tagName === 'VIDEO' || el.tagName === 'BUTTON' ||
                el.id === 'tm-lb-gallery-panel' || el.id === 'tm-lb-pill' ||
                (el.className && typeof el.className === 'string' && el.className.includes('tm-'))
            ));
            if (!hit) closeModal();
        };

        const SPEEDS = ['1', '1.25', '1.5', '2'];
        let _speed = GM_getValue(KEY_VIDEO_SPEED, '1');
        video.playbackRate = parseFloat(_speed) || 1;

        const _TOOL_BTN_CSS = `
            position:absolute; bottom:20px;
            background:rgba(0,0,0,0.6); color:rgba(255,255,255,0.9); border:none;
            padding:4px 11px; border-radius:14px; font:600 13px system-ui;
            cursor:pointer; z-index:4; transition:background 0.15s;
        `;
        const speedBtn = document.createElement('button');
        speedBtn.style.cssText = _TOOL_BTN_CSS + 'right:25px;';
        const _applySpeed = () => {
            speedBtn.textContent = _speed + 'x';
            video.playbackRate = parseFloat(_speed) || 1;
            GM_setValue(KEY_VIDEO_SPEED, _speed);
        };
        _applySpeed();
        speedBtn.addEventListener('click', () => {
            _speed = SPEEDS[(SPEEDS.indexOf(_speed) + 1) % SPEEDS.length];
            _applySpeed();
        });
        speedBtn.onmouseenter = () => speedBtn.style.background = 'rgba(255,255,255,0.2)';
        speedBtn.onmouseleave = () => speedBtn.style.background = 'rgba(0,0,0,0.6)';

        let _loop = false;
        const loopBtn = document.createElement('button');
        loopBtn.style.cssText = _TOOL_BTN_CSS + 'right:82px;';
        const _applyLoop = () => {
            loopBtn.textContent = _loop ? '🔁' : '↩';
            loopBtn.title = _loop ? 'Loop: ON' : 'Loop: OFF';
            video.loop = _loop;
        };
        _applyLoop();
        loopBtn.addEventListener('click', () => { _loop = !_loop; _applyLoop(); });
        loopBtn.onmouseenter = () => loopBtn.style.background = 'rgba(255,255,255,0.2)';
        loopBtn.onmouseleave = () => loopBtn.style.background = 'rgba(0,0,0,0.6)';
        modal.append(speedBtn, loopBtn);

        const keyHandler = (e) => {
            if (e.key === 'Escape') { closeModal(); return; }
            if (e.key === ' ' || e.key === 'Spacebar') {
                e.preventDefault();
                video.paused ? video.play() : video.pause();
                return;
            }
            if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
                currentIndex = currentIndex < total - 1 ? currentIndex + 1 : 0;
                updatePlayer();
            }
            if (e.key === 'ArrowLeft'  || e.key === 'ArrowUp') {
                currentIndex = currentIndex > 0 ? currentIndex - 1 : total - 1;
                updatePlayer();
            }
        };
        document.addEventListener('keydown', keyHandler);

        const _vpDownloadRightBase = viewImgBtn ? 130 : 75;
        const _vpGalleryRightBase = _vpDownloadRightBase + 55;
        vpDownloadBtn.style.right = _vpDownloadRightBase + 'px';
        const SVG_GRID_VP = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
            <rect x="1.5" y="1.5" width="5" height="5" rx="1"/>
            <rect x="9.5" y="1.5" width="5" height="5" rx="1"/>
            <rect x="1.5" y="9.5" width="5" height="5" rx="1"/>
            <rect x="9.5" y="9.5" width="5" height="5" rx="1"/>
        </svg>`;
        vpGalleryBtn = document.createElement('button');
        vpGalleryBtn.innerHTML = SVG_GRID_VP;
        vpGalleryBtn.title = 'Browse page media';
        vpGalleryBtn.style.cssText = `
            position: absolute; top: 20px; right: ${_vpGalleryRightBase}px;
            background: rgba(0,0,0,0.6); color: rgba(255,255,255,0.85); border: none;
            width: 40px; height: 40px; border-radius: 50%;
            cursor: pointer; display: flex; align-items: center; justify-content: center;
            transition: background 0.2s, right 0.25s ease; z-index: 4;
        `;
        vpGalleryBtn.onmouseenter = () => vpGalleryBtn.style.background = _vpGalleryOpen ? 'rgba(29,155,240,1)' : 'rgba(255,255,255,0.25)';
        vpGalleryBtn.onmouseleave = () => vpGalleryBtn.style.background = _vpGalleryOpen ? 'rgba(29,155,240,0.75)' : 'rgba(0,0,0,0.6)';

        if (!document.getElementById('tm-lb-style-gallery')) {
            const s = document.createElement('style');
            s.id = 'tm-lb-style-gallery';
            s.textContent = `
                #tm-lb-gallery-btn { position:absolute; top:20px; right:75px; background:rgba(0,0,0,0.6); color:rgba(255,255,255,0.85); border:none; width:40px; height:40px; border-radius:50%; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:background 0.2s, right 0.25s ease; z-index:31; }
                #tm-lb-gallery-btn.tm-gb-active { background:rgba(29,155,240,0.75); }
                #tm-lb-gallery-panel {
                    position:absolute; top:0; right:0; bottom:0; width:${GALLERY_PANEL_WIDTH}px;
                    background:rgba(8,8,12,0.82);
                    
                    border-left:1px solid rgba(255,255,255,0.10);
                    overflow-y:auto; overflow-x:hidden;
                    transform:translateX(100%);
                    transition:transform 0.3s cubic-bezier(0.22,1,0.36,1),
                               backdrop-filter 0.3s ease,
                               -webkit-backdrop-filter 0.3s ease;
                    z-index:30; padding:12px 8px 24px; box-sizing:border-box;
                    scrollbar-width:thin; scrollbar-color:rgba(255,255,255,0.15) transparent;
                }
                #tm-lb-gallery-panel.tm-gp-open {
                    transform:translateX(0);
                    backdrop-filter:blur(20px) saturate(1.4);
                    -webkit-backdrop-filter:blur(20px) saturate(1.4);
                }
                
                #tm-floating-video-modal .tm-nav-fade-btn { opacity:0; }
                #tm-floating-video-modal:hover .tm-nav-fade-btn { opacity:1; }
                #tm-image-lightbox .tm-nav-fade-btn { opacity:0; }
                #tm-image-lightbox:hover .tm-nav-fade-btn { opacity:1; }
                #tm-lb-gallery-panel .tm-gp-grid { display:grid; grid-template-columns:1fr 1fr; gap:4px; }
                .tm-gp-item { position:relative; aspect-ratio:1/1; border-radius:6px; overflow:hidden; cursor:pointer; border:2px solid transparent; transition:border-color 0.15s, transform 0.15s; background:rgba(40,40,40,0.9); flex-shrink:0; }
                .tm-gp-item.tm-gp-item-solo { grid-column:1/-1; aspect-ratio:16/9; }
                .tm-gp-item:hover { transform:scale(0.96); }
                .tm-gp-item.tm-gp-selected { border-color:rgba(29,155,240,0.9); }
                .tm-gp-item img { width:100%; height:100%; object-fit:cover; display:block; }
                .tm-gp-item .tm-gp-vid-badge { position:absolute; bottom:4px; right:4px; background:rgba(0,0,0,0.65); border-radius:4px; padding:1px 4px; font-size:10px; color:white; pointer-events:none; line-height:1.4; }
                .tm-gp-tweet-label { font:10px/1.4 system-ui,sans-serif; color:rgba(251,191,36,0.85); padding:8px 4px 3px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; grid-column:1/-1; }
                #tm-lb-pill { position:fixed; top:68px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.62); color:rgba(255,255,255,0.9); border-radius:9999px; padding:5px 14px 5px 10px; font:12px/1.5 system-ui,sans-serif; display:flex; align-items:center; gap:7px; max-width:min(480px,60vw); white-space:nowrap; overflow:hidden; pointer-events:none; z-index:9999998; opacity:0; transition:opacity 0.22s ease; }
                #tm-lb-pill.tm-pill-show { opacity:1; backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); }
                #tm-lb-pill .tm-pill-avatar { width:20px; height:20px; border-radius:50%; flex-shrink:0; display:flex; align-items:center; justify-content:center; font:700 9px system-ui; color:white; }
                #tm-lb-pill .tm-pill-author { font-weight:600; flex-shrink:0; }
                #tm-lb-pill .tm-pill-text { color:rgba(255,255,255,0.6); overflow:hidden; text-overflow:ellipsis; }
            `;
            document.head.appendChild(s);
        }

        vpGalleryPanel = document.createElement('div');
        vpGalleryPanel.id = 'tm-lb-gallery-panel';
        let vpPill = document.getElementById('tm-lb-pill');
        if (!vpPill) {
            vpPill = document.createElement('div');
            vpPill.id = 'tm-lb-pill';
            document.body.appendChild(vpPill);
        }

        vpGalleryBtn.onclick = e => {
            e.stopPropagation();
            _vpGalleryOpen = !_vpGalleryOpen;
            vpGalleryBtn.style.background = _vpGalleryOpen ? 'rgba(29,155,240,0.75)' : 'rgba(0,0,0,0.6)';
            vpGalleryPanel.classList.toggle('tm-gp-open', _vpGalleryOpen);

            const _shift = _vpGalleryOpen ? GALLERY_PANEL_WIDTH : 0;
            closeBtn.style.right = (25 + _shift) + 'px';
            if (viewImgBtn) viewImgBtn.style.right = (75 + _shift) + 'px';
            vpDownloadBtn.style.right = (_vpDownloadRightBase + _shift) + 'px';
            vpGalleryBtn.style.right = (_vpGalleryRightBase + _shift) + 'px';

            if (_vpGalleryOpen && !_vpGalleryItems) {
                _vpGalleryItems = _scanPageMedia();
                if (!_vpGalleryItems.length) {
                    const emptyDiv = document.createElement('div');
                    emptyDiv.style.cssText = 'color:rgba(255,255,255,0.4);font:12px system-ui;padding:20px 8px;text-align:center';
                    emptyDiv.textContent = 'No media found on this page';
                    vpGalleryPanel.appendChild(emptyDiv);
                    return;
                }
                const grid = document.createElement('div');
                grid.className = 'tm-gp-grid';
                _vpGalleryItems.forEach(group => {
                    const label = document.createElement('div');
                    label.className = 'tm-gp-tweet-label';
                    label.title = `@${group.handle} · ${group.text}`;
                    label.textContent = `@${group.handle}${group.text ? ' · ' + group.text : ''}`;
                    grid.appendChild(label);
                    group.items.forEach(item => {
                        const el = document.createElement('div');
                        el.className = 'tm-gp-item';
                        if (group.items.length === 1) el.classList.add('tm-gp-item-solo');
                        el.dataset.url = item.url ?? item.thumb ?? '';
                        const img = document.createElement('img');
                        img.src = item.thumb; img.alt = ''; img.loading = 'lazy';
                        el.appendChild(img);
                        if (item.type === 'video') {
                            const badge = document.createElement('span');
                            badge.className = 'tm-gp-vid-badge'; badge.textContent = '▶';
                            el.appendChild(badge);
                        }
                        el.onclick = async ev => {
                            ev.stopPropagation();
                            await new Promise(r => setTimeout(r, 0));
                            vpPill.innerHTML = `<span class="tm-pill-avatar" style="background:${group.avatarColor}">${_escHtml(group.avatarLetter)}</span><span class="tm-pill-author">@${_escHtml(group.handle)}</span>${group.text ? `<span class="tm-pill-text">${_escHtml(group.text)}</span>` : ''}`;
                            vpPill.classList.add('tm-pill-show');
                            clearTimeout(vpPill._autoTimer);
                            vpPill._autoTimer = setTimeout(() => { vpPill.classList.remove('tm-pill-show'); }, 1500);

                            const isValidUrl = u => typeof u === 'string' && u.startsWith('http');
                            if (item.type === 'video') {
                                let mp4Url = isValidUrl(item.url) ? item.url : null;
                                if (!mp4Url && group.tweetId) {
                                    try {
                                        const d = await fetchTweetMediaFromAPI(group.tweetId);
                                        const c = d?.videos?.[0];
                                        if (isValidUrl(c)) mp4Url = c;
                                    } catch(_) {}
                                }
                                if (mp4Url) {
                                    closeModal();
                                    showFloatingVideoPlayer([mp4Url], 0, null, true);
                                } else {
                                    showToast(T.msg_no_video || '❌ No Video');
                                }
                            } else if (isValidUrl(item.url)) {
                                closeModal();
                                showImageLightbox([item.url], null, true);
                            } else {
                                showToast(T.msg_no_media || '❌ No Media');
                            }
                        };
                        grid.appendChild(el);
                    });
                });
                vpGalleryPanel.appendChild(grid);
            }
        };

        modal.appendChild(video);
        modal.appendChild(closeBtn);
        if (viewImgBtn) modal.appendChild(viewImgBtn);
        modal.appendChild(vpDownloadBtn);
        modal.appendChild(vpGalleryBtn);
        modal.appendChild(vpGalleryPanel);
        modal.appendChild(counter);
        if (total > 1) { modal.appendChild(prevBtn); modal.appendChild(nextBtn); }
        document.body.appendChild(modal);

        updatePlayer();
        requestAnimationFrame(() => requestAnimationFrame(() => {
            modal.classList.add('tm-vp-visible');
            if (openGallery && vpGalleryBtn) vpGalleryBtn.onclick({ stopPropagation: () => {} });
        }));
    }

    function _scanPageMedia() {
        const AVATAR_COLORS = ['#1d9bf0','#7856ff','#ff7a00','#00ba7c','#f91880'];
        const results = [];
        const _hashColor = (handle) => {
            let idx = 0;
            for (let i = 0; i < handle.length; i++) idx += handle.charCodeAt(i);
            return AVATAR_COLORS[idx % AVATAR_COLORS.length];
        };

        document.querySelectorAll('article[data-testid="tweet"]').forEach(article => {
            let author = '', handle = '';
            try {
                const userBlock = article.querySelector('[data-testid="User-Name"]');
                if (userBlock) {
                    const lines = userBlock.innerText.split('\n').map(s => s.trim()).filter(Boolean);
                    author = lines[0] || '';
                    const at = lines.find(l => l.startsWith('@'));
                    handle = at ? at.slice(1) : '';
                }
            } catch(_) {}

            const textRaw = article.querySelector('[data-testid="tweetText"]')?.innerText?.trim() || '';
            const text = textRaw.length > 40 ? textRaw.slice(0, 40) + '…' : textRaw;

            let tweetId = '';
            for (const a of article.querySelectorAll('a[href*="/status/"]')) {
                const m = a.href.match(/\/status\/(\d+)/);
                if (m) { tweetId = m[1]; break; }
            }

            const _quotedImgs = new Set(article.querySelectorAll('[data-testid="tweet"] img[src*="pbs.twimg.com/media"]'));
            const _quotedVideos = new Set(article.querySelectorAll('[data-testid="tweet"] video[poster]'));
            const ownItems = [];
            const quotedItems = [];
            const seenUrls = new Set();

            article.querySelectorAll('img[src*="pbs.twimg.com/media"]').forEach(img => {
                if (img.src.includes('profile_images')) return;
                let thumb = img.src;
                let full  = img.src;
                try {
                    const u = new URL(img.src);
                    u.searchParams.set('name', 'large');
                    full  = u.toString();
                    u.searchParams.set('name', '360x360');
                    thumb = u.toString();
                } catch(_) {}
                if (seenUrls.has(full)) return;
                seenUrls.add(full);
                (_quotedImgs.has(img) ? quotedItems : ownItems).push({ type: 'image', thumb, url: full });
            });

            article.querySelectorAll('video[poster]').forEach(vid => {
                const poster = vid.getAttribute('poster');
                if (!poster || seenUrls.has(poster)) return;
                seenUrls.add(poster);

                let mp4 = _fiberVideoCache.get(article)?.urls?.[0] || null;

                if (!mp4 && tweetId) {
                    const entry = _apiVideoCache.get(tweetId);
                    if (entry && (Date.now() - entry.ts < _API_CACHE_TTL)) {
                        mp4 = entry.urls?.[0] || null;
                    }
                }

                if (!mp4) {
                    const m = poster.match(/(?:amplify_video_thumb|ext_tw_video_thumb|tweet_video_thumb)\/(\d+)/);
                    if (m) {
                        const entry = _apiVideoCache.get(m[1]);
                        if (entry && (Date.now() - entry.ts < _API_CACHE_TTL)) {
                            mp4 = entry.urls?.[0] || null;
                        }
                    }
                }

                (_quotedVideos.has(vid) ? quotedItems : ownItems).push({ type: 'video', thumb: poster, url: mp4 });
            });

            const items = ownItems.length ? ownItems : quotedItems;

            if (!items.length) return;

            const avatarColor = _hashColor(handle);
            const avatarLetter = (author[0] || handle[0] || '?').toUpperCase();

            results.push({ tweetId, author, handle, text, items, avatarColor, avatarLetter });
        });

        if (!results.length) {
            document.querySelectorAll('li[id^="verticalGridItem-"]').forEach(li => {
                const anchor = li.querySelector('a[href*="/status/"]');
                if (!anchor) return;
                const href = anchor.getAttribute('href') || '';
                const m = href.match(/^\/([^/]+)\/status\/(\d+)\/(photo|video)\/(\d+)/);
                if (!m) return;
                const [, handle, tweetId, mediaType] = m;
                const img = li.querySelector('img');
                if (!img) return;

                let thumb = img.src;
                try {
                    const u = new URL(img.src);
                    u.searchParams.set('name', '360x360');
                    thumb = u.toString();
                } catch(_) {}

                let full = null;
                if (mediaType === 'photo') {
                    try {
                        const u = new URL(img.src);
                        u.searchParams.set('name', 'large');
                        full = u.toString();
                    } catch(_) { full = img.src; }
                }

                results.push({
                    tweetId, author: '', handle, text: '',
                    items: [{ type: mediaType === 'video' ? 'video' : 'image', thumb, url: full }],
                    avatarColor: _hashColor(handle),
                    avatarLetter: (handle[0] || '?').toUpperCase(),
                });
            });
        }

        return results;
    }

    function showImageLightbox(urls, videoUrls = null, openGallery = false, tweetInfo = null) {
        if (!urls.length) return;

        const old = document.getElementById('tm-image-lightbox');
        if (old) old.remove();

        if (!document.getElementById('tm-lb-style')) {
            const s = document.createElement('style');
            s.id = 'tm-lb-style';
            s.textContent = `
                
                #tm-image-lightbox {
                    opacity: 0;
                    transition: opacity 0.22s ease;
                }
                #tm-image-lightbox.tm-lb-in { opacity: 1; }

                #tm-image-lightbox .tm-lb-single-img {
                    opacity: 0;
                    transform: scale(0.88) translateZ(0);
                    transition: opacity 0.32s ease 0.08s,
                                transform 0.40s cubic-bezier(0.22,1,0.36,1) 0.06s;
                    will-change: transform, opacity;
                }
                #tm-image-lightbox.tm-lb-in .tm-lb-single-img {
                    opacity: 1;
                    transform: scale(1) translateZ(0);
                }

                #tm-image-lightbox .tm-lb-card.tm-lb-animated {
                    transition:
                        transform  0.38s cubic-bezier(0.34,1.18,0.64,1),
                        opacity    0.26s ease;
                    will-change: transform, opacity;
                }

                #tm-image-lightbox .tm-lb-card          { cursor: pointer; z-index: var(--lb-z, 1); }
                
                #tm-image-lightbox .tm-lb-card.tm-lb-focused { cursor: default; }
                
                #tm-image-lightbox .tm-lb-card { box-shadow: 0 12px 36px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.07); }
                #tm-image-lightbox .tm-lb-card.tm-lb-focused { box-shadow: 0 28px 80px rgba(0,0,0,0.9), 0 0 0 1px rgba(255,255,255,0.18); }

                #tm-lb-gallery-btn {
                    position: absolute; top: 20px; right: 75px;
                    background: rgba(0,0,0,0.6); color: rgba(255,255,255,0.85); border: none;
                    width: 40px; height: 40px; border-radius: 50%;
                    cursor: pointer; display: flex; align-items: center; justify-content: center;
                    transition: background 0.2s, right 0.25s ease; z-index: 31;
                }
                #tm-lb-gallery-btn:hover        { background: rgba(255,255,255,0.25); }
                #tm-lb-gallery-btn.tm-gb-active { background: rgba(29,155,240,0.75); }
                #tm-lb-gallery-btn.tm-gb-active:hover { background: rgba(29,155,240,1); }

                #tm-lb-gallery-panel {
                    position: absolute; top: 0; right: 0; bottom: 0;
                    width: 212px;
                    background: rgba(8,8,12,0.82);
                    
                    border-left: 1px solid rgba(255,255,255,0.10);
                    overflow-y: auto; overflow-x: hidden;
                    transform: translateX(100%);
                    transition: transform 0.3s cubic-bezier(0.22,1,0.36,1),
                                backdrop-filter 0.3s ease,
                                -webkit-backdrop-filter 0.3s ease;
                    z-index: 30; padding: 12px 8px 24px;
                    box-sizing: border-box;
                    scrollbar-width: thin;
                    scrollbar-color: rgba(255,255,255,0.15) transparent;
                }
                #tm-lb-gallery-panel.tm-gp-open {
                    transform: translateX(0);
                    backdrop-filter: blur(20px) saturate(1.4);
                    -webkit-backdrop-filter: blur(20px) saturate(1.4);
                }
                #tm-lb-gallery-panel::-webkit-scrollbar { width: 4px; }
                #tm-lb-gallery-panel::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }

                #tm-lb-gallery-panel .tm-gp-grid {
                    display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
                }

                .tm-gp-item {
                    position: relative; aspect-ratio: 1/1; border-radius: 6px; overflow: hidden;
                    cursor: pointer; border: 2px solid transparent;
                    transition: border-color 0.15s, transform 0.15s;
                    background: rgba(40,40,40,0.9);
                    flex-shrink: 0;
                }
                
                .tm-gp-item.tm-gp-item-solo { grid-column: 1/-1; aspect-ratio: 16/9; }
                .tm-gp-item:hover  { transform: scale(0.96); }
                .tm-gp-item.tm-gp-selected { border-color: rgba(29,155,240,0.9); }
                .tm-gp-item img    { width: 100%; height: 100%; object-fit: cover; display: block; }

                .tm-gp-item .tm-gp-vid-badge {
                    position: absolute; bottom: 4px; right: 4px;
                    background: rgba(0,0,0,0.65); border-radius: 4px;
                    padding: 1px 4px; font-size: 10px; color: white;
                    pointer-events: none; line-height: 1.4;
                }

                .tm-gp-tweet-label {
                    font: 10px/1.4 system-ui, sans-serif;
                    color: rgba(251, 191, 36, 0.85); 
                    padding: 8px 4px 3px;
                    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
                    grid-column: 1 / -1;
                }

                #tm-lb-pill {
                    position: fixed; top: 68px; left: 50%;
                    transform: translateX(-50%);
                    background: rgba(0,0,0,0.62);
                    color: rgba(255,255,255,0.9); border-radius: 9999px;
                    padding: 5px 14px 5px 10px;
                    font: 12px/1.5 system-ui, sans-serif;
                    display: flex; align-items: center; gap: 7px;
                    max-width: min(480px, 60vw);
                    white-space: nowrap; overflow: hidden;
                    pointer-events: none; z-index: 9999998;
                    opacity: 0; transition: opacity 0.22s ease;
                }
                #tm-lb-pill.tm-pill-show { opacity: 1; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }
                #tm-lb-pill .tm-pill-avatar {
                    width: 20px; height: 20px; border-radius: 50%;
                    flex-shrink: 0; display: flex; align-items: center; justify-content: center;
                    font: 700 9px system-ui; color: white;
                }
                #tm-lb-pill .tm-pill-author { font-weight: 600; flex-shrink: 0; }
                #tm-lb-pill .tm-pill-text   {
                    color: rgba(255,255,255,0.6);
                    overflow: hidden; text-overflow: ellipsis;
                }

                .tm-lb-quick-actions {
                    position: absolute; top: 12px; right: 12px;
                    display: flex; gap: 6px; z-index: 29;
                    opacity: 0; pointer-events: none;
                    transition: opacity 0.18s ease;
                }
                .tm-lb-quick-actions.tm-qa-show { opacity: 1; pointer-events: auto; }
                .tm-lb-quick-actions button {
                    width: 32px; height: 32px; border-radius: 50%;
                    background: rgba(0,0,0,0.55); color: rgba(255,255,255,0.9); border: none;
                    cursor: pointer; display: flex; align-items: center; justify-content: center;
                    transition: background 0.15s;
                }
                .tm-lb-quick-actions button:hover { background: rgba(255,255,255,0.28); }
                
                #tm-image-lightbox .tm-lb-quick-actions.tm-qa-branch-b {
                    position: fixed; top: 0; right: auto; 
                }
            `;
            document.head.appendChild(s);
        }

        const total = urls.length;
        const isSingleImage = total === 1;
        let focused = 0;
        let _rafId = null;

        const VW = window.innerWidth;
        const VH = window.innerHeight;
        const CARD_W = Math.min(VW * 0.50, 580);
        const CARD_H = Math.min(VH * 0.88, 1000);
        const SPREAD = Math.min(CARD_W * 0.40, 200);

        function calcTransform(pos) {
            const abs = Math.abs(pos);
            return {
                dx:      pos * SPREAD,
                rot:     pos * 9,
                scale:   Math.max(0.68, 1 - abs * 0.12),
                zIndex:  20 - abs * 2,
                opacity: abs >= 3 ? 0.5 : 1,
                focused: pos === 0,
            };
        }

        const SVG_QA_DOWNLOAD = `<svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3v10M6 9l4 4 4-4"/><line x1="3" y1="17" x2="17" y2="17"/></svg>`;
        const SVG_QA_COPY_LINK = `<svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 9.5a2.5 2.5 0 0 0 3.5 0l2-2a2.5 2.5 0 0 0-3.5-3.5l-1 1"/><path d="M9.5 6.5a2.5 2.5 0 0 0-3.5 0l-2 2a2.5 2.5 0 0 0 3.5 3.5l1-1"/></svg>`;
        function _createQuickActionBtns(getUrl) {
            const wrap = document.createElement('div');
            wrap.className = 'tm-lb-quick-actions';

            const dlBtn = document.createElement('button');
            dlBtn.innerHTML = SVG_QA_DOWNLOAD;
            dlBtn.title = 'Download';
            dlBtn.setAttribute('aria-label', 'Download image');
            dlBtn.onclick = async e => {
                e.stopPropagation();
                const url = getUrl();
                if (!url) return;
                const original = dlBtn.innerHTML;
                dlBtn.innerHTML = '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9" stroke-dasharray="42" stroke-dashoffset="14"/></svg>';
                try {
                    await forceDownloadBlob(url, _lbFilename(url, focused + 1, tweetInfo));
                    dlBtn.innerHTML = `<svg viewBox="0 0 20 20" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4,10 8,14 16,6"/></svg>`;
                } catch (_) {
                    showToast(T.msg_download_failed || '❌ Download failed');
                    dlBtn.innerHTML = original;
                } finally {
                    setTimeout(() => { dlBtn.innerHTML = original; }, 1200);
                }
            };

            const copyBtn = document.createElement('button');
            copyBtn.innerHTML = SVG_QA_COPY_LINK;
            copyBtn.title = 'Copy link';
            copyBtn.setAttribute('aria-label', 'Copy image link');
            copyBtn.onclick = e => {
                e.stopPropagation();
                const url = getUrl();
                if (!url) return;
                GM_setClipboard(url);
                showToast(T.toast_grid_media_copied || 'Link copied!');
                const original = copyBtn.innerHTML;
                copyBtn.innerHTML = `<svg viewBox="0 0 20 20" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4,10 8,14 16,6"/></svg>`;
                setTimeout(() => { copyBtn.innerHTML = original; }, 1200);
            };

            wrap.appendChild(dlBtn);
            wrap.appendChild(copyBtn);
            return wrap;
        }

        const modal = document.createElement('div');
        modal.id = 'tm-image-lightbox';
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.95); z-index: 9999999;
            display: flex; align-items: center; justify-content: center;
            overflow: hidden; overscroll-behavior: contain;
        `;
        const _lbIsOnPanel = e => e.composedPath().some(el =>
            el instanceof Element && (el.id === 'tm-lb-gallery-panel' || el.id === 'tm-lb-pill')
        );
        modal.addEventListener('wheel', e => {
            if (_lbIsOnPanel(e)) return;
            e.preventDefault(); e.stopPropagation();
        }, { passive: false });
        modal.addEventListener('touchmove', e => {
            if (_lbIsOnPanel(e)) return;
            e.preventDefault();
        }, { passive: false });

        let keyHandler = () => {};
        let _lbDragACRef = null;
        let _lbBranchBDragAC = null;
        const _LB_SCROLL_KEYS = new Set([' ', 'PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown']);

        function closeLightbox() {
            modal.classList.remove('tm-lb-in');
            setTimeout(() => { modal.remove(); }, 220);
            document.removeEventListener('keydown', keyHandler);
            if (_lbDragACRef) { _lbDragACRef.abort(); _lbDragACRef = null; }
            if (_lbBranchBDragAC) { _lbBranchBDragAC.abort(); _lbBranchBDragAC = null; }
            _dialogOpenGlobal = false;
        }

        let _galleryOpen   = false;
        let _galleryItems  = null;

        function _buildGalleryUI(onSelect, extraBtns = [], onToggle = null) {
            const SVG_GRID_ICON = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
                <rect x="1.5" y="1.5" width="5" height="5" rx="1"/>
                <rect x="9.5" y="1.5" width="5" height="5" rx="1"/>
                <rect x="1.5" y="9.5" width="5" height="5" rx="1"/>
                <rect x="9.5" y="9.5" width="5" height="5" rx="1"/>
            </svg>`;
            const galleryBtn = document.createElement('button');
            galleryBtn.id = 'tm-lb-gallery-btn';
            galleryBtn.innerHTML = SVG_GRID_ICON;
            galleryBtn.title = 'Browse page media';

            const panel = document.createElement('div');
            panel.id = 'tm-lb-gallery-panel';

            let pill = document.getElementById('tm-lb-pill');
            if (!pill) {
                pill = document.createElement('div');
                pill.id = 'tm-lb-pill';
            }

            let _allThumbEls = [];
            function updateSelected(url) {
                _allThumbEls.forEach(el => {
                    el.classList.toggle('tm-gp-selected', el.dataset.url === url);
                });
            }

            function _toggleGallery(forceOpen) {
                _galleryOpen = (forceOpen !== undefined) ? forceOpen : !_galleryOpen;
                galleryBtn.classList.toggle('tm-gb-active', _galleryOpen);
                panel.classList.toggle('tm-gp-open', _galleryOpen);

                const _shift = _galleryOpen ? GALLERY_PANEL_WIDTH : 0;
                galleryBtn.style.right = (75 + _shift) + 'px';
                extraBtns.forEach(({ el, base }) => { if (el) el.style.right = (base + _shift) + 'px'; });
                if (onToggle) onToggle(_galleryOpen);

                if (_galleryOpen && !_galleryItems) {
                    _galleryItems = _scanPageMedia();
                    _allThumbEls = [];

                    if (!_galleryItems.length) {
                        const emptyDiv = document.createElement('div');
                        emptyDiv.style.cssText = 'color:rgba(255,255,255,0.4);font:12px system-ui;padding:20px 8px;text-align:center';
                        emptyDiv.textContent = 'No media found on this page';
                        panel.appendChild(emptyDiv);
                        return;
                    }

                    const grid = document.createElement('div');
                    grid.className = 'tm-gp-grid';

                    _galleryItems.forEach(group => {
                        const label = document.createElement('div');
                        label.className = 'tm-gp-tweet-label';
                        label.title = `@${group.handle} · ${group.text}`;
                        label.textContent = `@${group.handle}${group.text ? ' · ' + group.text : ''}`;
                        grid.appendChild(label);

                        group.items.forEach(item => {
                            const el = document.createElement('div');
                            el.className = 'tm-gp-item';
                            if (group.items.length === 1) el.classList.add('tm-gp-item-solo');
                            el.dataset.url = item.url ?? item.thumb ?? '';

                            const img = document.createElement('img');
                            img.src = item.thumb;
                            img.alt = '';
                            img.loading = 'lazy';
                            el.appendChild(img);

                            if (item.type === 'video') {
                                const badge = document.createElement('span');
                                badge.className = 'tm-gp-vid-badge';
                                badge.textContent = '▶';
                                el.appendChild(badge);
                            }

                            el.onclick = ev => {
                                ev.stopPropagation();
                                updateSelected(el.dataset.url);
                                onSelect(item, group).catch?.(() => {});
                            };

                            _allThumbEls.push(el);
                            grid.appendChild(el);
                        });
                    });

                    panel.appendChild(grid);
                }
            }

            galleryBtn.onclick = e => { e.stopPropagation(); _toggleGallery(); };

            return { galleryBtn, panel, pill, updateSelected, toggleGallery: _toggleGallery };
        }

        if (isSingleImage) {
            const container = document.createElement('div');
            container.style.cssText = `
                position: relative; width: 100%; height: 100%;
                display: flex; align-items: center; justify-content: center;
            `;

            const img = document.createElement('img');
            img.src = urls[0];
            img.draggable = false;
            img.className = 'tm-lb-single-img';
            img.style.cssText = `
                max-width: 95vw; max-height: 95vh;
                object-fit: contain; display: block;
                background: transparent; pointer-events: auto;
                user-select: none; -webkit-user-drag: none;
            `;

            container.appendChild(img);

            const qaBtnsA = _createQuickActionBtns(() => urls[0]);
            qaBtnsA.style.cssText = `
                position: absolute; top: 20px; right: 125px;
                display: flex; gap: 6px; z-index: 29;
                opacity: 1; pointer-events: auto; transition: right 0.25s ease;
            `;
            modal.appendChild(qaBtnsA);

            modal.appendChild(container);

            const closeBtn = document.createElement('button');
            closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
            closeBtn.setAttribute('aria-label', 'Close lightbox');
            closeBtn.style.cssText = `
                position: absolute; top: 20px; right: 25px;
                background: rgba(0,0,0,0.6); color: white; border: none;
                width: 40px; height: 40px; border-radius: 50%;
                cursor: pointer; display: flex; align-items: center; justify-content: center;
                transition: background 0.2s, right 0.25s ease; z-index: 30;
            `;
            closeBtn.onmouseenter = () => closeBtn.style.background = 'rgba(255,255,255,0.25)';
            closeBtn.onmouseleave = () => closeBtn.style.background = 'rgba(0,0,0,0.6)';
            closeBtn.onclick = closeLightbox;
            modal.onclick = e => {
                const path = e.composedPath();
                if (path.some(el => el !== modal && el instanceof Element && (
                    el.tagName === 'BUTTON' ||
                    el.id === 'tm-lb-gallery-panel' || el.id === 'tm-lb-pill'
                ))) return;

                const imgEl = container.querySelector('.tm-lb-single-img');
                const r = imgEl?.getBoundingClientRect();
                const overImg = r && e.clientX >= r.left && e.clientX <= r.right
                                   && e.clientY >= r.top  && e.clientY <= r.bottom;

                if (overImg && _galleryOpen) {
                    container._toggleGalleryRef?.();
                } else if (!overImg) {
                    closeLightbox();
                }
            };
            modal.appendChild(closeBtn);

            let viewVidBtn = null;
            if (videoUrls && videoUrls.length) {
                viewVidBtn = document.createElement('button');
                viewVidBtn.className = 'tm-nav-fade-btn';
                viewVidBtn.title = T.btn_switch_to_video || 'Switch to Video';
                viewVidBtn.innerHTML = `<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5,3 19,12 5,21" fill="currentColor" stroke="none"/></svg>`;
                viewVidBtn.style.cssText = `
                    position: absolute; top: 20px; right: 130px;
                    background: rgba(29,155,240,0.85); color: white; border: none;
                    width: 40px; height: 40px; border-radius: 50%;
                    cursor: pointer; display: flex; align-items: center; justify-content: center;
                    transition: background 0.2s, right 0.25s ease; z-index: 31;
                `;
                viewVidBtn.onmouseenter = () => viewVidBtn.style.background = 'rgba(29,155,240,1)';
                viewVidBtn.onmouseleave = () => viewVidBtn.style.background = 'rgba(29,155,240,0.85)';
                viewVidBtn.onclick = e => { e.stopPropagation(); closeLightbox(); showFloatingVideoPlayer(videoUrls, 0, urls, false, tweetInfo); };
                modal.appendChild(viewVidBtn);
            }

            keyHandler = e => {
                if (_LB_SCROLL_KEYS.has(e.key)) e.preventDefault();
                if (e.key === 'Escape') { closeLightbox(); return; }
                else if (e.key === '+' || e.key === '=') { _lbScale = Math.min(8, _lbScale * 1.15); _lbApplyTransform(); }
                else if (e.key === '-')                  { _lbScale = Math.max(0.2, _lbScale * 0.87); if (_lbScale <= 1.02) _lbResetTransform(); else _lbApplyTransform(); }
                else if (e.key === '0')                  { _lbResetTransform(); }
            };
            document.addEventListener('keydown', keyHandler);

            let _lbScale = 1;
            let _lbDragActive = false, _lbDragStart = null, _lbTranslate = { x: 0, y: 0 };
            const _lbDragAC = new AbortController();
            _lbDragACRef = _lbDragAC;
            const _lbResetTransform = () => {
                _lbScale = 1; _lbTranslate = { x: 0, y: 0 };
                img.style.transform = '';
                img.style.cursor = '';
            };
            const _lbApplyTransform = () => {
                img.style.transform = `scale(${_lbScale}) translate(${_lbTranslate.x / _lbScale}px, ${_lbTranslate.y / _lbScale}px)`;
                img.style.cursor = _lbScale > 1 ? 'grab' : '';
            };
            modal.addEventListener('wheel', e => {
                if (_lbIsOnPanel(e)) return;
                e.preventDefault();
                e.stopPropagation();
                const delta = e.deltaY < 0 ? 1.1 : 0.9;
                _lbScale = Math.min(8, Math.max(0.2, _lbScale * delta));
                if (_lbScale <= 1.02) { _lbResetTransform(); return; }
                _lbApplyTransform();
            }, { passive: false });
            img.addEventListener('mousedown', e => {
                if (_lbScale <= 1) return;
                e.preventDefault(); e.stopPropagation();
                _lbDragActive = true; _lbDragStart = { x: e.clientX - _lbTranslate.x, y: e.clientY - _lbTranslate.y };
                img.style.cursor = 'grabbing';
            });
            window.addEventListener('mousemove', e => {
                if (!_lbDragActive) return;
                _lbTranslate = { x: e.clientX - _lbDragStart.x, y: e.clientY - _lbDragStart.y };
                _lbApplyTransform();
            }, { signal: _lbDragAC.signal });
            window.addEventListener('mouseup', () => {
                if (!_lbDragActive) return;
                _lbDragActive = false;
                if (_lbScale > 1) img.style.cursor = 'grab';
            }, { signal: _lbDragAC.signal });
            img.addEventListener('dblclick', e => { e.stopPropagation(); _lbResetTransform(); });
            const { galleryBtn: gbA, panel: gpA, pill: pillA, updateSelected: updA, toggleGallery: tgA } = _buildGalleryUI(async (item, group) => {
                await new Promise(r => setTimeout(r, 0));
                if (item.type === 'video') {
                    const isValidUrl = u => typeof u === 'string' && u.startsWith('http');
                    let mp4Url = isValidUrl(item.url) ? item.url : null;
                    if (!mp4Url && group.tweetId) {
                        try {
                            const apiData = await fetchTweetMediaFromAPI(group.tweetId);
                            const candidate = apiData?.videos?.[0];
                            if (isValidUrl(candidate)) mp4Url = candidate;
                        } catch(_) {}
                    }
                    if (mp4Url) {
                        closeLightbox();
                        showFloatingVideoPlayer([mp4Url], 0, null, true);
                    } else {
                        showToast(T.msg_no_video || '❌ No Video');
                    }
                    return;
                }
                const targetUrl = item.url || item.thumb;
                if (!targetUrl) return;
                img.src = targetUrl;
                updA(targetUrl);
                _showPill(pillA, group);
            }, [{ el: closeBtn, base: 25 }, { el: viewVidBtn, base: 130 }, { el: qaBtnsA, base: 125 }]);
            container._toggleGalleryRef = tgA;
            modal.appendChild(gbA);
            modal.appendChild(gpA);
            document.body.appendChild(pillA);

            document.body.appendChild(modal);

            requestAnimationFrame(() => requestAnimationFrame(() => {
                modal.classList.add('tm-lb-in');
                if (openGallery) tgA(true);
            }));
            return;
        }

        const stage = document.createElement('div');
        stage.style.cssText = `
            position: relative;
            width: ${CARD_W}px; height: ${CARD_H}px;
            flex-shrink: 0; overflow: visible;
        `;

        const qaBtnsB = _createQuickActionBtns(() => urls[focused]);
        qaBtnsB.classList.add('tm-qa-branch-b');
        qaBtnsB.addEventListener('mouseenter', () => qaBtnsB.classList.add('tm-qa-show'));
        qaBtnsB.addEventListener('mouseleave', () => qaBtnsB.classList.remove('tm-qa-show'));
        function _updateQABPosition() {
            const fc = modal.querySelector('.tm-lb-card.tm-lb-focused');
            if (!fc) return;
            const r = fc.getBoundingClientRect();
            qaBtnsB.style.left = (r.right - 44 - 30) + 'px';
            qaBtnsB.style.top  = (r.top + 12) + 'px';
        }
        stage.addEventListener('transitionend', e => {
            if (e.propertyName !== 'transform') return;
            if (!e.target.classList.contains('tm-lb-focused')) return;
            _updateQABPosition();
        });

        const cards = urls.map((url, i) => {
            const card = document.createElement('div');
            card.className = 'tm-lb-card';
            card.style.cssText = `
                position: absolute; left: 0; top: 0;
                width: ${CARD_W}px; height: ${CARD_H}px;
                border-radius: 14px; overflow: hidden;
                background: radial-gradient(ellipse at 50% 38%, #1e1e1e 0%, #0a0a0a 100%);
                transform: translateY(${VH * 0.6}px) scale(0.72) translateZ(0);
                opacity: 0;
            `;
            const img = document.createElement('img');
            img.src = url;
            img.decoding = 'async';
            img.loading = i === 0 ? 'eager' : 'lazy';
            img.draggable = false;
            img.style.cssText = `
                width: 100%; height: 100%;
                object-fit: contain; display: block;
                background: transparent; pointer-events: auto;
                user-select: none; -webkit-user-drag: none;
            `;
            card.appendChild(img);
            card.addEventListener('click', e => {
                e.stopPropagation();
                if (i !== focused) {
                    focused = i;
                    scheduleUpdate();
                } else if (_toggleGalleryRef) {
                    _toggleGalleryRef();
                }
            });
            card.addEventListener('mouseenter', () => { if (i === focused) qaBtnsB.classList.add('tm-qa-show'); });
            card.addEventListener('mouseleave', () => qaBtnsB.classList.remove('tm-qa-show'));
            stage.appendChild(card);
            return card;
        });

        const dotsWrap = document.createElement('div');
        dotsWrap.style.cssText = `
            position: absolute; bottom: 22px; left: 50%;
            transform: translateX(-50%);
            display: flex; gap: 8px; z-index: 30;
        `;
        const dots = urls.map((_, i) => {
            const dot = document.createElement('div');
            dot.style.cssText = `
                width: 7px; height: 7px; border-radius: 50%;
                background: rgba(255,255,255,0.95); cursor: pointer;
                opacity: 0.35;
                transition: opacity 0.22s, transform 0.22s;
            `;
            dot.addEventListener('click', e => { e.stopPropagation(); focused = i; scheduleUpdate(); });
            dotsWrap.appendChild(dot);
            return dot;
        });

        const counter = document.createElement('div');
        counter.style.cssText = `
            position: absolute; top: 20px; left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.72);
            color: rgba(255,255,255,0.85);
            padding: 4px 14px; border-radius: 9999px;
            font: 13px/1.5 system-ui, sans-serif; z-index: 30;
            pointer-events: none; white-space: nowrap;
        `;

        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = `<svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><line x1="3" y1="3" x2="13" y2="13"/><line x1="13" y1="3" x2="3" y2="13"/></svg>`;
        closeBtn.setAttribute('aria-label', 'Close lightbox');
        closeBtn.style.cssText = `
            position: absolute; top: 20px; right: 25px;
            background: rgba(0,0,0,0.6); color: white; border: none;
            width: 40px; height: 40px; border-radius: 50%;
            cursor: pointer; display: flex; align-items: center; justify-content: center;
            transition: background 0.2s, right 0.25s ease; z-index: 30;
        `;
        closeBtn.onmouseenter = () => closeBtn.style.background = 'rgba(255,255,255,0.25)';
        closeBtn.onmouseleave = () => closeBtn.style.background = 'rgba(0,0,0,0.6)';
        closeBtn.onclick = closeLightbox;
        modal.onclick = e => {
            const hit = e.composedPath().some(el => el !== modal && el instanceof Element && (
                el.tagName === 'IMG' || el.tagName === 'BUTTON' ||
                el.id === 'tm-lb-gallery-panel' || el.id === 'tm-lb-pill' ||
                (el.className && typeof el.className === 'string' && el.className.includes('tm-'))
            ));
            if (!hit) closeLightbox();
        };

        const viewVidBtn = videoUrls && videoUrls.length ? document.createElement('button') : null;
        if (viewVidBtn) {
            viewVidBtn.className = 'tm-nav-fade-btn';
            viewVidBtn.title = T.btn_switch_to_video || 'Switch to Video';
            viewVidBtn.innerHTML = `<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5,3 19,12 5,21" fill="currentColor" stroke="none"/></svg>`;
            viewVidBtn.style.cssText = `
                position: absolute; top: 20px; right: 130px;
                background: rgba(29,155,240,0.85); color: white; border: none;
                width: 40px; height: 40px; border-radius: 50%;
                cursor: pointer; display: flex; align-items: center; justify-content: center;
                transition: background 0.2s, right 0.25s ease; z-index: 31;
            `;
            viewVidBtn.onmouseenter = () => viewVidBtn.style.background = 'rgba(29,155,240,1)';
            viewVidBtn.onmouseleave = () => viewVidBtn.style.background = 'rgba(29,155,240,0.85)';
            viewVidBtn.onclick = e => { e.stopPropagation(); closeLightbox(); showFloatingVideoPlayer(videoUrls, 0, urls, false, tweetInfo); };
        }

        let _bZoom = 1, _bTx = 0, _bTy = 0, _bDragActive = false, _bDragStart = null;
        const _bDragAC = new AbortController();
        _lbBranchBDragAC = _bDragAC;

        const _getZoomImg = () => {
            const fc = modal.querySelector('.tm-lb-card.tm-lb-focused');
            return fc ? fc.querySelector('img') : null;
        };
        const _bApply = () => {
            const img = _getZoomImg();
            if (!img) return;
            img.style.transform  = `scale(${_bZoom}) translate(${_bTx / _bZoom}px, ${_bTy / _bZoom}px)`;
            img.style.cursor     = _bZoom > 1 ? 'grab' : '';
        };
        const _bReset = () => {
            _bZoom = 1; _bTx = 0; _bTy = 0;
            const img = _getZoomImg();
            if (img) { img.style.transform = ''; img.style.cursor = ''; }
        };
        modal.addEventListener('wheel', e => {
            if (_lbIsOnPanel(e)) return;
            e.preventDefault();
            e.stopPropagation();
            _bZoom = Math.min(8, Math.max(0.2, _bZoom * (e.deltaY < 0 ? 1.1 : 0.9)));
            if (_bZoom <= 1.02) { _bReset(); return; }
            _bApply();
        }, { passive: false });
        modal.addEventListener('dblclick', e => {
            if (!e.target.closest('.tm-lb-card')) return;
            e.stopPropagation(); _bReset();
        });
        modal.addEventListener('mousedown', e => {
            if (_bZoom <= 1 || !e.target.closest('.tm-lb-card img')) return;
            e.preventDefault(); e.stopPropagation();
            _bDragActive = true; _bDragStart = { x: e.clientX - _bTx, y: e.clientY - _bTy };
            e.target.style.cursor = 'grabbing';
        });
        window.addEventListener('mousemove', e => {
            if (!_bDragActive) return;
            _bTx = e.clientX - _bDragStart.x; _bTy = e.clientY - _bDragStart.y; _bApply();
        }, { signal: _bDragAC.signal });
        window.addEventListener('mouseup', () => {
            if (!_bDragActive) return;
            _bDragActive = false;
            const img = _getZoomImg(); if (img && _bZoom > 1) img.style.cursor = 'grab';
        }, { signal: _bDragAC.signal });

        keyHandler = e => {
            if (_LB_SCROLL_KEYS.has(e.key)) e.preventDefault();
            if (e.key === 'Escape') { closeLightbox(); return; }
            else if (e.key === '+' || e.key === '=') { _bZoom = Math.min(8, _bZoom * 1.15); _bApply(); return; }
            else if (e.key === '-')                  { _bZoom = Math.max(0.2, _bZoom * 0.87); if (_bZoom <= 1.02) _bReset(); else _bApply(); return; }
            else if (e.key === '0')                  { _bReset(); return; }
            if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
                focused = focused < total - 1 ? focused + 1 : 0;
                _bReset(); scheduleUpdate();
            }
            if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
                focused = focused > 0 ? focused - 1 : total - 1;
                _bReset(); scheduleUpdate();
            }
        };
        document.addEventListener('keydown', keyHandler);

        const NAV_BTN_BASE = `
            position: absolute; top: 50%; transform: translateY(-50%);
            background: rgba(0,0,0,0.55); backdrop-filter: blur(4px);
            color: white; border: none;
            width: 46px; height: 46px; border-radius: 50%;
            cursor: pointer; display: flex; align-items: center; justify-content: center;
            transition: background 0.2s; z-index: 30;
        `;
        const SVG_PREV = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15,18 9,12 15,6"/></svg>`;
        const SVG_NEXT = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9,18 15,12 9,6"/></svg>`;

        const lbPrevBtn = total > 1 ? document.createElement('button') : null;
        const lbNextBtn = total > 1 ? document.createElement('button') : null;
        if (lbPrevBtn && lbNextBtn) {
            lbPrevBtn.className = 'tm-nav-fade-btn';
            lbPrevBtn.innerHTML = SVG_PREV;
            lbPrevBtn.style.cssText = NAV_BTN_BASE + 'left: 20px;';
            lbPrevBtn.onmouseenter = () => lbPrevBtn.style.background = 'rgba(255,255,255,0.25)';
            lbPrevBtn.onmouseleave = () => lbPrevBtn.style.background = 'rgba(0,0,0,0.55)';
            lbPrevBtn.onclick = e => {
                e.stopPropagation();
                focused = focused > 0 ? focused - 1 : total - 1;
                _bReset(); scheduleUpdate();
            };

            lbNextBtn.className = 'tm-nav-fade-btn';
            lbNextBtn.innerHTML = SVG_NEXT;
            lbNextBtn.style.cssText = NAV_BTN_BASE + 'right: 20px;';
            lbNextBtn.onmouseenter = () => lbNextBtn.style.background = 'rgba(255,255,255,0.25)';
            lbNextBtn.onmouseleave = () => lbNextBtn.style.background = 'rgba(0,0,0,0.55)';
            lbNextBtn.onclick = e => {
                e.stopPropagation();
                focused = focused < total - 1 ? focused + 1 : 0;
                _bReset(); scheduleUpdate();
            };
        }

        function scheduleUpdate() {
            if (_rafId) return;
            _rafId = requestAnimationFrame(() => {
                _rafId = null;
                _flushUpdate();
            });
        }

        function _flushUpdate() {
            cards.forEach((card, i) => {
                const { dx, rot, scale, zIndex, opacity, focused: isFocused } = calcTransform(i - focused);
                card.style.transform = `translateX(${dx}px) rotate(${rot}deg) scale(${scale}) translateZ(0)`;
                card.style.opacity = (_galleryOpen && !isFocused) ? '0' : opacity;
                card.style.zIndex = String(zIndex);
                card.classList.toggle('tm-lb-focused', isFocused);
            });
            dots.forEach((dot, i) => {
                dot.style.opacity   = i === focused ? '1' : '0.35';
                dot.style.transform = i === focused ? 'scale(1.4)' : 'scale(1)';
            });
            counter.textContent = `${focused + 1} / ${total}`;
            qaBtnsB.classList.remove('tm-qa-show');
            _updateQABPosition();
        }

        function _showPill(pillEl, group) {
            if (!group) { pillEl.classList.remove('tm-pill-show'); return; }
            pillEl.innerHTML = `
                <span class="tm-pill-avatar" style="background:${group.avatarColor}">${_escHtml(group.avatarLetter)}</span>
                <span class="tm-pill-author">@${_escHtml(group.handle)}</span>
                ${group.text ? `<span class="tm-pill-text">${_escHtml(group.text)}</span>` : ''}
            `;
            pillEl.classList.add('tm-pill-show');
            clearTimeout(pillEl._autoTimer);
            pillEl._autoTimer = setTimeout(() => { pillEl.classList.remove('tm-pill-show'); }, 1500);
        }

        let _toggleGalleryRef = null;
        const { galleryBtn: gbB, panel: gpB, pill: pillB, updateSelected: updB, toggleGallery: tgB } = _buildGalleryUI(async (item, group) => {
            await new Promise(r => setTimeout(r, 0));
            const isValidUrl = u => typeof u === 'string' && u.startsWith('http');
            if (item.type === 'video') {
                let mp4Url = isValidUrl(item.url) ? item.url : null;
                if (!mp4Url && group.tweetId) {
                    try {
                        const apiData = await fetchTweetMediaFromAPI(group.tweetId);
                        const candidate = apiData?.videos?.[0];
                        if (isValidUrl(candidate)) mp4Url = candidate;
                    } catch(_) {}
                }
                if (mp4Url) {
                    closeLightbox();
                    showFloatingVideoPlayer([mp4Url], 0, null, true);
                } else {
                    showToast(T.msg_no_video || '❌ No Video');
                }
                return;
            }
            if (item.type === 'image') {
                const targetUrl = item.url;
                if (!isValidUrl(targetUrl)) return;
                const idx = urls.indexOf(targetUrl);
                if (idx !== -1) {
                    focused = idx;
                    scheduleUpdate();
                } else {
                    closeLightbox();
                    showImageLightbox([targetUrl], null, true);
                }
                updB(targetUrl);
            }
            _showPill(pillB, group);
        }, [{ el: closeBtn, base: 25 }, { el: viewVidBtn, base: 130 }], () => scheduleUpdate());
        _toggleGalleryRef = tgB;

        modal.appendChild(stage);
        modal.appendChild(closeBtn);
        if (viewVidBtn) modal.appendChild(viewVidBtn);
        if (lbPrevBtn)  modal.appendChild(lbPrevBtn);
        if (lbNextBtn)  modal.appendChild(lbNextBtn);
        if (total > 1) { modal.appendChild(dotsWrap); modal.appendChild(counter); }
        modal.appendChild(qaBtnsB);
        modal.appendChild(gbB);
        modal.appendChild(gpB);
        document.body.appendChild(pillB);
        document.body.appendChild(modal);

        requestAnimationFrame(() => requestAnimationFrame(() => {
            modal.classList.add('tm-lb-in');

            cards.forEach((card, i) => {
                const delay = i * 55;
                card.style.zIndex = String(calcTransform(i - focused).zIndex);
                setTimeout(() => {
                    card.style.transition = `
                        transform 0.42s cubic-bezier(0.22,1,0.36,1) ${delay}ms,
                        opacity   0.28s ease ${delay}ms
                    `;
                    card.style.transform = `translateX(0) rotate(0deg) scale(1) translateZ(0)`;
                    card.style.opacity   = '1';
                }, 0);
            });

            const totalStagger = (total - 1) * 55 + 420;
            setTimeout(() => {
                cards.forEach(card => {
                    card.style.transition = '';
                    card.classList.add('tm-lb-animated');
                });
                void cards[0]?.offsetHeight;
                _flushUpdate();
                if (openGallery) tgB(true);
            }, totalStagger);
        }));
    }

    function formatDate(dateInput) {
        try {
            if (!dateInput) return '0000.00.00';
            const date = new Date(dateInput);
            if (isNaN(date.getTime())) return '0000.00.00';
            const y = date.getFullYear();
            const m = String(date.getMonth() + 1).padStart(2, '0');
            const d = String(date.getDate()).padStart(2, '0');
            return _cachedDateFormat === 'western' ? `${d}.${m}.${y}` : `${y}.${m}.${d}`;
        } catch (e) { return '0000.00.00'; }
    }

    function sanitizeForFilename(text, maxLength = 50) {
        if (!text) return "";
        let clean = text.replace(/[\r\n]+/g, ' ');
        clean = clean.replace(/[\\/:*?"<>|#%&]/g, '');
        clean = clean.replace(/[\u0000-\u001f\u007f\uff0f\uff3c\uff1a\uff0a\uff1f\uff02\uff1c\uff1e\uff5c\u200b\u200c\u200d\uFEFF]/g, '');
        clean = clean.replace(/\.+$/, '').replace(/^\.+/, '');
        clean = clean.trim();
        if (clean.length > maxLength) clean = clean.substring(0, maxLength).trimEnd().replace(/\.+$/, '');
        if (!clean) return '_';
        if (/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(clean)) clean = '_' + clean;
        return clean;
    }

    function _extractOrigMediaName(url) {
        if (!url) return '';
        try {
            const m1 = url.match(/\/media\/([^/?]+)/);
            if (m1) return m1[1];
            const m2 = url.match(/\/([^/?]+)\.mp4(?:\?|$)/);
            if (m2) return m2[1];
        } catch (_) {}
        return '';
    }

    function buildCustomFilename(vars, fallbackFn) {
        if (!_cachedCustomFilenameEnabled || !_cachedCustomFilenameTemplate) return fallbackFn();

        const safeExt = sanitizeForFilename(vars.ext);
        const safeOrigName = vars.origName ? sanitizeForFilename(vars.origName) : '';
        let name = _cachedCustomFilenameTemplate
            .replace(/\{screenName\}/g,  vars.screenName)
            .replace(/\{displayName\}/g, vars.displayName)
            .replace(/\{date\}/g,        vars.date)
            .replace(/\{id\}/g,          vars.id)
            .replace(/\{index\}/g,       String(vars.index))
            .replace(/\{ext\}/g,         safeExt)
            .replace(/\{origName\}/g,    safeOrigName)
            .replace(/\{text\}/g,        vars.text || '');
        name = name.replace(/[\\/]/g, '_');
        return name || fallbackFn();
    }

    function _lbFilename(url, index, tweetInfo) {
        let ext = 'jpg';
        if (url.includes('.mp4')) ext = 'mp4';
        else {
            try {
                const fmtParam = new URL(url).searchParams.get('format');
                ext = fmtParam || url.match(/\.(\w{3,4})(?:\?|$)/)?.[1] || 'jpg';
            } catch (_) {}
        }
        if (!tweetInfo) return `[twitter] media_${Date.now()}_${index}.${ext}`;
        const safeScreen = sanitizeForFilename(tweetInfo.screenName || 'unknown');
        const hasRichMeta = !!(tweetInfo.date || tweetInfo.displayName);
        if (!hasRichMeta) return `[twitter] ${safeScreen}_${tweetInfo.id || Date.now()}_${index}.${ext}`;
        const safeDisplay = sanitizeForFilename(tweetInfo.displayName || tweetInfo.screenName || 'unknown');
        const datePart    = tweetInfo.date || '';
        const idPart      = tweetInfo.id || '';
        const textPart    = tweetInfo.text ? `_${sanitizeForFilename(tweetInfo.text, 40)}` : '';
        const safeTextForToken = sanitizeForFilename(tweetInfo.text || '', CUSTOM_FN_TEXT_MAXLEN);
        return buildCustomFilename(
            { screenName: safeScreen, displayName: safeDisplay, date: datePart, id: idPart, index, ext, text: safeTextForToken, origName: _extractOrigMediaName(url) },
            () => `[twitter] ${safeDisplay}(@${safeScreen})_${datePart}${textPart}_${idPart}_${index}.${ext}`
        );
    }

    function getTweetInfo(article) {
        let date = '0000.00.00';
        let id = '0000';
        let screenName = 'unknown';
        let displayName = 'User';
        let tweetText = '';

        const timeEl = article.querySelector('time');
        if (timeEl) date = formatDate(timeEl.getAttribute('datetime'));
        else date = formatDate(new Date());

        const allLinks = article.querySelectorAll('a[href*="/status/"]');
        for (const link of allLinks) {
            const href = link.getAttribute('href');
            const match = href.match(/\/([a-zA-Z0-9_]+)\/status\/(\d+)/);
            if (match) {
                screenName = match[1];
                id = match[2];
                break;
            }
        }

        const textNode = article.querySelector('[data-testid="tweetText"]');
        if (textNode) tweetText = textNode.innerText || "";

        try {
            const userBlock = article.querySelector('[data-testid="User-Name"]');
            if (userBlock) {
                const lines = userBlock.innerText.split('\n');
                if (lines.length >= 1) displayName = lines[0].trim();
                if (screenName === 'unknown' && lines.length >= 2) {
                    const handle = lines.find(l => l.startsWith('@'));
                    if (handle) screenName = handle.replace('@', '');
                }
            }
        } catch(e) { console.warn('[MediaDL] Failed to extract displayName:', e); }

        if (id === '0000' && screenName === 'unknown') {
            id = "Ad_" + Date.now().toString().slice(-6);
            screenName = 'Promoted';
        }

        let videoThumb = null;
        const posterVid = article.querySelector('video[poster]');
        if (posterVid) {
            videoThumb = posterVid.getAttribute('poster');
        } else {
            const thumbImg = article.querySelector('img[src*="video_thumb"], img[src*="ext_tw_video_thumb"], img[src*="amplify_video_thumb"]');
            if (thumbImg) videoThumb = thumbImg.src;
        }

        let avatarUrl = '';
        const _avatarImg = article.querySelector('img[src*="profile_images"]');
        if (_avatarImg) avatarUrl = _avatarImg.src.replace('_normal.', '_200x200.');

        return {
            screenName: screenName,
            displayName: displayName,
            id: id,
            date: date,
            text: sanitizeForFilename(tweetText),
            videoThumb: videoThumb,
            avatarUrl: avatarUrl,
        };
    }

    const _apiVideoCache = new Map();
    const _API_CACHE_TTL = 300_000;

    const _FALLBACK_BEARER = 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
    let _cachedBearerToken = null;
    let _bearerPendingPromise = null;
    let _cachedGqlId = GM_getValue('app_gql_tweet_id', '2ICDjqPd81tulZcYrtpTuQ');

    async function _resolveBearerToken() {
        const _customToken = GM_getValue(KEY_CUSTOM_BEARER, '').trim();
        if (_customToken) return _customToken;
        if (_cachedBearerToken) return _cachedBearerToken;
        if (_bearerPendingPromise) return _bearerPendingPromise;

        _bearerPendingPromise = (async () => {
            try {
                for (const script of document.querySelectorAll('script[src*="main."]')) {
                    if (!script.src) continue;
                    const resp = await fetch(script.src);
                    if (!resp.ok) continue;
                    const text = await resp.text();
                    const m = text.match(/Bearer (AAAAAAA[A-Za-z0-9%_-]{20,})/);
                    if (m) { _cachedBearerToken = 'Bearer ' + m[1]; break; }
                }
            } catch(e) { _log('TMApi', 'Bearer token 解析失敗,將降回 fallback', e); }
            _bearerPendingPromise = null;
            if (!_cachedBearerToken) _cachedBearerToken = _FALLBACK_BEARER;
            return _cachedBearerToken;
        })();

        return _bearerPendingPromise;
    }

    function _parseVideosFromTweetResult(tweetResult) {
        try {
            const core = tweetResult?.result ?? tweetResult;
            const tweetId = core?.legacy?.id_str ?? core?.tweet?.legacy?.id_str;
            if (!tweetId) return null;

            let mp4Urls = [];

            const searchVariants = (obj, depth = 0) => {
                if (depth > 12 || !obj || typeof obj !== 'object') return;

                if (Array.isArray(obj.variants)) {
                    const mp4s = obj.variants.filter(v => v.content_type === 'video/mp4');
                    if (mp4s.length > 0) {
                        const best = mp4s.sort((a, b) => (b.bitrate || 0) - (a.bitrate || 0))[0];
                        if (best?.url) mp4Urls.push(best.url.split('?')[0]);
                    }
                }

                if (typeof obj.value === 'string' && obj.value.startsWith('{') && obj.value.includes('"video/mp4"')) {
                    try {
                        const parsedCard = JSON.parse(obj.value);
                        searchVariants(parsedCard, depth + 1);
                    } catch(e) {}
                }

                for (const key in obj) {
                    if (obj[key] && typeof obj[key] === 'object') {
                        searchVariants(obj[key], depth + 1);
                    }
                }
            };

            searchVariants(core);

            if (mp4Urls.length > 0) {
                return { id: tweetId, urls: [...new Set(mp4Urls)] };
            }
        } catch (e) { _log('TMApi', '_parseVideosFromTweetResult 解析失敗(可能是 schema 變動)', e); }
        return null;
    }

    function _processApiPayload(text) {
        try {
            const json = JSON.parse(text);
            const ts = Date.now();
            let _visitCount = 0;
            const walk = (obj, depth) => {
                if (!obj || typeof obj !== 'object' || depth > 40 || _visitCount++ > 5000) return;
                if (obj.tweet_results) {
                    const parsed = _parseVideosFromTweetResult(obj.tweet_results);
                    if (parsed) _apiVideoCache.set(parsed.id, { urls: parsed.urls, ts });
                }
                if (obj.tweetResult) {
                    const parsed = _parseVideosFromTweetResult(obj.tweetResult);
                    if (parsed) _apiVideoCache.set(parsed.id, { urls: parsed.urls, ts });
                }
                if (obj.retweeted_status_result) {
                    const parsed = _parseVideosFromTweetResult(obj.retweeted_status_result);
                    if (parsed) _apiVideoCache.set(parsed.id, { urls: parsed.urls, ts });
                }
                if (obj.quoted_status_result) {
                    const parsed = _parseVideosFromTweetResult(obj.quoted_status_result);
                    if (parsed) _apiVideoCache.set(parsed.id, { urls: parsed.urls, ts });
                }
                for (const v of Object.values(obj)) {
                    if (v && typeof v === 'object') walk(v, depth + 1);
                }
            };
            walk(json, 0);
            if (_apiVideoCache.size > 200) {
                const now = Date.now();
                for (const [id, entry] of _apiVideoCache) {
                    if (now - entry.ts >= _API_CACHE_TTL) _apiVideoCache.delete(id);
                }
                if (_apiVideoCache.size > 150) {
                    const overflow = _apiVideoCache.size - 150;
                    let i = 0;
                    for (const id of _apiVideoCache.keys()) {
                        if (i++ >= overflow) break;
                        _apiVideoCache.delete(id);
                    }
                }
            }
        } catch (e) { _log('TMApi', '_processApiPayload 解析失敗(可能是 GraphQL 回應格式變動)', e); }
    }

    if (_isTwitterDomain) (function _interceptFetch() {
        const _origFetch = unsafeWindow.fetch;
        unsafeWindow.fetch = function(...args) {
            const url = typeof args[0] === 'string' ? args[0] : args[0]?.url ?? '';
            const isGraphQL = url.includes('/graphql/') && (
                url.includes('HomeTimeline') || url.includes('TweetDetail') ||
                url.includes('UserTweets')   || url.includes('SearchTimeline') ||
                url.includes('ListTimeline') || url.includes('TweetResultByRestId')
            );
            const _gqlIdMatch = url.match(/\/i\/api\/graphql\/([A-Za-z0-9_-]{20,})\/TweetResultByRestId/);
            if (_gqlIdMatch && _gqlIdMatch[1] !== _cachedGqlId) {
                _cachedGqlId = _gqlIdMatch[1];
                GM_setValue('app_gql_tweet_id', _cachedGqlId);
            } else if (!_gqlIdMatch && url.includes('TweetResultByRestId')) {
                _log('TMApi', 'TweetResultByRestId URL 結構不符預期 regex,自動更新失效,仍使用舊 ID:', _cachedGqlId, url);
            }
            const promise = _origFetch.apply(this, args);
            if (!isGraphQL) return promise;
            return promise.then(resp => {
                const clone = resp.clone();
                clone.text().then(_processApiPayload).catch(e => _log('TMApi', 'clone.text() 讀取失敗,跳過本次 GraphQL 回應解析', e));
                return resp;
            });
        };
    })();

    const _fiberVideoCache = new WeakMap();
    const _FIBER_CACHE_TTL = 60_000;

    const _CIRCUIT_FAIL_THRESHOLD = 4;
    const _CIRCUIT_BASE_COOLDOWN  = 30_000;
    const _CIRCUIT_MAX_COOLDOWN   = 300_000;
    let _circuitFailCount  = 0;
    let _circuitOpenUntil  = 0;
    let _circuitCooldown   = _CIRCUIT_BASE_COOLDOWN;

    function _circuitIsOpen() {
        return _circuitOpenUntil > Date.now();
    }
    function _circuitRecordFailure() {
        _circuitFailCount++;
        if (_circuitFailCount >= _CIRCUIT_FAIL_THRESHOLD && !_circuitIsOpen()) {
            _circuitOpenUntil = Date.now() + _circuitCooldown;
            _log('TMApi', `電路斷路器開啟,${_circuitCooldown / 1000}s 內跳過網路請求直接降級(連續失敗 ${_circuitFailCount} 次)`);
            _circuitCooldown = Math.min(_circuitCooldown * 2, _CIRCUIT_MAX_COOLDOWN);
        }
    }
    function _circuitRecordSuccess() {
        if (_circuitFailCount > 0) _log('TMApi', '電路斷路器重置(請求已恢復成功)');
        _circuitFailCount = 0;
        _circuitOpenUntil = 0;
        _circuitCooldown  = _CIRCUIT_BASE_COOLDOWN;
    }

    async function fetchTweetMediaFromAPI(statusId) {
        const _cacheHit = _apiVideoCache.get(statusId);
        if (_cacheHit && (Date.now() - _cacheHit.ts < _API_CACHE_TTL)) return { videos: _cacheHit.urls, images: [] };

        if (!_isTwitterDomain) return null;

        if (_circuitIsOpen()) return null;

        try {
            const ct0 = document.cookie.match(/(?:^|;\s*)ct0=([^;]+)/)?.[1]?.trim() ?? '';
            const gt   = document.cookie.match(/(?:^|;\s*)gt=([^;]+)/)?.[1]?.trim()  ?? '';

            const AUTH_TOKEN = await _resolveBearerToken();

            const variables = {"tweetId":statusId,"with_rux_injections":false,"includePromotedContent":true,"withCommunity":true,"withQuickPromoteEligibilityTweetFields":true,"withBirdwatchNotes":true,"withVoice":true,"withV2Timeline":true};
            const features = {"articles_preview_enabled":true,"c9s_tweet_anatomy_moderator_badge_enabled":true,"freedom_of_speech_not_reach_fetch_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"view_counts_everywhere_api_enabled":true};

            let url = `https://${location.hostname}/i/api/graphql/${_cachedGqlId}/TweetResultByRestId?variables=${encodeURIComponent(JSON.stringify(variables))}&features=${encodeURIComponent(JSON.stringify(features))}`;

            let headers = { 'authorization': AUTH_TOKEN, 'x-twitter-active-user': 'yes' };
            if (ct0) headers['x-csrf-token'] = ct0;
            if (gt)  headers['x-guest-token'] = gt;

            const _fetchAC = new AbortController();
            const _fetchTimeout = setTimeout(() => _fetchAC.abort(), 8000);
            let res;
            try {
                res = await fetch(url, { headers, signal: _fetchAC.signal });
            } catch (e) {
                if (e.name === 'AbortError') _log('TMApi', 'fetchTweetMediaFromAPI timed out (8s)');
                else _log('TMApi', 'fetchTweetMediaFromAPI fetch error:', e);
                _circuitRecordFailure();
                return null;
            } finally {
                clearTimeout(_fetchTimeout);
            }
            if (!res.ok) {
                if (res.status === 401 || res.status === 403) {
                    _cachedBearerToken   = _FALLBACK_BEARER;
                    _bearerPendingPromise = null;
                    showToast(
                        '⚠️ API token expired. Set a custom Bearer Token in ⚙️ Settings → Advanced to restore video fetch.',
                        6000
                    );
                } else if (res.status === 404) {
                    _log('TMApi', 'TweetResultByRestId 404, cachedGqlId=', _cachedGqlId, '— 可能已輪替');
                    GM_deleteValue('app_gql_tweet_id');
                    _cachedGqlId = '2ICDjqPd81tulZcYrtpTuQ';
                }
                _circuitRecordFailure();
                return null;
            }
            _circuitRecordSuccess();
            let json = await res.json();
            let core = json.data?.tweetResult?.result?.tweet || json.data?.tweetResult?.result;
            if (!core) {
                _log('TMApi', 'fetchTweetMediaFromAPI: result 整層缺失(非 tombstone/unavailable),計入電路斷路器', statusId);
                _circuitRecordFailure();
                return null;
            }

            let result = { videos: [], images: [] };
            let quotedResult = { videos: [], images: [] };

            const walk = (obj, inQuote = false) => {
                if (!obj || typeof obj !== 'object') return;
                const target = inQuote ? quotedResult : result;

                if (obj.extended_entities?.media) {
                    obj.extended_entities.media.forEach(m => {
                        if (m.type === 'photo') {
                            target.images.push(m.media_url_https + '?name=orig');
                        } else if (m.type === 'video' || m.type === 'animated_gif') {
                            let mp4s = m.video_info?.variants?.filter(v => v.content_type === 'video/mp4') || [];
                            if (mp4s.length) {
                                let best = mp4s.sort((a,b)=>(b.bitrate||0)-(a.bitrate||0))[0];
                                target.videos.push(best.url.split('?')[0]);
                            }
                        }
                    });
                }

                ['tweet', 'retweeted_status_result', 'result', 'legacy'].forEach(k => {
                    if (obj[k]) walk(obj[k], inQuote);
                });
                if (obj.quoted_status_result) walk(obj.quoted_status_result, true);
            };
            walk(core);
            if (!result.images.length && !result.videos.length) {
                result = quotedResult;
            }

            try {
                const legacy = core.legacy || core.tweet?.legacy || null;
                if (legacy?.created_at) result.date = legacy.created_at;
                if (legacy?.full_text)  result.text = legacy.full_text;
                const userLegacy =
                    core.core?.user_results?.result?.legacy ||
                    core.user_results?.result?.legacy ||
                    core.user?.legacy || null;
                if (userLegacy?.name) result.displayName = userLegacy.name;
            } catch(_) {  }

            return result;
        } catch(e) {
            _log('TMApi', 'fetchTweetMediaFromAPI 未預期錯誤', e);
            _circuitRecordFailure();
            return null;
        }
    }

    async function extractVideoUrl(article) {
        const cached = _fiberVideoCache.get(article);
        if (cached && (Date.now() - cached.ts < _FIBER_CACHE_TTL)) return cached.urls;

        let statusId = null;
        const links = article.querySelectorAll('a[href*="/status/"]');
        for(let a of links) {
            const match = a.href.match(/\/status\/(\d+)/);
            if(match) { statusId = match[1]; break; }
        }

        if (statusId) {
            const apiData = await fetchTweetMediaFromAPI(statusId);
            if (apiData && apiData.videos.length > 0) {
                _fiberVideoCache.set(article, { urls: apiData.videos, ts: Date.now() });
                return apiData.videos;
            }
        }

        const _quotedVideoEls = new Set(article.querySelectorAll('[data-testid="tweet"] video'));
        const _allVideoEls = Array.from(article.querySelectorAll('video'));
        const _ownVideoEls = _allVideoEls.filter(v => !_quotedVideoEls.has(v));
        let result = (_ownVideoEls.length ? _ownVideoEls : _allVideoEls)
            .map(v => v.src || v.querySelector('source')?.src)
            .filter(src => src && src.includes('mp4') && !src.startsWith('blob:'))
            .map(src => src.split('?')[0]);
        return result;
    }

    async function extractMediaUrls(article) {
        const uniqueMedias = new Map();

        function addImageUrl(src) {
            if (!src) return;
            if (src.includes('/card_img/')) { uniqueMedias.set(src, src); return; }
            const idMatch = src.match(/\/(?:media|ext_tw_video_thumb|amplify_video_thumb|tweet_video_thumb)\/([A-Za-z0-9_-]+)/);
            const mediaId = idMatch ? idMatch[1] : src.split('?')[0];
            if (uniqueMedias.has(mediaId)) return;
            try {
                const url = new URL(src);
                url.searchParams.set('name', 'orig');
                uniqueMedias.set(mediaId, url.toString());
            } catch (e) {
                uniqueMedias.set(mediaId, src);
            }
        }

        let statusId = null;
        const links = article.querySelectorAll('a[href*="/status/"]');
        for(let a of links) {
            const match = a.href.match(/\/status\/(\d+)/);
            if(match) { statusId = match[1]; break; }
        }

        let apiSuccess = false;
        if (statusId) {
            const apiData = await fetchTweetMediaFromAPI(statusId);
            if (apiData && (apiData.videos.length > 0 || apiData.images.length > 0)) {
                apiData.videos.forEach(v => uniqueMedias.set(v, v));
                apiData.images.forEach(addImageUrl);
                apiSuccess = true;
            }
        }

        if (!apiSuccess) {
            const _quotedImgSrcs = new Set(
                Array.from(article.querySelectorAll('[data-testid="tweet"] img[src*="pbs.twimg.com"]')).map(img => img.src)
            );
            const _allImgSrcs = Array.from(article.querySelectorAll('img[src*="twimg.com"]'))
                .map(img => img.src)
                .filter(src => src.includes('pbs.twimg.com') && !src.includes('profile_images') && !src.includes('/emoji/') && !src.includes('twemoji'));
            const _ownImgSrcs = _allImgSrcs.filter(src => !_quotedImgSrcs.has(src));
            (_ownImgSrcs.length ? _ownImgSrcs : _allImgSrcs).forEach(addImageUrl);

            const videos = await extractVideoUrl(article);
            videos.forEach(v => uniqueMedias.set(v, v));
        }

        return Array.from(uniqueMedias.values());
    }

    function extractTweetUrl(article, baseUrl) {
        const candidates = [
            article.querySelector('a[href*="/status/"] > time')?.parentElement,
            article.querySelector('a[href*="/status/"]'),
        ];
        for (const el of candidates) {
            if (!el) continue;
            try {
                const pathname = new URL(el.href).pathname;
                return baseUrl + pathname;
            } catch (_) {}
        }
        return null;
    }

    function showActionToast(anchorEl, message, type = 'ok') {
        if (!message) return;
        const COLORS = { ok: '#1d9bf0', warn: '#ff8c00', error: '#e0245e' };
        const bg = COLORS[type] || COLORS.ok;

        clearTimeout(anchorEl._actionToastTimer);
        anchorEl._actionToast?.remove();

        const toast = document.createElement('div');
        toast.style.cssText = `
            position: fixed; z-index: 999999; pointer-events: none;
            background: ${bg}; color: #fff;
            padding: 5px 11px; border-radius: 10px;
            font: 600 12px/1.4 system-ui, -apple-system, sans-serif;
            white-space: nowrap; max-width: 220px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.28);
            opacity: 0; transition: opacity 0.18s ease;
        `;
        toast.textContent = message;
        document.body.appendChild(toast);
        anchorEl._actionToast = toast;

        requestAnimationFrame(() => {
            const r  = anchorEl.getBoundingClientRect();
            const tW = toast.offsetWidth;
            const tH = toast.offsetHeight;
            const GAP = 6;
            const vpW = window.innerWidth;

            let left = r.left + r.width / 2 - tW / 2;
            left = Math.max(8, Math.min(left, vpW - tW - 8));

            const topAbove = r.top - tH - GAP;
            const top = topAbove >= 8 ? topAbove : r.bottom + GAP;

            toast.style.left = `${Math.round(left)}px`;
            toast.style.top  = `${Math.round(top)}px`;
            toast.style.opacity = '1';
        });

        anchorEl._actionToastTimer = setTimeout(() => {
            toast.style.opacity = '0';
            setTimeout(() => {
                toast.remove();
                if (anchorEl._actionToast === toast) anchorEl._actionToast = null;
            }, 200);
        }, 1500);
    }
    const _LINK_ACTION_LABELS = {
        copy_url:           T.cma_action_copy_url     || 'Copy Tweet URL',
        copy_prefix_url:    T.cma_action_copy_prefix   || 'Copy with Prefix',
        save_text_bookmark: T.cma_action_save_bookmark || 'Save as Text Bookmark',
        none:               T.cma_action_none          || 'No Action',
    };
    const _LINK_SLOT_LABELS = {
        click:     T.cma_slot_click     || 'Single Click',
        longpress: T.cma_slot_longpress || 'Long Press',
        mclick:    T.cma_slot_mclick     || 'Middle Click',
        rclick:    T.cma_slot_rclick     || 'Right Click',
    };
    function _createMediaActionController(article, btn) {
        const SVG_FILM = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="16" height="12" rx="2"/><line x1="2" y1="7" x2="18" y2="7"/><line x1="2" y1="13" x2="18" y2="13"/><line x1="6" y1="4" x2="6" y2="7"/><line x1="10" y1="4" x2="10" y2="7"/><line x1="14" y1="4" x2="14" y2="7"/><line x1="6" y1="13" x2="6" y2="16"/><line x1="10" y1="13" x2="10" y2="16"/><line x1="14" y1="13" x2="14" y2="16"/></svg>`;
        const SVG_FILM_PLAY = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="14" height="14" rx="2.5"/><path d="M8 7.3v5.4l4.6-2.7z" fill="currentColor" stroke="none"/></svg>`;
        const SVG_FILM_DOWNLOAD = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3v9M6.5 9L10 12.5 13.5 9"/><path d="M4 15.5h12"/></svg>`;
        const SVG_CHECK_SM = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4,10 8,14 16,6"/></svg>`;
        const SVG_PREFIX_COPY = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
        const SVG_DL = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3v10M6 9l4 4 4-4"/><line x1="3" y1="17" x2="17" y2="17"/></svg>`;
        const COLOR_DL_OK = '#00ba7c';
        const _MEDIA_ICON_MAP = { classic: SVG_FILM, play: SVG_FILM_PLAY, download: SVG_FILM_DOWNLOAD };
        const _mediaIconSvg = _MEDIA_ICON_MAP[GM_getValue(KEY_MEDIA_ICON_STYLE, 'classic')] || SVG_FILM;

        const setMediaIcon = (state, extra, silentText, actionType = 'copy') => {
            const fbStyle = _cachedFeedbackStyle;
            btn.classList.remove('tm-anim-pop');
            btn.classList.remove('tm-anim-pulse');
            btn.classList.remove('tm-anim-flash');

            const setIconMode = (svg, customColor) => {
                const safeColor = (customColor && /^#[0-9a-fA-F]{3,8}$/.test(customColor)) ? customColor : null;
                btn.innerHTML = svg;
                btn.classList.add('tm-anim-pop');
                if (safeColor) {
                    const svgEl = btn.querySelector('svg');
                    if (svgEl) {
                        svgEl.style.color = safeColor;
                        svgEl.style.filter = `drop-shadow(0 0 4px ${safeColor}66)`;
                    }
                }
            };
            const setIconModeOnly = (svg, customColor) => {
                const safeColor = (customColor && /^#[0-9a-fA-F]{3,8}$/.test(customColor)) ? customColor : null;
                btn.innerHTML = svg;
                if (safeColor) {
                    const svgEl = btn.querySelector('svg');
                    if (svgEl) {
                        svgEl.style.color = safeColor;
                        svgEl.style.filter = `drop-shadow(0 0 4px ${safeColor}66)`;
                    }
                }
            };

            const getFeedbackText = () => silentText || extra;

            if (state === 'default') {
                btn.innerHTML = _mediaIconSvg;
            } else if (state === 'dl') {
                btn.innerHTML = SVG_DL;
            } else if (state === 'ok') {
                if (fbStyle === 'slide') {
                    const span = document.createElement('span');
                    span.className = 'tm-anim-pop-text tm-slide';
                    span.textContent = getFeedbackText() || 'Copied';
                    btn.innerHTML = '';
                    btn.appendChild(span);
                    span.addEventListener('animationend', () => setMediaIcon('default'), { once: true });
                } else if (fbStyle === 'pulse') {
                    if (actionType === 'prefix')   setIconModeOnly(SVG_PREFIX_COPY);
                    else if (actionType === 'download') setIconModeOnly(SVG_DL, COLOR_DL_OK);
                    else                           setIconModeOnly(SVG_CHECK_SM);
                    btn.classList.add('tm-anim-pulse');
                } else if (fbStyle === 'flash') {
                    if (actionType === 'prefix')   setIconModeOnly(SVG_PREFIX_COPY);
                    else if (actionType === 'download') setIconModeOnly(SVG_DL, COLOR_DL_OK);
                    else                           setIconModeOnly(SVG_CHECK_SM);
                    btn.classList.add('tm-anim-flash');
                } else if (fbStyle === 'icon') {
                    if (actionType === 'prefix')        setIconMode(SVG_PREFIX_COPY);
                    else if (actionType === 'download') setIconMode(SVG_DL, COLOR_DL_OK);
                    else                                setIconMode(SVG_CHECK_SM);
                } else {
                    btn.innerHTML = SVG_CHECK_SM;
                    btn.classList.add('tm-anim-pop');
                    showActionToast(btn, extra || T.msg_copied, 'ok');
                }
            } else if (state === 'warn') {
                if (fbStyle === 'icon') {
                    setIconMode(_mediaIconSvg, '#ff8c00');
                } else {
                    btn.innerHTML = _mediaIconSvg;
                    showActionToast(btn, extra, 'warn');
                }
            } else {
                if (fbStyle === 'icon') {
                    setIconMode(_mediaIconSvg, state === 'error' ? '#e0245e' : null);
                } else {
                    btn.innerHTML = _mediaIconSvg;
                    if (extra) showActionToast(btn, extra, state === 'error' ? 'error' : 'ok');
                }
            }

            const tweetId = _getTweetIdFromArticle(article);
            if (tweetId && _downloadedIds.has(tweetId)) {
                const _alreadyShown = btn.getAttribute('data-tm-badge-shown') === tweetId;
                _applyHistoryBadge(btn, _alreadyShown);
                if (!_alreadyShown) btn.setAttribute('data-tm-badge-shown', tweetId);
            }
        };

            const _doDownloadAll = async () => {
                const urls = await extractMediaUrls(article);
                if (!urls.length) { setMediaIcon('msg', T.msg_no_media, 'No Media'); setTimeout(() => setMediaIcon('default'), 1500); return; }
                const info = getTweetInfo(article);
                setMediaIcon('dl');
                const ring = createProgressRing();
                ring.el.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);pointer-events:none;';
                btn.appendChild(ring.el);
                let index = 1, failCount = 0;
                const total = urls.length;
                for (const url of urls) {
                    let ext = '.jpg';
                    if (url.includes('.mp4')) ext = '.mp4';
                    else if (url.includes('format=png')) ext = '.png';
                    else { const parts = url.split('/').pop().split('?')[0].split('.'); if (parts.length > 1) ext = '.' + parts.pop(); }
                    const safeDisplay = sanitizeForFilename(info.displayName);
                    const safeScreen  = sanitizeForFilename(info.screenName);
                    const textPart    = info.text ? `_${sanitizeForFilename(info.text, 40)}` : '';
                    const safeTextForToken = sanitizeForFilename(info.text || '', CUSTOM_FN_TEXT_MAXLEN);
                    const filename    = buildCustomFilename(
                        { screenName: safeScreen, displayName: safeDisplay, date: info.date, id: info.id, index, ext: ext.replace(/^\./, ''), text: safeTextForToken, origName: _extractOrigMediaName(url) },
                        () => `[twitter] ${safeDisplay}(@${safeScreen})_${info.date}${textPart}_${info.id}_${index}${ext}`
                    );
                    const fileOffset  = (index - 1) / total;
                    const fileShare   = 1 / total;
                    try {
                        await forceDownloadBlob(url, filename, (pct) => {
                            ring.update(pct === null ? null : Math.round((fileOffset + fileShare * pct / 100) * 100));
                        });
                    } catch(_) { failCount++; }
                    await new Promise(r => setTimeout(r, 250));
                    index++;
                }
                ring.remove();
                const successCount = total - failCount;
                if (failCount > 0) {
                    setMediaIcon('warn', `⚠️ ${successCount}/${total}`);
                    showToast(`⚠️ Downloaded ${successCount} of ${total} files (${failCount} failed).`);
                } else {
                    setMediaIcon('ok', T.msg_downloaded, 'Downloaded', 'download');
                    showToast(`✅ Downloaded ${total} file${total !== 1 ? 's' : ''}.`);
                    recordHistory(info, urls, btn);
                    fireMeteor(btn);
                }
                setTimeout(() => setMediaIcon('default'), 2000);
            };

            const _getMediaItems = () => {
                return [
                {
                    itemClass: 'tm-menu-item--copied',
                    icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="9" height="12" rx="1.5"/><path d="M3 4H2a1 1 0 0 0-1 1v8a1.5 1.5 0 0 0 1.5 1.5H11"/><line x1="7.5" y1="6" x2="11.5" y2="6"/><line x1="7.5" y1="8.5" x2="11.5" y2="8.5"/><line x1="7.5" y1="11" x2="10" y2="11"/></svg>`,
                    label: T.cma_action_copy || 'Copy Media URLs',
                    action: async () => {
                        const urls = await extractMediaUrls(article);
                        if (!urls.length) { setMediaIcon('msg', T.msg_no_media, 'No Media'); setTimeout(() => setMediaIcon('default'), 1500); return; }
                        GM_setClipboard(urls.join('\n'));
                        setMediaIcon('ok', T.msg_copied, 'Copied', 'copy');
                        setTimeout(() => setMediaIcon('default'), 1500);
                    },
                },
                {
                    itemClass: 'tm-menu-item--downloaded',
                    icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v8M5 7l3 3 3-3"/><path d="M2 12v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-1"/></svg>`,
                    label: T.cma_action_download_all || 'Download All',
                    action: _doDownloadAll,
                },
                'divider',
                {
                    itemClass: 'tm-menu-item--prefix',
                    icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="1.5" y="4" width="9" height="9" rx="1.5"/><path d="M5.5 4V2.5A1 1 0 0 1 6.5 1.5h7a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H12"/><line x1="4" y1="8" x2="8" y2="8"/><line x1="4" y1="10.5" x2="7" y2="10.5"/></svg>`,
                    label: T.cma_action_copy_prefix || 'Copy with Prefix',
                    action: async () => {
                        const urls = await extractMediaUrls(article);
                        if (!urls.length) { setMediaIcon('msg', T.msg_no_media, 'No Media'); setTimeout(() => setMediaIcon('default'), 1500); return; }
                        const prefix = GM_getValue(KEY_PREFIX_TEXT, '[text]');
                        GM_setClipboard(urls.map(u => `${prefix}(${u})`).join('\n'));
                        setMediaIcon('ok', T.msg_prefix_copied, 'Prefix Copied', 'prefix');
                        setTimeout(() => setMediaIcon('default'), 1500);
                    },
                },
                {
                    itemClass: 'tm-menu-item--preview',
                    icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="8" cy="8" rx="6.5" ry="4"/><circle cx="8" cy="8" r="2"/></svg>`,
                    label: T.menu_preview ? T.menu_preview.replace(/^👁\s*/, '') : 'Preview',
                    action: async () => {
                        let videos = [], imgUrls = [], statusId = null;
                        for (const a of article.querySelectorAll('a[href*="/status/"]')) {
                            const m = a.href.match(/\/status\/(\d+)/);
                            if (m) { statusId = m[1]; break; }
                        }
                        if (statusId) {
                            const apiData = await fetchTweetMediaFromAPI(statusId);
                            if (apiData) { videos = apiData.videos || []; imgUrls = apiData.images || []; }
                        }
                        if (!videos.length && !imgUrls.length) {
                            videos = await extractVideoUrl(article);
                            const all = await extractMediaUrls(article);
                            imgUrls = all.filter(u => !new Set(videos).has(u));
                        }
                        if (videos.length && imgUrls.length) showFloatingVideoPlayer(videos, 0, imgUrls, false, getTweetInfo(article));
                        else if (videos.length) showFloatingVideoPlayer(videos, 0, null, false, getTweetInfo(article));
                        else if (imgUrls.length) showImageLightbox(imgUrls, null, false, getTweetInfo(article));
                        else { setMediaIcon('msg', T.msg_no_media, 'No Media'); setTimeout(() => setMediaIcon('default'), 1500); }
                    },
                },
                ];
            };

            const _getCustomMediaActions = () => {
                try {
                    return { ...DEFAULT_MEDIA_ACTIONS, ...JSON.parse(GM_getValue(KEY_CUSTOM_MEDIA_ACTIONS, '{}')) };
                } catch (_) { return { ...DEFAULT_MEDIA_ACTIONS }; }
            };

            const _mediaActionCopy = async () => {
                const urls = await extractMediaUrls(article);
                if (!urls.length) {
                    setMediaIcon('msg', T.msg_no_media, 'No Media');
                    setTimeout(() => setMediaIcon('default'), 1500);
                    return;
                }
                GM_setClipboard(urls.join('\n'));
                setMediaIcon('ok', T.msg_copied, 'Copied', 'copy');
                setTimeout(() => setMediaIcon('default'), 1500);
                if (isFeatureNew('right_click_tip')) {
                    markFeatureSeen('right_click_tip');
                    setTimeout(() => showToast('💡 Tip: Right-click this button to download all media directly.', 5000), 400);
                }
            };

            const _mediaActionCopyPrefix = async () => {
                const urls = await extractMediaUrls(article);
                if (!urls.length) {
                    setMediaIcon('msg', T.msg_no_media, 'No Media');
                    setTimeout(() => setMediaIcon('default'), 1500);
                    return;
                }
                const prefix = GM_getValue(KEY_PREFIX_TEXT, '[text]');
                const txt = urls.map(u => `${prefix}(${u})`).join('\n');
                GM_setClipboard(txt);
                setMediaIcon('ok', T.msg_prefix_copied, 'Prefix Copied', 'prefix');
                setTimeout(() => setMediaIcon('default'), 1500);
            };

            const _mediaActionPreview = async () => {
                let videos = [], imgUrls = [];

                let statusId = null;
                for (const a of article.querySelectorAll('a[href*="/status/"]')) {
                    const m = a.href.match(/\/status\/(\d+)/);
                    if (m) { statusId = m[1]; break; }
                }

                if (statusId) {
                    const apiData = await fetchTweetMediaFromAPI(statusId);
                    if (apiData) {
                        videos  = apiData.videos  || [];
                        imgUrls = apiData.images   || [];
                    }
                }

                if (!videos.length && !imgUrls.length) {
                    videos  = await extractVideoUrl(article);
                    const allUrls = await extractMediaUrls(article);
                    const videoSet = new Set(videos);
                    imgUrls = allUrls.filter(u => !videoSet.has(u));
                }

                if (videos.length && imgUrls.length) {
                    showFloatingVideoPlayer(videos, 0, imgUrls, false, getTweetInfo(article));
                } else if (videos.length) {
                    showFloatingVideoPlayer(videos, 0, null, false, getTweetInfo(article));
                } else if (imgUrls.length) {
                    showImageLightbox(imgUrls, null, false, getTweetInfo(article));
                } else {
                    setMediaIcon('msg', T.msg_no_media, 'No Media');
                    setTimeout(() => setMediaIcon('default'), 1500);
                }
            };

            const _mediaActionDownloadAll = async () => {
                const urls = await extractMediaUrls(article);

                if (urls.length === 0) {
                    const info = getTweetInfo(article);
                    const rawText = article.querySelector('[data-testid="tweetText"]')?.innerText?.trim() || '';
                    _dialogOpenGlobal = true;
                    const confirmed = confirm(
                        `This post has no media.\n\n` +
                        `"${rawText.slice(0, 100)}${rawText.length > 100 ? '…' : ''}"\n\n` +
                        `Save it as a text bookmark for grouping?`
                    );
                    _dialogOpenGlobal = false;
                    if (!confirmed) return;
                    try {
                        const _now   = new Date();
                        const _yy    = _now.getFullYear();
                        const _mm    = String(_now.getMonth() + 1).padStart(2, '0');
                        const yyyymm = `${_yy}.${_mm}`;
                        const record = {
                            id:           Date.now(),
                            ts:           Date.now(),
                            yyyymm,
                            tweetId:      info.id,
                            tweetUrl:     `https://x.com/${info.screenName}/status/${info.id}`,
                            tweetDate:    info.date,
                            downloadDate: `${_yy}-${_mm}-${String(_now.getDate()).padStart(2,'0')}`,
                            screenName:   info.screenName,
                            displayName:  info.displayName,
                            text:         (rawText || info.text || '').slice(0, 280),
                            thumbUrls:    [],
                            mediaUrls:    [],
                            hasVideo:     false,
                            count:        0,
                            textOnly:     true,
                            avatarUrl:    info.avatarUrl || '',
                        };
                        const _tym = record.yyyymm;
                        let _tRecs = _readMonthRecords(_tym);
                        const _tOld = _tRecs.find(r => r.tweetId === info.id);
                        if (_tOld?.favorited) record.favorited = true;
                        _tRecs = _tRecs.filter(r => r.tweetId !== info.id);
                        _tRecs.unshift(record);
                        _updateHistoryIndex(_tym);
                        _writeMonthRecords(_tym, _tRecs);
                        _downloadedIds.add(info.id);
                        const _etx = document.getElementById('tm-hist-panel');
                        if (_etx) _etx.dispatchEvent(new CustomEvent('tm-hist-refresh'));
                        setMediaIcon('ok', '📌 Saved', 'Saved');
                        setTimeout(() => setMediaIcon('default'), 1800);
                        if (GM_getValue(KEY_GROUP_ON_DOWNLOAD, false)) {
                            StarPipState.pendingIsText        = true;
                            StarPipState.pendingGroupRecordId = record.id;
                            setTimeout(() => popStarPip(btn || null), 80);
                        }
                    } catch (err) {
                        console.warn('[MediaDL] textOnly record failed:', err);
                    }
                    return;
                }

                const info = getTweetInfo(article);
                setMediaIcon('dl');

                const ring = createProgressRing();
                ring.el.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);pointer-events:none;';
                btn.appendChild(ring.el);

                let index = 1;
                let failCount = 0;
                const total = urls.length;

                for (const url of urls) {
                    let ext = '.jpg';
                    if (url.includes('.mp4')) ext = '.mp4';
                    else if (url.includes('format=png')) ext = '.png';
                    else {
                         const parts = url.split('/').pop().split('?')[0].split('.');
                         if (parts.length > 1) ext = '.' + parts.pop();
                    }

                    const textPart = info.text ? `_${sanitizeForFilename(info.text, 40)}` : "";
                    const safeTextForToken = sanitizeForFilename(info.text || '', CUSTOM_FN_TEXT_MAXLEN);
                    const safeDisplay = sanitizeForFilename(info.displayName);
                    const safeScreen = sanitizeForFilename(info.screenName);
                    const filename = buildCustomFilename(
                        { screenName: safeScreen, displayName: safeDisplay, date: info.date, id: info.id, index, ext: ext.replace(/^\./, ''), text: safeTextForToken, origName: _extractOrigMediaName(url) },
                        () => `[twitter] ${safeDisplay}(@${safeScreen})_${info.date}${textPart}_${info.id}_${index}${ext}`
                    );

                    const fileOffset = (index - 1) / total;
                    const fileShare  = 1 / total;
                    try {
                        await forceDownloadBlob(url, filename, (pct) => {
                            if (pct === null) {
                                ring.update(null);
                            } else {
                                ring.update(Math.round((fileOffset + fileShare * pct / 100) * 100));
                            }
                        });
                    } catch(_) {
                        failCount++;
                    }
                    await new Promise(r => setTimeout(r, 250));
                    index++;
                }

                ring.remove();
                const successCount = total - failCount;
                if (failCount > 0) {
                    setMediaIcon('warn', `⚠️ ${successCount}/${total}`);
                    showToast(`⚠️ Downloaded ${successCount} of ${total} files (${failCount} failed).`);
                } else {
                    setMediaIcon('ok', T.msg_downloaded, 'Downloaded', 'download');
                    showToast(`✅ Downloaded ${total} file${total !== 1 ? 's' : ''}.`);
                    recordHistory(info, urls, btn);
                    fireMeteor(btn);
                }
                setTimeout(() => setMediaIcon('default'), 2000);
            };

        return {
            setIcon: setMediaIcon,
            getItems: _getMediaItems,
            getCustomActions: _getCustomMediaActions,
            copy: _mediaActionCopy,
            copyPrefix: _mediaActionCopyPrefix,
            preview: _mediaActionPreview,
            downloadAll: _mediaActionDownloadAll,
        };
    }

    function _createLinkActionController(article, icon) {
            const SVG_LINK = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M8 12a4 4 0 0 0 5.66 0l2-2a4 4 0 0 0-5.66-5.66l-1 1"/><path d="M12 8a4 4 0 0 0-5.66 0l-2 2a4 4 0 0 0 5.66 5.66l1-1"/></svg>`;
            const SVG_LINK_ROUNDED = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 13.5L7.2 15.3a2.8 2.8 0 0 1-4-4L5 9.5"/><path d="M11 6.5l1.8-1.8a2.8 2.8 0 0 1 4 4L15 10.5"/></svg>`;
            const SVG_LINK_COPY = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="7" width="10" height="10" rx="2"/><path d="M4 13V5a2 2 0 0 1 2-2h8"/></svg>`;
            const SVG_CHECK = `<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4,10 8,14 16,6"/></svg>`;
            const SVG_PREFIX_COPY = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
            const _LINK_ICON_MAP = { classic: SVG_LINK, rounded: SVG_LINK_ROUNDED, copy: SVG_LINK_COPY };
            const _linkIconSvg = _LINK_ICON_MAP[GM_getValue(KEY_LINK_ICON_STYLE, 'classic')] || SVG_LINK;

            const setLinkIcon = (state, extra, silentText, actionType = 'copy') => {
                const fbStyle = GM_getValue(KEY_FEEDBACK_STYLE, 'toast');
                icon.classList.remove('tm-anim-pop');
                icon.classList.remove('tm-anim-pulse');
                icon.classList.remove('tm-anim-flash');

                const setIconMode = (svg) => {
                    icon.innerHTML = svg;
                    icon.classList.add('tm-anim-pop');
                };
                const setIconModeOnly = (svg, customColor) => {
                    const safeColor = (customColor && /^#[0-9a-fA-F]{3,8}$/.test(customColor)) ? customColor : null;
                    icon.innerHTML = svg;
                    if (safeColor) {
                        const svgEl = icon.querySelector('svg');
                        if (svgEl) {
                            svgEl.style.color = safeColor;
                            svgEl.style.filter = `drop-shadow(0 0 4px ${safeColor}66)`;
                        }
                    }
                };

                if (state === 'ok') {
                    if (fbStyle === 'slide') {
                        const span = document.createElement('span');
                        span.className = 'tm-anim-pop-text tm-slide';
                        span.textContent = silentText || extra || 'Copied';
                        icon.innerHTML = '';
                        icon.appendChild(span);
                        span.addEventListener('animationend', () => setLinkIcon('default'), { once: true });
                    } else if (fbStyle === 'pulse') {
                        const useSvg = actionType === 'prefix' ? SVG_PREFIX_COPY : SVG_CHECK;
                        setIconModeOnly(useSvg);
                        icon.classList.add('tm-anim-pulse');
                    } else if (fbStyle === 'flash') {
                        const useSvg = actionType === 'prefix' ? SVG_PREFIX_COPY : SVG_CHECK;
                        setIconModeOnly(useSvg);
                        icon.classList.add('tm-anim-flash');
                    } else if (fbStyle === 'icon') {
                        const useSvg = actionType === 'prefix' ? SVG_PREFIX_COPY : SVG_CHECK;
                        setIconMode(useSvg);
                    } else {
                        icon.innerHTML = SVG_CHECK;
                        icon.classList.add('tm-anim-pop');
                        showActionToast(icon, extra || T.msg_copied, 'ok');
                    }
                } else {
                    icon.innerHTML = _linkIconSvg;
                }
            };
            setLinkIcon('default');

            const _doSaveLinkTextBookmark = async () => {
                const rawText = article.querySelector('[data-testid="tweetText"]')?.innerText?.trim() || '';
                const _allTweetTexts = article.querySelectorAll('[data-testid="tweetText"]');
                const _quotedText = _allTweetTexts.length > 1 ? (_allTweetTexts[1].innerText?.trim() || '') : '';
                const _fullSaveText = _quotedText ? `${rawText}\n\n[Quoted] ${_quotedText}` : rawText;
                const info = getTweetInfo(article);
                if (!rawText) {
                    _dialogOpenGlobal = true;
                    const ok = confirm(`This post has no text content.\nSave it as an empty text bookmark anyway?`);
                    _dialogOpenGlobal = false;
                    if (!ok) return;
                }
                try {
                    const _now   = new Date();
                    const _yy    = _now.getFullYear();
                    const _mm    = String(_now.getMonth() + 1).padStart(2, '0');
                    const yyyymm = `${_yy}.${_mm}`;
                    const record = {
                        id:           Date.now(),
                        ts:           Date.now(),
                        yyyymm,
                        tweetId:      info.id,
                        tweetUrl:     `https://x.com/${info.screenName}/status/${info.id}`,
                        tweetDate:    info.date,
                        downloadDate: `${_yy}-${_mm}-${String(_now.getDate()).padStart(2,'0')}`,
                        screenName:   info.screenName,
                        displayName:  info.displayName,
                        text:         _fullSaveText || '',
                        thumbUrls:    [],
                        mediaUrls:    [],
                        hasVideo:     false,
                        count:        0,
                        textOnly:     true,
                        avatarUrl:    info.avatarUrl || '',
                    };
                    const _tym  = record.yyyymm;
                    let _tRecs  = _readMonthRecords(_tym);
                    const _tOld = _tRecs.find(r => r.tweetId === info.id);
                    if (_tOld?.favorited) record.favorited = true;
                    _tRecs = _tRecs.filter(r => r.tweetId !== info.id);
                    _tRecs.unshift(record);
                    _updateHistoryIndex(_tym);
                    _writeMonthRecords(_tym, _tRecs);
                    _downloadedIds.add(info.id);
                    setLinkIcon('ok', '📌 Saved', 'Saved');
                    setTimeout(() => setLinkIcon('default'), 1800);
                    const _existPanel = document.getElementById('tm-hist-panel');
                    if (_existPanel) _existPanel.dispatchEvent(new CustomEvent('tm-hist-refresh'));
                    if (GM_getValue(KEY_GROUP_ON_DOWNLOAD, false)) {
                        StarPipState.pendingIsText        = true;
                        StarPipState.pendingGroupRecordId = record.id;
                        setTimeout(() => openTextGroupMenu(icon), 80);
                    }
                } catch (err) {
                    console.warn('[LinkBtn] textBookmark failed:', err);
                    setLinkIcon('default');
                }
            };

            const _linkTargetUrl = () => {
                const useCustom = _cachedClickModeCustom;
                const targetDomain = useCustom ? GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com') : 'x.com';
                return extractTweetUrl(article, 'https://' + targetDomain);
            };

            const _linkActionCopyUrl = async () => {
                const url = _linkTargetUrl();
                if (!url) return;
                GM_setClipboard(url);
                setLinkIcon('ok', T.msg_copied, 'Copied', 'copy');
                setTimeout(() => setLinkIcon('default'), 1500);
            };

            const _linkActionCopyPrefixUrl = async () => {
                const url = _linkTargetUrl();
                if (!url) return;
                const prefix = GM_getValue(KEY_PREFIX_TEXT, '[text]');
                GM_setClipboard(`${prefix}(${url})`);
                setLinkIcon('ok', T.msg_prefix_copied, 'Prefix Copied', 'prefix');
                setTimeout(() => setLinkIcon('default'), 1500);
            };

            const _linkActionSaveTextBookmark = _doSaveLinkTextBookmark;

            const _linkActionNone = async () => {};

            const _LINK_ACTION_REGISTRY = {
                copy_url:           _linkActionCopyUrl,
                copy_prefix_url:    _linkActionCopyPrefixUrl,
                save_text_bookmark: _linkActionSaveTextBookmark,
                none:               _linkActionNone,
            };

                const _getLinkItems = () => [
                    {
                        itemClass: 'tm-menu-item--copied',
                        icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 9.5a3.5 3.5 0 0 0 5 0l2-2a3.5 3.5 0 0 0-5-5L7 4"/><path d="M9.5 6.5a3.5 3.5 0 0 0-5 0l-2 2a3.5 3.5 0 0 0 5 5L9 12"/></svg>`,
                        label: T.cma_action_copy_url || 'Copy Tweet URL',
                        action: _linkActionCopyUrl,
                    },
                    {
                        itemClass: 'tm-menu-item--prefix',
                        icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="1.5" y="4" width="9" height="9" rx="1.5"/><path d="M5.5 4V2.5A1 1 0 0 1 6.5 1.5h7a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H12"/><line x1="4" y1="8" x2="8" y2="8"/><line x1="4" y1="10.5" x2="7" y2="10.5"/></svg>`,
                        label: T.cma_action_copy_prefix || 'Copy with Prefix',
                        action: _linkActionCopyPrefixUrl,
                    },
                    'divider',
                    {
                        icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2h10a1 1 0 0 1 1 1v11l-5-3-5 3V3a1 1 0 0 1 1-1z"/></svg>`,
                        label: T.cma_action_save_bookmark || 'Save as Text Bookmark',
                        action: _linkActionSaveTextBookmark,
                    },
                ];

                const _getCustomLinkActions = () => {
                    try {
                        return { ...DEFAULT_LINK_ACTIONS, ...JSON.parse(GM_getValue(KEY_CUSTOM_LINK_ACTIONS, '{}')) };
                    } catch (_) { return { ...DEFAULT_LINK_ACTIONS }; }
                };

        return {
            setIcon: setLinkIcon,
            saveTextBookmark: _doSaveLinkTextBookmark,
            targetUrl: _linkTargetUrl,
            copyUrl: _linkActionCopyUrl,
            copyPrefixUrl: _linkActionCopyPrefixUrl,
            actionNone: _linkActionNone,
            registry: _LINK_ACTION_REGISTRY,
            getItems: _getLinkItems,
            getCustomActions: _getCustomLinkActions,
        };
    }

    function insertCopyButton(article) {
        if (!article.querySelector('time')) {
            article.querySelector(`.${BUTTON_CLASS}`)?.remove();
            article.querySelector('.custom-copy-icon')?.remove();
            return;
        }
        {
            const _existingBtn  = article.querySelector(`.${BUTTON_CLASS}`);
            const _existingIcon = article.querySelector('.custom-copy-icon');
            if (_existingBtn) {
                const _needRebind =
                    (_cachedClickMode === 'menu'    && !_existingBtn._menuAC)  ||
                    (_cachedClickMode === 'classic' && !!_existingBtn._menuAC);
                if (!_needRebind) return;
                _existingBtn.remove();
                _existingIcon?.remove();
            }
        }
        const actions = Array.from(article.querySelectorAll('[role="group"]')).pop();
        if (!actions) return;

        const _cloneSrc = Array.from(actions.children).reverse().find(_el => _el.hasAttribute('data-testid'))
            || Array.from(actions.children).reverse().find(_el => !_el.classList.contains(BUTTON_CLASS) && !_el.classList.contains('custom-copy-icon') && !_el.classList.contains('mtga-btn'))
            || actions.children[actions.children.length - 1];
        if (!_cloneSrc) return;

        if (!document.getElementById('tm-icon-anim-style')) {
            const s = document.createElement('style');
            s.id = 'tm-icon-anim-style';
            s.textContent = `
                @keyframes tm-pop-bounce {
                    0%   { transform: scale(0.5); opacity: 0; }
                    60%  { transform: scale(1.15); opacity: 1; }
                    100% { transform: scale(1); opacity: 1; }
                }
                @keyframes tm-pop-bounce-text {
                    0%   { transform: translateY(-50%) scale(0.5); opacity: 0; }
                    60%  { transform: translateY(-50%) scale(1.1); opacity: 1; }
                    100% { transform: translateY(-50%) scale(1); opacity: 1; }
                }
                .tm-anim-pop {
                    animation: tm-pop-bounce 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
                }
                .tm-anim-pop-text {
                    position: absolute;
                    right: 4px;
                    top: 50%;
                    transform: translateY(-50%);
                    transform-origin: right center;
                    white-space: nowrap;
                    font-weight: 700;
                    font-size: 13px;
                    font-family: system-ui, -apple-system, sans-serif;
                    background: rgba(128, 128, 128, 0.2);
                    backdrop-filter: blur(4px);
                    padding: 5px 12px;
                    border-radius: 9999px;
                    color: currentColor;
                    z-index: 9999;
                    pointer-events: none;
                    animation: tm-pop-bounce-text 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
                }
            `;
            document.head.appendChild(s);
        }

        const btn = _cloneSrc.cloneNode(true);
        btn.classList.add(BUTTON_CLASS);
        btn.removeAttribute('data-testid');
        btn.removeAttribute('id');
        btn.querySelectorAll('[data-testid], [id]').forEach(_el => { _el.removeAttribute('data-testid'); _el.removeAttribute('id'); });
        btn.querySelectorAll('button, [role="button"]').forEach(_el => _el.removeAttribute('disabled'));
        const _btnTooltipText = _cachedClickMode === 'menu' ? T.btn_tooltip_menu : T.btn_tooltip;
        btn.title = _btnTooltipText;
        btn.setAttribute('aria-label', _btnTooltipText || 'Media: copy URL / download');
        btn.setAttribute('role', 'button');
        btn.style.position = 'relative';

        const mediaCtl = _createMediaActionController(article, btn);
        const setMediaIcon = mediaCtl.setIcon;

        setMediaIcon('default');

        const _clickMode = _cachedClickMode;

        if (_clickMode === 'menu') {
            const _getMediaItems = mediaCtl.getItems;

            { const _acC = _bindMenuClick(btn, _getMediaItems);
              const _acH = _bindMenuHover(btn, _getMediaItems);
              btn._menuAC = { abort() { _acC.abort(); _acH.abort(); } }; }
            btn.addEventListener('contextmenu', e => { e.preventDefault(); e.stopPropagation(); });

        } else {

            const _getCustomMediaActions = mediaCtl.getCustomActions;
            const _mediaActionCopy = mediaCtl.copy;
            const _mediaActionCopyPrefix = mediaCtl.copyPrefix;
            const _mediaActionPreview = mediaCtl.preview;
            const _mediaActionDownloadAll = mediaCtl.downloadAll;

            const _MEDIA_ACTION_REGISTRY = {
                copy:         _mediaActionCopy,
                copy_prefix:  _mediaActionCopyPrefix,
                preview:      _mediaActionPreview,
                download_all: _mediaActionDownloadAll,
            };

            let timer = null;
            let longFired = false;
            let _pressing = false;
            btn.addEventListener('mousedown', async (e) => {
                e.preventDefault(); e.stopPropagation();
                const map = _getCustomMediaActions();

                if (e.button === 0) {
                    longFired = false;
                    _pressing = true;
                    timer = setTimeout(async () => {
                        longFired = true;
                        _pressing = false;
                        timer = null;
                        await (_MEDIA_ACTION_REGISTRY[map.longpress] || _mediaActionCopyPrefix)();
                    }, 400);

                } else if (e.button === 1) {
                    await (_MEDIA_ACTION_REGISTRY[map.mclick] || _mediaActionPreview)();
                }
            });

            btn.addEventListener('mouseup', async (e) => {
                if (e.button !== 0) return;
                if (timer) {
                    clearTimeout(timer); timer = null;
                    _pressing = false;
                    if (longFired) return;
                    const map = _getCustomMediaActions();
                    await (_MEDIA_ACTION_REGISTRY[map.click] || _mediaActionCopy)();
                }
            });
            btn.addEventListener('mouseleave', () => { if (timer && !_pressing) { clearTimeout(timer); timer = null; } });
            btn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); });

            btn.addEventListener('contextmenu', async (e) => {
                e.preventDefault(); e.stopPropagation();
                const map = _getCustomMediaActions();
                await (_MEDIA_ACTION_REGISTRY[map.rclick] || _mediaActionDownloadAll)();
            });
        }

        const LINK_BTN_CLASS = 'custom-copy-icon';
        if (!article.querySelector(`.${LINK_BTN_CLASS}`)) {
            const icon = _cloneSrc.cloneNode(true);
            icon.classList.add(LINK_BTN_CLASS);
            icon.removeAttribute('data-testid');
            icon.removeAttribute('id');
            icon.querySelectorAll('[data-testid], [id]').forEach(_el => { _el.removeAttribute('data-testid'); _el.removeAttribute('id'); });
            icon.querySelectorAll('button, [role="button"]').forEach(_el => _el.removeAttribute('disabled'));
            icon.style.position = 'relative';

            const linkCtl = _createLinkActionController(article, icon);
            const _doSaveLinkTextBookmark = linkCtl.saveTextBookmark;
            const _linkActionSaveTextBookmark = linkCtl.saveTextBookmark;
            const _linkActionNone = linkCtl.actionNone;
            const _LINK_ACTION_REGISTRY = linkCtl.registry;

            if (_cachedClickMode === 'menu') {
                const _getLinkItems = linkCtl.getItems;
                { const _acC = _bindMenuClick(icon, _getLinkItems);
                  const _acH = _bindMenuHover(icon, _getLinkItems);
                  icon._menuAC = { abort() { _acC.abort(); _acH.abort(); } }; }
                icon.addEventListener('contextmenu', async (e) => { e.preventDefault(); e.stopPropagation(); await _doSaveLinkTextBookmark(); });
            } else {

                const _getCustomLinkActions = linkCtl.getCustomActions;

                icon.addEventListener('mouseenter', () => {
                    const map = _getCustomLinkActions();
                    const useCustom = _cachedClickModeCustom;
                    const domain = useCustom ? GM_getValue(KEY_LINK_DOMAIN_CLICK, 'x.com') : 'x.com';
                    const labelFor = (id) => {
                        const base = _LINK_ACTION_LABELS[id] || id;
                        return (id === 'copy_url' || id === 'copy_prefix_url') ? `${base} (${domain})` : base;
                    };
                    icon.title = [
                        `${_LINK_SLOT_LABELS.click}: ${labelFor(map.click)}`,
                        `${_LINK_SLOT_LABELS.longpress}: ${labelFor(map.longpress)}`,
                        `${_LINK_SLOT_LABELS.mclick}: ${labelFor(map.mclick)}`,
                        `${_LINK_SLOT_LABELS.rclick}: ${labelFor(map.rclick)}`,
                    ].join('\n');
                });

                let lTimer = null;

                icon.addEventListener('mousedown', e => {
                    if (e.button !== 0 && e.button !== 1) return;
                    e.preventDefault(); e.stopPropagation();
                    const map = _getCustomLinkActions();

                    if (e.button === 0) {
                        lTimer = setTimeout(async () => {
                            lTimer = null;
                            await (_LINK_ACTION_REGISTRY[map.longpress] || _linkActionNone)();
                        }, 500);

                    } else if (e.button === 1) {
                        (_LINK_ACTION_REGISTRY[map.mclick] || _linkActionNone)();
                    }
                });

                icon.addEventListener('mouseup', async (e) => {
                    if (e.button !== 0) return;
                    if (lTimer) {
                        clearTimeout(lTimer);
                        lTimer = null;
                        const map = _getCustomLinkActions();
                        await (_LINK_ACTION_REGISTRY[map.click] || _linkActionNone)();
                    }
                });

                icon.addEventListener('mouseleave', () => { if(lTimer) { clearTimeout(lTimer); lTimer = null; } });
                icon.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); });

                icon.addEventListener('contextmenu', async (e) => {
                    e.preventDefault(); e.stopPropagation();
                    const map = _getCustomLinkActions();
                    await (_LINK_ACTION_REGISTRY[map.rclick] || _linkActionSaveTextBookmark)();
                });

            }

            actions.style.setProperty('flex-wrap', 'nowrap', 'important');

            actions.appendChild(btn);
            actions.insertBefore(icon, btn);

            article.setAttribute('data-tm-v', '1');

            if (_downloadedIds.size > 0) {
                requestAnimationFrame(() => {
                    const tweetId = _getTweetIdFromArticle(article);
                    if (tweetId && _downloadedIds.has(tweetId)) _applyHistoryBadge(btn);
                });
            }
        }
    }

    if (_isTwitterDomain) {

    _syncCh = (typeof BroadcastChannel !== 'undefined')
        ? new BroadcastChannel('tm-download-sync') : null;
    if (_syncCh) {
        _syncCh.addEventListener('message', ({ data }) => {
            if (data?.type !== 'dl' || !data.tweetId) return;
            _downloadedIds.add(data.tweetId);
            document.querySelectorAll(`article a[href*="/status/${data.tweetId}"]`).forEach(a => {
                const art = a.closest('article');
                if (art) {
                    const targetBtn = art.querySelector('.force-media-copy-btn');
                    if (targetBtn) _applyHistoryBadge(targetBtn);
                }
            });
        });
    }
    let _tmdDebounceTimer = null;

    const _processedArticles = new WeakSet();

    const GRID_BTN_CLASS = 'tm-grid-media-btn';
    const _processedGridItems = new WeakSet();

    if (!document.getElementById('tm-grid-btn-style')) {
        const _gridStyle = document.createElement('style');
        _gridStyle.id = 'tm-grid-btn-style';
        _gridStyle.textContent = `
            .tm-grid-btn-layer {
                position: absolute; inset: 0; pointer-events: none;
                opacity: 0; transition: opacity 0.15s ease;
                z-index: 2;
            }
            li[id^="verticalGridItem-"]:hover .tm-grid-btn-layer { opacity: 1; }
            .${GRID_BTN_CLASS} {
                position: absolute; width: 26px; height: 26px;
                border-radius: 6px; border: none; pointer-events: auto;
                background: rgba(0,0,0,0.55); backdrop-filter: blur(2px);
                color: #fff; display: flex; align-items: center; justify-content: center;
                cursor: pointer; transition: background 0.15s ease, transform 0.1s ease;
            }
            .${GRID_BTN_CLASS}:hover { background: rgba(29,155,240,0.85); transform: scale(1.08); }
            .${GRID_BTN_CLASS}.tm-grid-btn-ok { background: rgba(0,186,124,0.9) !important; }

            .tm-grid-btn-copy-wrap { position: absolute; top: 6px; right: 6px; width: 26px; height: 26px; }
            .tm-grid-btn-copy-wrap .tm-grid-btn-copy { position: static; }
            .${GRID_BTN_CLASS}.tm-grid-btn-share {
                position: absolute; top: 0; right: 32px;
                opacity: 0; pointer-events: none;
                transform: translateX(6px);
                transition: opacity 0.15s ease, transform 0.15s ease, background 0.15s ease;
            }
            .tm-grid-btn-copy-wrap.tm-force-open .tm-grid-btn-share {
                opacity: 1; pointer-events: auto; transform: translateX(0);
            }
            .${GRID_BTN_CLASS}.tm-grid-btn-dl     { top: 36px; right: 6px; }
            .${GRID_BTN_CLASS}.tm-grid-btn-preview{ top: 66px; right: 6px; }
            .${GRID_BTN_CLASS} svg { width: 14px; height: 14px; pointer-events: none; }
        `;
        const _appendGridStyle = () => document.head.appendChild(_gridStyle);
        if (document.head) {
            _appendGridStyle();
        } else {
            document.addEventListener('DOMContentLoaded', _appendGridStyle, { once: true });
        }
    }

    const SVG_GRID_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" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
    const SVG_GRID_PREVIEW = `<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>`;
    const SVG_GRID_DL = `<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 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
    const SVG_GRID_SHARE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.6" y1="10.5" x2="15.4" y2="6.5"/><line x1="8.6" y1="13.5" x2="15.4" y2="17.5"/></svg>`;
    const SVG_GRID_CHECK = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="4,12 9,17 20,6"/></svg>`;

    _gridBtnFeedback = function (btn, originalSvg) {
        btn.innerHTML = SVG_GRID_CHECK;
        btn.classList.add('tm-grid-btn-ok');
        setTimeout(() => {
            btn.innerHTML = originalSvg;
            btn.classList.remove('tm-grid-btn-ok');
        }, 700);
    }

    insertGridMediaButtons = function () {
        if (!GM_getValue(KEY_GRID_MEDIA_BTN, true)) return;

        document.querySelectorAll('li[id^="verticalGridItem-"]').forEach(li => {
            const anchor = li.querySelector('a[href*="/status/"]');
            if (!anchor) return;
            const href = anchor.getAttribute('href') || '';
            const m = href.match(/^\/([^/]+)\/status\/(\d+)\/(photo|video)\/(\d+)/);
            if (!m) return;
            const [, screenName, tweetId, mediaType] = m;

            if (_processedGridItems.has(li) && li.dataset.tmGridHref === href) return;
            if (li.dataset.tmGridHref && li.dataset.tmGridHref !== href) {
                li.querySelector('.tm-grid-btn-layer')?.remove();
                _processedGridItems.delete(li);
            }

            const posHost = anchor.querySelector('div') || anchor;
            if (getComputedStyle(posHost).position === 'static') {
                posHost.style.position = 'relative';
            }

            const layer = document.createElement('div');
            layer.className = 'tm-grid-btn-layer';

            const _gridMediaUrls = (apiData) => apiData.videos.length ? apiData.videos : apiData.images;

            const _gridFilename = (apiData, url, idx) => {
                let ext = '.jpg';
                if (url.includes('.mp4')) ext = '.mp4';
                else if (url.includes('format=png')) ext = '.png';
                else { const parts = url.split('/').pop().split('?')[0].split('.'); if (parts.length > 1) ext = '.' + parts.pop(); }
                const safeScreen = sanitizeForFilename(screenName);
                const hasRichMeta = !!(apiData.date || apiData.displayName);
                if (!hasRichMeta) return `[twitter] ${safeScreen}_${tweetId}_${idx}${ext}`;
                const dateStr = apiData.date ? formatDate(apiData.date) : '';
                const safeDisplay = apiData.displayName ? sanitizeForFilename(apiData.displayName) : safeScreen;
                const textPart = apiData.text ? `_${sanitizeForFilename(apiData.text)}` : '';
                const safeTextForToken = sanitizeForFilename(apiData.text || '', CUSTOM_FN_TEXT_MAXLEN);
                return buildCustomFilename(
                    { screenName: safeScreen, displayName: safeDisplay, date: dateStr, id: tweetId, index: idx, ext: ext.replace(/^\./, ''), text: safeTextForToken, origName: _extractOrigMediaName(url) },
                    () => `[twitter] ${safeDisplay}(@${safeScreen})_${dateStr}${textPart}_${tweetId}_${idx}${ext}`
                );
            };

            const _gridDownloadOne = async (apiData, url, idx) => {
                try { await forceDownloadBlob(url, _gridFilename(apiData, url, idx)); } catch (_) {}
            };

            const _buildGridCopyMenuItems = (apiData, urls) => urls.map((url, i) => ({
                icon: SVG_GRID_COPY,
                label: (url.includes('.mp4') ? (T.grid_menu_item_video || 'Video {n}') : (T.grid_menu_item_image || 'Image {n}')).replace('{n}', i + 1),
                itemClass: 'tm-menu-item--copied',
                action: () => {
                    GM_setClipboard(url);
                    _gridBtnFeedback(copyBtn, SVG_GRID_COPY);
                    showToast(T.toast_grid_media_copied || 'Link copied!');
                },
            }));

            const copyBtn = document.createElement('button');
            copyBtn.type = 'button';
            copyBtn.className = `${GRID_BTN_CLASS} tm-grid-btn-copy`;
            copyBtn.title = T.grid_btn_copy_tip || 'Copy media link (long-press for menu)';
            copyBtn.innerHTML = SVG_GRID_COPY;
            let _copyLTimer = null;
            let _copyPressing = false;
            copyBtn.addEventListener('mousedown', (e) => {
                if (e.button !== 0) return;
                e.preventDefault(); e.stopPropagation();
                _copyPressing = true;
                _copyLTimer = setTimeout(async () => {
                    _copyLTimer = null;
                    _copyPressing = false;
                    const apiData = await fetchTweetMediaFromAPI(tweetId);
                    if (!apiData) { showToast(T.msg_no_media || 'No media found'); return; }
                    const urls = _gridMediaUrls(apiData);
                    if (!urls.length) { showToast(T.msg_no_media || 'No media found'); return; }
                    if (urls.length === 1) {
                        GM_setClipboard(urls[0]);
                        _gridBtnFeedback(copyBtn, SVG_GRID_COPY);
                        showToast(T.toast_grid_media_copied || 'Link copied!');
                    } else {
                        _showActionMenu(copyBtn, _buildGridCopyMenuItems(apiData, urls));
                    }
                }, 500);
            });
            copyBtn.addEventListener('mouseup', async (e) => {
                if (e.button !== 0) return;
                if (_copyLTimer) {
                    clearTimeout(_copyLTimer);
                    _copyLTimer = null;
                    _copyPressing = false;
                    const apiData = await fetchTweetMediaFromAPI(tweetId);
                    if (!apiData) { showToast(T.msg_no_media || 'No media found'); return; }
                    const urls = _gridMediaUrls(apiData);
                    if (!urls.length) { showToast(T.msg_no_media || 'No media found'); return; }
                    if (urls.length === 1) {
                        GM_setClipboard(urls[0]);
                        _gridBtnFeedback(copyBtn, SVG_GRID_COPY);
                        showToast(T.toast_grid_media_copied || 'Link copied!');
                    } else {
                        GM_setClipboard(urls[0]);
                        _gridBtnFeedback(copyBtn, SVG_GRID_COPY);
                        showToast(T.toast_grid_media_copied || 'Link copied!');
                    }
                }
            });
            copyBtn.addEventListener('mouseleave', () => { if (_copyLTimer && !_copyPressing) { clearTimeout(_copyLTimer); _copyLTimer = null; } });
            copyBtn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); });

            const copyWrap = document.createElement('div');
            copyWrap.className = 'tm-grid-btn-copy-wrap';

            const shareBtn = document.createElement('button');
            shareBtn.type = 'button';
            shareBtn.className = `${GRID_BTN_CLASS} tm-grid-btn-share`;
            shareBtn.title = T.grid_btn_share_tip || 'Copy tweet link (long-press to copy with prefix)';
            shareBtn.innerHTML = SVG_GRID_SHARE;
            let _shareLTimer = null;
            let _sharePressing = false;
            shareBtn.addEventListener('mousedown', (e) => {
                if (e.button !== 0) return;
                e.preventDefault(); e.stopPropagation();
                _sharePressing = true;
                _shareLTimer = setTimeout(() => {
                    _shareLTimer = null;
                    _sharePressing = false;
                    const tweetUrl = `https://x.com/${screenName}/status/${tweetId}`;
                    GM_setClipboard(_buildPrefixedTweetUrl(tweetUrl));
                    _gridBtnFeedback(shareBtn, SVG_GRID_SHARE);
                    showToast(T.msg_prefix_copied || 'Prefix Copied');
                }, 500);
            });
            shareBtn.addEventListener('mouseup', (e) => {
                if (e.button !== 0) return;
                if (_shareLTimer) {
                    clearTimeout(_shareLTimer);
                    _shareLTimer = null;
                    _sharePressing = false;
                    const tweetUrl = `https://x.com/${screenName}/status/${tweetId}`;
                    GM_setClipboard(_convertTweetUrlDomain(tweetUrl));
                    _gridBtnFeedback(shareBtn, SVG_GRID_SHARE);
                    showToast(T.toast_grid_media_copied || 'Link copied!');
                }
            });
            shareBtn.addEventListener('mouseleave', () => { if (_shareLTimer && !_sharePressing) { clearTimeout(_shareLTimer); _shareLTimer = null; } });
            shareBtn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); });

            const _shareOpen = () => {
                if (copyWrap._tmShareCloseTimer) { clearTimeout(copyWrap._tmShareCloseTimer); copyWrap._tmShareCloseTimer = null; }
                copyWrap.classList.add('tm-force-open');
            };
            const _shareClose = () => {
                copyWrap._tmShareCloseTimer = setTimeout(() => {
                    copyWrap.classList.remove('tm-force-open');
                    copyWrap._tmShareCloseTimer = null;
                }, 200);
            };
            copyWrap.addEventListener('mouseenter', _shareOpen);
            copyWrap.addEventListener('mouseleave', _shareClose);
            shareBtn.addEventListener('mouseenter', _shareOpen);
            shareBtn.addEventListener('mouseleave', _shareClose);

            copyWrap.append(shareBtn, copyBtn);

            const previewBtn = document.createElement('button');
            previewBtn.type = 'button';
            previewBtn.className = `${GRID_BTN_CLASS} tm-grid-btn-preview`;
            previewBtn.title = T.grid_btn_preview_tip || 'Preview in lightbox';
            previewBtn.innerHTML = SVG_GRID_PREVIEW;
            previewBtn.addEventListener('click', async (e) => {
                e.preventDefault(); e.stopPropagation();
                previewBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9" stroke-dasharray="42" stroke-dashoffset="14"/></svg>';
                const apiData = await fetchTweetMediaFromAPI(tweetId);
                previewBtn.innerHTML = SVG_GRID_PREVIEW;
                if (!apiData || (!apiData.videos.length && !apiData.images.length)) {
                    showToast(T.msg_no_media || 'No media found');
                    return;
                }
                const gridTweetInfo = {
                    screenName, displayName: apiData.displayName,
                    date: apiData.date ? formatDate(apiData.date) : '', id: tweetId, text: apiData.text,
                };
                if (apiData.videos.length) {
                    showFloatingVideoPlayer(apiData.videos, 0, apiData.images.length ? apiData.images : null, false, gridTweetInfo);
                } else {
                    showImageLightbox(apiData.images, null, true, gridTweetInfo);
                }
            });

            const dlBtn = document.createElement('button');
            dlBtn.type = 'button';
            dlBtn.className = `${GRID_BTN_CLASS} tm-grid-btn-dl`;
            dlBtn.title = T.grid_btn_download_tip || 'Download media (long-press for menu)';
            dlBtn.innerHTML = SVG_GRID_DL;
            let _dlLTimer = null;
            let _dlPressing = false;
            const _dlSpinnerSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9" stroke-dasharray="42" stroke-dashoffset="14"/></svg>';
            const _dlBuildMenuItems = (apiData, urls) => [
                {
                    icon: SVG_GRID_DL,
                    label: T.cma_action_download_all || 'Download All',
                    itemClass: 'tm-menu-item--downloaded',
                    action: async () => {
                        let idx = 1;
                        for (const url of urls) { await _gridDownloadOne(apiData, url, idx); idx++; }
                        _gridBtnFeedback(dlBtn, SVG_GRID_DL);
                        showToast(T.toast_grid_media_saved || 'Media saved!');
                    },
                },
                'divider',
                ...urls.map((url, i) => ({
                    icon: SVG_GRID_DL,
                    label: (url.includes('.mp4') ? (T.grid_menu_item_video || 'Video {n}') : (T.grid_menu_item_image || 'Image {n}')).replace('{n}', i + 1),
                    itemClass: 'tm-menu-item--downloaded',
                    action: async () => {
                        await _gridDownloadOne(apiData, url, i + 1);
                        _gridBtnFeedback(dlBtn, SVG_GRID_DL);
                        showToast(T.toast_grid_media_saved || 'Media saved!');
                    },
                })),
            ];
            dlBtn.addEventListener('mousedown', (e) => {
                if (e.button !== 0) return;
                e.preventDefault(); e.stopPropagation();
                _dlPressing = true;
                _dlLTimer = setTimeout(async () => {
                    _dlLTimer = null;
                    _dlPressing = false;
                    dlBtn.innerHTML = _dlSpinnerSvg;
                    const apiData = await fetchTweetMediaFromAPI(tweetId);
                    dlBtn.innerHTML = SVG_GRID_DL;
                    if (!apiData || (!apiData.videos.length && !apiData.images.length)) {
                        showToast(T.msg_no_media || 'No media found');
                        return;
                    }
                    const urls = _gridMediaUrls(apiData);
                    if (!urls.length) { showToast(T.msg_no_media || 'No media found'); return; }
                    if (urls.length === 1) {
                        await _gridDownloadOne(apiData, urls[0], 1);
                        _gridBtnFeedback(dlBtn, SVG_GRID_DL);
                        showToast(T.toast_grid_media_saved || 'Media saved!');
                    } else {
                        _showActionMenu(dlBtn, _dlBuildMenuItems(apiData, urls));
                    }
                }, 500);
            });
            dlBtn.addEventListener('mouseup', async (e) => {
                if (e.button !== 0) return;
                if (_dlLTimer) {
                    clearTimeout(_dlLTimer);
                    _dlLTimer = null;
                    _dlPressing = false;
                    dlBtn.innerHTML = _dlSpinnerSvg;
                    const apiData = await fetchTweetMediaFromAPI(tweetId);
                    dlBtn.innerHTML = SVG_GRID_DL;
                    if (!apiData || (!apiData.videos.length && !apiData.images.length)) {
                        showToast(T.msg_no_media || 'No media found');
                        return;
                    }
                    const urls = _gridMediaUrls(apiData);
                    if (!urls.length) { showToast(T.msg_no_media || 'No media found'); return; }
                    if (urls.length === 1) {
                        await _gridDownloadOne(apiData, urls[0], 1);
                        _gridBtnFeedback(dlBtn, SVG_GRID_DL);
                        showToast(T.toast_grid_media_saved || 'Media saved!');
                    } else {
                        let idx = 1;
                        for (const url of urls) { await _gridDownloadOne(apiData, url, idx); idx++; }
                        _gridBtnFeedback(dlBtn, SVG_GRID_DL);
                        showToast(T.toast_grid_media_saved || 'Media saved!');
                    }
                }
            });
            dlBtn.addEventListener('mouseleave', () => { if (_dlLTimer && !_dlPressing) { clearTimeout(_dlLTimer); _dlLTimer = null; } });
            dlBtn.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); });

            layer.append(copyWrap, previewBtn, dlBtn);
            posHost.appendChild(layer);

            li.dataset.tmGridHref = href;
            _processedGridItems.add(li);
        });
    }

    const AVATAR_BTN_CLASS = 'tm-avatar-media-btn';
    const _processedAvatars = new WeakSet();

    if (!document.getElementById('tm-avatar-btn-style')) {
        const _avatarStyle = document.createElement('style');
        _avatarStyle.id = 'tm-avatar-btn-style';
        _avatarStyle.textContent = `
            .${AVATAR_BTN_CLASS} {
                position: absolute; top: -3px; left: -3px; width: 16px; height: 16px;
                border-radius: 50%; background: rgba(0,0,0,0.55);
                display: flex; align-items: center; justify-content: center;
                color: #fff; text-decoration: none; pointer-events: none;
                opacity: 0; transition: opacity 0.15s ease, background 0.15s ease;
                z-index: 3;
            }
            .${AVATAR_BTN_CLASS}:hover { opacity: 1 !important; background: rgba(29,155,240,0.85); }
            [data-testid="Tweet-User-Avatar"]:hover .${AVATAR_BTN_CLASS} { opacity: 0.85; pointer-events: auto; }
            .${AVATAR_BTN_CLASS} svg { width: 11px; height: 11px; pointer-events: none; }
        `;
        const _appendAvatarStyle = () => document.head.appendChild(_avatarStyle);
        if (document.head) {
            _appendAvatarStyle();
        } else {
            document.addEventListener('DOMContentLoaded', _appendAvatarStyle, { once: true });
        }
    }

    const SVG_AVATAR_STACK = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12,2 2,7 12,12 22,7"/><polyline points="2,12 12,17 22,12"/><polyline points="2,17 12,22 22,17"/></svg>`;

    const AVATAR_BATCH_SIZE = 16;
    let _avatarBatchQueue = [];
    let _avatarBatchRunning = false;
    const _avatarBatchQueued = new WeakSet();
    _processAvatarBatch = function () {
        let n = 0;
        while (n < AVATAR_BATCH_SIZE && _avatarBatchQueue.length > 0) {
            const avatarWrap = _avatarBatchQueue.shift();
            _avatarBatchQueued.delete(avatarWrap);
            n++;
            if (_processedAvatars.has(avatarWrap) || !avatarWrap.isConnected) continue;

            const container = avatarWrap.querySelector('[data-testid^="UserAvatar-Container-"]');
            if (!container) continue;
            const testid = container.getAttribute('data-testid') || '';
            const screenName = testid.replace('UserAvatar-Container-', '');
            if (!screenName) continue;

            if (getComputedStyle(avatarWrap).position === 'static') {
                avatarWrap.style.position = 'relative';
            }

            const badge = document.createElement('a');
            badge.className = AVATAR_BTN_CLASS;
            const mediaHref = `https://${location.hostname}/${screenName}/media`;
            badge.href = mediaHref;
            badge.rel = 'noopener noreferrer';
            badge.title = T.avatar_media_btn_tip || 'Go to /media page';
            badge.innerHTML = SVG_AVATAR_STACK;
            badge.addEventListener('click', e => {
                e.stopPropagation();
                if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
                e.preventDefault();
                try {
                    history.pushState({ tmNav: true }, '', `/${screenName}/media`);
                    window.dispatchEvent(new Event('popstate'));
                } catch (_) { window.open(mediaHref, '_blank'); }
            });

            avatarWrap.appendChild(badge);
            _processedAvatars.add(avatarWrap);
        }

        if (_avatarBatchQueue.length > 0) {
            requestAnimationFrame(_processAvatarBatch);
        } else {
            _avatarBatchRunning = false;
        }
    }

    insertAvatarMediaButtons = function (enabled) {
        if (!enabled) return;

        document.querySelectorAll('article [data-testid="Tweet-User-Avatar"]').forEach(avatarWrap => {
            if (!_processedAvatars.has(avatarWrap) && !_avatarBatchQueued.has(avatarWrap)) {
                _avatarBatchQueue.push(avatarWrap);
                _avatarBatchQueued.add(avatarWrap);
            }
        });

        if (!_avatarBatchRunning && _avatarBatchQueue.length > 0) {
            _avatarBatchRunning = true;
            requestAnimationFrame(_processAvatarBatch);
        }
    }

    scanAndInsert = function () {
        _cachedClickMode       = GM_getValue(KEY_CLICK_MODE, 'classic');
        _cachedFeedbackStyle   = GM_getValue(KEY_FEEDBACK_STYLE, 'toast');
        _cachedClickModeCustom = GM_getValue(KEY_CLICK_MODE_CUSTOM, false);
        document.querySelectorAll('article').forEach(article => {
            const _fingerprint = _getTweetIdFromArticle(article)
                || article.querySelector('time')?.getAttribute('datetime')
                || null;
            const _sameContent = _fingerprint !== null && article.dataset.tmV === _fingerprint;

            const _existingBtnForModeCheck = article.querySelector(`.${BUTTON_CLASS}`);
            const _modeStale = _existingBtnForModeCheck && (
                (_cachedClickMode === 'menu'    && !_existingBtnForModeCheck._menuAC) ||
                (_cachedClickMode === 'classic' && !!_existingBtnForModeCheck._menuAC)
            );

            if (_processedArticles.has(article) && _sameContent && _existingBtnForModeCheck && !_modeStale) {
                return;
            }

            if (_processedArticles.has(article) && !_sameContent) {
                const _earlyBtn  = article.querySelector(`.${BUTTON_CLASS}`);
                const _earlyIcon = article.querySelector('.custom-copy-icon');
                if (_earlyBtn?._menuAC)  { _earlyBtn._menuAC.abort();  _earlyBtn._menuAC  = null; }
                if (_earlyIcon?._menuAC) { _earlyIcon._menuAC.abort(); _earlyIcon._menuAC = null; }
                _processedArticles.delete(article);
                delete article.dataset.tmV;
            }

            if (!_processedArticles.has(article)) {
                const staleBtn  = article.querySelector(`.${BUTTON_CLASS}`);
                const staleIcon = article.querySelector('.custom-copy-icon');
                if (staleBtn?._menuAC)  { staleBtn._menuAC.abort();  staleBtn._menuAC  = null; }
                if (staleIcon?._menuAC) { staleIcon._menuAC.abort(); staleIcon._menuAC = null; }
            }

            insertCopyButton(article);
            if (article.querySelector(`.${BUTTON_CLASS}`)) {
                _processedArticles.add(article);
                if (_fingerprint !== null) article.dataset.tmV = _fingerprint;
            }
        });

        insertGridMediaButtons();

        insertAvatarMediaButtons(GM_getValue(KEY_AVATAR_MEDIA_BTN, true));
    }

    let _isScrollingPage = false;
    let _scrollEndTimer  = null;
    window.addEventListener('scroll', () => {
        _isScrollingPage = true;
        clearTimeout(_scrollEndTimer);
        _scrollEndTimer = setTimeout(() => { _isScrollingPage = false; }, 300);
    }, { passive: true, capture: true });

    const observer = new MutationObserver(mutations => {
        let shouldCheck = false;
        for (let m of mutations) {
            if (m.addedNodes.length > 0 || m.removedNodes.length > 0) {
                shouldCheck = true;
                break;
            }
        }

        if (shouldCheck) {
            clearTimeout(_tmdDebounceTimer);
            _tmdDebounceTimer = setTimeout(scanAndInsert, _isScrollingPage ? 600 : 250);
        }
    });

    const _startObserving = () => {
        observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false });
    };
    if (document.body) {
        _startObserving();
    } else {
        document.addEventListener('DOMContentLoaded', _startObserving, { once: true });
    }

    let _scanIntervalId = null;
    _startScanInterval = function () {
        clearInterval(_scanIntervalId);
        const ms = Math.max(500, Math.min(5000,
            parseInt(GM_getValue(KEY_SCAN_INTERVAL, '1500'), 10) || 1500
        ));
        _scanIntervalId = setInterval(() => {
            if (_isScrollingPage) return;
            if (typeof requestIdleCallback !== 'undefined') {
                requestIdleCallback(scanAndInsert, { timeout: ms * 2 });
            } else {
                setTimeout(scanAndInsert, 0);
            }
        }, ms);
    };
    _startScanInterval();

    setTimeout(scanAndInsert, 1000);
    }
})();