// ==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.6.2 // @description No need to memorize search commands anymore. Adds a feature-rich floating window to X.com (Twitter) that combines an easy-to-use advanced search UI, search history, saved searches, local post (tweet) bookmarks with tags, regex-based muting, and folder-based account and list management. // @description:ja 検索コマンドはもう覚える必要なし。誰にでも使いやすい高度な検索UI、検索履歴、検索条件の保存、投稿(ツイート)をタグで管理できるローカルお気に入り機能、正規表現対応のミュート、フォルダー分け対応のアカウント/リスト管理機能などを統合した超多機能フローティングウィンドウを X.com(Twitter)に追加します。 // @description:en No need to memorize search commands anymore. Adds a feature-rich floating window to X.com (Twitter) that combines an easy-to-use advanced search UI, search history, saved searches, local post (tweet) bookmarks with tags, regex-based muting, and folder-based account and list management. // @description:zh-CN 无需再死记硬背搜索命令。为 X.com(Twitter)添加一个超多功能浮动窗口,集成易用的高级搜索界面、搜索历史、已保存的搜索条件、支持为帖子(推文)添加标签的本地收藏、基于正则表达式的屏蔽,以及支持按文件夹管理的账号和列表功能。 // @description:zh-TW 無需再死記硬背搜尋指令。為 X.com(Twitter)增加一個超多功能懸浮視窗,整合易用的高級搜尋介面、搜尋紀錄、已保存的搜尋條件、可用標籤管理貼文(推文)的本地收藏、正則表示式過濾,以及支援以資料夾分類的帳號和列表管理功能。 // @description:ko 더 이상 검색 명령어를 외울 필요 없습니다. X.com(Twitter)에 누구나 쉽게 사용할 수 있는 고급 검색 UI, 검색 기록, 검색 조건 저장, 게시글(트윗)을 태그로 관리할 수 있는 로컬 즐겨찾기 기능, 정규식 음소거, 폴더 분류가 가능한 계정 및 리스트 관리 기능 등을 통합한 다기능 플로팅 창을 추가합니다. // @description:fr Plus besoin de mémoriser les commandes de recherche. Ajoute à X.com (Twitter) une fenêtre flottante très complète regroupant une interface de recherche avancée et facile à utiliser, l’historique, les recherches enregistrées, des favoris locaux pour les publications (tweets) avec tags, un masquage par expressions régulières (regex) et une gestion des comptes et listes avec classement par dossiers. // @description:es ¡Olvídate de memorizar comandos de búsqueda! Añade a X.com (Twitter) una ventana flotante multifuncional con una interfaz de búsqueda avanzada y fácil de usar, historial, búsquedas guardadas, favoritos locales de publicaciones (tuits) con etiquetas, silenciado mediante expresiones regulares (regex) y gestión de cuentas y listas con organización por carpetas. // @description:de Kein Auswendiglernen von Suchbefehlen mehr! Fügt X.com (Twitter) ein multifunktionales schwebendes Fenster hinzu, das eine leicht zu bedienende erweiterte Suchoberfläche, Suchverlauf, gespeicherte Suchanfragen, lokale Lesezeichen für Posts (Tweets) mit Tags, Stummschaltung per regulären Ausdrücken (Regex) und eine ordnerbasierte Konten- und Listenverwaltung vereint. // @description:pt-BR Não precisa mais decorar comandos de busca! Adiciona ao X.com (Twitter) uma janela flutuante multifuncional com uma interface de busca avançada e fácil de usar, histórico, buscas salvas, favoritos locais de posts (tweets) com tags, silenciamento por expressões regulares (regex) e gestão de contas e listas com organização em pastas. // @description:ru Больше не нужно запоминать поисковые команды! Добавляет на X.com (Twitter) многофункциональное плавающее окно с удобным интерфейсом расширенного поиска, историей, сохранёнными запросами, локальными закладками постов (твитов) с тегами, фильтрацией по регулярным выражениям (regex) и управлением аккаунтами и списками с организацией по папкам. // @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 // @grant GM_info // @grant unsafeWindow // @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 // @updateURL https://raw.githubusercontent.com/koyasi777/advanced-search-for-x-twitter/main/x-advanced-search.user.js // @downloadURL https://raw.githubusercontent.com/koyasi777/advanced-search-for-x-twitter/main/x-advanced-search.user.js // ==/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 reposts", labelDateRange: "Date range", labelDateShortcut: "Quick Range", optDate1Day: "Past 24h", optDate1Week: "Past week", optDate1Month: "Past month", optDate3Months: "Past 3 months", optDate6Months: "Past 6 months", optDate1Year: "Past year", optDate2Years: "Past 2 years", optDate3Years: "Past 3 years", optDate5Years: "Past 5 years", optDateClear: "Clear dates", 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 matches only in display name", labelHitHandle: "Exclude matches 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", labelWordBoundary: "Whole word", labelEnabled: "Enabled", labelEnableAll: "Enable all", buttonAdd: "Add", emptyMuted: "No muted words.", mutedListTitle: "Muted words", mutedListHeading: "Muted items", optMuteHidden: "Hidden", optMuteCollapsed: "Collapsed", placeholderFilterMute: "Filter muted words...", muteLabel: "Muted: ", buttonShow: "Show", muteHit: "Mute hits in body", buttonRemute: "Re-mute", 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", labelInitialTab: "Startup tab", optInitialTabLast: "Last opened (Default)", 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: 'Tag name 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: "期間指定", labelDateShortcut: "期間ショートカット", optDate1Day: "過去24時間", optDate1Week: "過去1週間", optDate1Month: "過去1ヶ月", optDate3Months: "過去3ヶ月", optDate6Months: "過去6ヶ月", optDate1Year: "過去1年", optDate2Years: "過去2年", optDate3Years: "過去3年", optDate5Years: "過去5年", optDateClear: "日付クリア", 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: "大文字小文字を区別", labelWordBoundary: "完全一致(単語)", labelEnabled: "有効", labelEnableAll: "すべて有効", buttonAdd: "追加", emptyMuted: "ミュート語句はまだありません。", mutedListTitle: "ミュート語句", mutedListHeading: "ミュート一覧", optMuteHidden: "非表示", optMuteCollapsed: "折りたたみ", placeholderFilterMute: "ミュートを検索...", muteLabel: "ミュート: ", buttonShow: "表示する", muteHit: "本文でのヒットをミュート", buttonRemute: "再ミュート", 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: "自動判定", labelInitialTab: "起動時に開くタブ", optInitialTabLast: "前回のタブ (デフォルト)", 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': { 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: "日期范围", labelDateShortcut: "快速选择", optDate1Day: "过去 24 小时", optDate1Week: "过去 1 周", optDate1Month: "过去 1 个月", optDate3Months: "过去 3 个月", optDate6Months: "过去 6 个月", optDate1Year: "过去 1 年", optDate2Years: "过去 2 年", optDate3Years: "过去 3 年", optDate5Years: "过去 5 年", optDateClear: "清除日期", tooltipSince: "起始日期", tooltipUntil: "结束日期", labelFromUser: "来自这些账号 (from:)", placeholderFromUser: "例如:@X", labelToUser: "发送给这些账号 (to:)", placeholderToUser: "例如:@google", labelMentioning: "提及这些账号 (@)", placeholderMentioning: "例如:@OpenAI", buttonClear: "清除", buttonApply: "搜索", tooltipTrigger: "打开高级搜索", buttonOpen: "打开", tabSearch: "搜索", tabHistory: "历史", tabSaved: "已保存", buttonSave: "保存", buttonSaved: "已保存", secretMode: "无痕模式", secretOn: "无痕模式已开启 (不记录历史)", secretOff: "无痕模式已关闭", toastSaved: "已保存。", toastDeleted: "已删除。", toastReordered: "顺序已更新。", emptyHistory: "暂无历史记录。", emptySaved: "暂无保存的搜索。请在搜索标签页左下角点击保存按钮添加。", run: "运行", delete: "删除", updated: "已更新。", tooltipSecret: "切换无痕模式 (不记录搜索历史)", historyClearAll: "全部清除", confirmClearHistory: "确定要清除所有历史记录吗?", labelAccountScope: "账号范围", optAccountAll: "所有账号", optAccountFollowing: "关注的账号", labelLocationScope: "位置范围", optLocationAll: "所有位置", optLocationNearby: "附近", chipFollowing: "已关注", chipNearby: "附近", labelSearchTarget: "搜索目标", labelHitName: "排除仅在显示名称中的匹配", labelHitHandle: "排除仅在用户名 (@handle) 中的匹配", hintSearchTarget: "隐藏仅在名称或用户名中匹配(而非正文)的帖子。", hintName: "如果关键词仅出现在显示名称中,则隐藏。", hintHandle: "如果关键词仅出现在 @用户名 中,则隐藏。例外:当查询中明确使用了 from:/to:/@ 时除外。", tabMute: "屏蔽", labelMuteWord: "添加屏蔽词", placeholderMuteWord: "例如:剧透", labelCaseSensitive: "区分大小写", labelWordBoundary: "全字匹配", labelEnabled: "已启用", labelEnableAll: "全部启用", buttonAdd: "添加", emptyMuted: "暂无屏蔽词。", mutedListTitle: "屏蔽词", mutedListHeading: "屏蔽列表", optMuteHidden: "隐藏", optMuteCollapsed: "折叠", placeholderFilterMute: "筛选屏蔽词...", muteLabel: "已屏蔽: ", buttonShow: "显示", muteHit: "屏蔽正文匹配项", buttonRemute: "重新屏蔽", 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: "查询词 (A-Z)", sortNameDesc: "查询词 (Z-A)", /* 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: "界面语言", optUILangAuto: "自动", labelInitialTab: "启动时打开的标签页", optInitialTabLast: "上次打开的标签页 (默认)", 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-TW': { 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: "日期範圍", labelDateShortcut: "快速範圍", optDate1Day: "過去 24 小時", optDate1Week: "過去 1 週", optDate1Month: "過去 1 個月", optDate3Months: "過去 3 個月", optDate6Months: "過去 6 個月", optDate1Year: "過去 1 年", optDate2Years: "過去 2 年", optDate3Years: "過去 3 年", optDate5Years: "過去 5 年", optDateClear: "清除日期", tooltipSince: "開始日期", tooltipUntil: "結束日期", labelFromUser: "來自這些帳號 (from:)", placeholderFromUser: "例如:@X", labelToUser: "發送給這些帳號 (to:)", placeholderToUser: "例如:@google", labelMentioning: "提及這些帳號 (@)", placeholderMentioning: "例如:@OpenAI", buttonClear: "清除", buttonApply: "搜尋", tooltipTrigger: "打開進階搜尋", buttonOpen: "打開", tabSearch: "搜尋", tabHistory: "紀錄", tabSaved: "已儲存", buttonSave: "儲存", buttonSaved: "已儲存", secretMode: "無痕模式", secretOn: "無痕模式已開啟 (不記錄歷史)", secretOff: "無痕模式已關閉", toastSaved: "已儲存。", toastDeleted: "已刪除。", toastReordered: "順序已更新。", emptyHistory: "暫無搜尋紀錄。", emptySaved: "暫無儲存的搜尋。請在搜尋分頁左下角點擊儲存按鈕添加。", run: "執行", delete: "刪除", updated: "已更新。", tooltipSecret: "切換無痕模式 (不記錄搜尋歷史)", historyClearAll: "全部清除", confirmClearHistory: "確定要清除所有搜尋紀錄嗎?", labelAccountScope: "帳號範圍", optAccountAll: "所有帳號", optAccountFollowing: "跟隨的帳號", labelLocationScope: "位置範圍", optLocationAll: "所有位置", optLocationNearby: "附近", chipFollowing: "正在跟隨", chipNearby: "附近", labelSearchTarget: "搜尋目標", labelHitName: "排除僅在顯示名稱中的相符項目", labelHitHandle: "排除僅在使用者名稱 (@handle) 中的相符項目", hintSearchTarget: "隱藏僅在名稱或使用者名稱中相符(而非內文)的貼文。", hintName: "如果關鍵字僅出現在顯示名稱中,則隱藏。", hintHandle: "如果關鍵字僅出現在 @使用者名稱 中,則隱藏。例外:當查詢中明確使用了 from:/to:/@ 時除外。", tabMute: "靜音", labelMuteWord: "新增靜音詞彙", placeholderMuteWord: "例如:劇透", labelCaseSensitive: "區分大小寫", labelWordBoundary: "全字匹配", labelEnabled: "已啟用", labelEnableAll: "全部啟用", buttonAdd: "新增", emptyMuted: "暫無靜音詞彙。", mutedListTitle: "靜音詞彙", mutedListHeading: "靜音清單", optMuteHidden: "隱藏", optMuteCollapsed: "收合", placeholderFilterMute: "篩選靜音詞彙...", muteLabel: "已靜音: ", buttonShow: "顯示", muteHit: "靜音內文相符項目", buttonRemute: "重新靜音", 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: "關鍵字 (A-Z)", sortNameDesc: "關鍵字 (Z-A)", /* 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: "介面語言", optUILangAuto: "自動", labelInitialTab: "啟動時開啟的分頁", optInitialTabLast: "上次開啟的分頁 (預設)", 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: '收藏標籤設定', }, 'ko': { 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: "날짜 범위", labelDateShortcut: "빠른 범위 설정", optDate1Day: "지난 24시간", optDate1Week: "지난 1주", optDate1Month: "지난 1개월", optDate3Months: "지난 3개월", optDate6Months: "지난 6개월", optDate1Year: "지난 1년", optDate2Years: "지난 2년", optDate3Years: "지난 3년", optDate5Years: "지난 5년", optDateClear: "날짜 초기화", tooltipSince: "시작일", tooltipUntil: "종료일", labelFromUser: "다음 계정에서 (from:)", placeholderFromUser: "예: @X", labelToUser: "다음 계정으로 (to:)", placeholderToUser: "예: @google", labelMentioning: "다음 계정 언급 (@)", placeholderMentioning: "예: @OpenAI", buttonClear: "지우기", buttonApply: "검색", tooltipTrigger: "고급 검색 열기", buttonOpen: "열기", tabSearch: "검색", tabHistory: "기록", tabSaved: "저장됨", buttonSave: "저장", buttonSaved: "저장됨", secretMode: "시크릿 모드", secretOn: "시크릿 모드 켜짐 (기록되지 않음)", secretOff: "시크릿 모드 꺼짐", toastSaved: "저장되었습니다.", toastDeleted: "삭제되었습니다.", toastReordered: "순서가 업데이트되었습니다.", emptyHistory: "기록이 없습니다.", emptySaved: "저장된 검색이 없습니다. 검색 탭 왼쪽 하단의 저장 버튼으로 추가하세요.", run: "실행", delete: "삭제", updated: "업데이트됨.", tooltipSecret: "시크릿 모드 전환 (검색 기록을 저장하지 않음)", historyClearAll: "모두 지우기", confirmClearHistory: "모든 기록을 삭제하시겠습니까?", labelAccountScope: "계정 범위", optAccountAll: "모든 계정", optAccountFollowing: "팔로우 중인 계정", labelLocationScope: "위치 범위", optLocationAll: "모든 위치", optLocationNearby: "내 주변", chipFollowing: "팔로잉", chipNearby: "주변", labelSearchTarget: "검색 대상", labelHitName: "표시 이름(닉네임)만 일치하는 결과 제외", labelHitHandle: "사용자 아이디(@handle)만 일치하는 결과 제외", hintSearchTarget: "본문이 아닌 이름/아이디만 일치하는 게시물을 숨깁니다.", hintName: "키워드가 표시 이름에만 포함된 경우 숨깁니다.", hintHandle: "키워드가 @아이디에만 포함된 경우 숨깁니다. 예외: 검색어에 from:/to:/@ 등으로 명시한 경우는 표시합니다.", tabMute: "뮤트", labelMuteWord: "뮤트 단어 추가", placeholderMuteWord: "예: 스포일러", labelCaseSensitive: "대소문자 구분", labelWordBoundary: "단어 단위", labelEnabled: "활성화", labelEnableAll: "모두 활성화", buttonAdd: "추가", emptyMuted: "뮤트된 단어가 없습니다.", mutedListTitle: "뮤트 단어", mutedListHeading: "뮤트 목록", optMuteHidden: "숨기기", optMuteCollapsed: "접기", placeholderFilterMute: "뮤트 단어 검색...", muteLabel: "뮤트됨: ", buttonShow: "표시", muteHit: "본문 일치 항목 뮤트", buttonRemute: "다시 뮤트", 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: "자동", labelInitialTab: "시작 시 열 탭", optInitialTabLast: "마지막에 연 탭 (기본)", labelImportExport: "가져오기 / 내보내기", placeholderSettingsJSON: "백업 JSON을 여기에 붙여넣으세요...", tooltipSettings: "설정 열기", toastImported: "가져오기가 완료되었습니다.", alertInvalidJSON: "유효하지 않은 JSON 파일입니다.", alertInvalidData: "유효하지 않은 데이터 형식입니다.", alertInvalidApp: '"Advanced Search for X"의 백업 파일이 아닙니다.', toastExported: "파일로 내보냈습니다.", 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: '태그 설정', }, 'fr': { modalTitle: "Recherche avancée", tooltipClose: "Fermer", labelAllWords: "Tous ces mots", placeholderAllWords: "ex: AI actualités", labelExactPhrase: "Cette phrase exacte", placeholderExactPhrase: 'ex: "ChatGPT 4o"', labelAnyWords: "L'un de ces mots (OR)", placeholderAnyWords: "ex: iPhone Android", labelNotWords: "Aucun de ces mots (-)", placeholderNotWords: "ex: -soldes -pub", labelHashtag: "Hashtags (#)", placeholderHashtag: "ex: #Paris2024", labelLang: "Langue (lang:)", optLangDefault: "Toutes les langues", optLangJa: "Japonais (ja)", optLangEn: "Anglais (en)", optLangId: "Indonésien (id)", optLangHi: "Hindi (hi)", optLangDe: "Allemand (de)", optLangTr: "Turc (tr)", optLangEs: "Espagnol (es)", optLangPt: "Portugais (pt)", optLangAr: "Arabe (ar)", optLangFr: "Français (fr)", optLangKo: "Coréen (ko)", optLangRu: "Russe (ru)", optLangZhHans: "Chinois simplifié (zh-cn)", optLangZhHant: "Chinois traditionnel (zh-tw)", hrSeparator: " ", labelFilters: "Filtres", labelVerified: "Comptes certifiés", labelLinks: "Liens", labelImages: "Images", labelVideos: "Vidéos", labelReposts: "Republications", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Inclure", checkExclude: "Exclure", labelReplies: "Réponses", optRepliesDefault: "Par défaut (Tout)", optRepliesInclude: "Inclure les réponses", optRepliesOnly: "Réponses uniquement", optRepliesExclude: "Exclure les réponses", labelEngagement: "Engagement", placeholderMinReplies: "Min réponses", placeholderMinLikes: "Min J'aime", placeholderMinRetweets: "Min republications", labelDateRange: "Période", labelDateShortcut: "Plage rapide", optDate1Day: "Dernières 24h", optDate1Week: "Semaine dernière", optDate1Month: "Mois dernier", optDate3Months: "3 derniers mois", optDate6Months: "6 derniers mois", optDate1Year: "Année dernière", optDate2Years: "2 dernières années", optDate3Years: "3 dernières années", optDate5Years: "5 dernières années", optDateClear: "Effacer les dates", tooltipSince: "Depuis cette date", tooltipUntil: "Jusqu'à cette date", labelFromUser: "De ces comptes (from:)", placeholderFromUser: "ex: @X", labelToUser: "À ces comptes (to:)", placeholderToUser: "ex: @google", labelMentioning: "Mentionnant ces comptes (@)", placeholderMentioning: "ex: @OpenAI", buttonClear: "Effacer", buttonApply: "Rechercher", tooltipTrigger: "Ouvrir la recherche avancée", buttonOpen: "Ouvrir", tabSearch: "Recherche", tabHistory: "Historique", tabSaved: "Enregistré", buttonSave: "Enregistrer", buttonSaved: "Enregistré", secretMode: "Mode privé", secretOn: "Mode privé activé (Pas d'historique)", secretOff: "Mode privé désactivé", toastSaved: "Enregistré.", toastDeleted: "Supprimé.", toastReordered: "Ordre mis à jour.", emptyHistory: "Aucun historique.", emptySaved: "Aucune recherche enregistrée. Ajoutez-en via le bouton Enregistrer en bas à gauche de l'onglet Recherche.", run: "Lancer", delete: "Supprimer", updated: "Mis à jour.", tooltipSecret: "Basculer le mode privé (aucun historique ne sera enregistré)", historyClearAll: "Tout effacer", confirmClearHistory: "Effacer tout l'historique ?", labelAccountScope: "Comptes", optAccountAll: "Tous les comptes", optAccountFollowing: "Comptes suivis", labelLocationScope: "Lieu", optLocationAll: "Tous les lieux", optLocationNearby: "Proche de vous", chipFollowing: "Abonnements", chipNearby: "À proximité", labelSearchTarget: "Cible de la recherche", labelHitName: "Exclure les résultats dans le nom d'affichage", labelHitHandle: "Exclure les résultats dans le nom d'utilisateur (@)", hintSearchTarget: "Masquer les posts qui ne correspondent que par le nom ou l'identifiant (pas dans le texte).", hintName: "Si un mot-clé n'apparaît que dans le nom d'affichage, le masquer.", hintHandle: "Si un mot-clé n'apparaît que dans le @nom_utilisateur, le masquer. Exception : si la requête utilise explicitement from:/to:/@.", tabMute: "Masquer", labelMuteWord: "Ajouter un mot masqué", placeholderMuteWord: "ex: spoiler", labelCaseSensitive: "Sensible à la casse", labelWordBoundary: "Mot entier", labelEnabled: "Activé", labelEnableAll: "Tout activer", buttonAdd: "Ajouter", emptyMuted: "Aucun mot masqué.", mutedListTitle: "Mots masqués", mutedListHeading: "Liste masquée", optMuteHidden: "Masqué", optMuteCollapsed: "Réduit", placeholderFilterMute: "Filtrer les mots masqués...", muteLabel: "Masqué : ", buttonShow: "Afficher", muteHit: "Masquer les résultats dans le texte", buttonRemute: "Masquer à nouveau", buttonImport: "Importer", buttonExport: "Exporter", /* Accounts tab */ tabAccounts: "Comptes", emptyAccounts: "Aucun compte. Ouvrez un profil et cliquez sur le bouton Ajouter pour l'enregistrer.", buttonAddAccount: "Ajouter compte", toastAccountAdded: "Compte ajouté.", toastAccountExists: "Déjà ajouté.", buttonConfirm: "Confirmer", /* Lists tab */ tabLists: "Listes", emptyLists: "Aucune liste. Ouvrez une liste et cliquez sur le bouton + en haut à droite pour l'ajouter.", buttonAddList: "Ajouter liste", toastListAdded: "Liste ajoutée.", toastListExists: "Déjà ajoutée.", /* History tab */ placeholderSearchHistory: "Historique (requête)", labelSortBy: "Trier par :", placeholderSearchSaved: "Recherches enregistrées (requête)", sortNewest: "Plus récent", sortOldest: "Plus ancien", sortNameAsc: "Nom (A-Z)", sortNameDesc: "Nom (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Filtrer comptes (@, nom)", placeholderFilterLists: "Filtrer listes (nom, url)", buttonAddFolder: "+Dossier", folderFilterAll: "TOUT", folderFilterUnassigned: "Non classé", folderRename: "Renommer", folderRenameTitle: "Renommer le dossier", folderDelete: "Supprimer", folderDeleteTitle: "Supprimer le dossier", promptNewFolder: "Nom du dossier", confirmDeleteFolder: "Supprimer ce dossier et tout son contenu ? Cette action est irréversible.", optListsAll: "Listes", defaultSavedFolders: "Recherches enregistrées", /* Favorites */ tabFavorites: "Favoris", emptyFavorites: "Aucun favori. Cliquez sur l'icône ★ d'un tweet pour l'enregistrer.", optFavoritesAll: "Tous les favoris", toastFavorited: "Ajouté aux favoris.", toastUnfavorited: "Retiré des favoris.", /* Settings */ settingsTitle: "Paramètres", settingsTitleGeneral: "Général", settingsTitleFeatures: "Affichage onglets", settingsTitleData: "Données", buttonClose: "Fermer", labelUILang: "Langue de l'interface", optUILangAuto: "Auto", labelInitialTab: "Onglet au démarrage", optInitialTabLast: "Dernier ouvert (Défaut)", labelImportExport: "Importer / Exporter", placeholderSettingsJSON: "Collez le JSON de sauvegarde ici...", tooltipSettings: "Ouvrir les paramètres", toastImported: "Importé.", toastExported: "Exporté vers un fichier.", alertInvalidJSON: "Fichier JSON invalide.", alertInvalidData: "Format de données invalide.", alertInvalidApp: 'Ce fichier n\'est pas une sauvegarde valide pour "Advanced Search for X".', buttonReset: "Réinitialiser tout", confirmResetAll: "Tout réinitialiser ? Cette action est irréversible.", toastReset: "Toutes les données ont été réinitialisées.", buttonImportSuccess: "Importation réussie 👍️", /* Favorites Sort */ sortSavedNewest: "Date d'ajout (Récent)", sortSavedOldest: "Date d'ajout (Ancien)", sortPostedNewest: "Date de publication (Récent)", sortPostedOldest: "Date de publication (Ancien)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Non classé', FT_DROPDOWN_TITLE: 'Tags favoris', FT_DROPDOWN_SETTINGS_TITLE: 'Réglages des tags', FT_DROPDOWN_NEW_TAG: 'Nouveau tag', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Nom du tag', FT_DROPDOWN_NEW_TAG_ADD: 'Ajouter', FT_FILTER_ALL: 'Tout', FT_SETTINGS_TITLE: 'Réglages des tags favoris', FT_SETTINGS_EMPTY_TAG_LIST: 'Aucun tag. Ajoutez-en un depuis "Nouveau tag".', FT_SETTINGS_UNCATEGORIZED_NAME: 'Non classé', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'Le nom "Non classé" ne peut pas être modifié.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Non classé" ne peut pas être supprimé.', FT_SETTINGS_CLOSE: 'Fermer', FT_SETTINGS_DELETE_BUTTON: 'Supprimer', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Affichage', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Format du tag', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Libellé seul (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Chemin complet', FT_CONFIRM_DELETE_TAG_MSG: 'Supprimer le tag "{tagName}" ?\nLes favoris associés deviendront "Non classé".', FT_SETTINGS_BUTTON_TITLE: 'Réglages des tags', }, 'es': { modalTitle: "Búsqueda avanzada", tooltipClose: "Cerrar", labelAllWords: "Todas estas palabras", placeholderAllWords: "ej. AI noticias", labelExactPhrase: "Esta frase exacta", placeholderExactPhrase: 'ej. "ChatGPT 4o"', labelAnyWords: "Cualquiera de estas palabras (OR)", placeholderAnyWords: "ej. iPhone Android", labelNotWords: "Ninguna de estas palabras (-)", placeholderNotWords: "ej. -oferta -anuncio", labelHashtag: "Hashtags (#)", placeholderHashtag: "ej. #Tecnología", labelLang: "Idioma (lang:)", optLangDefault: "Cualquier idioma", optLangJa: "Japonés (ja)", optLangEn: "Inglés (en)", optLangId: "Indonesio (id)", optLangHi: "Hindi (hi)", optLangDe: "Alemán (de)", optLangTr: "Turco (tr)", optLangEs: "Español (es)", optLangPt: "Portugués (pt)", optLangAr: "Árabe (ar)", optLangFr: "Francés (fr)", optLangKo: "Coreano (ko)", optLangRu: "Ruso (ru)", optLangZhHans: "Chino simplificado (zh-cn)", optLangZhHant: "Chino tradicional (zh-tw)", hrSeparator: " ", labelFilters: "Filtros", labelVerified: "Cuentas verificadas", labelLinks: "Enlaces", labelImages: "Imágenes", labelVideos: "Vídeos", labelReposts: "Reposts", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Incluir", checkExclude: "Excluir", labelReplies: "Respuestas", optRepliesDefault: "Por defecto (Todo)", optRepliesInclude: "Incluir respuestas", optRepliesOnly: "Solo respuestas", optRepliesExclude: "Excluir respuestas", labelEngagement: "Interacciones", placeholderMinReplies: "Mín. respuestas", placeholderMinLikes: "Mín. Me gusta", placeholderMinRetweets: "Mín. reposts", labelDateRange: "Rango de fechas", labelDateShortcut: "Rango rápido", optDate1Day: "Últimas 24 horas", optDate1Week: "Última semana", optDate1Month: "Último mes", optDate3Months: "Últimos 3 meses", optDate6Months: "Últimos 6 meses", optDate1Year: "Último año", optDate2Years: "Últimos 2 años", optDate3Years: "Últimos 3 años", optDate5Years: "Últimos 5 años", optDateClear: "Borrar fechas", tooltipSince: "Desde esta fecha", tooltipUntil: "Hasta esta fecha", labelFromUser: "De estas cuentas (from:)", placeholderFromUser: "ej. @X", labelToUser: "Para estas cuentas (to:)", placeholderToUser: "ej. @google", labelMentioning: "Mencionando a estas cuentas (@)", placeholderMentioning: "ej. @OpenAI", buttonClear: "Borrar", buttonApply: "Buscar", tooltipTrigger: "Abrir búsqueda avanzada", buttonOpen: "Abrir", tabSearch: "Búsqueda", tabHistory: "Historial", tabSaved: "Guardado", buttonSave: "Guardar", buttonSaved: "Guardado", secretMode: "Secreto", secretOn: "Modo secreto ACTIVADO (sin historial)", secretOff: "Modo secreto DESACTIVADO", toastSaved: "Guardado.", toastDeleted: "Eliminado.", toastReordered: "Orden actualizado.", emptyHistory: "Aún no hay historial.", emptySaved: "No hay búsquedas guardadas. Añade una desde el botón Guardar abajo a la izquierda en la pestaña Búsqueda.", run: "Ejecutar", delete: "Eliminar", updated: "Actualizado.", tooltipSecret: "Alternar modo secreto (no se guardará historial)", historyClearAll: "Borrar todo", confirmClearHistory: "¿Borrar todo el historial?", labelAccountScope: "Cuentas", optAccountAll: "Todas las cuentas", optAccountFollowing: "Cuentas que sigues", labelLocationScope: "Ubicación", optLocationAll: "Todas las ubicaciones", optLocationNearby: "Cerca de ti", chipFollowing: "Siguiendo", chipNearby: "Cerca", labelSearchTarget: "Ámbito de búsqueda", labelHitName: "Excluir coincidencias solo en nombre", labelHitHandle: "Excluir coincidencias solo en usuario (@)", hintSearchTarget: "Ocultar publicaciones que solo coincidan en el nombre o usuario (no en el cuerpo).", hintName: "Si la palabra clave aparece solo en el nombre mostrado, ocultarla.", hintHandle: "Si la palabra clave aparece solo en el @usuario, ocultarla. Excepción: si la consulta usa explícitamente from:/to:/@.", tabMute: "Silenciar", labelMuteWord: "Añadir palabra silenciada", placeholderMuteWord: "ej. spoiler", labelCaseSensitive: "Distinguir mayúsculas", labelWordBoundary: "Palabra completa", labelEnabled: "Habilitado", labelEnableAll: "Habilitar todo", buttonAdd: "Añadir", emptyMuted: "No hay palabras silenciadas.", mutedListTitle: "Palabras silenciadas", mutedListHeading: "Lista de silenciados", optMuteHidden: "Oculto", optMuteCollapsed: "Colapsado", placeholderFilterMute: "Filtrar palabras silenciadas...", muteLabel: "Silenciado: ", buttonShow: "Mostrar", muteHit: "Silenciar coincidencias en cuerpo", buttonRemute: "Volver a silenciar", buttonImport: "Importar", buttonExport: "Exportar", /* Accounts tab */ tabAccounts: "Cuentas", emptyAccounts: "Aún no hay cuentas. Abre un perfil y haz clic en el botón Añadir para guardarlo.", buttonAddAccount: "Añadir cuenta", toastAccountAdded: "Cuenta añadida.", toastAccountExists: "Ya existe.", buttonConfirm: "Confirmar", /* Lists tab */ tabLists: "Listas", emptyLists: "Aún no hay listas. Abre una lista y haz clic en el botón + arriba a la derecha para añadirla.", buttonAddList: "Añadir lista", toastListAdded: "Lista añadida.", toastListExists: "Ya existe.", /* History tab */ placeholderSearchHistory: "Historial de búsqueda (consulta)", labelSortBy: "Ordenar por:", placeholderSearchSaved: "Búsquedas guardadas (consulta)", sortNewest: "Más reciente", sortOldest: "Más antiguo", sortNameAsc: "Nombre (A-Z)", sortNameDesc: "Nombre (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Filtrar cuentas (@, nombre)", placeholderFilterLists: "Filtrar listas (nombre, url)", buttonAddFolder: "+Carpeta", folderFilterAll: "TODO", folderFilterUnassigned: "Sin asignar", folderRename: "Renombrar", folderRenameTitle: "Renombrar carpeta", folderDelete: "Eliminar", folderDeleteTitle: "Eliminar carpeta", promptNewFolder: "Nombre de nueva carpeta", confirmDeleteFolder: "¿Eliminar esta carpeta y todo su contenido? Esto no se puede deshacer.", optListsAll: "Listas", defaultSavedFolders: "Búsquedas guardadas", /* Favorites */ tabFavorites: "Favoritos", emptyFavorites: "No hay favoritos. Haz clic en el icono ★ de los tweets para guardarlos.", optFavoritesAll: "Todos los favoritos", toastFavorited: "Añadido a favoritos.", toastUnfavorited: "Eliminado de favoritos.", /* Settings */ settingsTitle: "Configuración", settingsTitleGeneral: "General", settingsTitleFeatures: "Visibilidad de pestañas", settingsTitleData: "Datos", buttonClose: "Cerrar", labelUILang: "Idioma de interfaz", optUILangAuto: "Automático", labelInitialTab: "Pestaña de inicio", optInitialTabLast: "Última abierta (Predeterminado)", labelImportExport: "Importar / Exportar", placeholderSettingsJSON: "Pega el JSON de respaldo aquí...", tooltipSettings: "Abrir configuración", toastImported: "Importado.", toastExported: "Exportado a archivo.", alertInvalidJSON: "Archivo JSON inválido.", alertInvalidData: "Formato de datos inválido.", alertInvalidApp: 'Este archivo no es un respaldo válido para "Advanced Search for X".', buttonReset: "Restablecer todo", confirmResetAll: "¿Restablecer todos los datos? Esto no se puede deshacer.", toastReset: "Todos los datos han sido restablecidos.", buttonImportSuccess: "Importado con éxito 👍️", /* Favorites Sort */ sortSavedNewest: "Fecha de guardado (Reciente)", sortSavedOldest: "Fecha de guardado (Antigua)", sortPostedNewest: "Fecha de publicación (Reciente)", sortPostedOldest: "Fecha de publicación (Antigua)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Sin categoría', FT_DROPDOWN_TITLE: 'Etiquetas de favoritos', FT_DROPDOWN_SETTINGS_TITLE: 'Configuración de etiquetas', FT_DROPDOWN_NEW_TAG: 'Nueva etiqueta', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Nombre de etiqueta', FT_DROPDOWN_NEW_TAG_ADD: 'Añadir', FT_FILTER_ALL: 'Todo', FT_SETTINGS_TITLE: 'Configuración de etiquetas', FT_SETTINGS_EMPTY_TAG_LIST: 'No hay etiquetas. Añade una desde "Nueva etiqueta".', FT_SETTINGS_UNCATEGORIZED_NAME: 'Sin categoría', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'El nombre "Sin categoría" no se puede cambiar.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Sin categoría" no se puede eliminar.', FT_SETTINGS_CLOSE: 'Cerrar', FT_SETTINGS_DELETE_BUTTON: 'Eliminar', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Visualización', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Formato de etiqueta', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Solo etiqueta (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Ruta completa (full)', FT_CONFIRM_DELETE_TAG_MSG: '¿Eliminar la etiqueta "{tagName}"?\nLos favoritos con esta etiqueta pasarán a "Sin categoría".', FT_SETTINGS_BUTTON_TITLE: 'Configuración de etiquetas', }, 'de': { modalTitle: "Erweiterte Suche", tooltipClose: "Schließen", labelAllWords: "All diese Wörter", placeholderAllWords: "z.B. AI Nachrichten", labelExactPhrase: "Genau dieser Ausdruck", placeholderExactPhrase: 'z.B. "ChatGPT 4o"', labelAnyWords: "Beliebige dieser Wörter (OR)", placeholderAnyWords: "z.B. iPhone Android", labelNotWords: "Keines dieser Wörter (-)", placeholderNotWords: "z.B. -Verkauf -Werbung", labelHashtag: "Hashtags (#)", placeholderHashtag: "z.B. #TechEvent", labelLang: "Sprache (lang:)", optLangDefault: "Beliebige Sprache", optLangJa: "Japanisch (ja)", optLangEn: "Englisch (en)", optLangId: "Indonesisch (id)", optLangHi: "Hindi (hi)", optLangDe: "Deutsch (de)", optLangTr: "Türkisch (tr)", optLangEs: "Spanisch (es)", optLangPt: "Portugiesisch (pt)", optLangAr: "Arabisch (ar)", optLangFr: "Französisch (fr)", optLangKo: "Koreanisch (ko)", optLangRu: "Russisch (ru)", optLangZhHans: "Chinesisch vereinfacht (zh-cn)", optLangZhHant: "Chinesisch traditionell (zh-tw)", hrSeparator: " ", labelFilters: "Filter", labelVerified: "Verifizierte Konten", labelLinks: "Links", labelImages: "Bilder", labelVideos: "Videos", labelReposts: "Reposts", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Einschl.", checkExclude: "Ausschl.", labelReplies: "Antworten", optRepliesDefault: "Standard (Alle)", optRepliesInclude: "Antworten einschließen", optRepliesOnly: "Nur Antworten", optRepliesExclude: "Antworten ausschließen", labelEngagement: "Interaktionen", placeholderMinReplies: "Min. Antworten", placeholderMinLikes: "Min. Gefällt mir", placeholderMinRetweets: "Min. Reposts", labelDateRange: "Zeitraum", labelDateShortcut: "Schnellauswahl", optDate1Day: "Letzte 24 Std.", optDate1Week: "Letzte Woche", optDate1Month: "Letzter Monat", optDate3Months: "Letzte 3 Monate", optDate6Months: "Letzte 6 Monate", optDate1Year: "Letztes Jahr", optDate2Years: "Letzte 2 Jahre", optDate3Years: "Letzte 3 Jahre", optDate5Years: "Letzte 5 Jahre", optDateClear: "Datum löschen", tooltipSince: "Seit diesem Datum", tooltipUntil: "Bis zu diesem Datum", labelFromUser: "Von diesen Konten (from:)", placeholderFromUser: "z.B. @X", labelToUser: "An diese Konten (to:)", placeholderToUser: "z.B. @google", labelMentioning: "Erwähnung dieser Konten (@)", placeholderMentioning: "z.B. @OpenAI", buttonClear: "Löschen", buttonApply: "Suchen", tooltipTrigger: "Erweiterte Suche öffnen", buttonOpen: "Öffnen", tabSearch: "Suche", tabHistory: "Verlauf", tabSaved: "Gespeichert", buttonSave: "Speichern", buttonSaved: "Gespeichert", secretMode: "Inkognito", secretOn: "Inkognito-Modus AN (Kein Verlauf)", secretOff: "Inkognito-Modus AUS", toastSaved: "Gespeichert.", toastDeleted: "Gelöscht.", toastReordered: "Reihenfolge aktualisiert.", emptyHistory: "Noch kein Verlauf.", emptySaved: "Keine gespeicherten Suchen. Fügen Sie welche über den Speichern-Button unten links im Suche-Tab hinzu.", run: "Ausführen", delete: "Löschen", updated: "Aktualisiert.", tooltipSecret: "Inkognito-Modus umschalten (kein Verlauf wird gespeichert)", historyClearAll: "Alle löschen", confirmClearHistory: "Gesamten Verlauf löschen?", labelAccountScope: "Konten", optAccountAll: "Alle Konten", optAccountFollowing: "Konten, denen du folgst", labelLocationScope: "Standort", optLocationAll: "Alle Standorte", optLocationNearby: "In deiner Nähe", chipFollowing: "Folge ich", chipNearby: "In der Nähe", labelSearchTarget: "Suchziel", labelHitName: "Treffer nur im Anzeigenamen ausschließen", labelHitHandle: "Treffer nur im Benutzernamen (@) ausschließen", hintSearchTarget: "Beiträge ausblenden, die nur im Namen oder Handle übereinstimmen (nicht im Text).", hintName: "Wenn ein Stichwort nur im Anzeigenamen vorkommt, ausblenden.", hintHandle: "Wenn ein Stichwort nur im @Benutzernamen vorkommt, ausblenden. Ausnahme: wenn die Anfrage explizit from:/to:/@ verwendet.", tabMute: "Stummschalten", labelMuteWord: "Stummes Wort hinzufügen", placeholderMuteWord: "z.B. Spoiler", labelCaseSensitive: "Groß-/Kleinschreibung", labelWordBoundary: "Ganzes Wort", labelEnabled: "Aktiviert", labelEnableAll: "Alle aktivieren", buttonAdd: "Hinzufügen", emptyMuted: "Keine stummgeschalteten Wörter.", mutedListTitle: "Stummgeschaltete Wörter", mutedListHeading: "Stummgeschaltete Liste", optMuteHidden: "Verborgen", optMuteCollapsed: "Eingeklappt", placeholderFilterMute: "Stummgeschaltete Wörter filtern...", muteLabel: "Stummgeschaltet: ", buttonShow: "Anzeigen", muteHit: "Treffer im Text stummschalten", buttonRemute: "Erneut stummschalten", buttonImport: "Importieren", buttonExport: "Exportieren", /* Accounts tab */ tabAccounts: "Konten", emptyAccounts: "Noch keine Konten. Öffnen Sie ein Profil und klicken Sie auf Hinzufügen, um es zu speichern.", buttonAddAccount: "Konto hinzufügen", toastAccountAdded: "Konto hinzugefügt.", toastAccountExists: "Bereits vorhanden.", buttonConfirm: "Bestätigen", /* Lists tab */ tabLists: "Listen", emptyLists: "Noch keine Listen. Öffnen Sie eine Liste und klicken Sie oben rechts auf +, um sie hinzuzufügen.", buttonAddList: "Liste hinzufügen", toastListAdded: "Liste hinzugefügt.", toastListExists: "Bereits vorhanden.", /* History tab */ placeholderSearchHistory: "Suchverlauf (Query)", labelSortBy: "Sortieren nach:", placeholderSearchSaved: "Gespeicherte Suchen (Query)", sortNewest: "Neueste zuerst", sortOldest: "Älteste zuerst", sortNameAsc: "Name (A-Z)", sortNameDesc: "Name (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Konten filtern (@, Name)", placeholderFilterLists: "Listen filtern (Name, URL)", buttonAddFolder: "+Ordner", folderFilterAll: "ALLE", folderFilterUnassigned: "Nicht zugewiesen", folderRename: "Umbenennen", folderRenameTitle: "Ordner umbenennen", folderDelete: "Löschen", folderDeleteTitle: "Ordner löschen", promptNewFolder: "Neuer Ordnername", confirmDeleteFolder: "Diesen Ordner und alle Elemente darin löschen? Dies kann nicht rückgängig gemacht werden.", optListsAll: "Listen", defaultSavedFolders: "Gespeicherte Suchen", /* Favorites */ tabFavorites: "Favoriten", emptyFavorites: "Keine Favoriten. Klicken Sie auf das ★-Symbol bei Tweets, um sie zu speichern.", optFavoritesAll: "Alle Favoriten", toastFavorited: "Zu Favoriten hinzugefügt.", toastUnfavorited: "Aus Favoriten entfernt.", /* Settings */ settingsTitle: "Einstellungen", settingsTitleGeneral: "Allgemein", settingsTitleFeatures: "Tab-Sichtbarkeit", settingsTitleData: "Daten", buttonClose: "Schließen", labelUILang: "Oberflächensprache", optUILangAuto: "Automatisch", labelInitialTab: "Start-Tab", optInitialTabLast: "Zuletzt geöffnet (Standard)", labelImportExport: "Import / Export", placeholderSettingsJSON: "Backup-JSON hier einfügen...", tooltipSettings: "Einstellungen öffnen", toastImported: "Importiert.", toastExported: "In Datei exportiert.", alertInvalidJSON: "Ungültige JSON-Datei.", alertInvalidData: "Ungültiges Datenformat.", alertInvalidApp: 'Diese Datei ist kein gültiges Backup für "Advanced Search for X".', buttonReset: "Alle Daten zurücksetzen", confirmResetAll: "Alle Daten zurücksetzen? Dies kann nicht rückgängig gemacht werden.", toastReset: "Alle Daten wurden zurückgesetzt.", buttonImportSuccess: "Erfolgreich importiert 👍️", /* Favorites Sort */ sortSavedNewest: "Speicherdatum (Neu)", sortSavedOldest: "Speicherdatum (Alt)", sortPostedNewest: "Veröffentlichungsdatum (Neu)", sortPostedOldest: "Veröffentlichungsdatum (Alt)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Unkategorisiert', FT_DROPDOWN_TITLE: 'Favoriten-Tags', FT_DROPDOWN_SETTINGS_TITLE: 'Tag-Einstellungen', FT_DROPDOWN_NEW_TAG: 'Neuer Tag', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Tag-Name', FT_DROPDOWN_NEW_TAG_ADD: 'Hinzufügen', FT_FILTER_ALL: 'Alle', FT_SETTINGS_TITLE: 'Favoriten-Tag-Einstellungen', FT_SETTINGS_EMPTY_TAG_LIST: 'Keine Tags. Fügen Sie einen über "Neuer Tag" hinzu.', FT_SETTINGS_UNCATEGORIZED_NAME: 'Unkategorisiert', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'Der Name "Unkategorisiert" kann nicht geändert werden.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Unkategorisiert" kann nicht gelöscht werden.', FT_SETTINGS_CLOSE: 'Schließen', FT_SETTINGS_DELETE_BUTTON: 'Löschen', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Anzeige', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Tag-Format', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Nur Label (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Voller Pfad (full)', FT_CONFIRM_DELETE_TAG_MSG: 'Tag "{tagName}" löschen?\nFavoriten mit diesem Tag werden "Unkategorisiert".', FT_SETTINGS_BUTTON_TITLE: 'Tag-Einstellungen', }, 'pt-BR': { modalTitle: "Busca avançada", tooltipClose: "Fechar", labelAllWords: "Todas estas palavras", placeholderAllWords: "ex: AI notícias", labelExactPhrase: "Esta frase exata", placeholderExactPhrase: 'ex: "ChatGPT 4o"', labelAnyWords: "Qualquer destas palavras (OR)", placeholderAnyWords: "ex: iPhone Android", labelNotWords: "Nenhuma destas palavras (-)", placeholderNotWords: "ex: -promoção -ads", labelHashtag: "Hashtags (#)", placeholderHashtag: "ex: #Tecnologia", labelLang: "Idioma (lang:)", optLangDefault: "Qualquer idioma", optLangJa: "Japonês (ja)", optLangEn: "Inglês (en)", optLangId: "Indonésio (id)", optLangHi: "Hindi (hi)", optLangDe: "Alemão (de)", optLangTr: "Turco (tr)", optLangEs: "Espanhol (es)", optLangPt: "Português (pt)", optLangAr: "Árabe (ar)", optLangFr: "Francês (fr)", optLangKo: "Coreano (ko)", optLangRu: "Russo (ru)", optLangZhHans: "Chinês Simplificado (zh-cn)", optLangZhHant: "Chinês Tradicional (zh-tw)", hrSeparator: " ", labelFilters: "Filtros", labelVerified: "Contas verificadas", labelLinks: "Links", labelImages: "Imagens", labelVideos: "Vídeos", labelReposts: "Reposts", labelTimelineHashtags: "Hashtags (#)", checkInclude: "Incluir", checkExclude: "Excluir", labelReplies: "Respostas", optRepliesDefault: "Padrão (Tudo)", optRepliesInclude: "Incluir respostas", optRepliesOnly: "Apenas respostas", optRepliesExclude: "Excluir respostas", labelEngagement: "Engajamento", placeholderMinReplies: "Mín respostas", placeholderMinLikes: "Mín curtidas", placeholderMinRetweets: "Mín reposts", labelDateRange: "Período", labelDateShortcut: "Intervalo rápido", optDate1Day: "Últimas 24h", optDate1Week: "Última semana", optDate1Month: "Último mês", optDate3Months: "Últimos 3 meses", optDate6Months: "Últimos 6 meses", optDate1Year: "Último ano", optDate2Years: "Últimos 2 anos", optDate3Years: "Últimos 3 anos", optDate5Years: "Últimos 5 anos", optDateClear: "Limpar datas", tooltipSince: "A partir desta data", tooltipUntil: "Até esta data", labelFromUser: "Destas contas (from:)", placeholderFromUser: "ex: @X", labelToUser: "Para estas contas (to:)", placeholderToUser: "ex: @google", labelMentioning: "Mencionando estas contas (@)", placeholderMentioning: "ex: @OpenAI", buttonClear: "Limpar", buttonApply: "Buscar", tooltipTrigger: "Abrir busca avançada", buttonOpen: "Abrir", tabSearch: "Busca", tabHistory: "Histórico", tabSaved: "Salvos", buttonSave: "Salvar", buttonSaved: "Salvo", secretMode: "Secreto", secretOn: "Modo secreto ON (Sem histórico)", secretOff: "Modo secreto OFF", toastSaved: "Salvo.", toastDeleted: "Excluído.", toastReordered: "Ordem atualizada.", emptyHistory: "Sem histórico ainda.", emptySaved: "Nenhuma busca salva. Adicione pelo botão Salvar no canto inferior esquerdo da aba Busca.", run: "Executar", delete: "Excluir", updated: "Atualizado.", tooltipSecret: "Alternar Modo Secreto (histórico não será gravado)", historyClearAll: "Limpar tudo", confirmClearHistory: "Limpar todo o histórico?", labelAccountScope: "Contas", optAccountAll: "Todas as contas", optAccountFollowing: "Contas que você segue", labelLocationScope: "Localização", optLocationAll: "Todas as localizações", optLocationNearby: "Perto de você", chipFollowing: "Seguindo", chipNearby: "Próximo", labelSearchTarget: "Alvo da busca", labelHitName: "Excluir resultados apenas no nome", labelHitHandle: "Excluir resultados apenas no usuário (@)", hintSearchTarget: "Ocultar posts que correspondem apenas ao nome ou usuário (não no corpo).", hintName: "Se a palavra-chave aparecer apenas no nome de exibição, ocultar.", hintHandle: "Se a palavra-chave aparecer apenas no @usuario, ocultar. Exceção: quando a consulta usar explicitamente from:/to:/@.", tabMute: "Silenciar", labelMuteWord: "Adicionar palavra silenciada", placeholderMuteWord: "ex: spoiler", labelCaseSensitive: "Diferenciar maiúsculas", labelWordBoundary: "Palavra inteira", labelEnabled: "Ativado", labelEnableAll: "Ativar tudo", buttonAdd: "Adicionar", emptyMuted: "Nenhuma palavra silenciada.", mutedListTitle: "Palavras silenciadas", mutedListHeading: "Lista de silenciados", optMuteHidden: "Oculto", optMuteCollapsed: "Colapsado", placeholderFilterMute: "Filtrar palavras silenciadas...", muteLabel: "Silenciado: ", buttonShow: "Mostrar", muteHit: "Silenciar resultados no corpo", buttonRemute: "Silenciar novamente", buttonImport: "Importar", buttonExport: "Exportar", /* Accounts tab */ tabAccounts: "Contas", emptyAccounts: "Nenhuma conta ainda. Abra um perfil e clique no botão Adicionar para salvar.", buttonAddAccount: "Adicionar conta", toastAccountAdded: "Conta adicionada.", toastAccountExists: "Já adicionada.", buttonConfirm: "Confirmar", /* Lists tab */ tabLists: "Listas", emptyLists: "Nenhuma lista ainda. Abra uma Lista e clique no botão + no canto superior direito para adicionar.", buttonAddList: "Adicionar lista", toastListAdded: "Lista adicionada.", toastListExists: "Já adicionada.", /* History tab */ placeholderSearchHistory: "Histórico de busca (query)", labelSortBy: "Ordenar por:", placeholderSearchSaved: "Buscas salvas (query)", sortNewest: "Mais recente", sortOldest: "Mais antigo", sortNameAsc: "Nome (A-Z)", sortNameDesc: "Nome (Z-A)", /* Folder/List/Account tabs */ placeholderFilterAccounts: "Filtrar contas (@, nome)", placeholderFilterLists: "Filtrar listas (nome, url)", buttonAddFolder: "+Pasta", folderFilterAll: "TUDO", folderFilterUnassigned: "Não atribuído", folderRename: "Renomear", folderRenameTitle: "Renomear pasta", folderDelete: "Excluir", folderDeleteTitle: "Excluir pasta", promptNewFolder: "Nome da nova pasta", confirmDeleteFolder: "Excluir esta pasta e todos os itens dentro dela? Isso não pode ser desfeito.", optListsAll: "Listas", defaultSavedFolders: "Buscas Salvas", /* Favorites */ tabFavorites: "Favoritos", emptyFavorites: "Nenhum favorito ainda. Clique no ícone ★ nos tweets para salvar.", optFavoritesAll: "Todos os favoritos", toastFavorited: "Adicionado aos favoritos.", toastUnfavorited: "Removido dos favoritos.", /* Settings */ settingsTitle: "Configurações", settingsTitleGeneral: "Geral", settingsTitleFeatures: "Visibilidade de abas", settingsTitleData: "Dados", buttonClose: "Fechar", labelUILang: "Idioma da interface", optUILangAuto: "Automático", labelInitialTab: "Aba inicial", optInitialTabLast: "Última aberta (Padrão)", labelImportExport: "Importar / Exportar", placeholderSettingsJSON: "Cole o JSON de backup aqui...", tooltipSettings: "Abrir configurações", toastImported: "Importado.", toastExported: "Exportado para arquivo.", alertInvalidJSON: "Arquivo JSON inválido.", alertInvalidData: "Formato de dados inválido.", alertInvalidApp: 'Este arquivo não é um backup válido para "Advanced Search for X".', buttonReset: "Redefinir tudo", confirmResetAll: "Redefinir todos os dados? Isso não pode ser desfeito.", toastReset: "Todos os dados foram redefinidos.", buttonImportSuccess: "Importado com sucesso 👍️", /* Favorites Sort */ sortSavedNewest: "Data (Mais recente)", sortSavedOldest: "Data (Mais antigo)", sortPostedNewest: "Postado (Mais recente)", sortPostedOldest: "Postado (Mais antigo)", /* --- Favorite Tags --- */ FT_UNCATEGORIZED: 'Sem categoria', FT_DROPDOWN_TITLE: 'Tags favoritas', FT_DROPDOWN_SETTINGS_TITLE: 'Configurações de tags', FT_DROPDOWN_NEW_TAG: 'Nova tag', FT_DROPDOWN_NEW_TAG_PLACEHOLDER: 'Nome da tag', FT_DROPDOWN_NEW_TAG_ADD: 'Adicionar', FT_FILTER_ALL: 'Tudo', FT_SETTINGS_TITLE: 'Configurações de tags favoritas', FT_SETTINGS_EMPTY_TAG_LIST: 'Sem tags. Adicione em "Nova tag".', FT_SETTINGS_UNCATEGORIZED_NAME: 'Sem categoria', FT_SETTINGS_UNCATEGORIZED_NAME_TOOLTIP: 'O nome "Sem categoria" não pode ser alterado.', FT_SETTINGS_UNCATEGORIZED_DELETE_TOOLTIP: '"Sem categoria" não pode ser excluída.', FT_SETTINGS_CLOSE: 'Fechar', FT_SETTINGS_DELETE_BUTTON: 'Excluir', FT_SETTINGS_UP: '▲', FT_SETTINGS_DOWN: '▼', FT_SETTINGS_DISPLAY_SECTION_TITLE: 'Exibição', FT_SETTINGS_DISPLAY_MODE_LABEL: 'Formato da tag', FT_SETTINGS_DISPLAY_MODE_LEAF: 'Apenas etiqueta (leaf)', FT_SETTINGS_DISPLAY_MODE_FULL: 'Caminho completo (full)', FT_CONFIRM_DELETE_TAG_MSG: 'Excluir tag "{tagName}"?\nFavoritos com esta tag ficarão "Sem categoria".', FT_SETTINGS_BUTTON_TITLE: 'Configurações de tags', }, 'ru': { modalTitle: "Расширенный поиск", tooltipClose: "Закрыть", labelAllWords: "Все эти слова", placeholderAllWords: "напр., AI новости", labelExactPhrase: "Точная фраза", placeholderExactPhrase: 'напр., "ChatGPT 4o"', labelAnyWords: "Любое из этих слов (OR)", placeholderAnyWords: "напр., iPhone Android", labelNotWords: "Исключить слова (-)", placeholderNotWords: "напр., -распродажа -реклама", labelHashtag: "Хэштеги (#)", placeholderHashtag: "напр., #TechEvent", 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: "Диапазон дат", labelDateShortcut: "Быстрый выбор", optDate1Day: "За 24 часа", optDate1Week: "За неделю", optDate1Month: "За месяц", optDate3Months: "За 3 месяца", optDate6Months: "За 6 месяцев", optDate1Year: "За год", optDate2Years: "За 2 года", optDate3Years: "За 3 года", optDate5Years: "За 5 лет", optDateClear: "Очистить даты", tooltipSince: "С этой даты", tooltipUntil: "По эту дату", labelFromUser: "От этих аккаунтов (from:)", placeholderFromUser: "напр., @X", labelToUser: "Этим аккаунтам (to:)", placeholderToUser: "напр., @google", labelMentioning: "Упоминание этих аккаунтов (@)", placeholderMentioning: "напр., @OpenAI", buttonClear: "Очистить", buttonApply: "Поиск", tooltipTrigger: "Открыть расширенный поиск", buttonOpen: "Открыть", tabSearch: "Поиск", tabHistory: "История", tabSaved: "Сохраненное", buttonSave: "Сохранить", buttonSaved: "Сохранено", secretMode: "Секретный", secretOn: "Секретный режим ВКЛ (без истории)", secretOff: "Секретный режим ВЫКЛ", 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: "Учитывать регистр", labelWordBoundary: "Слово целиком", labelEnabled: "Включено", labelEnableAll: "Включить все", buttonAdd: "Добавить", emptyMuted: "Нет скрытых слов.", mutedListTitle: "Скрытые слова", mutedListHeading: "Список скрытого", optMuteHidden: "Скрыто", optMuteCollapsed: "Свернуто", placeholderFilterMute: "Фильтр скрытых слов...", muteLabel: "Скрыто: ", buttonShow: "Показать", muteHit: "Скрывать совпадения в тексте", buttonRemute: "Скрыть снова", 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: "Язык интерфейса", optUILangAuto: "Авто", labelInitialTab: "Вкладка при запуске", optInitialTabLast: "Последняя открытая (По умолч.)", 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: 'Настройка тегов', } }, 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 = ` `; const SETTINGS_SVG = ` `; const FOLDER_TOGGLE_OPEN_SVG = ` `; const FOLDER_TOGGLE_CLOSED_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', '--modal-tabs-shadow': '0 1px 12px rgba(0, 0, 0, 0.22)', }, 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', '--modal-tabs-shadow': '0 5px 12px rgba(0, 0, 0, 0.27)', }, 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', '--modal-tabs-shadow': '0 5px 12px rgba(0, 0, 0, 0.27)', } }, 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); } }; /** * Mobile Drag & Drop Shim * タッチイベントを検知し、HTML5 Drag & Dropイベント(dragstart, dragover, drop等)を発火させる */ function enableMobileDragSupport() { let dragSource = null; let lastTarget = null; // DataTransferのデータを保持する擬似ストア let dataTransferStore = {}; // 擬似的な DragEvent を作成するヘルパー const createEvent = (type, touch, target) => { const event = new CustomEvent(type, { bubbles: true, cancelable: true }); // dataTransfer オブジェクトを擬似的に再現 event.dataTransfer = { effectAllowed: 'move', dropEffect: 'move', types: Object.keys(dataTransferStore), setData: (format, data) => { dataTransferStore[format] = data; }, getData: (format) => dataTransferStore[format], clearData: () => { dataTransferStore = {}; } }; // 座標情報を付与 (getDragAfterElement 等の計算に必要) event.clientX = touch.clientX; event.clientY = touch.clientY; event.pageX = touch.pageX; event.pageY = touch.pageY; // ターゲット要素を上書き設定 (CustomEventの制約回避) Object.defineProperty(event, 'target', { value: target, enumerable: true }); return event; }; const onTouchStart = (e) => { // ハンドル、またはドラッグ可能な要素自体へのタッチか判定 const handle = e.target.closest('.adv-item-handle, .adv-folder-header, .adv-tab-btn, .ft-modal-tag-drag-handle'); if (!handle) return; const draggable = handle.closest('[draggable="true"]'); if (!draggable) return; dragSource = draggable; dataTransferStore = {}; // データ初期化 const touch = e.changedTouches[0]; const evt = createEvent('dragstart', touch, dragSource); dragSource.dispatchEvent(evt); }; const onTouchMove = (e) => { if (!dragSource) return; // スクロール防止(CSSのtouch-actionで防げない場合用) if (e.cancelable) e.preventDefault(); const touch = e.changedTouches[0]; // 指の下にある要素を取得 const element = document.elementFromPoint(touch.clientX, touch.clientY); if (!element) return; // dragover は頻繁に発火させる必要がある // ターゲットが変わった場合は dragenter/dragleave も検討すべきだが、 // このアプリのロジック(並び替え)では dragover がメインのため、そこに集中する // 既存ロジックが .closest('.adv-item') 等を使っているため、適切なターゲットに対して発火 // ここでは elementFromPoint で取れた要素に対して dragover を投げる const evt = createEvent('dragover', touch, element); element.dispatchEvent(evt); lastTarget = element; }; const onTouchEnd = (e) => { if (!dragSource) return; const touch = e.changedTouches[0]; // 最後に指があった要素に対して drop を発火 if (lastTarget) { const evtDrop = createEvent('drop', touch, lastTarget); lastTarget.dispatchEvent(evtDrop); } const evtEnd = createEvent('dragend', touch, dragSource); dragSource.dispatchEvent(evtEnd); // クリーンアップ dragSource = null; lastTarget = null; dataTransferStore = {}; }; document.addEventListener('touchstart', onTouchStart, { passive: false }); document.addEventListener('touchmove', onTouchMove, { passive: false }); document.addEventListener('touchend', onTouchEnd); } function decodeURIComponentSafe(s) { try { return decodeURIComponent(s); } catch { return s; } } // 正規表現の特殊文字をエスケープする function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // “ ” 『』などのスマート引用を 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); })(); }); } // ▼ State生成(React Routerの仕様に準拠したクリーンなState) function createCleanState(currentPath) { // 6文字のランダムな英数字 (React Router標準のkey生成ロジック模倣) const key = Math.random().toString(36).slice(2, 8); return { key: key, state: { fromApp: true, previousPath: currentPath || location.pathname, // ここに focalTweetId や context などの「前の文脈」を含めないことで // React Router に「新しいビューとしてレンダリング」させる } }; } // ▼ SPA 遷移関数(Chrome/Firefox/Safari全対応・Sandbox突破版) 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'); // 1. クリーンなStateを作成 let nextState = createCleanState(location.pathname); // 2.【Firefox対策】特権領域(UserScript)のオブジェクトをページ領域へ複製 // これをしないと Firefox で "Permission denied to access property" エラーになる if (typeof cloneInto === 'function') { nextState = cloneInto(nextState, document.defaultView || window); } // 3. 環境に応じたグローバルオブジェクトの取得 // unsafeWindow が使えるなら、ページ本来の window (実体) を使う const targetWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window; // 4. History API 更新 // ページ実体の history を操作する targetWindow.history.pushState(nextState, '', to.pathname + to.search + to.hash); // 5. イベント発火 (PopStateEvent) // React Router は window.addEventListener('popstate', ...) で待機しているため // ページ実体のコンストラクタでイベントを作り、ページ実体の window で発火する const PopStateEventClass = targetWindow.PopStateEvent || PopStateEvent; const evt = new PopStateEventClass('popstate', { state: nextState }); targetWindow.dispatchEvent(evt); // 6. 適用待ち (DOM変化の監視) const ok = await waitForRouteApply(to.pathname, timeoutMs); if (ok) return; } catch (e) { console.error('[spaNavigate] Fallback triggered due to:', e); // エラー時はフォールバックへ進む } // フォールバック(通常遷移: ページリロード発生) 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); // ▼ 自動的に閉じるパスかどうかを判定する関数 const isAutoClosePath = (pathname) => { const targets = ['/messages', '/i/grok', '/settings', '/i/chat', '/i/spaces', '/i/monetization']; return targets.some(t => pathname.startsWith(t)); }; GM_addStyle(` :root { --modal-primary-color:#1d9bf0; --modal-primary-color-hover:#1a8cd8; --modal-primary-text-color:#fff; } #layers { z-index: 6000 !important; } /* #layers (Grok/DM) をモーダルより手前に表示する設定 */ #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: 5000; 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{transform-origin:top left; 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:8px;align-items: center;} .adv-form-group-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; gap: 12px; } .adv-form-group-header label { margin-bottom: 0; white-space: nowrap; flex-shrink: 0; } .adv-select-mini { background-color: var(--modal-input-bg, #202327); color: var(--modal-text-primary, #e7e9ea); border: 1px solid var(--modal-input-border, #38444d); border-radius: 18px !important; font-size: 13px !important; height: 34px; line-height: normal; padding: 0 8px; width: auto; min-width: 90px; max-width: 200px; text-overflow: ellipsis; cursor: pointer; outline: none; } .adv-select-mini:hover { border-color: var(--modal-text-secondary, #8b98a5); } .adv-select-mini:focus { border-color: var(--modal-primary-color); } .adv-form-group-date-container input[type=date] {flex:1;min-width: 0;width: auto !important;} .adv-date-separator {color:var(--modal-text-secondary, #8b98a5);font-weight:700;user-select:none;flex-shrink:0;padding: 0 2px;} .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{transform-origin:top left; 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; transform-origin: top left; border-bottom: 1px solid var(--modal-border, #333); padding: 0 8px 0 6px; gap: 4px; align-items: stretch; flex-wrap: wrap; container-type: inline-size; /* ▼ 固定表示設定 */ position: sticky; top: 0; z-index: 10; background-color: var(--modal-bg, #000); box-shadow: var(--modal-tabs-shadow); } .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); touch-action: none; } .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: block; line-height: 1.5; } .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 10px; display:flex; gap:10px; justify-content: space-between; align-items:center; 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-content-left { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; } .adv-mute-word { font-weight:700; color:var(--modal-text-primary,#e7e9ea); word-break:break-word; font-size: 14px; } /* 左下のオプション群 */ .adv-mute-options-row { display: flex; gap: 12px; align-items: center; } /* 右側のコンテナ(削除ボタンのみ) */ .adv-mute-actions-right { display:flex; align-items:center; justify-content:center; flex: 0 0 auto; white-space: nowrap; padding-left: 4px; } @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; } /* ▼▼▼ Mute Header Fix ▼▼▼ */ .adv-mute-header { display:flex; justify-content:space-between; align-items:center; margin: 4px 0 12px; gap: 10px; flex-wrap: nowrap; /* 折り返しを禁止して1行に強制 */ } .adv-mute-header input[type="text"] { flex: 1; min-width: 0; border-radius: 8px; padding: 6px 10px; font-size: 14px; background-color: var(--modal-input-bg,#202327); border: 1px solid var(--modal-input-border,#38444d); color: var(--modal-text-primary,#e7e9ea); } .adv-mute-header input[type="text"]:focus { outline: 0; border-color: var(--modal-primary-color); } .adv-mute-title { font-weight:700; color: var(--modal-text-primary,#e7e9ea); white-space: nowrap; /* テキスト折り返し禁止 */ overflow: hidden; text-overflow: ellipsis; /* 溢れたら...にする */ flex-shrink: 1; /* 幅不足時はタイトル側を縮める */ min-width: 0; } .adv-mute-header-controls { display: flex; align-items: center; gap: 8px; /* 余白を少し詰める */ flex-shrink: 0; /* 操作パネルは縮めない */ } #adv-mute-mode { padding: 3px 24px 3px 8px; /* 矢印スペース考慮 */ font-size: 12px; height: 28px; cursor: pointer; width: auto; } /* マスター切替の一瞬だけ付けるガードクラス */ .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" を使っているので配色は自動追従 */ } /* リストコンテナ自体に十分な高さを確保し、下部にドロップ用の余白を強制的に広げる */ #adv-accounts-list, #adv-lists-list, #adv-saved-list { min-height: 200px; /* アイテムが空でもドロップできるようにする */ padding-bottom: 20px; box-sizing: content-box; /* padding分を確実に高さに加える */ } /* 未分類セクションが空の時も、ドラッグ中は少し広げて受け入れやすくする */ body.adv-dragging .adv-unassigned { min-height: 60px; background-color: rgba(128, 128, 128, 0.05); /* 視覚的にエリアを暗示 */ border-radius: 8px; transition: min-height 0.2s ease, background-color 0.2s; } /* === 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; touch-action: none; } .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; /* ボタン内の文字を縦中央に */ justify-content: center; /* ボタン内の文字を横中央に */ margin-left: 7px; padding: 0 8px; height: 20px; border-radius: 9999px; border: 1px solid currentColor; font-size: 11px; line-height: 1; cursor: pointer; user-select: none; white-space: nowrap; background: rgba(255, 255, 255, 0.03); flex: 0 0 auto; order: 9999; align-self: center; vertical-align: middle; } .ft-tag-chip-label { max-width: 150px; overflow: hidden; text-overflow: ellipsis; padding-bottom: 0.4px; } .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; min-width: 0; box-sizing: border-box; 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; box-sizing: border-box; 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; white-space: nowrap; } .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; padding-bottom: 30px; /* 余白を大きく取る */ min-height: 100px; /* 空っぽでもドロップできるように */ position: relative; /* ルートドロップの枠線表示用 */ box-sizing: content-box; /* paddingを含めない高さ計算 */ } /* 一番下の余白にドラッグした時に、リスト全体の下に枠線を出すクラス */ .ft-modal-tag-list.ft-drag-to-root::after { content: ''; position: absolute; bottom: 20px; /* 余白の中ほどに線を引く */ left: 0; right: 0; height: 2px; background-color: var(--modal-primary-color, #1d9bf0); box-shadow: 0 0 4px var(--modal-primary-color, #1d9bf0); } .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, .ft-modal-tag-item-drop-after::after { content: ''; position: absolute; left: 0; right: 0; height: 1px; /* 従来の色(白っぽいグレー) */ background-color: var(--ft-border-accent, rgba(239, 243, 244, 0.8)); border: none; /* border-top/bottom を background-color に変更して統一 */ pointer-events: none; } .ft-modal-tag-item-drop-before::before { top: -3.5px; } .ft-modal-tag-item-drop-after::after { bottom: -3.5px; } /* ルート階層用(青い線) */ .ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-before::before, .ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-after::after { background-color: var(--modal-primary-color, #1d9bf0); box-shadow: 0 0 4px var(--modal-primary-color, #1d9bf0); /* 発光させて強調 */ height: 2px; z-index: 10; } /* 青い線の位置(太くなった分、あるいは強調のため少し外側に広げる) */ .ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-before::before { top: -4.2px; } .ft-modal-tag-item.ft-is-root-ref.ft-modal-tag-item-drop-after::after { bottom: -4.2px; } .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; touch-action: 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; } /* --- Link Card (OGP) --- */ .adv-card-box { margin-top: 8px; border: 1px solid var(--modal-border); border-radius: 12px; overflow: hidden; text-decoration: none; display: flex; /* Flexに変更(Small Card対応) */ flex-direction: column; transition: background-color 0.2s; } .adv-card-box:hover { background-color: rgba(255, 255, 255, 0.03); } /* 通常の画像 */ .adv-card-image { width: 100%; height: auto; aspect-ratio: 1.91 / 1; object-fit: cover; display: block; border-bottom: 1px solid var(--modal-border); } /* Small Card用レイアウト */ .adv-card-box.small-card { flex-direction: row; /* 横並び */ align-items: center; height: 100px; /* 高さを固定 */ } .adv-card-box.small-card .adv-card-image { width: 100px; height: 100px; aspect-ratio: 1 / 1; border-bottom: none; border-right: 1px solid var(--modal-border); flex-shrink: 0; } /* SVGアイコン用コンテナ */ .adv-card-icon-container { width: 100px; height: 100px; display: flex; align-items: center; justify-content: center; background-color: var(--modal-input-bg); border-right: 1px solid var(--modal-border); color: var(--modal-text-secondary); flex-shrink: 0; } .adv-card-icon-container svg { width: 24px; height: 24px; fill: currentColor; } .adv-card-content { padding: 8px 12px; flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; } .adv-card-domain { font-size: 13px; color: var(--modal-text-secondary); margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .adv-card-title { font-size: 14px; font-weight: 700; color: var(--modal-text-primary); line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } /* Favorites Item Tag Container */ .adv-fav-tag-container { display: inline-block; margin-left: -2px; margin-bottom: 1px; vertical-align: middle; transform: translateY(-2.5px); } /* --- Mute Collapse Styles --- */ /* Hard Mute: data-adv-hidden */ [data-testid="cellInnerDiv"][data-adv-hidden], article[data-adv-hidden] { display: none !important; content-visibility: hidden; contain: strict; } /* Soft Mute: data-adv-collapsed */ /* 1. Hide original content */ [data-testid="cellInnerDiv"][data-adv-collapsed] > div:not(.adv-collapsed-placeholder), article[data-adv-collapsed] > div:not(.adv-collapsed-placeholder) { display: none !important; } /* 2. Show placeholder */ .adv-collapsed-placeholder { display: none; align-items: center; justify-content: space-between; padding: 12px 16px; background-color: var(--modal-input-bg, #202327); border-bottom: 1px solid var(--modal-border, #38444d); cursor: pointer; user-select: none; } .adv-collapsed-placeholder:hover { background-color: color-mix(in srgb, var(--modal-input-bg, #202327) 85%, var(--modal-text-primary, #e7e9ea)); } [data-testid="cellInnerDiv"][data-adv-collapsed] .adv-collapsed-placeholder, article[data-adv-collapsed] .adv-collapsed-placeholder { display: flex !important; } .adv-collapsed-label { flex: 1; font-size: 13px; color: var(--modal-text-secondary, #8b98a5); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 12px; } .adv-btn-show { background: transparent; border: 1px solid var(--modal-primary-color, #1d9bf0); color: var(--modal-primary-color, #1d9bf0); border-radius: 9999px; padding: 4px 16px; font-size: 12px; font-weight: 700; cursor: pointer; transition: background-color 0.2s; } .adv-btn-show:hover { background-color: rgba(29, 155, 240, 0.1); } /* タグチップのサイズ微調整 */ .adv-item-sub .ft-tag-chip { margin-left: 8px; font-size: 10px; padding: 0 6px; height: 18px; } /* ▼▼▼ 再ミュートボタンのスタイル ▼▼▼ */ .adv-btn-remute { margin-right: 12px; /* Caret(…)との間隔を確保 */ padding: 4px 12px; /* クリックしやすいよう少し拡大 */ font-size: 12px; font-weight: 700; border-radius: 9999px; border: 1px solid var(--modal-border, #38444d); color: var(--modal-text-secondary, #8b98a5); background: transparent; cursor: pointer; white-space: nowrap; display: inline-flex; align-items: center; height: 28px; /* ヘッダーのアクションボタンの高さに合わせる */ line-height: 1; transition: all 0.2s; } .adv-btn-remute:hover { background: rgba(244, 33, 46, 0.1); /* Red tint */ color: rgb(244, 33, 46); border-color: rgb(244, 33, 46); } /* ▼▼▼ 検索入力中のフォーカス制御 (Focus Mode) ▼▼▼ */ /* 1. モーダルを背景レベルまで下げる */ #advanced-search-modal.adv-z-lower { z-index: 0 !important; } /* 2. Xのアプリ全体をモーダルの上に持ち上げる(サジェスト救出のため) */ /* #react-root は body 直下の X アプリケーションのルート要素 */ #react-root.adv-app-lifted { z-index: 1 !important; position: relative !important; /* z-indexを効かせるために必須 */ } /* 3. サイドバー全体を「不可視」にする これにより、トレンド・おすすめユーザー・フッター・枠線などが全て消え、 背後にあるモーダルが見えるように。 (opacityではなくvisibilityを使うことで、枠線も判定も完全に消す) */ #react-root.adv-app-lifted [data-testid="sidebarColumn"] { visibility: hidden !important; } /* 4. 検索フォームとその中身だけを「可視化」して救出する (visibilityは親がhiddenでも自分をvisibleにすれば表示される) */ #react-root.adv-app-lifted [data-testid="sidebarColumn"] form[role="search"], #react-root.adv-app-lifted [data-testid="sidebarColumn"] form[role="search"] * { visibility: visible !important; opacity: 1 !important; } /* 5. サジェスト(入力候補)も同様に救出する */ #react-root.adv-app-lifted [data-testid="sidebarColumn"] [role="listbox"], #react-root.adv-app-lifted [data-testid="sidebarColumn"] [role="listbox"] * { visibility: visible !important; opacity: 1 !important; } /* モーダルが左側にあって被る場合のみ、左メニューを隠す */ #react-root.adv-app-lifted.adv-overlap-left-menu header[role="banner"] { visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; } /* === Native Search Resizer === */ form[role="search"] { position: relative !important; /* リサイザーの基準点 */ max-width: none !important; /* 幅制限の解除 */ } .adv-native-search-resizer { position: absolute; right: -8px; top: 0; bottom: 0; width: 16px; cursor: col-resize; z-index: 9999; background: transparent; touch-action: none; /* スマホでのスクロール干渉防止 */ } .adv-native-search-resizer:hover { background: rgba(29,155,240,0.15); /* ホバー時に薄く青色を表示 */ } /* ▼SP時 (幅700px以下) はレイヤー調整を無視して強制最前面にする */ @media screen and (max-width: 700px) { /* 設定モーダル等をメインモーダルと同じ最前面レイヤーに持ち上げる */ /* これによりDOM順序が後の設定モーダルが手前に表示される */ #adv-settings-modal.adv-settings-modal, .ft-modal-backdrop { z-index: 2147483647 !important; } /* モーダル本体: 画面中央に固定し、サイズを強制適用 */ #advanced-search-modal { z-index: 2147483647 !important; /* 最前面 */ /* 位置の強制固定 (JSによるstyle属性を無視させる) */ position: fixed !important; top: 50% !important; left: 50% !important; right: auto !important; bottom: auto !important; transform: translate(-50%, -50%) !important; /* サイズの強制 (画面に合わせる) */ width: 96vw !important; height: 85.5vh !important; max-width: none !important; max-height: none !important; border-radius: 16px !important; } /* ヘッダー: ドラッグできると思わせないカーソルにする */ .adv-modal-header { cursor: default !important; } /* リサイザー: 非表示にして操作不能にする */ .adv-resizer { display: none !important; } /* モーダルが開いている(bodyにクラスがある)時はトリガーを消す */ body.adv-modal-active #advanced-search-trigger { display: none !important; } } `); const modalHTML = `


`; 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. 最も確実な方法: