☰

Advanced Search for X (Twitter) 🔍

Adds a floating modal for advanced search on X.com (Twitter). Syncs with search box and remembers position/display state. The top-right search icon is now draggable and its position persists.

As of 23.11.2025. See ბოლო ვერსია.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Advanced Search for X (Twitter) 🔍
// @name:ja      Advanced Search for XTwitter🔍
// @name:en      Advanced Search for X (Twitter) 🔍
// @name:zh-CN   Advanced Search for XTwitter🔍
// @name:zh-TW   Advanced Search for XTwitter🔍
// @name:ko      Advanced Search for X (Twitter) 🔍
// @name:fr      Advanced Search for X (Twitter) 🔍
// @name:es      Advanced Search for X (Twitter) 🔍
// @name:de      Advanced Search for X (Twitter) 🔍
// @name:pt-BR   Advanced Search for X (Twitter) 🔍
// @name:ru      Advanced Search for X (Twitter) 🔍
// @version      6.1.2
// @description      Adds a floating modal for advanced search on X.com (Twitter). Syncs with search box and remembers position/display state. The top-right search icon is now draggable and its position persists.
// @description:ja   X.comTwitterに高床な怜玢機胜を呌び出せるフロヌティング・モヌダルを远加したす。怜玢ボックスず双方向で同期し、䜍眮や衚瀺状態も蚘憶したす。右䞊の怜玢アむコンはドラッグで移動でき、䜍眮は保存されたす。
// @description:en   Adds a floating modal for advanced search on X.com (formerly Twitter). Syncs with search box and remembers position/display state. The top-right search icon is draggable with persistent position.
// @description:zh-CN 䞺X.comTwitter添加高级搜玢浮劚暡态框支持䞎搜玢框双向同步并记䜏䜍眮䞎星瀺状态。右䞊角的搜玢囟标可拖劚并䌚记䜏䜍眮。
// @description:zh-TW 為 X.comTwitter增加高玚搜尋暡態框支揎與搜尋框雙向同步䞊蚘䜏䜍眮與顯瀺狀態。右䞊角搜尋圖瀺可拖曳䜍眮會被保存。
// @description:ko   X.com(Twitter)에 고꞉ 검색 몚달을 추가합니닀. 검색찜곌 양방향 동Ʞ화하며 위치와 표시 상태륌 Ʞ억합니닀. 우상닚 검색 아읎윘은 드래귞 읎동 및 위치 저장읎 가능합니닀.
// @description:fr   Ajoute une fenêtre modale de recherche avancée à X.com (Twitter), synchronisée avec la barre de recherche et mémorise de l’état d’affichage. L’icÃŽne de recherche en haut à droite est déplaçable.
// @description:es   Agrega un modal flotante de búsqueda avanzada en X.com (Twitter), sincronizado con la caja de búsqueda y con estado persistente.
// @description:de   FÃŒgt X.com (Twitter) ein modales Fenster fÃŒr erweiterte Suche hinzu, synchronisiert mit der Suchleiste und speichert Position/Zustand. Das Suchsymbol oben rechts ist per Drag & Drop verschiebbar und bleibt gespeichert.
// @description:pt-BR Adiciona um modal de busca avançada flutuante no X.com (Twitter), sincronizado com a caixa de busca e com estado salvo. O ícone de busca no canto superior direito é arrastável com posição persistente.
// @description:ru   ДПбавляет ЌПЎальМПе ПкМП расшОреММПгП пПОска Ма X.com (Twitter). СОМхрПМОзОруется с пПОскПвПй стрПкПй О запПЌОМает сПстПяМОе. КМПпку пПОска в правПЌ верхМеЌ углу ЌПжМП перетаскОвать; её пПлПжеМОе сПхраМяется.
// @namespace    https://github.com/koyasi777/advanced-search-for-x-twitter
// @author       koyasi777
// @match        https://x.com/*
// @match        https://twitter.com/*
// @exclude      https://x.com/i/tweetdeck*
// @exclude      https://twitter.com/i/tweetdeck*
// @icon         https://raw.githubusercontent.com/koyasi777/advanced-search-for-x-twitter/refs/heads/main/extension/icons/icon-128.png
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @run-at       document-idle
// @license      MIT
// @homepageURL  https://github.com/koyasi777/advanced-search-for-x-twitter
// @supportURL   https://github.com/koyasi777/advanced-search-for-x-twitter/issues
// ==/UserScript==

const __X_ADV_SEARCH_MAIN_LOGIC__ = function() {
    'use strict';

    if (window.__X_ADV_SEARCH_INITED__) return;
    window.__X_ADV_SEARCH_INITED__ = true;

    const i18n = {
        translations: {
            'en': {
                modalTitle: "Advanced Search",
                tooltipClose: "Close",
                labelAllWords: "All of these words",
                placeholderAllWords: "e.g., AI news",
                labelExactPhrase: "This exact phrase",
                placeholderExactPhrase: 'e.g., "ChatGPT 4o"',
                labelAnyWords: "Any of these words (OR)",
                placeholderAnyWords: "e.g., iPhone Android",
                labelNotWords: "None of these words (-)",
                placeholderNotWords: "e.g., -sale -ads",
                labelHashtag: "Hashtags (#)",
                placeholderHashtag: "e.g., #TechEvent",
                labelLang: "Language (lang:)",
                optLangDefault: "Any language",
                optLangJa: "Japanese (ja)",
                optLangEn: "English (en)",
                optLangId: "Indonesian (id)",
                optLangHi: "Hindi (hi)",
                optLangDe: "German (de)",
                optLangTr: "Turkish (tr)",
                optLangEs: "Spanish (es)",
                optLangPt: "Portuguese (pt)",
                optLangAr: "Arabic (ar)",
                optLangFr: "French (fr)",
                optLangKo: "Korean (ko)",
                optLangRu: "Russian (ru)",
                optLangZhHans: "Chinese Simplified (zh-cn)",
                optLangZhHant: "Chinese Traditional (zh-tw)",
                hrSeparator: " ",
                labelFilters: "Filters",
                labelVerified: "Verified accounts",
                labelLinks: "Links",
                labelImages: "Images",
                labelVideos: "Videos",
                labelReposts: "Reposts",
                labelTimelineHashtags: "Hashtags (#)",
                checkInclude: "Include",
                checkExclude: "Exclude",
                labelReplies: "Replies",
                optRepliesDefault: "Default (Show all)",
                optRepliesInclude: "Include replies",
                optRepliesOnly: "Replies only",
                optRepliesExclude: "Exclude replies",
                labelEngagement: "Engagement",
                placeholderMinReplies: "Min replies",
                placeholderMinLikes: "Min likes",
                placeholderMinRetweets: "Min retweets",
                labelDateRange: "Date range",
                tooltipSince: "From this date",
                tooltipUntil: "Until this date",
                labelFromUser: "From these accounts (from:)",
                placeholderFromUser: "e.g., @X",
                labelToUser: "To these accounts (to:)",
                placeholderToUser: "e.g., @google",
                labelMentioning: "Mentioning these accounts (@)",
                placeholderMentioning: "e.g., @OpenAI",
                buttonClear: "Clear",
                buttonApply: "Search",
                tooltipTrigger: "Open Advanced Search",
                buttonOpen: "Open",

                tabSearch: "Search",
                tabHistory: "History",
                tabSaved: "Saved",
                buttonSave: "Save",
                buttonSaved: "Saved",
                secretMode: "Secret",
                secretOn: "Secret mode ON (No history)",
                secretOff: "Secret mode OFF",
                toastSaved: "Saved.",
                toastDeleted: "Deleted.",
                toastReordered: "Order updated.",
                emptyHistory: "No history yet.",
                emptySaved: "No saved searches. Add from the Save button at the bottom left of the Search tab.",
                run: "Run",
                delete: "Delete",
                updated: "Updated.",
                tooltipSecret: "Toggle Secret Mode (no history will be recorded)",
                historyClearAll: "Clear All",
                confirmClearHistory: "Clear all history?",

                labelAccountScope: "Accounts",
                optAccountAll: "All accounts",
                optAccountFollowing: "Accounts you follow",
                labelLocationScope: "Location",
                optLocationAll: "All locations",
                optLocationNearby: "Near you",
                chipFollowing: "Following",
                chipNearby: "Nearby",

                labelSearchTarget: "Search target",
                labelHitName: "Exclude hits only in display name",
                labelHitHandle: "Exclude hits only in username (@handle)",
                hintSearchTarget: "Hide posts that only match in name or handle (not in body).",
                hintName: "If a keyword appears only in the display name, hide it.",
                hintHandle: "If a keyword appears only in @username, hide it. Exception: when the query explicitly uses from:/to:/@ with the same word.",

                tabMute: "Mute",
                labelMuteWord: "Add mute word",
                placeholderMuteWord: "e.g., spoiler",
                labelCaseSensitive: "Case sensitive",
                labelEnabled: "Enabled",
                labelEnableAll: "Enable all",
                buttonAdd: "Add",
                emptyMuted: "No muted words.",
                mutedListTitle: "Muted words",
                mutedListHeading: "Muted items",
                muteHit: "Mute hits in body",
                buttonImport: "Import",
                buttonExport: "Export",

                /* Accounts tab */
                tabAccounts: "Accounts",
                emptyAccounts: "No accounts yet. Open a profile and click the Add button to save it.",
                buttonAddAccount: "Add account",
                toastAccountAdded: "Account added.",
                toastAccountExists: "Already added.",
                buttonConfirm: "Confirm",

                /* Lists tab */
                tabLists: "Lists",
                emptyLists: "No lists yet. Open a List and click the + button in the top-right to add it.",
                buttonAddList: "Add list",
                toastListAdded: "List added.",
                toastListExists: "Already added.",

                /* History tab */
                placeholderSearchHistory: "Search history (query)",
                labelSortBy: "Sort by:",
                placeholderSearchSaved: "Search saved (query)",
                sortNewest: "Newest first",
                sortOldest: "Oldest first",
                sortNameAsc: "Query (A-Z)",
                sortNameDesc: "Query (Z-A)",

                /* Folder/List/Account tabs */
                placeholderFilterAccounts: "Filter accounts (@, name)",
                placeholderFilterLists: "Filter lists (name, url)",
                buttonAddFolder: "+Folder",
                folderFilterAll: "ALL",
                folderFilterUnassigned: "Unassigned",
                folderRename: "Rename",
                folderRenameTitle: "Rename folder",
                folderDelete: "Delete",
                folderDeleteTitle: "Delete folder",
                promptNewFolder: "New folder name",
                confirmDeleteFolder: "Delete this folder and all items inside it? This cannot be undone.",
                optListsAll: "Lists",
                defaultSavedFolders: "Saved Searches",

                /* Favorites */
                tabFavorites: "Favorites",
                emptyFavorites: "No favorite tweets yet. Click the ★ icon on tweets to save them.",
                optFavoritesAll: "All Favorites",
                toastFavorited: "Added to favorites.",
                toastUnfavorited: "Removed from favorites.",

                /* Settings */
                settingsTitle: "Settings",
                settingsTitleGeneral: "General",
                settingsTitleFeatures: "Tab Visibility",
                settingsTitleData: "Data",
                buttonClose: "Close",
                labelUILang: "Interface language",
                optUILangAuto: "Auto",
                labelImportExport: "Import / Export",
                placeholderSettingsJSON: "Paste backup JSON here...",
                tooltipSettings: "Open settings",
                toastImported: "Imported.",
                alertInvalidJSON: "Invalid JSON file.",
                alertInvalidData: "Invalid data format.",
                alertInvalidApp: 'This file is not a valid backup for "Advanced Search for X".',
                toastExported: "Exported to file.",
                buttonReset: "Reset all data",
                confirmResetAll: "Reset all data? This cannot be undone.",
                toastReset: "All data has been reset.",
                buttonImportSuccess: "Imported successfully 👍",

                /* Favorites Sort */
                sortSavedNewest: "Saved date (Newest)",
                sortSavedOldest: "Saved date (Oldest)",
                sortPostedNewest: "Posted date (Newest)",
                sortPostedOldest: "Posted date (Oldest)",

                /* --- Favorite Tags --- */
                FT_UNCATEGORIZED: 'Uncategorized',
                FT_DROPDOWN_TITLE: 'Favorite Tags',
                FT_DROPDOWN_SETTINGS_TITLE: 'Favorite Tag Settings',
                FT_DROPDOWN_NEW_TAG: 'New tag',
                FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Tag name',
                FT_DROPDOWN_NEW_TAG_ADD: 'Add',
                FT_FILTER_ALL: 'All',
                FT_SETTINGS_TITLE: 'Favorite Tag Settings',
                FT_SETTINGS_EMPTY_TAG_LIST:
                  'No tags yet. You can add one from "New tag".',
                FT_SETTINGS_UNCATEGORIZED_NAME: 'Uncategorized',
                FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP:
                  'The name of "Uncategorized" cannot be changed.',
                FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP:
                  '"Uncategorized" cannot be deleted.',
                FT_SETTINGS_CLOSE: 'Close',
                FT_SETTINGS_DELETE_BUTTON: 'Delete',
                FT_SETTINGS_UP: '▲',
                FT_SETTINGS_DOWN: '▌',
                FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Display',
                FT_SETTINGS_DISPLAY_MODE_LABEL: 'Tag label format',
                FT_SETTINGS_DISPLAY_MODE_LEAF: 'Leaf only',
                FT_SETTINGS_DISPLAY_MODE_FULL: 'Full path',
                FT_CONFIRM_DELETE_TAG_MSG: 'Delete tag "{tagName}"?\nFavorites with this tag will become "Uncategorized".',
                FT_SETTINGS_BUTTON_TITLE: 'Favorite Tag Settings',
            },
            'ja': {
                modalTitle: "高床な怜玢",
                tooltipClose: "閉じる",
                labelAllWords: "すべおの語句を含む",
                placeholderAllWords: "䟋: AI ニュヌス",
                labelExactPhrase: "この語句を完党に含む",
                placeholderExactPhrase: '䟋: "ChatGPT 4o"',
                labelAnyWords: "いずれかの語句を含む (OR)",
                placeholderAnyWords: "䟋: iPhone Android",
                labelNotWords: "含たない語句 (-)",
                placeholderNotWords: "䟋: -セヌル -広告",
                labelHashtag: "ハッシュタグ (#)",
                placeholderHashtag: "䟋: #技術曞兞",
                labelLang: "蚀語 (lang:)",
                optLangDefault: "指定しない",
                optLangJa: "日本語 (ja)",
                optLangEn: "英語 (en)",
                optLangId: "むンドネシア語 (id)",
                optLangHi: "ヒンディヌ語 (hi)",
                optLangDe: "ドむツ語 (de)",
                optLangTr: "トルコ語 (tr)",
                optLangEs: "スペむン語 (es)",
                optLangPt: "ポルトガル語 (pt)",
                optLangAr: "アラビア語 (ar)",
                optLangFr: "フランス語 (fr)",
                optLangKo: "韓囜語 (ko)",
                optLangRu: "ロシア語 (ru)",
                optLangZhHans: "䞭囜語簡䜓字(zh-cn)",
                optLangZhHant: "䞭囜語繁䜓字(zh-tw)",
                hrSeparator: " ",
                labelFilters: "フィルタヌ",
                labelVerified: "認蚌枈みアカりント",
                labelLinks: "リンク",
                labelImages: "画像",
                labelVideos: "動画",
                labelReposts: "リポスト",
                labelTimelineHashtags: "ハッシュタグ (#)",
                checkInclude: "含む",
                checkExclude: "含たない",
                labelReplies: "返信",
                optRepliesDefault: "指定しない",
                optRepliesInclude: "返信を含める",
                optRepliesOnly: "返信のみ",
                optRepliesExclude: "返信を陀倖",
                labelEngagement: "゚ンゲヌゞメント",
                placeholderMinReplies: "最小返信数",
                placeholderMinLikes: "最小いいね数",
                placeholderMinRetweets: "最小リポスト数",
                labelDateRange: "期間指定",
                tooltipSince: "この日以降",
                tooltipUntil: "この日以前",
                labelFromUser: "このアカりントから (from:)",
                placeholderFromUser: "䟋: @X",
                labelToUser: "このアカりントぞ (to:)",
                placeholderToUser: "䟋: @google",
                labelMentioning: "このアカりントぞのメンション (@)",
                placeholderMentioning: "䟋: @OpenAI",
                buttonClear: "クリア",
                buttonApply: "怜玢実行",
                tooltipTrigger: "高床な怜玢を開く",
                buttonOpen: "開く",

                tabSearch: "怜玢",
                tabHistory: "履歎",
                tabSaved: "保存",
                buttonSave: "保存",
                buttonSaved: "保存枈み",
                secretMode: "シヌクレット",
                secretOn: "シヌクレットモヌド ON履歎は蚘録したせん",
                secretOff: "シヌクレットモヌド OFF",
                toastSaved: "保存したした。",
                toastDeleted: "削陀したした。",
                toastReordered: "䞊び順を曎新したした。",
                emptyHistory: "履歎はただありたせん。",
                emptySaved: "保存枈みの怜玢はありたせん。怜玢タブの巊䞋の保存から远加しおください。",
                run: "実行",
                delete: "削陀",
                updated: "曎新したした。",
                tooltipSecret: "シヌクレットモヌドを切り替え履歎を蚘録したせん",
                historyClearAll: "すべお削陀",
                confirmClearHistory: "履歎をすべお削陀したすか",

                labelAccountScope: "アカりント",
                optAccountAll: "すべおのアカりント",
                optAccountFollowing: "フォロヌしおいるアカりント",
                labelLocationScope: "堎所",
                optLocationAll: "すべおの堎所",
                optLocationNearby: "近くの堎所",
                chipFollowing: "フォロヌ䞭",
                chipNearby: "近く",

                labelSearchTarget: "怜玢察象",
                labelHitName: "衚瀺名名前のみのヒットは陀倖",
                labelHitHandle: "ナヌザヌ名@のみのヒットは陀倖",
                hintSearchTarget: "本文ではなく、名前/ナヌザヌ名のみに䞀臎した投皿を非衚瀺にしたす。",
                hintName: "キヌワヌドが衚瀺名のみに含たれる堎合は非衚瀺にしたす。",
                hintHandle: "キヌワヌドが @ナヌザヌ名のみに含たれる堎合は非衚瀺にしたす。䟋倖: 同じ語を from:/to:/@ で明瀺しおいるずきは衚瀺したす。",

                tabMute: "ミュヌト",
                labelMuteWord: "ミュヌト語句の远加",
                placeholderMuteWord: "䟋: ネタバレ",
                labelCaseSensitive: "倧文字小文字を区別",
                labelEnabled: "有効",
                labelEnableAll: "すべお有効",
                buttonAdd: "远加",
                emptyMuted: "ミュヌト語句はただありたせん。",
                mutedListTitle: "ミュヌト語句",
                mutedListHeading: "ミュヌト䞀芧",
                muteHit: "本文でのヒットをミュヌト",
                buttonImport: "むンポヌト",
                buttonExport: "゚クスポヌト",

                /* Accounts tab */
                tabAccounts: "アカりント",
                emptyAccounts: "アカりントはただありたせん。アカりントペヌゞの远加ボタンから远加しおください。",
                buttonAddAccount: "アカりントを远加",
                toastAccountAdded: "アカりントを远加したした。",
                toastAccountExists: "すでに远加枈みです。",
                buttonConfirm: "確認",

                /* Lists tab */
                tabLists: "リスト",
                emptyLists: "リストはただありたせん。リストを開き右䞊の+ボタンから远加しおください。",
                buttonAddList: "リストを远加",
                toastListAdded: "リストを远加したした。",
                toastListExists: "すでに远加枈みです。",

                /* History tab */
                placeholderSearchHistory: "履歎を怜玢ク゚リ",
                labelSortBy: "䞊び順:",
                placeholderSearchSaved: "保存枈みを怜玢ク゚リ",
                sortNewest: "新しい順",
                sortOldest: "叀い順",
                sortNameAsc: "ク゚リ (昇順)",
                sortNameDesc: "ク゚リ (降順)",

                /* Folder/List/Account tabs */
                placeholderFilterAccounts: "アカりントを怜玢 (@, 名前)",
                placeholderFilterLists: "リストを怜玢 (名前, URL)",
                buttonAddFolder: "+フォルダヌ",
                folderFilterAll: "すべお",
                folderFilterUnassigned: "未分類",
                folderRename: "名前倉曎",
                folderRenameTitle: "フォルダヌ名を倉曎",
                folderDelete: "削陀",
                folderDeleteTitle: "フォルダヌを削陀",
                promptNewFolder: "新しいフォルダヌ名",
                confirmDeleteFolder: "このフォルダヌず䞭のすべおのアむテムを完党に削陀したすかこの操䜜は元に戻せたせん。",
                optListsAll: "リスト",
                defaultSavedFolders: "保存枈み怜玢",

                /* Favorites */
                tabFavorites: "お気に入り",
                emptyFavorites: "お気に入りはただありたせん。ツむヌトの★ボタンをクリックしお保存できたす。",
                optFavoritesAll: "すべおのお気に入り",
                toastFavorited: "お気に入りに远加したした。",
                toastUnfavorited: "お気に入りから削陀したした。",

                /* Settings */
                settingsTitle: "蚭定",
                settingsTitleGeneral: "䞀般蚭定",
                settingsTitleFeatures: "タブ衚瀺蚭定",
                settingsTitleData: "デヌタ管理",
                buttonClose: "閉じる",
                labelUILang: "UI 蚀語",
                optUILangAuto: "自動刀定",
                labelImportExport: "むンポヌト / ゚クスポヌト",
                placeholderSettingsJSON: "ここにバックアップ JSON を貌り付けおください...",
                tooltipSettings: "蚭定を開く",
                toastImported: "むンポヌトしたした。",
                toastExported: "ファむルに゚クスポヌトしたした。",
                alertInvalidJSON: "無効なJSONファむルです。",
                alertInvalidData: "無効なデヌタ圢匏です。",
                alertInvalidApp: "このファむルは「Advanced Search for X」のバックアップデヌタではありたせん。",
                buttonReset: "すべお初期化",
                confirmResetAll: "すべおのデヌタを初期化したすかこの操䜜は元に戻せたせん。",
                toastReset: "すべおのデヌタを初期化したした。",
                buttonImportSuccess: "むンポヌトに成功したした👍",

                /* Favorites Sort */
                sortSavedNewest: "远加日 (新しい順)",
                sortSavedOldest: "远加日 (叀い順)",
                sortPostedNewest: "投皿日 (新しい順)",
                sortPostedOldest: "投皿日 (叀い順)",

                /* --- Favorite Tags --- */
                FT_UNCATEGORIZED: '未分類',
                FT_DROPDOWN_TITLE: 'お気に入りタグ',
                FT_DROPDOWN_SETTINGS_TITLE: 'お気に入りタグ蚭定',
                FT_DROPDOWN_NEW_TAG: '新しいタグ',
                FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'タグ名',
                FT_DROPDOWN_NEW_TAG_ADD: '远加',
                FT_FILTER_ALL: 'すべお',
                FT_SETTINGS_TITLE: 'お気に入りタグ蚭定',
                FT_SETTINGS_EMPTY_TAG_LIST:
                  'タグはただありたせん。「新しいタグ」から远加できたす。',
                FT_SETTINGS_UNCATEGORIZED_NAME: '未分類',
                FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: '未分類の名前は倉曎できたせん',
                FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '未分類は削陀できたせん',
                FT_SETTINGS_CLOSE: '閉じる',
                FT_SETTINGS_DELETE_BUTTON: '削陀',
                FT_SETTINGS_UP: '▲',
                FT_SETTINGS_DOWN: '▌',
                FT_SETTINGS_DISPLAY_SECTION_TITLE: '衚瀺蚭定',
                FT_SETTINGS_DISPLAY_MODE_LABEL: 'タグの衚瀺圢匏',
                FT_SETTINGS_DISPLAY_MODE_LEAF: '末尟のみ (leaf)',
                FT_SETTINGS_DISPLAY_MODE_FULL: 'フルパス (full)',
                FT_CONFIRM_DELETE_TAG_MSG: 'タグ「{tagName}」を削陀したすか\nこのタグが付いおいたお気に入りは未分類になりたす。',
                FT_SETTINGS_BUTTON_TITLE: 'お気に入りタグ蚭定',
            },
            'zh-CN': {},
            'ko': {},
            'fr': {},
            'es': {},
            'de': {},
            'pt-BR': {},
            'ru': {}
        },
        lang: 'en',
        init: function() {
            const supportedLangs = Object.keys(this.translations);
            let detectedLang = document.documentElement.lang || navigator.language || 'en';
            if (supportedLangs.includes(detectedLang)) { this.lang = detectedLang; return; }
            const baseLang = detectedLang.split('-')[0];
            if (supportedLangs.includes(baseLang)) { this.lang = baseLang; return; }
            this.lang = 'en';
        },
        t: function(key) { return this.translations[this.lang]?.[key] || this.translations['en'][key] || `[${key}]`; },
        apply: function(container) {
            container.querySelectorAll('[data-i18n]').forEach(el => { el.textContent = this.t(el.dataset.i18n); });
            container.querySelectorAll('[data-i18n-placeholder]').forEach(el => { el.placeholder = this.t(el.dataset.i18nPlaceholder); });
            container.querySelectorAll('[data-i18n-title]').forEach(el => { el.title = this.t(el.dataset.i18nTitle); });
        }
    };

    const SEARCH_SVG = `
    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
      <circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2" fill="none"></circle>
      <line x1="16.65" y1="16.65" x2="22" y2="22"
            stroke="currentColor" stroke-width="2" stroke-linecap="round"></line>
    </svg>`;

    const SETTINGS_SVG = `
    <svg
      viewBox="0 0 24 24"
      aria-hidden="true"
      focusable="false"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 00-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 00-2.282.819l-.922 1.597a1.875 1.875 0 00.432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 000 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 00-.432 2.385l.922 1.597a1.875 1.875 0 002.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 002.28-.819l.923-1.597a1.875 1.875 0 00-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 000-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 00-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 00-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 00-1.85-1.567h-1.843zM12 15.75a3.75 3.75 0 100-7.5 3.75 3.75 0 000 7.5z"
      />
    </svg>
    `;

    const FOLDER_TOGGLE_OPEN_SVG = `
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
        <path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
      </svg>
    `;
    const FOLDER_TOGGLE_CLOSED_SVG = `
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
        <path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
      </svg>
    `;

    // トグルボタンの小ナヌティリティ
    function renderFolderToggleButton(collapsed) {
      const btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'adv-folder-toggle-btn';
      btn.setAttribute('aria-label', collapsed ? 'Expand' : 'Collapse');
      btn.setAttribute('title', collapsed ? 'Expand' : 'Collapse');
      btn.setAttribute('aria-expanded', (!collapsed).toString());
      btn.style.cssText = `
        appearance:none;border:none;background:transparent;cursor:pointer;
        width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;
        margin-right:8px;color:inherit;flex:0 0 auto;
      `;
      btn.innerHTML = collapsed ? FOLDER_TOGGLE_CLOSED_SVG : FOLDER_TOGGLE_OPEN_SVG;
      return btn;
    }
    function updateFolderToggleButton(btn, collapsed) {
      if (!btn) return;
      btn.innerHTML = collapsed ? FOLDER_TOGGLE_CLOSED_SVG : FOLDER_TOGGLE_OPEN_SVG;
      btn.setAttribute('aria-label', collapsed ? 'Expand' : 'Collapse');
      btn.setAttribute('title', collapsed ? 'Expand' : 'Collapse');
      btn.setAttribute('aria-expanded', (!collapsed).toString());
    }

    const themeManager = {
        colors: {
            light: {
                '--modal-bg': '#ffffff', '--modal-text-primary': '#0f1419', '--modal-text-secondary': '#536471', '--modal-border': '#d9e1e8',
                '--modal-input-bg': '#eff3f4', '--modal-input-border': '#cfd9de', '--modal-button-hover-bg': 'rgba(15, 20, 25, 0.1)',
                '--modal-scrollbar-thumb': '#aab8c2', '--modal-scrollbar-track': '#eff3f4', '--modal-close-color': '#0f1419',
                '--modal-close-hover-bg': 'rgba(15, 20, 25, 0.1)', '--hr-color': '#eff3f4',
            },
            dim: {
                '--modal-bg': '#15202b', '--modal-text-primary': '#f7f9f9', '--modal-text-secondary': '#8899a6', '--modal-border': '#38444d',
                '--modal-input-bg': '#192734', '--modal-input-border': '#38444d', '--modal-button-hover-bg': 'rgba(247, 249, 249, 0.1)',
                '--modal-scrollbar-thumb': '#536471', '--modal-scrollbar-track': '#192734', '--modal-close-color': '#f7f9f9',
                '--modal-close-hover-bg': 'rgba(247, 249, 249, 0.1)', '--hr-color': '#38444d',
            },
            dark: {
                '--modal-bg': '#000000', '--modal-text-primary': '#e7e9ea', '--modal-text-secondary': '#71767b', '--modal-border': '#2f3336',
                '--modal-input-bg': '#16181c', '--modal-input-border': '#54595d', '--modal-button-hover-bg': 'rgba(231, 233, 234, 0.1)',
                '--modal-scrollbar-thumb': '#536471', '--modal-scrollbar-track': '#16181c', '--modal-close-color': '#e7e9ea',
                '--modal-close-hover-bg': 'rgba(231, 233, 234, 0.1)', '--hr-color': '#2f3336',
            }
        },
        applyTheme: function(modalElement, triggerEl) {
            if (!modalElement) return;
            const bodyBg = getComputedStyle(document.body).backgroundColor;
            let theme = 'dark';
            if (bodyBg === 'rgb(21, 32, 43)') theme = 'dim';
            else if (bodyBg === 'rgb(255, 255, 255)') theme = 'light';

            // â–Œ ブックマヌクUIのテヌマ切替甚にクラスを付䞎
            try {
                document.documentElement.classList.remove('x-theme-light', 'x-theme-dim', 'x-theme-dark');
                if (theme === 'light') {
                    document.documentElement.classList.add('x-theme-light');
                } else if (theme === 'dim') {
                    document.documentElement.classList.add('x-theme-dim');
                } else {
                    document.documentElement.classList.add('x-theme-dark');
                }
            } catch (e) {}

            const themeColors = this.colors[theme] || this.colors.dark;
            const targets = [modalElement, document.documentElement];
            if (triggerEl) targets.push(triggerEl);
            for (const t of targets) {
             for (const [key, value] of Object.entries(themeColors)) {
               t.style.setProperty(key, value);
             }
            }
        },
        observeChanges: function(modalElement, triggerEl) {
            const observer = new MutationObserver(() => this.applyTheme(modalElement, triggerEl));
            observer.observe(document.body, { attributes: true, attributeFilter: ['style'] });
            this.applyTheme(modalElement, triggerEl);
        }
    };

    function decodeURIComponentSafe(s) {
      try { return decodeURIComponent(s); } catch { return s; }
    }

    // “ ” 『』などのスマヌト匕甚を ASCII の " に寄せる
    function normalizeQuotes(s) {
      return String(s).replace(/[\u201C\u201D\u300C\u300D\uFF02]/g, '"');
    }

    // 解析前に軜く正芏化URL から来る %22..., 連続空癜など
    function normalizeForParse(s) {
      if (!s) return '';
      let out = String(s);
      // URL っぜい゚ンコヌドだけ軜く剥がす%22 等
      if (/%[0-9A-Fa-f]{2}/.test(out)) out = decodeURIComponentSafe(out);
      out = normalizeQuotes(out);
      // 制埡文字を朰し、空癜を敎圢
      out = out.replace(/\s+/g, ' ').trim();
      return out;
    }

    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // ── OR/匕甚のための簡易トヌクナむザ
    function tokenizeQuotedWords(s) {
      const out = [];
      let cur = '';
      let inQ = false;
      for (let i = 0; i < s.length; i++) {
        const c = s[i];
        if (c === '"') { inQ = !inQ; cur += c; continue; }
        if (!inQ && /\s/.test(c)) { if (cur) { out.push(cur); cur=''; } }
        else { cur += c; }
      }
      if (cur) out.push(cur);
      return out.filter(Boolean);
    }

    // トップレベルの OR で文字列を分割匕甚/括匧を考慮
    function splitTopLevelOR(str) {
      const parts = [];
      let cur = '';
      let inQ = false, depth = 0;
      for (let i = 0; i < str.length; ) {
        const c = str[i];
        if (c === '"') { inQ = !inQ; cur += c; i++; continue; }
        if (!inQ && (c === '(' || c === ')')) { depth += (c === '(' ? 1 : -1); cur += c; i++; continue; }
        if (!inQ && depth === 0) {
          // 単語境界の "or" / "OR"
          if ((str.slice(i, i+2).toLowerCase() === 'or') &&
              (i === 0 || /\s|\(/.test(str[i-1] || '')) &&
              (i+2 >= str.length || /\s|\)/.test(str[i+2] || ''))) {
            parts.push(cur.trim());
            cur = '';
            i += 2;
            continue;
          }
        }
        cur += c; i++;
      }
      if (cur.trim()) parts.push(cur.trim());
      return parts.length > 1 ? parts : null;
    }

    // OR 専甚刀定挔算子/吊定/括匧が無い玠の OR 矀なら true
    function isPureORQuery(q) {
      const hasOps = /(?:^|\s)(?:from:|to:|lang:|filter:|is:|min_replies:|min_faves:|min_retweets:|since:|until:)\b/i.test(q);
      const hasNeg = /(^|\s)-\S/.test(q);
      const hasPar = /[()]/.test(q);
      return !hasOps && !hasNeg && !hasPar;
    }

    function waitForElement(selector, timeout = 10000, checkProperty = null) {
        return new Promise((resolve) => {
            const checkInterval = 100;
            let elapsedTime = 0;
            const intervalId = setInterval(() => {
                const element = document.querySelector(selector);
                if (element) {
                    if (checkProperty) {
                        if (element[checkProperty]) {
                            clearInterval(intervalId);
                            resolve(element);
                            return;
                        }
                    } else {
                        clearInterval(intervalId);
                        resolve(element);
                        return;
                    }
                }
                elapsedTime += checkInterval;
                if (elapsedTime >= timeout) {
                    clearInterval(intervalId);
                    resolve(null);
                }
            }, checkInterval);
        });
    }

    function hideUIImmediately(modal, trigger) {
        if (modal)  modal.style.display = 'none';
        if (trigger) trigger.style.display = 'none';
    }

    // â–Œ ルヌト適甚を軜く怜蚌URL䞀臎 + プロフィヌル系DOMが珟れたか
    function waitForRouteApply(path, timeoutMs = 2000) {
      const goal = new URL(path, location.origin).pathname;
      // ルヌト毎の刀定を甚意必芁に応じお拡匵
      const perRouteProbes = [
       // 怜玢ペヌゞ怜玢結果タむムラむン or 怜玢ボックス or 䜕かしらのツむヌト
       { test: p => p.startsWith('/search'),
         sels: [
           '[aria-label*="Search results"], [aria-label*="怜玢結果"]',
           'div[data-testid="primaryColumn"] input[data-testid="SearchBox_Search_Input"]',
           'div[data-testid="primaryColumn"] article[data-testid="tweet"]'
         ] },
       // プロフィヌル
       { test: p => /^\/[A-Za-z0-9_]{1,50}\/?$/.test(p),
         sels: [
           '[data-testid="UserName"]',
           'div[data-testid="UserProfileHeader_Items"]',
           'div[data-testid="UserDescription"]'
         ] },
       // デフォルト保険䞻芁カラムに䜕かレンダされたらOK
       { test: _ => true,
         sels: [
           'div[data-testid="primaryColumn"]',
           'main[role="main"]'
         ] }
      ];
      const probes = (perRouteProbes.find(x => x.test(goal)) || perRouteProbes.at(-1)).sels;
      return new Promise(resolve => {
        const t0 = performance.now();
        (function tick() {
          const elapsed = performance.now() - t0;
          const urlOk = location.pathname === goal;
          const domOk = probes.some(sel => document.querySelector(sel));
          if (urlOk && domOk) return resolve(true);
          if (elapsed >= timeoutMs) return resolve(false);
          // 立ち䞊がりは速く、以埌はやや疎にポヌリング
          setTimeout(tick, elapsed < 300 ? 60 : elapsed < 700 ? 120 : 180);
        })();
      });
    }

    // â–Œ SPA 遷移の栞。pushState → 合成 popstate → DOM適甚埅ち → 倱敗ならフォヌルバック
    async function spaNavigate(path, { ctrlMeta = false, timeoutMs = 1200 } = {}) {
      try {
        const to = new URL(path, location.origin);
        if (to.origin !== location.origin) throw new Error('cross-origin');

        history.pushState(history.state, '', to.pathname + to.search + to.hash);
        // X のルヌタヌは popstate を賌読しおいる想定
        window.dispatchEvent(new PopStateEvent('popstate', { state: history.state }));

        const ok = await waitForRouteApply(to.pathname, timeoutMs);
        if (ok) return; // 成功
      } catch (e) {
        // fall through to fallback
      }
      // フォヌルバック修食キヌありなら新芏タブ、なければ通垞遷移
      if (ctrlMeta) window.open(path, '_blank', 'noopener');
      else location.assign(path);
    }

    const uid = () => Math.random().toString(36).slice(2) + Date.now().toString(36);

    let isUpdating = false;
    let manualOverrideOpen = false;
    const lastHistory = { q: null, pf: null, lf: null, ts: 0 };

    // â–Œ パヌス結果をキャッシュスクロヌル時の再パヌス防止
    let __cachedSearchTokens = null;
    let __cachedSearchQuery = null; // このク゚リ文字列で __cachedSearchTokens が生成された

    // â–Œ 入力䞭ガヌドIME合成を含めおカバヌ
    let __typingGuardUntil = 0;
    const TYPING_GRACE_MS = 600; // 入力終了からこのmsはスキャン停止
    const markTyping = () => { __typingGuardUntil = Date.now() + TYPING_GRACE_MS; };
    const isTyping = () => Date.now() < __typingGuardUntil;

    const isMediaViewPath = (pathname) => /\/status\/\d+\/(?:photo|video|media|analytics)(?:\/\d+)?\/?$/.test(pathname);
    const isComposePath = (pathname) => /^\/compose\/post(?:\/|$)/.test(pathname);
    const isProfileMediaPath = (pathname) => /^\/[A-Za-z0-9_]{1,50}\/(?:photo|header_photo)\/?$/.test(pathname);
    const isBroadcastPath = (pathname) => /^\/i\/broadcasts\//.test(pathname);
    const isBlockedPath = (pathname) => isMediaViewPath(pathname) || isComposePath(pathname) || isProfileMediaPath(pathname) || isBroadcastPath(pathname);

    GM_addStyle(`
        :root { --modal-primary-color:#1d9bf0; --modal-primary-color-hover:#1a8cd8; --modal-primary-text-color:#fff; }
        #advanced-search-trigger { position:fixed; top:18px; right:20px; z-index:9999; background-color:var(--modal-primary-color); color:var(--modal-primary-text-color); border:none; border-radius:50%; width:50px; height:50px; font-size:24px; cursor:pointer; box-shadow:0 4px 12px rgba(0,0,0,0.15); display:flex; align-items:center; justify-content:center; transition:transform .2s, background-color .2s; }
        #advanced-search-trigger:hover { transform:scale(1.1); background-color:var(--modal-primary-color-hover); }
        #advanced-search-modal { position:fixed; z-index:10000; width:450px; display:none; flex-direction:column; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; background-color:var(--modal-bg, #000); color:var(--modal-text-primary, #e7e9ea); border:1px solid var(--modal-border, #333); border-radius:16px; box-shadow:0 8px 24px rgba(29,155,240,.2); transition:background-color .2s,color .2s,border-color .2s; }
        .adv-modal-header{padding:12px 16px;border-bottom:1px solid var(--modal-border,#333);cursor:move;display:flex;justify-content:space-between;align-items:center}
        .adv-modal-title-left{display:flex;align-items:center;gap:8px;}
        .adv-modal-header h2{margin:0;font-size:18px;font-weight:700}
        .adv-settings-btn{
          margin-left:6px;
          width:26px;height:26px;
          border-radius:9999px;
          border:1px solid var(--modal-input-border,#38444d);
          background:var(--modal-input-bg,#202327);
          display:inline-flex;
          align-items:center;
          justify-content:center;
          cursor:pointer;
          padding:0;
        }
        .adv-settings-btn:hover{
          background-color:var(--modal-button-hover-bg,rgba(231,233,234,.1));
        }
        .adv-settings-btn svg{
          width:14px;
          height:14px;
        }
        .adv-modal-close{background:0 0;border:none;color:var(--modal-close-color,#e7e9ea);font-size:24px;cursor:pointer;width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:background-color .2s}
        .adv-modal-close:hover{background-color:var(--modal-close-hover-bg,rgba(231,233,234,.1))}
        .adv-modal-body{flex:1;overflow-y:auto;padding:0}
        .adv-form-group{margin-bottom:16px}
        .adv-form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:700;color:var(--modal-text-secondary,#8b98a5)}
        .adv-form-group input[type=text],.adv-form-group input[type=number],.adv-form-group input[type=date],.adv-form-group select{width:100%;background-color:var(--modal-input-bg,#202327);border:1px solid var(--modal-input-border,#38444d);border-radius:4px;padding:8px 12px;color:var(--modal-text-primary,#e7e9ea);font-size:15px;box-sizing:border-box}
        .adv-form-group input:focus,.adv-form-group select:focus{outline:0;border-color:var(--modal-primary-color)}
        .adv-form-group input::placeholder{color:var(--modal-text-secondary,#536471)}
        .adv-form-group-date-container{display:flex;gap:10px}
        .adv-filter-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
        .adv-checkbox-group{background-color:var(--modal-input-bg,#202327);border:1px solid var(--modal-input-border,#38444d);border-radius:8px;padding:10px;display:flex;flex-direction:column;gap:8px}
        .adv-checkbox-group span{font-weight:700;font-size:14px;color:var(--modal-text-primary,#e7e9ea)}
        .adv-checkbox-item{display:flex;align-items:center}
        .adv-checkbox-item input{margin-right:8px; accent-color:var(--modal-primary-color);}
        .adv-checkbox-item label{color:var(--modal-text-secondary,#8b98a5);margin-bottom:0}
        .adv-checkbox-item input[type="checkbox"]:disabled {
          opacity: 0.5;
          cursor: not-allowed;
        }
        .adv-checkbox-item input[type="checkbox"]:disabled + label {
          opacity: 0.5;
          cursor: not-allowed;
          text-decoration: line-through;
        }
        .adv-modal-footer{padding:12px 16px;border-top:1px solid var(--modal-border,#333);display:flex;justify-content:flex-end;gap:12px}
        .adv-modal-button{padding:5px 16px;border-radius:9999px;border:1px solid var(--modal-text-secondary,#536471);background-color:transparent;color:var(--modal-text-primary,#e7e9ea);font-weight:700;cursor:pointer;transition:background-color .2s}
        .adv-modal-button:hover{background-color:var(--modal-button-hover-bg,rgba(231,233,234,.1))}
        .adv-modal-button.primary,
        .adv-chip.primary {
          background-color:var(--modal-primary-color);
          border-color:var(--modal-primary-color);
          color:var(--modal-primary-text-color);
        }
        .adv-modal-button.primary:hover{background-color:var(--modal-primary-color-hover)}
        .adv-modal-button[disabled]{opacity:.5; cursor:not-allowed;}
        #adv-settings-import.adv-modal-button[disabled]{opacity:1;}
        .adv-modal-body::-webkit-scrollbar{width:8px}
        .adv-modal-body::-webkit-scrollbar-track{background:var(--modal-scrollbar-track,#202327)}
        .adv-modal-body::-webkit-scrollbar-thumb{background:var(--modal-scrollbar-thumb,#536471);border-radius:4px}
        body.adv-dragging{user-select:none}
        .adv-account-label-group{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
        .adv-exclude-toggle{display:flex;align-items:center}
        .adv-exclude-toggle input{margin-right:4px}
        .adv-exclude-toggle label{font-size:13px;font-weight:normal;color:var(--modal-text-secondary,#8b98a5);cursor:pointer}
        hr.adv-separator{border:none;height:1px;background-color:var(--hr-color,#333);margin:20px 0;transition:background-color .2s}
        /* ★党タブ共通のズヌム察象に拡匵怜玢タブの既存idにも適甚維持 */
        .adv-zoom-root, #adv-zoom-root{ transform-origin: top left; will-change: transform; padding:12px 11.6px 10px 11px; }
        #adv-zoom-root {
          padding-top: 16px; /* 怜玢タブの䞊䜙癜だけを 16px に䞊曞き */
          padding-left:16px; padding-right:20px;
        }
        .adv-modal-body{ overflow:auto; }

        .adv-form-row.two-cols { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
        @media (max-width: 480px) { .adv-form-row.two-cols { grid-template-columns:1fr; } }

        .adv-tabs {
            display: flex;
            border-bottom: 1px solid var(--modal-border, #333);
            padding: 0 8px 0 6px;
            gap: 4px;
            align-items: stretch;

            /* 幅䞍足の時は隠さず、2行にする */
            flex-wrap: wrap;

            /* 幅怜知の基準にする */
            container-type: inline-size;
        }

        .adv-tab-btn {
            appearance: none;
            border: none;
            background: transparent;
            color: var(--modal-text-secondary, #8b98a5);
            padding: 10px 8px;
            cursor: pointer;
            font-weight: 700;
            border-radius: 8px 8px 0 0;
            font-size: 0.78rem;

            /* ボタン内のテキストは折り返さない */
            white-space: nowrap;

            /* 䜙ったスペヌスを党員で分け合う均等配眮・最倧化 */
            flex: 1 1 auto;
            text-align: center;

            /* なめらかな倉化 */
            transition: font-size 0.1s, padding 0.1s, background-color 0.2s;
        }

        .adv-tab-btn.active {
            color: var(--modal-text-primary, #e7e9ea);
            background-color: var(--modal-input-bg, #202327);
            border: 1px solid var(--modal-input-border, #38444d);
            border-bottom: none;
            /* アクティブタブは少し匷調 */
            z-index: 1;
        }

        /* ▌▌▌ コンテナク゚リ: 幅に応じお最適化 ▌▌▌ */

        /* 幅 480px 以䞋: フォントを少し小さくし、1行収たりを狙う */
        @container (max-width: 480px) {
            .adv-tab-btn {
                font-size: 12px;
                padding: 8px 4px;
            }
        }

        /* 幅 380px 以䞋: さらにフォントを詰め、もし2行になっおも違和感ないサむズに */
        @container (max-width: 380px) {
            .adv-tab-btn {
                font-size: 11px;
                padding: 6px 2px;
                border-radius: 6px; /* 角䞞も少し小さく */
            }
            /* 2行になった際に䞊䞋の列がくっ぀きすぎないようにする */
            .adv-tabs {
                row-gap: 2px;
            }
            /* 2行目のボヌダヌ凊理芋た目を敎える */
            .adv-tab-btn.active {
                border-bottom: 1px solid var(--modal-input-bg, #202327);
                margin-bottom: -1px;
            }
        }

        .adv-tab-content { display:none; }
        .adv-tab-content.active { display:block; }

        .adv-secret-wrap { display:flex; align-items:center; gap:8px; }
        .adv-secret-btn { cursor:pointer; border:1px solid var(--modal-input-border,#38444d); background:var(--modal-input-bg,#202327); color:var(--modal-text-primary,#e7e9ea); padding:4px 8px; border-radius:9999px; font-weight:700; user-select:none; display:flex; align-items:center; gap:6px; font-size:12px; }
        .adv-secret-btn .dot { width:7px; height:7px; border-radius:50%; background:#777; box-shadow:0 0 0px #0000; transition:all .2s; }
        .adv-secret-btn.off { opacity:0.9; }
        .adv-secret-btn.on { background-color:var(--modal-primary-color); border-color:var(--modal-primary-color); color:var(--modal-primary-text-color); }
        .adv-secret-btn.on .dot { background:#fff; box-shadow:0 0 8px rgba(255,255,255,.9); }

        .adv-list { display:flex; flex-direction:column; gap:8px; }
        .adv-item { position: relative; border:1px solid var(--modal-input-border,#38444d); background:var(--modal-input-bg,#202327); border-radius:8px; padding:8px; display:flex; gap:8px; align-items:flex-start; }
        .adv-item.dragging { opacity:.6; }
        .adv-item-handle { cursor:grab; user-select:none; padding:4px 6px; border-radius:6px; border:1px dashed var(--modal-border,#333); }
        .adv-item-avatar { width:36px; height:36px; border-radius:9999px; object-fit:cover; flex:0 0 auto; background:var(--modal-border,#333); }
        a.adv-link { color: inherit; text-decoration: none; }
        a.adv-link:hover { text-decoration: underline; cursor: pointer; }
        .adv-item-avatar-link { display:inline-block; border-radius:9999px; }
        .adv-item-main { flex:1; min-width:0; }

        .adv-item-title { font-size:14px; font-weight:700; color:var(--modal-text-primary,#e7e9ea); word-break:break-word; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
        .adv-item-sub { font-size:12px; color:var(--modal-text-secondary,#8b98a5); margin-top:2px; display:flex; gap:6px; flex-wrap:wrap; align-items:center; }
        .adv-item-actions { display:flex; gap:6px; align-items:center; align-self:center; }
        .adv-chip { border:1px solid var(--modal-input-border,#38444d); background:transparent; color:var(--modal-text-primary,#e7e9ea); padding:4px 8px; border-radius:9999px; font-size:12px; cursor:pointer; }

        .adv-fav-btn-pos { position: absolute; right: 8px; }
        .adv-fav-btn-top { top: 8px; }
        .adv-fav-btn-bottom { bottom: 8px; }
        .adv-chip.danger { border-color:#8b0000; color:#ffb3b3; }
        .adv-modal-button.danger {
          border-color:#8b0000;
          color:#ffb3b3;
        }
        .adv-modal-button.danger:hover{
          background-color:rgba(139,0,0,0.2);
        }
        .adv-chip.scope { padding:2px 6px; font-size:11px; line-height:1.2; opacity:0.95; }

        .adv-toast { position:fixed; z-index:10001; left:50%; transform:translateX(-50%); bottom:24px; background:#111a; color:#fff; backdrop-filter: blur(6px); border:1px solid #fff3; padding:8px 12px; border-radius:8px; font-weight:700; opacity:0; pointer-events:none; transition:opacity .2s, transform .2s; }
        .adv-toast.show { opacity:1; transform:translateX(-50%) translateY(-6px); }

        .adv-modal-footer { justify-content:flex-end; }
        .adv-modal-footer .adv-modal-button#adv-save-button { margin-right:auto; }

        .adv-tab-toolbar {
          display:flex;
          justify-content: space-between;
          align-items: center;
          gap: 8px;
          flex-wrap: wrap;
          margin-bottom:12px;
          padding: 0 2px;
        }
        /* ツヌルバヌの巊偎怜玢・゜ヌト */
        .adv-tab-toolbar-left {
          display: flex;
          align-items: center;
          gap: 8px;
          flex: 1 1 auto;
          min-width: 150px;
        }
        /* ツヌルバヌの右偎すべお削陀ボタン */
        .adv-tab-toolbar-right {
          display: flex;
          flex: 0 0 auto;
        }
        /* ツヌルバヌ入力欄の共通スタむル */
        .adv-select, .adv-input {
          background-color:var(--modal-input-bg,#202327);
          border:1px solid var(--modal-input-border,#38444d);
          border-radius:8px;
          padding:6px 10px;
          color:var(--modal-text-primary,#e7e9ea);
        }
        /* 怜玢ボックスずセレクトボックスのスタむル.adv-folder-toolbar内ず共通化 */
        /* 共通スタむルは .adv-input, .adv-select が担圓 */
        .adv-tab-toolbar .adv-input {
          flex: 1;
          min-width: 80px;
        }
        .adv-tab-toolbar .adv-select {
          flex: 0 1 auto;
        }

        [data-testid="cellInnerDiv"][data-adv-hidden],
        article[data-adv-hidden] {
          display:none !important;
          content-visibility: hidden;
          contain: strict;
        }

        #advanced-search-modal { max-height:none; }
        .adv-resizer { position:absolute; z-index:10002; background:transparent; }
        .adv-resizer.e, .adv-resizer.w { top:-3px; bottom:-3px; width:8px; }
        .adv-resizer.e { right:-3px; cursor: ew-resize; }
        .adv-resizer.w { left:-3px;  cursor: ew-resize; }
        .adv-resizer.n, .adv-resizer.s { left:-3px; right:-3px; height:8px; }
        .adv-resizer.n { top:-3px;    cursor: ns-resize; }
        .adv-resizer.s { bottom:-3px; cursor: ns-resize; }
        .adv-resizer.se, .adv-resizer.ne, .adv-resizer.sw, .adv-resizer.nw { width:12px; height:12px; }
        .adv-resizer.se { right:-4px;  bottom:-4px; cursor:nwse-resize; }
        .adv-resizer.ne { right:-4px;  top:-4px;    cursor:nesw-resize; }
        .adv-resizer.sw { left:-4px;   bottom:-4px; cursor:nesw-resize; }
        .adv-resizer.nw { left:-4px;   top:-4px;    cursor:nwse-resize; }

        /* ▶ Mute タブ */
        .adv-mute-add { display:flex; gap:8px; align-items:center; margin-bottom:10px; }
        .adv-mute-add input[type=text]{ flex:1; border-radius:8px; padding: 6px 10px; font-size: 14px; }
        .adv-mute-list { display:flex; flex-direction:column; gap:8px; }

        /* â–Œ グロヌバル無効マスタヌOFFのずきリスト党䜓を淡く */
        .adv-mute-list.disabled {
          opacity: .6;
          filter: grayscale(35%);
        }

        /* â–Œ 個別無効enabled=falseの行だけ淡く打ち消し等の芖芚 */
        .adv-mute-item {
          border:1px solid var(--modal-input-border,#38444d);
          background:var(--modal-input-bg,#202327);
          border-radius:8px;
          padding:8px;
          display:flex;
          flex-wrap: wrap;
          gap:8px;
          align-items:flex-start;
          transition: opacity .15s ease, filter .15s ease, border-color .15s ease;
        }
        .adv-mute-item.disabled {
          opacity: .55;
          filter: grayscale(25%);
          border-color: color-mix(in oklab, var(--modal-input-border,#38444d), transparent 20%);
        }
        .adv-mute-item.disabled .adv-mute-word {
          color: var(--modal-text-secondary,#8b98a5);
          text-decoration: line-through;
        }

        .adv-mute-word {
          font-weight:700;
          color:var(--modal-text-primary,#e7e9ea);
          word-break:break-word;
        }

        .adv-mute-actions {
          display:flex;
          gap:6px;
          align-items:center;
          flex: 0 0 auto;
          white-space: nowrap;
          margin-left: auto;
        }
        @media (max-width: 480px) {
          .adv-mute-actions { margin-top: 4px; }
        }
        .adv-toggle {
          display: inline-flex;
          gap: 6px;
          align-items: center;
          color: var(--modal-text-secondary,#8b98a5);
          line-height: 1;
          margin-bottom:0!important;
        }
        .adv-toggle input[type="checkbox"] {
          width: 14px;
          height: 14px;
          margin: 0;
          flex: 0 0 auto;
          vertical-align: middle;
        }
        .adv-toggle span {
          font-size: 11px;
          line-height: 1;
        }
        .adv-mute-header { display:flex; justify-content:space-between; align-items:center; margin:12px 0 6px; }
        .adv-mute-title  { font-weight:700; color: var(--modal-text-primary,#e7e9ea); }

        /* マスタヌ切替の䞀瞬だけ付けるガヌドクラス */
        .adv-no-anim, .adv-no-anim * {
          transition: none !important;
        }
        #adv-history-empty:not(:empty),
        #adv-saved-empty:not(:empty),
        #adv-favorites-empty:not(:empty),
        #adv-accounts-empty:not(:empty),
        #adv-lists-empty:not(:empty) {
          padding-inline: 7px;
        }
        #adv-mute-empty:not(:empty) {
          padding-top: 6px;
        }

        /* â–Œ マスタヌOFF䞭は、個別無効の“さらに薄く”を抑制芪の薄さのみ適甚 */
        .adv-mute-list.disabled .adv-mute-item.disabled {
          opacity: 1;    /* 子の远加の薄さを無効化芪のopacityのみが効く */
          filter: none;  /* 子の远加グレヌスケヌルも無効化芪偎のfilterのみ適甚 */
          /* ボヌダヌだけ通垞色に戻す */
          /* border-color: var(--modal-input-border,#38444d); */
        }

        /* === Trigger: モヌダルず同質の芋た目に合わせる === */
        #advanced-search-trigger.adv-trigger-search {
          width: 49px; height: 49px;
          border-radius: 9999px;
          background-color: var(--modal-bg, #000);
          color: var(--modal-text-primary, #e7e9ea);
          border: 2px solid var(--modal-border, #2f3336);          /* ← モヌダルず同じ枠色 */
          box-shadow: 0 8px 24px rgba(29,155,240,.2);              /* ← モヌダルず同じshadow */
          display:flex; align-items:center; justify-content:center;
          transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
        }

        #advanced-search-trigger.adv-trigger-search:hover {
          /* 背景は倉えず、浮かせる衚珟だけ匷化 */
          transform: translateZ(0) scale(1.04);
          box-shadow: 0 12px 36px rgba(29,155,240,.28);
          border-color: var(--modal-border, #2f3336);
        }

        #advanced-search-trigger.adv-trigger-search:active {
          transform: translateZ(0) scale(0.98);
          box-shadow: 0 6px 18px rgba(29,155,240,.22);
        }

        #advanced-search-trigger.adv-trigger-search:focus-visible {
          outline: none;
          box-shadow:
            0 8px 24px rgba(29,155,240,.2),
            0 0 0 3px color-mix(in oklab, var(--modal-primary-color, #1d9bf0) 45%, transparent);
        }

        #advanced-search-trigger.adv-trigger-search svg {
          width: 22px; height: 22px;
          display:block;
          /* 怜玢アむコンは stroke="currentColor" を䜿っおいるので配色は自動远埓 */
        }

        /* === Folders === */
        .adv-folder { border:1px solid var(--modal-input-border,#38444d); border-radius:10px; margin-bottom:10px; }
        .adv-folder-header {
          display:flex; justify-content:space-between; align-items:center;
          padding:8px 10px; background:var(--modal-input-bg,#202327); border-bottom:1px solid var(--modal-input-border,#38444d);
        }
        .adv-folder[data-drop="1"] { outline:2px dashed var(--modal-primary-color); outline-offset:-2px; }
        .adv-folder-title { display:flex; gap:8px; align-items:baseline; }
        .adv-folder-actions { display:flex; gap:6px; }
        .adv-folder-toolbar { display:flex; gap:8px; align-items:center; margin:0 0 12px; padding:0 2px; }
        .adv-folder-toolbar input[type="text"] { flex:1; min-width:80px; }
        .adv-folder-collapsed .adv-list { display:none; }

        /* ▶ Folder headers: show grab cursor except on action buttons */
        .adv-folder-header { cursor: grab; }
        .adv-folder-header:active { cursor: grabbing; }

        /* ボタン䞊では通垞のポむンタ=ドラッグ開始させない芋た目 */
        .adv-folder-header .adv-folder-actions,
        .adv-folder-header .adv-folder-actions * {
          cursor: pointer;
        }

        /* â–Œ トグルボタン巊端 */
        .adv-folder-toggle {
          appearance: none;
          border: none;
          background: transparent;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          width: 28px;
          height: 28px;
          border-radius: 6px;
          cursor: pointer;
          margin-right: 6px;
        }

        .adv-folder-toggle:focus-visible {
          outline: none;
          box-shadow: 0 0 0 2px color-mix(in oklab, var(--modal-primary-color, #1d9bf0) 60%, transparent);
        }

        /* â–Œ アむコンchevron */
        .adv-folder-toggle svg {
          width: 16px; height: 16px;
          transition: transform .15s ease;
        }

        /* â–Œ 開閉で向きを倉える右▶ → 䞋▌ */
        .adv-folder:not(.adv-folder-collapsed) .adv-folder-toggle svg {
          transform: rotate(90deg);
        }

        /* â–Œ 開いおいるヘッダヌはわずかに背景匷調 */
        .adv-folder:not(.adv-folder-collapsed) .adv-folder-header {
          background: color-mix(in oklab, var(--modal-input-bg,#202327) 92%, var(--modal-primary-color,#1d9bf0));
        }

        /* â–Œ ドラッグハンドルは“掎める”芋た目を匷調 */
        .adv-folder-drag-handle {
          cursor: grab;
          user-select: none;
          padding: 4px 6px;
          border-radius: 6px;
          border: 1px dashed var(--modal-border,#38444d);
        }
        .adv-folder-drag-handle:active { cursor: grabbing; }

        /* â–Œ Unassigned セクション芋出しなし・枠なし */
        .adv-unassigned {
          margin-bottom: 10px;
          min-height: 30px; /* ★ 空の時でもドロップできるように最小高さを確保 */
        }
        .adv-unassigned .adv-list {
          display: flex;
          flex-direction: column;
          gap: 8px;
        }
        /* フォルダヌ䞊び替え甚のドラッグ時の芖芚Unassigned も察象 */
        .adv-unassigned.dragging-folder {
          opacity: .6;
        }

        /* タブ背景およびリストコンテナ背景ぞのドロップハむラむト */
        #adv-tab-accounts.adv-bg-drop-active,
        #adv-tab-lists.adv-bg-drop-active,
        #adv-tab-saved.adv-bg-drop-active,
        #adv-accounts-list.adv-bg-drop-active,
        #adv-lists-list.adv-bg-drop-active,
        #adv-saved-list.adv-bg-drop-active {
          outline: 2px dashed var(--modal-primary-color, #1d9bf0);
          /* リストコンテナ偎はパディングが無いためオフセットを小さく */
          outline-offset: -4px;
        }
        /* タブパネル䞊郚䜙癜偎は既存のオフセットを維持 */
        #adv-tab-accounts.adv-bg-drop-active,
        #adv-tab-lists.adv-bg-drop-active,
        #adv-tab-saved.adv-bg-drop-active {
          outline-offset: -8px;
        }

        /* 背景Unassigned 宛おをドロップ䞭は、フォルダヌ内の“薄い残像”を消す */
        #adv-tab-accounts.adv-bg-drop-active .adv-list .adv-item.dragging,
        #adv-accounts-list.adv-bg-drop-active .adv-list .adv-item.dragging,
        #adv-tab-lists.adv-bg-drop-active .adv-list .adv-item.dragging,
        #adv-lists-list.adv-bg-drop-active .adv-list .adv-item.dragging,
        #adv-tab-saved.adv-bg-drop-active .adv-list .adv-item.dragging,
        #adv-saved-list.adv-bg-drop-active .adv-list .adv-item.dragging {
          display: none !important;
        }

        /* === Settings modal === */
        #adv-settings-modal.adv-settings-modal{
          position:fixed;
          inset:0;
          z-index:10001;
          display:none;
          align-items:center;
          justify-content:center;
          background:rgba(0,0,0,.5);
        }
        .adv-settings-dialog{
          width:420px;
          max-width:90vw;
          max-height:80vh;
          background-color:var(--modal-bg,#000);
          color:var(--modal-text-primary,#e7e9ea);
          border-radius:16px;
          border:1px solid var(--modal-border,#333);
          box-shadow:0 8px 24px rgba(0,0,0,.3);
          display:flex;
          flex-direction:column;
          overflow:hidden;
          font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
        }
        .adv-settings-header{
          padding:12px 16px;
          border-bottom:1px solid var(--modal-border,#333);
          display:flex;
          align-items:center;
          justify-content:space-between;
        }
        .adv-settings-title{
          margin:0;
          font-size:16px;
          font-weight:700;
        }
        .adv-settings-close{
          border:none;
          background:transparent;
          color:var(--modal-close-color,#e7e9ea);
          font-size:20px;
          width:32px;
          height:32px;
          border-radius:50%;
          display:flex;
          align-items:center;
          justify-content:center;
          cursor:pointer;
        }
        .adv-settings-close:hover{
          background-color:var(--modal-close-hover-bg,rgba(231,233,234,.1));
        }
        .adv-settings-body{
          padding:12px 16px 23px 16px;
          overflow-y:auto;
          display:flex;
          flex-direction:column;
          gap:16px;
        }
        .adv-settings-group label{
          display:block;
          margin-bottom:4px;
          font-size:14px;
          font-weight:700;
          color:var(--modal-text-secondary,#8b98a5);
        }
        .adv-settings-group select,
        .adv-settings-group textarea{
          width:100%;
          background-color:var(--modal-input-bg,#202327);
          border:1px solid var(--modal-input-border,#38444d);
          border-radius:8px;
          padding:8px 10px;
          color:var(--modal-text-primary,#e7e9ea);
          font-size:14px;
          box-sizing:border-box;
        }
        .adv-settings-group textarea{
          resize:vertical;
          min-height:80px;
        }
        .adv-settings-section-header {
          margin: 12px 0 2px 0;
          padding-bottom: 4px;
          border-bottom: 1px solid var(--modal-border,#333);
          font-size: 13px;
          font-weight: 700;
          color: var(--modal-text-primary,#e7e9ea);
        }
        .adv-settings-toggle-row {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding: 6px 0;
        }
        .adv-settings-toggle-row .adv-toggle {
          font-size: 14px;
          color: var(--modal-text-primary,#e7e9ea);
          user-select: none;
          cursor: pointer;
        }
        .adv-settings-toggle-row .adv-toggle span {
          font-size: 14px;
        }
        /* Simple toggle switch CSS */
        .adv-switch {
          position: relative;
          display: inline-block;
          width: 40px;
          height: 22px;
        }
        .adv-switch input {
          opacity: 0;
          width: 0;
          height: 0;
        }
        .adv-slider {
          position: absolute;
          cursor: pointer;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          background-color: var(--modal-input-border,#38444d);
          transition: .2s;
          border-radius: 22px;
        }
        .adv-slider:before {
          position: absolute;
          content: "";
          height: 16px;
          width: 16px;
          left: 3px;
          bottom: 3px;
          background-color: var(--modal-bg, #000);
          transition: .2s;
          border-radius: 50%;
        }
        .adv-switch input:checked + .adv-slider {
          background-color: var(--modal-primary-color);
        }
        .adv-switch input:checked + .adv-slider:before {
          transform: translateX(18px);
        }
        .adv-settings-actions-inline{
          display:flex;
          gap:8px;
          margin-top:6px;
          flex-wrap:wrap;
        }
        .adv-settings-footer{
          padding:10px 16px;
          border-top:1px solid var(--modal-border,#333);
          display:flex;
          justify-content:flex-end;
          gap:8px;
        }

        /* === Tab Drag & Drop === */
        .adv-tab-btn {
          user-select: none;
        }
        .adv-tab-btn:active {
          cursor: grabbing;
        }
        .adv-tab-btn.dragging {
          opacity: .5;
        }

        /* --- Favorite Tags CSS --- */

        /* â–Œ ブックマヌクUI専甚の配色倉数を定矩 */
        :root {
          /* デフォルト (Dim / Dark) は静的なダヌクテヌマ */
          --ft-bg: rgb(21, 24, 28);
          --ft-border-light: rgba(239, 243, 244, 0.24);
          --ft-border-dim: rgba(239, 243, 244, 0.15);
          --ft-border-strong: rgba(239, 243, 244, 0.3);
          --ft-border-accent: rgba(239, 243, 244, 0.8);
          --ft-text-primary: rgb(239, 243, 244);
          --ft-text-secondary: rgba(239, 243, 244, 0.7);
          --ft-input-bg: rgba(0,0,0,0.2);
          --ft-input-border: rgba(239,243,244,0.2);
          --ft-hover-bg: rgba(255, 255, 255, 0.06);
          --ft-hover-bg-strong: rgba(255, 255, 255, 0.08);
          --ft-accent-color: #1d9bf0;
        }
        :root.x-theme-light {
          /* Lightテヌマの時だけ、X本䜓の動的倉数を参照する */
          --ft-bg: var(--modal-bg);
          --ft-border-light: var(--modal-border);
          --ft-border-dim: var(--modal-border);
          --ft-border-strong: var(--modal-text-secondary);
          --ft-border-accent: var(--modal-text-primary);
          --ft-text-primary: var(--modal-text-primary);
          --ft-text-secondary: var(--modal-text-secondary);
          --ft-input-bg: var(--modal-input-bg);
          --ft-input-border: var(--modal-input-border);
          --ft-hover-bg: var(--modal-button-hover-bg);
          --ft-hover-bg-strong: var(--modal-button-hover-bg);
          --ft-accent-color: var(--modal-primary-color);
        }

        /* Tag chip on tweet header */
        .ft-tag-chip {
          display: inline-flex;
          align-items: center;
          margin-left: 4px; /* JS (ft_attachTagChipToArticle) 偎の gap: 4px ず連動 */
          padding: 1px 8px;
          border-radius: 9999px;
          border: 1px solid currentColor;
          font-size: 11px;
          line-height: 1.4;
          cursor: pointer;
          user-select: none;
          white-space: nowrap;
          background: rgba(255, 255, 255, 0.03); /* これは静的なたた (ほが透明なので) */
          flex: 0 0 auto;
        }
        .ft-tag-chip-label {
          max-width: 150px;
          overflow: hidden;
          text-overflow: ellipsis;
        }
        .ft-tag-chip-uncategorized {
          opacity: 0.7;
        }

        /* Dropdown for selecting tag / filter */
        .ft-tag-dropdown {
          position: fixed;
          z-index: 2147482000;
          min-width: 220px;
          max-width: 260px;
          max-height: 60vh;
          overflow-y: auto;
          padding: 8px;
          border-radius: 12px;
          border: 1px solid var(--ft-border-light);
          background: var(--ft-bg);
          box-shadow: 0 12px 30px rgba(0, 0, 0, 0.7);
          font-size: 13px;
          color: var(--ft-text-primary);
        }
        .ft-tag-dropdown-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 6px;
          font-weight: 600;
        }
        .ft-tag-dropdown-close {
          border: none;
          background: transparent;
          color: inherit;
          cursor: pointer;
          padding: 2px 4px;
        }
        .ft-tag-dropdown-tags {
          display: flex;
          flex-direction: column;
          gap: 4px;
          margin-bottom: 8px;
        }
        .ft-tag-dropdown-tag-item {
          display: flex;
          align-items: center;
          padding: 4px 6px;
          border-radius: 6px;
          cursor: pointer;
        }
        .ft-tag-dropdown-tag-item:hover {
          background: var(--ft-hover-bg);
        }
        .ft-tag-dropdown-tag-color {
          width: 10px;
          height: 10px;
          border-radius: 9999px;
          margin-right: 6px;
        }
        .ft-tag-dropdown-tag-label {
          flex: 1;
        }
        .ft-tag-dropdown-tag-selected::after {
          content: '✓';
          margin-left: 6px;
          font-size: 11px;
        }

        /* New tag row in dropdown */
        .ft-tag-dropdown-new {
          border-top: 1px solid var(--ft-border-dim);
          padding-top: 6px;
          display: flex;
          flex-direction: column;
          gap: 4px;
        }
        .ft-tag-dropdown-new-row {
          display: flex;
          gap: 4px;
        }
        .ft-tag-dropdown-new-input {
          flex: 1;
          background: var(--ft-input-bg);
          border: 1px solid var(--ft-input-border);
          border-radius: 6px;
          padding: 3px 6px;
          color: inherit;
        }
        .ft-tag-dropdown-new-color {
          width: 36px;
          padding: 0;
          border-radius: 6px;
          border: 1px solid var(--ft-input-border);
          background: transparent;
        }
        .ft-tag-dropdown-new-button {
          border-radius: 6px;
          border: 1px solid var(--ft-border-strong);
          background: transparent;
          color: inherit;
          padding: 2px 6px;
          font-size: 12px;
          cursor: pointer;
        }
        .ft-tag-dropdown-new-button:hover {
          background: var(--ft-hover-bg);
        }

        /* Bookmark header controls (テヌマ倉数適甚) */
        .ft-filter-button {
          border-radius: 8px;
          border: 1px solid var(--modal-border, rgba(239,243,244,0.3));
          background: var(--modal-input-bg, rgba(0,0,0,0.2));
          color: var(--modal-text-primary, rgb(239,243,244));
          font-size: 14px;
          padding: 4px 10px;
          display: inline-flex;
          align-items: center;
          gap: 6px;
          cursor: pointer;
        }
        .ft-filter-button-label {
          max-width: 140px;
          overflow: hidden;
          white-space: nowrap;
          text-overflow: ellipsis;
        }
        .ft-filter-button-caret {
          font-size: 10px;
          opacity: 0.8;
        }
        .ft-filter-button[disabled] {
          opacity: 0.4;
          cursor: default;
        }
        .ft-filter-button:not([disabled]):hover {
          background: var(--modal-button-hover-bg, rgba(255,255,255,0.06));
          border-color: var(--modal-text-secondary, rgba(239,243,244,0.6));
        }
        .ft-settings-button {
          border-radius: 9999px;
          width: 26px;
          height: 26px;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          border: 1px solid var(--modal-border, rgba(239,243,244,0.3));
          background: var(--modal-input-bg, rgba(0,0,0,0.2));
          color: var(--modal-text-primary, rgb(239,243,244));
          cursor: pointer;
        }
        .ft-settings-button:hover {
          background: var(--modal-button-hover-bg, rgba(255,255,255,0.06));
        }

        /* Settings modal */
        .ft-modal-backdrop {
          position: fixed;
          inset: 0;
          background: rgba(0,0,0,0.6);
          z-index: 2147483000;
          display: flex;
          align-items: center;
          justify-content: center;
        }
        .ft-modal {
          width: min(380px, 100vw - 32px);
          max-height: 80vh;
          border-radius: 16px;
          background: var(--ft-bg);
          border: 1px solid var(--ft-border-light);
          box-shadow: 0 20px 40px rgba(0,0,0,0.75);
          display: flex;
          flex-direction: column;
          color: var(--ft-text-primary);
        }
        .ft-modal-header {
          padding: 10px 14px;
          border-bottom: 1px solid var(--ft-border-dim);
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 8px;
        }
        .ft-modal-title {
          font-size: 14px;
          font-weight: 600;
        }
        .ft-modal-toggle {
          display: inline-flex;
          align-items: center;
          gap: 4px;
          font-size: 12px;
        }
        .ft-modal-toggle input[type="checkbox"] {
          accent-color: var(--ft-accent-color);
        }
        .ft-modal-body {
          padding: 10px 14px 12px;
          overflow-y: auto;
          font-size: 13px;
        }
        .ft-modal-footer {
          padding: 8px 14px 10px;
          border-top: 1px solid var(--ft-border-dim);
          display: flex;
          justify-content: flex-end;
          gap: 8px;
        }
        .ft-modal-button {
          border-radius: 9999px;
          border: 1px solid var(--ft-border-strong);
          background: transparent;
          color: inherit;
          font-size: 12px;
          padding: 4px 10px;
          cursor: pointer;
        }
        .ft-modal-button:hover {
          background: var(--ft-hover-bg);
        }

        /* Display settings section */
        .ft-modal-display-settings {
          margin-bottom: 10px;
          padding-bottom: 8px;
          border-bottom: 1px solid var(--ft-border-dim);
          font-size: 12px;
        }
        .ft-modal-display-settings-row {
          display: flex;
          flex-wrap: wrap;
          gap: 8px;
          align-items: center;
          margin-top: 4px;
        }
        .ft-modal-display-radio {
          display: inline-flex;
          align-items: center;
          gap: 4px;
        }

        /* Tag list in modal */
       .ft-modal-tag-list {
          display: flex;
          flex-direction: column;
          gap: 6px;
          margin-bottom: 10px;
        }
        .ft-modal-tag-item {
          position: relative;
          display: grid;
          /* [mainCell] [dragHandle] [orderButtons] [deleteBtn] */
          grid-template-columns: minmax(0, 1fr) auto auto auto;
          align-items: center;
          gap: 6px;
          /* cursor: grab; を削陀 (ハンドルが担圓) */
        }
        .ft-modal-tag-main {
          display: flex;
          align-items: center;
          gap: 6px;
        }
        .ft-modal-tag-item-dragging {
          opacity: 0.6;
        }
        .ft-modal-tag-item-drop-before::before {
          content: '';
          position: absolute;
          left: 0;
          right: 0;
          top: -4px;
          border-top: 2px solid var(--ft-border-accent);
        }
        .ft-modal-tag-item-drop-after::after {
          content: '';
          position: absolute;
          left: 0;
          right: 0;
          bottom: -4px;
          border-bottom: 2px solid var(--ft-border-accent);
        }
        .ft-modal-tag-item-drop-child {
          background: var(--ft-hover-bg-strong);
        }
        .ft-modal-tag-name {
          background: var(--ft-input-bg);
          border-radius: 6px;
          border: 1px solid var(--ft-input-border);
          padding: 3px 6px;
          color: inherit;
          font-size: 12px;
        }
        .ft-modal-tag-color {
          width: 40px;
          padding: 0;
          border-radius: 6px;
          border: 1px solid var(--ft-input-border);
          background: transparent;
        }
        .ft-modal-tag-order,
        .ft-modal-tag-delete {
          border-radius: 6px;
          border: 1px solid var(--ft-border-strong);
          background: transparent;
          color: inherit;
          padding: 2px 4px;
          cursor: pointer;
          font-size: 11px;
        }
        .ft-modal-tag-order:hover,
        .ft-modal-tag-delete:hover {
          background: var(--ft-hover-bg-strong);
        }

        /* --- Drag handle for tag settings --- */
        .ft-modal-tag-drag-handle {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          width: 20px;
          height: 20px;
          border-radius: 4px;
          cursor: grab;
          color: var(--ft-text-secondary);
          user-select: none;
        }
        .ft-modal-tag-drag-handle:hover {
          background: var(--ft-hover-bg-strong);
          color: var(--ft-text-primary);
        }
        /* Uncategorized: disable drag */
        .ft-modal-tag-item[data-kind="uncat"] .ft-modal-tag-drag-handle {
          cursor: not-allowed;
          opacity: 0.5;
        }

        /* New tag row */
        .ft-modal-new-tag {
          border-top: 1px solid var(--ft-border-dim);
          padding-top: 8px;
          display: flex;
          flex-direction: column;
          gap: 6px;
        }
        .ft-modal-new-tag-row {
          display: grid;
          grid-template-columns: auto minmax(0, 1fr) auto;
          gap: 6px;
        }

        /* 未分類は名前倉曎䞍可削陀䞍可の芖芚衚珟 */
        .ft-modal-tag-name[readonly] {
          cursor: not-allowed;
          opacity: 0.8;
        }
        .ft-modal-tag-delete:disabled {
          cursor: not-allowed;
          opacity: 0.4;
        }

        /* Hidden helper */
        .ft-hidden {
          display: none !important;
          content-visibility: hidden;
          contain: strict;
        }
        /* --- End Favorite Tags CSS --- */

        /* --- Favorites Feature --- */
        .adv-fav-btn {
          display: inline-flex; align-items: center; justify-content: center;
          background: transparent; border: none; cursor: pointer;
          color: rgb(83, 100, 113); /* Default grey */
          padding: 0; margin: 0;
          width: 34.75px; height: 34.75px; /* X standard icon size touch target */
          border-radius: 50%;
          transition: background-color 0.2s, color 0.2s;
        }
        /* ネむティブのクラスを借甚した時は固定サむズを無効化する */
        .adv-fav-btn.adv-native-style {
          width: auto;
          height: auto;
          min-width: 34.75px; /* 最䜎限の倧きさは確保 */
          min-height: 34.75px;
        }
        .adv-fav-btn:hover {
          background-color: rgba(29, 155, 240, 0.1);
          color: rgb(29, 155, 240);
        }
        .adv-fav-btn.active {
          color: rgb(249, 24, 128); /* Pink/Red like Like, or Gold? Let's use Gold for Star */
          color: rgb(255, 215, 0);
        }
        .adv-fav-btn.active:hover {
          background-color: rgba(255, 215, 0, 0.1);
        }
        .adv-fav-btn svg {
          width: 20px; height: 20px;
          fill: currentColor;
        }
        .adv-item-body-text {
          font-size: 13px; color: var(--modal-text-primary); margin-top: 4px;
          white-space: pre-wrap;       /* 改行を維持 */
          word-break: break-word;      /* 長い単語を折り返し */
        }
        /* Favorites Media */
        .adv-item-media-row {
          display: flex;
          gap: 4px;
          margin-top: 6px;
          overflow-x: auto;
          padding-bottom: 2px;
        }
        .adv-item-media-row::-webkit-scrollbar { height: 4px; }
        .adv-item-media-row::-webkit-scrollbar-thumb { background: var(--modal-border); border-radius: 2px; }
        .adv-media-thumb {
          height: 60px;
          min-width: 60px;
          border-radius: 6px;
          border: 1px solid var(--modal-border);
          object-fit: cover;
          cursor: pointer;
        }
        /* Favorites Quote */
        .adv-quote-box {
          margin-top: 8px;
          border: 1px solid var(--modal-border);
          border-radius: 12px;
          padding: 8px 12px;
          background-color: rgba(0, 0, 0, 0.03);
        }
        .adv-quote-header {
          display: flex;
          align-items: center;
          gap: 6px;
          margin-bottom: 4px;
          font-size: 12px;
        }
        .adv-quote-avatar {
          width: 20px;
          height: 20px;
          border-radius: 50%;
          object-fit: cover;
        }
        .adv-quote-name {
          font-weight: 700;
          color: var(--modal-text-primary);
        }
        .adv-quote-handle {
          color: var(--modal-text-secondary);
        }
        .adv-quote-text {
          font-size: 13px;
          color: var(--modal-text-primary);
          white-space: pre-wrap;
          word-break: break-word;
        }
        /* Content Link */
        .adv-content-link {
          color: var(--modal-primary-color);
          text-decoration: none;
        }
        .adv-content-link:hover {
          text-decoration: underline;
        }

        /* Media Play Icon */
        .adv-media-wrap {
          position: relative;
          display: inline-flex;
        }
        .adv-media-play-icon {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
          width: 24px;
          height: 24px;
          background-color: rgba(0, 0, 0, 0.6);
          color: #fff;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;
          pointer-events: none; /* クリックを䞋の画像(リンク)に透過させる */
          backdrop-filter: blur(1px);
          z-index: 1;
        }
        .adv-media-play-icon svg {
          width: 14px;
          height: 14px;
          fill: currentColor;
          display: block;
          margin-left: 2px;
        }
        /* Favorites Item Tag Container */
        .adv-fav-tag-container {
           margin-top:0.7px;
           margin-left: 2px;
           display: inline-flex;
           align-items: center;
        }
        /* タグチップのサむズ埮調敎 */
        .adv-item-sub .ft-tag-chip {
            margin-left: 8px;
            font-size: 10px;
            padding: 0 6px;
            height: 18px;
        }
    `);

    const modalHTML = `
        <div id="advanced-search-modal">
            <div class="adv-modal-header">
                <div class="adv-modal-title-left">
                    <h2 data-i18n="modalTitle"></h2>
                    <button id="adv-settings-button" class="adv-settings-btn" type="button" data-i18n-title="tooltipSettings">
                        ${SETTINGS_SVG}
                    </button>
                </div>
                <div class="adv-secret-wrap">
                    <button id="adv-secret-btn" class="adv-secret-btn off" data-i18n-title="tooltipSecret" title="">
                        <span class="dot" aria-hidden="true"></span>
                        <span id="adv-secret-label" data-i18n="secretMode"></span>
                        <span id="adv-secret-state" style="font-weight:700;"></span>
                    </button>
                    <button class="adv-modal-close" data-i18n-title="tooltipClose">&times;</button>
                </div>
            </div>

            <div class="adv-modal-body">
                <div class="adv-tabs">
                    <button class="adv-tab-btn active" data-tab="search" data-i18n="tabSearch"></button>
                    <button class="adv-tab-btn" data-tab="history" data-i18n="tabHistory"></button>
                    <button class="adv-tab-btn" data-tab="saved" data-i18n="tabSaved"></button>
                    <button class="adv-tab-btn" data-tab="favorites" data-i18n="tabFavorites"></button>
                    <button class="adv-tab-btn" data-tab="mute" data-i18n="tabMute"></button>
                    <button class="adv-tab-btn" data-tab="lists" data-i18n="tabLists"></button>
                    <button class="adv-tab-btn" data-tab="accounts" data-i18n="tabAccounts"></button>
                </div>

                <div class="adv-tab-content active" id="adv-tab-search">
                    <div id="adv-zoom-root" class="adv-zoom-root">
                    <form id="advanced-search-form">
                        <div class="adv-form-group"><label for="adv-all-words" data-i18n="labelAllWords"></label><input type="text" id="adv-all-words" data-i18n-placeholder="placeholderAllWords"></div>
                        <div class="adv-form-group"><label for="adv-exact-phrase" data-i18n="labelExactPhrase"></label><input type="text" id="adv-exact-phrase" data-i18n-placeholder="placeholderExactPhrase"></div>
                        <div class="adv-form-group"><label for="adv-any-words" data-i18n="labelAnyWords"></label><input type="text" id="adv-any-words" data-i18n-placeholder="placeholderAnyWords"></div>
                        <div class="adv-form-group"><label for="adv-not-words" data-i18n="labelNotWords"></label><input type="text" id="adv-not-words" data-i18n-placeholder="placeholderNotWords"></div>
                        <div class="adv-form-group"><label for="adv-hashtag" data-i18n="labelHashtag"></label><input type="text" id="adv-hashtag" data-i18n-placeholder="placeholderHashtag"></div>
                        <div class="adv-form-group">
                          <label for="adv-lang" data-i18n="labelLang"></label>
                          <select id="adv-lang">
                            <option value="" data-i18n="optLangDefault"></option>
                            <option value="ja" data-i18n="optLangJa"></option>
                            <option value="en" data-i18n="optLangEn"></option>
                            <option value="id" data-i18n="optLangId"></option>     <!-- むンドネシア -->
                            <option value="hi" data-i18n="optLangHi"></option>     <!-- ヒンディヌむンド -->
                            <option value="de" data-i18n="optLangDe"></option>     <!-- ドむツ -->
                            <option value="tr" data-i18n="optLangTr"></option>     <!-- トルコ -->
                            <option value="es" data-i18n="optLangEs"></option>     <!-- スペむン語メキシコ含む -->
                            <option value="pt" data-i18n="optLangPt"></option>     <!-- ポルトガル語ブラゞル-->
                            <option value="ar" data-i18n="optLangAr"></option>     <!-- アラビア語サりゞ等 -->
                            <option value="fr" data-i18n="optLangFr"></option>
                            <option value="ko" data-i18n="optLangKo"></option>
                            <option value="ru" data-i18n="optLangRu"></option>
                            <option value="zh-cn" data-i18n="optLangZhHans"></option> <!-- 簡䜓䞭文 -->
                            <option value="zh-tw" data-i18n="optLangZhHant"></option> <!-- 繁體䞭文 -->
                          </select>
                        </div>
                        <hr class="adv-separator">
                        <div class="adv-form-group">
                            <label data-i18n="labelFilters"></label>
                            <div class="adv-filter-grid">
                                <div class="adv-checkbox-group"><span data-i18n="labelVerified"></span><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-verified-include"><label for="adv-filter-verified-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-verified-exclude"><label for="adv-filter-verified-exclude" data-i18n="checkExclude"></label></div></div>
                                <div class="adv-checkbox-group"><span data-i18n="labelLinks"></span><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-links-include"><label for="adv-filter-links-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-links-exclude"><label for="adv-filter-links-exclude" data-i18n="checkExclude"></label></div></div>
                                <div class="adv-checkbox-group"><span data-i18n="labelImages"></span><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-images-include"><label for="adv-filter-images-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-images-exclude"><label for="adv-filter-images-exclude" data-i18n="checkExclude"></label></div></div>
                                <div class="adv-checkbox-group"><span data-i18n="labelVideos"></span><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-videos-include"><label for="adv-filter-videos-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-videos-exclude"><label for="adv-filter-videos-exclude" data-i18n="checkExclude"></label></div></div>
                                <div class="adv-checkbox-group"><span data-i18n="labelReposts"></span><div class="adv-checkbox-item" style="display: none;"><input type="checkbox" id="adv-filter-reposts-include" disabled><label for="adv-filter-reposts-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-reposts-exclude"><label for="adv-filter-reposts-exclude" data-i18n="checkExclude"></label></div></div>
                                <div class="adv-checkbox-group"><span data-i18n="labelTimelineHashtags"></span><div class="adv-checkbox-item" style="display: none;"><input type="checkbox" id="adv-filter-hashtags-include" disabled><label for="adv-filter-hashtags-include" data-i18n="checkInclude"></label></div><div class="adv-checkbox-item"><input type="checkbox" id="adv-filter-hashtags-exclude"><label for="adv-filter-hashtags-exclude" data-i18n="checkExclude"></label></div></div>
                            </div>
                        </div>

                        <div class="adv-form-group" title="" data-i18n-title="hintSearchTarget">
                          <label data-i18n="labelSearchTarget"></label>
                          <div class="adv-checkbox-group">
                            <div class="adv-checkbox-item">
                              <input type="checkbox" id="adv-exclude-hit-name" checked>
                              <label for="adv-exclude-hit-name" data-i18n="labelHitName" title="" data-i18n-title="hintName"></label>
                            </div>
                            <div class="adv-checkbox-item">
                              <input type="checkbox" id="adv-exclude-hit-handle" checked>
                              <label for="adv-exclude-hit-handle" data-i18n="labelHitHandle" title="" data-i18n-title="hintHandle"></label>
                            </div>
                          </div>
                        </div>

                        <div class="adv-form-row two-cols">
                            <div class="adv-form-group">
                                <label for="adv-account-scope" data-i18n="labelAccountScope"></label>
                                <select id="adv-account-scope">
                                    <option value="" data-i18n="optAccountAll"></option>
                                    <option value="following" data-i18n="optAccountFollowing"></option>
                                </select>
                            </div>
                            <div class="adv-form-group">
                                <label for="adv-location-scope" data-i18n="labelLocationScope"></label>
                                <select id="adv-location-scope">
                                    <option value="" data-i18n="optLocationAll"></option>
                                    <option value="nearby" data-i18n="optLocationNearby"></option>
                                </select>
                            </div>
                        </div>

                        <div class="adv-form-group"><label data-i18n="labelReplies"></label><select id="adv-replies"><option value="" data-i18n="optRepliesDefault"></option><option value="include" data-i18n="optRepliesInclude"></option><option value="only" data-i18n="optRepliesOnly"></option><option value="exclude" data-i18n="optRepliesExclude"></option></select></div>
                        <hr class="adv-separator">
                        <div class="adv-form-group">
                            <label data-i18n="labelEngagement"></label>
                            <div class="adv-filter-grid">
                                <input type="number" id="adv-min-replies" data-i18n-placeholder="placeholderMinReplies" min="0">
                                <input type="number" id="adv-min-faves" data-i18n-placeholder="placeholderMinLikes" min="0">
                                <input type="number" id="adv-min-retweets" data-i18n-placeholder="placeholderMinRetweets" min="0">
                            </div>
                        </div>
                        <div class="adv-form-group">
                            <label data-i18n="labelDateRange"></label>
                            <div class="adv-form-group-date-container">
                                <input type="date" id="adv-since" data-i18n-title="tooltipSince">
                                <input type="date" id="adv-until" data-i18n-title="tooltipUntil">
                            </div>
                        </div>
                        <hr class="adv-separator">
                        <div class="adv-form-group">
                            <div class="adv-account-label-group">
                                <label for="adv-from-user" data-i18n="labelFromUser"></label>
                                <div class="adv-exclude-toggle"><input type="checkbox" id="adv-from-user-exclude"><label for="adv-from-user-exclude" data-i18n="checkExclude"></label></div>
                            </div>
                            <input type="text" id="adv-from-user" data-i18n-placeholder="placeholderFromUser">
                        </div>
                        <div class="adv-form-group">
                            <div class="adv-account-label-group">
                                <label for="adv-to-user" data-i18n="labelToUser"></label>
                                <div class="adv-exclude-toggle"><input type="checkbox" id="adv-to-user-exclude"><label for="adv-to-user-exclude" data-i18n="checkExclude"></label></div>
                            </div>
                            <input type="text" id="adv-to-user" data-i18n-placeholder="placeholderToUser">
                        </div>
                        <div class="adv-form-group">
                            <div class="adv-account-label-group">
                                <label for="adv-mentioning" data-i18n="labelMentioning"></label>
                                <div class="adv-exclude-toggle"><input type="checkbox" id="adv-mentioning-exclude"><label for="adv-mentioning-exclude" data-i18n="checkExclude"></label></div>
                            </div>
                            <input type="text" id="adv-mentioning" data-i18n-placeholder="placeholderMentioning">
                        </div>
                    </form>
                    </div>
                </div>

                <div class="adv-tab-content" id="adv-tab-history">
                  <div class="adv-zoom-root">
                    <div class="adv-tab-toolbar">
                        <div class="adv-tab-toolbar-left">
                            <input id="adv-history-search" class="adv-input" type="text" data-i18n-placeholder="placeholderSearchHistory">
                            <select id="adv-history-sort" class="adv-select" data-i18n-title="labelSortBy" title="Sort by:">
                                <option value="newest" data-i18n="sortNewest"></option>
                                <option value="oldest" data-i18n="sortOldest"></option>
                                <option value="name_asc" data-i18n="sortNameAsc"></option>
                                <option value="name_desc" data-i18n="sortNameDesc"></option>
                            </select>
                        </div>
                        <div class="adv-tab-toolbar-right">
                            <button id="adv-history-clear-all" class="adv-chip danger"></button>
                        </div>
                    </div>
                    <div id="adv-history-empty" class="adv-item-sub"></div>
                    <div id="adv-history-list" class="adv-list"></div>
                  </div>
                </div>

                <div class="adv-tab-content" id="adv-tab-saved">
                  <div class="adv-zoom-root">
                    <div id="adv-saved-empty" class="adv-item-sub"></div>
                    <div id="adv-saved-list" class="adv-list"></div>
                  </div>
                </div>

                <div class="adv-tab-content" id="adv-tab-favorites">
                  <div class="adv-zoom-root">
                    <div id="adv-favorites-list" class="adv-list"></div>
                    <div id="adv-favorites-empty" class="adv-item-sub"></div>
                  </div>
                </div>

                <div class="adv-tab-content" id="adv-tab-lists">
                  <div class="adv-zoom-root">
                    <div id="adv-lists-empty" class="adv-item-sub"></div>
                    <div id="adv-lists-list" class="adv-list"></div>
                  </div>
                </div>

                <div class="adv-tab-content" id="adv-tab-accounts">
                  <div class="adv-zoom-root">
                    <div id="adv-accounts-empty" class="adv-item-sub"></div>
                    <div id="adv-accounts-list" class="adv-list"></div>
                  </div>
                </div>

                <!-- ▶ Mute タブ -->
                <div class="adv-tab-content" id="adv-tab-mute">
                  <div class="adv-zoom-root">
                    <div class="adv-form-group">
                      <!-- 远加する䞊びたず“远加”UI -->
                      <div class="adv-mute-add">
                        <input type="text" id="adv-mute-input" data-i18n-placeholder="placeholderMuteWord">
                        <button id="adv-mute-add" class="adv-modal-button" data-i18n="buttonAdd"></button>
                        <label class="adv-toggle" title="">
                          <input type="checkbox" id="adv-mute-cs">
                          <span data-i18n="labelCaseSensitive"></span>
                        </label>
                      </div>

                      <!-- â–Œ 新しい芋出しブロックミュヌト䞀芧 + すべお有効/無効 -->
                      <div class="adv-mute-header" style="margin-top:12px;">
                        <div class="adv-mute-title" data-i18n="mutedListHeading"></div>
                        <label class="adv-toggle">
                          <input type="checkbox" id="adv-mute-enable-all" checked>
                          <span data-i18n="labelEnableAll"></span>
                        </label>
                      </div>

                      <div id="adv-mute-empty" class="adv-item-sub"></div>
                      <div id="adv-mute-list" class="adv-mute-list"></div>
                    </div>
                  </div>
                </div>

            </div>
            <div class="adv-modal-footer">
                <button id="adv-save-button" class="adv-modal-button" data-i18n="buttonSave"></button>
                <button id="adv-clear-button" class="adv-modal-button" data-i18n="buttonClear"></button>
                <button id="adv-apply-button" class="adv-modal-button primary" data-i18n="buttonApply"></button>
            </div>
        </div>

        <div id="adv-toast" class="adv-toast" role="status" aria-live="polite"></div>
            <div id="adv-settings-modal" class="adv-settings-modal">
            <div class="adv-settings-dialog">
                <div class="adv-settings-header">
                    <div style="display:flex; align-items:center; gap:15px;">
                        <h3 class="adv-settings-title" data-i18n="settingsTitle"></h3>
                        <button id="adv-settings-reset" type="button" class="adv-chip danger" data-i18n="buttonReset"></button>
                    </div>
                    <button id="adv-settings-close" type="button" class="adv-settings-close" data-i18n-title="tooltipClose">&times;</button>
                </div>
                <div class="adv-settings-body">
                  <div class="adv-settings-section-header" data-i18n="settingsTitleGeneral"></div>
                    <div class="adv-settings-group">
                        <label for="adv-settings-lang" data-i18n="labelUILang"></label>
                        <select id="adv-settings-lang">
                            <option value="" data-i18n="optUILangAuto"></option>
                            <option value="en">English</option>
                            <option value="ja">日本語</option>
                            <option value="fr">Français</option>
                            <option value="es">Español</option>
                            <option value="de">Deutsch</option>
                            <option value="pt-BR">Português (Brasil)</option>
                            <option value="ru">РусскОй</option>
                            <option value="ko">한국얎</option>
                            <option value="zh-CN">简䜓䞭文</option>
                            <option value="zh-TW">繁體䞭文</option>
                        </select>
                    </div>

                      <div class="adv-settings-section-header" data-i18n="settingsTitleFeatures"></div>
                      <div class="adv-settings-group">
                        <div class="adv-settings-toggle-row">
                          <label class="adv-toggle" for="adv-settings-tab-toggle-search">
                              <span data-i18n="tabSearch"></span>
                          </label>
                          <label class="adv-switch">
                              <input id="adv-settings-tab-toggle-search" type="checkbox">
                              <span class="adv-slider"></span>
                          </label>
                        </div>
                        <div class="adv-settings-toggle-row">
                          <label class="adv-toggle" for="adv-settings-tab-toggle-history">
                              <span data-i18n="tabHistory"></span>
                          </label>
                          <label class="adv-switch">
                              <input id="adv-settings-tab-toggle-history" type="checkbox">
                              <span class="adv-slider"></span>
                          </label>
                        </div>
                        <div class="adv-settings-toggle-row">
                          <label class="adv-toggle" for="adv-settings-tab-toggle-saved">
                              <span data-i18n="tabSaved"></span>
                          </label>
                          <label class="adv-switch">
                              <input id="adv-settings-tab-toggle-saved" type="checkbox">
                              <span class="adv-slider"></span>
                          </label>
                        </div>
                        <div class="adv-settings-toggle-row">
                          <label class="adv-toggle" for="adv-settings-tab-toggle-favorites">
                              <span data-i18n="tabFavorites"></span>
                          </label>
                          <label class="adv-switch">
                              <input id="adv-settings-tab-toggle-favorites" type="checkbox">
                              <span class="adv-slider"></span>
                          </label>
                        </div>
                        <div class="adv-settings-toggle-row">
                          <label class="adv-toggle" for="adv-settings-tab-toggle-mute">
                              <span data-i18n="tabMute"></span>
                          </label>
                          <label class="adv-switch">
                              <input id="adv-settings-tab-toggle-mute" type="checkbox">
                              <span class="adv-slider"></span>
                          </label>
                        </div>
                        <div class="adv-settings-toggle-row">
                          <label class="adv-toggle" for="adv-settings-tab-toggle-lists">
                              <span data-i18n="tabLists"></span>
                          </label>
                          <label class="adv-switch">
                              <input id="adv-settings-tab-toggle-lists" type="checkbox">
                              <span class="adv-slider"></span>
                          </label>
                        </div>
                        <div class="adv-settings-toggle-row">
                          <label class="adv-toggle" for="adv-settings-tab-toggle-accounts">
                              <span data-i18n="tabAccounts"></span>
                          </label>
                          <label class="adv-switch">
                              <input id="adv-settings-tab-toggle-accounts" type="checkbox">
                              <span class="adv-slider"></span>
                          </label>
                        </div>
                      </div>
                      <div class="adv-settings-section-header" data-i18n="settingsTitleData"></div>
                      <div class="adv-settings-group">
                          <div class="adv-settings-actions-inline">
                              <button id="adv-settings-export" type="button" class="adv-modal-button" data-i18n="buttonExport"></button>
                              <button id="adv-settings-import" type="button" class="adv-modal-button primary" data-i18n="buttonImport"></button>
                              <input id="adv-settings-file-input" type="file" accept="application/json" style="display:none">
                          </div>
                      </div>

                    </div>
                    <div class="adv-settings-footer">
                        <button id="adv-settings-close-footer" type="button" class="adv-modal-button" data-i18n="buttonClose"></button>
                    </div>
            </div>
        </div>
    `;

    const initialize = async () => {
        i18n.init();

        const kv = {
            get(key, def) { try { return GM_getValue(key, def); } catch (_) { return def; } },
            set(key, val) { try { GM_setValue(key, val); } catch (_) {} },
            del(key)      { try { GM_deleteValue(key); } catch (_) {} },
        };
        const loadJSON = (key, def) => {
            try {
                const raw = kv.get(key, JSON.stringify(def));
                return JSON.parse(raw);
            } catch(_) { return def; }
        };
        const saveJSON = (key, value) => {
            try { kv.set(key, JSON.stringify(value)); } catch(_) {}
        };

        const DEFAULT_TABS = ['search', 'history', 'saved', 'favorites', 'mute', 'lists', 'accounts'];
        const DEFAULT_TABS_VISIBILITY = {
            search: true,
            history: true,
            saved: true,
            favorites: true,
            mute: true,
            lists: true,
            accounts: true,
        };
        const loadTabsVisibility = () => {
            const stored = loadJSON(TABS_VISIBILITY_KEY, DEFAULT_TABS_VISIBILITY);
            const normalized = { ...DEFAULT_TABS_VISIBILITY };
            for (const key of DEFAULT_TABS) {
                normalized[key] = stored[key] === false ? false : true; // false のみ明瀺的に匕き継ぐ
            }
            return normalized;
        };
        const saveTabsVisibility = (state) => {
            saveJSON(TABS_VISIBILITY_KEY, state);
        };

        /* --- Favorite Tags: Code Block --- */

        // ------------- 定数 & 状態 ------------- //
        const FT_STATE_KEY = 'ftTagState_v1';
        const FT_FILTER_ALL = 'all';
        const FT_FILTER_UNCATEGORIZED = 'uncategorized';
        const FT_TWEET_ID_REGEX = /\/status\/(\d+)/;

        let ft_state = null;
        let ft_initialized = false;
        let ft_currentFilter = FT_FILTER_ALL;
        let ft_currentDropdown = null;
        let ft_settingsModalBackdrop = null;
        let ft_dragSrcEntry = null;

        // ------------- State 管理 ------------- //

        function ft_createDefaultState() {
            return {
                enabled: true,
                tags: [],
                tweetTags: {},
                uncategorized: { color: '#8899A6', order: 0 },
                display: { mode: 'leaf' },
            };
        }

        function ft_normalizeTagOrdersFor(stateObj) {
            if (!stateObj || !Array.isArray(stateObj.tags)) return;
            const groups = new Map();
            for (const tag of stateObj.tags) {
                if (!tag || typeof tag !== 'object') continue;
                const pid = tag.parentId || null;
                if (!groups.has(pid)) groups.set(pid, []);
                groups.get(pid).push(tag);
            }
            for (const arr of groups.values()) {
                arr.sort((a, b) => (typeof a.order === 'number' ? a.order : 0) - (typeof b.order === 'number' ? b.order : 0));
                arr.forEach((tag, i) => { tag.order = i; });
            }
        }

        function ft_countRootTagsFor(stateObj) {
            if (!stateObj || !Array.isArray(stateObj.tags)) return 0;
            return stateObj.tags.filter((t) => !t.parentId).length;
        }

        function ft_clampUncategorizedOrderFor(stateObj) {
            if (!stateObj) return;
            if (!stateObj.uncategorized || typeof stateObj.uncategorized !== 'object') {
                stateObj.uncategorized = { color: '#8899A6', order: 0 };
            }
            const rootCount = ft_countRootTagsFor(stateObj);
            let pos = typeof stateObj.uncategorized.order === 'number' ? stateObj.uncategorized.order : 0;
            if (pos < 0) pos = 0;
            if (pos > rootCount) pos = rootCount;
            stateObj.uncategorized.order = pos;
        }

        function ft_normalizeTagOrders() { if (ft_state) ft_normalizeTagOrdersFor(ft_state); }
        function ft_clampUncategorizedOrder() { if (ft_state) ft_clampUncategorizedOrderFor(ft_state); }

        function ft_loadState() {
            try {
                const parsed = loadJSON(FT_STATE_KEY, null);
                if (!parsed || typeof parsed !== 'object') return ft_createDefaultState();

                if (!Array.isArray(parsed.tags)) parsed.tags = [];
                if (!parsed.tweetTags || typeof parsed.tweetTags !== 'object') parsed.tweetTags = {};
                parsed.enabled = true;

                if (!parsed.uncategorized || typeof parsed.uncategorized !== 'object') {
                    parsed.uncategorized = { color: '#8899A6', order: 0 };
                } else {
                    if (!parsed.uncategorized.color) parsed.uncategorized.color = '#8899A6';
                    if (typeof parsed.uncategorized.order !== 'number') parsed.uncategorized.order = 0;
                }

                if (!parsed.display || typeof parsed.display !== 'object') {
                    parsed.display = { mode: 'leaf' };
                } else if (parsed.display.mode !== 'leaf' && parsed.display.mode !== 'full') {
                    parsed.display.mode = 'leaf';
                }

                ft_normalizeTagOrdersFor(parsed);
                ft_clampUncategorizedOrderFor(parsed);
                return parsed;
            } catch (e) {
                return ft_createDefaultState();
            }
        }

        function ft_saveState(newState) {
            if (newState) ft_state = newState;
            try {
                if (ft_state) {
                    ft_normalizeTagOrdersFor(ft_state);
                    ft_clampUncategorizedOrderFor(ft_state);
                    saveJSON(FT_STATE_KEY, ft_state);
                }
            } catch (e) {}
            requestAnimationFrame(() => {
                ft_refreshAllTagChips();

                // お気に入りタブが開いおいれば再描画しおタグ倉曎/絞り蟌みを反映
                if (getActiveTabName() === 'favorites') {
                    renderFavorites();
                }
            });
        }

        function ft_generateTagId() {
            return 'tag_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
        }

        function ft_getTagById(tagId) {
            return ft_state.tags.find((t) => t.id === tagId) || null;
        }

        function ft_getAllTags() {
            return ft_state.tags.slice();
        }

        function ft_getTagColor(tagId) {
            const tag = ft_getTagById(tagId);
            return tag ? tag.color || '#1d9bf0' : '#8899A6';
        }

        function ft_getUncategorizedColor() {
            return ft_state?.uncategorized?.color || '#8899A6';
        }

        function ft_createNewTag(name, color, parentId) {
            const pid = parentId || null;
            const siblingsCount = ft_state.tags.filter((t) => (t.parentId || null) === pid).length;
            const tag = {
                id: ft_generateTagId(),
                name,
                color,
                parentId: pid,
                order: siblingsCount,
            };
            ft_state.tags.push(tag);
            return tag;
        }

        function ft_countRootTags() {
            return ft_countRootTagsFor(ft_state);
        }

        function ft_getTagAncestors(tag) {
            const result = [];
            if (!tag) return result;
            const seen = new Set();
            let current = tag;
            while (current) {
                if (seen.has(current.id)) break;
                seen.add(current.id);
                result.unshift(current);
                if (!current.parentId) break;
                current = ft_getTagById(current.parentId);
            }
            return result;
        }

        function ft_getTagFullPath(tag) {
            const ancestors = ft_getTagAncestors(tag);
            if (!ancestors.length) return tag ? tag.name || '' : '';
            return ancestors.map((t) => t.name || '').join(' / ');
        }

        function ft_getTagDisplayLabelFromTag(tag) {
            if (!tag) return '';
            const mode = ft_state?.display?.mode;
            if (mode === 'full') return ft_getTagFullPath(tag);
            return tag.name;
        }

        function ft_getTagListWithUncategorized() {
            const result = [];
            if (!ft_state || !Array.isArray(ft_state.tags)) return result;

            const byParent = new Map();
            for (const tag of ft_state.tags) {
                if (!tag || typeof tag !== 'object') continue;
                const pid = tag.parentId || null;
                if (!byParent.has(pid)) byParent.set(pid, []);
                byParent.get(pid).push(tag);
            }

            for (const arr of byParent.values()) {
                arr.sort((a, b) => (typeof a.order === 'number' ? a.order : 0) - (typeof b.order === 'number' ? b.order : 0));
            }

            function dfs(parentId, depth) {
                const arr = byParent.get(parentId || null);
                if (!arr) return;
                for (const tag of arr) {
                    result.push({ tag, depth });
                    dfs(tag.id, depth + 1);
                }
            }
            dfs(null, 0);

            const entries = [];
            const rootCount = result.filter((e) => e.depth === 0).length;
            let uncatPos = ft_state.uncategorized.order || 0;
            if (uncatPos < 0) uncatPos = 0;
            if (uncatPos > rootCount) uncatPos = rootCount;

            let rootIndex = 0;
            for (const item of result) {
                if (item.depth === 0 && rootIndex === uncatPos) {
                    entries.push({ kind: 'uncat', depth: 0 });
                }
                entries.push({ kind: 'tag', tag: item.tag, depth: item.depth });
                if (item.depth === 0) rootIndex++;
            }
            if (rootCount === 0 || uncatPos === rootCount) {
                entries.push({ kind: 'uncat', depth: 0 });
            }
            return entries;
        }

        function ft_isTagInSubtree(tagId, rootTagId) {
            // ft_state が存圚しない堎合は即座に false を返す
            if (!ft_state || !tagId || !rootTagId) return false;

            if (tagId === rootTagId) return true;
            let current = ft_getTagById(tagId);
            const visited = new Set();
            while (current && current.parentId) {
                if (visited.has(current.id)) break;
                visited.add(current.id);
                if (current.parentId === rootTagId) return true;
                current = ft_getTagById(current.parentId);
            }
            return false;
        }

        function ft_wouldCreateCycle(newParentId, childId) {
            if (!newParentId || !childId) return false;
            if (newParentId === childId) return true;
            let current = ft_getTagById(newParentId);
            const visited = new Set();
            while (current && current.parentId) {
                if (visited.has(current.id)) break;
                visited.add(current.id);
                if (current.parentId === childId) return true;
                current = ft_getTagById(current.parentId);
            }
            return false;
        }

        // ------------- ルヌト & ナヌティリティ ------------- //

        // ツむヌトのDOMからIDを抜出
        function ft_extractTweetId(article) {
            if (article.dataset.ftTweetId) return article.dataset.ftTweetId;

            // 匕甚ツむヌトカヌド郚分の䞭にあるリンクを陀倖するための刀定関数
            // div[role="link"] は匕甚カヌドのコンテナに付䞎される属性です
            const isInsideQuote = (el) => {
                return !!el.closest('div[role="link"]');
            };

            // 1. 最も確実な方法: <time>タグの芪アンカヌを探す
            const timeEls = Array.from(article.querySelectorAll('time'));
            for (const timeEl of timeEls) {
                const timeAnchor = timeEl.closest('a');
                if (timeAnchor) {
                    // ★远加: 匕甚カヌドの䞭にある時刻リンクならスキップする
                    if (isInsideQuote(timeAnchor)) continue;

                    const href = timeAnchor.getAttribute('href');
                    const m = href.match(/\/status\/(\d+)/);
                    if (m) return m[1];
                }
            }

            // 2. フォヌルバック: 埓来の怜玢方法
            try {
                const anchors = Array.from(article.querySelectorAll('a[href*="/status/"]'));
                for (const a of anchors) {
                    if (a.dataset.testid === 'tweet-text-show-more-link') continue;

                    // 匕甚カヌドの䞭にあるリンクならスキップする
                    if (isInsideQuote(a)) continue;

                    const href = a.getAttribute('href') || '';
                    const m = href.match(/\/status\/(\d+)/);
                    if (m) return m[1];
                }
            } catch (e) {}

            return null;
        }

        function ft_getTweetCardRoot(article) {
            return article.closest('div[data-testid="cellInnerDiv"]') || article;
        }

        function ft_findHeaderMetaContainer(article) {
            const timeEl = article.querySelector('time');
            if (!timeEl) return null;
            const anchor = timeEl.closest('a');
            if (!anchor) return null;
            let container = anchor.parentElement;
            if (container && container.parentElement) container = container.parentElement;
            if (!container || !container.parentElement) return null;
            return container.parentElement;
        }

        // ------------- タグチップ描画むベント委譲察応 ------------- //

        function ft_buildTagChip(tweetId) {
            const currentTagId = ft_state.tweetTags[tweetId];
            const isUncategorized = !currentTagId;
            const tag = currentTagId ? ft_getTagById(currentTagId) : null;
            const label = isUncategorized
                ? i18n.t('FT_UNCATEGORIZED')
                : ft_getTagDisplayLabelFromTag(tag) || i18n.t('FT_UNCATEGORIZED');
            const color = isUncategorized ? ft_getUncategorizedColor() : ft_getTagColor(currentTagId);

            const btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'ft-tag-chip' + (isUncategorized ? ' ft-tag-chip-uncategorized' : '');
            btn.style.color = color;
            btn.style.borderColor = color;
            btn.dataset.tweetId = tweetId;

            const span = document.createElement('span');
            span.className = 'ft-tag-chip-label';
            span.textContent = label;
            span.style.pointerEvents = 'none'; // クリックをボタンに透過

            btn.appendChild(span);
            return btn;
        }

        function ft_attachTagChipToArticle(article, tweetId) {
            if (!ft_state.enabled) return;
            const headerRow = ft_findHeaderMetaContainer(article);
            if (!headerRow) return;

            headerRow.style.display = 'flex';
            headerRow.style.flexDirection = 'row';
            headerRow.style.flexWrap = 'nowrap';
            headerRow.style.alignItems = 'center';
            headerRow.style.justifyContent = 'flex-start';
            headerRow.style.columnGap = '4px';

            let existing = headerRow.querySelector('.ft-tag-chip');
            const chip = ft_buildTagChip(tweetId);
            if (existing) {
                existing.replaceWith(chip);
            } else {
                headerRow.appendChild(chip);
            }
            article.classList.add('ft-chip-attached');
        }

        function ft_removeTagChipFromArticle(article) {
            const chip = article.querySelector('.ft-tag-chip');
            if (chip) chip.remove();
            article.classList.remove('ft-chip-attached');
        }

        function ft_refreshAllTagChips() {
            const articles = document.querySelectorAll('article[data-testid="tweet"]');
            for (const article of articles) {
                ft_processTweetArticle(article);
            }
        }

        // ------------- タグ / フィルタ甚ドロップダりン ------------- //

        function ft_closeTagDropdown() {
            if (ft_currentDropdown) {
                ft_currentDropdown.remove();
                ft_currentDropdown = null;
            }
        }

        // タグごずの件数を集蚈するヘルパヌ
        function ft_countTagUsage() {
            const counts = { uncat: 0, total: 0 };
            // 党タグの初期倀を0にする
            if (ft_state && ft_state.tags) {
                ft_state.tags.forEach(t => counts[t.id] = 0);
            }

            // 珟圚のお気に入りリストをロヌドしお集蚈
            const favs = loadFavorites();
            counts.total = favs.length;

            favs.forEach(item => {
                const tagId = ft_state.tweetTags[item.id];
                if (tagId && counts[tagId] !== undefined) {
                    counts[tagId]++;
                } else {
                    counts.uncat++;
                }
            });
            return counts;
        }

        function ft_buildTagDropdownContent(tweetId) {
            const wrapper = document.createElement('div');
            wrapper.className = 'ft-tag-dropdown';
            wrapper.dataset.tweetId = tweetId;

            const header = document.createElement('div');
            header.className = 'ft-tag-dropdown-header';
            const headerLeft = document.createElement('div');
            headerLeft.style.display = 'flex'; headerLeft.style.alignItems = 'center'; headerLeft.style.gap = '4px';
            const title = document.createElement('div');
            title.textContent = i18n.t('FT_DROPDOWN_TITLE');
            const settingsBtn = document.createElement('button');
            settingsBtn.type = 'button'; settingsBtn.className = 'ft-settings-button'; settingsBtn.textContent = '⚙';
            settingsBtn.title = i18n.t('FT_SETTINGS_BUTTON_TITLE');
            settingsBtn.addEventListener('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); ft_closeTagDropdown(); ft_openSettingsModal(); });
            headerLeft.appendChild(title); headerLeft.appendChild(settingsBtn);
            const closeBtn = document.createElement('button');
            closeBtn.className = 'ft-tag-dropdown-close'; closeBtn.type = 'button'; closeBtn.textContent = '×';
            closeBtn.addEventListener('click', () => ft_closeTagDropdown());
            header.appendChild(headerLeft); header.appendChild(closeBtn);

            const tagList = document.createElement('div');
            tagList.className = 'ft-tag-dropdown-tags';
            const currentTagId = ft_state.tweetTags[tweetId] || null;
            const entries = ft_getTagListWithUncategorized();

            const counts = ft_countTagUsage();

            for (const entry of entries) {
                const item = document.createElement('div');
                const colorDot = document.createElement('div');
                const label = document.createElement('div');
                label.className = 'ft-tag-dropdown-tag-label';

                if (entry.kind === 'uncat') {
                    item.className = 'ft-tag-dropdown-tag-item' + (currentTagId ? '' : ' ft-tag-dropdown-tag-selected');
                    colorDot.className = 'ft-tag-dropdown-tag-color';
                    colorDot.style.backgroundColor = ft_getUncategorizedColor();
                    label.textContent = i18n.t('FT_UNCATEGORIZED') + ` (${counts.uncat})`;
                    item.addEventListener('click', () => { delete ft_state.tweetTags[tweetId]; ft_saveState(); ft_closeTagDropdown(); });
                } else {
                    const tag = entry.tag;
                    item.className = 'ft-tag-dropdown-tag-item' + (tag.id === currentTagId ? ' ft-tag-dropdown-tag-selected' : '');
                    colorDot.className = 'ft-tag-dropdown-tag-color';
                    colorDot.style.backgroundColor = tag.color || '#1d9bf0';
                    if (entry.depth > 0) colorDot.style.marginLeft = `${entry.depth * 12}px`;
                    const c = counts[entry.tag.id] || 0;
                    label.textContent = (entry.tag.name || '') + ` (${c})`;
                    item.addEventListener('click', () => { ft_state.tweetTags[tweetId] = tag.id; ft_saveState(); ft_closeTagDropdown(); });
                }
                item.appendChild(colorDot); item.appendChild(label);
                tagList.appendChild(item);
            }

            const newSection = document.createElement('div');
            newSection.className = 'ft-tag-dropdown-new';
            const newLabel = document.createElement('div');
            newLabel.textContent = i18n.t('FT_DROPDOWN_NEW_TAG');
            const newRow = document.createElement('div');
            newRow.className = 'ft-tag-dropdown-new-row';
            const newInput = document.createElement('input');
            newInput.type = 'text'; newInput.placeholder = i18n.t('FT_DROPDOWN_NEW_TAG_PLACEHOLDER'); newInput.className = 'ft-tag-dropdown-new-input';
            const newColor = document.createElement('input');
            newColor.type = 'color'; newColor.value = '#1d9bf0'; newColor.className = 'ft-tag-dropdown-new-color';
            const addBtn = document.createElement('button');
            addBtn.type = 'button'; addBtn.className = 'ft-tag-dropdown-new-button'; addBtn.textContent = i18n.t('FT_DROPDOWN_NEW_TAG_ADD');

            function doAddTag() {
                const name = newInput.value.trim();
                if (!name) return;
                const tag = ft_createNewTag(name, newColor.value || '#1d9bf0', null);
                ft_state.tweetTags[tweetId] = tag.id;
                ft_saveState();
                ft_closeTagDropdown();
            }
            addBtn.addEventListener('click', doAddTag);
            newInput.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') { ev.preventDefault(); doAddTag(); } });

            newRow.appendChild(newColor); newRow.appendChild(newInput); newRow.appendChild(addBtn);
            newSection.appendChild(newLabel); newSection.appendChild(newRow);
            wrapper.appendChild(header); wrapper.appendChild(tagList); wrapper.appendChild(newSection);
            return wrapper;
        }

        function ft_openTagDropdown(chipEl, tweetId) {
            ft_closeTagDropdown();
            const dropdown = ft_buildTagDropdownContent(tweetId);
            ft_currentDropdown = dropdown;
            document.body.appendChild(dropdown);
            const rect = chipEl.getBoundingClientRect();
            const margin = 8;
            const width = dropdown.offsetWidth || 240;
            const height = dropdown.offsetHeight || 200;
            let left = rect.left;
            if (left + width + margin > window.innerWidth) left = window.innerWidth - width - margin;
            if (left < margin) left = margin;
            let top = rect.bottom + 4;
            if (top + height + margin > window.innerHeight) top = rect.top - height - 4;
            if (top < margin) top = margin;
            dropdown.style.left = `${left}px`; dropdown.style.top = `${top}px`;
        }

        // ---- ブックマヌクフィルタ甚ドロップダりン ---- //
        function ft_buildFilterDropdownContent(targetValue, onSelectCallback) {
            const currentValue = targetValue;

            const wrapper = document.createElement('div');
            wrapper.className = 'ft-tag-dropdown ft-filter-dropdown';
            const header = document.createElement('div'); header.className = 'ft-tag-dropdown-header';
            const headerLeft = document.createElement('div'); headerLeft.style.display = 'flex'; headerLeft.style.alignItems = 'center'; headerLeft.style.gap = '4px';
            const title = document.createElement('div'); title.textContent = i18n.t('FT_DROPDOWN_TITLE');
            const settingsBtn = document.createElement('button'); settingsBtn.type = 'button'; settingsBtn.className = 'ft-settings-button'; settingsBtn.textContent = '⚙'; settingsBtn.title = i18n.t('FT_SETTINGS_BUTTON_TITLE');
            settingsBtn.addEventListener('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); ft_closeTagDropdown(); ft_openSettingsModal(); });
            headerLeft.appendChild(title); headerLeft.appendChild(settingsBtn);
            const closeBtn = document.createElement('button'); closeBtn.className = 'ft-tag-dropdown-close'; closeBtn.type = 'button'; closeBtn.textContent = '×';
            closeBtn.addEventListener('click', () => ft_closeTagDropdown());
            header.appendChild(headerLeft); header.appendChild(closeBtn);

            const tagList = document.createElement('div'); tagList.className = 'ft-tag-dropdown-tags';
            function addItem(value, label, color, depth) {
                const item = document.createElement('div');
                item.className = 'ft-tag-dropdown-tag-item' + (value === currentValue ? ' ft-tag-dropdown-tag-selected' : '');
                const colorDot = document.createElement('div');
                colorDot.className = 'ft-tag-dropdown-tag-color'; colorDot.style.backgroundColor = color || 'rgba(239,243,244,0.6)';
                if (depth > 0) colorDot.style.marginLeft = `${depth * 12}px`;
                const text = document.createElement('div');
                text.className = 'ft-tag-dropdown-tag-label'; text.textContent = label;
                item.appendChild(colorDot); item.appendChild(text);
                item.addEventListener('click', () => {
                    if (typeof onSelectCallback === 'function') {
                        onSelectCallback(value);
                    }
                    ft_closeTagDropdown();
                });
                tagList.appendChild(item);
            }
            const counts = ft_countTagUsage();
            addItem(FT_FILTER_ALL, i18n.t('FT_FILTER_ALL') + ` (${counts.total})`, 'rgba(239,243,244,0.7)', 0);

            const entries = ft_getTagListWithUncategorized();
            for (const entry of entries) {
                if (entry.kind === 'uncat') {
                    addItem(FT_FILTER_UNCATEGORIZED, i18n.t('FT_UNCATEGORIZED') + ` (${counts.uncat})`, ft_getUncategorizedColor(), 0);
                } else {
                    // フィルタ時は「そのタグ + 子孫タグ」の合蚈件数を蚈算しお衚瀺する
                    // (絞り蟌み機胜がサブツリヌ怜玢であるため、件数も合わせるのが自然)
                    let subTreeCount = 0;
                    if (ft_state && ft_state.tags) {
                        ft_state.tags.forEach(t => {
                            if (ft_isTagInSubtree(t.id, entry.tag.id)) {
                                subTreeCount += (counts[t.id] || 0);
                            }
                        });
                    }
                    // 合蚈件数を衚瀺
                    addItem(entry.tag.id, (entry.tag.name || '') + ` (${subTreeCount})`, entry.tag.color, entry.depth);
                }
            }
            wrapper.appendChild(header); wrapper.appendChild(tagList);
            return wrapper;
        }

        function ft_openFilterDropdown(buttonEl, targetValue, onSelectCallback) {
            ft_closeTagDropdown();
            const dropdown = ft_buildFilterDropdownContent(targetValue, onSelectCallback);
            ft_currentDropdown = dropdown;
            document.body.appendChild(dropdown);
            const rect = buttonEl.getBoundingClientRect();
            const margin = 8;
            const width = dropdown.offsetWidth || 240;
            const height = dropdown.offsetHeight || 200;
            let left = rect.left;
            if (left + width + margin > window.innerWidth) left = window.innerWidth - width - margin;
            if (left < margin) left = margin;
            let top = rect.bottom + 4;
            if (top + height + margin > window.innerHeight) top = rect.top - height - 4;
            if (top < margin) top = margin;
            dropdown.style.left = `${left}px`; dropdown.style.top = `${top}px`;
        }

        // ------------- 蚭定モヌダル ------------- //

        function ft_closeSettingsModal() {
            if (ft_settingsModalBackdrop) { ft_settingsModalBackdrop.remove(); ft_settingsModalBackdrop = null; }
            ft_dragSrcEntry = null;
        }

        function ft_openSettingsModal() {
            ft_closeSettingsModal();
            const backdrop = document.createElement('div'); backdrop.className = 'ft-modal-backdrop';
            const modal = document.createElement('div'); modal.className = 'ft-modal';
            const header = document.createElement('div'); header.className = 'ft-modal-header';
            const title = document.createElement('div'); title.className = 'ft-modal-title'; title.textContent = i18n.t('FT_SETTINGS_TITLE');
            header.appendChild(title);
            const body = document.createElement('div'); body.className = 'ft-modal-body';
            const displaySection = document.createElement('div'); displaySection.className = 'ft-modal-display-settings';
            const displayTitle = document.createElement('div'); displayTitle.textContent = i18n.t('FT_SETTINGS_DISPLAY_SECTION_TITLE');
            const modeRow = document.createElement('div'); modeRow.className = 'ft-modal-display-settings-row';
            const modeLabel = document.createElement('span'); modeLabel.textContent = i18n.t('FT_SETTINGS_DISPLAY_MODE_LABEL');
            const radioLeafLabel = document.createElement('label'); radioLeafLabel.className = 'ft-modal-display-radio';
            const radioLeaf = document.createElement('input'); radioLeaf.type = 'radio'; radioLeaf.name = 'ft-display-mode'; radioLeaf.value = 'leaf'; radioLeaf.checked = ft_state.display.mode === 'leaf';
            const radioLeafText = document.createElement('span'); radioLeafText.textContent = i18n.t('FT_SETTINGS_DISPLAY_MODE_LEAF');
            radioLeafLabel.appendChild(radioLeaf); radioLeafLabel.appendChild(radioLeafText);
            const radioFullLabel = document.createElement('label'); radioFullLabel.className = 'ft-modal-display-radio';
            const radioFull = document.createElement('input'); radioFull.type = 'radio'; radioFull.name = 'ft-display-mode'; radioFull.value = 'full'; radioFull.checked = ft_state.display.mode === 'full';
            const radioFullText = document.createElement('span'); radioFullText.textContent = i18n.t('FT_SETTINGS_DISPLAY_MODE_FULL');
            radioFullLabel.appendChild(radioFull); radioFullLabel.appendChild(radioFullText);
            modeRow.appendChild(modeLabel); modeRow.appendChild(radioLeafLabel); modeRow.appendChild(radioFullLabel);
            radioLeaf.addEventListener('change', () => { if (radioLeaf.checked) { ft_state.display.mode = 'leaf'; ft_saveState(); } });
            radioFull.addEventListener('change', () => { if (radioFull.checked) { ft_state.display.mode = 'full'; ft_saveState(); } });
            displaySection.appendChild(displayTitle); displaySection.appendChild(modeRow);
            const tagListEl = document.createElement('div'); tagListEl.className = 'ft-modal-tag-list';

            function clearDropClasses() {
                tagListEl.querySelectorAll('.ft-modal-tag-item').forEach(el => el.classList.remove('ft-modal-tag-item-drop-before', 'ft-modal-tag-item-drop-after', 'ft-modal-tag-item-drop-child'));
            }
            function deleteTagAndReparentChildren(tag) {
                ft_state.tags.forEach(child => { if (child.parentId === tag.id) child.parentId = tag.parentId || null; });
                ft_state.tags = ft_state.tags.filter(t => t.id !== tag.id);
                Object.keys(ft_state.tweetTags).forEach(tid => { if (ft_state.tweetTags[tid] === tag.id) delete ft_state.tweetTags[tid]; });
                ft_normalizeTagOrders(); ft_clampUncategorizedOrder(); ft_saveState();
            }
            function moveTagInSiblings(tag, direction) {
                const siblings = ft_state.tags.filter(t => (t.parentId || null) === (tag.parentId || null)).sort((a, b) => a.order - b.order);
                const idx = siblings.findIndex(t => t.id === tag.id);
                if (idx < 0 || idx + direction < 0 || idx + direction >= siblings.length) return;
                const other = siblings[idx + direction];
                [tag.order, other.order] = [other.order, tag.order];
                ft_normalizeTagOrders(); ft_saveState();
            }
            function moveTagAsChild(srcTag, targetTag) {
                if (!srcTag || !targetTag || ft_wouldCreateCycle(targetTag.id, srcTag.id)) return;
                srcTag.parentId = targetTag.id;
                const children = ft_state.tags.filter(t => (t.parentId || null) === targetTag.id);
                srcTag.order = (children.length ? Math.max(...children.map(t => t.order)) : -1) + 1;
                ft_normalizeTagOrders(); ft_saveState();
            }
            function moveTagBefore(srcTag, targetTag) {
                if (!srcTag || !targetTag || ft_wouldCreateCycle(targetTag.parentId, srcTag.id)) return;
                srcTag.parentId = targetTag.parentId; srcTag.order = targetTag.order - 0.5; ft_normalizeTagOrders(); ft_saveState();
            }
            function moveTagAfter(srcTag, targetTag) {
                if (!srcTag || !targetTag || ft_wouldCreateCycle(targetTag.parentId, srcTag.id)) return;
                srcTag.parentId = targetTag.parentId; srcTag.order = targetTag.order + 0.5; ft_normalizeTagOrders(); ft_saveState();
            }
            function moveTagToRootRelativeToUncat(srcTag, mode) {
                if (!srcTag) return;
                srcTag.parentId = null;
                let uncatPos = ft_state.uncategorized.order;
                const insertIndex = mode === 'before' ? uncatPos : uncatPos + 1;
                const rootTags = ft_state.tags.filter(t => !t.parentId && t.id !== srcTag.id).sort((a, b) => a.order - b.order);
                rootTags.splice(insertIndex, 0, srcTag);
                rootTags.forEach((t, i) => t.order = i);
                ft_normalizeTagOrders(); ft_clampUncategorizedOrder(); ft_saveState();
            }
            function getDropTargetInfoFromY(y) {
                const items = Array.from(tagListEl.querySelectorAll('.ft-modal-tag-item'));
                if (!items.length) return null;
                const rects = items.map(row => row.getBoundingClientRect());
                const boundaries = [rects[0].top];
                for (let i = 1; i < items.length; i++) boundaries.push((rects[i - 1].bottom + rects[i].top) / 2);
                boundaries.push(rects[items.length - 1].bottom);
                let idx = 0; let min = Infinity;
                for (let i = 0; i < boundaries.length; i++) { const d = Math.abs(y - boundaries[i]); if (d < min) { min = d; idx = i; } }
                if (idx === 0) return { row: items[0], mode: 'before' };
                if (idx === items.length) return { row: items[items.length - 1], mode: 'after' };
                return { row: items[idx], mode: 'before' };
            }
            function rebuildTagList() {
                tagListEl.innerHTML = '';
                const entries = ft_getTagListWithUncategorized();
                if (entries.length === 1 && entries[0].kind === 'uncat') {
                    const empty = document.createElement('div'); empty.style.opacity = '0.7'; empty.style.fontSize = '12px'; empty.textContent = i18n.t('FT_SETTINGS_EMPTY_TAG_LIST'); tagListEl.appendChild(empty);
                }
                const rootCount = ft_countRootTags(); ft_clampUncategorizedOrder();
                entries.forEach(entry => {
                    const row = document.createElement('div'); row.className = 'ft-modal-tag-item'; row.dataset.kind = entry.kind;
                    const mainCell = document.createElement('div'); mainCell.className = 'ft-modal-tag-main'; if (entry.depth > 0) mainCell.style.paddingLeft = `${entry.depth * 16}px`;
                    const nameInput = document.createElement('input'); nameInput.className = 'ft-modal-tag-name'; nameInput.type = 'text';
                    const colorInput = document.createElement('input'); colorInput.className = 'ft-modal-tag-color'; colorInput.type = 'color';
                    const orderDiv = document.createElement('div');
                    const upBtn = document.createElement('button'); upBtn.className = 'ft-modal-tag-order'; upBtn.textContent = i18n.t('FT_SETTINGS_UP'); upBtn.type='button';
                    const downBtn = document.createElement('button'); downBtn.className = 'ft-modal-tag-order'; downBtn.textContent = i18n.t('FT_SETTINGS_DOWN'); downBtn.type='button';
                    orderDiv.appendChild(upBtn); orderDiv.appendChild(downBtn);
                    const delBtn = document.createElement('button'); delBtn.className = 'ft-modal-tag-delete'; delBtn.textContent = i18n.t('FT_SETTINGS_DELETE_BUTTON'); delBtn.type='button';
                    mainCell.appendChild(colorInput); mainCell.appendChild(nameInput);
                    const dragHandle = document.createElement('div'); dragHandle.className = 'ft-modal-tag-drag-handle'; dragHandle.innerHTML = '≡';

                    if (entry.kind === 'uncat') {
                        row.draggable = false; dragHandle.draggable = false; dragHandle.title = i18n.t('FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP');
                        nameInput.value = i18n.t('FT_SETTINGS_UNCATEGORIZED_NAME'); nameInput.readOnly = true; nameInput.title = i18n.t('FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP');
                        colorInput.value = ft_getUncategorizedColor();
                        colorInput.addEventListener('change', () => { ft_state.uncategorized.color = colorInput.value; ft_saveState(); });
                        delBtn.disabled = true;
                        upBtn.disabled = ft_state.uncategorized.order <= 0;
                        downBtn.disabled = ft_state.uncategorized.order >= rootCount;
                        upBtn.addEventListener('click', () => { ft_state.uncategorized.order--; ft_saveState(); rebuildTagList(); });
                        downBtn.addEventListener('click', () => { ft_state.uncategorized.order++; ft_saveState(); rebuildTagList(); });
                    } else {
                        const tag = entry.tag; row.dataset.tagId = tag.id;
                        dragHandle.draggable = true;
                        nameInput.value = tag.name; colorInput.value = tag.color || '#1d9bf0';
                        nameInput.addEventListener('change', () => { if(nameInput.value.trim()) { tag.name = nameInput.value.trim(); ft_saveState(); rebuildTagList(); } });
                        colorInput.addEventListener('change', () => { tag.color = colorInput.value; ft_saveState(); });
                        delBtn.addEventListener('click', () => { if(confirm(i18n.t('FT_CONFIRM_DELETE_TAG_MSG').replace('{tagName}', tag.name))) { deleteTagAndReparentChildren(tag); rebuildTagList(); } });
                        const siblings = ft_state.tags.filter(t => (t.parentId || null) === (tag.parentId || null)).sort((a, b) => a.order - b.order);
                        const idx = siblings.findIndex(t => t.id === tag.id);
                        upBtn.disabled = idx <= 0; downBtn.disabled = idx >= siblings.length - 1;
                        upBtn.addEventListener('click', () => { moveTagInSiblings(tag, -1); rebuildTagList(); });
                        downBtn.addEventListener('click', () => { moveTagInSiblings(tag, 1); rebuildTagList(); });
                        dragHandle.addEventListener('dragstart', (ev) => { ev.stopPropagation(); ft_dragSrcEntry = { kind: 'tag', tagId: tag.id }; row.classList.add('ft-modal-tag-item-dragging'); ev.dataTransfer.setData('text/plain', tag.id); ev.dataTransfer.effectAllowed='move'; });
                        dragHandle.addEventListener('dragend', (ev) => { ev.stopPropagation(); ft_dragSrcEntry = null; row.classList.remove('ft-modal-tag-item-dragging'); clearDropClasses(); });
                        row.addEventListener('dragover', (ev) => {
                            if (!ft_dragSrcEntry) return;
                            const rect = row.getBoundingClientRect(); const ratio = (ev.clientY - rect.top) / rect.height;
                            clearDropClasses();
                            if (ratio > 0.3 && ratio < 0.7) { ev.preventDefault(); row.classList.add('ft-modal-tag-item-drop-child'); }
                        });
                        row.addEventListener('drop', (ev) => {
                            if (!ft_dragSrcEntry) return;
                            const rect = row.getBoundingClientRect(); const ratio = (ev.clientY - rect.top) / rect.height;
                            if (ratio > 0.3 && ratio < 0.7) { ev.preventDefault(); ev.stopPropagation(); moveTagAsChild(ft_getTagById(ft_dragSrcEntry.tagId), tag); rebuildTagList(); }
                        });
                    }
                    row.appendChild(mainCell); row.appendChild(dragHandle); row.appendChild(orderDiv); row.appendChild(delBtn); tagListEl.appendChild(row);
                });
            }
            tagListEl.ondragover = (ev) => {
                if (!ft_dragSrcEntry) return; if (ev.defaultPrevented) return;
                ev.preventDefault(); clearDropClasses();
                const info = getDropTargetInfoFromY(ev.clientY);
                if (info) info.row.classList.add(info.mode === 'before' ? 'ft-modal-tag-item-drop-before' : 'ft-modal-tag-item-drop-after');
            };
            tagListEl.ondrop = (ev) => {
                if (!ft_dragSrcEntry) return; if (ev.defaultPrevented) return;
                ev.preventDefault(); clearDropClasses();
                const info = getDropTargetInfoFromY(ev.clientY);
                if (!info) return;
                const srcTag = ft_getTagById(ft_dragSrcEntry.tagId);
                if (info.row.dataset.kind === 'tag') {
                   const tTag = ft_getTagById(info.row.dataset.tagId);
                   if (info.mode === 'before') moveTagBefore(srcTag, tTag); else moveTagAfter(srcTag, tTag);
                } else {
                   moveTagToRootRelativeToUncat(srcTag, info.mode);
                }
                rebuildTagList();
            };
            rebuildTagList();
            const newTagSection = document.createElement('div'); newTagSection.className = 'ft-modal-new-tag';
            const newTagLabel = document.createElement('div'); newTagLabel.textContent = i18n.t('FT_DROPDOWN_NEW_TAG');
            const newTagRow = document.createElement('div'); newTagRow.className = 'ft-modal-new-tag-row';
            const newNameInput = document.createElement('input'); newNameInput.className = 'ft-modal-tag-name'; newNameInput.type = 'text'; newNameInput.placeholder = i18n.t('FT_DROPDOWN_NEW_TAG_PLACEHOLDER');
            const newColorInput = document.createElement('input'); newColorInput.className = 'ft-modal-tag-color'; newColorInput.type = 'color'; newColorInput.value = '#1d9bf0';
            const newAddBtn = document.createElement('button'); newAddBtn.className = 'ft-modal-button'; newAddBtn.textContent = i18n.t('FT_DROPDOWN_NEW_TAG_ADD'); newAddBtn.type='button';
            function doAddNew() {
                const name = newNameInput.value.trim();
                if (!name) return;
                ft_createNewTag(name, newColorInput.value, null); ft_saveState();
                newNameInput.value = ''; rebuildTagList();
            }
            newAddBtn.addEventListener('click', doAddNew);
            newNameInput.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') { ev.preventDefault(); doAddNew(); } });
            newTagRow.appendChild(newColorInput); newTagRow.appendChild(newNameInput); newTagRow.appendChild(newAddBtn);
            newTagSection.appendChild(newTagLabel); newTagSection.appendChild(newTagRow);
            body.appendChild(displaySection); body.appendChild(tagListEl); body.appendChild(newTagSection);
            const footer = document.createElement('div'); footer.className = 'ft-modal-footer';
            const closeBtn = document.createElement('button'); closeBtn.className = 'ft-modal-button'; closeBtn.textContent = i18n.t('FT_SETTINGS_CLOSE'); closeBtn.type='button';
            closeBtn.addEventListener('click', ft_closeSettingsModal);
            footer.appendChild(closeBtn);
            modal.appendChild(header); modal.appendChild(body); modal.appendChild(footer);
            backdrop.appendChild(modal);
            backdrop.addEventListener('mousedown', (ev) => { if (ev.target === backdrop) ft_closeSettingsModal(); });
            ft_settingsModalBackdrop = backdrop;
            document.body.appendChild(backdrop);
        }

        // ------------- むベント委譲 & 堅牢初期化 (Optimized) ------------- //

        function ft_installGlobalListeners() {
            // Document level Delegation
            document.addEventListener('click', (ev) => {
                const target = ev.target;
                if (!(target instanceof Element)) return;

                // 1. Tag Chip Click
                const chipBtn = target.closest('.ft-tag-chip');
                if (chipBtn) {
                    ev.stopPropagation(); ev.preventDefault();
                    const tweetId = chipBtn.dataset.tweetId;
                    if (tweetId && ft_state.enabled) {
                        ft_openTagDropdown(chipBtn, tweetId);
                    }
                    return;
                }

                // 3. Close Dropdown on outside click
                if (ft_currentDropdown && !ft_currentDropdown.contains(target)) {
                    ft_closeTagDropdown();
                }

            }, true); // Use capture for better delegation reliability

            document.addEventListener('keydown', (ev) => {
                if (ev.key === 'Escape') { ft_closeTagDropdown(); ft_closeSettingsModal(); }
            });
        }

        function ft_startRobustPolling() {
            let count = 0;
            const maxChecks = 10;
            const intervalId = setInterval(() => {
                count++;
                processNewTweets(true);
                if (count >= maxChecks) clearInterval(intervalId);
            }, 500);
        }

        // ------------- ツむヌト凊理 (単䞀芁玠) ------------- //

        function ft_processTweetArticle(article) {
            const tweetId = article.dataset.ftTweetId || ft_extractTweetId(article);
            if (!tweetId) return;
            article.dataset.ftTweetId = tweetId;

            if (!ft_state.enabled) {
                 ft_removeTagChipFromArticle(article);
                 return;
            }

            // ネむティブブックマヌクや単なるタグ有無のチェックを削陀
            // 「お気に入り枈み (isFav)」の堎合のみタグチップを衚瀺する
            const isFav = (typeof isFavorited === 'function') && isFavorited(tweetId);

            if (isFav) {
                ft_attachTagChipToArticle(article, tweetId);
            } else {
                ft_removeTagChipFromArticle(article);
            }
        }

        // ------------- 初期化 ------------- //

        function ft_init() {
            if (ft_initialized) return;
            ft_initialized = true;
            ft_state = ft_loadState();
            ft_installGlobalListeners();
            processNewTweets(true);
            ft_startRobustPolling();
        }

        /* --- End Favorite Tags: Code Block --- */

        const LANG_OVERRIDE_KEY = 'advUILang_v1';
        // Settings で指定された UI 蚀語があれば、怜出結果より優先しお適甚
        try {
            const overrideLang = kv.get(LANG_OVERRIDE_KEY, '');
            if (overrideLang && i18n.translations[overrideLang]) {
                i18n.lang = overrideLang;
            }
        } catch (_) {}

        const trigger = document.createElement('button');
        const HISTORY_SORT_KEY = 'advHistorySort_v1';
        trigger.id = 'advanced-search-trigger';
        trigger.type = 'button';
        trigger.innerHTML = SEARCH_SVG;
        trigger.classList.add('adv-trigger-search');
        trigger.setAttribute('aria-label', i18n.t('tooltipTrigger'));
        trigger.setAttribute('aria-haspopup', 'dialog');
        trigger.setAttribute('aria-expanded', 'false');
        document.body.appendChild(trigger);

        const modalContainer = document.createElement('div');
        modalContainer.innerHTML = modalHTML;
        document.body.appendChild(modalContainer);
        i18n.apply(modalContainer);

        const modal = document.getElementById('advanced-search-modal');
        const form = document.getElementById('advanced-search-form');
        const closeButton = modal.querySelector('.adv-modal-close');
        const clearButton = document.getElementById('adv-clear-button');
        const applyButton = document.getElementById('adv-apply-button');
        const saveButton = document.getElementById('adv-save-button');
        const footerEl = modal.querySelector('.adv-modal-footer');
        const toastEl = document.getElementById('adv-toast');
        const secretBtn = document.getElementById('adv-secret-btn');
        const secretStateEl = document.getElementById('adv-secret-state');

        const settingsModal = document.getElementById('adv-settings-modal');
        const settingsLangSel = document.getElementById('adv-settings-lang');
        const settingsFileInput = document.getElementById('adv-settings-file-input');
        const settingsOpenBtn = document.getElementById('adv-settings-button');
        const settingsCloseBtn = document.getElementById('adv-settings-close');
        const settingsCloseFooterBtn = document.getElementById('adv-settings-close-footer');
        const settingsExportBtn = document.getElementById('adv-settings-export');
        const settingsImportBtn = document.getElementById('adv-settings-import');
        const settingsResetBtn = document.getElementById('adv-settings-reset');

        const historyClearAllBtn = document.getElementById('adv-history-clear-all');
        historyClearAllBtn.textContent = i18n.t('historyClearAll');

        const accountScopeSel = document.getElementById('adv-account-scope');
        const locationScopeSel = document.getElementById('adv-location-scope');

        ['n','e','s','w','ne','nw','se','sw'].forEach(dir => {
            const h = document.createElement('div');
            h.className = `adv-resizer ${dir}`;
            h.dataset.dir = dir;
            modal.appendChild(h);
        });

        const EXC_NAME_KEY   = 'advExcludeHitName_v1';
        const EXC_HANDLE_KEY = 'advExcludeHitHandle_v1';
        const EXC_REPOSTS_KEY = 'advExcludeReposts_v1';
        const EXC_HASHTAGS_KEY = 'advExcludeTimelineHashtags_v1';
        const excNameEl   = document.getElementById('adv-exclude-hit-name');
        const excHandleEl = document.getElementById('adv-exclude-hit-handle');
        const excRepostsEl = document.getElementById('adv-filter-reposts-exclude');
        const excHashtagsEl = document.getElementById('adv-filter-hashtags-exclude');
        const loadExcludeFlags = () => ({
            name: kv.get(EXC_NAME_KEY, '1') === '1',
            handle: kv.get(EXC_HANDLE_KEY, '1') === '1',
            reposts: kv.get(EXC_REPOSTS_KEY, '0') === '1',
            hashtags: kv.get(EXC_HASHTAGS_KEY, '0') === '1',
        });
        const saveExcludeFlags = (v) => {
            kv.set(EXC_NAME_KEY, v.name ? '1':'0');
            kv.set(EXC_HANDLE_KEY, v.handle ? '1':'0');
            kv.set(EXC_REPOSTS_KEY, v.reposts ? '1':'0');
            kv.set(EXC_HASHTAGS_KEY, v.hashtags ? '1':'0');
        };
{
            const st = loadExcludeFlags();
            if (excNameEl) excNameEl.checked = st.name;
            if (excHandleEl) excHandleEl.checked = st.handle;
            if (excRepostsEl) excRepostsEl.checked = st.reposts;
            if (excHashtagsEl) excHashtagsEl.checked = st.hashtags;
        }
        [excNameEl, excHandleEl, excRepostsEl, excHashtagsEl].forEach(el=>{
            if (!el) return;
            el.addEventListener('change', ()=>{
                saveExcludeFlags({
                    name: excNameEl?.checked ?? false,
                    handle: excHandleEl?.checked ?? false,
                    reposts: excRepostsEl?.checked ?? false,
                    hashtags: excHashtagsEl?.checked ?? false,
                });
                rescanAllTweetsForFilter();
            });
        });

        themeManager.observeChanges(modal, trigger);

        // Accounts/Listsタブの背景をドロップタヌゲットにするためのヘルパヌ
        const setupBackgroundDrop = (panel, host, unassignFunction) => {
            const feedbackClass = 'adv-bg-drop-active';
            const SECT_MIME = 'adv/folder'; // フォルダ䞊び替えD&DのMIME

            // panel 内の .adv-zoom-root もむベントの察象に远加
            const zoomRoot = panel?.querySelector('.adv-zoom-root');
            const eventTargets = [panel, host, zoomRoot].filter(Boolean); // むベントをリッスンする察象

            // 砎線を衚瀺する察象は panel のみずする
            const feedbackTargets = [panel].filter(Boolean); // 砎線を衚瀺する察象

            const onDragEnter = (ev) => {
                // アむテムtext/plainであり、セクションadv/folderではない
                if (ev.dataTransfer.types && !ev.dataTransfer.types.includes(SECT_MIME) && ev.dataTransfer.types.includes('text/plain')) {
                    // タヌゲットが panel, host, zoomRoot のいずれか
                    if (eventTargets.includes(ev.target)) {
                        // 砎線は feedbackTargets に付ける (今回は panel のみ)
                        feedbackTargets.forEach(t => t.classList.add(feedbackClass));
                    }
                }
            };

            const onDragLeave = (ev) => {
                // タヌゲット自身から離れた時だけフィヌドバックを消す
                if (eventTargets.includes(ev.target)) {
                    // 砎線は feedbackTargets から消す
                    feedbackTargets.forEach(t => t.classList.remove(feedbackClass));
                }
            };

            const onDragOver = (ev) => {
                // dropむベントを発火させるために、dragoverでpreventDefaultが必芁
                // アむテムであり、タヌゲットが panel/host/zoomRoot 自身の堎合のみ蚱可
                if (eventTargets.includes(ev.target) && ev.dataTransfer.types && !ev.dataTransfer.types.includes(SECT_MIME) && ev.dataTransfer.types.includes('text/plain')) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    // 砎線は feedbackTargets に付け続ける
                    feedbackTargets.forEach(t => t.classList.add(feedbackClass));
                } else {
                    // 子芁玠フォルダなどの䞊に来たら背景ハむラむトは消す
                    feedbackTargets.forEach(t => t.classList.remove(feedbackClass));
                    // 残っおいるフォルダヌ芋出しの砎線を確実に解陀
                    document.querySelectorAll('.adv-folder-header[data-drop="1"]').forEach(el => { delete el.dataset.drop; });
                }
            };

            const onDrop = (ev) => {
                feedbackTargets.forEach(t => t.classList.remove(feedbackClass)); // ドロップ時は垞にハむラむト解陀

                // 最終チェックアむテムであり、パネル/ホスト/zoomRoot 自身ぞのドロップ
                if (eventTargets.includes(ev.target) && ev.dataTransfer.types && !ev.dataTransfer.types.includes(SECT_MIME) && ev.dataTransfer.types.includes('text/plain')) {
                    ev.preventDefault();
                    ev.stopPropagation();

                    const draggedId = ev.dataTransfer.getData('text/plain');
                    if (draggedId) {
                        unassignFunction(draggedId); // (unassignAccount たたは unassignList を実行)
                    }
                }
            };

            // むベントは eventTargets に登録する
            eventTargets.forEach(target => {
                if (!target) return; // hostがただ存圚しない堎合など
                target.addEventListener('dragenter', onDragEnter);
                target.addEventListener('dragleave', onDragLeave);
                target.addEventListener('dragover', onDragOver);
                target.addEventListener('drop', onDrop);
            });
        };

        // --- generic unassign helper (de-duplicate) ---
        // Remove an item from all folders under FOLDERS_KEY,
        // then move the item to the top of the master list (Unassigned head).
        function unassignItemGeneric({ FOLDERS_KEY, loadItems, saveItems, itemId }) {
            // 1) remove from every folder
            const folders = loadFolders(FOLDERS_KEY, '');
            let changed = false;
            for (const f of folders) {
                const before = f.order.length;
                f.order = f.order.filter(id => id !== itemId);
                if (f.order.length !== before) { f.ts = Date.now(); changed = true; }
            }
            if (changed) saveFolders(FOLDERS_KEY, folders);

            // 2) bump the item to the head of the master list (Unassigned first)
            const all = loadItems();
            const hit = all.find(x => x.id === itemId);
            if (hit) {
                const next = [hit, ...all.filter(x => x.id !== itemId)];
                saveItems(next);
            }
        }

        // --- generic "move item to a folder" helper ---
        function moveItemToFolderGeneric({ FOLDERS_KEY, itemId, folderId }) {
            const fArr = loadFolders(FOLDERS_KEY, '');
            // remove from every folder
            for (const f of fArr) {
                const before = f.order.length;
                f.order = f.order.filter(id => id !== itemId);
                if (f.order.length !== before) f.ts = Date.now();
            }
            // add to head of the target folder
            const target = fArr.find(f => f.id === folderId);
            if (target) {
                target.order = [itemId, ...target.order.filter(id => id !== itemId)];
                target.ts = Date.now();
            }
            saveFolders(FOLDERS_KEY, fArr);
        }

        // === [ADD] 特化 move 関数トヌスト再描画たで含む ===
        function moveAccountToFolder(accountId, folderId) {
          moveItemToFolderGeneric({
            FOLDERS_KEY: ACCOUNTS_FOLDERS_KEY,
            itemId: accountId,
            folderId
          });
          showToast(i18n.t('toastReordered'));
          try { renderAccounts(); } catch(_) {}
        }

        function moveSavedToFolder(savedId, folderId) {
          moveItemToFolderGeneric({
            FOLDERS_KEY: SAVED_FOLDERS_KEY,
            itemId: savedId,
            folderId
          });
          showToast(i18n.t('toastReordered'));
          try { renderSaved(); } catch(_) {}
        }

        function moveListToFolder(listId, targetFolderId) {
          moveItemToFolderGeneric({
            FOLDERS_KEY: LISTS_FOLDERS_KEY,
            itemId: listId,
            folderId: targetFolderId
          });
          showToast(i18n.t('toastReordered'));
          try { renderLists(); } catch(_) {}
        }

        // 未分類化ロゞックを共通化 (Account甹)
        const unassignAccount = (draggedId) => {
            unassignItemGeneric({
                FOLDERS_KEY: ACCOUNTS_FOLDERS_KEY,
                loadItems: loadAccounts,
                saveItems: saveAccounts,
                itemId: draggedId,
            });
            showToast(i18n.t('toastReordered'));
            renderAccounts();
        };

        // 未分類化ロゞックを共通化 (List甹)
        const unassignList = (draggedId) => {
            unassignItemGeneric({
                FOLDERS_KEY: LISTS_FOLDERS_KEY,
                loadItems: loadLists,
                saveItems: saveLists,
                itemId: draggedId,
            });
            showToast(i18n.t('toastReordered'));
            renderLists();
        };

        // 未分類化ロゞックを共通化 (Saved甹)
        const unassignSaved = (draggedId) => {
            unassignItemGeneric({
                FOLDERS_KEY: SAVED_FOLDERS_KEY,
                loadItems: () => migrateList(loadJSON(SAVED_KEY, [])),
                saveItems: (arr) => saveJSON(SAVED_KEY, migrateList(arr)),
                itemId: draggedId,
            });
            showToast(i18n.t('toastReordered'));
            renderSaved();
        };

        /* ========= Favorites Logic ========= */
        const FAV_KEY = 'advFavorites_v1';
        const FAV_SORT_KEY = 'advFavoritesSort_v1';

        // 高速化: メモリキャッシュ倉数を定矩
        let _favCache = null; // 配列デヌタ (レンダリング甚)
        let _favSet = null;   // ID怜玢甚 Set (O(1)刀定甚)

        // デヌタ構造: { id: tweetId, text, user: {name, handle, avatar}, media: [], ts, ... }

        // キャッシュがあればそれを返す。なければロヌドしおキャッシュ構築。
        const loadFavorites = () => {
            if (_favCache) return _favCache;

            const raw = loadJSON(FAV_KEY, []);
            _favCache = raw;
            _favSet = new Set(raw.map(x => x.id));

            return _favCache;
        };

        // 保存時にキャッシュずSetも同時に曎新する
        const saveFavorites = (arr) => {
            _favCache = arr;
            _favSet = new Set(arr.map(x => x.id));
            saveJSON(FAV_KEY, arr);
        };

        const toggleFavorite = (tweetMeta) => {
            // loadFavoritesはキャッシュを返すので高速
            const list = loadFavorites();
            const idx = list.findIndex(x => x.id === tweetMeta.id);

            if (idx >= 0) {
                // --- 削陀 (Remove) ---
                // 配列を盎接倉曎せず、新しい配列を䜜っお敎合性を保぀のがベストだが
                // ここでは元のロゞックに合わせお砎壊的倉曎をしおから saveFavorites で党䜓曎新する
                list.splice(idx, 1);
                saveFavorites(list); // ここで _favSet も曎新される

                // 解陀時はタグデヌタも削陀する
                if (ft_state && ft_state.tweetTags && ft_state.tweetTags[tweetMeta.id]) {
                    delete ft_state.tweetTags[tweetMeta.id];
                    ft_saveState();
                }

                renderFavorites(); // (お気に入りタブが開いおいる堎合甚)
                showToast(i18n.t('toastUnfavorited'));
            } else {
                list.unshift({ ...tweetMeta, ts: Date.now() });
                saveFavorites(list); // ここで _favSet も曎新される
                renderFavorites();
                showToast(i18n.t('toastFavorited'));
            }

            // 最埌に党同期
            updateAllFavoriteButtons();          // ボタン曎新
            refreshTagChipsForTweet(tweetMeta.id); // タグチップ曎新

            return idx < 0; // 远加されたら true
        };

        // Setを䜿った超高速刀定 (JSON.parseが発生しない)
        const isFavorited = (tweetId) => {
            if (!_favSet) loadFavorites(); // 初回ロヌドがただなら実行
            return _favSet.has(tweetId);
        };

        const deleteFavorite = (id) => {
            // 1. お気に入りリストから削陀
            const list = loadFavorites().filter(x => x.id !== id);
            saveFavorites(list); // ここで _favSet も曎新される

            // 2. タグデヌタも削陀する (デヌタのクリヌンアップ)
            if (ft_state && ft_state.tweetTags && ft_state.tweetTags[id]) {
                delete ft_state.tweetTags[id];
                ft_saveState(); // 状態を保存
            }

            // 3. UI曎新 (リスト再描画 & トヌスト)
            renderFavorites();
            showToast(i18n.t('toastDeleted'));

            // 4. タむムラむン䞊の芋た目を同期
            updateAllFavoriteButtons();   // ボタンの色を曎新
            refreshTagChipsForTweet(id);  // タグチップを消去
        };

        // お気に入りリストのむベント委譲ハンドラ
        function setupFavoritesDelegation() {
            const listEl = document.getElementById('adv-favorites-list');
            // ただ芁玠がない、たたは既に登録枈みならスキップ
            if (!listEl || listEl._delegationAttached) return;
            listEl._delegationAttached = true;

            listEl.addEventListener('click', (e) => {
                const target = e.target;
                if (!target) return;

                // A. 削陀ボタン
                const deleteBtn = target.closest('[data-action="delete"]');
                if (deleteBtn) {
                    const row = deleteBtn.closest('.adv-item');
                    if (row && row.dataset.id) {
                        e.stopPropagation();
                        deleteFavorite(row.dataset.id);
                    }
                    return;
                }

                // B. Openボタン
                const openBtn = target.closest('[data-action="open"]');
                if (openBtn) {
                    const row = openBtn.closest('.adv-item');
                    if (row && row.dataset.id) {
                        e.stopPropagation();
                        // IDから最新デヌタを匕くクロヌゞャがないため
                        const item = loadFavorites().find(x => x.id === row.dataset.id);
                        if (item) {
                            const url = `/${item.user.handle}/status/${item.id}`;
                            spaNavigate(url, { ctrlMeta: e.ctrlKey || e.metaKey });
                            if (window.innerWidth <= 700) closeModal();
                        }
                    }
                    return;
                }

                // C. ナヌザヌリンク
                const userLink = target.closest('.adv-link-user');
                if (userLink) {
                    e.preventDefault();
                    e.stopPropagation();
                    const href = userLink.getAttribute('href');
                    if (href) {
                        spaNavigate(href, { ctrlMeta: e.ctrlKey || e.metaKey });
                        if (window.innerWidth <= 700) closeModal();
                    }
                    return;
                }

                // D. メディアサムネむル
                const mediaImg = target.closest('.adv-media-thumb');
                if (mediaImg) {
                    e.stopPropagation();
                    const row = mediaImg.closest('.adv-item');
                    if (!row) return;
                    // 最新デヌタの再取埗
                    const item = loadFavorites().find(x => x.id === row.dataset.id);
                    if (!item) return;

                    const type = mediaImg.dataset.type;
                    const index = mediaImg.dataset.index;
                    const isQuote = mediaImg.dataset.isQuote === '1';

                    // 匕甚でIDがない堎合(取埗䞍胜)は無芖
                    if (isQuote && item.quote && !item.quote.id) return;

                    let targetBaseUrl = `/${item.user.handle}/status/${item.id}`;
                    if (isQuote && item.quote && item.quote.id) {
                        targetBaseUrl = `/${item.quote.user.handle}/status/${item.quote.id}`;
                    }

                    let targetPath = targetBaseUrl;
                    if (type === 'image') {
                        targetPath = `${targetBaseUrl}/photo/${index}`;
                    }

                    spaNavigate(targetPath, { ctrlMeta: e.ctrlKey || e.metaKey });
                    if (window.innerWidth <= 700) closeModal();
                    return;
                }

                // E. 本文䞭のリンク (.adv-content-link)
                const contentLink = target.closest('.adv-content-link');
                if (contentLink) {
                     e.stopPropagation();
                     // target="_blank" ならブラりザデフォルト動䜜ぞ
                     return;
                }
            });
        }

        // 行レンダリング
        // addEventListener を党削陀し、玔粋なDOM生成のみにする
        function renderFavoriteRow(item) {
            const row = document.createElement('div');
            row.className = 'adv-item';
            // お気に入りタブだけはボタンが絶察配眮なので、右䜙癜を個別に確保する
            row.style.paddingRight = '60px';
            row.dataset.id = item.id;

            const text = item.text || '';
            const bodyHtml = safeLinkify(text);
            const displayTime = item.postedAt ? fmtTime(item.postedAt) : fmtTime(item.ts);

            // --- メディアHTML生成 ---
            const buildMediaHtml = (mediaList, isQuote = false) => {
                if (!mediaList || mediaList.length === 0) return '';
                let html = '<div class="adv-item-media-row">';
                mediaList.forEach((m, i) => {
                    const mediaType = m.type || 'image';
                    const isVideo = mediaType === 'video';
                    const playIcon = isVideo
                        ? `<div class="adv-media-play-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z"></path></svg></div>`
                        : '';
                    // 匕甚でIDがない堎合、クリックできない芖芚衚珟(cursor:default)をHTML生成時点で適甚
                    let styleAttr = '';
                    if (isQuote && item.quote && !item.quote.id) {
                         styleAttr = 'style="cursor:default"';
                    }

                    html += `<div class="adv-media-wrap">
                                <img src="${escapeAttr(m.url)}"
                                     data-type="${mediaType}"
                                     data-index="${i + 1}"
                                     data-is-quote="${isQuote ? '1' : '0'}"
                                     class="adv-media-thumb" loading="lazy" alt="Media" title="Open Media"
                                     ${styleAttr}>
                                ${playIcon}
                             </div>`;
                });
                html += '</div>';
                return html;
            };
            const mainMediaHtml = buildMediaHtml(item.media, false);

            // --- 匕甚HTML ---
            let quoteHtml = '';
            if (item.quote) {
                const q = item.quote;
                const qUserUrl = `/${escapeAttr(q.user.handle)}`;
                const qMediaHtml = buildMediaHtml(q.media, true);
                const qBodyHtml = safeLinkify(q.text);
                quoteHtml = `
                    <div class="adv-quote-box">
                        <div class="adv-quote-header">
                            ${q.user.avatar ? `<a class="adv-link adv-link-user" href="${qUserUrl}"><img src="${escapeAttr(q.user.avatar)}" class="adv-quote-avatar"></a>` : ''}
                            <a class="adv-link adv-link-user" href="${qUserUrl}" title="Quote User">
                                <span class="adv-quote-name">${escapeHTML(q.user.name)}</span>
                                <span class="adv-quote-handle">@${escapeHTML(q.user.handle)}</span>
                            </a>
                        </div>
                        <div class="adv-quote-text">${qBodyHtml}</div>
                        ${qMediaHtml}
                    </div>
                `;
            }

            const userUrl = `/${escapeAttr(item.user.handle)}`;

            row.innerHTML = `
                ${item.user.avatar
                    ? `<a class="adv-item-avatar-link adv-link adv-link-user" href="${userUrl}">
                         <img class="adv-item-avatar" src="${escapeAttr(item.user.avatar)}">
                       </a>`
                    : `<a class="adv-item-avatar-link adv-link adv-link-user" href="${userUrl}">
                         <div class="adv-item-avatar"></div>
                       </a>`
                }

                <div class="adv-item-main">
                    <div class="adv-item-title">
                        <a class="adv-link adv-link-user" href="${userUrl}" title="Open Profile">${escapeHTML(item.user.name)} <span style="font-weight:normal;color:var(--modal-text-secondary)">@${escapeHTML(item.user.handle)}</span></a>
                        <span class="adv-fav-tag-container"></span>
                    </div>
                    <div class="adv-item-body-text">${bodyHtml}</div>
                    ${mainMediaHtml}
                    ${quoteHtml}
                    <div class="adv-item-sub">
                        <span>${displayTime}</span>
                    </div>
                </div>

                <button class="adv-chip primary adv-fav-btn-pos adv-fav-btn-top" data-action="open">${i18n.t('buttonOpen')}</button>
                <button class="adv-chip danger adv-fav-btn-pos adv-fav-btn-bottom" data-action="delete">${i18n.t('delete')}</button>
            `;

            // タグチップの生成ず挿入
            const tagContainer = row.querySelector('.adv-fav-tag-container');
            if (tagContainer && typeof ft_buildTagChip === 'function') {
                const chip = ft_buildTagChip(item.id);
                // 泚蚘: ここは ft_installGlobalListeners で委譲されおいるため、個別の addEventListener は䞍芁です
                // もし ft_buildTagChip 内でむベントを付けおいる堎合はそのたた機胜したす
                tagContainer.appendChild(chip);
            }

            return row;
        }

        const advFavoritesListEl = document.getElementById('adv-favorites-list');

        // お気に入りタブ専甚の珟圚の絞り蟌み状態メモリ保持
        let favFilterTagId = 'ALL'; // 'ALL', 'UNCAT', or tagId
        let favSearchQuery = '';

        function renderFavorites() {
            const listEl = document.getElementById('adv-favorites-list');
            const emptyEl = document.getElementById('adv-favorites-empty');
            if (!listEl) return;

            // 1. ツヌルバヌの生成ただ無ければ
            if (!listEl.previousElementSibling?.classList?.contains('adv-folder-toolbar')) {
                const bar = document.createElement('div');
                bar.className = 'adv-folder-toolbar';
                // タグ絞り蟌みボタン、゜ヌト遞択、怜玢ボックス
                bar.innerHTML = `
                    <div style="display:flex; gap:6px; align-items:center; flex:0 0 auto;">
                        <button id="adv-favorites-tag-filter-btn" class="ft-filter-button" type="button">
                            <span class="ft-filter-button-label"></span>
                            <span class="ft-filter-button-caret">â–Ÿ</span>
                        </button>
                        <select id="adv-favorites-sort" class="adv-select" style="max-width:140px; font-size:12px;">
                            <option value="saved_newest" data-i18n="sortSavedNewest"></option>
                            <option value="saved_oldest" data-i18n="sortSavedOldest"></option>
                            <option value="posted_newest" data-i18n="sortPostedNewest"></option>
                            <option value="posted_oldest" data-i18n="sortPostedOldest"></option>
                        </select>
                    </div>
                    <input id="adv-favorites-search" class="adv-input" type="text" placeholder="${i18n.t('placeholderSearchSaved')}" style="flex:1; min-width:80px;">
                `;

                // 翻蚳適甚動的生成のためここで適甚
                bar.querySelectorAll('[data-i18n]').forEach(el => { el.textContent = i18n.t(el.dataset.i18n); });

                listEl.parentElement.insertBefore(bar, listEl);

                // むベントリスナヌ登録
                const btn = bar.querySelector('#adv-favorites-tag-filter-btn');
                const sortSel = bar.querySelector('#adv-favorites-sort');
                const inp = bar.querySelector('#adv-favorites-search');

                // A. タグフィルタ
                btn.addEventListener('click', (ev) => {
                    ev.stopPropagation();
                    ev.preventDefault();
                    ft_openFilterDropdown(btn, favFilterTagId, (val) => {
                        favFilterTagId = val;
                        renderFavorites();
                    });
                });

                // B. ゜ヌト倉曎
                sortSel.value = kv.get(FAV_SORT_KEY, 'saved_newest');
                sortSel.addEventListener('change', () => {
                    kv.set(FAV_SORT_KEY, sortSel.value);
                    renderFavorites();
                });

                // C. 怜玢
                inp.addEventListener('input', debounce(() => {
                    favSearchQuery = inp.value;
                    renderFavorites();
                }, 200));
            }

            // 2. ツヌルバヌの状態曎新ラベル蚭定など
            const btn = document.getElementById('adv-favorites-tag-filter-btn');
            const labelSpan = btn ? btn.querySelector('.ft-filter-button-label') : null;
            const inp = document.getElementById('adv-favorites-search');
            const sortSel = document.getElementById('adv-favorites-sort');

            if (inp) inp.placeholder = i18n.t('placeholderSearchSaved');
            if (labelSpan) {
                let labelText = i18n.t('FT_FILTER_ALL');
                if (favFilterTagId === FT_FILTER_UNCATEGORIZED) {
                    labelText = i18n.t('FT_UNCATEGORIZED');
                } else if (favFilterTagId !== 'ALL') {
                    const tag = ft_getTagById(favFilterTagId);
                    if (tag) {
                        labelText = ft_getTagDisplayLabelFromTag(tag) || tag.name;
                    } else {
                        favFilterTagId = 'ALL';
                    }
                }
                labelSpan.textContent = labelText;
            }
            if (inp && inp.value !== favSearchQuery) inp.value = favSearchQuery;

            // ゜ヌト蚭定の読み蟌みUIず同期
            const currentSort = kv.get(FAV_SORT_KEY, 'saved_newest');
            if (sortSel && sortSel.value !== currentSort) sortSel.value = currentSort;

            // 3. デヌタのロヌドずフィルタリング
            const allItems = loadFavorites(); // { id, text, user, postedAt, ts, ... }

            let filtered = allItems.filter(item => {
                // A. テキスト怜玢
                const q = favSearchQuery.trim().toLowerCase(); // 怜玢時に初めお正芏化する
                if (q) {
                    const targetText = (item.text + ' ' + item.user.name + ' ' + item.user.handle).toLowerCase();
                    if (!targetText.includes(q)) return false;
                }
                // B. タグフィルタ
                if (favFilterTagId === 'ALL') return true;

                // ft_state および ft_state.tweetTags の存圚確認を行う
                const itemTagId = (ft_state && ft_state.tweetTags) ? ft_state.tweetTags[item.id] : null;

                if (favFilterTagId === FT_FILTER_UNCATEGORIZED) return !itemTagId;
                return itemTagId ? ft_isTagInSubtree(itemTagId, favFilterTagId) : false;
            });

            // 4. ゜ヌト適甚
            // ts: 远加日時, postedAt: 投皿日時
            // postedAt が無い叀いデヌタは ts をフォヌルバックずしお䜿う
            filtered.sort((a, b) => {
                const tsA = a.ts || 0;
                const tsB = b.ts || 0;
                const postedA = a.postedAt || tsA; // fallback
                const postedB = b.postedAt || tsB; // fallback

                switch (currentSort) {
                    case 'saved_oldest':  return tsA - tsB;
                    case 'posted_newest': return postedB - postedA;
                    case 'posted_oldest': return postedA - postedB;
                    case 'saved_newest':
                    default:              return tsB - tsA;
                }
            });

            // 5. リスト描画
            listEl.innerHTML = '';

            // 党デヌタ(allItems)が空の時だけメッセヌゞを出す。
            // 怜玢やフィルタでヒットしなかっただけなら、メッセヌゞは出さずに空欄にする。
            if (allItems.length === 0) {
                emptyEl.textContent = i18n.t('emptyFavorites');
                emptyEl.style.display = 'block';
            } else {
                emptyEl.style.display = 'none';
                filtered.forEach(item => {
                    const row = renderFavoriteRow(item);
                    listEl.appendChild(row);
                });
            }
        }

        /* タブごず保存に察応 */
        const ZOOM_KEYS = {
          search:  'advZoom_tab_search_v1',
          history: 'advZoom_tab_history_v1',
          saved:   'advZoom_tab_saved_v1',
          favorites: 'advZoom_tab_favorites_v1',
          lists:   'advZoom_tab_lists_v1',
          accounts:'advZoom_tab_accounts_v1',
          mute:    'advZoom_tab_mute_v1',
        };
        const ZOOM_MIN = 0.5, ZOOM_MAX = 2.0, ZOOM_STEP = 0.1;

        /* 各タブの珟圚倀メモリキャッシュ */
        const zoomByTab = {
          search:  1.0,
          history: 1.0,
          saved:   1.0,
          lists:   1.0,
          accounts:1.0,
          mute:    1.0,
        };

        const getActiveTabName = () => {
          const btn = document.querySelector('.adv-tab-btn.active');
          return btn?.dataset?.tab || 'search';
        };
        const getActiveZoomRoot = () =>
          document.querySelector('.adv-tab-content.active .adv-zoom-root') ||
          document.getElementById('adv-zoom-root');

        const clampZoom = z => Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Math.round(z*100)/100));

        const loadZoomFor = (tab) => {
          try {
            const k = ZOOM_KEYS[tab] || ZOOM_KEYS.search;
            // デフォルト倀を '1' から分岐させる
            const defaultZoom = (tab === 'search') ? '0.87' : '1'; // 怜玢タブのみ 0.87 に
            const v = parseFloat(kv.get(k, defaultZoom)); // '1' だった郚分を defaultZoom に倉曎
            if (!Number.isNaN(v)) zoomByTab[tab] = clampZoom(v);
          } catch {}
        };
        const saveZoomFor = (tab) => {
          try {
            const k = ZOOM_KEYS[tab] || ZOOM_KEYS.search;
            kv.set(k, String(zoomByTab[tab]));
          } catch {}
        };

        /* 初期ロヌド党タブ */
        Object.keys(zoomByTab).forEach(loadZoomFor);

        const applyZoom = () => {
          const tab = getActiveTabName();
          const el = getActiveZoomRoot();
          if (!el) return;
          const z = zoomByTab[tab] ?? 1.0;

          el.style.zoom = '';
          el.style.transform = '';
          el.style.width = '';

          if ('zoom' in el.style) {
            el.style.zoom = z;
          } else {
            el.style.transform = `scale(${z})`;
            el.style.width = `${(100 / z).toFixed(3)}%`;
          }
        };

        const setZoomActiveTab = (z) => {
          const tab = getActiveTabName();
          zoomByTab[tab] = clampZoom(z);
          applyZoom();
          saveZoomFor(tab);
        };

        /* タブ芋出しは拡倧しない.adv-zoom-rootの内偎だけ反応 */
        const onWheelZoom = (e) => {
          const isAccel = e.ctrlKey || e.metaKey;
          if (!isAccel) return;
          if (!e.target.closest('.adv-zoom-root')) return; // タブバヌ等は陀倖
          e.preventDefault();
          const tab = getActiveTabName();
          const cur = zoomByTab[tab] ?? 1.0;
          const factor = e.deltaY > 0 ? (1 - ZOOM_STEP) : (1 + ZOOM_STEP);
          setZoomActiveTab(cur * factor);
        };
        const onKeyZoom = (e) => {
          const accel = (e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey;
          if (!accel) return;
          if (!e.target.closest('.adv-zoom-root')) return; // タブバヌ等は陀倖
          const k = e.key;
          const tab = getActiveTabName();
          const cur = zoomByTab[tab] ?? 1.0;
          if (k === '+' || k === '=') { e.preventDefault(); setZoomActiveTab(cur + ZOOM_STEP); }
          else if (k === '-' || k === '_') { e.preventDefault(); setZoomActiveTab(cur - ZOOM_STEP); }
          else if (k === '0') { e.preventDefault(); setZoomActiveTab(1.0); }
        };

        /* 初回適甚衚瀺時に再適甚 */
        requestAnimationFrame(applyZoom);
        modal.addEventListener('wheel', onWheelZoom, { passive:false });
        modal.addEventListener('keydown', onKeyZoom);
        const modalDisplayObserver = new MutationObserver(() => {
          if (modal.style.display === 'flex') applyZoom();
        });
        modalDisplayObserver.observe(modal, { attributes:true, attributeFilter:['style'] });

        /* タブ切替時にもズヌム再適甚 */

        const searchInputSelectors = [
            'div[data-testid="primaryColumn"] input[data-testid="SearchBox_Search_Input"]', // 怜玢ペヌゞ
            'div[data-testid="sidebarColumn"] input[data-testid="SearchBox_Search_Input"]', // サむドバヌ
            'input[aria-label="Search query"]', // 暙準英語
            'input[aria-label="怜玢ク゚リ"]' // 暙準日本語
        ];

        // â–Œ 関数名を getActiveSearchInputs (耇数圢) に倉曎
        const getActiveSearchInputs = () => {
            const inputs = new Set(); // 重耇排陀

            // 1. 暙準の怜玢窓を探す
            for (const selector of searchInputSelectors) {
                const input = document.querySelector(selector);
                if (input && input.offsetParent !== null) {
                    inputs.add(input);
                }
            }
            // フォヌルバックdata-testid のみ
            document.querySelectorAll('input[data-testid="SearchBox_Search_Input"]').forEach(input => {
                if (input && input.offsetParent !== null) {
                    inputs.add(input);
                }
            });

            return Array.from(inputs); // Set を配列にしお返す
        };

        // React controlled input を確実に同期させる共通関数
        const syncControlledInput = (el, nextVal) => {
          try {
            const proto = Object.getPrototypeOf(el) || HTMLInputElement.prototype;
            const desc = Object.getOwnPropertyDescriptor(proto, 'value');
            if (desc && desc.set) {
              desc.set.call(el, nextVal); // React の setter を叩く
            } else {
              el.value = nextVal;
            }
            el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
          } catch {
            try { el.value = nextVal; el.dispatchEvent(new Event('input', { bubbles: true })); } catch {}
          }
        };

        const MODAL_STATE_KEY   = 'advSearchModalState_v3.2';
        const TRIGGER_STATE_KEY = 'advSearchTriggerState_v1.0';
        const HISTORY_KEY = 'advSearchHistory_v2';
        const SAVED_KEY   = 'advSearchSaved_v2';
        const SECRET_KEY  = 'advSearchSecretMode_v1';

        const MUTE_KEY = 'advMutedWords_v1';
        const migrateMuted = (list) =>
          Array.isArray(list)
            ? list
                .map(it => ({
                  id: it.id || uid(),
                  word: (it.word||'').trim(),
                  cs: !!it.cs,
                  enabled: it.enabled !== false,
                  ts: it.ts || Date.now()
                }))
                .filter(it => it.word)
            : [];
        const loadMuted = () => migrateMuted(loadJSON(MUTE_KEY, []));
        const saveMuted = (arr) => saveJSON(MUTE_KEY, migrateMuted(arr));

        const addMuted = (word, cs=false) => {
          const w = (word||'').trim();
          if (!w) return;
          const list = loadMuted();
          if (list.some(it => it.word === w && !!it.cs === !!cs)) return;
          list.unshift({ id: uid(), word: w, cs: !!cs, enabled: true, ts: Date.now() });
          saveMuted(list);
          renderMuted();
          rescanAllTweetsForFilter();
        };

        const deleteMuted = (id) => {
          const list = loadMuted().filter(it => it.id !== id);
          saveMuted(list);
          renderMuted();
          rescanAllTweetsForFilter();
        };

        const toggleMutedCS = (id) => {
          const list = loadMuted().map(it => it.id === id ? { ...it, cs: !it.cs, ts: Date.now() } : it);
          saveMuted(list);
          renderMuted();
          rescanAllTweetsForFilter();
        };

        const toggleMutedEnabled = (id) => {
          const list = loadMuted().map(it => it.id === id ? { ...it, enabled: !it.enabled, ts: Date.now() } : it);
          saveMuted(list);
          renderMuted();
          rescanAllTweetsForFilter();
        };

        const SETTINGS_EXPORT_VERSION = 2;
        function buildSettingsExportJSON() {
          // タブごずのズヌム
          const zoom = {};
          try {
            for (const [tab, key] of Object.entries(ZOOM_KEYS)) {
              zoom[tab] = kv.get(key, '1');
            }
          } catch (_) {}

          const safeParse = (key, def) => {
            try { return JSON.parse(kv.get(key, JSON.stringify(def))); } catch (_) { return def; }
          };

          const data = {
            // アプリ識別子
            appName: 'AdvancedSearchForX',

            v: SETTINGS_EXPORT_VERSION,

            // 蚀語・陀倖蚭定・ミュヌト
            lang: kv.get(LANG_OVERRIDE_KEY, ''),
            excludeFlags: loadExcludeFlags(),
            muteMaster: loadMuteMaster(),
            muted: loadMuted(),

            // 怜玢履歎・保存枈み怜玢
            history: loadJSON(HISTORY_KEY, []),
            saved: loadJSON(SAVED_KEY, []),
            favorites: loadJSON(FAV_KEY, []),

            // シヌクレットモヌド・履歎゜ヌト
            secret: kv.get(SECRET_KEY, '0') === '1',
            historySort: kv.get(HISTORY_SORT_KEY, 'newest'),

            // タブ状態
            tabs: {
              last: kv.get(LAST_TAB_KEY, 'search'),
              order: loadJSON(TABS_ORDER_KEY, []),
              visibility: loadTabsVisibility(),
            },

            // モヌダルトリガヌ䜍眮・サむズ
            modalState: safeParse(MODAL_STATE_KEY, null),
            triggerState: safeParse(TRIGGER_STATE_KEY, null),

            // ズヌム
            zoom,

            // アカりント・リスト・各フォルダ
            accounts: (typeof loadAccounts === 'function') ? loadAccounts() : [],
            lists: (typeof loadLists === 'function') ? loadLists() : [],
            folders: {
              accounts: (typeof loadFolders === 'function' && typeof ACCOUNTS_FOLDERS_KEY !== 'undefined')
                ? loadFolders(ACCOUNTS_FOLDERS_KEY, '')
                : [],
              lists: (typeof loadFolders === 'function' && typeof LISTS_FOLDERS_KEY !== 'undefined')
                ? loadFolders(LISTS_FOLDERS_KEY, '')
                : [],
              saved: (typeof loadFolders === 'function' && typeof SAVED_FOLDERS_KEY !== 'undefined')
                ? loadFolders(SAVED_FOLDERS_KEY, i18n.t('defaultSavedFolders'))
                : [],
            },

            // Unassigned の挿入䜍眮
            unassignedIndex: {
              saved: parseInt(kv.get('advSavedUnassignedIndex_v1', '0'), 10) || 0,
              accounts: parseInt(kv.get('advAccountsUnassignedIndex_v1', '0'), 10) || 0,
              lists: parseInt(kv.get('advListsUnassignedIndex_v1', '0'), 10) || 0,
            },
            /* --- Favorite Tags Data --- */
            favoriteTags: (typeof ft_loadState === 'function') ? ft_loadState() : ft_createDefaultState(),
          };

          return JSON.stringify(data, null, 2);
        }

        function applySettingsImportJSON(text) {
            let data;
            try {
                data = JSON.parse(text);
            } catch (_) {
                alert(i18n.t('alertInvalidJSON'));
                return false;
            }
            if (!data || typeof data !== 'object') {
                alert(i18n.t('alertInvalidData'));
                return false;
            }

            // バリデヌションロゞック
            // 1. アプリ識別子 (appName) があるかチェック
            const hasSignature = (data.appName === 'AdvancedSearchForX');

            // 2. 識別子がない堎合、このアプリ特有の構造vプロパティ + 䞻芁な配列のいずれかを持っおいるかチェック埌方互換性救枈
            const hasValidStructure = (
                typeof data.v === 'number' && 
                (Array.isArray(data.history) || Array.isArray(data.saved) || Array.isArray(data.favorites) || typeof data.tabs === 'object')
            );

            if (!hasSignature && !hasValidStructure) {
                alert(i18n.t('alertInvalidApp'));
                return false;
            }
            // バリデヌション終了

            // --- 基本蚭定v1/v2 共通 ---
            if (data.lang !== undefined) {
                try { kv.set(LANG_OVERRIDE_KEY, data.lang || ''); } catch (_) {}
            }

            if (data.excludeFlags) {
                saveExcludeFlags({
                    name: !!data.excludeFlags.name,
                    handle: !!data.excludeFlags.handle,
                    reposts: !!data.excludeFlags.reposts,
                    hashtags: !!data.excludeFlags.hashtags,
                });
            }

            if (Array.isArray(data.muted)) {
                saveMuted(data.muted);
            }

            if (typeof data.muteMaster === 'boolean') {
                saveMuteMaster(data.muteMaster);
            }

            // --- v2 以降で远加された保存デヌタ ---
            if (Array.isArray(data.history)) {
                saveJSON(HISTORY_KEY, data.history);
            }
            if (Array.isArray(data.saved)) {
                saveJSON(SAVED_KEY, data.saved);
            }

            // saveFavorites を経由させおキャッシュ(_favSet)も曎新する
            if (Array.isArray(data.favorites)) {
                saveFavorites(data.favorites);
            }

            if (typeof data.secret === 'boolean') {
                try { kv.set(SECRET_KEY, data.secret ? '1' : '0'); } catch (_) {}
            }
            if (data.historySort) {
                try { kv.set(HISTORY_SORT_KEY, data.historySort); } catch (_) {}
            }
            if (data.tabs && typeof data.tabs === 'object') {
                if (data.tabs.last) {
                    try { kv.set(LAST_TAB_KEY, data.tabs.last); } catch (_) {}
                }
                if (Array.isArray(data.tabs.order)) {
                    saveJSON(TABS_ORDER_KEY, data.tabs.order);
                }
                if (data.tabs.visibility && typeof data.tabs.visibility === 'object') {
                    saveTabsVisibility(data.tabs.visibility);
                }
            }
            if (data.modalState) {
                try { kv.set(MODAL_STATE_KEY, JSON.stringify(data.modalState)); } catch (_) {}
            }
            if (data.triggerState) {
                try { kv.set(TRIGGER_STATE_KEY, JSON.stringify(data.triggerState)); } catch (_) {}
            }
            if (data.zoom && typeof data.zoom === 'object') {
                try {
                    for (const [tab, key] of Object.entries(ZOOM_KEYS)) {
                        if (data.zoom[tab] != null) {
                            kv.set(key, String(data.zoom[tab]));
                        }
                    }
                } catch (_) {}
            }

            if (Array.isArray(data.accounts) && typeof saveAccounts === 'function') {
                try { saveAccounts(data.accounts); } catch (_) {}
            }
            if (Array.isArray(data.lists) && typeof saveLists === 'function') {
                try { saveLists(data.lists); } catch (_) {}
            }

            if (data.folders && typeof data.folders === 'object') {
                if (Array.isArray(data.folders.accounts) && typeof ACCOUNTS_FOLDERS_KEY !== 'undefined') {
                    try { saveFolders(ACCOUNTS_FOLDERS_KEY, data.folders.accounts); } catch (_) {}
                }
                if (Array.isArray(data.folders.lists) && typeof LISTS_FOLDERS_KEY !== 'undefined') {
                    try { saveFolders(LISTS_FOLDERS_KEY, data.folders.lists); } catch (_) {}
                }
                if (Array.isArray(data.folders.saved) && typeof SAVED_FOLDERS_KEY !== 'undefined') {
                    try { saveFolders(SAVED_FOLDERS_KEY, data.folders.saved); } catch (_) {}
                }
            }

            if (data.unassignedIndex && typeof data.unassignedIndex === 'object') {
                if ('saved' in data.unassignedIndex) try { kv.set('advSavedUnassignedIndex_v1', String(data.unassignedIndex.saved | 0)); } catch (_) {}
                if ('accounts' in data.unassignedIndex) try { kv.set('advAccountsUnassignedIndex_v1', String(data.unassignedIndex.accounts | 0)); } catch (_) {}
                if ('lists' in data.unassignedIndex) try { kv.set('advListsUnassignedIndex_v1', String(data.unassignedIndex.lists | 0)); } catch (_) {}
            }

            /* --- Favorite Tags Data --- */
            if (data.favoriteTags && typeof ft_saveState === 'function') {
                try {
                    const s = data.favoriteTags;
                    ft_normalizeTagOrdersFor(s);
                    ft_clampUncategorizedOrderFor(s);
                    ft_saveState(s); // ストレヌゞぞの保存

                    if (typeof ft_state !== 'undefined') {
                        ft_state = s;
                    }
                } catch (_) {}
            }

            // 蚀語を再適甚
            try {
                const override = kv.get(LANG_OVERRIDE_KEY, '');
                if (override && i18n.translations[override]) {
                    i18n.lang = override;
                } else if (!override) {
                    i18n.init();
                }
            } catch (_) {}

            try {
                i18n.apply(document.getElementById('advanced-search-modal'));
                i18n.apply(document.getElementById('adv-settings-modal'));
            } catch (_) {}

            try { applySecretBtn(); } catch (_) {}
            try { renderHistory(); } catch (_) {}
            try { renderSaved(); } catch (_) {}
            try { renderLists(); } catch (_) {}
            try { renderAccounts(); } catch (_) {}
            try { renderMuted(); } catch (_) {}

            // お気に入りリストを再描画し、ボタン状態・タグチップを党曎新する
            try {
                renderFavorites();
                updateAllFavoriteButtons();
            } catch (_) {}

            try { rescanAllTweetsForFilter(); } catch (_) {}

            /* --- Favorite Tags UI Refresh --- */
            try {
                if (typeof ft_refreshAllTagChips === 'function') ft_refreshAllTagChips();
            } catch (_) {}

            // タブの衚瀺状態を適甚
            try { applyTabsVisibility(); } catch (_) {}

            showToast(i18n.t('toastImported'));
            return true;
        }

        // マスタヌON/OFF党䜓の適甚を止めるだけ。各゚ントリの enabled は保持
        const MUTE_MASTER_KEY = 'advMuteMasterEnabled_v1';
        const LAST_TAB_KEY = 'advSearchLastTab_v1';
        const TABS_ORDER_KEY = 'advTabsOrder_v1';
        const TABS_VISIBILITY_KEY = 'advTabsVisibility_v1'; // ★ 新芏远加
        const loadMuteMaster = () => { try { return kv.get(MUTE_MASTER_KEY, '1') === '1'; } catch(_) { return true; } };
        const saveMuteMaster = (on) => { try { kv.set(MUTE_MASTER_KEY, on ? '1' : '0'); } catch(_) {} };

        const tabButtons = Array.from(document.querySelectorAll('.adv-tab-btn'));

        // Get tab panels for background drop
        const tabAccountsPanel = document.getElementById('adv-tab-accounts');
        const tabListsPanel = document.getElementById('adv-tab-lists');
        const tabSavedPanel    = document.getElementById('adv-tab-saved');

        const activateTab = (name) => {
            let targetName = name;
            const visibility = loadTabsVisibility();

            // タヌゲットが非衚瀺に蚭定されおいるかチェック
            if (visibility[targetName] === false) {
                // 非衚瀺の堎合、フォヌルバック先を探す
                // 'search' は true が保蚌されおいるので、必ず 'search' にフォヌルバックされる
                const orderedButtons = Array.from(document.querySelectorAll('.adv-tab-btn'));
                const firstVisible = orderedButtons.find(btn => {
                    const tab = btn.dataset.tab;
                    return tab && visibility[tab] !== false;
                });
                targetName = firstVisible ? firstVisible.dataset.tab : 'search';
            }

            // tabButtons は DOM の順序ず同期しおいる必芁があるため、DOM から再取埗
            const currentTabButtons = Array.from(document.querySelectorAll('.adv-tab-btn'));
            currentTabButtons.forEach(b => b.classList.toggle('active', b.dataset.tab === targetName));

            // [tabSearch, tabHistory, tabSaved, tabLists, tabAccounts, tabMute] // 叀い配列参照を削陀
            document.querySelectorAll('.adv-tab-content').forEach(el => {
              el.classList.toggle('active', el.id === `adv-tab-${targetName}`);
            });


            footerEl.style.display = (targetName === 'search') ? '' : 'none';
            // 最埌に開いたタブを保存 (非衚瀺でも芁求されたタブを保存する)
            try {
                kv.set(LAST_TAB_KEY, name); // ★ 元の name を保存する
            } catch(e) {
                console.error('Failed to save last tab state:', e);
            }
            if (targetName === 'history') renderHistory();
            if (targetName === 'saved') renderSaved();
            if (targetName === 'lists') renderLists();
            if (targetName === 'accounts') renderAccounts();
            if (targetName === 'mute') renderMuted();
            if (targetName === 'favorites') renderFavorites();
            if (targetName === 'search') updateSaveButtonState();

            /* タブ切替ごずに該圓タブのズヌム率を反映 */
            applyZoom();
        };

        const applyTabsVisibility = () => {
            const visibility = loadTabsVisibility();
            // tabButtons は DOM の順序を反映しおいる必芁があるため、DOM から再取埗
            const currentTabButtons = Array.from(document.querySelectorAll('.adv-tab-btn'));
            let firstVisibleTab = 'search'; // フォヌルバック (searchはtrue固定なので)

            for (const btn of currentTabButtons) {
                const tabName = btn.dataset.tab;
                if (!tabName) continue;

                // visibility[tabName] が false の堎合のみ非衚瀺 (true や undefined は衚瀺)
                const isVisible = visibility[tabName] !== false;
                btn.style.display = isVisible ? '' : 'none';

                // フォヌルバック先タブを決定 (search が最優先)
                if (isVisible && firstVisibleTab === 'search' && tabName !== 'search') {
                    firstVisibleTab = tabName; // search 以倖で最初に芋぀かった衚瀺可胜なタブ
                }
            }

            // 'search' が衚瀺可胜か確認 (true 固定だが念のため)
            if (visibility['search'] === true) {
                firstVisibleTab = 'search';
            }

            // 最埌にアクティブだったタブが非衚瀺になっおいないかチェック
            const activeBtn = document.querySelector('.adv-tab-btn.active');
            if (activeBtn && activeBtn.style.display === 'none') {
                // 非衚瀺にされたので、衚瀺可胜な最初のタブ (通垞は 'search') に切り替える
                activateTab(firstVisibleTab);
            }
        };

        // タブの順序を読み蟌んで適甚
        (function applyTabsOrder() {
          const tabsContainer = document.querySelector('.adv-tabs');
          if (!tabsContainer) return;

          // 珟圚のボタンを data-tab をキヌにした Map ずしお保持
          const currentButtons = new Map();
          const defaultOrder = [];
          tabsContainer.querySelectorAll('.adv-tab-btn[data-tab]').forEach(btn => {
              const tabName = btn.dataset.tab;
              if (tabName) {
                  currentButtons.set(tabName, btn);
                  defaultOrder.push(tabName);
              }
          });

          // 保存された順序を読み蟌む
          const savedOrder = loadJSON(TABS_ORDER_KEY, defaultOrder);

          // 保存された順序を怜蚌し、䞍足分を補う
          const finalOrder = [];
          const seen = new Set();
          // 1. 保存された順序のうち、珟圚も存圚するものを远加
          savedOrder.forEach(tabName => {
              if (currentButtons.has(tabName)) {
                  finalOrder.push(tabName);
                  seen.add(tabName);
              }
          });
          // 2. デフォルト順序のうち、ただ远加されおいないもの新しいタブを末尟に远加
          defaultOrder.forEach(tabName => {
              if (!seen.has(tabName)) {
                  finalOrder.push(tabName);
              }
          });

          // 順序が実際に倉曎されおいるか確認
          if (JSON.stringify(savedOrder) !== JSON.stringify(finalOrder)) {
              saveJSON(TABS_ORDER_KEY, finalOrder);
          }

          // DOMを䞊び替える
          finalOrder.forEach(tabName => {
              const btn = currentButtons.get(tabName);
              if (btn) {
                  tabsContainer.appendChild(btn);
              }
          });
          // tabButtons 配列も再取埗順序が倉曎されたため
            tabButtons.splice(0, tabButtons.length, ...Array.from(document.querySelectorAll('.adv-tab-btn')));
        })();

        // タブの衚瀺/非衚瀺をDOMに適甚 (activateTab の前に呌ぶ)
        applyTabsVisibility();

        const saveModalRelativeState = () => {
            if (modal.style.display === 'none') {
                try {
                    const current = (()=>{
                        try { return JSON.parse(kv.get(MODAL_STATE_KEY, '{}')); } catch(_) { return {}; }
                    })();
                    current.visible = false;
                    kv.set(MODAL_STATE_KEY, JSON.stringify(current));
                } catch(_) {}
                return;
            }
            const rect = modal.getBoundingClientRect();
            const winW = window.innerWidth, winH = window.innerHeight;
            const fromRight = winW - rect.right, fromBottom = winH - rect.bottom;
            const h_anchor = rect.left < fromRight ? 'left' : 'right';
            const h_value  = h_anchor === 'left' ? rect.left : fromRight;
            const v_anchor = rect.top  < fromBottom ? 'top'  : 'bottom';
            const v_value  = v_anchor === 'top' ? rect.top : fromBottom;
            const state = { h_anchor, h_value, v_anchor, v_value, visible: true,
                            w: Math.round(rect.width), h: Math.round(rect.height) };
            kv.set(MODAL_STATE_KEY, JSON.stringify(state));
        };
        const applyModalStoredPosition = () => {
            try {
                const s = JSON.parse(kv.get(MODAL_STATE_KEY, '{}'));
                const h_anchor = s.h_anchor || 'right';
                const h_value  = s.h_value ?? 20;
                const v_anchor = s.v_anchor || 'top';
                const v_value  = s.v_value ?? 80;
                modal.style.left = modal.style.right = modal.style.top = modal.style.bottom = 'auto';
                if (h_anchor === 'right') modal.style.right = `${h_value}px`; else modal.style.left = `${h_value}px`;
                if (v_anchor === 'bottom') modal.style.bottom = `${v_value}px`; else modal.style.top = `${v_value}px`;

                const minW = 300, minH = 240;
                if (s.w) modal.style.width  = `${Math.max(minW, Math.min(s.w, window.innerWidth  - 20))}px`;
                else     modal.style.width  = '450px';
                if (s.h) modal.style.height = `${Math.max(minH, Math.min(s.h, window.innerHeight - 20))}px`;
                else     modal.style.height = '';
            } catch(e) { console.error('Failed to apply modal position:', e); }
        };
        const keepModalInViewport = () => {
            if (modal.style.display === 'none') return;
            const rect = modal.getBoundingClientRect();
            const winW = window.innerWidth, winH = window.innerHeight, m = 10;

            const minW = 300, minH = 240;
            const maxW = Math.max(minW, winW - 2*m);
            const maxH = Math.max(minH, winH - 2*m);
            const w = Math.min(Math.max(rect.width,  minW), maxW);
            const h = Math.min(Math.max(rect.height, minH), maxH);
            if (Math.round(w) !== Math.round(rect.width))  modal.style.width  = `${w}px`;
            if (Math.round(h) !== Math.round(rect.height)) modal.style.height = `${h}px`;

            let x = rect.left, y = rect.top;
            if (x < m) x = m; if (y < m) y = m;
            if (x + w > winW - m) x = winW - w - m;
            if (y + h > winH - m) y = winH - h - m;
            if (Math.round(x) !== Math.round(rect.left) || Math.round(y) !== Math.round(rect.top)) {
                modal.style.left = `${x}px`; modal.style.top = `${y}px`;
                modal.style.right = 'auto'; modal.style.bottom = 'auto';
            }
        };
        const loadModalState = () => {
            try { applyModalStoredPosition(); } catch(e) {
                console.error('Failed to load modal state:', e);
                kv.del(MODAL_STATE_KEY);
            }
        };

        const saveTriggerRelativeState = () => {
            const rect = trigger.getBoundingClientRect();
            const winW = window.innerWidth, winH = window.innerHeight;
            const fromRight = winW - rect.right, fromBottom = winH - rect.bottom;
            const h_anchor = rect.left < fromRight ? 'left' : 'right';
            const h_value  = h_anchor === 'left' ? rect.left : fromRight;
            const v_anchor = rect.top  < fromBottom ? 'top'  : 'bottom';
            const v_value  = v_anchor === 'top' ? rect.top : fromBottom;
            const state = { h_anchor, h_value, v_anchor, v_value };
            kv.set(TRIGGER_STATE_KEY, JSON.stringify(state));
        };
        const applyTriggerStoredPosition = () => {
            try {
                const s = JSON.parse(kv.get(TRIGGER_STATE_KEY, '{}'));
                const h_anchor = s.h_anchor || 'right';
                const h_value  = s.h_value ?? 20;
                const v_anchor = s.v_anchor || 'top';
                const v_value  = s.v_value ?? 18;
                trigger.style.left = trigger.style.right = trigger.style.top = trigger.style.bottom = 'auto';
                if (h_anchor === 'right') trigger.style.right = `${h_value}px`; else trigger.style.left = `${h_value}px`;
                if (v_anchor === 'bottom') trigger.style.bottom = `${v_value}px`; else trigger.style.top = `${v_value}px`;
            } catch(e) { console.error('Failed to apply trigger position:', e); }
        };
        const keepTriggerInViewport = () => {
            const rect = trigger.getBoundingClientRect();
            const winW = window.innerWidth, winH = window.innerHeight, m = 6;
            let x = rect.left, y = rect.top;
            if (x < m) x = m; if (y < m) y = m;
            if (x + rect.width > winW - m) x = winW - rect.width - m;
            if (y + rect.height > winH - m) y = winH - rect.height - m;
            if (Math.round(x) !== Math.round(rect.left) || Math.round(y) !== Math.round(rect.top)) {
                trigger.style.left = `${x}px`; trigger.style.top = `${y}px`;
                trigger.style.right = 'auto'; trigger.style.bottom = 'auto';
                saveTriggerRelativeState();
            }
        };
        const setupTriggerDrag = () => {
            const DRAG_THRESHOLD = 4;
            let isPointerDown = false, isDragging = false, start = {x:0,y:0,left:0,top:0}, suppressClick=false;
            const onPointerDown = (e) => {
                if (e.button !== 0) return;
                isPointerDown = true; isDragging = false; suppressClick=false;
                const rect = trigger.getBoundingClientRect();
                start = { x:e.clientX, y:e.clientY, left:rect.left, top:rect.top };
                try{ trigger.setPointerCapture(e.pointerId);}catch(_){}
            };
            const onPointerMove = (e) => {
                if (!isPointerDown) return;
                const dx = e.clientX - start.x, dy = e.clientY - start.y;
                if (!isDragging) {
                    if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
                    isDragging = true;
                    trigger.style.right = 'auto'; trigger.style.bottom = 'auto';
                    trigger.style.left = `${start.left}px`; trigger.style.top = `${start.top}px`;
                    document.body.classList.add('adv-dragging');
                }
                const winW = window.innerWidth, winH = window.innerHeight;
                const w = trigger.offsetWidth, h = trigger.offsetHeight;
                let nx = start.left + dx, ny = start.top + dy;
                nx = Math.max(0, Math.min(nx, winW - w)); ny = Math.max(0, Math.min(ny, winH - h));
                trigger.style.left = `${nx}px`; trigger.style.top = `${ny}px`;
            };
            const onPointerUp = (e) => {
                if (!isPointerDown) return; isPointerDown = false;
                try{ trigger.releasePointerCapture(e.pointerId);}catch(_){}
                if (isDragging) {
                    isDragging = false; document.body.classList.remove('adv-dragging');
                    suppressClick = true; setTimeout(()=>{suppressClick=false;},150);
                    saveTriggerRelativeState();
                }
            };
            trigger.addEventListener('click', (e)=> {
                if (suppressClick) {
                    e.preventDefault();
                    e.stopPropagation();
                    suppressClick = false;
                    return;
                }
            }, true);
            trigger.addEventListener('pointerdown', onPointerDown);
            window.addEventListener('pointermove', onPointerMove);
            window.addEventListener('pointerup', onPointerUp);
            window.addEventListener('pointercancel', onPointerUp);
        };

        applyTriggerStoredPosition();
        requestAnimationFrame(keepTriggerInViewport);
        setupTriggerDrag();

        const readScopesFromControls = () => ({ pf: accountScopeSel.value === 'following', lf: locationScopeSel.value === 'nearby' });
        const applyScopesToControls = ({pf=false, lf=false}) => {
            accountScopeSel.value = pf ? 'following' : '';
            locationScopeSel.value = lf ? 'nearby' : '';
        };
        const readScopesFromURL = (urlStr) => {
            try {
                const u = new URL(urlStr || location.href, location.origin);
                const pf = (u.searchParams.get('pf') || '') === 'on';
                const lf = (u.searchParams.get('lf') || '') === 'on';
                return { pf, lf };
            } catch { return { pf:false, lf:false }; }
        };

        const STATE_SYNC = {
            parseFromSearchToModal: () => {
                if (isUpdating || modal.style.display === 'none') return;
                // â–Œ 耇数圢に倉曎し、最初の芁玠を取埗
                const inputs = getActiveSearchInputs();
                const si = inputs[0]; // 耇数のうち最初のを代衚ずしお読み蟌む
                parseQueryAndApplyToModal(si ? si.value : '');
                applyScopesToControls(readScopesFromURL());
                updateSaveButtonState();
            }
        };

        const buildQueryStringFromModal = () => {
            const q = [];
            const fields = {
                all: document.getElementById('adv-all-words').value.trim(),
                exact: document.getElementById('adv-exact-phrase').value.trim(),
                any: document.getElementById('adv-any-words').value.trim(),
                not: document.getElementById('adv-not-words').value.trim(),
                hash: document.getElementById('adv-hashtag').value.trim(),
                lang: document.getElementById('adv-lang').value,
                replies: document.getElementById('adv-replies').value,
                min_replies: document.getElementById('adv-min-replies').value,
                min_faves: document.getElementById('adv-min-faves').value,
                min_retweets: document.getElementById('adv-min-retweets').value,
                since: document.getElementById('adv-since').value,
                until: document.getElementById('adv-until').value,
            };
            if (fields.all) q.push(fields.all);
            if (fields.exact) q.push(`"${fields.exact.replace(/"/g,'')}"`);

            // 匕甚で 1 語ずしお扱い、OR 連結を生成
            if (fields.any) {
              const tokens = tokenizeQuotedWords(fields.any).map(t => {
                // 既に "
": そのたた。未匕甚で空癜を含む → 匕甚を付ける
                if (/^".*"$/.test(t)) return t;
                if (/\s/.test(t)) return `"${t.replace(/"/g,'')}"`;
                return t;
              });
              if (tokens.length) q.push(`(${tokens.join(' OR ')})`);
            }

            if (fields.not) q.push(...fields.not.split(/\s+/).filter(Boolean).map(w=>`-${w}`));
            if (fields.hash) q.push(...fields.hash.split(/\s+/).filter(Boolean).map(h=>`#${h.replace(/^#/,'')}`));
            if (fields.lang) q.push(`lang:${fields.lang}`);

            const createAccountQuery = (inputId, operator) => {
                const value = document.getElementById(inputId).value.trim();
                if (!value) return null;
                const isExclude = document.getElementById(`${inputId}-exclude`).checked;
                const terms = value.split(/\s+/).filter(Boolean);
                if (isExclude) return terms.map(t=>`-${operator}${t.replace(/^@/,'')}`).join(' ');
                const processed = terms.map(t=>`${operator}${t.replace(/^@/,'')}`);
                return processed.length>1 ? `(${processed.join(' OR ')})` : processed[0];
            };
            const fromQ = createAccountQuery('adv-from-user','from:'); if (fromQ) q.push(fromQ);
            const toQ = createAccountQuery('adv-to-user','to:'); if (toQ) q.push(toQ);
            const mentionQ = createAccountQuery('adv-mentioning','@'); if (mentionQ) q.push(mentionQ);

            if (fields.min_replies) q.push(`min_replies:${fields.min_replies}`);
            if (fields.min_faves) q.push(`min_faves:${fields.min_faves}`);
            if (fields.min_retweets) q.push(`min_retweets:${fields.min_retweets}`);
            if (fields.since) q.push(`since:${fields.since}`);
            if (fields.until) q.push(`until:${fields.until}`);

            const addFilter = (type, mapping) => {
                const include = document.getElementById(`adv-filter-${type}-include`).checked;
                const exclude = document.getElementById(`adv-filter-${type}-exclude`).checked;
                if (include) q.push(mapping);
                if (exclude) q.push(`-${mapping}`);
            };
            addFilter('verified','is:verified');
            addFilter('links','filter:links');
            addFilter('images','filter:images');
            addFilter('videos','filter:videos');

            if (fields.replies) {
                const replyMap = { include:'include:replies', only:'filter:replies', exclude:'-filter:replies' };
                if (replyMap[fields.replies]) q.push(replyMap[fields.replies]);
            }
            return q.join(' ');
        };

        const parseQueryAndApplyToModal = (query) => {
            if (isUpdating) return; isUpdating = true;
            const formEl = document.getElementById('advanced-search-form');
            formEl.reset();
            // フォヌムリセット時に disabled を解陀
            ['verified', 'links', 'images', 'videos'].forEach(groupName => {
                const includeEl = document.getElementById(`adv-filter-${groupName}-include`);
                const excludeEl = document.getElementById(`adv-filter-${groupName}-exclude`);
                if (includeEl) includeEl.disabled = false;
                if (excludeEl) excludeEl.disabled = false;
            });
            try {
              const st = loadExcludeFlags();
              const nameEl   = document.getElementById('adv-exclude-hit-name');
              const handleEl = document.getElementById('adv-exclude-hit-handle');
              const repostsEl = document.getElementById('adv-filter-reposts-exclude');
              const hashtagsEl = document.getElementById('adv-filter-hashtags-exclude');
              if (nameEl)   { nameEl.checked = nameEl.defaultChecked = !!st.name; }
              if (handleEl) { handleEl.checked = handleEl.defaultChecked = !!st.handle; }
              if (repostsEl) { repostsEl.checked = repostsEl.defaultChecked = !!st.reposts; }
              if (hashtagsEl) { hashtagsEl.checked = hashtagsEl.defaultChecked = !!st.hashtags; }
            } catch (_) {}

            // ク゚リを正芏化スマヌト匕甚・%xx・空癜
            const rawNorm = normalizeForParse(query || '');

            // トップレベル OR を先に芋る玔粋 OR / ハむブリッド OR の切り分け
            const orParts = splitTopLevelOR(rawNorm);
            if (orParts && isPureORQuery(rawNorm)) {
              // 匕甚を 1 語ずしお数えるトヌクナむザ
              const tokenize = (s) => tokenizeQuotedWords(s).filter(Boolean);
              const tokenized = orParts.map(p => tokenize(p));

              const allAreSingle = tokenized.every(ts => ts.length === 1);
              if (allAreSingle) {
                // ① 玔粋 OR党郚 any に入れるexact/all は空→ 早期 return
                document.getElementById('adv-any-words').value = orParts.join(' ');
                isUpdating = false;
                return;
              }

              const head = tokenized[0];
              const rest = tokenized.slice(1);
              const restAllSingle = rest.every(ts => ts.length === 1);

              if (head.length >= 2 && restAllSingle) {
                // ② ハむブリッド OR
                //    - 先頭片の「最埌のトヌクン」→ OR 集合
                //    - 先頭片の「それ以倖」      → all必須語
                //    - 埌続片単䞀トヌクン   → OR 集合
                const required = head.slice(0, -1);
                const orTokens = [head[head.length - 1], ...rest.map(ts => ts[0])];

                document.getElementById('adv-all-words').value = required.join(' ');
                document.getElementById('adv-any-words').value = orTokens.join(' ');
                // exact は空のたた匕甚は any 偎ぞ
                isUpdating = false;
                return;
              }
              // それ以倖レアは通垞パヌスにフォヌルバック
            }

            // ここから通垞パヌスrawNorm をベヌス
            let q = ` ${rawNorm} `;

            // 蚀語や挔算子は先に抜く匕甚の前埌どちらでもOKだが、先にやるず芖芚的に期埅通り
            const extract = (regex, cb) => {
              let m;
              while ((m = regex.exec(q)) !== null) {
                cb(m[1].trim());
                q = q.replace(m[0], ' ');
                regex.lastIndex = 0;
              }
            };

            // 蚀語
            extract(/\blang:([^\s()"]+)/gi, v => { document.getElementById('adv-lang').value = v.toLowerCase(); });

            // ハッシュタグ
            extract(/\s#([^\s)"]+)/g, v => {
              const el = document.getElementById('adv-hashtag');
              el.value = (el.value + ' ' + v).trim();
            });

            // 最小゚ンゲヌゞメント・期間
            extract(/\bmin_replies:(\d+)\b/gi, v => document.getElementById('adv-min-replies').value = v);
            extract(/\bmin_faves:(\d+)\b/gi,   v => document.getElementById('adv-min-faves').value   = v);
            extract(/\bmin_retweets:(\d+)\b/gi,v => document.getElementById('adv-min-retweets').value= v);
            extract(/\bsince:(\d{4}-\d{2}-\d{2})\b/gi, v => document.getElementById('adv-since').value = v);
            extract(/\buntil:(\d{4}-\d{2}-\d{2})\b/gi, v => document.getElementById('adv-until').value = v);

            // フィルタ
            const filterMap = { 'is:verified':'verified', 'filter:links':'links', 'filter:images':'images', 'filter:videos':'videos' };
            Object.entries(filterMap).forEach(([op,id])=>{
              const r = new RegExp(`\\s(-?)${op.replace(':','\\:')}\\b`, 'gi');
              q = q.replace(r, (m, neg) => {
                document.getElementById(`adv-filter-${id}-${neg ? 'exclude' : 'include'}`).checked = true;
                return ' ';
              });
            });

            // 返信
            if (/\binclude:replies\b/i.test(q)) { document.getElementById('adv-replies').value='include'; q=q.replace(/\binclude:replies\b/ig,' '); }
            else if (/\bfilter:replies\b/i.test(q)) { document.getElementById('adv-replies').value='only'; q=q.replace(/\bfilter:replies\b/ig,' '); }
            else if (/\b-filter:replies\b/i.test(q)) { document.getElementById('adv-replies').value='exclude'; q=q.replace(/\b-filter:replies\b/ig,' '); }

            // アカりント挔算子
            const parseAccountField = (inputId, operator) => {
              const exclOp = `-${operator}`;
              const values = [];
              // 陀倖
              const reEx = new RegExp(`\\s${exclOp.replace(/[-:]/g,'\\$&')}([^\\s()"]+)`, 'gi');
              q = q.replace(reEx, (m, u) => { values.push(u); document.getElementById(`${inputId}-exclude`).checked = true; return ' '; });
              // 包含括匧 OR グルヌプ
              const reGroup = new RegExp(`\\((?:${operator.replace(':','\\:')}([^\\s()"]+))(?:\\s+OR\\s+${operator.replace(':','\\:')}([^\\s()"]+))*\\)`, 'gi');
              q = q.replace(reGroup, (m) => {
                m.replace(new RegExp(`${operator.replace(':','\\:')}([^\\s()"]+)`, 'gi'), (_m, u) => { values.push(u); return _m; });
                return ' ';
              });
              // 単䜓
              const reIn = new RegExp(`\\s(?!-)${operator.replace(':','\\:')}([^\\s()"]+)`, 'gi');
              q = q.replace(reIn, (m, u) => { values.push(u); return ' '; });
              if (values.length) document.getElementById(inputId).value = [...new Set(values)].join(' ');
            };
            parseAccountField('adv-from-user','from:');
            parseAccountField('adv-to-user','to:');
            parseAccountField('adv-mentioning','@');

            // â–Œ 括匧内 OR は any ぞ**先にやる**。匕甚は壊さない、グルヌプ䞞ごず陀去
            {
              const groups = q.match(/\((?:[^()"]+|"[^"]*")+\)/g); // 匕甚察応の簡易版
              if (groups) {
                const tokens = groups
                  .map(g => g.slice(1, -1))                      // (...) → 䞭身
                  .flatMap(s => s.split(/\s+OR\s+/i))
                  .map(s => s.trim())
                  .filter(Boolean);
                if (tokens.length) {
                  const el = document.getElementById('adv-any-words');
                  el.value = (el.value ? el.value + ' ' : '') + tokens.join(' ');
                }
                // グルヌプは䞞ごず削る以埌の匕甚抜出に巻き蟌たせない
                q = q.replace(/\((?:[^()"]+|"[^"]*")+\)/g, ' ');
              }
            }

            // â–Œ 匕甚フレヌズ括匧の倖だけが残っおいる。exact は最初の1件のみ
            {
              let exactSet = false;
              q = q.replace(/"([^"]+)"/g, (_m, p1) => {
                if (!exactSet) {
                  document.getElementById('adv-exact-phrase').value = p1.trim();
                  exactSet = true;
                }
                return ' ';
              });
            }

            // 陀倖語
            const nots = (q.match(/\s-\S+/g) || []).map(w => w.trim().slice(1));
            if (nots.length) document.getElementById('adv-not-words').value = nots.join(' ');
            q = q.replace(/\s-\S+/g,' ');

            document.getElementById('adv-all-words').value =
              q.trim().split(/\s+/).filter(Boolean).join(' ');

            // フィルタ適甚埌に disabled 状態を再評䟡
            ['verified', 'links', 'images', 'videos'].forEach(groupName => {
                const includeEl = document.getElementById(`adv-filter-${groupName}-include`);
                const excludeEl = document.getElementById(`adv-filter-${groupName}-exclude`);
                if (!includeEl || !excludeEl) return;
                if (includeEl.checked) excludeEl.disabled = true;
                if (excludeEl.checked) includeEl.disabled = true;
            });

            isUpdating = false;
        };

        const syncFromModalToSearchBox = () => {
            if (isUpdating) return; isUpdating=true;
            const finalQuery = buildQueryStringFromModal();
            // â–Œ 耇数圢に倉曎し、ルヌプ凊理
            const inputs = getActiveSearchInputs();
            if (inputs.length > 0) {
                inputs.forEach(si => {
                    if (si) { syncControlledInput(si, finalQuery); }
                });
            }
            isUpdating=false;
            updateSaveButtonState();
        };
        const syncFromSearchBoxToModal = STATE_SYNC.parseFromSearchToModal;

        const showToast = (msg) => {
            toastEl.textContent = msg;
            toastEl.classList.add('show');
            setTimeout(()=> toastEl.classList.remove('show'), 1500);
        };

        function openSettingsModal() {
          if (!settingsModal) return;
          settingsModal.style.display = 'flex';

          // UI蚀語の読み蟌み
          try {
            const override = kv.get(LANG_OVERRIDE_KEY, '');
            if (settingsLangSel) settingsLangSel.value = override || '';
          } catch (_) {}

          // タブ衚瀺蚭定の読み蟌みず蚭定
          try {
            const visibility = loadTabsVisibility();
            DEFAULT_TABS.forEach(tabName => {
              const toggle = document.getElementById(`adv-settings-tab-toggle-${tabName}`);
              if (!toggle) return;

              // 状態を同期 (search は disabled checked になっおいるので loadTabsVisibility に远埓)
              toggle.checked = visibility[tabName] !== false;

              // 倚重登録を防止
              if (toggle.dataset.listenerAttached) return;
              toggle.dataset.listenerAttached = 'true';

              toggle.addEventListener('change', () => {
                const currentState = loadTabsVisibility();
                currentState[tabName] = toggle.checked;
                saveTabsVisibility(currentState);
                // 即座にタブバヌに反映
                applyTabsVisibility();
              });
            });
          } catch (e) {
            console.error('[AdvSearch] Failed to setup Tab Toggles:', e);
          }

          try {
            const dialog = settingsModal.querySelector('.adv-settings-dialog');
            themeManager.applyTheme(dialog, trigger);
          } catch (_) {}
        }

        function closeSettingsModal() {
          if (!settingsModal) return;
          settingsModal.style.display = 'none';
        }

        if (settingsOpenBtn) {
          settingsOpenBtn.addEventListener('click', (e)=>{
            e.stopPropagation();
            openSettingsModal();
          });
        }
        if (settingsCloseBtn) {
          settingsCloseBtn.addEventListener('click', (e)=>{
            e.stopPropagation();
            closeSettingsModal();
          });
        }
        if (settingsCloseFooterBtn) {
          settingsCloseFooterBtn.addEventListener('click', (e)=>{
            e.stopPropagation();
            closeSettingsModal();
          });
        }
        if (settingsModal) {
          settingsModal.addEventListener('click', (e)=>{
            if (e.target === settingsModal) {
              closeSettingsModal();
            }
          });
        }

        if (settingsExportBtn) {
          settingsExportBtn.addEventListener('click', () => {
            const json = buildSettingsExportJSON();
            try {
              const blob = new Blob([json], { type: 'application/json' });
              const url = URL.createObjectURL(blob);
              const a = document.createElement('a');

              const now = new Date();
              const pad = (n) => String(n).padStart(2, '0');
              const fname =
                `advanced-search-for-x-twitter-backup-${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}` +
                `-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}.json`;

              a.href = url;
              a.download = fname;
              document.body.appendChild(a);
              a.click();
              document.body.removeChild(a);
              URL.revokeObjectURL(url);
            } catch (_) {
              // 倱敗しおも、ずりあえずトヌストだけは出す
            }
            showToast(i18n.t('toastExported'));
          });
        }

        if (settingsImportBtn && settingsFileInput) {
          let importResetTimer = null;

          settingsImportBtn.addEventListener('click', () => {
            settingsFileInput.click();
          });

          settingsFileInput.addEventListener('change', () => {
            const file = settingsFileInput.files && settingsFileInput.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = () => {
              let ok = false;
              try {
                ok = applySettingsImportJSON(String(reader.result || ''));
              } finally {
                // 同じファむルを続けお遞べるようにリセット
                settingsFileInput.value = '';
              }

              if (ok && settingsImportBtn) {
                const successLabel = i18n.t('buttonImportSuccess');
                const normalLabel  = i18n.t('buttonImport');

                settingsImportBtn.textContent = successLabel;
                settingsImportBtn.disabled = true;

                if (importResetTimer) clearTimeout(importResetTimer);
                importResetTimer = setTimeout(() => {
                  settingsImportBtn.disabled = false;
                  settingsImportBtn.textContent = normalLabel;
                }, 2000);
              }
            };
            reader.readAsText(file);
          });
        }

        if (settingsResetBtn) {
          settingsResetBtn.addEventListener('click', () => {
            if (!confirm(i18n.t('confirmResetAll'))) return;

            const KEYS_TO_DELETE = [
              MODAL_STATE_KEY,
              TRIGGER_STATE_KEY,
              HISTORY_KEY,
              SAVED_KEY,
              SECRET_KEY,
              MUTE_KEY,
              MUTE_MASTER_KEY,
              LAST_TAB_KEY,
              TABS_ORDER_KEY,
              TABS_VISIBILITY_KEY,
              LANG_OVERRIDE_KEY,
              HISTORY_SORT_KEY,
              EXC_NAME_KEY,
              EXC_HANDLE_KEY,
              EXC_REPOSTS_KEY,
              EXC_HASHTAGS_KEY,
              FAV_KEY,
              'advSavedUnassignedIndex_v1',
              'advAccountsUnassignedIndex_v1',
              'advListsUnassignedIndex_v1',
              ...Object.values(ZOOM_KEYS),
              FT_STATE_KEY,
            ];

            KEYS_TO_DELETE.forEach(k => {
              try { kv.del(k); } catch (_) {}
            });

            // 各皮配列系は空配列で䞊曞き
            try { saveMuted([]); } catch (_) {}
            try { saveJSON(HISTORY_KEY, []); } catch (_) {}
            try { saveJSON(SAVED_KEY, []); } catch (_) {}
            try { saveFavorites([]); } catch (_) {}
            try { saveAccounts([]); } catch (_) {}
            try { saveLists([]); } catch (_) {}
            try { saveFolders(ACCOUNTS_FOLDERS_KEY, []); } catch (_) {}
            try { saveFolders(LISTS_FOLDERS_KEY, []); } catch (_) {}
            try { saveFolders(SAVED_FOLDERS_KEY, []); } catch (_) {}

            /* --- Favorite Tags Data --- */
            try {
              if (typeof ft_createDefaultState === 'function' && typeof ft_saveState === 'function') {
                const defaultBmState = ft_createDefaultState();
                // saveState は内郚で saveJSON を呌ぶ
                ft_saveState(defaultBmState);
                if (typeof ft_state !== 'undefined' && ft_state !== null) {
                  // グロヌバル倉数もリセット
                  Object.assign(ft_state, defaultBmState);
                }
              }
            } catch (_) {}

            // ズヌムキャッシュずパヌスキャッシュもリセット
            try {
              Object.keys(zoomByTab).forEach(tab => {
                zoomByTab[tab] = (tab === 'search') ? 0.87 : 1.0;
              });
            } catch (_) {}
            __cachedSearchTokens = null;
            __cachedSearchQuery = null;

            // 蚀語蚭定を再適甚オヌバヌラむドがなければ自動怜出
            try {
              const override = kv.get(LANG_OVERRIDE_KEY, '');
              if (override && i18n.translations[override]) {
                i18n.lang = override;
              } else {
                i18n.init();
              }
            } catch (_) {
              i18n.init();
            }

            try {
              i18n.apply(document.getElementById('advanced-search-modal'));
              i18n.apply(document.getElementById('adv-settings-modal'));
            } catch (_) {}

            // UI 状態を初期化
            try {
              // タブの衚瀺状態をリセット
              applyTabsVisibility();
              // 最埌のタブを 'search' にリセット
              activateTab('search');

              parseQueryAndApplyToModal('');
              applyScopesToControls({ pf: false, lf: false });
              applySecretBtn();
              renderHistory();
              renderSaved();
              renderLists();
              renderAccounts();
              renderMuted();
              updateSaveButtonState();
              rescanAllTweetsForFilter();

              /* --- Favorite Tags UI Refresh --- */
              try {
                if (typeof ft_refreshAllTagChips === 'function') ft_refreshAllTagChips();
              } catch (_) {}

            } catch (_) {}

            // モヌダル䜍眮・サむズをデフォルトに近い状態ぞ戻す
            try {
              modal.style.width = '';
              modal.style.height = '';
              modal.style.left = '';
              modal.style.right = '';
              modal.style.top = '';
              modal.style.bottom = '';
              loadModalState();
            } catch (_) {}

            // トリガヌボタンの䜍眮もリセット
            try {
              trigger.style.left = '';
              trigger.style.right = '';
              trigger.style.top = '';
              trigger.style.bottom = '';
              applyTriggerStoredPosition();
              keepTriggerInViewport();
            } catch (_) {}

            showToast(i18n.t('toastReset'));
          });
        }

        if (settingsLangSel) {
          settingsLangSel.addEventListener('change', ()=>{
            const v = settingsLangSel.value;
            try { kv.set(LANG_OVERRIDE_KEY, v || ''); } catch (_) {}
            if (v && i18n.translations[v]) {
              i18n.lang = v;
            } else {
              i18n.init();
              try {
                const override = kv.get(LANG_OVERRIDE_KEY, '');
                if (override && i18n.translations[override]) i18n.lang = override;
              } catch (_) {}
            }

            try {
              i18n.apply(document.getElementById('advanced-search-modal'));
              i18n.apply(document.getElementById('adv-settings-modal'));
            } catch (_) {}

            trigger.setAttribute('aria-label', i18n.t('tooltipTrigger'));
            historyClearAllBtn.textContent = i18n.t('historyClearAll');
            applySecretBtn();

            try { renderHistory(); } catch (_) {}
            try { renderSaved(); } catch (_) {}
            try { renderLists(); } catch (_) {}
            try { renderAccounts(); } catch (_) {}
            try { renderMuted(); } catch (_) {}
            try { renderFavorites(); } catch (_) {}
          });
        }

        const loadSecret = () => { try { return kv.get(SECRET_KEY, '0') === '1'; } catch(_) { return false; } };
        const saveSecret = (on) => { try { kv.set(SECRET_KEY, on ? '1' : '0'); } catch(_) {} };
        const applySecretBtn = () => {
            const on = loadSecret();
            secretBtn.classList.toggle('on', on);
            secretBtn.classList.toggle('off', !on);
            secretBtn.title = i18n.t(on ? 'secretOn' : 'secretOff');
            secretStateEl.textContent = on ? 'ON' : 'OFF';
        };
        secretBtn.addEventListener('click', (e)=>{
            e.stopPropagation();
            const on = !loadSecret();
            saveSecret(on);
            applySecretBtn();
            showToast(i18n.t(on ? 'secretOn' : 'secretOff'));
        });
        applySecretBtn();

        const migrateList = (list) => Array.isArray(list) ? list.map(it => ({ id:it.id||uid(), q:it.q||'', ts:it.ts||Date.now(), pf:!!it.pf, lf:!!it.lf })) : [];

        const recordHistory = (q, pf, lf) => {
            if (!q || loadSecret()) return;
            const now = Date.now();
            if (lastHistory.q === q && lastHistory.pf === pf && lastHistory.lf === lf && (now - lastHistory.ts) < 3000) return;
            lastHistory.q = q; lastHistory.pf = pf; lastHistory.lf = lf; lastHistory.ts = now;

            const listRaw = loadJSON(HISTORY_KEY, []);
            const list = migrateList(listRaw);
            const idx = list.findIndex(it => it.q === q && !!it.pf === !!pf && !!it.lf === !!lf);
            if (idx === 0) {
                list[0].ts = now;
            } else if (idx > 0) {
                const [item] = list.splice(idx, 1);
                item.ts = now;
                list.unshift(item);
            } else {
                list.unshift({ id: uid(), q, pf: !!pf, lf: !!lf, ts: now });
                // if (list.length > 50) list.length = 50;
            }
            saveJSON(HISTORY_KEY, list);
            renderHistory();
        };

        const deleteHistory = (id) => {
            const listRaw = loadJSON(HISTORY_KEY, []);
            const list = migrateList(listRaw);
            const next = list.filter(it => it.id !== id);
            saveJSON(HISTORY_KEY, next);
            renderHistory();
            showToast(i18n.t('toastDeleted'));
        };

        const clearAllHistory = () => {
            if (!confirm(i18n.t('confirmClearHistory'))) return;
            saveJSON(HISTORY_KEY, []);
            renderHistory();
            showToast(i18n.t('toastDeleted'));
        };

        const addSaved = (q, pf, lf) => {
            const listRaw = loadJSON(SAVED_KEY, []);
            const list = migrateList(listRaw);
            if (list.some(it => it.q === q && !!it.pf === !!pf && !!it.lf === !!lf)) {
                updateSaveButtonState();
                return;
            }
            const item = { id: uid(), q, pf: !!pf, lf: !!lf, ts: Date.now() };
            list.push(item);
            saveJSON(SAVED_KEY, list);
            renderSaved();
            showToast(i18n.t('toastSaved'));
            updateSaveButtonState();
        };

        const deleteSaved = (id) => {
            const listRaw = loadJSON(SAVED_KEY, []);
            const list = migrateList(listRaw);
            const next = list.filter(it => it.id !== id);
            saveJSON(SAVED_KEY, next);
            renderSaved();
            showToast(i18n.t('toastDeleted'));
            updateSaveButtonState();
        };

        const fmtTime = (ts) => { try { return new Date(ts).toLocaleString(); } catch { return ''; } };

        const updateSaveButtonState = () => {
            const q = buildQueryStringFromModal().trim();
            const {pf, lf} = readScopesFromControls();
            const saved = migrateList(loadJSON(SAVED_KEY, []));
            const exists = !!q && saved.some(it => it.q === q && !!it.pf === !!pf && !!it.lf === !!lf);
            saveButton.disabled = !q || exists;
            saveButton.textContent = i18n.t(exists ? 'buttonSaved' : 'buttonSave');
            saveButton.setAttribute('aria-disabled', saveButton.disabled ? 'true' : 'false');
        };



        // タブのクリックむベントずD&Dむベントリスナヌをセットアップ
        (function setupTabDragAndDrop() {
            const tabsContainer = document.querySelector('.adv-tabs');
            if (!tabsContainer) return;

            tabButtons.forEach(btn => {
                // 1. クリックむベント既存のロゞック
                btn.addEventListener('click', (e) => {
                    e.preventDefault();
                    activateTab(btn.dataset.tab);
                });

                // 2. D&Dむベント新芏
                btn.draggable = true;

                btn.addEventListener('dragstart', (ev) => {
                    btn.classList.add('dragging');
                    ev.dataTransfer.setData('text/plain', btn.dataset.tab);
                    ev.dataTransfer.effectAllowed = 'move';
                });

                btn.addEventListener('dragend', () => {
                    btn.classList.remove('dragging');
                });
            });

            tabsContainer.addEventListener('dragover', (ev) => {
                ev.preventDefault();
                const dragging = tabsContainer.querySelector('.adv-tab-btn.dragging');
                if (!dragging) return;

                // 氎平方向の挿入䜍眮を蚈算
                const after = getDragAfterElementHorizontal(tabsContainer, ev.clientX, '.adv-tab-btn');
                if (after == null) {
                    tabsContainer.appendChild(dragging);
                } else {
                    tabsContainer.insertBefore(dragging, after);
                }
            });

            tabsContainer.addEventListener('drop', (ev) => {
                ev.preventDefault();
                const dragging = tabsContainer.querySelector('.adv-tab-btn.dragging');
                if (dragging) {
                    dragging.classList.remove('dragging');
                }

                // 最終的な順序をDOMから読み取っお保存
                const newOrder = [...tabsContainer.querySelectorAll('.adv-tab-btn[data-tab]')]
                    .map(btn => btn.dataset.tab)
                    .filter(Boolean);

                saveJSON(TABS_ORDER_KEY, newOrder);
                // tabButtons 配列も曎新
                tabButtons.splice(0, tabButtons.length, ...Array.from(document.querySelectorAll('.adv-tab-btn')));
                showToast(i18n.t('toastReordered'));
            });
        })();

        const scopeChipsHTML = (pf, lf) => {
            const chips = [];
            if (pf) chips.push(`<span class="adv-chip scope" role="note">${i18n.t('chipFollowing')}</span>`);
            if (lf) chips.push(`<span class="adv-chip scope" role="note">${i18n.t('chipNearby')}</span>`);
            return chips.join('');
        };

        const historyEmptyEl = document.getElementById('adv-history-empty');
        const historyListEl = document.getElementById('adv-history-list');
        const historySearchEl = document.getElementById('adv-history-search');
        const historySortEl = document.getElementById('adv-history-sort');

        const renderHistory = () => {
          const listAll = migrateList(loadJSON(HISTORY_KEY, []));

          // 1. Get filter/sort values
          const q = (historySearchEl?.value || '').toLowerCase().trim();
          const sort = historySortEl?.value || kv.get(HISTORY_SORT_KEY, 'newest');
          if (historySortEl && historySortEl.value !== sort) {
            historySortEl.value = sort;
          }

          // 2. Filter
          const listFiltered = q
            ? listAll.filter(item => (item.q || '').toLowerCase().includes(q))
            : listAll;

          // 3. Sort
          const listSorted = listFiltered.sort((a, b) => {
            switch (sort) {
              case 'oldest': return (a.ts || 0) - (b.ts || 0);
              case 'name_asc': return (a.q || '').localeCompare(b.q || '');
              case 'name_desc': return (b.q || '').localeCompare(a.q || '');
              case 'newest':
              default:
                return (b.ts || 0) - (a.ts || 0);
            }
          });

          // 4. Render
          historyListEl.innerHTML = '';
          historyEmptyEl.textContent = listAll.length === 0 ? i18n.t('emptyHistory') : '';

          listSorted.forEach(item => {
            const row = document.createElement('div');
            row.className = 'adv-item';
            row.dataset.id = item.id;

            row.innerHTML = `
              <div class="adv-item-main">
                <div class="adv-item-title">${escapeHTML(item.q)}</div>
                <div class="adv-item-sub">
                  <span>${fmtTime(item.ts)}</span>
                  ${scopeChipsHTML(!!item.pf, !!item.lf)}
                </div>
              </div>
              <div class="adv-item-actions">
                <button class="adv-chip primary" data-action="run">${i18n.t('run')}</button>
                <button class="adv-chip danger" data-action="delete">${i18n.t('delete')}</button>
              </div>
            `;

            row.querySelector('[data-action="run"]').addEventListener('click', () => {
              parseQueryAndApplyToModal(item.q);
              applyScopesToControls({ pf: !!item.pf, lf: !!item.lf });
              // activateTab('search');
              executeSearch({ pf: item.pf, lf: item.lf });
            });

            row.querySelector('[data-action="delete"]').addEventListener('click', () => {
              deleteHistory(item.id);
            });

            historyListEl.appendChild(row);
          });
        };

        historyClearAllBtn.addEventListener('click', clearAllHistory);

        // 履歎タブの怜玢ず゜ヌトのむベントリスナヌ
        if (historySearchEl) {
          historySearchEl.addEventListener('input', debounce(renderHistory, 150));
        }
        if (historySortEl) {
          historySortEl.value = kv.get(HISTORY_SORT_KEY, 'newest'); // 初期倀を蚭定
          historySortEl.addEventListener('change', () => {
            kv.set(HISTORY_SORT_KEY, historySortEl.value);
            renderHistory();
          });
        }

        const savedEmptyEl = document.getElementById('adv-saved-empty');
        const savedListEl = document.getElementById('adv-saved-list');

        const renderSaved = () => {
          ensureFolderToolbars();

          const itemsLoader = () => migrateList(loadJSON(SAVED_KEY, []));
          const itemsSaver  = (arr) => saveJSON(SAVED_KEY, migrateList(arr));

          renderFolderedCollection({
            hostId: 'adv-saved-list',
            emptyId: 'adv-saved-empty',
            filterSelectId: 'adv-saved-folder-filter',
            searchInputId:  'adv-saved-search',
            newFolderBtnId: 'adv-saved-new-folder',

            foldersKey: SAVED_FOLDERS_KEY,
            defaultFolderName: i18n.t('defaultSavedFolders'),

            loadItems: itemsLoader,
            saveItems: itemsSaver,
            renderRow: (item) => {
              // 以前の renderSavedRow ず同じ芋た目
              const row = document.createElement('div');
              row.className = 'adv-item';
              row.draggable = true;
              row.dataset.id = item.id;
              row.innerHTML = `
                <div class="adv-item-handle" title="Drag">≡</div>
                <div class="adv-item-main">
                  <div class="adv-item-title">${escapeHTML(item.q)}</div>
                  <div class="adv-item-sub">
                    <span>${fmtTime(item.ts)}</span>
                    ${scopeChipsHTML(!!item.pf, !!item.lf)}
                  </div>
                </div>
                <div class="adv-item-actions">
                  <button class="adv-chip primary" data-action="run">${i18n.t('run')}</button>
                  <button class="adv-chip danger"  data-action="delete">${i18n.t('delete')}</button>
                </div>
              `;
              row.querySelector('[data-action="run"]').addEventListener('click', ()=>{
                parseQueryAndApplyToModal(item.q);
                applyScopesToControls({pf:!!item.pf, lf:!!item.lf});
                // activateTab('search');
                executeSearch({pf:item.pf, lf:item.lf});
              });
              row.querySelector('[data-action="delete"]').addEventListener('click', ()=> deleteSaved(item.id));

              row.addEventListener('dragstart', (ev) => {
                row.classList.add('dragging');
                ev.dataTransfer.setData('text/plain', item.id);
                ev.dataTransfer.effectAllowed = 'move';
              });
              row.addEventListener('dragend', () => row.classList.remove('dragging'));
              return row;
            },

            onUnassign: unassignSaved,
            onMoveToFolder: moveSavedToFolder,

            emptyMessage: i18n.t('emptySaved'),
            unassignedIndexKey: 'advSavedUnassignedIndex_v1',
          });

          updateSaveButtonState();
        };

        const getDragAfterElement = (container, y) => {
            const els = [...container.querySelectorAll('.adv-item:not(.dragging)')];
            let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
            for (const el of els) {
                const box = el.getBoundingClientRect();
                const offset = y - box.top - box.height / 2;
                if (offset < 0 && offset > closest.offset) {
                    closest = { offset, element: el };
                }
            }
            return closest.element;
        };

        // === セクションフォルダ/Unassigned甚瞊方向の挿入䜍眮蚈算 ===
        function getSectionAfterElement(container, y) {
          const els = [...container.querySelectorAll('.adv-folder:not(.dragging-folder), .adv-unassigned:not(.dragging-folder)')];
          let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
          for (const el of els) {
            const box = el.getBoundingClientRect();
            const offset = y - box.top - box.height / 2;
            if (offset < 0 && offset > closest.offset) {
              closest = { offset, element: el };
            }
          }
          return closest.element;
        }

        // === 汎甚フォルダ描画レンダラ ===
        // 各タブSaved/Accounts/Listsなどの重耇ロゞックを1か所に集玄したす。
        function renderFolderedCollection(cfg) {
          const {
            // 固有ID/キヌ
            hostId, emptyId,
            filterSelectId, searchInputId, newFolderBtnId,
            foldersKey, defaultFolderName,
            // デヌタI/O
            loadItems, saveItems, loadFoldersFn = loadFolders, saveFoldersFn = saveFolders,
            // Row描画/操䜜
            renderRow, onUnassign, onMoveToFolder,
            // 文蚀/保存キヌ
            emptyMessage,
            unassignedIndexKey, // ex: 'advAccountsUnassignedIndex_v1' / 'advSavedUnassignedIndex_v1'
          } = cfg;

          // ツヌルバヌは呌び出し偎で ensureFolderToolbars() しおある前提
          const host   = document.getElementById(hostId);
          const empty  = document.getElementById(emptyId);
          const sel    = document.getElementById(filterSelectId);
          const qInput = document.getElementById(searchInputId);
          const addBtn = document.getElementById(newFolderBtnId);
          if (!host) return;

          // 1) デヌタロヌド
          const items = loadItems();
          let folders = loadFoldersFn(foldersKey, defaultFolderName);
          const idToItem = Object.fromEntries(items.map(x => [x.id, x]));

          // 2) 死祚掃陀フォルダの order から存圚しないIDを陀去
          let needSave = false;
          for (const f of folders) {
            const before = f.order.length;
            f.order = f.order.filter(id => !!idToItem[id]);
            if (f.order.length !== before) { needSave = true; f.ts = Date.now(); }
          }
          if (needSave) saveFoldersFn(foldersKey, folders);

          // 3) 未所属セット
          const allIds    = new Set(items.map(x => x.id));
          const inFolders = new Set(folders.flatMap(f => f.order));
          const unassignedIds = [...allIds].filter(id => !inFolders.has(id));

          // 4) フィルタUIセレクト怜玢新芏フォルダ
          if (sel) {
            const prev = sel.value;
            sel.innerHTML = '';
            const optAll = document.createElement('option'); optAll.value='__ALL__'; optAll.textContent=i18n.t('folderFilterAll'); sel.appendChild(optAll);
            const optUn  = document.createElement('option'); optUn.value='__UNASSIGNED__'; optUn.textContent=i18n.t('folderFilterUnassigned'); sel.appendChild(optUn);
            folders.forEach(f=>{
              const o = document.createElement('option'); o.value = f.id; o.textContent = f.name; sel.appendChild(o);
            });
            sel.value = [...sel.options].some(o=>o.value===prev) ? prev : '__ALL__';
            sel.onchange = () => renderFolderedCollection(cfg);
          }
          if (qInput && !qInput._advBound) {
            qInput._advBound = true;
            // debounce を適甚
            qInput.addEventListener('input', debounce(() => renderFolderedCollection(cfg), 150));
          }
          if (addBtn && !addBtn._advBound) {
            addBtn._advBound = true;
            addBtn.addEventListener('click', () => {
              const nm = prompt(i18n.t('promptNewFolder'), '');
              if (!nm || !nm.trim()) return;
              const fs = loadFoldersFn(foldersKey, defaultFolderName);
              fs.push({ id: uid(), name: nm.trim(), order: [], ts: Date.now() });
              saveFoldersFn(foldersKey, fs);
              renderFolderedCollection(cfg);
            });
          }

          const filterFolder = sel?.value || '__ALL__';
          const q = (qInput?.value || '').toLowerCase().trim();

         const matchItem = (it) => {
              // JSON化せず、怜玢察象になりうるフィヌルドの倀を盎接結合しお刀定する
              // これにより、保存ク゚リ内の " (ダブルクォヌト) が゚スケヌプされずに怜玢可胜になる
              const targetText = [
                  it.q,       // Saved / History 甹
                  it.name,    // Accounts / Lists / Folders 甹
                  it.handle,  // Accounts 甹
                  it.url,     // Lists 甹
                  it.user?.name,   // (予備)
                  it.user?.handle  // (予備)
              ].map(val => (val || '').toString().toLowerCase()).join(' ');

              return !q || targetText.includes(q);
          };

          host.innerHTML = '';
          empty.textContent = items.length ? '' : (emptyMessage || '');

          // 5) Unassigned むンデックス保持
          const getUnIdx = () => {
            try { const v = GM_getValue(unassignedIndexKey, 0); return Math.max(0, Math.min(folders.length, +v || 0)); }
            catch { return 0; }
          };
          const setUnIdx = (idx) => { try { GM_setValue(unassignedIndexKey, String(idx)); } catch {} };

          // 6) 衚瀺察象フォルダ
          const foldersToDraw =
            filterFolder === '__ALL__'        ? [...folders] :
            filterFolder === '__UNASSIGNED__' ? [] :
            folders.filter(f => f.id === filterFolder);

          // 7) セクション䞊び__ALL__ の堎合のみ Unassigned を混圚
          const buildSectionsOrder = () => {
            if (filterFolder !== '__ALL__') return foldersToDraw.map(f => f.id);
            const idx = getUnIdx();
            const arr = foldersToDraw.map(f => f.id);
            arr.splice(Math.max(0, Math.min(arr.length, idx)), 0, '__UNASSIGNED__');
            return arr;
          };

          // 8) DOM → 順序保存
          const persistSectionsFromDOM = () => {
            const order = [...host.querySelectorAll('.adv-folder, .adv-unassigned')].map(sec => sec.dataset.folderId);

            // フォルダ順Unassigned を陀いた順序で保存
            const newFolderOrderIds = [...new Set(order.filter(id => id !== '__UNASSIGNED__'))];
            let fs = loadFoldersFn(foldersKey, defaultFolderName);
            const map = Object.fromEntries(fs.map(f => [f.id, f]));
            const reordered = newFolderOrderIds.map(id => map[id]).filter(Boolean);
            fs.forEach(f => { if (!reordered.includes(f)) reordered.push(f); });
            saveFoldersFn(foldersKey, reordered);

            // Unassigned の䜍眮を保存
            const unIdx = order.indexOf('__UNASSIGNED__');
            if (unIdx >= 0) setUnIdx(unIdx);

            showToast(i18n.t('toastReordered'));
          };

          // 9) Unassigned セクション
          const renderUnassignedSection = () => {
            const sec = document.createElement('section');
            sec.className = 'adv-unassigned';
            sec.dataset.folderId = '__UNASSIGNED__';
            sec.setAttribute('draggable', 'true');

            const list = document.createElement('div'); list.className = 'adv-list';

            const itemsUn = unassignedIds.map(id => idToItem[id]).filter(Boolean).filter(matchItem);
            itemsUn.forEach(it => list.appendChild(renderRow(it)));

            // セクションD&Dセクション入替
            const SECT_MIME = 'adv/folder';
            sec.addEventListener('dragstart', (ev) => {
              const item = ev.target.closest('.adv-item');
              if (!item) {
                ev.dataTransfer.setData(SECT_MIME, '__UNASSIGNED__');
                ev.dataTransfer.effectAllowed = 'move';
                sec.classList.add('dragging-folder');
              }
            });
            sec.addEventListener('dragend', () => sec.classList.remove('dragging-folder'));
            sec.addEventListener('dragover', (ev) => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) {
                ev.preventDefault();
                const dragging = host.querySelector('.dragging-folder');
                if (!dragging || dragging === sec) return;
                const after = getSectionAfterElement(host, ev.clientY);
                if (after == null) host.appendChild(dragging);
                else host.insertBefore(dragging, after);
              }
            });

            // アむテムのプレビュヌ移動DOM
            list.addEventListener('dragover', ev => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return; // セクションD&Dは無芖
              ev.preventDefault(); ev.stopPropagation();
              const dragging = document.querySelector('.adv-item.dragging');
              if (!dragging) return;
              const after = getDragAfterElement(list, ev.clientY);
              if (after == null) list.appendChild(dragging);
              else list.insertBefore(dragging, after);
            });

            // ▌「未分類化」ハンドラセクション背景甚
            // フォルダからドロップされた堎合に "先頭に移動" させる。
            const dropToUnassign = (ev) => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
              ev.preventDefault(); ev.stopPropagation();
              const draggedId = ev.dataTransfer.getData('text/plain');
              if (draggedId) onUnassign(draggedId); // onUnassign は "先頭に移動" する
            };

            // ▌「未分類アむテムの䞊び替え」ハンドラリスト本䜓甚
            // 未分類リスト内での䞊び替え、たたはフォルダから特定䜍眮ぞのドロップ。
            const dropToReorderUnassigned = (ev) => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
              ev.preventDefault(); ev.stopPropagation();
              const draggedId = ev.dataTransfer.getData('text/plain');
              if (!draggedId) return;

              // 1. DOMの芖芚的な順序dragoverで倉曎枈みをID配列ずしお読み取る
              const newOrderIdsInList = [...list.querySelectorAll('.adv-item')].map(el => el.dataset.id);

              // 2. マスタヌリスト党アむテムずフォルダ内アむテムの情報をロヌド
              const allItems = loadItems();
              const allItemsMap = new Map(allItems.map(it => [it.id, it]));
              const allFolderItems = new Set(folders.flatMap(f => f.order));

              // 3. 新しいマスタヌリストを構築
              const nextMasterList = [];
              const seen = new Set();

              // 3a. たず、DOMから読み取った「未分類の新しい順序」でアむテムを远加
              for (const id of newOrderIdsInList) {
                // このリストにあるべきアむテムマスタヌに存圚し、フォルダに属さないのみ
                if (id && allItemsMap.has(id) && !allFolderItems.has(id)) {
                  nextMasterList.push(allItemsMap.get(id));
                  seen.add(id);
                }
              }

              // 3b. 次に、残りのアむテム党フォルダ内のアむテム䜕らかの理由で挏れた未分類アむテムを远加
              // これにより、マスタヌリストの順序は「未分類の䞊び替え順」「それ以倖」ずなる
              for (const item of allItems) {
                if (!seen.has(item.id)) {
                  nextMasterList.push(item);
                }
              }

              // 4. マスタヌリストを保存
              saveItems(nextMasterList);

              // 5. もしアむテムがフォルダから移動しおきた堎合、フォルダから削陀クリヌンアップ
              const fs = loadFoldersFn(foldersKey, defaultFolderName);
              let folderChanged = false;
              for (const f of fs) {
                const before = f.order.length;
                f.order = f.order.filter(id => id !== draggedId);
                if (f.order.length !== before) { f.ts = Date.now(); folderChanged = true; }
              }

              if (folderChanged) {
                saveFoldersFn(foldersKey, fs);
                // フォルダ構成が倉わった堎合は、リスト党䜓を再描画
                showToast(i18n.t('toastReordered'));
                renderFolderedCollection(cfg);
              } else {
                // 未分類内での移動だけなら再描画は䞍芁DOMは曎新枈み
                showToast(i18n.t('toastReordered'));
              }
            };

            // â–Œ リスト本䜓には「䞊び替え」を、セクション背景には「未分類化」を割り圓おる
            list.addEventListener('drop', dropToReorderUnassigned);
            sec.addEventListener('dragover', ev => { if (!(ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME))) { ev.preventDefault(); ev.stopPropagation(); }});
            sec.addEventListener('drop', dropToUnassign);

            sec.appendChild(list);
            return sec;
          };

          // 10) フォルダセクション
          const renderFolderSection = (folder) => {
            const section = document.createElement('section');
            section.className = 'adv-folder';
            section.dataset.folderId = folder.id;
            if (folder.collapsed) section.classList.add('adv-folder-collapsed');

            const header = document.createElement('div');
            header.className = 'adv-folder-header';
            header.setAttribute('draggable', 'true');

            const toggleBtn = renderFolderToggleButton(!!folder.collapsed);
            const titleWrap = document.createElement('div'); titleWrap.className = 'adv-folder-title';
            titleWrap.appendChild(toggleBtn);
            const nameEl = document.createElement('strong'); nameEl.textContent = folder.name; titleWrap.appendChild(nameEl);
            const countEl = document.createElement('span'); countEl.className='adv-item-sub'; countEl.textContent = `(${folder.order.length})`;
            titleWrap.appendChild(countEl);

            const actions = document.createElement('div');
            actions.className = 'adv-folder-actions';
            actions.innerHTML = `
              <button class="adv-chip"        data-action="rename"  title="${i18n.t('folderRenameTitle')}">${i18n.t('folderRename')}</button>
              <button class="adv-chip danger" data-action="delete"  title="${i18n.t('folderDeleteTitle')}">${i18n.t('folderDelete')}</button>
            `;

            header.appendChild(titleWrap);
            header.appendChild(actions);

            // セクションD&D
            const SECT_MIME = 'adv/folder';
            header.addEventListener('dragstart', (ev) => {
              if (ev.target && (ev.target.closest('.adv-folder-actions') || ev.target.closest('.adv-folder-toggle-btn'))) { ev.preventDefault(); return; }
              ev.dataTransfer.setData(SECT_MIME, folder.id);
              ev.dataTransfer.effectAllowed = 'move';
              section.classList.add('dragging-folder');
            });
            header.addEventListener('dragend', () => section.classList.remove('dragging-folder'));
            section.addEventListener('dragover', (ev) => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) {
                ev.preventDefault();
                const dragging = host.querySelector('.dragging-folder');
                if (!dragging || dragging === section) return;
                const after = getSectionAfterElement(host, ev.clientY);
                if (after == null) host.appendChild(dragging);
                else host.insertBefore(dragging, after);
              }
            });

            // 折りたたみ
            const collapseToggle = () => {
              section.classList.toggle('adv-folder-collapsed');
              const all = loadFoldersFn(foldersKey, defaultFolderName);
              const f = all.find(x => x.id === folder.id);
              if (f) { f.collapsed = section.classList.contains('adv-folder-collapsed'); f.ts = Date.now(); saveFoldersFn(foldersKey, all); }
              updateFolderToggleButton(toggleBtn, !!section.classList.contains('adv-folder-collapsed'));
            };
            toggleBtn.addEventListener('click', (e)=>{ e.stopPropagation(); collapseToggle(); });
            toggleBtn.addEventListener('keydown', (e)=>{ if (e.key===' '||e.key==='Enter'){ e.preventDefault(); collapseToggle(); } });

            // Rename / Delete
            actions.querySelector('[data-action="rename"]').addEventListener('click', ()=>{
              const nm = prompt(i18n.t('promptNewFolder'), folder.name);
              if (!nm || !nm.trim()) return;
              const fArr = loadFoldersFn(foldersKey, defaultFolderName);
              const f = fArr.find(x=>x.id===folder.id); if (!f) return;
              f.name = nm.trim(); f.ts = Date.now(); saveFoldersFn(foldersKey, fArr);
              renderFolderedCollection(cfg); showToast(i18n.t('updated'));
            });
            actions.querySelector('[data-action="delete"]').addEventListener('click', ()=>{
              if (!confirm(i18n.t('confirmDeleteFolder'))) return;

                // 1. 削陀察象のアむテムIDセットを取埗
                const itemsToDelete = new Set(folder.order || []);

                // 2. アむテムのマスタヌリストから該圓アむテムを削陀
                if (itemsToDelete.size > 0) {
                    try {
                        const allItems = loadItems(); // 芪スコヌプの loadItems を䜿甚
                        const nextItems = allItems.filter(item => !itemsToDelete.has(item.id));
                        saveItems(nextItems); // 芪スコヌプの saveItems を䜿甚
                    } catch (e) {
                        console.error('Failed to delete items in folder:', e);
                        // アむテム削陀に倱敗しおも、フォルダ削陀は続行
                    }
                }

              // 3. フォルダ自䜓を削陀
              let fArr = loadFoldersFn(foldersKey, defaultFolderName);
              const idx = fArr.findIndex(x=>x.id===folder.id); if (idx<0) return;
              fArr.splice(idx,1);
              saveFoldersFn(foldersKey, fArr);

              // 4. 再描画
              renderFolderedCollection(cfg); showToast(i18n.t('toastDeleted'));
            });

            // フォルダ芋出しにドロップ → そのフォルダぞ移動
            header.addEventListener('dragover', ev => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
              ev.preventDefault();
              // 排他制埡: 他のフォルダのハむラむトを消す
              document.querySelectorAll('.adv-folder[data-drop="1"]').forEach(el => {
                if (el !== section) delete el.dataset.drop;
              });
              section.dataset.drop='1';
            });
            header.addEventListener('dragleave', (ev) => {
              // 子芁玠ぞの移動でも䞀旊消すが、dragoverですぐ埩掻する
              delete section.dataset.drop;
            });
            header.addEventListener('drop', ev => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
              ev.preventDefault(); delete section.dataset.drop;
              const draggedId = ev.dataTransfer.getData('text/plain');
              if (!draggedId) return;
              onMoveToFolder(draggedId, folder.id);
            });

            // リスト本䜓
            const list = document.createElement('div'); list.className = 'adv-list';
            const itemsInFolder = folder.order.map(id => idToItem[id]).filter(Boolean).filter(matchItem);
            itemsInFolder.forEach(it => list.appendChild(renderRow(it)));

            // 䞊びプレビュヌ
            list.addEventListener('dragover', ev => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return; // ガヌド远加
              ev.preventDefault();
              ev.stopPropagation(); // 䌝播停止も远加

              // 排他制埡: 他のフォルダのハむラむトを消す
              document.querySelectorAll('.adv-folder[data-drop="1"]').forEach(el => {
                if (el !== section) delete el.dataset.drop;
              });
              section.dataset.drop='1';

              const dragging = document.querySelector('.adv-item.dragging');
              if (!dragging) return;
              const after = getDragAfterElement(list, ev.clientY);
              if (after == null) list.appendChild(dragging);
              else list.insertBefore(dragging, after);
            });

            list.addEventListener('dragleave', ev => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return;
              ev.stopPropagation();
              // 子芁玠ぞの移動でも䞀旊消すが、dragoverですぐ埩掻する
              delete section.dataset.drop;
            });

            // 䞊び確定か぀別フォルダ→このフォルダぞの“移動”も吞収
            list.addEventListener('drop', (ev) => {
              if (ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME)) return; // ガヌド远加
              ev.preventDefault(); ev.stopPropagation();
              delete section.dataset.drop;
              const draggedId = ev.dataTransfer.getData('text/plain');
              if (!draggedId) return;

              const newOrder = [...list.querySelectorAll('.adv-item')].map(el => el.dataset.id);

              const fArr = loadFoldersFn(foldersKey, defaultFolderName);
              const f = fArr.find(x=>x.id===folder.id);
              if (!f) return;

              const isMove = !f.order.includes(draggedId);
              if (isMove) {
                for (const f_other of fArr) {
                  if (f_other.id === folder.id) continue;
                  const o_before = f_other.order.length;
                  f_other.order = f_other.order.filter(id => id !== draggedId);
                  if (f_other.order.length !== o_before) f_other.ts = Date.now();
                }
              }

              f.order = newOrder;
              f.ts = Date.now();
              saveFoldersFn(foldersKey, fArr);
              showToast(i18n.t('toastReordered'));

              if (isMove) renderFolderedCollection(cfg);
            });

            section.appendChild(header);
            section.appendChild(list);
            return section;
          };

          // 11) 単䞀衚瀺かALL衚瀺か
          const order = (filterFolder !== '__ALL__')
            ? (filterFolder === '__UNASSIGNED__' ? ['__UNASSIGNED__'] : foldersToDraw.map(f => f.id))
            : buildSectionsOrder();

          order.forEach(id => {
            if (id === '__UNASSIGNED__') host.appendChild(renderUnassignedSection());
            else {
              const f = folders.find(x => x.id === id);
              if (f) host.appendChild(renderFolderSection(f));
            }
          });

          if (!host._advFolderDropAttached) { // 倚重登録防止フラグ
              host._advFolderDropAttached = true;

              host.addEventListener('drop', (ev) => {
                  const SECT_MIME = 'adv/folder';
                  if (!(ev.dataTransfer.types && ev.dataTransfer.types.includes(SECT_MIME))) {
                      // アむテムのドロップ (text/plain) は他のリスナヌが凊理するため無芖
                      return;
                  }

                  // セクション䞊び替え (adv/folder) の drop むベント
                  const sectionEl = ev.target.closest('.adv-folder, .adv-unassigned');

                  // むベントが host (コンテナ) たたはその盎䞋の子セクションで発生した堎合のみ凊理
                  if (ev.target === host || (sectionEl && sectionEl.parentElement === host)) {
                      ev.preventDefault();
                      ev.stopPropagation();

                      // dragover で DOM は既に入れ替わっおいるはず
                      persistSectionsFromDOM(); // DOMの珟圚の順序を保存

                      // 保存埌に再描画
                      renderFolderedCollection(cfg);
                  }
              });
          }
        }

        // タブ䞊び替え氎平甚のヘルパヌ
        const getDragAfterElementHorizontal = (container, x, selector) => {
            const els = [...container.querySelectorAll(`${selector}:not(.dragging)`)];
            let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
            for (const el of els) {
                const box = el.getBoundingClientRect();
                // 氎平方向の䞭心からのオフセットを蚈算
                const offset = x - box.left - box.width / 2;
                // 挿入すべき「次の芁玠」オフセットがマむナスで最も0に近いを探す
                if (offset < 0 && offset > closest.offset) {
                    closest = { offset, element: el };
                }
            }
            return closest.element;
        };

        function escapeHTML(s) {
            return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
        }

        // テキスト内のURLをリンク化するHTML゚スケヌプ枈みテキストに察しお適甚
        function safeLinkify(text) {
            if (!text) return '';
            let escaped = escapeHTML(text);

            // Xの仕様で https:// の盎埌に䞍可芖な空癜や改行が含たれる堎合があるため陀去
            // これにより "https:// amzn.to" が "https://amzn.to" に結合され、党䜓が正しくリンク化されたす
            escaped = escaped.replace(/(https?:\/\/)\s+/gi, '$1');

            // URL正芏衚珟
            // Group 1: http/https/www で始たるURL
            // Group 2: プロトコルなしのドメむン (誀怜知防止の埌読み付き)
            const urlRegex = /((?:https?:\/\/|www\.)[^\s]+)|((?<![@\w.:/\-])\b[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}(?:\/[^\s]*)?)/gi;

            return escaped.replace(urlRegex, (match) => {
                let cleanUrl = match;
                let suffix = '';
                const trailingMatch = cleanUrl.match(/[.,;:)\]]+$/);
                if (trailingMatch) {
                    suffix = trailingMatch[0];
                    cleanUrl = cleanUrl.slice(0, -suffix.length);
                }

                let href = cleanUrl;
                if (!href.match(/^(?:https?:|:\/\/)/i)) {
                     href = 'https://' + href;
                }

                return `<a href="${href}" target="_blank" rel="noopener noreferrer" class="adv-content-link">${cleanUrl}</a>${suffix}`;
            });
        }

        function escapeAttr(s) {
          return String(s).replace(/[&<>"']/g, c => (
            {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]
          ));
        }

        function parseSearchTokens(queryOrURL) {
          // 0) ク゚リ取埗URL→怜玢ボックス→モヌダルの順でフォヌルバック
          let qRaw = '';
          try {
            if (queryOrURL) {
              qRaw = String(queryOrURL);
            } else {
              const u = new URL(location.href);
              qRaw = u.searchParams.get('q') || '';
            }
          } catch (_) {}
          if (!qRaw) {
            // â–Œ 耇数圢に倉曎
            const inputs = typeof getActiveSearchInputs === 'function' ? getActiveSearchInputs() : [];
            const si = inputs[0]; // 代衚ずしお最初のものを䜿う
            if (si?.value) qRaw = si.value;
          }
          if (!qRaw && typeof buildQueryStringFromModal === 'function') {
            qRaw = buildQueryStringFromModal() || '';
          }

          // 取埗したク゚リ文字列がキャッシュず同䞀なら、パヌスせずキャッシュを返す
          if (__cachedSearchQuery === qRaw && __cachedSearchTokens) {
              return __cachedSearchTokens;
          }
          // ク゚リが異なるため、パヌスを続行
          __cachedSearchQuery = qRaw; // 新しいク゚リをキャッシュ
          __cachedSearchTokens = null; // 叀いトヌクンを砎棄パヌス倱敗に備える

          // 正芏化%xx/スマヌト匕甚/空癜敎圢
          const rawNorm0 = normalizeForParse(qRaw);
          let q = ` ${rawNorm0} `;

          // 1) 陀倖語-xxxを控えおのちに差し匕く
          const NEG = [];
          (q.match(/\s-\S+/g) || []).forEach(w => NEG.push(w.trim().slice(1)));

          // 2) ORグルヌプ括匧を先に抜き出し匕甚を含む簡易察応
          const orGroups = [];
          const groupRegex = /\((?:[^()"]+|"[^"]*")+\)/g;
          let groupMatch;
          while ((groupMatch = groupRegex.exec(q)) !== null) {
            const inner = groupMatch[0].slice(1, -1); // (...) 䞭身
            const parts = inner.split(/\s+OR\s+/i).map(s => s.trim()).filter(Boolean);
            if (parts.length >= 2) {
              const tokens = parts.flatMap(p => tokenizeQuotedWords(p)).filter(Boolean);
              if (tokens.length) orGroups.push(tokens);
            }
          }
          // グルヌプは䞞ごず削る以降の抜出を安定化
          q = q.replace(groupRegex, ' ');

          // 3) 玔粋トップレベルOR括匧なし怜出䟋`foo OR "bar baz" OR #tag`
          const pureOr = splitTopLevelOR(rawNorm0);
          let pureOrTokens = [];
          if (pureOr && isPureORQuery(rawNorm0)) {
            pureOrTokens = pureOr.flatMap(p => tokenizeQuotedWords(p)).filter(Boolean);
            if (pureOrTokens.length >= 2) {
              orGroups.push(pureOrTokens);
              // 玔粋ORは required には入れない埌で words から陀倖
            }
          }

          // 4) 匕甚フレヌズを抜出exactはAND盞圓ずしお扱う
          const phrases = [];
          q = q.replace(/"([^"]+)"/g, (_m, p1) => {
            if (p1 && (p1 = p1.trim())) phrases.push(p1);
            return ' ';
          });

          // 5) ハッシュタグ抜出
          const hashtags = [];
          q = q.replace(/\s#([^\s)"]+)/g, (_m, p1) => {
            const tag = '#' + p1;
            hashtags.push(tag);
            return ' ';
          });

          // 6) from:/to:/@陀倖ではないもの→ 䟋倖刀定甚 opUsers
          const opUsers = new Set();
          rawNorm0.replace(/(?:^|\s)(?:from:|to:|@)([^\s()]+)/g, (m, user) => {
            // 盎前が "-" の吊定挔算子なら陀倖䟋: "-from:foo"
            if (!/^\s*-/.test(m)) {
              opUsers.add(String(user || '').toLowerCase());
            }
            return m;
          });

          // 7) 蚀語/最小倀/日付/フィルタ/アカりント挔算子などを q から陀去
          q = q
            .replace(/\s(?:lang|min_replies|min_faves|min_retweets|since|until):[^\s]+/gi, ' ')
            .replace(/\s(?:is:verified|filter:(?:links|images|videos|replies)|include:replies|-filter:replies)\b/gi, ' ')
            .replace(/\s(?:from:|to:|@)[^\s()]+/gi, ' ')
            .replace(/[()]/g, ' ')
            .replace(/\bOR\b/gi, ' ');

          // 8) 残りを単語化句読点剥がし。#は枩存枈み
          const trimPunctKeepHash = (s) => {
            if (!s) return '';
            if (s.startsWith('#')) return s;
            return s.replace(/^[\p{P}\p{S}]+/gu, '').replace(/[\p{P}\p{S}]+$/gu, '');
          };

          let words = q
            .split(/\s+/)
            .map(s => s.trim())
            .filter(Boolean)
            .map(trimPunctKeepHash)
            .filter(Boolean);

          // 9) NEG を差し匕く
          const normalize = (s) => String(s || '').toLowerCase();
          const NEGnorm = NEG.map(normalize);

          // 10) 玔粋ORで拟ったトヌクンは AND 候補から先に陀倖重耇/衝突を避ける
          if (pureOrTokens.length) {
            const pureSet = new Set(pureOrTokens.map(t => t.toLowerCase()));
            const stripQuote = (s) => s.replace(/^"(.*)"$/, '$1').toLowerCase();
            words = words.filter(w => !pureSet.has(stripQuote(w)));
          }

          // 11) requiredAND盞圓を構成フレヌズ + ハッシュタグ + 通垞語
          const requiredTermsArr = [
            ...phrases,
            ...hashtags,
            ...words.filter(w => !NEGnorm.includes(normalize(w))),
          ];

          // 12) includeTerms埓来互換required + OR党トヌクン平坊化
          const includeTerms = new Set([
            ...requiredTermsArr,
            ...orGroups.flatMap(g => g),
          ]);

          // 13) hashtagSet
          const hashtagSet = new Set(
            hashtags.map(h => h.startsWith('#') ? h : ('#' + h)).map(normalize)
          );

          // 14) 返华requiredはSet、orGroupsは配列の配列
          const result = {
            requiredTerms: new Set(requiredTermsArr),
            orGroups,                  // [ ['ente','セヌル'], ['foo','bar'] , ... ]
            includeTerms,              // AND/ORすべおを平坊化した包含語集合
            opUsers,
            hashtagSet,
          };

          __cachedSearchTokens = result; // パヌス結果をキャッシュに保存
          return result;
        }

        function pickTweetFields(article) {
            // 1. 本文の取埗
            const bodyEl = article.querySelector('[data-testid="tweetText"]');
            const body = bodyEl ? bodyEl.innerText : '';

            let disp = '';
            let handle = '';

            try {
                // 2. ナヌザヌ情報゚リア (User-Name) を取埗
                // 共有いただいたDOMでは、ここに衚瀺名ずハンドル(@xxx)の䞡方が含たれおいたす
                const userRow = article.querySelector('[data-testid="User-Name"]');

                if (userRow) {
                    // User-Name内のすべおの「リンク」たたは「テキストコンテナ」をチェック
                    // 共有DOMでは aタグの䞭に span がある構造です
                    const anchors = Array.from(userRow.querySelectorAll('a[href^="/"], div[dir="ltr"] span'));

                    for (const node of anchors) {
                        // テキストを取埗前埌の空癜を陀去
                        const text = node.innerText.trim();

                        // 空文字、たたは時間衚瀺の区切り蚘号「·」などは無芖
                        if (!text || text === '·') continue;

                        // â–Œ 刀定ロゞック: テキストが '@' で始たるならハンドル、そうでなければ衚瀺名
                        if (text.startsWith('@')) {
                            // ハンドルが芋぀かった (@を陀去しお保存)
                            handle = text.replace(/^@/, '');
                        } else {
                            // ただ衚瀺名がセットされおいなければ、これを衚瀺名ずする
                            // (怜蚌枈みアカりントのアむコンなどがテキストずしお混ざるのを防ぐため、ある皋床の長さチェックを入れおも良いが、基本はこのたたでOK)
                            if (!disp) {
                                disp = text;
                            }
                        }
                    }

                    // フォヌルバック: 䞊蚘で芋぀からなかった堎合、User-Name盎䞋の党テキストから解析
                    if (!handle) {
                        const allText = userRow.innerText.split('\n');
                        for (const t of allText) {
                            const trimT = t.trim();
                            if (trimT.startsWith('@')) {
                                handle = trimT.replace(/^@/, '');
                            } else if (!disp && trimT && trimT !== '·') {
                                disp = trimT;
                            }
                        }
                    }
                }
            } catch(e) {
                console.error('[pickTweetFields] Error parsing user info:', e);
            }

            // 3. 返信先ハンドルの取埗 ("Replying to @..." の郚分)
            // 本文や自分の名前以倖で、ヘッダヌ付近にある @リンク を探す
            const replyHandles = Array.from(
                article.querySelectorAll('div[dir="ltr"] a[href^="/"]')
            )
            .filter(a => {
                const txt = (a.innerText || '').trim();
                // @で始たるリンクであるこず
                if (!txt.startsWith('@')) return false;

                // 本文内のメンションは陀倖
                if (bodyEl && bodyEl.contains(a)) return false;

                // 送信者自身のハンドル衚蚘は陀倖
                const userRow = article.querySelector('[data-testid="User-Name"]');
                if (userRow && userRow.contains(a)) return false;

                return true;
            })
            .map(a => a.innerText.trim())
            .filter(Boolean);

            return { body, disp, handle, replyHandles };
        }

        function getTweetCell(article) {
          return article.closest('[data-testid="cellInnerDiv"]') || article;
        }

        function shouldHideTweetByNameHandle(article, flags, tokens) {
          const {
            requiredTerms = new Set(),
            orGroups = [],
            includeTerms = new Set(),
            opUsers,
            hashtagSet
          } = tokens || {};

          if (includeTerms.size === 0) return false;

          const { body, disp, handle, replyHandles } = pickTweetFields(article);

          // 正芏化系ナヌティリティ本文怜玢はスペヌス正芏化
          const normSpace = (s) => String(s || '')
            .toLowerCase()
            .replace(/[_.\-]+/g, ' ')
            .replace(/\s+/g, ' ')
            .trim();

          const normId = (s) => String(s || '').replace(/^@/, '').toLowerCase();
          const stripNonAlnum = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9\u00c0-\u024f]+/gi, '');

          const textBody = normSpace(body);
          const textName = normSpace(disp);

          // ハンドル矀の正芏化
          const handlesRaw   = [handle, ...replyHandles].map(normId).filter(Boolean);
          const handlesSpace = handlesRaw.map(normSpace);
          const handlesTok   = handlesSpace.map(h => h.split(' ').filter(Boolean));
          const handlesTight = handlesRaw.map(stripNonAlnum);

          // 本文に珟れた語正芏化枈みを控える
          const inBody = new Set();
          for (const term of includeTerms) {
            const t = normSpace(term);
            if (t && textBody.includes(t)) inBody.add(t);
          }

          // 名前/ハンドルで呜䞭した語を蚘録本文に出おいるものは陀倖しお蚘録しない
          const inMeta = new Set(); // normSpace/stripNonAlnum の䞡方を入れる

          const markMetaHit = (tSpace, tTight) => {
            if (tSpace && !inBody.has(tSpace)) inMeta.add(tSpace);
            if (tTight) inMeta.add(tTight);
          };

          // --- 衚瀺名ヒットの蚘録短語ガヌド぀き ---
          if (flags.name) {
            for (const term of includeTerms) {
              const t = normSpace(term);
              if (!t) continue;
              // 2文字以䞋の英字のみは無芖過剰陀倖防止
              if (/^[a-z]{1,2}$/.test(t)) continue;
              if (textName.includes(t) && !inBody.has(t)) {
                markMetaHit(t, null);
              }
            }
          }

          // --- @ナヌザヌ名ヒットの蚘録挔算子䟋倖/短語ガヌド/境界 ---
          if (flags.handle) {
            for (const term of includeTerms) {
              const raw = String(term || '');
              const rawLC = raw.trim().toLowerCase();

              // ハッシュタグは察象倖
              if (rawLC.startsWith('#') || (hashtagSet && hashtagSet.has(rawLC.startsWith('#') ? rawLC : '#' + rawLC))) {
                continue;
              }

              const bare = raw.replace(/^@/, '').toLowerCase();
              if (opUsers && opUsers.has(bare)) continue; // from:/to:/@ 明瀺は䟋倖

              const tSpace = normSpace(raw);
              const tTight = stripNonAlnum(raw);

              // 短語ガヌド英数のみで長さ<3は無芖
              if (/^[a-z0-9]+$/.test(tTight) && tTight.length < 3) continue;

              // 1) トヌクン䞀臎/連続トヌクン䞀臎
              if (tSpace) {
                const tTokens = tSpace.split(' ').filter(Boolean);
                for (const hTokens of handlesTok) {
                  if (tTokens.length === 1) {
                    if (hTokens.some(tok => tok === tTokens[0]) && !inBody.has(tSpace)) {
                      markMetaHit(tSpace, null);
                      break;
                    }
                  } else {
                    for (let i = 0; i + tTokens.length <= hTokens.length; i++) {
                      let ok = true;
                      for (let j = 0; j < tTokens.length; j++) {
                        if (hTokens[i + j] !== tTokens[j]) { ok = false; break; }
                      }
                      if (ok && !inBody.has(tSpace)) {
                        markMetaHit(tSpace, null);
                        break;
                      }
                    }
                  }
                }
              }

              // 2) 非英数字陀去の完党䞀臎郚分䞀臎は䞍可
              if (tTight && handlesTight.some(h => h === tTight) && !(tSpace && inBody.has(tSpace))) {
                markMetaHit(tSpace, tTight);
              }
            }
          }

          // === 最終刀定 ===
          // ANDrequiredTerms: “本文に出おいない & metaでのみヒット” が1語でもあれば隠す
          for (const t of requiredTerms) {
            const s = normSpace(t);
            if (s && !inBody.has(s) && (inMeta.has(s) || inMeta.has(stripNonAlnum(t)))) {
              return true;
            }
          }

          // ORorGroups: 各グルヌプが「本文で満たされおいないのに metaだけで満たされる」堎合は隠す
          for (const group of orGroups) {
            let anyBody = false;
            let anyMeta = false;
            for (const w of group) {
              const s = normSpace(w);
              const tight = stripNonAlnum(w);
              if (s && inBody.has(s)) anyBody = true;
              if (s && inMeta.has(s)) anyMeta = true;
              if (tight && inMeta.has(tight)) anyMeta = true;
              if (anyBody && anyMeta) break;
            }
            if (!anyBody && anyMeta) return true;
          }

          // ここたで来たら隠さない
          return false;
        }

        function evaluateTweetForFiltering(art, flags, muteSettings, tokens) {
            const cell = getTweetCell(art);
            const reasons = [];
            let tweetBodyText = null; // 本文テキストのキャッシュ甚

            const { hasMute, muteCI, muteCS } = muteSettings;

            // 1. 名前/ハンドル陀倖
            if ((flags.name || flags.handle) && tokens) {
                const hideByNameHandle = shouldHideTweetByNameHandle(art, flags, tokens);
                if (hideByNameHandle) reasons.push('name_handle_only');
            }

            // 2. ミュヌトワヌド陀倖
            if (hasMute) {
                tweetBodyText = tweetBodyText ?? (art.querySelector('[data-testid="tweetText"]')?.innerText || '');
                const bodyCI = tweetBodyText.toLowerCase();
                let hideByMute = false;
                for (const w of muteCI) { if (w && bodyCI.includes(w)) { hideByMute = true; break; } }
                if (!hideByMute) {
                  for (const w of muteCS) { if (w && tweetBodyText.includes(w)) { hideByMute = true; break; } }
                }
                if (hideByMute) reasons.push('muted_word');
            }

            // 3. リポスト陀倖
            if (flags.reposts) {
                const socialContext = art.querySelector('[data-testid="socialContext"]');
                if (socialContext) {
                    const pinIconPath = 'M7 4.5C7 3.12 8.12 2 9.5 2h5C15.88 2 17 3.12 17 4.5v5.26L20.12 16H13v5l-1 2-1-2v-5H3.88L7 9.76V4.5z';
                    const isPinned = art.querySelector(`svg path[d="${pinIconPath}"]`);
                    if (!isPinned) {
                        reasons.push('repost');
                    }
                }
            }

            // 4. ハッシュタグ陀倖
            if (flags.hashtags) {
                tweetBodyText = tweetBodyText ?? (art.querySelector('[data-testid="tweetText"]')?.innerText || '');
                if (tweetBodyText.includes('#')) {
                  reasons.push('hashtag');
                }
            }

            // 最終刀定
            if (reasons.length > 0) {
                cell.setAttribute('data-adv-hidden', reasons.join(' '));
            } else {
                cell.removeAttribute('data-adv-hidden');
            }

            // 凊理枈みフラグは呌び出し元(processNewTweets)で共通化するため、ここではセットしない
            // art.setAttribute('data-adv-processed-filter', '1');
        }

        // â–Œ ミュヌト蚭定倉曎時などに、党ツむヌトを匷制再スキャンする
        function rescanAllTweetsForFilter() {
            try {
                const flags = {
                    name:   document.getElementById('adv-exclude-hit-name')?.checked ?? true,
                    handle: document.getElementById('adv-exclude-hit-handle')?.checked ?? true,
                    reposts: document.getElementById('adv-filter-reposts-exclude')?.checked ?? false,
                    hashtags: document.getElementById('adv-filter-hashtags-exclude')?.checked ?? false,
                };

                const masterOn = loadMuteMaster();
                const muted = loadMuted();
                const hasMute = masterOn && muted.length > 0;
                const enabledMuted = hasMute ? muted.filter(m => m.enabled !== false) : [];
                const muteSettings = {
                    hasMute,
                    muteCI: enabledMuted.length ? new Set(enabledMuted.filter(m => !m.cs).map(m => m.word.toLowerCase())) : new Set(),
                    muteCS: enabledMuted.length ? enabledMuted.filter(m => m.cs).map(m => m.word) : [],
                };

                if (!flags.name && !flags.handle && !hasMute && !flags.reposts && !flags.hashtags) {
                    document.querySelectorAll('[data-adv-hidden]').forEach(cell => {
                        cell.removeAttribute('data-adv-hidden');
                    });
                    cleanupAdjacentSeparators();
                    return;
                }

                const tokens = (flags.name || flags.handle) ? parseSearchTokens() : null;
                // 党ツむヌトを察象
                const list = document.querySelectorAll('article[data-testid="tweet"]');

                for (const art of list) {
                    // 共通関数を呌ぶ
                    evaluateTweetForFiltering(art, flags, muteSettings, tokens);
                }

                cleanupAdjacentSeparators();
            } catch (e) {
                console.error('rescanAllTweetsForFilter failed', e);
            }
        }

        function cleanupAdjacentSeparators() {
          // 既存のたた必芁ならここに区切り線セルの非衚瀺凊理
        }

        const executeSearch = async (scopesOverride) => {
          const finalQuery = buildQueryStringFromModal().trim();
          if (!finalQuery) return;

          const scopes = scopesOverride || readScopesFromControls();
          const params = new URLSearchParams({ q: finalQuery, src: 'typed_query' });
          if (scopes.pf) params.set('pf', 'on');
          if (scopes.lf) params.set('lf', 'on');

          const targetPath = `/search?${params.toString()}`;

          // 1) たず怜玢ボックスが芋぀かれば React state を曎新しお芋た目ず䞭身を同調
          // â–Œ 耇数圢に倉曎し、ルヌプ凊理
          const inputs = getActiveSearchInputs?.() || [];
          if (inputs.length > 0) {
              inputs.forEach(si => {
                  if (si) { syncControlledInput(si, finalQuery); }
              });
          }

          // 2) ルヌトに関わらず垞に SPA 遷移で怜玢を確定
          recordHistory(finalQuery, scopes.pf, scopes.lf);
          const before = location.href;
          try {
            await spaNavigate(targetPath);
            if (window.innerWidth <= 700) {
                closeModal();
            }
          } catch {
            // SPA 倱敗時のフォヌルバック
            location.assign(`https://x.com${targetPath}`);
            return;
          }

          // 3) 遷移が成功したら䜙蚈な replaceState はしないURL ずルヌタヌ state の乖離を避ける
          //    もしフォヌカスが残っおいたら倖す
          // â–Œ ルヌプ凊理
          try { inputs.forEach(si => si && si.blur()); } catch {}

        };

        const onScopeChange = async () => {
        // â–Œ 耇数圢に倉曎
          const inputs = getActiveSearchInputs();
          const si = inputs[0]; // 代衚ずしお最初のものを䜿う
          const q = (() => {
            if (si && si.value && si.value.trim()) return si.value.trim();
            return buildQueryStringFromModal().trim();
          })();

          const { pf, lf } = readScopesFromControls();
          const params = new URLSearchParams({ src: 'typed_query' });
          if (q) params.set('q', q);
          if (pf) params.set('pf', 'on');
          if (lf) params.set('lf', 'on');

          // 入力偎を先に最新化
          // â–Œ ルヌプ凊理
          if (inputs.length > 0) {
              inputs.forEach(input => {
                  if (input) syncControlledInput(input, q);
              });
          }

          recordHistory(q, pf, lf);
          const path = `/search?${params.toString()}`;
          try {
            await spaNavigate(path);
          } catch {
            location.assign(`https://x.com${path}`);
          }
        };
        accountScopeSel.addEventListener('change', onScopeChange);
        locationScopeSel.addEventListener('change', onScopeChange);

        const setupModalDrag = () => {
            const header = modal.querySelector('.adv-modal-header');
            let dragging=false, offset={x:0,y:0};
            header.addEventListener('mousedown', e=>{
                if (e.target.matches('button,a') && !e.target.classList.contains('adv-secret-btn')) return;
                dragging=true;
                const rect = modal.getBoundingClientRect();
                modal.style.right=modal.style.bottom='auto';
                modal.style.left=`${rect.left}px`; modal.style.top=`${rect.top}px`;
                offset = { x:e.clientX-rect.left, y:e.clientY-rect.top };
                document.body.classList.add('adv-dragging');
            });
            document.addEventListener('mousemove', e=>{
                if(!dragging) return;
                let nx = e.clientX - offset.x, ny = e.clientY - offset.y;
                nx=Math.max(0,Math.min(nx,window.innerWidth - modal.offsetWidth));
                ny=Math.max(0,Math.min(ny,window.innerHeight - modal.offsetHeight));
                modal.style.left=`${nx}px`; modal.style.top=`${ny}px`;
            });
            document.addEventListener('mouseup', ()=>{
                if(dragging){ dragging=false; document.body.classList.remove('adv-dragging'); saveModalRelativeState(); }
            });
        };

        const setupModalResize = () => {
            const MIN_W = 300, MIN_H = 240;
            const MARGIN = 10;
            let resizing = null;

            const onPointerDown = (e) => {
                const h = e.target.closest('.adv-resizer');
                if (!h) return;
                e.preventDefault();
                const dir = h.dataset.dir;
                const r = modal.getBoundingClientRect();

                modal.style.right = 'auto';
                modal.style.bottom= 'auto';
                modal.style.left  = `${r.left}px`;
                modal.style.top   = `${r.top}px`;

                resizing = {
                    dir,
                    startX: e.clientX,
                    startY: e.clientY,
                    startLeft: r.left,
                    startTop:  r.top,
                    startW: r.width,
                    startH: r.height
                };
                try { h.setPointerCapture(e.pointerId); } catch(_) {}
                document.body.classList.add('adv-dragging');
            };

            const clamp = (val, min, max) => Math.max(min, Math.min(max, val));

            const onPointerMove = (e) => {
                if (!resizing) return;

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

                let newLeft = resizing.startLeft;
                let newTop  = resizing.startTop;
                let newW    = resizing.startW;
                let newH    = resizing.startH;

                const dir = resizing.dir;

                if (dir.includes('e')) newW = resizing.startW + dx;
                if (dir.includes('w')) { newW = resizing.startW - dx; newLeft = resizing.startLeft + dx; }

                if (dir.includes('s')) newH = resizing.startH + dy;
                if (dir.includes('n')) { newH = resizing.startH - dy; newTop = resizing.startTop + dy; }

                const maxW = window.innerWidth  - 2*MARGIN;
                const maxH = window.innerHeight - 2*MARGIN;

                newW = clamp(newW, MIN_W, maxW);
                newH = clamp(newH, MIN_H, maxH);
                newLeft = clamp(newLeft, MARGIN, Math.max(MARGIN, window.innerWidth  - newW - MARGIN));
                newTop  = clamp(newTop,  MARGIN, Math.max(MARGIN, window.innerHeight - newH - MARGIN));

                modal.style.left   = `${Math.round(newLeft)}px`;
                modal.style.top    = `${Math.round(newTop)}px`;
                modal.style.width  = `${Math.round(newW)}px`;
                modal.style.height = `${Math.round(newH)}px`;
            };

            const onPointerUp = (e) => {
                if (!resizing) return;
                document.body.classList.remove('adv-dragging');
                try { e.target.releasePointerCapture?.(e.pointerId); } catch(_) {}
                resizing = null;
                saveModalRelativeState();
            };

            modal.addEventListener('pointerdown', onPointerDown);
            window.addEventListener('pointermove', onPointerMove);
            window.addEventListener('pointerup',   onPointerUp);
            window.addEventListener('pointercancel', onPointerUp);
        };

        /* ========= Accounts storage & UI ========= */
        function renderAccountRow(item) {
          const row = document.createElement('div');
          row.className = 'adv-item';
          row.draggable = true;
          row.dataset.id = item.id;

          const title = escapeHTML(item.name || `@${item.handle}`);
          const sub   = escapeHTML(`@${item.handle}`);

          row.innerHTML = `
            <div class="adv-item-handle" title="Drag">≡</div>
            ${
              item.avatar
                ? `<a class="adv-item-avatar-link adv-link" href="/${escapeAttr(item.handle)}" title="@${escapeAttr(item.handle)}">
                     <img class="adv-item-avatar" src="${escapeAttr(item.avatar)}" alt="@${escapeAttr(item.handle)}">
                   </a>`
                : `<a class="adv-item-avatar-link adv-link" href="/${escapeAttr(item.handle)}" title="@${escapeAttr(item.handle)}">
                     <div class="adv-item-avatar" aria-hidden="true"></div>
                   </a>`
            }
            <div class="adv-item-main">
              <div class="adv-item-title">
                <a class="adv-link" href="/${escapeAttr(item.handle)}" title="@${escapeAttr(item.handle)}">${title}</a>
              </div>
              <div class="adv-item-sub">
                <a class="adv-link" href="/${escapeAttr(item.handle)}">@${escapeHTML(item.handle)}</a>
                <span>${fmtTime(item.ts)}</span>
              </div>
            </div>
            <div class="adv-item-actions">
              <button class="adv-chip primary" data-action="confirm">${i18n.t('buttonConfirm')}</button>
              <button class="adv-chip danger" data-action="delete">${i18n.t('delete')}</button>
            </div>
          `;

          row.querySelector('[data-action="confirm"]').addEventListener('click', (e) => {
            spaNavigate(`/${item.handle}`, { ctrlMeta: e.ctrlKey || e.metaKey });
            if (window.innerWidth <= 700) {
                closeModal();
            }
          });
          row.querySelectorAll('a.adv-link').forEach(a => {
            a.addEventListener('click', (ev) => {
              if (ev.defaultPrevented || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey || ev.button !== 0) return;
              ev.preventDefault();
              const href = a.getAttribute('href') || `/${item.handle}`;
              spaNavigate(href, { ctrlMeta: false });
              if (window.innerWidth <= 700) {
                  closeModal();
              }
            });
          });
          row.querySelector('[data-action="delete"]').addEventListener('click', () => deleteAccount(item.id));

          row.addEventListener('dragstart', (ev) => {
            row.classList.add('dragging');
            ev.dataTransfer.setData('text/plain', item.id);
            ev.dataTransfer.effectAllowed = 'move';
          });
          row.addEventListener('dragend', () => row.classList.remove('dragging'));

          return row;
        }

        function renderAccounts() {
          ensureFolderToolbars();

          renderFolderedCollection({
            hostId: 'adv-accounts-list',
            emptyId: 'adv-accounts-empty',
            filterSelectId: 'adv-accounts-folder-filter',
            searchInputId:  'adv-accounts-search',
            newFolderBtnId: 'adv-accounts-new-folder',

            foldersKey: ACCOUNTS_FOLDERS_KEY,
            defaultFolderName: i18n.t('optAccountAll'),

            loadItems: loadAccounts,
            saveItems: saveAccounts,
            renderRow: renderAccountRow,

            onUnassign: unassignAccount,
            onMoveToFolder: moveAccountToFolder,

            emptyMessage: i18n.t('emptyAccounts'),
            unassignedIndexKey: 'advAccountsUnassignedIndex_v1',
          });
        }

        function renderListRow(item) {
          const row = document.createElement('div');
          row.className = 'adv-item';
          row.draggable = true;
          row.dataset.id = item.id;

          const title = escapeHTML(item.name);
          const sub   = escapeHTML(item.url);

          row.innerHTML = `
            <div class="adv-item-handle" title="Drag">≡</div>
            <div class="adv-item-main">
              <div class="adv-item-title">
                <a class="adv-link" href="${escapeAttr(item.url)}">${title}</a>
              </div>
              <div class="adv-item-sub">
                <a class="adv-link" href="${escapeAttr(item.url)}">${sub}</a>
                <span>${fmtTime(item.ts)}</span>
              </div>
            </div>
            <div class="adv-item-actions">
              <button class="adv-chip primary" data-action="confirm">${i18n.t('buttonConfirm')}</button>
              <button class="adv-chip danger" data-action="delete">${i18n.t('delete')}</button>
            </div>
          `;

          row.querySelector('[data-action="confirm"]').addEventListener('click', (e) => {
            spaNavigate(item.url, { ctrlMeta: e.ctrlKey || e.metaKey });
            if (window.innerWidth <= 700) {
                closeModal();
            }
          });
          row.querySelectorAll('a.adv-link').forEach(a => {
            a.addEventListener('click', (ev) => {
              if (ev.defaultPrevented || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey || ev.button !== 0) return;
              ev.preventDefault();
              const href = a.getAttribute('href') || item.url;
              spaNavigate(href, { ctrlMeta: false });
              if (window.innerWidth <= 700) {
                  closeModal();
              }
            });
          });
          row.querySelector('[data-action="delete"]').addEventListener('click', () => deleteList(item.id));

          row.addEventListener('dragstart', (ev) => {
            row.classList.add('dragging');
            ev.dataTransfer.setData('text/plain', item.id);
            ev.dataTransfer.effectAllowed = 'move';
          });
          row.addEventListener('dragend', () => row.classList.remove('dragging'));

          return row;
        }


        const ACCOUNTS_KEY = 'advAccounts_v1';
        const ACCOUNTS_FOLDERS_KEY = 'advAccountsFolders_v1';
        const LISTS_FOLDERS_KEY    = 'advListsFolders_v1';
        // â–Œ セクションフォルダヌ + Unassignedの䞊び順を氞続化するキヌ
        const SAVED_FOLDERS_KEY    = 'advSavedFolders_v1'

        function loadFolders(key, _defaultName="") {
          const raw = loadJSON(key, null);
          if (raw && Array.isArray(raw.folders)) {
            return raw.folders.map(f => ({
              id: f.id,
              name: f.name,
              order: Array.isArray(f.order) ? f.order : [],
              ts: f.ts || Date.now(),
              collapsed: !!f.collapsed,
            }));
          }
          // 初期は空配列フォルダヌ0件の䞖界
          return [];
        }

        function saveFolders(key, folders) {
          saveJSON(key, { folders: folders.map(f=>({
            id:f.id, name:f.name, order:[...new Set(f.order)], ts:f.ts||Date.now(), collapsed: !!f.collapsed,
          }))});
        }

        function ensureFolderToolbars() {
          // Accounts tab
          {
            const host = document.getElementById('adv-accounts-list');
            const empty = document.getElementById('adv-accounts-empty');
            const target = empty || host; // emptyがあればその前に挿入HTML順序が empty->list なので䞀番䞊になる
            if (target && !target.previousElementSibling?.classList?.contains('adv-folder-toolbar')) {
              const bar = document.createElement('div');
              bar.className = 'adv-folder-toolbar';
              bar.innerHTML = `
                <select id="adv-accounts-folder-filter" class="adv-select"></select>
                <input id="adv-accounts-search" class="adv-input" type="text" data-i18n-placeholder="placeholderFilterAccounts" placeholder="${i18n.t('placeholderFilterAccounts')}">
                <button id="adv-accounts-new-folder" class="adv-chip" data-i18n="buttonAddFolder">${i18n.t('buttonAddFolder')}</button>
              `;
              target.parentElement.insertBefore(bar, target);
            }
          }
          // Lists tab
          {
            const host = document.getElementById('adv-lists-list');
            const empty = document.getElementById('adv-lists-empty');
            const target = empty || host;
            if (target && !target.previousElementSibling?.classList?.contains('adv-folder-toolbar')) {
              const bar = document.createElement('div');
              bar.className = 'adv-folder-toolbar';
              bar.innerHTML = `
                <select id="adv-lists-folder-filter" class="adv-select"></select>
                <input id="adv-lists-search" class="adv-input" type="text" data-i18n-placeholder="placeholderFilterLists" placeholder="${i18n.t('placeholderFilterLists')}">
                <button id="adv-lists-new-folder" class="adv-chip" data-i18n="buttonAddFolder">${i18n.t('buttonAddFolder')}</button>
              `;
              target.parentElement.insertBefore(bar, target);
            }
          }
          // Saved tab
          {
            const host = document.getElementById('adv-saved-list');
            const empty = document.getElementById('adv-saved-empty');
            const target = empty || host;
            if (target && !target.previousElementSibling?.classList?.contains('adv-folder-toolbar')) {
              const bar = document.createElement('div');
              bar.className = 'adv-folder-toolbar';
              bar.innerHTML = `
                <select id="adv-saved-folder-filter" class="adv-select"></select>
                <input id="adv-saved-search" class="adv-input" type="text" data-i18n-placeholder="placeholderSearchSaved" placeholder="${i18n.t('placeholderSearchSaved')}">
                <button id="adv-saved-new-folder" class="adv-chip" data-i18n="buttonAddFolder">${i18n.t('buttonAddFolder')}</button>
              `;
              target.parentElement.insertBefore(bar, target);
            }
          }
        }

        const migrateAccounts = (list) =>
          Array.isArray(list)
            ? list
                .map(it => ({
                  id: it.id || uid(),
                  handle: (it.handle || '').replace(/^@/, '').trim(),
                  name: (it.name || '').trim(),
                  avatar: it.avatar || '',
                  ts: it.ts || Date.now(),
                }))
                .filter(it => it.handle)
            : [];
        const loadAccounts = () => migrateAccounts(loadJSON(ACCOUNTS_KEY, []));
        const saveAccounts = (arr) => saveJSON(ACCOUNTS_KEY, migrateAccounts(arr));
        // 远加 or 曎新既存があれば name / avatar 差分のみ曎新
        const addAccount = ({ handle, name='', avatar='' }) => {
          const h = (handle || '').replace(/^@/, '').trim();
          if (!h) return 'empty';
          const list = loadAccounts();
          const ix = list.findIndex(x => x.handle.toLowerCase() === h.toLowerCase());
          if (ix >= 0) {
            let changed = false;
            if (name && name !== list[ix].name) { list[ix].name = name; changed = true; }
            if (avatar && avatar !== list[ix].avatar) { list[ix].avatar = avatar; changed = true; }
            if (changed) {
              list[ix].ts = Date.now();
              saveAccounts(list);
              renderAccounts();
              return 'updated';
            }
            return 'exists';
          }
          const id = uid();
          list.unshift({ id, handle: h, name, avatar, ts: Date.now() });
          saveAccounts(list);
          // フォルダヌぞは入れない未所属のたた
          try {
            const folders = loadFolders(ACCOUNTS_FOLDERS_KEY, i18n.t('optAccountAll'));
            // 念のため党フォルダヌから重耇を陀去だけしお保存未所属を保持
            folders.forEach(f => { f.order = f.order.filter(x => x !== id); });
            saveFolders(ACCOUNTS_FOLDERS_KEY, folders);
          } catch(_) {}
          renderAccounts();
          return 'ok';
        };
        // 既存アカりントがある堎合だけ name / avatar を曎新未登録なら䜕もしない
        const updateAccountIfExists = ({ handle, name='', avatar='' }) => {
          const h = (handle || '').replace(/^@/, '').trim();
          if (!h) return 'empty';
          const list = loadAccounts();
          const ix = list.findIndex(x => x.handle.toLowerCase() === h.toLowerCase());
          if (ix < 0) return 'not_found';
          let changed = false;
          if (name && name !== list[ix].name)   { list[ix].name   = name;   changed = true; }
          if (avatar && avatar !== list[ix].avatar) { list[ix].avatar = avatar; changed = true; }
          if (changed) {
            list[ix].ts = Date.now();
            saveAccounts(list);
            renderAccounts();
            return 'updated';
          }
          return 'unchanged';
        };
        const deleteAccount = (id) => {
            // â–Œ 削陀察象のハンドルを保持しおおく
            const accounts = loadAccounts();
            const deletedAccount = accounts.find(x => x.id === id);
            const deletedHandle = deletedAccount?.handle.toLowerCase();

            const next = accounts.filter(x => x.id !== id); // accounts倉数を䜿甚
            saveAccounts(next);
            renderAccounts();
            showToast(i18n.t('toastDeleted'));

            // â–Œ ペヌゞ䞊のボタンを匷制再描画
            // 珟圚のペヌゞハンドルを取埗
            const currentHandle = getProfileHandleFromURL()?.toLowerCase();
            // もし削陀したアカりントのペヌゞに今たさに居るなら、ボタンを匷制曎新
            if (deletedHandle && currentHandle === deletedHandle) {
                ensureProfileAddButton(true);
            }
        };

        const accountsListEl  = document.getElementById('adv-accounts-list');
        const advSavedListEl  = document.getElementById('adv-saved-list');

        function getProfileHandleFromURL(href = location.href) {
          try {
            const u = new URL(href, location.origin);
            const segs = u.pathname.split('/').filter(Boolean);
            if (segs.length === 0) return '';

            // 先頭セグメントを候補にする
            const first = segs[0];

            // 明らかな非プロフィヌルの予玄セグメントを陀倖
            const RESERVED = new Set([
              'home','explore','notifications','messages','i','settings',
              'compose','search','login','signup','tos','privacy','about'
            ]);
            if (RESERVED.has(first)) return '';

            // ナヌザヌ名パタヌン: プロフ盎䞋/配䞋タブ/with_replies, /media, /likes 等を蚱容
            if (/^[A-Za-z0-9_]{1,50}$/.test(first)) {
              return first; // /<handle> や /<handle>/with_replies /media /likes ... をすべおカバヌ
            }

            return '';
          } catch {
            // DOM フォヌルバック
            try {
              const a = document.querySelector('[data-testid="User-Name"] a[href^="/"], [data-testid="UserName"] a[href^="/"]');
              if (a) {
                const m = (a.getAttribute('href') || '').match(/^\/([A-Za-z0-9_]{1,50})/);
                if (m) return m[1];
              }
            } catch (_) {}
            return '';
          }
        }

        // 指定ハンドルのプロフィヌル領域だけをスコヌプにしお name / avatar を取埗
        function collectProfileMeta(handle) {
          let name = '';
          let avatar = '';
          try {
            const h = String(handle || '').replace(/^@/, '').trim();

            // 1) プロフィヌル領域衚瀺名
            //    ※ グロヌバルヘッダの自分の名前を拟わないように、最初に [data-testid="UserName"] を基準に限定
            const profileRoot =
              document.querySelector('[data-testid="UserName"]') ||
              document.querySelector('[data-testid="User-Name"]');

            if (profileRoot) {
              const texts = Array.from(profileRoot.querySelectorAll('span, div[dir="auto"]'))
                .map(el => (el.textContent || '').trim())
                .filter(Boolean);
              // 䟋: ["みみる@米囜株投資", "@mimiru_usstock", ...]
              name = texts.find(t => !t.startsWith('@')) || '';
            }

            // 2) アバタヌ領域をハンドルで限定
            //    DOM䟋: <div data-testid="UserAvatar-Container-mimiru_usstock"> ... </div>
            let avatarScope = null;
            if (h) {
              avatarScope = document.querySelector(`[data-testid="UserAvatar-Container-${CSS.escape(h)}"]`);
            }
            // フォヌルバックハンドル付き data-testid が無い叀い/差分レむアりト
            if (!avatarScope) {
              // プロフィヌルのヘッダ右偎の塊に限定
              avatarScope = profileRoot?.closest('[data-testid="UserProfileHeader_Items"]')?.parentElement
                         || profileRoot?.parentElement
                         || document;
            }

            // 2-1) たず <img> 優先
            const img = avatarScope.querySelector('img[src*="profile_images"]');
            if (img?.src) {
              avatar = img.src;
            } else {
              // 2-2) 背景画像 style="background-image:url(...)" から抜出
              //     提瀺DOMの:
              //     <div class="... r-1wyyakw ..." style="background-image:url('...')"></div>
              const bg = avatarScope.querySelector('[style*="background-image"]');
              if (bg) {
                const m = String(bg.getAttribute('style') || '').match(/background-image:\s*url\((["']?)(.*?)\1\)/i);
                if (m && m[2]) avatar = m[2];
              }
            }

            // バナヌ(header_photo) を誀怜出しないように、ヘッダバナヌ領域を陀倖
            // banner は /header_photo ぞのリンク配䞋; avatarScope 内に入らない蚭蚈だが保険
            if (avatar && /profile_banners\//.test(avatar)) {
              avatar = '';
            }

          } catch {}
          return { name, avatar };
        }

        let profileButtonObserver = null;
        let profileButtonInstalledFor = '';
        function ensureProfileAddButton(force = false) {
          const handle = getProfileHandleFromURL();
          if (!handle) return;
            // 同ハンドル内タブ遷移時でも、既存ボタンが消えおいたら再蚭眮できるようにする
          if (!force && profileButtonInstalledFor === handle && document.getElementById('adv-add-account-btn')) {
            return;
          }

          const moreBtn = document.querySelector('button[data-testid="userActions"]');
          if (!moreBtn) return;

          const parent = moreBtn.parentElement;
          if (!parent) return; // 芪コンテナがなければ挿入もできない

          // 状態远加枈みかを先に刀定
          const h_lower = handle.toLowerCase();
          const accounts = loadAccounts();
          const existingAccount = accounts.find(x => x.handle.toLowerCase() === h_lower);
          const isAdded = !!existingAccount;
          const accountId = existingAccount?.id || null;

          // 既存のボタンが残っおいれば、ハンドルに関わらず匷制的に削陀する
          const existingBtn = parent.querySelector('#adv-add-account-btn');
          if (existingBtn) {
              existingBtn.remove();
          }

          const btn = document.createElement('button');
          btn.id = 'adv-add-account-btn';
          btn.type = 'button';
          // 芋た目を完党同期class も style もコピヌ
          const syncVisual = (dst, src) => {
            dst.className = src.className;
            const st = src.getAttribute('style');
            if (st !== null) dst.setAttribute('style', st);
            // 念のため currentColor 継承
            dst.style.color ||= 'inherit';
          };
          syncVisual(btn, moreBtn);

          // 将来のテヌマ切替hover などで X が style/class を曞き換えたら远埓
          // 以前のObserverが残っおいれば砎棄し、リヌクを防ぐ
          if (profileButtonObserver) {
              profileButtonObserver.disconnect();
          }
          const visMo = new MutationObserver(() => syncVisual(btn, moreBtn));
          visMo.observe(moreBtn, { attributes: true, attributeFilter: ['class', 'style'] });
          // 新しいObserverを倉数に保持
          profileButtonObserver = visMo;
          // 状態に応じおラベルを倉曎
          const label = i18n.t(isAdded ? 'delete' : 'buttonAddAccount'); // 「削陀」キヌを流甚
          btn.setAttribute('aria-label', label);
          btn.title = label;
          // â–Œ 内偎の div / svg / span から「class ず inline style」を抜出
          const innerDiv   = moreBtn.querySelector('div[dir="ltr"]') || moreBtn.querySelector('div');
          const innerCls   = innerDiv?.getAttribute('class') || innerDiv?.classList?.value || '';
          const innerStyle = innerDiv?.getAttribute('style') || '';
          const svgEl      = innerDiv?.querySelector('svg') || moreBtn.querySelector('svg');
          const svgCls     = svgEl?.getAttribute('class') || svgEl?.classList?.value || '';
          const spanEl     = innerDiv?.querySelector('span') || moreBtn.querySelector('span');
          const spanCls    = spanEl?.getAttribute('class') || spanEl?.classList?.value || '';

          // 状態に応じおSVGパスを切り替え
          const ICON_PATH_ADD = 'M18 5h2v3h3v2h-3v3h-2V10h-3V8h3V5z';
          const ICON_PATH_CHECK = 'M23 8l-5 5-3-3 1.5-1.5L18 10l3.5-3.5L23 8z'; // 右䞊に配眮したチェック
          const iconPath = isAdded ? ICON_PATH_CHECK : ICON_PATH_ADD;

          btn.innerHTML = `
            <div dir="ltr" class="${innerCls}" style="${innerStyle}">
              <svg
                viewBox="0 0 24 24"
                aria-hidden="true"
                role="img"
                class="${svgCls}"
                fill="currentColor"
              >
                <circle cx="10" cy="7.5" r="3.5"></circle>
                <path d="M3.5 18.5C3.5 15.46 6.79 13 10 13s6.5 2.46 6.5 5.5V20H3.5v-1.5z"></path>
                <path d="${iconPath}"></path>
              </svg>
              <span class="${spanCls}"></span>
            </div>
          `;

          btn.addEventListener('click', () => {
            if (isAdded) {
              // 远加枈みの堎合削陀
              if (accountId) {
                deleteAccount(accountId); // deleteAccount は toast を内蔵しおいる
              }
            } else {
              // 未远加の堎合远加
              const { name, avatar } = collectProfileMeta(handle);
              const ret = addAccount({ handle, name, avatar });
              if (ret === 'ok') showToast(i18n.t('toastAccountAdded'));
              else if (ret === 'updated') showToast(i18n.t('updated'));
              else if (ret === 'exists') showToast(i18n.t('toastAccountExists'));
            }
            // 状態が倉わったので、ボタンを即座に再描画アむコンを切り替え
            ensureProfileAddButton(true); // force=true で再実行
          });
          // moreBtn.parentElement?.insertBefore(btn, moreBtn);
          parent.insertBefore(btn, moreBtn); // parent倉数を䜿甚
          profileButtonInstalledFor = handle;

          // プロフィヌルに来たタむミングで自動同期
          // 未登録は远加しない。既存時のみ差分曎新。
          try {
            const { name, avatar } = collectProfileMeta(handle);
            const status = updateAccountIfExists({ handle, name, avatar });
            if (status === 'updated') showToast(i18n.t('updated'));
            // 'not_found' / 'unchanged' は無通知でOK
          } catch {}

        }

        /* ========= Lists storage & UI ========= */
        const LISTS_KEY = 'advLists_v1';

        const migrateLists = (list) =>
          Array.isArray(list)
            ? list
                .map(it => ({
                  id: it.id || uid(),
                  name: (it.name || '').trim(),
                  url: (it.url || '').trim(),
                  ts: it.ts || Date.now(),
                }))
                .filter(it => it.name && it.url)
            : [];

        const loadLists  = () => migrateLists(loadJSON(LISTS_KEY, []));
        const saveLists  = (arr) => saveJSON(LISTS_KEY, migrateLists(arr));

        const addList = ({ name, url }) => {
          const nm = (name || '').trim();
          let u = (url || '').trim();
          if (!nm || !u) return 'empty';
          try {
            const parsed = new URL(u, location.origin);
            if (parsed.origin === location.origin) u = parsed.pathname + parsed.search + parsed.hash;
          } catch {}
          const list = loadLists();
          if (list.some(x => x.url === u)) return 'exists';
          const id = uid();
          list.unshift({ id, name: nm, url: u, ts: Date.now() });
          saveLists(list);
          // フォルダヌぞは入れない未所属のたた
          try {
            const folders = loadFolders(LISTS_FOLDERS_KEY, i18n.t('optLocationAll'));
            folders.forEach(f => { f.order = f.order.filter(x => x !== id); });
            saveFolders(LISTS_FOLDERS_KEY, folders);
          } catch(_) {}
          renderLists();
          return 'ok';
        };

        const deleteList = (id) => {
            // â–Œ 削陀察象のURLを保持しおおく
            const lists = loadLists();
            const deletedList = lists.find(x => x.id === id);
            const deletedUrl = deletedList?.url;

            const next = lists.filter(x => x.id !== id); // lists倉数を䜿甚
            saveLists(next);
            renderLists();
            showToast(i18n.t('toastDeleted'));

            // â–Œ ペヌゞ䞊のボタンを匷制再描画
            // 珟圚がリストペヌゞか、そのURLは䜕かを取埗
            if (isListPath()) {
                const { url: currentUrl } = getListMeta();
                // もし削陀したリストのペヌゞに今たさに居るなら、ボタンを匷制曎新
                if (deletedUrl && currentUrl === deletedUrl) {
                    ensureListAddButton(true);
                }
            }
        };

        const advListsListEl  = document.getElementById('adv-lists-list');

        // ===== FOLDER MIGRATION =====
        (function migrateAccountsToFolders(){
          // 既存フォルダヌがあっおも root 前提の自動䜜成/自動割圓はしない。
          // 叀いデヌタで item.folderId === 'root' の痕跡があれば“未所属”に正芏化。
          try {
            let items = loadAccounts();
            let changed = false;
            items = items.map(it => {
              if (it.folderId === 'root') { delete it.folderId; changed = true; }
              return it;
            });
            if (changed) saveAccounts(items);
          } catch(_) {}
        })();

        (function migrateListsToFolders(){
          // root 前提の自動䜜成/自動割圓は行わない。
          try {
            let items = loadLists();
            let changed = false;
            items = items.map(it => {
              if (it.folderId === 'root') { delete it.folderId; changed = true; }
              return it;
            });
            if (changed) saveLists(items);
          } catch(_) {}
        })();

        // UI toolbars
        ensureFolderToolbars();

        function renderLists() {
          ensureFolderToolbars();

          renderFolderedCollection({
            hostId: 'adv-lists-list',
            emptyId: 'adv-lists-empty',
            filterSelectId: 'adv-lists-folder-filter',
            searchInputId:  'adv-lists-search',
            newFolderBtnId: 'adv-lists-new-folder',

            foldersKey: LISTS_FOLDERS_KEY,
            defaultFolderName: i18n.t('optListsAll'),

            loadItems: loadLists,
            saveItems: saveLists,
            renderRow: renderListRow,

            onUnassign: unassignList,
            onMoveToFolder: moveListToFolder,

            emptyMessage: i18n.t('emptyLists'),
            unassignedIndexKey: 'advListsUnassignedIndex_v1',
          });
        }

        const isListPath = (pathname = location.pathname) => /^\/i\/lists\/\d+\/?$/.test(pathname);

        function getListMeta() {
          // 1) <title> から取り出し最優先
          let rawTitle = '';
          try { rawTitle = (document.title || '').trim(); } catch (_) {}

          // 末尟の " / X" たたは " / Twitter" を削る
          let baseTitle = rawTitle.replace(/\s*\/\s*(X|Twitter)\s*$/i, '').trim();

          let name = '';
          let m;

          // パタヌンA: "@owner/リスト名"
          m = baseTitle.match(/^\s*@([A-Za-z0-9_]{1,50})\/\s*(.+)\s*$/);
          if (m) {
            name = (m[2] || '').trim();
          }

          // パタヌンB: "リスト名 (@owner)"
          if (!name) {
            m = baseTitle.match(/^\s*(.+?)\s*\(@[A-Za-z0-9_]{1,50}\)\s*$/);
            if (m) {
              name = (m[1] || '').trim();
            }
          }

          // 䜙分な匕甚笊 “ ” " ' に察応
          if (name) {
            name = name.replace(/^[“"'](.+)[”"']$/, '$1').trim();
          }

          // 2) タむトルで取れない/怪しい時は芋出しから拟う@を含む/長文/ヘルプ文は陀倖
          if (!name) {
            try {
              const headingRoot =
                document.querySelector('[data-testid="ScrollSnap-ListHeader"]') ||
                document.querySelector('[data-testid="primaryColumn"]') ||
                document;

              const candidates = Array.from(
                headingRoot.querySelectorAll('h1[role="heading"], h2[role="heading"], h3[role="heading"]')
              )
                .flatMap(h => Array.from(h.querySelectorAll('span, div[dir="ltr"], div[dir="auto"]'))
                  .map(el => (el.textContent || '').trim()))
                .filter(Boolean)
                // 「@ 」はオヌナヌ衚蚘なので陀倖
                .filter(txt => !/^@/.test(txt))
                // 長文やヘルプ文キヌボヌドショヌトカット系を匟く
                .filter(txt => {
                  const t = txt.replace(/\s+/g, ' ');
                  if (t.length > 80) return false;
                  const NG = ['キヌボヌドショヌトカット', 'keyboard', 'help', 'ショヌトカット', 'press', '?'];
                  return !NG.some(ng => t.toLowerCase().includes(ng.toLowerCase()));
                });

              if (candidates.length) {
                // 䞀番短い候補装食の少ないタむトルの可胜性が高い
                name = candidates.sort((a, b) => a.length - b.length)[0].trim();
              }
            } catch (_) {}
          }

          // 3) 最終フォヌルバック
          if (!name) name = '';

          // URL は珟ペヌゞSPA察応でク゚リ/ハッシュも保持
          const url = location.pathname + location.search + location.hash;
          return { name, url };
        }

        let listButtonObserver = null;
        let listButtonInstalledAt = '';
        function ensureListAddButton(force = false) {
          if (!isListPath()) return;
          if (!force && listButtonInstalledAt === location.pathname) return;

          const shareBtn = document.querySelector('button[data-testid="share-button"]');
          if (!shareBtn) return;

          const parent = shareBtn.parentElement;
          if (!parent) return;

          // â–Œ 状態刀定ロゞックを远加
          const { name: currentName, url: currentUrl } = getListMeta();
          // リスト名やURLが取埗できないリストペヌゞではない堎合はボタンを远加しない
          if (!currentName || !currentUrl) return;

          const lists = loadLists();
          const existingList = lists.find(x => x.url === currentUrl);
          const isAdded = !!existingList;
          const listId = existingList?.id || null;

          // 既存のボタンが残っおいれば、匷制的に削陀する
          const existingBtn = parent.querySelector('#adv-add-list-btn');
          if (existingBtn) {
              existingBtn.remove();
          }

          const btn = document.createElement('button');
          btn.id = 'adv-add-list-btn';
          btn.type = 'button';

          const syncVisual = (dst, src) => {
            dst.className = src.className;
            const st = src.getAttribute('style');
            if (st !== null) dst.setAttribute('style', st);
            dst.style.color ||= 'inherit';
          };
          syncVisual(btn, shareBtn);

          // 以前のObserverが残っおいれば砎棄し、リヌクを防ぐ
          if (listButtonObserver) {
              listButtonObserver.disconnect();
          }
          const visMo = new MutationObserver(() => syncVisual(btn, shareBtn));
          visMo.observe(shareBtn, { attributes: true, attributeFilter: ['class', 'style'] });
          // 新しいObserverを倉数に保持
          listButtonObserver = visMo;

          // â–Œ isAdded に応じおラベルを倉曎"削陀"キヌを流甚
          const label = i18n.t(isAdded ? 'delete' : 'buttonAddList');
          btn.setAttribute('aria-label', label);
          btn.title = label;

          const innerDiv   = shareBtn.querySelector('div[dir="ltr"]') || shareBtn.querySelector('div');
          const innerCls   = innerDiv?.getAttribute('class') || innerDiv?.classList?.value || '';
          const innerStyle = innerDiv?.getAttribute('style') || '';
          const svgEl      = innerDiv?.querySelector('svg') || shareBtn.querySelector('svg');
          const svgCls     = svgEl?.getAttribute('class') || svgEl?.classList?.value || '';
          const spanEl     = innerDiv?.querySelector('span') || shareBtn.querySelector('span');
          const spanCls    = spanEl?.getAttribute('class') || spanEl?.classList?.value || '';

          // â–Œ アむコンパスを定矩
          const ICON_PATH_ADD = 'M12 5c.55 0 1 .45 1 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H6a1 1 0 110-2h5V6c0-.55.45-1 1-1z';
          // アカりントボタンずは異なり、シンプルなチェックマヌクを䜿甚
          const ICON_PATH_CHECK = 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z';
          const iconPath = isAdded ? ICON_PATH_CHECK : ICON_PATH_ADD;

          // â–Œ iconPath を䜿甚するように innerHTML を倉曎
          btn.innerHTML = `
              <div dir="ltr" class="${innerCls}" style="${innerStyle}">
                  <svg viewBox="0 0 24 24" aria-hidden="true" class="${svgCls}" fill="currentColor">
                      <g><path d="${iconPath}"></path></g>
                  </svg>
                  <span class="${spanCls}"></span>
              </div>
          `;

          // â–Œ クリックむベントのロゞックをトグルに倉曎
          btn.addEventListener('click', () => {
              if (isAdded) {
                  // 既に登録枈みの堎合削陀
                  if (listId) {
                      deleteList(listId); // deleteList は内郚で toastDeleted を呌び出したす
                  }
              } else {
                  // 未登録の堎合远加
                  // (関数冒頭で取埗した currentName, currentUrl を䜿甚)
                  const ret = addList({ name: currentName, url: currentUrl });
                  if (ret === 'ok') showToast(i18n.t('toastListAdded'));
                  else if (ret === 'exists') showToast(i18n.t('toastListExists'));
              }

              // 状態が倉わったため、ボタンを匷制的に再描画アむコンを即時切替
              ensureListAddButton(true);
          });

          // 巊隣に蚭眮
          // shareBtn.parentElement?.insertBefore(btn, shareBtn);
          parent.insertBefore(btn, shareBtn); // parent倉数を䜿甚

          listButtonInstalledAt = location.pathname;
        }

        const reconcileUI = () => {
            const stored = (()=>{ try { return JSON.parse(kv.get(MODAL_STATE_KEY,'{}')); } catch{ return {}; } })();
            const desiredVisible = !!stored.visible;
            const blocked = isBlockedPath(location.pathname);

            if (blocked) {
                trigger.style.display = 'none';
            } else {
                trigger.style.display = '';
                applyTriggerStoredPosition();
                requestAnimationFrame(keepTriggerInViewport);
            }

            const shouldShow = (!blocked) && (desiredVisible || manualOverrideOpen);
            const wasShown = (modal.style.display === 'flex');
            modal.style.display = shouldShow ? 'flex' : 'none';
            if (shouldShow) {
                // 既に衚瀺されおいる堎合(wasShown=true)は、䜍眮の匷制適甚をスキップする
                if (!wasShown) {
                    applyModalStoredPosition();
                }

                // 画面倖にはみ出しおいないかのチェックだけは毎回行う䜍眮ズレ補正のため
                requestAnimationFrame(keepModalInViewport);

                if (!wasShown) {
                    syncFromSearchBoxToModal();
                    applyScopesToControls(readScopesFromURL());
                    updateSaveButtonState();
                }
            }
        };

        trigger.addEventListener('click', () => {
            if (trigger.style.display === 'none') return;
            const isVisibleNow = modal.style.display === 'flex';
            if (isVisibleNow) {
                manualOverrideOpen = false;
                modal.style.display = 'none';
                saveModalRelativeState();
            } else {
                manualOverrideOpen = true;
                modal.style.display = 'flex';
                syncFromSearchBoxToModal();
                applyScopesToControls(readScopesFromURL());
                applyModalStoredPosition();
                requestAnimationFrame(keepModalInViewport);
                applyZoom();
                saveModalRelativeState();
                updateSaveButtonState();
            }
        });

        const closeModal = () => {
            manualOverrideOpen = false;
            modal.style.display = 'none';
            saveModalRelativeState();
        };
        closeButton.addEventListener('click', closeModal);

        clearButton.addEventListener('click', () => {
            form.reset();
            // クリア時に disabled を解陀
            ['verified', 'links', 'images', 'videos'].forEach(groupName => {
                const includeEl = document.getElementById(`adv-filter-${groupName}-include`);
                const excludeEl = document.getElementById(`adv-filter-${groupName}-exclude`);
                if (includeEl) includeEl.disabled = false;
                if (excludeEl) excludeEl.disabled = false;
            });
            syncFromModalToSearchBox();
        });

        applyButton.addEventListener('click', () => {
            // 怜玢確定 → ルヌティング反映埅ち → スキャン
            Promise.resolve(executeSearch())
              .finally(() => setTimeout(() => processNewTweets(true), 800));
        });

        saveButton.addEventListener('click', () => {
            const q = buildQueryStringFromModal().trim();
            if (!q) return;
            const {pf, lf} = readScopesFromControls();
            addSaved(q, pf, lf);
            activateTab('saved');
        });

        form.addEventListener('input', syncFromModalToSearchBox);
        form.addEventListener('keydown', e => {
            if (e.key === 'Enter' && (e.target.matches('input[type="text"], input[type="number"]'))) {
                e.preventDefault();
                // 怜玢確定 → ルヌティング反映埅ち → スキャン
                Promise.resolve(executeSearch())
                  .finally(() => setTimeout(() => processNewTweets(true), 800));
            }
        });

        const muteEmptyEl = document.getElementById('adv-mute-empty');
        const muteListEl  = document.getElementById('adv-mute-list');
        const muteInputEl = document.getElementById('adv-mute-input');
        const muteCsEl    = document.getElementById('adv-mute-cs');
        const muteAddBtn  = document.getElementById('adv-mute-add');

        const renderMuted = () => {
          const list = loadMuted();
          muteListEl.innerHTML = '';
          muteEmptyEl.textContent = list.length ? '' : i18n.t('emptyMuted');
          list.forEach(item => {
            const row = document.createElement('div');
            row.className = 'adv-mute-item';
            if (!item.enabled) row.classList.add('disabled');
            row.innerHTML = `
              <div class="adv-mute-word">${escapeHTML(item.word)}</div>
              <div class="adv-mute-actions">
                <label class="adv-toggle">
                  <input type="checkbox" ${item.enabled ? 'checked' : ''} data-action="toggle-enabled">
                  <span data-i18n="labelEnabled">${i18n.t('labelEnabled')}</span>
                </label>
                <label class="adv-toggle">
                  <input type="checkbox" ${item.cs ? 'checked' : ''} data-action="toggle-cs">
                  <span data-i18n="labelCaseSensitive">${i18n.t('labelCaseSensitive')}</span>
                </label>
                <button class="adv-chip danger" data-action="delete">${i18n.t('delete')}</button>
              </div>
            `;
            row.querySelector('[data-action="toggle-enabled"]').addEventListener('change', () => toggleMutedEnabled(item.id));
            row.querySelector('[data-action="toggle-cs"]').addEventListener('change', () => toggleMutedCS(item.id));
            row.querySelector('[data-action="delete"]').addEventListener('click', () => deleteMuted(item.id));
            muteListEl.appendChild(row);
          });
        };

        function applyMuteVisualState() {
          const listEl = document.getElementById('adv-mute-list');
          if (!listEl) return;
          const masterOn = loadMuteMaster();

          // â–Œ 切替の瞬間だけトランゞション党停止
          listEl.classList.add('adv-no-anim');
          // 匷制リフロヌでスタむル確定
          void listEl.offsetHeight;
          listEl.classList.toggle('disabled', !masterOn);
          // 次フレヌムで解陀描画を跚がせるのがポむント
          requestAnimationFrame(() => {
            listEl.classList.remove('adv-no-anim');
          });
        }

        muteAddBtn.addEventListener('click', () => {
          addMuted(muteInputEl.value, !!muteCsEl.checked);
          muteInputEl.value = '';
          muteCsEl.checked = false;
        });
        muteInputEl.addEventListener('keydown', (e) => {
          if (e.key === 'Enter') { e.preventDefault(); muteAddBtn.click(); }
        });

        const muteEnableAllEl = document.getElementById('adv-mute-enable-all');
        if (muteEnableAllEl && !muteEnableAllEl._advBound) {
          muteEnableAllEl._advBound = true;
          // 初期状態はマスタヌ倀をそのたた反映
          try {
            muteEnableAllEl.checked = loadMuteMaster();
          } catch {}
          applyMuteVisualState();    // 初期描画でリスト倖芳を敎える

          muteEnableAllEl.addEventListener('change', () => {
            saveMuteMaster(!!muteEnableAllEl.checked);
            applyMuteVisualState();   // 芖芚の即時反映リスト半透明/通垞
            rescanAllTweetsForFilter();    // 機胜面の反映既存
          });

        }

        const installNavigationHooks = (onRouteChange) => {
            let lastHref = location.href;
            const _debounce = (fn, wait=60) => { let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), wait); }; };
            const fireIfChanged = _debounce(() => {
                const now = location.href;
                if (now !== lastHref) {
                    lastHref = now;
                    try {
                        const u = new URL(now, location.origin);
                        if (u.pathname.startsWith('/search')) {
                            const q = u.searchParams.get('q') || '';
                            const pf = (u.searchParams.get('pf') || '') === 'on';
                            const lf = (u.searchParams.get('lf') || '') === 'on';
                            if (q) recordHistory(decodeURIComponent(q), pf, lf);
                        } else if (u.pathname.startsWith('/hashtag/')) {
                            const hashtag = u.pathname.substring('/hashtag/'.length).split('/')[0];
                            if (hashtag) {
                                const q = `#${decodeURIComponent(hashtag)}`;
                                // ハッシュタグペヌゞは pf/lf スコヌプを持たない想定
                                recordHistory(q, false, false);
                            }
                        }
                    } catch(_) {}
                    onRouteChange();
                }
            }, 60);
            const wrapHistory = (m) => {
                const orig = history[m];
                history[m] = function(...args){
                    try {
                        const href = args && args[2];
                        if (href) {
                            const u = new URL(href, location.href);
                            if (u.origin === location.origin && isBlockedPath(u.pathname)) {
                                hideUIImmediately(
                                    document.getElementById('advanced-search-modal'),
                                    document.getElementById('advanced-search-trigger')
                                );
                            }
                        }
                    } catch(_) {}
                    const ret = orig.apply(this, args);
                    queueMicrotask(fireIfChanged);
                    return ret;
                };
            };
            wrapHistory('pushState'); wrapHistory('replaceState');
            window.addEventListener('popstate', fireIfChanged);
            document.addEventListener('click', (e) => {
                const a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
                if (!a) return;
                try {
                    const u = new URL(a.href, location.href);
                    if (u.origin === location.origin) {
                        const sameTab = !(e.metaKey || e.ctrlKey || e.shiftKey || a.target === '_blank' || e.button === 1);
                        if (sameTab && isBlockedPath(u.pathname)) {
                            hideUIImmediately(
                                document.getElementById('advanced-search-modal'),
                                document.getElementById('advanced-search-trigger')
                            );
                        }
                        setTimeout(fireIfChanged, 0);
                    }
                } catch(_) {}
            }, true);

            return fireIfChanged;
        };

        // ツむヌトのDOMから保存甚デヌタを抜出
        function ft_extractTweetMeta(article, tweetId) {
            const text = article.querySelector('[data-testid="tweetText"]')?.innerText || '';
            const userRow = article.querySelector('[data-testid="User-Name"]');
            let name = '', handle = '', avatar = '';

            if (userRow) {
                name = userRow.querySelector('a span')?.innerText || '';
                const userLink = userRow.querySelector('a[href^="/"]');
                if (userLink) {
                    const href = userLink.getAttribute('href');
                    if (href) {
                        const parts = href.split('/');
                        if (parts.length >= 2) handle = parts[1];
                    }
                }
            }
            const img = article.querySelector('[data-testid="Tweet-User-Avatar"] img');
            if (img) avatar = img.src;

            // 投皿日時の取埗
            let postedAt = Date.now(); // フォヌルバックは珟圚時刻
            const timeEl = article.querySelector('time');
            if (timeEl && timeEl.getAttribute('datetime')) {
                postedAt = new Date(timeEl.getAttribute('datetime')).getTime();
            }

            // メディア抜出ヘルパヌ
            const extractMedia = (rootElement, excludeElement) => {
                const extracted = [];
                rootElement.querySelectorAll('div[data-testid="tweetPhoto"] img').forEach(m => {
                    if (excludeElement && excludeElement.contains(m)) return;
                    if (m.src) extracted.push({ type: 'image', url: m.src });
                });
                rootElement.querySelectorAll('video').forEach(v => {
                    if (excludeElement && excludeElement.contains(v)) return;
                    if (v.poster) extracted.push({ type: 'video', url: v.poster });
                });
                const unique = [];
                const seen = new Set();
                for (const m of extracted) {
                    if (!seen.has(m.url)) { seen.add(m.url); unique.push(m); }
                }
                return unique;
            };

            // 匕甚コンテナ特定
            let quoteContainer = null;
            const quoteCandidates = Array.from(article.querySelectorAll('div[role="link"]'));
            quoteContainer = quoteCandidates.find(el => {
                if (el.getAttribute('tabindex') !== '0') return false;
                const qUser = el.querySelector('[data-testid="User-Name"]');
                if (!qUser) return false;
                const hasText = el.querySelector('[data-testid="tweetText"]');
                const hasMedia = el.querySelector('[data-testid="tweetPhoto"]') || el.querySelector('video');
                return hasText || hasMedia;
            });

            const mainMedia = extractMedia(article, quoteContainer);

            let quote = null;
            if (quoteContainer) {
                const qText = quoteContainer.querySelector('[data-testid="tweetText"]')?.innerText || '';
                let qName = '', qHandle = '', qAvatar = '';
                const qUserRow = quoteContainer.querySelector('[data-testid="User-Name"]');
                if (qUserRow) {
                    qName = qUserRow.textContent.split('@')[0].trim();
                    const handleMatch = qUserRow.innerText.match(/@([a-zA-Z0-9_]+)/);
                    if (handleMatch) qHandle = handleMatch[1];
                }
                const qImg = quoteContainer.querySelector('img[src*="profile_images"]');
                if (qImg) qAvatar = qImg.src;

                let qTweetId = '';
                const photoLink = quoteContainer.querySelector('a[href*="/status/"][href*="/photo/"]');
                if (photoLink) {
                    const m = photoLink.getAttribute('href').match(/\/status\/(\d+)/);
                    if (m) qTweetId = m[1];
                }

                const qMedia = extractMedia(quoteContainer, null);

                quote = {
                    id: qTweetId,
                    text: qText,
                    user: { name: qName, handle: qHandle, avatar: qAvatar },
                    media: qMedia
                };
            }

            return {
                id: tweetId,
                text,
                user: { name, handle, avatar },
                media: mainMedia,
                quote,
                ts: Date.now(), // 保存操䜜をした日時゜ヌト甚などで䜿甚する堎合のため残す
                postedAt: postedAt // 実際の投皿日時
            };
        }

        // 特定のTweetIDを持぀蚘事のタグチップだけを即座に再評䟡・再描画する
        function refreshTagChipsForTweet(tweetId) {
            // data-ft-tweet-id 属性を持぀蚘事をピンポむントで取埗
            const articles = document.querySelectorAll(`article[data-ft-tweet-id="${tweetId}"]`);
            articles.forEach(article => {
                // 既存のロゞックを利甚しおチップの着脱を再刀定
                if (typeof ft_processTweetArticle === 'function') {
                    ft_processTweetArticle(article);
                }
            });
        }

        // 画面䞊のすべおのお気に入りボタンの状態を曎新する
        function updateAllFavoriteButtons() {
            document.querySelectorAll('.adv-fav-btn').forEach(btn => {
                const tid = btn.dataset.tweetId;
                if (tid) {
                    const active = isFavorited(tid);
                    btn.classList.toggle('active', active);
                }
            });
        }

        // ★マヌクボタンの泚入
        function injectFavoriteButton(article, tweetId) {
            const actionBar = article.querySelector('[role="group"]');
            if (!actionBar) return;

            // 1. シェアボタンを探す
            let shareBtn = actionBar.querySelector('[data-testid="share-button"]');
            if (!shareBtn) {
                const buttons = actionBar.querySelectorAll('button');
                for (const b of buttons) {
                    const label = b.getAttribute('aria-label') || '';
                    if (label.includes('共有') || label.includes('Share') || b.innerHTML.includes('M12 2.59l5.7 5.7')) {
                        shareBtn = b;
                        break;
                    }
                }
            }

            // シェアボタンが芋぀からない堎合はフォヌルバック末尟远加
            if (!shareBtn) {
                if (actionBar.querySelector('.adv-fav-btn')) return;
                const fallbackBtn = createFavButtonElement(article, tweetId, null);
                const wrapper = document.createElement('div');
                wrapper.style.cssText = 'display:flex;align-items:center;';
                wrapper.appendChild(fallbackBtn);
                actionBar.appendChild(wrapper);
                return;
            }

            // 2. シェアボタンの芪コンテナdisplay: inline-grid のや぀を探す
            let shareContainer = shareBtn.closest('div[style*="display: inline-grid"]') || shareBtn.parentNode?.parentNode;
            if (!shareContainer) shareContainer = shareBtn.parentNode;

            if (shareContainer.querySelector('.adv-fav-btn')) return;

            // 3. ボタン芁玠を䜜成
            const btn = createFavButtonElement(article, tweetId, shareBtn);

            // 4. 絶察配眮甚のスタむルを適甚
            shareContainer.style.position = 'relative';
            shareContainer.style.overflow = 'visible';

            // コンテナの巊偎に、星ボタンが入る分のスペヌス(箄36px)を匷制的に空ける
            // これにより、タむムラむン䞊で巊隣のブックマヌクボタンず重ならなくなりたす
            shareContainer.style.marginLeft = '36px';

            btn.style.position = 'absolute';
            btn.style.right = '100%';
            btn.style.top = '50%';
            btn.style.transform = 'translateY(-50%)';
            btn.style.marginRight = '2px'; // シェアボタンずの隙間

            // 5. 挿入
            shareContainer.appendChild(btn);
        }

        // ボタン生成ロゞックを分離共通化
        function createFavButtonElement(article, tweetId, sourceBtn) {
            const btn = document.createElement('button');
            // adv-native-style: CSSで固定サむズを解陀するためのクラス
            btn.className = (sourceBtn ? sourceBtn.className : '') + ' adv-fav-btn adv-native-style';
            btn.dataset.tweetId = tweetId;
            btn.type = 'button';
            btn.title = i18n.t('tabFavorites');

            // SVG (Star)
            btn.innerHTML = `
            <svg viewBox="0 0 24 24" aria-hidden="true">
              <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path>
            </svg>`;

            const updateState = () => {
                const active = isFavorited(tweetId);
                btn.classList.toggle('active', active);
            };
            updateState();

            btn.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                const meta = ft_extractTweetMeta(article, tweetId);
                toggleFavorite(meta);
                updateState();
                ft_processTweetArticle(article);
            });

            return btn;
        }

        // ============================================================
        //  High Performance Tweet Processor (O(1))
        // ============================================================

        function processSingleTweet(article) {
            // 重耇凊理ガヌド
            if (article.dataset.advProcessed) return;

            // 1. 共通: ツむヌトIDの確保
            const tweetId = article.dataset.ftTweetId || ft_extractTweetId(article);
            if (!tweetId) return;
            article.dataset.ftTweetId = tweetId;

            // 2. 蚭定ロヌド (Advanced Search甹) ...
            const flags = {
                name: document.getElementById('adv-exclude-hit-name')?.checked ?? true,
                handle: document.getElementById('adv-exclude-hit-handle')?.checked ?? true,
                reposts: document.getElementById('adv-filter-reposts-exclude')?.checked ?? false,
                hashtags: document.getElementById('adv-filter-hashtags-exclude')?.checked ?? false,
            };

            // 3. Advanced Search Filtering ...
            const masterOn = loadMuteMaster();
            const muted = loadMuted();
            const hasMute = masterOn && muted.length > 0;

            if (flags.name || flags.handle || hasMute || flags.reposts || flags.hashtags) {
                const enabledMuted = hasMute ? muted.filter(m => m.enabled !== false) : [];
                const muteSettings = {
                    hasMute,
                    muteCI: enabledMuted.length ? new Set(enabledMuted.filter(m => !m.cs).map(m => m.word.toLowerCase())) : new Set(),
                    muteCS: enabledMuted.length ? enabledMuted.filter(m => m.cs).map(m => m.word) : [],
                };
                const tokens = (flags.name || flags.handle) ? parseSearchTokens() : null;
                try {
                    evaluateTweetForFiltering(article, flags, muteSettings, tokens);
                } catch (e) { console.error('[AdvSearch] Filter error', e); }
            }

            // 4. Favorite Tags Processing (タグ衚瀺)
            if (typeof ft_processTweetArticle === 'function') {
                try {
                    ft_processTweetArticle(article);
                } catch (e) { console.error('[FT] Tag error', e); }
            }

            // 5.Inject Favorite Button
            injectFavoriteButton(article, tweetId);

            // 6. 凊理枈みフラグを立おる
            article.dataset.advProcessed = '1';
        }

        // 手動実行蚭定倉曎時などやペヌゞ遷移時甚
        // 普段のスクロヌル時は Observer が processSingleTweet を呌ぶので、これは走らない
        function processNewTweets(force = false) {
            // 匷制フラグがない堎合、入力䞭はスキップ
            if (!force && isTyping()) return;

            // 党ツむヌトを取埗しお凊理
            const articles = document.querySelectorAll('article[data-testid="tweet"]');
            for (const art of articles) {
                processSingleTweet(art);
            }

            // 区切り線のクリヌンアップなどはここで
            cleanupAdjacentSeparators();
        }

        // 統合スキャン関数 ここたで â–Œ
        const setupObservers = () => {
            // URL倉曎怜知甚のプレヌスホルダ
            let _fireIfChanged = () => {};

            const observer = new MutationObserver((mutations) => {
                let searchBoxChanged = false;

                for (const m of mutations) {
                    // 远加されたノヌドだけをルヌプする (Differential Update)
                    if (m.addedNodes.length > 0) {
                        for (const node of m.addedNodes) {
                            if (node.nodeType !== Node.ELEMENT_NODE) continue;

                            // A. 怜玢ボックスの怜知
                            if (!searchBoxChanged) {
                                if (node.matches?.('input[data-testid="SearchBox_Search_Input"]') ||
                                    node.querySelector?.('input[data-testid="SearchBox_Search_Input"]')) {
                                    searchBoxChanged = true;
                                }
                            }

                            // B. ツむヌト (article) が盎接远加された堎合
                            if (node.tagName === 'ARTICLE' && node.getAttribute('data-testid') === 'tweet') {
                                processSingleTweet(node);
                            }
                            // C. コンテナ (div/section等) が远加され、䞭にツむヌトが含たれる堎合
                            else if (node.firstElementChild) {
                                // cellInnerDiv もここに含たれる
                                const nestedTweets = node.querySelectorAll('article[data-testid="tweet"]');
                                if (nestedTweets.length > 0) {
                                    for (const tweet of nestedTweets) {
                                        processSingleTweet(tweet);
                                    }
                                }
                            }
                        }
                    }
                }

                // 怜玢ボックスが倉わっおいたらモヌダルず同期
                if (searchBoxChanged) {
                    syncFromSearchBoxToModal();
                }

                // プロフィヌル/リストのボタン蚭眮DOM倉化時は垞にチェックしおも軜量
                try {
                    ensureProfileAddButton(false);
                    ensureListAddButton(false);
                } catch (_) {}

                // URL倉曎チェック (Debounced)
                _fireIfChanged();
            });

            const appContainer = document.querySelector('div[data-testid="app-container"]');
            const observeTarget = appContainer || document.body;
            observer.observe(observeTarget, { childList: true, subtree: true });

            // installNavigationHooks はそのたた利甚
            _fireIfChanged = installNavigationHooks(() => {
                // Navigation Change Logic...
                if (profileButtonObserver) { profileButtonObserver.disconnect(); profileButtonObserver = null; }
                if (listButtonObserver) { listButtonObserver.disconnect(); listButtonObserver = null; }

                manualOverrideOpen = false;
                reconcileUI();
                syncFromSearchBoxToModal();
                applyScopesToControls(readScopesFromURL());
                updateSaveButtonState();

                // ペヌゞ遷移時は匷制的に党スキャン (Force)
                processNewTweets(true);
            });
        };
        window.addEventListener('resize', debounce(()=>{
            if (modal.style.display === 'flex') { applyModalStoredPosition(); requestAnimationFrame(keepModalInViewport); }
            if (trigger.style.display !== 'none') { applyTriggerStoredPosition(); requestAnimationFrame(keepTriggerInViewport); }
        }, 100));
        loadModalState();
        reconcileUI();
        setupModalDrag();
        setupModalResize();
        // 排他チェックボックスのロゞック
        const setupExclusiveChecks = () => {
            const groups = [
                'verified', 'links', 'images', 'videos'
            ];
            groups.forEach(groupName => {
                const includeEl = document.getElementById(`adv-filter-${groupName}-include`);
                const excludeEl = document.getElementById(`adv-filter-${groupName}-exclude`);
                if (!includeEl || !excludeEl) return;
                const handleChange = (eventSource, oppositeEl) => {
                    if (eventSource.checked) {
                        oppositeEl.disabled = true;
                    } else {
                        oppositeEl.disabled = false;
                    }
                };
                includeEl.addEventListener('change', () => handleChange(includeEl, excludeEl));
                excludeEl.addEventListener('change', () => handleChange(excludeEl, includeEl));
            });
        };
        setupExclusiveChecks();
        setupObservers();

        // むベント委任のためのルヌト芁玠を取埗
        const appContainer = document.querySelector('div[data-testid="app-container"]') || document.body;

        // 監芖察象セレクタを結合
        const allSearchSelectorsStr = searchInputSelectors.join(', ');

        // 1. 怜玢ボックスの入力 (むベント委任)
        appContainer.addEventListener('input', (e) => {
            if (!e.target || !e.target.matches(allSearchSelectorsStr)) return;
            const input = e.target;

            if (isUpdating || modal.style.display === 'none') return;

            const activeInputs = getActiveSearchInputs();
            if (activeInputs.includes(input)) {
                parseQueryAndApplyToModal(input.value);
                applyScopesToControls(readScopesFromURL());
                updateSaveButtonState();
            }
        });

        // 2. タむピングガヌド (むベント委任)
        const typingEvents = ['input', 'keydown', 'keyup', 'compositionstart', 'compositionupdate', 'compositionend'];
        typingEvents.forEach(evName => {
            appContainer.addEventListener(evName, (e) => {
                if (e.target && e.target.matches(allSearchSelectorsStr)) {
                    markTyping();
                }
            }, { passive: true, capture: true }); // capture: true でより確実に補足
        });

        // 3. フォヌム送信 (むベント委任)
        appContainer.addEventListener('submit', (e) => {
            if (!e.target || !e.target.closest('form')) return;

            // SearchBox_Search_Input を含むフォヌムか刀定
            const input = e.target.querySelector('input[data-testid="SearchBox_Search_Input"]');
            if (input) {
                const val = (input.value || '').trim();
                const {pf, lf} = readScopesFromControls();
                if (val) recordHistory(val, pf, lf);
            }
        }, true); // キャプチャフェヌズで実行

        // ▌ Setup background drop zones ▌
        // このブロックは、最初の renderAccounts / renderLists / renderSaved を呌ぶ前に眮く
        setupBackgroundDrop(tabAccountsPanel, accountsListEl,  unassignAccount);
        setupBackgroundDrop(tabListsPanel,    advListsListEl,  unassignList);
        setupBackgroundDrop(tabSavedPanel,    advSavedListEl,  unassignSaved);

        // ft_init を最優先で実行し、stateをロヌドさせる
        if (typeof ft_init === 'function') {
            ft_init();
        }

        // 䞇が䞀ロヌド倱敗しおいた堎合に備え、デフォルトStateを泚入しお null萜ちを防ぐ
        if (typeof ft_state === 'undefined' || !ft_state) {
            ft_state = ft_createDefaultState();
        }

        setupFavoritesDelegation();
        renderFavorites(); // 初期描画 (これで ft_state がある状態で走る)
        renderHistory();
        renderSaved();
        renderAccounts();
        renderMuted();
        // 保存された最埌のタブを読み蟌んでアクティブにする
        const lastTab = kv.get(LAST_TAB_KEY, 'search');
        activateTab(lastTab || 'search');
        (async () => {
            const input = await waitForElement(searchInputSelectors.join(','), 7000);
            if (input) {
                syncFromSearchBoxToModal();
                applyScopesToControls(readScopesFromURL());
                updateSaveButtonState();
                processNewTweets(true);

                // force=true で初回描画を匷制
                ensureProfileAddButton(true);
                ensureListAddButton(true);

                /* --- Favorite Tags Init Scan --- */
                try {
                   processNewTweets(true);
                } catch(e) { console.error('[FT] Init error', e); }
            }
        })();
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
};

// --- 環境刀定ブヌトロヌダヌ ---
// 1. UserScript環境: GM_info があり、か぀アダプタヌによる遅延実行埅ち(window.__X_ADV_SEARCH_MAIN__)ではない堎合
if (typeof GM_info !== 'undefined' && typeof window.__X_ADV_SEARCH_MAIN__ === 'undefined') {
    __X_ADV_SEARCH_MAIN_LOGIC__();
}
// 2. 拡匵機胜環境: adapter.js から呌ばれるのを埅぀ために window に公開
else {
    window.__X_ADV_SEARCH_MAIN__ = __X_ADV_SEARCH_MAIN_LOGIC__;
}