Facebook ログインウォールリムーバー

このスクリプトは、Facebookデスクトップサイトでのゲスト(未ログイン)ブラウジング体験を向上させることを目的としています。一般的な中断要素を削除し、ログインしていないユーザー向けの便利な機能を追加します。

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Facebook Login Wall Remover
// @name:en      Facebook Login Wall Remover
// @name:zh-TW   Facebook 登入牆移除器
// @name:ja      Facebook ログインウォールリムーバー
// @version      0.7.0
// @description  This script improves the guest browsing experience on the Facebook desktop site. It aims to remove common interruptions and add helpful features for users who are not logged in.
// @description:en This script improves the guest browsing experience on the Facebook desktop site. It aims to remove common interruptions and add helpful features for users who are not logged in.
// @description:zh-TW 這個腳本的用途是改善在 Facebook 桌面版網站上未登入狀態的瀏覽體驗。它會移除一些常見的干擾,並加入一些方便的功能。
// @description:ja このスクリプトは、Facebookデスクトップサイトでのゲスト(未ログイン)ブラウジング体験を向上させることを目的としています。一般的な中断要素を削除し、ログインしていないユーザー向けの便利な機能を追加します。
// @author       StonedKhajiit
// @match        *://*.facebook.com/*
// @exclude      *://m.facebook.com/*
// @exclude      *://mobile.facebook.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=facebook.com
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_setClipboard
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @grant        GM_openInTab
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        window.close
// @noframes
// @run-at       document-start
// @license      MIT
// @namespace https://greasyfork.org/en/users/1467948-stonedkhajiit
// ==/UserScript==

(function() {
    'use strict';

    const app = {
        // --- APPLICATION CONFIGURATION ---
        config: {
            LOG_PREFIX: `[FB Login Wall Remover]`,
            THROTTLE_DELAY: 250,
            PROCESSED_MARKER: 'gmProcessed',
            WORKER_PARAM: 'fpc_worker_task',
            SCROLL_RESTORER_CONFIG: {
                CORRECTION_DURATION: 250,
                CORRECTION_FREQUENCY: 16,
                WATCHER_FREQUENCY: 150,
                MODAL_GRACE_PERIOD: 300,
            },
            AUTO_LOADER: {
                POLL_INTERVAL: 300,
                MAX_WAIT_TIME: 3500,
                MIN_COOLDOWN: 1000,
                MAX_RETRIES: 3,
            },
            ERROR_RECOVERY: {
                RELOAD_BUTTON_LABELS: [
                    "Reload Page", "重新載入頁面", "ページを更新",
                    "Volver a cargar página", "Ricarica la pagina", "Seite neu laden",
                    "Actualiser la Page", "Обновить страницу", "Sayfayı Yenile",
                    "페이지 새로 고침", "Tải lại trang"
                ],
                MAX_RETRIES: 2,
                STORAGE_KEY: 'fblwr_retry_state',
            },
            ADS_LIB: {
                KEY_TARGET_ID: 'fblwr_ads_target_id',
                KEY_INT_ACTION: 'fblwr_int_action',
                INITIAL_DELAY: 300,
                POLL_INTERVAL: 250,
                MAX_ATTEMPTS: 20, 
            },
            TRANSPARENCY: {
                SEE_ALL_BUTTONS: [
                    'See all transparency information',
                    '查看所有透明度資訊',
                    '透明性に関する情報をすべて見る'
                ]
            },
            TEXT_EXPANDER: {
                TARGETS: [
                    "See more",
                    "查看更多",
                    "さらに表示",
                    "See More", "閱讀更多", "もっと見る"
                ],
                SCOPE_SELECTOR: 'div[role="article"]',
                BUTTON_SELECTOR: 'div[role="button"]',
                PROCESSED_ATTR: 'data-gm-expanded',
            },
            LOGIN_STATE_MARKERS: {
                LOGGED_OUT: [
                    { selector: 'form#login_form', reason: 'Primary login form element' },
                    { selector: 'input[name="pass"]', reason: 'Password input field' },
                    { selector: 'a[href*="/recover/initiate"]', reason: 'Forgot Account link' },
                    { selector: 'a[href*="/login/"]', reason: 'Login link' }
                ],
                LOGGED_IN: [
                    { selector: 'input[type="search"]', reason: 'Search input field' },
                    { selector: 'a[href="/friends/"]', reason: 'Friends tab' }
                ]
            },
            SELECTORS: {
                GLOBAL: {
                    POST_CONTAINER: 'div[role="article"]',
                    MODAL_CONTAINER: 'div.__fb-light-mode',
                    DIALOG: '[role="dialog"]',
                    LOGIN_FORM: 'form#login_form, form[id="login_popup_cta_form"]',
                    MEDIA_LINK: `a[href*="/photo"], a[href*="fbid="], a[href*="/videos/"], a[href*="/watch/"], a[href*="/reel/"]`,
                    CLOSE_BUTTON: [
                        "Close", "關閉", "閉じる", "Cerrar", "Fermer", "Schließen", "Fechar", "Chiudi", "Sluiten", "Закрыть", "Kapat", "Zamknij",
                        "Tutup", "Đóng", "ปิด", "Zatvori", "Zavrieť", "Zavřít", "Bezárás", "Stäng", "Luk", "Lukk", "Sulje", "Κλείσιμο",
                        "Închide", "إغلاق", "סגור"
                    ].map(label => `[aria-label="${label}"][role="button"]`).join(', ') + ', div[role="button"]:has(i[data-visualcompletion="css-img"])',
                },
                NAVIGATOR: {
                    HIGHLIGHT_CLASS: 'gm-post-highlight',
                },
                POST_TOOLS: {
                    BUTTON_CLASS: 'gm-tool-button',
                    PROCESSED_MARKER: 'gmToolsProcessed',
                    FEED_POST_HEADER: 'div[data-ad-rendering-role="profile_name"] h2, div[data-ad-rendering-role="profile_name"] h3',
                    DIALOG_POST_HEADER: 'h2, h3',
                    CONTENT_BODY: 'div[data-ad-rendering-role="story_message"]',
                    EXPAND_BTN: 'div[role="button"]',
                    TEXT_BLOCKS: 'div[dir="auto"]'
                }
            },
            STRINGS: {
                en: {
                    // --- General ---
                    notificationDeadlock: 'A login prompt was hidden, but the feed can no longer load new content.\nPlease reload to continue browsing.',
                    notificationSettingsReload: 'Some settings updated. Please reload.',
                    resetSettings: 'Reset Settings',
                    resetSettingsConfirm: 'Reset all settings to default?',
                    notificationSettingsReset: 'Settings reset.',
                    menuResetSettings: '🚨 Reset All Settings',
                    autoOpenMediaInNewTab: 'Auto-open media in new tab',
                    showDeadlockNotification: 'Show deadlock notification',
                    hideUselessElements: 'Hide useless UI elements',
                    hidePostStats: 'Hide post stats (Likes, Comments)',
                    autoUnmuteEnabled: 'Auto unmute videos',
                    setVolumeLabel: 'Auto-unmute volume',
                    postNumberingEnabled: 'Display post numbers',
                    expandContentEnabled: 'Auto-expand content',
                    errorRecoveryEnabled: 'Auto-reload on error',
                    transparencyButtonsEnabled: 'Show Page Transparency shortcuts',
                    
                    // --- ID Revealer ---
                    idRevealerEnabled: 'Enable ID Revealer (Click Title)',
                    idRevealerTooltip: 'Click to reveal ID & Info',
                    idRevealerLinkFormat: 'ID Link Format',
                    idFormatUserID: 'User ID URL (facebook.com/id)',
                    idFormatClassic: 'Classic Profile URL (profile.php?id=)',
                    idFormatUsername: 'Username URL (Current URL)',
                    id_copy_all: 'Copy All Info',
                    id_label_user: 'User ID',
                    id_label_page: 'Page ID',
                    id_label_meta: 'Profile ID',
                    profile_name_label: 'Name',
                    profile_url_label: 'Profile URL',
                    copy_success_generic: '{label} Copied',
                    all_copied: 'All Info Copied',

                    // --- Search Bar ---
                    searchPlaceholder: 'Search...',
                    searchButton: 'Search',
                    searchGroupContextual: 'Search Current Page',
                    searchGroupGlobal: 'Search All of Facebook',
                    searchScopePosts: 'Posts',
                    searchScopePhotos: 'Photos',
                    searchScopeVideos: 'Videos',
                    searchScopeReels: 'Reels',
                    searchScopePages: 'Pages',
                    searchScopePeople: 'People',
                    searchScopeGroups: 'Groups',
                    searchScopeGlobalVideos: 'Videos',
                    searchScopeGlobalPosts: 'Posts',
                    searchScopeEvents: 'Events',
                    searchScopeMarketplace: 'Marketplace',
                    searchTooltipPosts: 'Search current page posts',
                    searchTooltipPhotos: 'Search photos',
                    searchTooltipVideos: 'Search videos',
                    searchTooltipReels: 'Search Reels',
                    searchTooltipPages: 'Search Pages',
                    searchTooltipPeople: 'Search People',
                    searchTooltipGroups: 'Search Groups',
                    searchTooltipGlobalPosts: 'Search Public Posts',
                    searchTooltipGlobalVideos: 'Search Watch',
                    searchTooltipEvents: 'Search Events',
                    searchTooltipMarketplace: 'Search Marketplace',
                    searchAllContextualTooltip: 'Search {scope} on this page',
                    navigateToContextual: 'Go to {scope} section',
                    pinToolbar: 'Pin',
                    unpinToolbar: 'Unpin',
                    shortcutWatch: 'Watch',
                    shortcutEvents: 'Events',
                    shortcutMarketplace: 'Marketplace',

                    // --- Settings Modal ---
                    settingsTitle: 'Settings',
                    saveAndClose: 'Save & Close',
                    menuSettings: '⚙️ Settings',
                    settingsColumnGeneral: 'General',
                    settingsColumnNavigation: 'Navigation',
                    settingsColumnTools: 'Tools',

                    // --- Navigation ---
                    keyboardNavEnabled: 'Enable keyboard navigation',
                    keyNavNextPrimary: 'Next (J)',
                    keyNavPrevPrimary: 'Prev (K)',
                    keyNavNextSecondary: 'Next (Right)',
                    keyNavPrevSecondary: 'Prev (Left)',
                    floatingNavEnabled: 'Enable floating buttons',
                    floatingNavPrevTooltip: 'Previous Post',
                    floatingNavNextTooltip: 'Next Post',
                    navigationScrollAlignment: 'Alignment',
                    scrollAlignmentCenter: 'Center',
                    scrollAlignmentTop: 'Top',
                    enableSmoothScrolling: 'Smooth Scroll',
                    continuousNavInterval: 'Nav Interval',
                    wheelNavEnabled: 'Wheel Nav',
                    wheelNavModifier: 'Modifier',
                    modifierAlt: 'Alt',
                    modifierCtrl: 'Ctrl',
                    modifierShift: 'Shift',
                    modifierNone: 'None',

                    // --- Tools (Copier & AutoLoad) ---
                    copier_enablePermalink: 'Enable Permalink Icon',
                    copier_enableCopyContent: 'Enable Smart Copy Button',
                    copier_fetchPermalinkSmart: 'Permalink (Smart)',
                    copier_fetchPermalinkDirect: 'Permalink (Direct)',
                    copier_copyContent: 'Copy Post Content',
                    copier_copyContentSuccess: '✅ Content Copied',
                    copier_copyContentFailed: '❌ Copy Failed',
                    copier_processing: 'Processing...',
                    copier_successPermalink: '✅ Copied',
                    copier_failure: '❌ Failed',
                    copier_notificationPermalinkCopied: 'Permalink copied:\n{url}',
                    copier_notificationErrorGeneric: 'Failed to fetch.',
                    copier_notificationErrorNoSourceUrl: 'No source URL.',
                    copier_notificationErrorTimeout: 'Fetch timed out.',
                    copier_notificationContentNotFound: '❌ Content not found.',
                    copier_menu_useSmartLink: 'Use Smart Link (Async)',
                    copier_menu_permalinkFormat: 'Permalink Format',
                    copier_format_full: 'Full URL',
                    copier_format_username: 'Username+ID',
                    copier_format_author_id: 'AuthorID+ID',
                    copier_format_shortest: 'Shortest',
                    copier_includeEmojis: 'Include emojis',
					copier_expandHashtags: 'Expand Hashtags to URL',
					copier_expand_mentions: 'Expand Mentions to URL',
                    
                    autoLoader_batchSize: 'Batch Count',
                    tooltipAutoLoadStart: 'Auto-Load',
                    tooltipAutoLoadStop: 'Stop',
                    tooltipBatchCopy: 'Batch Copy',
                    batchCopy_includeHeader: 'Include Header in Batch Copy',
                    autoLoad_status_loading: 'Loading... ({current}/{target})',
                    autoLoad_status_retrying: 'Retrying...',
                    autoLoad_status_success: 'Done.',
                    autoLoad_status_stopped: 'Stopped.',
                    autoLoad_status_deadlock: 'Deadlock.',
                    batchCopy_start: 'Processing {count} posts...',
                    batchCopy_success: '✅ Copied {count} posts.',
                    batchCopy_empty: 'No posts.',
                    floatingNav_showAutoLoad: 'Show Auto-Load',
                    floatingNav_showBatchCopy: 'Show Batch Copy',

                    // --- Copy Metadata ---
                    copy_includeMetadata: 'Include Metadata',
                    copy_meta_url: 'Include Link (Top)',
                    copy_meta_order: 'Include Order [#] (Batch Only)',
                    copy_meta_author_name: 'Include Author',
                    copy_meta_date: 'Include Date',
                    copy_meta_stats: 'Include Stats (Master)',
                    copy_meta_stats_total: 'Include Total Count (1.9K)',
                    copy_meta_stats_detailed: 'Include Details (👍❤️)',
                    copy_meta_link_preview: 'Include Link Preview',
					copy_meta_image_count: 'Include Image Count',
					image_count_label: 'Images',
					copy_meta_video_duration: 'Include Video/Reel Duration',
					label_reel: 'Reel',
					label_video: 'Video',
                    stats_label_like: 'Like',
                    stats_label_comment: 'Comment',
                    stats_label_share: 'Share',
                    stats_label_reaction: 'Reactions',
                    preview_label_title: 'Title',
                    preview_label_source: 'Source',
                    preview_label_desc: 'Desc',
                    preview_label_link: 'Link',
                    
                    // --- Other Tooltips ---
                    tooltipAds: 'Ads Library',
                    tooltipTransparency: 'Transparency',
                    notificationReelSearchError: 'Page name not found',
                },
                'zh-TW': {
                    // --- 一般設定 ---
                    notificationDeadlock: '登入提示已隱藏,動態消息將無法載入新內容。\n【提示】為避免動態消息卡住,請養成用滑鼠中鍵在新分頁開啟連結的習慣。請重新整理頁面以繼續瀏覽。',
                    notificationSettingsReload: '部分設定已更新,請重新整理頁面以完全生效。',
                    resetSettings: '重設設定',
                    resetSettingsConfirm: '您確定要將所有設定重設為預設值嗎?此操作無法復原。',
                    notificationSettingsReset: '設定已重設為預設值,部分變更可能需要重新整理頁面才能生效。',
                    menuResetSettings: '🚨 重設所有設定',
                    autoOpenMediaInNewTab: '自動在新分頁開啟媒體 (防鎖定)',
                    showDeadlockNotification: '顯示頁面鎖定通知',
                    hideUselessElements: '隱藏對訪客無用的介面元素',
                    hidePostStats: '隱藏貼文統計數據 (讚數、留言數)',
                    autoUnmuteEnabled: '自動取消影片靜音',
                    setVolumeLabel: '自動音量大小',
                    postNumberingEnabled: '在動態消息上顯示貼文順序',
                    expandContentEnabled: '自動展開貼文內容 (查看更多)',
                    errorRecoveryEnabled: '錯誤頁面自動恢復 (按鈕偵測)',
                    transparencyButtonsEnabled: '顯示粉絲專頁資訊透明度捷徑按鈕 (左下角)',
                    
                    // --- ID Revealer ---
                    idRevealerEnabled: '啟用 ID 顯示器 (點擊標題)',
                    idRevealerTooltip: '點擊以顯示 Profile ID 與資訊',
                    idRevealerLinkFormat: 'ID 連結格式',
                    idFormatUserID: 'User ID 格式 (facebook.com/id)',
                    idFormatClassic: '經典格式 (profile.php?id=)',
                    idFormatUsername: '使用者名稱 (當前網址)',
                    id_copy_all: '複製全部資訊',
                    id_label_user: 'User ID',
                    id_label_page: 'Page ID',
                    id_label_meta: 'Profile ID',
                    profile_name_label: '專頁名稱',
                    profile_url_label: 'Profile URL',
                    copy_success_generic: '已複製 {label}',
                    all_copied: '全部資訊已複製',

                    // --- 搜尋工具列 ---
                    searchPlaceholder: '搜尋...',
                    searchButton: '搜尋',
                    searchGroupContextual: '搜尋當前頁面',
                    searchGroupGlobal: '搜尋整個 Facebook',
                    searchScopePosts: '貼文',
                    searchScopePhotos: '相片',
                    searchScopeVideos: '影片',
                    searchScopeReels: '連續短片',
                    searchScopePages: '專頁',
                    searchScopePeople: '人物',
                    searchScopeGroups: '社團',
                    searchScopeGlobalVideos: '影片',
                    searchScopeGlobalPosts: '貼文',
                    searchScopeEvents: '活動',
                    searchScopeMarketplace: '市集',
                    searchTooltipPosts: '搜尋目前頁面的貼文 (若在首頁則搜尋整個 Facebook)。',
                    searchTooltipPhotos: '搜尋目前頁面的相片。',
                    searchTooltipVideos: '搜尋目前頁面的影片。',
                    searchTooltipReels: '使用「專頁名稱」+「關鍵字」來搜尋整個 Facebook 的連續短片。',
                    searchTooltipPages: '在整個 Facebook 中搜尋粉絲專頁、公眾人物或組織。',
                    searchTooltipPeople: '在整個 Facebook 中搜尋個人檔案。',
                    searchTooltipGroups: '在整個 Facebook 中搜尋社團。',
                    searchTooltipGlobalPosts: '在整個 Facebook 中搜尋公開貼文。',
                    searchTooltipGlobalVideos: '使用 Facebook Watch 內建功能搜尋所有影片。',
                    searchTooltipEvents: '使用 Facebook 內建功能搜尋所有活動。',
                    searchTooltipMarketplace: '使用 Facebook Marketplace 內建功能搜尋所有商品。',
                    searchAllContextualTooltip: '使用 Google 搜尋此頁面的所有 {scope}',
                    navigateToContextual: '前往 {scope} 區塊',
                    pinToolbar: '釘選工具列',
                    unpinToolbar: '取消釘選工具列',
                    shortcutWatch: '前往 Watch 影片',
                    shortcutEvents: '前往 活動',
                    shortcutMarketplace: '前往 Marketplace 市集',

                    // --- 設定視窗 ---
                    settingsTitle: '設定',
                    saveAndClose: '儲存並關閉',
                    menuSettings: '⚙️ 設定',
                    settingsColumnGeneral: '一般設定',
                    settingsColumnNavigation: '導覽設定',
                    settingsColumnTools: '貼文工具',

                    // --- 導覽功能 ---
                    keyboardNavEnabled: '啟用鍵盤導覽',
                    keyNavNextPrimary: '下一篇 (主要按鍵)',
                    keyNavPrevPrimary: '上一篇 (主要按鍵)',
                    keyNavNextSecondary: '下一篇 (次要按鍵)',
                    keyNavPrevSecondary: '上一篇 (次要按鍵)',
                    floatingNavEnabled: '啟用浮動導覽按鈕',
                    floatingNavPrevTooltip: '上一篇貼文',
                    floatingNavNextTooltip: '下一篇貼文',
                    navigationScrollAlignment: '導覽滾動對齊',
                    scrollAlignmentCenter: '置中',
                    scrollAlignmentTop: '貼齊頂部',
                    enableSmoothScrolling: '啟用平滑捲動',
                    continuousNavInterval: '連續導覽間隔時間',
                    wheelNavEnabled: '啟用滑鼠滾輪導覽',
                    wheelNavModifier: '滾輪導覽修飾鍵',
                    modifierAlt: 'Alt',
                    modifierCtrl: 'Ctrl',
                    modifierShift: 'Shift',
                    modifierNone: '無 (取代頁面捲動)',

                    // --- 複製與載入工具 ---
                    copier_enablePermalink: '啟用 永久連結按鈕 (僅圖示)',
                    copier_enableCopyContent: '啟用 複製內容按鈕 (智慧)',
                    copier_fetchPermalinkSmart: '永久連結 (智慧)',
                    copier_fetchPermalinkDirect: '永久連結 (直接)',
                    copier_copyContent: '複製貼文內容',
                    copier_copyContentSuccess: '✅ 內容已複製',
                    copier_copyContentFailed: '❌ 複製失敗',
                    copier_processing: '處理中...',
                    copier_successPermalink: '✅ 已複製',
                    copier_failure: '❌ 失敗',
                    copier_notificationPermalinkCopied: '永久連結已複製:\n{url}',
                    copier_notificationErrorGeneric: '獲取永久連結失敗。',
                    copier_notificationErrorNoSourceUrl: '失敗:找不到來源網址。',
                    copier_notificationErrorTimeout: '失敗:背景處理逾時。',
                    copier_notificationContentNotFound: '❌ 找不到內容區塊。',
                    copier_menu_useSmartLink: '複製內容時使用智慧連結 (需等待)',
                    copier_menu_permalinkFormat: '永久連結格式',
                    copier_format_full: '完整網址 (含 Slug)',
                    copier_format_username: '使用者名稱 + 貼文 ID',
                    copier_format_author_id: '作者 ID + 貼文 ID (最可靠)',
                    copier_format_shortest: '最短連結 (fb.com, 相容性較差)',
                    copier_includeEmojis: '複製內容包含表情符號',
					copier_expandHashtags: '將 Hashtag 展開為網址',
					copier_expand_mentions: '將提及對象展開為網址',
                    
                    autoLoader_batchSize: '自動載入批次數量',
                    tooltipAutoLoadStart: '自動載入貼文',
                    tooltipAutoLoadStop: '停止載入',
                    tooltipBatchCopy: '批次複製所有貼文',
                    batchCopy_includeHeader: '批次複製包含頁首資訊',
                    autoLoad_status_loading: '載入中... ({current}/{target})',
                    autoLoad_status_retrying: '重試中... ({count}/{max})',
                    autoLoad_status_success: '自動載入完成。',
                    autoLoad_status_stopped: '使用者手動停止。',
                    autoLoad_status_deadlock: '偵測到阻擋,載入已停止。',
                    batchCopy_start: '正在處理 {count} 篇貼文...',
                    batchCopy_success: '✅ 已複製 {count} 篇貼文。',
                    batchCopy_empty: '沒有貼文可複製。',
                    floatingNav_showAutoLoad: '顯示 自動載入按鈕',
                    floatingNav_showBatchCopy: '顯示 批次複製按鈕',

                    // --- 複製中繼資料 ---
                    copy_includeMetadata: '複製內容包含中繼資料',
                    copy_meta_url: '包含貼文連結 (置頂)',
                    copy_meta_order: '包含貼文順序 [#xx] (僅批次複製)',
                    copy_meta_author_name: '包含發文者名稱',
                    copy_meta_date: '包含發文時間',
                    copy_meta_stats: '包含互動統計',
                    copy_meta_stats_total: '包含總數 (1.9K 心情)',
                    copy_meta_stats_detailed: '包含詳細心情 (👍❤️)',
                    copy_meta_link_preview: '包含連結預覽資訊 (標題/來源/摘要)',
					copy_meta_image_count: '包含圖片數量統計',
					image_count_label: '圖片',
					copy_meta_video_duration: '包含影片/Reels 時長',
					label_reel: 'Reel',
					label_video: '影片',
                    stats_label_like: '讚',
                    stats_label_comment: '留言',
                    stats_label_share: '分享',
                    stats_label_reaction: '心情',
                    preview_label_title: '標題',
                    preview_label_source: '來源',
                    preview_label_desc: '摘要',
                    preview_label_link: '連結',
                    
                    // --- 其他提示 ---
                    tooltipAds: '前往 廣告檔案庫 (關於)',
                    tooltipTransparency: '查看 粉絲專頁資訊透明度',
                    notificationReelSearchError: '無法取得目前頁面名稱以進行連續短片搜尋。',
                },
                ja: {
                    // --- 一般 ---
                    notificationDeadlock: 'ログインプロンプトが非表示になりましたが、フィードは新しいコンテンツを読み込めなくなりました。\n【ヒント】フィードがロックされないように、新しいタブでリンクを開く(中央クリック)習慣を付けてください。閲覧を続けるには、このページをリロードしてください。',
                    notificationSettingsReload: '一部の設定が更新されました。完全に有効にするには、ページをリロードしてください。',
                    resetSettings: '設定をリセット',
                    resetSettingsConfirm: 'すべての設定をデフォルトにリセットしますか?この操作は元に戻せません。',
                    notificationSettingsReset: '設定がデフォルトにリセットされました。一部の変更は、ページをリロードすると有効になります。',
                    menuResetSettings: '🚨 全ての設定をリセット',
                    autoOpenMediaInNewTab: 'メディアを新しいタブで開く (デッドロック防止)',
                    showDeadlockNotification: 'デッドロック通知を表示',
                    hideUselessElements: '不要なUI要素を非表示にする(ゲスト用)',
                    hidePostStats: '投稿の統計データを非表示 (いいね!、コメント数)',
                    autoUnmuteEnabled: '動画のミュートを自動解除',
                    setVolumeLabel: '自動音量',
                    postNumberingEnabled: 'フィードに投稿順序番号を表示する',
                    expandContentEnabled: '投稿の内容を自動的に展開 (さらに表示)',
                    errorRecoveryEnabled: 'エラーページ自動回復 (ボタン検出)',
                    transparencyButtonsEnabled: 'ページの透明性ショートカットを表示 (左下)',
                    
                    // --- ID Revealer ---
                    idRevealerEnabled: 'ID表示機能を有効にする(タイトルをクリック)',
                    idRevealerTooltip: 'クリックしてプロフィールIDと情報を表示',
                    idRevealerLinkFormat: 'IDリンク形式',
                    idFormatUserID: 'User ID形式 (facebook.com/id)',
                    idFormatClassic: 'クラシック (profile.php?id=)',
                    idFormatUsername: 'ユーザー名 (現在のURL)',
                    id_copy_all: 'すべての情報をコピー',
                    id_label_user: 'User ID',
                    id_label_page: 'Page ID',
                    id_label_meta: 'Profile ID',
                    profile_name_label: 'プロフィール名',
                    profile_url_label: 'Profile URL',
                    copy_success_generic: '{label}をコピーしました',
                    all_copied: 'すべての情報をコピーしました',

                    // --- 検索バー ---
                    searchPlaceholder: '検索...',
                    searchButton: '検索',
                    searchGroupContextual: '現在のページを検索',
                    searchGroupGlobal: 'Facebook全体を検索',
                    searchScopePosts: '投稿',
                    searchScopePhotos: '写真',
                    searchScopeVideos: '動画',
                    searchScopeReels: 'リール',
                    searchScopePages: 'ページ',
                    searchScopePeople: '人物',
                    searchScopeGroups: 'グループ',
                    searchScopeGlobalVideos: '動画',
                    searchScopeGlobalPosts: '投稿',
                    searchScopeEvents: 'イベント',
                    searchScopeMarketplace: 'マーケット',
                    searchTooltipPosts: '現在のページの投稿を検索',
                    searchTooltipPhotos: '写真を検索',
                    searchTooltipVideos: '動画を検索',
                    searchTooltipReels: 'リールを検索',
                    searchTooltipPages: 'ページを検索',
                    searchTooltipPeople: '人物を検索',
                    searchTooltipGroups: 'グループを検索',
                    searchTooltipGlobalPosts: '公開投稿を検索',
                    searchTooltipGlobalVideos: 'Watchを検索',
                    searchTooltipEvents: 'イベントを検索',
                    searchTooltipMarketplace: 'マーケットプレイスを検索',
                    searchAllContextualTooltip: 'このページの {scope} を検索',
                    navigateToContextual: '{scope} へ移動',
                    pinToolbar: '固定',
                    unpinToolbar: '固定解除',
                    shortcutWatch: 'Watchへ',
                    shortcutEvents: 'イベントへ',
                    shortcutMarketplace: 'マーケットへ',

                    // --- 設定モーダル ---
                    settingsTitle: '設定',
                    saveAndClose: '保存して閉じる',
                    menuSettings: '⚙️ 設定',
                    settingsColumnGeneral: '一般',
                    settingsColumnNavigation: 'ナビ',
                    settingsColumnTools: 'ツール',

                    // --- ナビゲーション ---
                    keyboardNavEnabled: 'キーボードナビゲーションを有効にする',
                    keyNavNextPrimary: '次の投稿 (J)',
                    keyNavPrevPrimary: '前の投稿 (K)',
                    keyNavNextSecondary: '次の投稿 (→)',
                    keyNavPrevSecondary: '前の投稿 (←)',
                    floatingNavEnabled: 'フローティングボタンを有効にする',
                    floatingNavPrevTooltip: '前の投稿',
                    floatingNavNextTooltip: '次の投稿',
                    navigationScrollAlignment: 'スクロール位置',
                    scrollAlignmentCenter: '中央',
                    scrollAlignmentTop: '上部',
                    enableSmoothScrolling: 'スムーズスクロール',
                    continuousNavInterval: '連続間隔',
                    wheelNavEnabled: 'ホイールナビ',
                    wheelNavModifier: '修飾キー',
                    modifierAlt: 'Alt',
                    modifierCtrl: 'Ctrl',
                    modifierShift: 'Shift',
                    modifierNone: 'なし',

                    // --- ツール (コピー & 自動読み込み) ---
                    copier_enablePermalink: '固定リンクボタンを有効にする (アイコンのみ)',
                    copier_enableCopyContent: '内容コピーボタンを有効にする (スマート)',
                    copier_fetchPermalinkSmart: '固定リンク (スマート)',
                    copier_fetchPermalinkDirect: '固定リンク (直接)',
                    copier_copyContent: '投稿内容をコピー',
                    copier_copyContentSuccess: '✅ コピーしました',
                    copier_copyContentFailed: '❌ 失敗しました',
                    copier_processing: '処理中...',
                    copier_successPermalink: '✅ コピーしました',
                    copier_failure: '❌ 失敗',
                    copier_notificationPermalinkCopied: '固定リンクをクリップボードにコピーしました:\n{url}',
                    copier_notificationErrorGeneric: '固定リンクの取得に失敗しました。',
                    copier_notificationErrorNoSourceUrl: '失敗:ソースURLが見つかりません。',
                    copier_notificationErrorTimeout: '失敗:取得タイムアウト。',
                    copier_notificationContentNotFound: '❌ コンテンツが見つかりませんでした。',
                    copier_menu_useSmartLink: 'コンテンツコピー時にスマートリンクを使用 (待機あり)',
                    copier_menu_permalinkFormat: '固定リンク形式',
                    copier_format_full: '完全なURL',
                    copier_format_username: 'ユーザー名+ID',
                    copier_format_author_id: '作者ID+ID (推奨)',
                    copier_format_shortest: '短縮 (fb.com)',
                    copier_includeEmojis: '絵文字を含める',
					copier_expandHashtags: 'ハッシュタグをURLに展開',
					copier_expand_mentions: 'メンションをURLに展開',
                    
                    autoLoader_batchSize: '自動読み込みバッチ数',
                    tooltipAutoLoadStart: '投稿を自動読み込み',
                    tooltipAutoLoadStop: '読み込み停止',
                    tooltipBatchCopy: '全投稿を一括コピー',
                    batchCopy_includeHeader: '一括コピーにヘッダーを含める',
                    autoLoad_status_loading: '読み込み中... ({current}/{target})',
                    autoLoad_status_retrying: '再試行中... ({count}/{max})',
                    autoLoad_status_success: '自動読み込み完了。',
                    autoLoad_status_stopped: 'ユーザーにより停止。',
                    autoLoad_status_deadlock: 'ブロックを検出。停止します。',
                    batchCopy_start: '{count} 件の投稿を処理中...',
                    batchCopy_success: '✅ {count} 件の投稿をコピーしました。',
                    batchCopy_empty: 'コピーする投稿がありません。',
                    floatingNav_showAutoLoad: '自動読み込みボタンを表示',
                    floatingNav_showBatchCopy: '一括コピーボタンを表示',

                    // --- メタデータコピー ---
                    copy_includeMetadata: 'メタデータを含める (作成者、日付、リンク...)',
                    copy_meta_url: '投稿リンクを含める (上部)',
                    copy_meta_order: '投稿順序 [#xx] を含める (一括コピーのみ)',
                    copy_meta_author_name: '作成者名を含める',
                    copy_meta_date: '日付/時間を含める',
                    copy_meta_stats: 'インタラクション統計を含める',
                    copy_meta_stats_total: '合計リアクション数を含める',
                    copy_meta_stats_detailed: '詳細なリアクションを含める',
                    copy_meta_link_preview: 'リンクプレビュー情報を含める',
					copy_meta_image_count: '画像数を含める',
					image_count_label: '画像',
					copy_meta_video_duration: '動画/リールの長さを含める',
					label_reel: 'リール',
					label_video: '動画',
                    stats_label_like: 'いいね',
                    stats_label_comment: 'コメント',
                    stats_label_share: 'シェア',
                    stats_label_reaction: 'リアクション',
                    preview_label_title: 'タイトル',
                    preview_label_source: 'ソース',
                    preview_label_desc: '概要',
                    preview_label_link: 'リンク',
                    
                    // --- その他 ---
                    tooltipAds: '広告ライブラリ',
                    tooltipTransparency: 'ページの透明性',
                    notificationReelSearchError: 'ページ名が見つかりません。',
                },
            },
        },

        // --- APPLICATION STATE ---
        state: {
            settings: {},
            T: {}, // Localized strings
            cachedPageID: null,
            currentPath: '',
        },

        // --- UTILITY FUNCTIONS ---
        utils: {
            isLoggedIn() {
                const banner = document.querySelector('div[role="banner"]');
                if (!banner) {
                    console.warn(`${app.config.LOG_PREFIX} [Auth] Could not find banner element, assuming logged out.`);
                    return false;
                }
                if (app.config.LOGIN_STATE_MARKERS.LOGGED_OUT.some(marker => banner.querySelector(marker.selector))) {
                    return false;
                }
                if (app.config.LOGIN_STATE_MARKERS.LOGGED_IN.some(marker => banner.querySelector(marker.selector))) {
                    return true;
                }
                console.warn(`${app.config.LOG_PREFIX} [Auth] No definitive login markers found, defaulting to logged out.`);
                return false;
            },
            throttle(func, delay) {
                let timeoutId = null;
                return (...args) => {
                    if (timeoutId === null) {
                        timeoutId = setTimeout(() => {
                            func.apply(this, args);
                            timeoutId = null;
                        }, delay);
                    }
                };
            },
            createStyledElement(tag, styles = {}, properties = {}) {
                const element = document.createElement(tag);
                Object.assign(element.style, styles);
                const { on, children, ...rest } = properties;
                Object.assign(element, rest);
                if (on) {
                    for (const [eventName, handler] of Object.entries(on)) {
                        element.addEventListener(eventName, handler);
                    }
                }
                if (children) {
                    const childList = Array.isArray(children) ? children : [children];
                    element.append(...childList.filter(Boolean));
                }
                return element;
            },
            isFeedPage() {
                const { pathname } = window.location;
                if (/\/posts\//.test(pathname)) return false; 
                if (/\/videos\//.test(pathname) && pathname.split('/').length > 2) return false; 
                if (/\/photos?\//.test(pathname)) return false; 
                if (pathname.includes('/permalink.php')) return false;
                if (pathname.includes('/story.php')) return false;
                if (pathname.includes('/media/set')) return false;

                const pathSegments = pathname.split('/').filter(Boolean);

                if (pathSegments.length === 0) return true; 
                if (pathSegments[0] === 'groups' && pathSegments.length >= 1) return true; 

                const disallowedFirstSegments = [
                    'watch', 'marketplace', 'gaming', 'events', 'messages',
                    'notifications', 'friends', 'photo', 'videos', 'reel',
                    'posts', 'stories', 'settings', 'saved'
                ];

                return !disallowedFirstSegments.includes(pathSegments[0]);
            },
            delay: ms => new Promise(res => setTimeout(res, ms)),
            smartOpen(event, url) {
                event.preventDefault();
                const isBackground = event.button === 1 || event.ctrlKey || event.metaKey;
                if (isBackground) {
                    GM_openInTab(url, { active: false, insert: true });
                } else {
                    window.open(url, '_blank');
                }
            },
            // Check if element is visible (for deadlock detection)
            isVisible(el) {
                if (!el) return false;
                return el.offsetParent !== null && window.getComputedStyle(el).display !== 'none';
            }
        },

        // --- CORE MODULES ---
        modules: {
            settingsManager: {
                app: null,
                registeredCommands: [],
                definitions: (() => {
                    const generalSettings = [
                        { key: 'autoOpenMediaInNewTab', type: 'boolean', defaultValue: true, labelKey: 'autoOpenMediaInNewTab', group: 'general' },
                        { key: 'hideUselessElements', type: 'boolean', defaultValue: true, labelKey: 'hideUselessElements', group: 'general', needsReload: true },
                        { key: 'hidePostStats', type: 'boolean', defaultValue: false, labelKey: 'hidePostStats', group: 'general', instant: true },
                        { key: 'showDeadlockNotification', type: 'boolean', defaultValue: true, labelKey: 'showDeadlockNotification', group: 'general' },
                        { key: 'errorRecoveryEnabled', type: 'boolean', defaultValue: true, labelKey: 'errorRecoveryEnabled', group: 'general' },
                        { key: 'transparencyButtonsEnabled', type: 'boolean', defaultValue: true, labelKey: 'transparencyButtonsEnabled', group: 'general' },
                        { key: 'idRevealerEnabled', type: 'boolean', defaultValue: true, labelKey: 'idRevealerEnabled', group: 'general' },
                        {
                            key: 'idRevealerLinkFormat', type: 'select', defaultValue: 'userid', labelKey: 'idRevealerLinkFormat',
                            options: [
                                { value: 'userid', labelKey: 'idFormatUserID' }, { value: 'classic', labelKey: 'idFormatClassic' }, { value: 'username', labelKey: 'idFormatUsername' }
                            ],
                            group: 'general'
                        },
                        { key: 'autoUnmuteEnabled', type: 'boolean', defaultValue: true, labelKey: 'autoUnmuteEnabled', group: 'general' },
                        { key: 'autoUnmuteVolume', type: 'range', defaultValue: 25, labelKey: 'setVolumeLabel', options: { min: 0, max: 100, step: 5, unit: '%' }, group: 'general' },
                        { key: 'postNumberingEnabled', type: 'boolean', defaultValue: true, labelKey: 'postNumberingEnabled', group: 'general' },
                        { key: 'expandContentEnabled', type: 'boolean', defaultValue: true, labelKey: 'expandContentEnabled', group: 'general' },
                    ];
                    const navigationSettings = [
                        { key: 'keyboardNavEnabled', type: 'boolean', defaultValue: true, labelKey: 'keyboardNavEnabled', group: 'navigation' },
                        { key: 'keyNavNextPrimary', type: 'text', defaultValue: 'j', labelKey: 'keyNavNextPrimary', group: 'navigation' },
                        { key: 'keyNavPrevPrimary', type: 'text', defaultValue: 'k', labelKey: 'keyNavPrevPrimary', group: 'navigation' },
                        { key: 'keyNavNextSecondary', type: 'text', defaultValue: 'ArrowRight', labelKey: 'keyNavNextSecondary', group: 'navigation' },
                        { key: 'keyNavPrevSecondary', type: 'text', defaultValue: 'ArrowLeft', labelKey: 'keyNavPrevSecondary', group: 'navigation' },
                        { key: 'floatingNavEnabled', type: 'boolean', defaultValue: true, labelKey: 'floatingNavEnabled', group: 'navigation', instant: true },
                        { key: 'floatingNav_showAutoLoad', type: 'boolean', defaultValue: true, labelKey: 'floatingNav_showAutoLoad', group: 'navigation', instant: true },
                        { key: 'floatingNav_showBatchCopy', type: 'boolean', defaultValue: true, labelKey: 'floatingNav_showBatchCopy', group: 'navigation', instant: true },
                        { key: 'autoLoadBatchSize', type: 'range', defaultValue: 20, labelKey: 'autoLoader_batchSize', options: { min: 10, max: 100, step: 5, unit: '' }, group: 'navigation' },
                        { key: 'wheelNavEnabled', type: 'boolean', defaultValue: true, labelKey: 'wheelNavEnabled', group: 'navigation' },
                        { key: 'wheelNavModifier', type: 'select', defaultValue: 'shiftKey', labelKey: 'wheelNavModifier', options: [ { value: 'altKey', labelKey: 'modifierAlt' }, { value: 'ctrlKey', labelKey: 'modifierCtrl' }, { value: 'shiftKey', labelKey: 'modifierShift' }, { value: 'none', labelKey: 'modifierNone' } ], group: 'navigation' },
                        { key: 'navigationScrollAlignment', type: 'select', defaultValue: 'top', labelKey: 'navigationScrollAlignment', options: [ { value: 'center', labelKey: 'scrollAlignmentCenter' }, { value: 'top', labelKey: 'scrollAlignmentTop' } ], group: 'navigation' },
                        { key: 'enableSmoothScrolling', type: 'boolean', defaultValue: true, labelKey: 'enableSmoothScrolling', group: 'navigation' },
                        { key: 'continuousNavInterval', type: 'range', defaultValue: 500, labelKey: 'continuousNavInterval', options: { min: 0, max: 1000, step: 50, unit: 'ms' }, group: 'navigation' },
                    ];
                    const toolsSettings = [
                        { key: 'permalinkCopierEnabled', type: 'boolean', defaultValue: true, labelKey: 'copier_enablePermalink', group: 'tools', instant: true },
                        { key: 'enableCopyContentButton', type: 'boolean', defaultValue: true, labelKey: 'copier_enableCopyContent', group: 'tools', instant: true },
                        { key: 'copier_includeEmojis', type: 'boolean', defaultValue: true, labelKey: 'copier_includeEmojis', group: 'tools', instant: true },
                        { key: 'copier_useSmartLink', type: 'boolean', defaultValue: true, labelKey: 'copier_menu_useSmartLink', group: 'tools', instant: true },
                        { key: 'batchCopy_includeHeader', type: 'boolean', defaultValue: true, labelKey: 'batchCopy_includeHeader', group: 'tools' },
                        { key: 'copy_includeMetadata', type: 'boolean', defaultValue: true, labelKey: 'copy_includeMetadata', group: 'tools', instant: true },
                        { key: 'copy_meta_url', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_url', group: 'tools' },
                        { key: 'copy_meta_order', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_order', group: 'tools' },
                        { key: 'copy_meta_author_name', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_author_name', group: 'tools' },
                        { key: 'copy_meta_date', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_date', group: 'tools' },
                        { key: 'copy_meta_stats', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_stats', group: 'tools' },
                        { key: 'copy_meta_stats_total', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_stats_total', group: 'tools' },
                        { key: 'copy_meta_stats_detailed', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_stats_detailed', group: 'tools' },
                        { key: 'copy_meta_link_preview', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_link_preview', group: 'tools' },
                        {
                            key: 'copier_permalinkFormat', type: 'select', defaultValue: 'author_id', labelKey: 'copier_menu_permalinkFormat',
                            options: [
                                { value: 'author_id', labelKey: 'copier_format_author_id' }, { value: 'username', labelKey: 'copier_format_username' },
                                { value: 'full', labelKey: 'copier_format_full' }, { value: 'shortest', labelKey: 'copier_format_shortest' },
                            ],
                            group: 'tools'
                        },
						{ key: 'copy_meta_image_count', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_image_count', group: 'tools' },
						{ key: 'copier_expandHashtags', type: 'boolean', defaultValue: false, labelKey: 'copier_expandHashtags', group: 'tools' },
						{ key: 'copy_meta_video_duration', type: 'boolean', defaultValue: true, labelKey: 'copy_meta_video_duration', group: 'tools' },
						{ key: 'copier_expandMentions', type: 'boolean', defaultValue: true, labelKey: 'copier_expand_mentions', group: 'tools' },
                    ];
                    return [...generalSettings, ...navigationSettings, ...toolsSettings];
                })(),
                init(app) {
                    this.app = app;
                    this.definitions.forEach(def => {
                        this.app.state.settings[def.key] = GM_getValue(def.key, def.defaultValue);
                    });
                    this.renderMenu();
                },
                renderMenu() {
                    if (this.registeredCommands && this.registeredCommands.length > 0) {
                        this.registeredCommands.forEach(cmdId => GM_unregisterMenuCommand(cmdId));
                    }
                    this.registeredCommands = []; 
                    const T = this.app.state.T;
                    const settingsCommand = () => this.app.modules.settingsModal.open();
                    this.registeredCommands.push(GM_registerMenuCommand(T.menuSettings, settingsCommand));
                    const resetCommand = () => {
                        if (window.confirm(T.resetSettingsConfirm)) {
                            this.resetAllSettings();
                            this.app.modules.toastNotifier.show(T.notificationSettingsReset, 'success');
                        }
                    };
                    this.registeredCommands.push(GM_registerMenuCommand(T.menuResetSettings, resetCommand));
                },
                updateSetting(key, value) {
                    GM_setValue(key, value);
                    this.app.state.settings[key] = value;
                },
                handleSettingChange(key, newValue, oldValue) {
                    const PHT = this.app.modules.postHeaderTools;
                    const FN = this.app.modules.floatingNavigator;
                    const SI = this.app.modules.styleInjector;
                    const ER = this.app.modules.errorRecovery;
                    const TA = this.app.modules.transparencyActions;
                    const CE = this.app.modules.contentExpander;
                    const IR = this.app.modules.idRevealer;
                    switch (key) {
                        case 'permalinkCopierEnabled':
                        case 'enableCopyContentButton':
                        case 'copier_useSmartLink':
                        case 'copier_includeEmojis':
                        case 'copy_includeMetadata':
                            // Always re-evaluate buttons if any tool setting changes
                            if (PHT) PHT.reEvaluateAllButtons();
                            break;
                        case 'floatingNavEnabled':
                            if (newValue) FN.init(this.app); else FN.deinit();
                            break;
                        case 'floatingNav_showAutoLoad':
                        case 'floatingNav_showBatchCopy':
                            if (FN && this.app.state.settings.floatingNavEnabled) {
                                FN.deinit();
                                FN.init(this.app);
                            }
                            break;
                        case 'hidePostStats':
                            SI.updateStatsBarVisibility(newValue);
                            break;
                        case 'errorRecoveryEnabled':
                            if (newValue) ER.init(this.app);
                            break;
                        case 'transparencyButtonsEnabled':
                            if (newValue) TA.init(this.app); else TA.deinit();
                            break;
                        case 'idRevealerEnabled':
                            if (newValue) IR.init(this.app);
                            break;
                        case 'expandContentEnabled':
                            if (newValue) CE.init(this.app);
                            break;
                    }
                },
                resetAllSettings() {
                    this.definitions.forEach(def => {
                        const oldValue = this.app.state.settings[def.key];
                        const newValue = def.defaultValue;
                        if (oldValue !== newValue) {
                            this.updateSetting(def.key, newValue);
                            if (def.instant) this.handleSettingChange(def.key, newValue, oldValue);
                        }
                    });
                },
            },

            settingsModal: {
                app: null,
                modalContainer: null,
                init(app) { this.app = app; },
                open() {
                    if (!this.modalContainer) this._createModal();
                    const T = this.app.state.T;
                    this.modalContainer.body.innerHTML = ''; 
                    const layout = this.app.utils.createStyledElement('div', { display: 'flex', gap: '24px', flexWrap: 'wrap' }, {
                        children: [
                            this._createSettingsColumn(T.settingsColumnGeneral, 'general'),
                            this._createSettingsColumn(T.settingsColumnNavigation, 'navigation'),
                            this._createSettingsColumn(T.settingsColumnTools, 'tools')
                        ]
                    });
                    this.modalContainer.body.append(layout);
                    this._setupDependentControls(this.modalContainer.body);
                    this.modalContainer.backdrop.style.display = 'block';
                    this.modalContainer.modal.style.display = 'flex';
                },
                _createSettingsColumn(title, group) {
                    const children = [
                        this.app.utils.createStyledElement('h4', {}, { className: 'gm-col-header', textContent: title }),
                        ...this.app.modules.settingsManager.definitions.filter(def => def.group === group).map(def => this._createSettingRow(def))
                    ];
                    return this.app.utils.createStyledElement('div', {}, { className: 'gm-col', children });
                },
                _createSettingRow(def) {
                    const U = this.app.utils;
                    const T = this.app.state.T;
                    const SM = this.app.modules.settingsManager;
                    const currentValue = this.app.state.settings[def.key];
                    let inputElement, valueSpan;

                    const handleInput = (e) => {
                        const val = def.type === 'boolean' ? e.target.checked : (def.type === 'range' ? parseInt(e.target.value, 10) : e.target.value);
                        if (valueSpan) valueSpan.textContent = `${e.target.value}${def.options.unit || ''}`;
                        SM.updateSetting(def.key, val);
                        if (def.instant) SM.handleSettingChange(def.key, val, this.app.state.settings[def.key]);
                    };

                    switch (def.type) {
                        case 'boolean':
                            inputElement = U.createStyledElement('input', {}, { className: 'gm-input-right', type: 'checkbox', id: `setting-${def.key}`, checked: currentValue, on: { input: handleInput } });
                            break;
                        case 'range':
                            valueSpan = U.createStyledElement('span', { marginLeft: '12px', minWidth: '45px', textAlign: 'right', fontSize: '13px' }, { textContent: `${currentValue}${def.options.unit || ''}` });
                            inputElement = U.createStyledElement('div', { display: 'flex', alignItems: 'center', flexGrow: '1' }, {
                                children: [
                                    U.createStyledElement('input', { flexGrow: '1' }, { type: 'range', id: `setting-${def.key}`, min: def.options.min, max: def.options.max, step: def.options.step, value: currentValue, on: { input: handleInput } }),
                                    valueSpan
                                ]
                            });
                            break;
                        case 'text':
                            inputElement = U.createStyledElement('input', {}, { className: 'gm-input-text', type: 'text', id: `setting-${def.key}`, value: currentValue, on: { input: handleInput } });
                            break;
                        case 'select':
                            inputElement = U.createStyledElement('select', {}, { 
                                className: 'gm-input-select', id: `setting-${def.key}`, value: currentValue, on: { input: handleInput },
                                children: def.options.map(opt => U.createStyledElement('option', {}, { value: opt.value, textContent: T[opt.labelKey] }))
                            });
                            inputElement.value = currentValue; 
                            break;
                    }

                    const wrapper = U.createStyledElement('div', {}, { 
                        className: 'gm-setting-row', 
                        children: [
                            U.createStyledElement('label', {}, { className: 'gm-setting-label', htmlFor: `setting-${def.key}`, textContent: T[def.labelKey] }),
                            inputElement
                        ]
                    });

                    if (def.key === 'autoUnmuteVolume') wrapper.dataset.controls = 'autoUnmuteEnabled';
                    if (def.key.startsWith('keyNav')) wrapper.dataset.controls = 'keyboardNavEnabled';
                    if (def.key === 'copier_permalinkFormat') wrapper.dataset.controls = 'permalinkCopierEnabled';
                    if (def.key === 'idRevealerLinkFormat') wrapper.dataset.controls = 'idRevealerEnabled';
                    if (def.key === 'autoLoadBatchSize') wrapper.dataset.controls = 'floatingNavEnabled';
                    
                    if (['copy_meta_url', 'copy_meta_order', 'copy_meta_author_name', 'copy_meta_date', 'copy_meta_stats', 'copy_meta_link_preview'].includes(def.key)) {
                        wrapper.dataset.controls = 'copy_includeMetadata';
                        wrapper.style.paddingLeft = '15px'; // Indent
                        wrapper.style.fontSize = '0.9em';
                    }
                    if (['copy_meta_stats_total', 'copy_meta_stats_detailed'].includes(def.key)) {
                        wrapper.dataset.controls = 'copy_meta_stats';
                        wrapper.style.paddingLeft = '30px'; // Double Indent
                        wrapper.style.fontSize = '0.85em';
                    }
                    if (['floatingNav_showAutoLoad', 'floatingNav_showBatchCopy'].includes(def.key)) {
                        wrapper.dataset.controls = 'floatingNavEnabled';
                        wrapper.style.paddingLeft = '15px';
                    }

                    return wrapper;
                },
                _setupDependentControls(container) {
                    const controllers = {
                        autoUnmuteEnabled: container.querySelector('#setting-autoUnmuteEnabled'),
                        keyboardNavEnabled: container.querySelector('#setting-keyboardNavEnabled'),
                        permalinkCopierEnabled: container.querySelector('#setting-permalinkCopierEnabled'),
                        idRevealerEnabled: container.querySelector('#setting-idRevealerEnabled'),
                        floatingNavEnabled: container.querySelector('#setting-floatingNavEnabled'),
                        copy_includeMetadata: container.querySelector('#setting-copy_includeMetadata'),
                        copy_meta_stats: container.querySelector('#setting-copy_meta_stats'),
                    };
                    const toggleGroup = (controller, isEnabled) => {
                        container.querySelectorAll(`[data-controls="${controller.id.substring(8)}"]`).forEach(control => {
                            // Don't hide completely to preserve layout, just disable
                            control.style.opacity = isEnabled ? '1' : '0.5';
                            const input = control.querySelector('input, select');
                            if (input) {
                                input.disabled = !isEnabled;
                                // Recursively handle nested dependencies (e.g. stats children)
                                const nestedController = controllers[input.id.substring(8)];
                                if (nestedController && !isEnabled) {
                                    toggleGroup(nestedController, false);
                                } else if (nestedController && isEnabled) {
                                    toggleGroup(nestedController, input.checked);
                                }
                            }
                        });
                    };
                    const updateAll = () => Object.values(controllers).forEach(c => c && toggleGroup(c, c.checked));
                    Object.values(controllers).forEach(c => c && c.addEventListener('input', updateAll));
                    updateAll();
                },
                _createModal() {
                    const T = this.app.state.T;
                    const U = this.app.utils;
                    
                    const close = () => this.close();
                    const save = () => this.saveAndClose();
                    const reset = () => {
                        if (window.confirm(T.resetSettingsConfirm)) {
                            this.app.modules.settingsManager.resetAllSettings();
                            this.app.modules.toastNotifier.show(T.notificationSettingsReset, 'success');
                            this.open();
                        }
                    };

                    const header = U.createStyledElement('div', {}, { className: 'gm-settings-header', children: [
                        U.createStyledElement('h2', {}, { className: 'gm-settings-title', textContent: T.settingsTitle }),
                        U.createStyledElement('button', {}, { className: 'gm-settings-close-btn', innerHTML: '×', on: { click: close } })
                    ]});

                    const body = U.createStyledElement('div', {}, { className: 'gm-settings-body' });

                    const footer = U.createStyledElement('div', {}, { className: 'gm-settings-footer', children: [
                        U.createStyledElement('button', {}, { className: 'gm-btn-reset', textContent: T.resetSettings, on: { click: reset } }),
                        U.createStyledElement('div', { display: 'flex', gap: '8px' }, { children: [
                            U.createStyledElement('button', {}, { className: 'gm-btn-save', textContent: T.saveAndClose, on: { click: save } })
                        ]})
                    ]});

                    const modal = U.createStyledElement('div', {}, { className: 'gm-settings-modal', children: [header, body, footer] });
                    const backdrop = U.createStyledElement('div', {}, { className: 'gm-settings-backdrop', on: { click: close } });

                    document.body.append(backdrop, modal);
                    this.modalContainer = { backdrop, modal, body };
                    this.close();
                },
                close() {
                    if (this.modalContainer) {
                        this.modalContainer.backdrop.style.display = 'none';
                        this.modalContainer.modal.style.display = 'none';
                    }
                },
                saveAndClose() {
                    const needsReload = this.app.modules.settingsManager.definitions.some(def => def.needsReload && this.app.state.settings[def.key] !== GM_getValue(def.key));
                    if (needsReload) this.app.modules.toastNotifier.show(this.app.state.T.notificationSettingsReload, 'success');
                    this.close();
                }
            },

            interceptor: {
                init() {
                    const originalAddEventListener = EventTarget.prototype.addEventListener;
                    Object.defineProperty(EventTarget.prototype, 'addEventListener', {
                        configurable: true,
                        enumerable: true,
                        get: () => function(type, listener, options) {
                            if (type === 'scroll' && this !== window) return;
                            return originalAddEventListener.call(this, type, listener, options);
                        },
                    });
                },
            },

            historyInterceptor: {
                init() {
                    const originalPushState = history.pushState;
                    history.pushState = function(...args) {
                        originalPushState.apply(this, args);
                        window.dispatchEvent(new Event('historyChange'));
                    };
                    const originalReplaceState = history.replaceState;
                    history.replaceState = function(...args) {
                        originalReplaceState.apply(this, args);
                        window.dispatchEvent(new Event('historyChange'));
                    };
                }
            },

            toastNotifier: {
                app: null,
                init(app) { this.app = app; },
                show(message, type = 'success', duration = 4000) {
                    const toast = this.app.utils.createStyledElement('div', {}, {
                        className: `gm-toast gm-toast-${type}`,
                        innerHTML: `<span>${message}</span>`
                    });
                    document.body.append(toast);
                    setTimeout(() => toast.classList.add('gm-toast-visible'), 10);
                    setTimeout(() => {
                        toast.classList.remove('gm-toast-visible');
                        setTimeout(() => toast.remove(), 500);
                    }, duration);
                }
            },

            styleInjector: {
                app: null,
                statsStyleElement: null,
                init(app) {
                    this.app = app;
                    const C = this.app.config;
                    const settings = this.app.state.settings;

                    const CSS_RULES = [
                        // --- Base Hide Rules ---
                        `div[data-nosnippet], div[role="banner"]:has(a[href*="/reg/"]) { display: none !important; }`,
                        // --- Post Highlights ---
                        `.${C.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS} { outline: 3px solid #1877F2 !important; box-shadow: 0 0 15px rgba(24, 119, 242, 0.5) !important; border-radius: 8px; z-index: 10 !important; }`,
                        // --- Floating Nav ---
                        `.gm-floating-nav { position: fixed; bottom: 20px; right: 20px; z-index: 9990; display: flex; flex-direction: column; gap: 8px; }`,
                        `.gm-floating-nav button { background-color: rgba(255, 255, 255, 0.95); border: 1px solid #ddd; border-radius: 50%; width: 40px; height: 40px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 5px rgba(0,0,0,0.15); transition: background-color 0.2s, transform 0.1s; position: relative; }`,
                        `.gm-floating-nav button:hover { background-color: #f0f2f5; }`,
                        `.gm-floating-nav button:active { transform: scale(0.95); }`,
                        `.gm-floating-nav svg { width: 20px; height: 20px; fill: #65676b; }`,
                        // --- Toolbar & Search ---
                        `.gm-toolbar { position: fixed; top: 0; left: 0; width: 100%; padding: 4px 16px; background-color: #FFFFFF; z-index: 9998; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 2px 5px rgba(0,0,0,0.2); box-sizing: border-box; transition: transform 0.3s ease-in-out; gap: 16px; }`,
                        `.gm-button-group { display: flex; align-items: center; }`,
                        `.gm-button-group button { background-color: #F0F2F5; border: 1px solid #CED0D4; padding: 0; height: 32px; width: 32px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; }`,
                        `.gm-button-group button:hover { background-color: #E4E6EB; }`,
                        `.gm-button-group button:not(:first-child) { margin-left: -1px; }`,
                        `.gm-button-group button:first-child { border-radius: 6px 0 0 6px; }`,
                        `.gm-button-group button:last-child { border-radius: 0 6px 6px 0; }`,
                        `.gm-button-group button svg { width: 20px; height: 20px; fill: #65676B; pointer-events: none; }`,
                        `.gm-button-group button.gm-pinned { background-color: #E7F3FF; }`,
                        `.gm-search-core-wrapper { flex-grow: 1; display: flex; justify-content: center; }`,
                        `.gm-search-component-wrapper { display: flex; align-items: center; border: 1px solid #CED0D4; border-radius: 18px; background-color: #F0F2F5; padding: 0; transition: border-color 0.2s, box-shadow 0.2s, background-color 0.2s; height: 36px; flex-grow: 1; max-width: 600px; }`,
                        `.gm-search-component-wrapper.gm-focused { border-color: #1877F2; box-shadow: 0 0 0 2px rgba(24, 119, 242, 0.2); background-color: #FFFFFF; }`,
                        `.gm-search-component-wrapper > select, .gm-search-input-container > input { background: transparent; border: none; outline: none; height: 100%; padding-top: 0; padding-bottom: 0; }`,
                        `.gm-search-component-wrapper > select { padding-left: 12px; padding-right: 8px; margin-right: 8px; border-right: 1px solid #CED0D4; color: #65676B; font-weight: 500; }`,
                        `.gm-search-input-container { position: relative; display: flex; align-items: center; flex-grow: 1; height: 100%; }`,
                        `.gm-search-input-container > input { padding-right: 80px; width: 100%; }`,
                        `.gm-search-clear-button { position: absolute; right: 40px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #65676B; font-size: 14px; width: 20px; height: 20px; display: none; align-items: center; justify-content: center; background: none; border: none; padding: 0; }`,
                        `.gm-search-button-integrated { position: absolute; right: 0; height: 100%; width: 40px; background: transparent; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; border-radius: 0 18px 18px 0; }`,
                        `.gm-search-button-integrated svg { width: 18px; height: 18px; fill: #1877F2; }`,
                        `.gm-hover-hint { position: fixed; top: 0; left: 50%; width: 40px; height: 4px; background-color: rgba(0, 0, 0, 0.25); border-radius: 2px; z-index: 10000; opacity: 0; transform: translate(-50%, -12px); transition: opacity 0.2s ease-in-out, transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); pointer-events: none; }`,
                        `.gm-hover-hint.gm-visible { opacity: 1; transform: translate(-50%, 8px); }`,
                        `.gm-hover-trigger { position: fixed; top: 0; left: 0; width: 100%; height: 10px; z-index: 9999; }`,
                        // --- Toasts ---
                        `.gm-toast { position: fixed; top: 20px; left: 50%; transform: translate(-50%, -100px); padding: 12px 20px; border-radius: 8px; color: white; font-size: 14px; font-weight: bold; z-index: 99999; opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; box-shadow: 0 4px 10px rgba(0,0,0,0.2); backdrop-filter: blur(5px); white-space: pre-wrap; }`,
                        `.gm-toast-visible { opacity: 1; transform: translate(-50%, 0); }`,
                        `.gm-toast-success { background-color: rgba(24, 119, 242, 0.85); }`,
                        `.gm-toast-failure { background-color: rgba(220, 53, 69, 0.85); }`,
                        `.gm-toast a { color: white; text-decoration: underline; font-weight: 600; transition: opacity 0.2s; }`,
                        `.gm-toast a:hover { opacity: 0.8; }`,
                        // --- Post Tools (Permalink & Copy) ---
                        `.${C.SELECTORS.POST_TOOLS.BUTTON_CLASS} { --positive-background: #E7F3FF; --negative-background: #FDEDEE; --hover-overlay: rgba(0, 0, 0, 0.05); --secondary-text: #65676B; --media-inner-border: #CED0D4; }`,
                        // --- Tools Icons & Animation ---
                        `@keyframes gm-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`,
                        `.gm-spin { animation: gm-spin 0.8s linear infinite; transform-origin: center; }`,
                        `.gm-icon-wrapper { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; }`,
                        `.gm-icon-wrapper svg { display: block; }`,
                        // --- Transparency Buttons ---
                        `.gm-transparency-container { position: fixed; bottom: 20px; left: 20px; z-index: 9990; display: none; flex-direction: column; gap: 10px; }`,
                        `.gm-transparency-btn { width: 40px; height: 40px; border-radius: 50%; background-color: white; border: 1px solid #ddd; box-shadow: 0 2px 5px rgba(0,0,0,0.15); cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; transition: transform 0.1s; }`,
                        `.gm-transparency-btn:hover { background-color: #f0f2f5; }`,
                        `.gm-transparency-btn:active { transform: scale(0.95); }`,
                        // --- Post Numbering ---
                        `.gm-post-number { position: absolute; top: -10px; left: -10px; background-color: #e4e6eb; color: #65676b; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: bold; z-index: 1; }`,
                        // --- Settings Modal ---
                        `.gm-settings-backdrop { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 99998; }`,
                        `.gm-settings-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 99999; min-width: 600px; max-width: 90vw; display: flex; flex-direction: column; max-height: 85vh; }`,
                        `.gm-settings-header { display: flex; justify-content: space-between; align-items: center; padding: 16px; border-bottom: 1px solid #ddd; }`,
                        `.gm-settings-title { margin: 0; font-size: 18px; }`,
                        `.gm-settings-close-btn { background: none; border: none; font-size: 24px; cursor: pointer; padding: 0 8px; }`,
                        `.gm-settings-body { padding: 16px; overflow-y: auto; flex: 1; }`,
                        `.gm-settings-footer { display: flex; justify-content: space-between; align-items: center; padding: 16px; border-top: 1px solid #ddd; }`,
                        `.gm-col { flex: 1; min-width: 250px; }`,
                        `.gm-col-header { margin-top: 0; border-bottom: 1px solid #eee; padding-bottom: 8px; }`,
                        `.gm-setting-row { display: flex; align-items: center; margin-bottom: 12px; transition: opacity 0.2s; }`,
                        `.gm-setting-label { margin-right: 8px; flex-shrink: 0; }`,
                        `.gm-input-right { margin-left: auto; }`,
                        `.gm-input-text { margin-left: auto; border: 1px solid #ccc; border-radius: 4px; padding: 4px 8px; width: 100px; }`,
                        `.gm-input-select { margin-left: auto; border: 1px solid #ccc; border-radius: 4px; padding: 4px 8px; }`,
                        `.gm-btn-save { padding: 8px 16px; border: 1px solid #1877F2; background-color: #1877F2; color: white; border-radius: 6px; cursor: pointer; }`,
                        `.gm-btn-reset { padding: 8px 12px; border: 1px solid #d9534f; background-color: transparent; color: #d9534f; border-radius: 6px; cursor: pointer; transition: background-color 0.2s, color 0.2s; }`,
                        `.gm-btn-reset:hover { background-color: #d9534f; color: white; }`
                    ];

                    if (settings.hideUselessElements) {
                        const toolbarRuleA = `div:has(> div > div > div[role="button"]:not([aria-haspopup]) > div > div > i[data-visualcompletion="css-img"])`;
                        const toolbarRuleB = `div:has(> div > div[role="button"]:not([aria-haspopup]) > div > div > i[data-visualcompletion="css-img"])`;
                        const threeDotMenuSelector = `div[role="button"][aria-haspopup="menu"]:has(svg circle + circle + circle)`;
                        const stickyBannerSelector = 'div[style*="top:"][style*="z-index"]:has(div[role="tablist"])';
                        CSS_RULES.push(
                            `div[role="banner"]:has(${C.SELECTORS.GLOBAL.LOGIN_FORM}) { display: none !important; }`,
                            `${toolbarRuleA}, ${toolbarRuleB} { display: none !important; }`,
                            `${threeDotMenuSelector} { display: none !important; }`,
                            `${stickyBannerSelector} { position: static !important; }`
                        );
                    }

                    const styleElement = this.app.utils.createStyledElement('style', {}, { textContent: CSS_RULES.join('\n') });
                    document.head.append(styleElement);
                    this.updateStatsBarVisibility(settings.hidePostStats);
                },
                updateStatsBarVisibility(shouldHide) {
                    if (shouldHide) {
                        if (!this.statsStyleElement) {
                            const css = `div:has(> div > span[role="toolbar"]) { display: none !important; }`;
                            this.statsStyleElement = this.app.utils.createStyledElement('style', {}, { textContent: css });
                            document.head.append(this.statsStyleElement);
                        }
                    } else {
                        if (this.statsStyleElement) {
                            this.statsStyleElement.remove();
                            this.statsStyleElement = null;
                        }
                    }
                }
            },

            domCleaner: {
                app: null,
                init(app) {
                    this.app = app;
                    const C = this.app.config;
                    const T = this.app.state.T;
                    const FINGERPRINTS = [
                        { selector: `${C.SELECTORS.GLOBAL.DIALOG}:has([href*="/policies/cookies/"]) [role="button"][tabindex="0"]`, action: 'click' },
                        { selector: `${C.SELECTORS.GLOBAL.DIALOG}:has(${C.SELECTORS.GLOBAL.LOGIN_FORM})`, action: 'handle_login_modal' },
                    ];

                    const showDeadlockNotification = () => {
                        if (!this.app.state.settings.showDeadlockNotification) return;
                        this.app.modules.toastNotifier.show(T.notificationDeadlock, 'failure', 8000);
                    };

                    const runEngine = () => {
                        for (const fp of FINGERPRINTS) {
                            if (fp.setting && !this.app.state.settings[fp.setting]) continue;
                            document.querySelectorAll(fp.selector).forEach(el => {
                                if (el.dataset[C.PROCESSED_MARKER]) return;
                                el.dataset[C.PROCESSED_MARKER] = 'true';
                                switch (fp.action) {
                                    case 'click':
                                        el.click();
                                        break;
                                    case 'handle_login_modal':
                                        const closeButton = el.querySelector(C.SELECTORS.GLOBAL.CLOSE_BUTTON);
                                        if (closeButton) {
                                            closeButton.click();
                                        } else {
                                            const container = Array.from(document.querySelectorAll(C.SELECTORS.GLOBAL.MODAL_CONTAINER)).find(c => c.contains(el));
                                            if (container) {
                                                container.style.display = 'none';
                                                console.warn(`${C.LOG_PREFIX} Non-closable modal hidden. Page may be deadlocked.`);
                                                showDeadlockNotification();
                                            }
                                        }
                                        break;
                                }
                            });
                        }
                    };

                    const throttledEngine = this.app.utils.throttle(runEngine, C.THROTTLE_DELAY);
                    const observer = new MutationObserver(throttledEngine);
                    observer.observe(document.documentElement, { childList: true, subtree: true });
                    throttledEngine();
                }
            },

            contentExpander: {
                app: null,
                init(app) {
                    this.app = app;
                    if (!this.app.state.settings.expandContentEnabled) return;

                    const C = this.app.config.TEXT_EXPANDER;
                    
                    const runExpander = () => {
                        // Scope to article > button to prevent misclicks on sidebar/nav
                        const selector = `${C.SCOPE_SELECTOR} ${C.BUTTON_SELECTOR}`;
                        const candidates = document.querySelectorAll(selector);

                        candidates.forEach(btn => {
                            if (btn.hasAttribute(C.PROCESSED_ATTR)) return;
                            // Ensure visibility
                            if (btn.offsetParent === null) return;
                            
                            const text = btn.textContent.trim();
                            if (C.TARGETS.includes(text)) {
                                btn.setAttribute(C.PROCESSED_ATTR, 'true');
                                btn.click();
                            }
                        });
                    };

                    const throttledRun = this.app.utils.throttle(runExpander, 500);
                    const observer = new MutationObserver(throttledRun);
                    observer.observe(document.body, { childList: true, subtree: true });
                    runExpander();
                }
            },

            contentAutoLoader: {
                app: null,
                state: {
                    isRunning: false,
                    targetCount: 0,
                    retryCount: 0,
                    lastScrollTime: 0
                },
                init(app) { this.app = app; },
                
                async start() {
                    if (this.state.isRunning) return;
                    
                    const T = this.app.state.T;
                    const batchSize = this.app.state.settings.autoLoadBatchSize || 20;
                    const currentPosts = this.app.modules.postNavigatorCore.getSortedPosts().length;
                    
                    this.state.isRunning = true;
                    this.state.retryCount = 0;
                    this.state.targetCount = currentPosts + batchSize;
                    
                    this.app.modules.floatingNavigator.updateButtonState('start');
                    this.app.modules.toastNotifier.show(
                        T.autoLoad_status_loading.replace('{current}', currentPosts).replace('{target}', this.state.targetCount), 
                        'success'
                    );

                    await this.loop();
                },

                stop(reasonKey = '') {
                    this.state.isRunning = false;
                    this.app.modules.floatingNavigator.updateButtonState('stop');
                    
                    if (reasonKey) {
                        const T = this.app.state.T;
                        const type = reasonKey.includes('success') ? 'success' : 'failure';
                        this.app.modules.toastNotifier.show(T[reasonKey], type);
                    }
                },

                async loop() {
                    const C = this.app.config.AUTO_LOADER;
                    const T = this.app.state.T;

                    while (this.state.isRunning) {
                        if (this.isDeadlocked()) {
                            this.stop('autoLoad_status_deadlock');
                            return;
                        }

                        const currentCount = this.app.modules.postNavigatorCore.getSortedPosts().length;
                        if (currentCount >= this.state.targetCount) {
                            this.stop('autoLoad_status_success');
                            return;
                        }

                        const timeSinceLastScroll = Date.now() - this.state.lastScrollTime;
                        if (timeSinceLastScroll < C.MIN_COOLDOWN) {
                            await this.app.utils.delay(C.MIN_COOLDOWN - timeSinceLastScroll);
                        }

                        this.app.modules.toastNotifier.show(
                            T.autoLoad_status_loading.replace('{current}', currentCount).replace('{target}', this.state.targetCount),
                            'success', 1000
                        );
                        
                        window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
                        this.state.lastScrollTime = Date.now();

                        const result = await this.waitForNewContent(currentCount);

                        if (result === 'loaded') {
                            this.state.retryCount = 0;
                        } else if (result === 'timeout') {
                            this.state.retryCount++;
                            this.app.modules.toastNotifier.show(
                                T.autoLoad_status_retrying.replace('{count}', this.state.retryCount).replace('{max}', C.MAX_RETRIES),
                                'failure', 1000
                            );
                            
                            if (this.state.retryCount >= C.MAX_RETRIES) {
                                this.stop('autoLoad_status_success'); // Treat as success/partial success if we hit wall
                                return;
                            }
                        }
                    }
                },

                waitForNewContent(baselineCount) {
                    const C = this.app.config.AUTO_LOADER;
                    return new Promise((resolve) => {
                        const startTime = Date.now();
                        const poller = setInterval(() => {
                            if (!this.state.isRunning) { clearInterval(poller); resolve('stopped'); return; }
                            if (this.isDeadlocked()) { clearInterval(poller); return; } // Main loop will handle stop

                            const newCount = this.app.modules.postNavigatorCore.getSortedPosts().length;
                            if (newCount > baselineCount) { clearInterval(poller); resolve('loaded'); }
                            
                            if (Date.now() - startTime > C.MAX_WAIT_TIME) { clearInterval(poller); resolve('timeout'); }
                        }, C.POLL_INTERVAL);
                    });
                },

                isDeadlocked() {
                    // Reuse the robust logic we built in the test script
                    // 1. Check Dialog with Login Form
                    const C = this.app.config.SELECTORS;
                    const U = this.app.utils;
                    const dialog = document.querySelector(C.GLOBAL.DIALOG);
                    if (dialog && U.isVisible(dialog)) {
                        if (dialog.querySelector(C.GLOBAL.LOGIN_FORM)) return true;
                    }
                    // 2. Check NoSnippet banner
                    const noSnippet = document.querySelector('div[data-nosnippet]');
                    if (noSnippet && U.isVisible(noSnippet)) return true;
                    
                    // 3. Check any visible login form not in banner
                    const forms = document.querySelectorAll(C.GLOBAL.LOGIN_FORM);
                    for (const form of forms) {
                        if (U.isVisible(form) && !form.closest('[role="banner"]')) return true;
                    }
                    return false;
                },

                async copyAllPosts() {
                    const T = this.app.state.T;
                    const settings = this.app.state.settings;
                    const posts = this.app.modules.postNavigatorCore.getSortedPosts();
                    if (posts.length === 0) {
                        this.app.modules.toastNotifier.show(T.batchCopy_empty, 'failure');
                        return;
                    }

                    this.app.modules.toastNotifier.show(T.batchCopy_start.replace('{count}', posts.length), 'success');
                    
                    // Small delay to let UI update
                    await this.app.utils.delay(50);

                    const PHT = this.app.modules.postHeaderTools;
                    let bodyText = '';
                    let successCount = 0;
                    
                    const BATCH_SEPARATOR = '\n\n═══════════════════════════════════════════════════════════════\n\n';

                    // Batch processing loop
                    for (const post of posts) {
                        const text = await PHT.extractPostMetadata(post, {
                            forceRawLink: true, 
                            includeOrder: settings.copy_meta_order,
                            isBatch: true
                        });
                        
                        if (text) {
                            bodyText += text + BATCH_SEPARATOR;
                            successCount++;
                        }
                    }

                    // --- Generate Header ---
                    let finalText = bodyText;
                    
                    if (settings.batchCopy_includeHeader) {
                        const cleanUrl = window.location.origin + window.location.pathname;
                        const pageTitle = document.title.replace(/ \| Facebook$/, '').replace(/^\(\d+\) /, '');
                        const now = new Date().toLocaleString(navigator.language, { hour12: false });
                        
                        const header = 
`═══════════════════════════════════════════════════════════════
【 BATCH EXPORT SUMMARY 】
Source: ${pageTitle}
URL:    ${cleanUrl}
Time:   ${now}
Count:  ${successCount} Posts
═══════════════════════════════════════════════════════════════

`;
                        finalText = header + bodyText;
                    }

                    try {
                        await GM_setClipboard(finalText);
                        this.app.modules.toastNotifier.show(T.batchCopy_success.replace('{count}', successCount), 'success');
                    } catch (err) {
                        console.error(err);
                        this.app.modules.toastNotifier.show(T.copier_failure, 'failure');
                    }
                },
            },

            errorRecovery: {
                app: null,
                init(app) {
                    this.app = app;
                    if (!this.app.state.settings.errorRecoveryEnabled) return;
                    
                    const run = () => {
                        const C = this.app.config.ERROR_RECOVERY;
                        const selector = C.RELOAD_BUTTON_LABELS.map(label => `div[role="button"][aria-label="${label}"]`).join(', ');
                        const btn = document.querySelector(selector);
                        if (!btn) return;
                        const currentUrl = window.location.href;
                        let state = { url: '', count: 0 };
                        try {
                            const stored = sessionStorage.getItem(C.STORAGE_KEY);
                            if (stored) state = JSON.parse(stored);
                        } catch (e) {}

                        if (state.url !== currentUrl) state = { url: currentUrl, count: 0 };
                        if (state.count >= C.MAX_RETRIES) {
                            console.warn(`${this.app.config.LOG_PREFIX} [ErrorRecovery] Max retries reached for this URL.`);
                            return;
                        }

                        console.log(`${this.app.config.LOG_PREFIX} [ErrorRecovery] Error page detected. Retrying...`);
                        state.count++;
                        sessionStorage.setItem(C.STORAGE_KEY, JSON.stringify(state));
                        btn.click();
                        
                        setTimeout(() => {
                            if (document.querySelector(selector)) window.location.reload();
                        }, 1000);
                    };

                    const throttledRun = this.app.utils.throttle(run, 1000);
                    const observer = new MutationObserver(throttledRun);
                    observer.observe(document.body, { childList: true, subtree: true });
                    run();
                }
            },

            transparencyActions: {
                app: null,
                container: null,
                interval: null,
                init(app) {
                    this.app = app;
                    if (!this.app.state.settings.transparencyButtonsEnabled) return;
                    this.updateUI();
                    this.interval = setInterval(() => {
                        const currentPath = window.location.pathname;
                        if (currentPath !== this.app.state.currentPath) {
                            this.app.state.currentPath = currentPath;
                            this.app.state.cachedPageID = null;
                            this.updateUI(); 
                        }
                        const exclude = ['/watch', '/marketplace', '/gaming', '/events', '/groups', '/messages', '/ads'];
                        if (exclude.some(ex => currentPath.startsWith(ex)) || currentPath === '/') return;
                        if (this.app.state.cachedPageID) return;
                        const id = this._extractPageID();
                        if (id) {
                            this.app.state.cachedPageID = id;
                            this.updateUI(); 
                        }
                    }, 1000);
                },
                deinit() {
                    if (this.container) this.container.remove();
                    if (this.interval) clearInterval(this.interval);
                },
                _extractPageID() {
                    const scripts = document.querySelectorAll('script[type="application/json"]');
                    const reAssociated = /"associated_page_id":"(\d+)"/;
                    const reDelegate = /"delegate_page":\{[^{}]*"id":"(\d+)"/;

                    for (const script of scripts) {
                        const content = script.textContent;
                        if (!content || content.includes('timeline_list_feed_units')) continue;
                        const matchAssoc = content.match(reAssociated);
                        if (matchAssoc && matchAssoc[1] && matchAssoc[1] !== '0') return matchAssoc[1];
                        if (content.includes('profile_header_renderer') || content.includes('profile_tile_sections')) {
                            const matchDel = content.match(reDelegate);
                            if (matchDel && matchDel[1] && matchDel[1] !== '0') return matchDel[1];
                        }
                    }
                    return null;
                },
                updateUI() {
                    if (!this.container) {
                        const T = this.app.state.T;
                        const C = this.app.config.ADS_LIB;
                        const U = this.app.utils;

                        const btnAds = this._createBtn('📢', T.tooltipAds, (e) => {
                            if (this.app.state.cachedPageID) {
                                localStorage.setItem(C.KEY_TARGET_ID, this.app.state.cachedPageID);
                                const url = `https://www.facebook.com/ads/library/?active_status=active&view_all_page_id=${this.app.state.cachedPageID}`;
                                U.smartOpen(e, url);
                            }
                        });

                        const btnInt = this._createBtn('🛡️', T.tooltipTransparency, (e) => {
                            if (this.app.state.cachedPageID) {
                                localStorage.setItem(C.KEY_INT_ACTION, 'true');
                                const loc = window.location;
                                let targetUrl = '';
                                if (loc.pathname.includes('profile.php')) {
                                    const params = new URLSearchParams(loc.search);
                                    targetUrl = `${loc.origin}${loc.pathname}?id=${params.get('id')}&sk=about_profile_transparency`;
                                } else if (loc.pathname.startsWith('/people/') || loc.pathname.startsWith('/p/')) {
                                    const cleanPath = loc.pathname.replace(/\/$/, '');
                                    targetUrl = `${loc.origin}${cleanPath}/?sk=about_profile_transparency`;
                                } else {
                                    const rootPath = `/${loc.pathname.split('/')[1]}`;
                                    targetUrl = `${loc.origin}${rootPath}/about_profile_transparency`;
                                }
                                U.smartOpen(e, targetUrl);
                            }
                        });

                        this.container = U.createStyledElement('div', {}, { 
                            className: 'gm-transparency-container',
                            children: [btnInt, btnAds]
                        });
                        document.body.appendChild(this.container);
                    }
                    if (this.app.state.cachedPageID) this.container.style.display = 'flex';
                    else this.container.style.display = 'none';
                },
                _createBtn(icon, title, onClick) {
                    return this.app.utils.createStyledElement('button', {}, { 
                        className: 'gm-transparency-btn', title: title, innerHTML: icon,
                        on: {
                            mouseup: (e) => { e.currentTarget.style.transform = 'scale(1)'; onClick(e); },
                            mousedown: (e) => { if (e.button === 1) e.preventDefault(); e.currentTarget.style.transform = 'scale(0.95)'; },
                            mouseover: (e) => e.currentTarget.style.backgroundColor = '#f0f2f5',
                            mouseleave: (e) => e.currentTarget.style.backgroundColor = 'white'
                        }
                    });
                }
            },

            adsLibraryHandler: {
                app: null,
                init(app) {
                    this.app = app;
                    if (!window.location.pathname.includes('/ads/library/')) return;
                    const C = this.app.config.ADS_LIB;
                    const urlParams = new URLSearchParams(window.location.search);
                    const currentID = urlParams.get('view_all_page_id');
                    const targetID = localStorage.getItem(C.KEY_TARGET_ID);

                    if (currentID && targetID && currentID === targetID) {
                        console.log(`${this.app.config.LOG_PREFIX} [AdsLib] ID Match. Auto-nav initiating...`);
                        localStorage.removeItem(C.KEY_TARGET_ID);
                        this._initAdBlockerRemover();
                        setTimeout(() => {
                            let attempts = 0;
                            const autoClicker = setInterval(() => {
                                attempts++;
                                const allLinks = document.querySelectorAll('div[role="link"]');
                                const parentMap = new Map();
                                allLinks.forEach(link => {
                                    const parent = link.parentElement;
                                    if (!parentMap.has(parent)) parentMap.set(parent, []);
                                    parentMap.get(parent).push(link);
                                });
                                let targetGroup = null;
                                for (const [parent, children] of parentMap.entries()) {
                                    if (children.length >= 3) {
                                        targetGroup = children;
                                        break;
                                    }
                                }
                                if (targetGroup && targetGroup[1]) { 
                                    clearInterval(autoClicker);
                                    targetGroup[1].click();
                                } else if (attempts > C.MAX_ATTEMPTS) {
                                    clearInterval(autoClicker);
                                }
                            }, C.POLL_INTERVAL);
                        }, C.INITIAL_DELAY);
                    }
                },
                _initAdBlockerRemover() {
                    let hasHandledWarning = false;
                    let observer = null;
                    const cleanDialogs = () => {
                        if (hasHandledWarning) return;
                        const dialogs = document.querySelectorAll('div[role="dialog"]');
                        for (const dialog of dialogs) {
                            if (dialog.dataset.gmProcessed) continue;
                            if (!dialog.querySelector('input, select, textarea')) {
                                const hasHeading = dialog.querySelector('[role="heading"]');
                                const buttons = dialog.querySelectorAll('[role="button"]');
                                if (hasHeading && buttons.length > 0) {
                                    console.log(`${this.app.config.LOG_PREFIX} [AdsLib] Removing blocking dialog.`);
                                    const lastBtn = buttons[buttons.length - 1];
                                    if (lastBtn) lastBtn.click();
                                    dialog.dataset.gmProcessed = 'true';
                                    setTimeout(() => {
                                        if (document.body.contains(dialog) && dialog.style.display !== 'none') {
                                            dialog.style.display = 'none';
                                            const layer = dialog.closest('.x1n2onr6');
                                            if (layer) layer.style.display = 'none';
                                        }
                                    }, 300);
                                    hasHandledWarning = true;
                                    if (observer) { observer.disconnect(); observer = null; }
                                }
                            }
                        }
                    };
                    observer = new MutationObserver(cleanDialogs);
                    observer.observe(document.body, { childList: true, subtree: true });
                    cleanDialogs();
                }
            },

            internalTransparencyHandler: {
                app: null,
                init(app) {
                    this.app = app;
                    if (!window.location.href.includes('about_profile_transparency')) return;
                    const C = this.app.config.ADS_LIB; 
                    const T_CONF = this.app.config.TRANSPARENCY;

                    if (localStorage.getItem(C.KEY_INT_ACTION) === 'true') {
                        console.log(`${this.app.config.LOG_PREFIX} [IntTrans] Expanding details...`);
                        localStorage.removeItem(C.KEY_INT_ACTION);
                        setTimeout(() => {
                            let attempts = 0;
                            const autoExpander = setInterval(() => {
                                attempts++;
                                const selector = T_CONF.SEE_ALL_BUTTONS.map(l => `div[role="button"][aria-label="${l}"]`).join(', ');
                                const btn = document.querySelector(selector);
                                if (btn) {
                                    clearInterval(autoExpander);
                                    btn.click();
                                } else if (attempts > C.MAX_ATTEMPTS) {
                                    clearInterval(autoExpander);
                                }
                            }, C.POLL_INTERVAL);
                        }, C.INITIAL_DELAY);
                    }
                }
            },

            idRevealer: {
                app: null,
                activeBadge: null,
                targetH1: null,

                init(app) {
                    this.app = app;
                    // Gatekeeper
                    if (!this.app.state.settings.idRevealerEnabled) return;

                    // Observe DOM to inject triggers when H1 appears
                    const observer = new MutationObserver(this.app.utils.throttle(() => this.inject(), 500));
                    observer.observe(document.body, { childList: true, subtree: true });
                    
                    window.addEventListener('resize', () => this.updatePosition());
                    window.addEventListener('scroll', () => this.updatePosition(), true);
                    window.addEventListener('historyChange', () => this.closeBadge());
                },

                inject() {
                    // [Fix] Exclude Group pages to prevent extracting random member IDs
                    if (/\/groups\//.test(window.location.pathname)) return;

                    const h1 = document.querySelector('h1:not([data-gm-id-revealer])');
                    if (!h1) return;
                    
                    const T = this.app.state.T;
                    h1.dataset.gmIdRevealer = 'true';
                    
                    // Visual cues for clickability
                    h1.style.cursor = 'pointer';
                    h1.title = T.idRevealerTooltip;
                    
                    h1.addEventListener('mouseover', () => {
                        h1.style.textDecoration = 'underline';
                        h1.style.textDecorationColor = 'rgba(0,0,0,0.3)';
                    });
                    h1.addEventListener('mouseout', () => {
                        h1.style.textDecoration = 'none';
                    });

                    // Click: Toggle Badge
                    h1.addEventListener('click', (e) => {
                        e.stopPropagation(); 
                        e.preventDefault();

                        if (this.activeBadge && this.targetH1 === h1) {
                            this.closeBadge();
                        } else {
                            this.showBadge(h1);
                        }
                    });
                },

                extractIds() {
                    const ids = { metaId: null, userId: null, pageId: null };

                    // 1. Meta Tags
                    const metaSelectors = ['meta[property="al:android:url"]', 'meta[property="al:ios:url"]'];
                    for (const sel of metaSelectors) {
                        const meta = document.querySelector(sel);
                        if (meta && meta.content) {
                            const match = meta.content.match(/fb:\/\/profile\/(\d+)/);
                            if (match && match[1]) ids.metaId = match[1];
                        }
                    }
                    if (!ids.metaId) {
                        const metaApp = document.querySelector('meta[name="apple-itunes-app"]');
                        if (metaApp && metaApp.content) {
                            const match = metaApp.content.match(/app-argument=fb:\/\/profile\/(\d+)/);
                            if (match && match[1]) ids.metaId = match[1];
                        }
                    }

                    // 2. JSON Scan
                    const scripts = document.querySelectorAll('script[type="application/json"]');
                    const reUserID = /"userID":"(\d+)"/;
                    const reDelegate = /"delegate_page":\{[^{}]*"id":"(\d+)"/;
                    const reAssoc = /"associated_page_id":"(\d+)"/;

                    for (const script of scripts) {
                        const content = script.textContent;
                        if (!content) continue;

                        if (!ids.userId) {
                            const mUser = content.match(reUserID);
                            if (mUser && mUser[1] !== '0') ids.userId = mUser[1];
                        }
                        if (!ids.pageId) {
                            const mDel = content.match(reDelegate);
                            if (mDel && mDel[1] && mDel[1] !== '0') ids.pageId = mDel[1];
                            else {
                                const mAssoc = content.match(reAssoc);
                                if (mAssoc && mAssoc[1] && mAssoc[1] !== '0') ids.pageId = mAssoc[1];
                            }
                        }
                        if (ids.userId && ids.pageId) break;
                    }

                    // Consolidate results with specific labels
                    const T = this.app.state.T;
                    const result = new Map();
                    
                    if (ids.metaId) result.set(T.id_label_meta, ids.metaId);
                    if (ids.userId && ids.userId !== ids.metaId) result.set(T.id_label_user, ids.userId);
                    if (ids.pageId && ids.pageId !== ids.metaId && ids.pageId !== ids.userId) result.set(T.id_label_page, ids.pageId);

                    return result;
                },

                generateLink(id) {
                    const format = this.app.state.settings.idRevealerLinkFormat;
                    if (format === 'classic') return `https://www.facebook.com/profile.php?id=${id}`;
                    if (format === 'username') {
                        const url = new URL(window.location.href);
                        url.search = ''; 
                        return url.href.replace(/\/$/, '');
                    }
                    // 'userid' format: Standard User ID URL
                    return `https://www.facebook.com/${id}`;
                },

                showBadge(h1) {
                    this.closeBadge(); // Close existing if any

                    const idMap = this.extractIds();
                    if (idMap.size === 0) return;

                    this.targetH1 = h1;
                    this.createBadgeUI(h1, idMap);
                    this.updatePosition();
                },

                // Helper to get text content excluding children like the verified badge
                _extractText(node) {
                    let text = '';
                    for (const child of node.childNodes) {
                        if (child.nodeType === Node.TEXT_NODE) {
                            text += child.textContent;
                        }
                    }
                    return text.trim();
                },

                createBadgeUI(h1, idMap) {
                    const U = this.app.utils;
                    const T = this.app.state.T;

                    const container = U.createStyledElement('div', {
                        position: 'fixed', zIndex: '99999', backgroundColor: '#FFFFFF',
                        borderRadius: '8px', boxShadow: '0 12px 28px 0 rgba(0, 0, 0, 0.2), 0 2px 4px 0 rgba(0, 0, 0, 0.1)',
                        border: '1px solid rgba(0, 0, 0, 0.1)', padding: '12px',
                        minWidth: '280px', maxWidth: '320px', display: 'flex', flexDirection: 'column', gap: '8px',
                        fontFamily: 'Segoe UI, Helvetica, Arial, sans-serif', fontSize: '13px', color: '#050505',
                        opacity: '0', transition: 'opacity 0.1s ease-out', pointerEvents: 'auto'
                    }, { className: 'gm-id-revealer-badge' });

                    // --- Header: Title + Close ---
                    const header = U.createStyledElement('div', {
                        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
                        marginBottom: '4px', borderBottom: '1px solid #DADDE1', paddingBottom: '8px'
                    });
                    header.append(
                        U.createStyledElement('span', { fontWeight: '600', color: '#65676B' }, { textContent: 'ID Revealer' }),
                        U.createStyledElement('div', {
                            cursor: 'pointer', padding: '4px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center'
                        }, {
                            innerHTML: '<svg viewBox="0 0 24 24" width="16" height="16" fill="#65676B"><path d="M18.707 5.293a1 1 0 0 0-1.414 0L12 10.586 6.707 5.293a1 1 0 0 0-1.414 1.414L10.586 12l-5.293 5.293a1 1 0 0 0 1.414 1.414L12 13.414l5.293 5.293a1 1 0 0 0 1.414-1.414L13.414 12l5.293-5.293a1 1 0 0 0 0-1.414z"></path></svg>',
                            on: {
                                click: (e) => { e.stopPropagation(); this.closeBadge(); },
                                mouseover: (e) => e.currentTarget.style.backgroundColor = '#F0F2F5',
                                mouseout: (e) => e.currentTarget.style.backgroundColor = 'transparent'
                            }
                        })
                    );
                    container.appendChild(header);

                    // --- Helper: Create Row ---
                    const createRow = (label, text, copyMsgLabel = null, isLink = false) => {
                        const row = U.createStyledElement('div', { 
                            display: 'flex', alignItems: 'center', justifyContent: 'space-between', 
                            padding: '4px 8px', borderRadius: '4px', cursor: 'pointer', transition: 'background-color 0.1s'
                        }, {
                            on: {
                                click: () => {
                                    GM_setClipboard(text);
                                    const msg = T.copy_success_generic.replace('{label}', copyMsgLabel || label);
                                    this.app.modules.toastNotifier.show(msg, 'success');
                                },
                                mouseover: (e) => e.currentTarget.style.backgroundColor = '#F0F2F5',
                                mouseout: (e) => e.currentTarget.style.backgroundColor = 'transparent'
                            }
                        });

                        row.append(
                            U.createStyledElement('span', { color: '#65676B', fontWeight: '500', marginRight: '8px' }, { textContent: label }),
                            U.createStyledElement('span', {
                                color: isLink ? '#1877F2' : '#050505', fontWeight: isLink ? '400' : '600',
                                whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '200px', direction: 'ltr'
                            }, { textContent: text, title: text })
                        );
                        return row;
                    };

                    // --- Content: Profile Name ---
                    const profileName = this._extractText(h1);
                    if (profileName) {
                        container.appendChild(createRow(T.profile_name_label, profileName, T.profile_name_label));
                    }

                    // --- Content: IDs ---
                    idMap.forEach((id, label) => {
                        container.appendChild(createRow(label, id, label));
                    });

                    // --- Content: Profile URL ---
                    const mainId = idMap.values().next().value;
                    if (mainId) {
                        const linkUrl = this.generateLink(mainId);
                        container.appendChild(createRow(T.profile_url_label, linkUrl, T.profile_url_label, true));
                        
                        // --- Footer: Copy All ---
                        const footer = U.createStyledElement('div', { marginTop: '8px', paddingTop: '8px', borderTop: '1px solid #DADDE1', textAlign: 'center' });
                        const copyAllBtn = U.createStyledElement('button', {
                            border: 'none', background: 'none', color: '#1877F2', fontWeight: '600', cursor: 'pointer', fontSize: '13px', width: '100%', padding: '4px'
                        }, {
                            textContent: T.id_copy_all,
                            on: {
                                click: (e) => {
                                    e.stopPropagation();
                                    const lines = [];
                                    if(profileName) lines.push(`${T.profile_name_label}: ${profileName}`);
                                    idMap.forEach((val, key) => lines.push(`${key}: ${val}`));
                                    lines.push(`${T.profile_url_label}: ${linkUrl}`);
                                    GM_setClipboard(lines.join('\n'));
                                    this.app.modules.toastNotifier.show(T.all_copied, 'success');
                                },
                                mouseover: (e) => e.currentTarget.style.textDecoration = 'underline',
                                mouseout: (e) => e.currentTarget.style.textDecoration = 'none'
                            }
                        });
                        footer.appendChild(copyAllBtn);
                        container.appendChild(footer);
                    }

                    document.body.appendChild(container);
                    container.offsetHeight; // force reflow
                    container.style.opacity = '1';
                    this.activeBadge = container;
                },

                updatePosition() {
                    if (!this.activeBadge || !this.targetH1) return;
                    
                    const rect = this.targetH1.getBoundingClientRect();
                    const badgeRect = this.activeBadge.getBoundingClientRect();
                    
                    let top = rect.bottom + 8;
                    let left = rect.left;
                    const viewportWidth = window.innerWidth;
                    const viewportHeight = window.innerHeight;

                    if (left + badgeRect.width > viewportWidth) left = viewportWidth - badgeRect.width - 20;
                    if (left < 0) left = 10;
                    if (top + badgeRect.height > viewportHeight) top = rect.top - badgeRect.height - 8;

                    this.activeBadge.style.top = `${top}px`;
                    this.activeBadge.style.left = `${left}px`;
                },
                
                closeBadge() {
                    if (this.activeBadge) {
                        this.activeBadge.remove();
                        this.activeBadge = null;
                        this.targetH1 = null;
                    }
                }
            },

            postNavigatorCore: {
                app: null,
                currentPostIndex: -1,
                isRetrying: false,
                RETRY_INTERVAL: 200,
                MAX_RETRY_DURATION: 3000,
                continuousNavInterval: null,
                init(app) {
                    this.app = app;
                },
                startContinuousNavigation(direction) {
                    this.stopContinuousNavigation();
                    this.navigateToPost(direction);
                    const interval = this.app.state.settings.continuousNavInterval;
                    this.continuousNavInterval = setInterval(() => {
                        this.navigateToPost(direction);
                    }, interval);
                },
                stopContinuousNavigation() {
                    if (this.continuousNavInterval) {
                        clearInterval(this.continuousNavInterval);
                        this.continuousNavInterval = null;
                    }
                },
                getSortedPosts() {
                    const posts = Array.from(document.querySelectorAll('[role="article"][aria-posinset]')).filter(p => !p.closest(this.app.config.SELECTORS.GLOBAL.DIALOG));
                    posts.sort((a, b) => parseInt(a.getAttribute('aria-posinset'), 10) - parseInt(b.getAttribute('aria-posinset'), 10));
                    return posts;
                },
                triggerLoadAndRetry() {
                    if (this.isRetrying) return;
                    this.isRetrying = true;
                    const initialPostCount = this.getSortedPosts().length;
                    window.scrollTo({
                        top: document.body.scrollHeight,
                        behavior: 'smooth'
                    });
                    const startTime = Date.now();
                    const retryInterval = setInterval(() => {
                        const newPostCount = this.getSortedPosts().length;
                        if (newPostCount > initialPostCount) {
                            clearInterval(retryInterval);
                            this.isRetrying = false;
                            this.navigateToPost('next');
                            return;
                        }
                        if (Date.now() - startTime > this.MAX_RETRY_DURATION) {
                            clearInterval(retryInterval);
                            this.isRetrying = false;
                            console.log(`${this.app.config.LOG_PREFIX} Failed to load new posts.`);
                        }
                    }, this.RETRY_INTERVAL);
                },
                navigateToPost(direction) {
                    const posts = this.getSortedPosts();
                    if (posts.length === 0) return;
                    const currentHighlighted = document.querySelector(`.${this.app.config.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS}`);
                    if (currentHighlighted) {
                        currentHighlighted.classList.remove(this.app.config.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS);
                        const currentIndex = posts.findIndex(p => p === currentHighlighted);
                        if (currentIndex !== -1) this.currentPostIndex = currentIndex;
                    }
                    if (direction === 'next') this.currentPostIndex++;
                    else this.currentPostIndex--;
                    if (this.currentPostIndex >= posts.length) {
                        if (direction === 'next') {
                            this.currentPostIndex = posts.length - 1;
                            this.triggerLoadAndRetry();
                            return;
                        }
                        this.currentPostIndex = posts.length - 1;
                    }
                    if (this.currentPostIndex < 0) this.currentPostIndex = 0;
                    const targetPost = posts[this.currentPostIndex];
                    if (targetPost) {
                        targetPost.classList.add(this.app.config.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS);
                        const alignment = this.app.state.settings.navigationScrollAlignment;
                        const useSmoothScroll = this.app.state.settings.enableSmoothScrolling;
                        const scrollBehavior = useSmoothScroll ? 'smooth' : 'auto';
                        if (alignment === 'top') {
                            const searchBarHeight = this.app.modules.searchBar ? this.app.modules.searchBar.getOccupiedHeight() : 0;
                            const postTop = targetPost.getBoundingClientRect().top + window.scrollY;
                            const targetY = postTop - searchBarHeight - 10;
                            window.scrollTo({
                                top: targetY,
                                behavior: scrollBehavior
                            });
                        } else {
                            targetPost.scrollIntoView({
                                behavior: scrollBehavior,
                                block: 'center'
                            });
                        }
                    }
                }
            },

            linkInterceptor: {
                app: null,
                init(app) {
                    this.app = app;
                    const UNSAFE_ANCESTORS = ['[role="tablist"]', '[data-pagelet="ProfileTabs"]'];
                    const unsafeSelector = UNSAFE_ANCESTORS.join(', ');
                    const handleClick = (event) => {
                        if (!this.app.state.settings.autoOpenMediaInNewTab || event.button !== 0) return;
                        const mediaLinkAncestor = event.target.closest(this.app.config.SELECTORS.GLOBAL.MEDIA_LINK);
                        if (!mediaLinkAncestor || mediaLinkAncestor.closest(unsafeSelector)) return;
                        let currentElement = event.target;
                        while (currentElement && currentElement !== mediaLinkAncestor) {
                            const tagName = currentElement.tagName.toLowerCase();
                            const role = currentElement.getAttribute('role');
                            if ((tagName === 'a' && currentElement !== mediaLinkAncestor) || role === 'button' || role === 'slider') return;
                            currentElement = currentElement.parentElement;
                        }
                        event.preventDefault();
                        event.stopPropagation();
                        window.open(mediaLinkAncestor.href, '_blank');
                    };
                    document.body.addEventListener('click', handleClick, true);
                }
            },

            scrollRestorer: {
                app: null,
                init(app) {
                    this.app = app;
                    let restoreY = null;
                    let watcherInterval = null;
                    let correctionInterval = null;
                    const C = this.app.config;
                    const forceScrollCorrection = () => {
                        if (restoreY === null) return;
                        if (correctionInterval) clearInterval(correctionInterval);
                        const {
                            CORRECTION_DURATION,
                            CORRECTION_FREQUENCY
                        } = C.SCROLL_RESTORER_CONFIG;
                        const startTime = Date.now();
                        const initialRestoreY = restoreY;
                        correctionInterval = setInterval(() => {
                            window.scrollTo({
                                top: initialRestoreY,
                                behavior: 'instant'
                            });
                            if (Date.now() - startTime > CORRECTION_DURATION) {
                                clearInterval(correctionInterval);
                                correctionInterval = null;
                                restoreY = null;
                            }
                        }, CORRECTION_FREQUENCY);
                    };
                    const startWatcher = () => {
                        if (watcherInterval) clearInterval(watcherInterval);
                        let isContentModalDetected = false;
                        let modalFirstSeenTime = null;
                        watcherInterval = setInterval(() => {
                            const modal = document.querySelector(C.SELECTORS.GLOBAL.DIALOG);
                            const hasLoginForm = modal && modal.querySelector(C.SELECTORS.GLOBAL.LOGIN_FORM);
                            if (!isContentModalDetected && modal && !hasLoginForm) {
                                isContentModalDetected = true;
                                modalFirstSeenTime = Date.now();
                            } else if (isContentModalDetected && !modal) {
                                if (Date.now() - modalFirstSeenTime > C.SCROLL_RESTORER_CONFIG.MODAL_GRACE_PERIOD) {
                                    clearInterval(watcherInterval);
                                    watcherInterval = null;
                                    forceScrollCorrection();
                                }
                            }
                        }, C.SCROLL_RESTORER_CONFIG.WATCHER_FREQUENCY);
                    };
                    const recordClick = e => {
                        if (watcherInterval || correctionInterval) return;
                        if (e.target.closest(C.SELECTORS.GLOBAL.POST_CONTAINER) && !e.target.closest('a[target="_blank"]')) {
                            restoreY = window.scrollY;
                            setTimeout(startWatcher, 0);
                        }
                    };
                    const handleCloseClick = e => {
                        if (e.target.closest(C.SELECTORS.GLOBAL.CLOSE_BUTTON) && restoreY !== null && watcherInterval) {
                            clearInterval(watcherInterval);
                            watcherInterval = null;
                            forceScrollCorrection();
                        }
                    };
                    document.body.addEventListener('click', recordClick, true);
                    document.body.addEventListener('click', handleCloseClick, true);
                }
            },

            autoUnmuter: {
                app: null,
                init(app) {
                    this.app = app;
                    const nativeVolumeSetter = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'volume').set;
                    const nativeMutedSetter = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'muted').set;
                    const attemptUnmute = (video) => {
                        if (!this.app.state.settings.autoUnmuteEnabled) return;
                        if (video instanceof HTMLVideoElement && (video.muted || video.volume === 0)) {
                            const targetVolume = this.app.state.settings.autoUnmuteVolume / 100;
                            nativeMutedSetter.call(video, false);
                            nativeVolumeSetter.call(video, targetVolume);
                            if (video.audioTracks?.length > 0) {
                                for (let track of video.audioTracks) track.enabled = true;
                            }
                            video.dispatchEvent(new Event('volumechange', {
                                bubbles: true
                            }));
                        }
                    };
                    document.addEventListener('play', (e) => attemptUnmute(e.target), true);
                    const observer = new MutationObserver(mutations => {
                        mutations.forEach(mutation => mutation.addedNodes.forEach(node => {
                            if (node.nodeName === 'VIDEO') attemptUnmute(node);
                            else if (node.querySelectorAll) node.querySelectorAll('video').forEach(attemptUnmute);
                        }));
                    });
                    observer.observe(document.body, {
                        childList: true,
                        subtree: true
                    });
                    document.addEventListener('click', (event) => {
                        if (event.target.closest('[aria-label*="mute" i], [aria-label*="sound" i], [role="button"][aria-pressed]')) {
                            setTimeout(() => document.querySelectorAll('video').forEach(attemptUnmute), 150);
                        }
                    }, true);
                    const checkInterval = setInterval(() => document.querySelectorAll('video').forEach(attemptUnmute), 2000);
                    window.addEventListener('beforeunload', () => {
                        clearInterval(checkInterval);
                        observer.disconnect();
                    });
                }
            },

            postNumbering: {
                app: null,
                init(app) {
                    this.app = app;
                    if (!this.app.state.settings.postNumberingEnabled) return;
                    const processNewPosts = () => {
                        if (!this.app.utils.isFeedPage()) return;
                        const posts = document.querySelectorAll('[role="article"][aria-posinset]:not([data-gm-numbered])');
                        posts.forEach(articleElement => {
                            if (articleElement.closest(this.app.config.SELECTORS.GLOBAL.DIALOG)) return;
                            const postNumber = articleElement.getAttribute('aria-posinset');
                            if (!postNumber) return;
                            articleElement.style.position = 'relative';
                            const numberTag = this.app.utils.createStyledElement('span', {}, { className: 'gm-post-number', textContent: postNumber });
                            articleElement.appendChild(numberTag);
                            articleElement.dataset.gmNumbered = 'true';
                        });
                    };
                    const throttledProcess = this.app.utils.throttle(processNewPosts, 300);
                    const observer = new MutationObserver(throttledProcess);
                    observer.observe(document.body, {
                        childList: true,
                        subtree: true
                    });
                    throttledProcess();
                }
            },

            searchBar: {
                app: null,
                elements: {},
                state: {
                    isPinned: true,
                    isAtTop: true, 
                },
                icons: {
                    pinned: `<svg viewBox="0 0 24 24" width="20" height="20"><path fill="#1877F2" d="M16 11V5h1.5a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5h-11a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5H8v6l-2 3v2h5v6l1 1 1-1v-6h5v-2l-2-3z"></path></svg>`,
                    unpinned: `<svg viewBox="0 0 24 24" width="20" height="20"><g transform="rotate(180 12 12)"><path d="M16 11V5h1.5a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5h-11a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5H8v6l-2 3v2h5v6l1 1 1-1v-6h5v-2l-2-3z" style="fill:none; stroke:#65676B; stroke-width:1.5px;"></path></g></svg>`,
                    watch: `<svg viewBox="0 0 24 24" width="20" height="20" style="fill:none; stroke:#65676B; stroke-width:1.8px; stroke-linecap:round; stroke-linejoin:round;"><rect x="2.5" y="5.5" width="19" height="13" rx="3" ry="3"></rect><path d="M10 9l5 3-5 3V9z"></path></svg>`,
                    events: `<svg viewBox="0 0 24 24" width="20" height="20"><path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2zm-7 5h5v5h-5v-5z"></path></svg>`,
                    marketplace: `<svg viewBox="2 2 28 28" width="20" height="20"><path fill="#65676B" d="M28.908,12.571a.952.952,0,0,0-.1-.166,3.146,3.146,0,0,0-.118-.423c-.006-.016-.012-.032-.02-.048L25.917,5.6A1,1,0,0,0,25,5H7a1,1,0,0,0-.917.6l-2.77,6.381a2.841,2.841,0,0,0,0,2.083A4.75,4.75,0,0,0,6,16.609V27a1,1,0,0,0,1,1H25a1,1,0,0,0,1-1V16.609a4.749,4.749,0,0,0,2.687-2.543,2.614,2.614,0,0,0,.163-.655A1.057,1.057,0,0,0,28.908,12.571ZM13,26V20h2v6Zm4,0V20h2v6Zm7,0H21V19a1,1,0,0,0-1-1H12a1,1,0,0,0-1,1v7H8V17a5.2,5.2,0,0,0,4-1.8,5.339,5.339,0,0,0,8,0A5.2,5.2,0,0,0,24,17Zm2.837-12.7A3.015,3.015,0,0,1,24,15a2.788,2.788,0,0,1-3-2.5,1,1,0,0,0-2,0A2.788,2.788,0,0,1,16,15a2.788,2.788,0,0,1-3-2.5,1,1,0,0,0-2,0A2.788,2.788,0,0,1,8,15a3.016,3.016,0,0,1-2.838-1.7.836.836,0,0,1,0-.571L7.656,7H24.344l2.477,5.7A.858.858,0,0,1,26.837,13.3Z"/></svg>`,
                    search: `<svg viewBox="0 0 24 24" width="18" height="18"><path d="M20.71 19.29l-3.4-3.39A7.92 7.92 0 0 0 19 11a8 8 0 1 0-8 8 7.92 7.92 0 0 0 4.9-1.69l3.39 3.4a1.002 1.002 0 0 0 1.42 0 1 1 0 0 0 0-1.42zM5 11a6 6 0 1 1 6 6 6 6 0 0 1-6-6z"></path></svg>`,
                    settings: `<svg viewBox="0 0 24 24" width="20" height="20"><g transform="scale(0.85) translate(2.1, 2.1)"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84a.484.484 0 0 0-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.488.488 0 0 0-.59.22L2.74 8.87c-.12.19-.06.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .43-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"></path></g></svg>`
                },
                getOccupiedHeight() {
                    if (this.state.isPinned && this.elements.toolbar && this.elements.toolbar.style.visibility !== 'hidden') {
                        return this.elements.toolbar.offsetHeight;
                    }
                    return 0;
                },
                init(app) {
                    this.app = app;
                    if (!this.app.state.settings.hideUselessElements) return;

                    this.state.isPinned = GM_getValue('isSearchBarPinned', true);
                    this.elements = this._createUI();
                    document.body.append(this.elements.toolbar, this.elements.hoverTrigger, this.elements.hoverHint);
                    this._bindEvents();
                    
                    setTimeout(() => {
                        this.updateVisibility();
                        this._updatePinState();
                    }, 50);
                },
                _getCurrentPageName() {
                    const h1 = document.querySelector('h1');
                    if (!h1) return null;
                    let pageName = '';
                    for (const node of h1.childNodes) {
                        if (node.nodeType === Node.TEXT_NODE) pageName += node.nodeValue;
                    }
                    return pageName.trim();
                },
                _createUI() {
                    const U = this.app.utils;
                    const elements = {};
                    const shortcutsGroup = this._createShortcutsGroup(elements);
                    const searchComponent = this._createSearchComponent(elements);
                    const toolsGroup = this._createToolsGroup(elements);

                    elements.toolbar = U.createStyledElement('div', {}, { className: 'gm-toolbar', children: [shortcutsGroup, searchComponent, toolsGroup] });
                    elements.hoverTrigger = U.createStyledElement('div', {}, { className: 'gm-hover-trigger' });
                    elements.hoverHint = U.createStyledElement('div', {}, { className: 'gm-hover-hint' });

                    return elements;
                },
                _createShortcutsGroup(elements) {
                    const T = this.app.state.T;
                    const U = this.app.utils;
                    const shortcuts = [
                        { key: 'watch', url: '/watch/', icon: this.icons.watch, tooltip: T.shortcutWatch },
                        { key: 'events', url: '/events/', icon: this.icons.events, tooltip: T.shortcutEvents },
                        { key: 'marketplace', url: '/marketplace/', icon: this.icons.marketplace, tooltip: T.shortcutMarketplace },
                    ];
                    const buttons = shortcuts.map(sc => {
                        const button = U.createStyledElement('button', {}, { 
                            title: sc.tooltip, innerHTML: sc.icon,
                            on: { mousedown: (e) => {
                                if (e.button === 0 || e.button === 1) { 
                                    e.preventDefault();
                                    window.open(`https://www.facebook.com${sc.url}`, e.button === 1 ? '_blank' : '_self');
                                }
                            }}
                        });
                        elements[`shortcut_${sc.key}`] = button;
                        return button;
                    });
                    return U.createStyledElement('div', {}, { className: 'gm-button-group', children: buttons });
                },
                _createSearchComponent(elements) {
                    const T = this.app.state.T;
                    const U = this.app.utils;
                    
                    elements.scopeSelector = U.createStyledElement('select', { cursor: 'pointer' });
                    const scopes = {
                        [T.searchGroupContextual]: [
                            { text: T.searchScopePosts, value: '', tooltip: T.searchTooltipPosts },
                            { text: T.searchScopePhotos, value: '/photos/', tooltip: T.searchTooltipPhotos },
                            { text: T.searchScopeVideos, value: '/videos/', tooltip: T.searchTooltipVideos },
                            { text: T.searchScopeReels, value: 'reel_search', tooltip: T.searchTooltipReels },
                        ],
                        [T.searchGroupGlobal]: [
                            { text: T.searchScopePages, value: 'site:www.facebook.com -inurl:/people/ -inurl:/groups/ -inurl:/events/ -inurl:/marketplace/ -inurl:/gaming/ -inurl:/posts/ -inurl:/videos/ -inurl:/watch/ -inurl:/reels/ -inurl:/reel/ -inurl:/photos/ -inurl:/photo/ -inurl:/albums/ -inurl:/media/ -inurl:/about/ -inurl:/mentions/ -inurl:/permalink/ -inurl:/story.php -inurl:/photo.php -inurl:/permalink.php -inurl:/hashtag/ -inurl:comment_id= -inurl:?locale= -inurl:?locale2=', tooltip: T.searchTooltipPages },
                            { text: T.searchScopePeople, value: 'site:www.facebook.com/people/ -inurl:?locale= -inurl:?locale2=', tooltip: T.searchTooltipPeople },
                            { text: T.searchScopeGroups, value: 'site:www.facebook.com/groups/ -inurl:/posts/ -inurl:/permalink/ -inurl:?locale= -inurl:?locale2=', tooltip: T.searchTooltipGroups },
                            { text: T.searchScopeGlobalPosts, value: 'site:www.facebook.com inurl:/posts/ -inurl:?locale= -inurl:?locale2=', tooltip: T.searchTooltipGlobalPosts },
                            { text: T.searchScopeGlobalVideos, value: 'watch_search', tooltip: T.searchTooltipGlobalVideos },
                            { text: T.searchScopeEvents, value: 'events_search', tooltip: T.searchTooltipEvents },
                            { text: T.searchScopeMarketplace, value: 'marketplace_search', tooltip: T.searchTooltipMarketplace },
                        ]
                    };
                    for (const groupLabel in scopes) {
                        const optgroup = U.createStyledElement('optgroup', {}, { label: groupLabel });
                        scopes[groupLabel].forEach(scope => optgroup.appendChild(U.createStyledElement('option', {}, { value: scope.value, textContent: scope.text, title: scope.tooltip })));
                        elements.scopeSelector.appendChild(optgroup);
                    }
                    
                    elements.searchInput = U.createStyledElement('input', {}, { type: 'text', placeholder: T.searchPlaceholder });
                    elements.clearButton = U.createStyledElement('button', {}, { className: 'gm-search-clear-button', textContent: '✖', on: { mousedown: (e) => e.preventDefault() } });
                    elements.searchButton = U.createStyledElement('button', {}, { className: 'gm-search-button-integrated', innerHTML: this.icons.search, title: T.searchButton });
                    
                    const inputContainer = U.createStyledElement('div', {}, { 
                        className: 'gm-search-input-container',
                        children: [elements.searchInput, elements.clearButton, elements.searchButton]
                    });
                    
                    elements.searchComponentWrapper = U.createStyledElement('div', {}, { 
                        className: 'gm-search-component-wrapper',
                        children: [elements.scopeSelector, inputContainer]
                    });
                    return U.createStyledElement('div', {}, { className: 'gm-search-core-wrapper', children: [elements.searchComponentWrapper] });
                },
                _createToolsGroup(elements) {
                    const U = this.app.utils;
                    elements.pinButton = U.createStyledElement('button', {}, { innerHTML: this.icons.unpinned + this.icons.pinned });
                    elements.settingsButton = U.createStyledElement('button', {}, { 
                        innerHTML: this.icons.settings, 
                        title: this.app.state.T.menuSettings 
                    });
                    return U.createStyledElement('div', {}, { className: 'gm-button-group', children: [elements.pinButton, elements.settingsButton] });
                },
                _bindEvents() {
                    const { searchInput, clearButton, searchButton, pinButton, settingsButton, toolbar, scopeSelector, searchComponentWrapper, hoverTrigger } = this.elements;

                    pinButton.addEventListener('click', () => this._togglePinState());
                    settingsButton.addEventListener('click', () => this.app.modules.settingsModal.open());
                    
                    searchButton.addEventListener('mousedown', (e) => {
                        if (searchButton.disabled) return;
                        if (e.button === 0) {
                            e.preventDefault();
                            this.performSearch('_blank');
                        } 
                        else if (e.button === 1) {
                            e.preventDefault();
                            this.performSearch('background');
                        }
                    });
                    searchInput.addEventListener('input', () => {
                        clearButton.style.display = searchInput.value ? 'flex' : 'none';
                        this._updateSearchButtonState();
                    });
                    searchInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !searchButton.disabled) this.performSearch('_blank'); });
                    searchInput.addEventListener('focus', () => searchComponentWrapper.classList.add('gm-focused'));
                    searchInput.addEventListener('blur', () => searchComponentWrapper.classList.remove('gm-focused'));
                    clearButton.addEventListener('click', () => { searchInput.value = ''; searchInput.focus(); clearButton.style.display = 'none'; this._updateSearchButtonState(); });
                    toolbar.addEventListener('focusout', (e) => { if (!this.state.isPinned && !toolbar.contains(e.relatedTarget)) setTimeout(() => this._hideBar(), 100); });
                    scopeSelector.addEventListener('change', () => this._updateSearchButtonState());
                    hoverTrigger.addEventListener('mouseenter', this._onHoverTriggerEnter.bind(this));
                    toolbar.addEventListener('mouseenter', this._onSearchBarEnter.bind(this));
                    hoverTrigger.addEventListener('mouseleave', this._onMouseLeave.bind(this));
                    toolbar.addEventListener('mouseleave', this._onMouseLeave.bind(this));
                    window.addEventListener('scroll', this.app.utils.throttle(() => this._handleScroll(), 100), { passive: true });
                    window.addEventListener('historyChange', () => this.updateVisibility());
                    new MutationObserver(this.app.utils.throttle(() => this.updateVisibility(), 150)).observe(document.body, { childList: true, subtree: true });
                },
                _updateSearchButtonState() {
                    const T = this.app.state.T;
                    const { searchInput, scopeSelector, searchButton } = this.elements;
                    const selectedOption = scopeSelector.options[scopeSelector.selectedIndex];
                    const isContextual = selectedOption.parentElement.label === T.searchGroupContextual;
                    const keyword = searchInput.value.trim();
                    const requiresKeyword = !isContextual;

                    if (requiresKeyword && !keyword) {
                        searchButton.disabled = true;
                        searchButton.style.opacity = '0.5';
                        searchButton.style.cursor = 'not-allowed';
                        searchButton.title = T.searchButton;
                    } else {
                        searchButton.disabled = false;
                        searchButton.style.opacity = '1';
                        searchButton.style.cursor = 'pointer';
                        searchButton.title = !keyword && isContextual ? T.searchAllContextualTooltip.replace('{scope}', selectedOption.textContent) : T.searchButton;
                    }
                },
                performSearch(target = '_blank') { 
                    const T = this.app.state.T;
                    const selectedOption = this.elements.scopeSelector.options[this.elements.scopeSelector.selectedIndex];
                    const isContextual = selectedOption.parentElement.label === T.searchGroupContextual;
                    const keyword = this.elements.searchInput.value.trim();
                    if (!isContextual && !keyword) return;
                    
                    const langExclusion = ' -inurl:?locale= -inurl:?locale2=';
                    const openUrl = (url) => {
                        switch (target) {
                            case 'background': GM_openInTab(url, { active: false, insert: true }); break;
                            case '_self': window.open(url, '_self'); break;
                            case '_blank': default: window.open(url, '_blank'); break;
                        }
                    };
                    const selectedScopeValue = selectedOption.value;
                    let searchUrl = '';
                    const internalSearchMap = {
                        'events_search': `https://www.facebook.com/events/search/?q=${encodeURIComponent(keyword)}`,
                        'marketplace_search': `https://www.facebook.com/marketplace/search/?query=${encodeURIComponent(keyword)}`,
                        'watch_search': `https://www.facebook.com/watch/search/?q=${encodeURIComponent(keyword)}`
                    };
                    if (internalSearchMap[selectedScopeValue]) {
                        openUrl(internalSearchMap[selectedScopeValue]);
                        return;
                    }
                    if (selectedScopeValue === 'reel_search') {
                        const pageName = this._getCurrentPageName();
                        if (!pageName) {
                            this.app.modules.toastNotifier.show(T.notificationReelSearchError, 'failure');
                            return;
                        }
                        const combinedKeywords = keyword ? `${keyword} "${pageName}"` : `"${pageName}"`;
                        searchUrl = `https://www.google.com/search?q=${encodeURIComponent(combinedKeywords + ' site:www.facebook.com/reel/' + langExclusion)}`;
                    } else if (selectedScopeValue.startsWith('site:')) {
                        searchUrl = `https://www.google.com/search?q=${encodeURIComponent(keyword)}+${encodeURIComponent(selectedScopeValue)}`;
                    } else {
                        let basePath = '';
                        const pathSegments = window.location.pathname.split('/').filter(Boolean);
                        if (pathSegments.length > 0 && pathSegments[0] !== 'groups') {
                             basePath = (pathSegments[0] === 'people' && pathSegments[2] && !isNaN(pathSegments[2])) ? `/${pathSegments[2]}` : `/${pathSegments[0]}`;
                        } else if (pathSegments.length > 1 && pathSegments[0] === 'groups') {
                             basePath = `/${pathSegments[0]}/${pathSegments[1]}`;
                        }
                        const siteTarget = `site:www.facebook.com${basePath}${selectedScopeValue}`;
                        const fullSiteTarget = siteTarget + langExclusion;
                        const query = keyword ? `${encodeURIComponent(keyword)}+${encodeURIComponent(fullSiteTarget)}` : encodeURIComponent(fullSiteTarget);
                        searchUrl = `https://www.google.com/search?q=${query}`;
                    }
                    if (selectedScopeValue === '/photos/') searchUrl += '&udm=2';
                    openUrl(searchUrl);
                },
                updateVisibility() {
                    const C = this.app.config;
                    const currentPath = window.location.pathname;
                    const isDisallowed = ['/photo/', '/videos/', '/reel/', '/posts/', '/watch/'].some(p => currentPath.startsWith(p)) || !!document.querySelector(C.SELECTORS.GLOBAL.DIALOG);
                    const newVisibility = isDisallowed ? 'hidden' : 'visible';
                    const elementsToToggle = [this.elements.toolbar, this.elements.hoverTrigger, this.elements.hoverHint];
                    if (this.elements.toolbar.style.visibility !== newVisibility) elementsToToggle.forEach(el => el.style.visibility = newVisibility);
                    const isGroupPage = currentPath.startsWith('/groups/');
                    for (const option of this.elements.scopeSelector.options) {
                        if (option.parentElement.tagName !== 'OPTGROUP') continue;
                        const isContextualGroup = option.parentElement.label === this.app.state.T.searchGroupContextual;
                        option.disabled = isGroupPage && isContextualGroup && option.value !== '';
                    }
                    if (isGroupPage && this.elements.scopeSelector.options[this.elements.scopeSelector.selectedIndex].disabled) this.elements.scopeSelector.value = '';
                    this._handleScroll(true);
                    this._updateSearchButtonState();
                },
                _handleScroll(force = false) {
                    if (this.state.isPinned) return;
                    const currentIsAtTop = window.scrollY < 10;
                    if (!force && currentIsAtTop === this.state.isAtTop) return;
                    this.state.isAtTop = currentIsAtTop;
                    if (this.state.isAtTop) {
                        this._showBar();
                        this.elements.hoverHint.classList.remove('gm-visible');
                        if (this.elements.toolbar.contains(document.activeElement)) document.activeElement.blur();
                    } else {
                        if (!this.elements.toolbar.contains(document.activeElement)) this._hideBar();
                        this.elements.hoverHint.classList.add('gm-visible');
                    }
                },
                _togglePinState() {
                    this.state.isPinned = !this.state.isPinned;
                    GM_setValue('isSearchBarPinned', this.state.isPinned);
                    this._updatePinState();
                },
                _updatePinState() {
                    const T = this.app.state.T;
                    const { pinButton, hoverHint } = this.elements;
                    const isPinned = this.state.isPinned;
                    pinButton.classList.toggle('gm-pinned', isPinned);
                    const [unpinnedIcon, pinnedIcon] = pinButton.querySelectorAll('svg');
                    pinnedIcon.style.display = isPinned ? 'block' : 'none';
                    unpinnedIcon.style.display = isPinned ? 'none' : 'block';
                    pinButton.title = isPinned ? T.unpinToolbar : T.pinToolbar;
                    if (isPinned) {
                        this._showBar();
                        hoverHint.classList.remove('gm-visible');
                    } else {
                        this._handleScroll(true); 
                    }
                },
                _showBar() { this.elements.toolbar.style.transform = 'translateY(0)'; },
                _hideBar() { this.elements.toolbar.style.transform = 'translateY(-100%)'; },
                _onHoverTriggerEnter() { if (!this.state.isPinned) { this.elements.hoverHint.classList.remove('gm-visible'); this._showBar(); } },
                _onSearchBarEnter() { if (!this.state.isPinned) this._showBar(); },
                _onMouseLeave() { if (!this.state.isPinned && !this.elements.toolbar.contains(document.activeElement)) { this._hideBar(); if (!this.state.isAtTop) this.elements.hoverHint.classList.add('gm-visible'); } },
            },

            keyboardNavigator: { 
                app: null, 
                activeKey: null, 
                init(app) { 
                    this.app = app; 
                    if (!this.app.state.settings.keyboardNavEnabled) return; 
                    document.addEventListener('keydown', this.handleKeyDown.bind(this)); 
                    document.addEventListener('keyup', this.handleKeyUp.bind(this)); 
                }, 
                handleKeyDown(event) { 
                    if (event.key === this.activeKey) return; 
                    if (!this.app.utils.isFeedPage()) return; 
                    const target = event.target; 
                    if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) return; 
                    const settings = this.app.state.settings; 
                    let direction = null; 
                    switch (event.key) { 
                        case settings.keyNavNextPrimary: 
                        case settings.keyNavNextSecondary: 
                            direction = 'next'; 
                            break; 
                        case settings.keyNavPrevPrimary: 
                        case settings.keyNavPrevSecondary: 
                            direction = 'prev'; 
                            break; 
                    } 
                    if (direction) { 
                        event.preventDefault(); 
                        this.activeKey = event.key; 
                        if (this.app.modules.postNavigatorCore) {
                            this.app.modules.postNavigatorCore.startContinuousNavigation(direction); 
                        }
                    } 
                }, 
                handleKeyUp(event) { 
                    if (event.key === this.activeKey) { 
                        this.activeKey = null; 
                        if (this.app.modules.postNavigatorCore) {
                            this.app.modules.postNavigatorCore.stopContinuousNavigation(); 
                        }
                    } 
                } 
            },

            floatingNavigator: { 
                app: null, 
                container: null, 
                btnAutoLoad: null,
                isInitialized: false, 
                init(app) { 
                    if (this.isInitialized) return; 
                    this.app = app; 
                    if (!this.app.state.settings.floatingNavEnabled) return; 
                    const T = this.app.state.T; 
                    const U = this.app.utils; 
                    const settings = this.app.state.settings;
                    // Dynamic reference
                    const getCore = () => this.app.modules.postNavigatorCore; 
                    const getLoader = () => this.app.modules.contentAutoLoader;
                    
                    this.container = U.createStyledElement('div', {}, { className: 'gm-floating-nav' }); 
                    
                    // --- Navigation Buttons ---
                    const prevButton = U.createStyledElement('button', {}, { title: T.floatingNavPrevTooltip }); 
                    prevButton.innerHTML = `<svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"></path></svg>`; 
                    prevButton.addEventListener('mousedown', () => { const c = getCore(); if(c) c.startContinuousNavigation('prev'); }); 
                    prevButton.addEventListener('mouseleave', () => { const c = getCore(); if(c) c.stopContinuousNavigation(); }); 
                    
                    const nextButton = U.createStyledElement('button', {}, { title: T.floatingNavNextTooltip }); 
                    nextButton.innerHTML = `<svg viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"></path></svg>`; 
                    nextButton.addEventListener('mousedown', () => { const c = getCore(); if(c) c.startContinuousNavigation('next'); }); 
                    nextButton.addEventListener('mouseleave', () => { const c = getCore(); if(c) c.stopContinuousNavigation(); }); 
                    
                    document.body.addEventListener('mouseup', () => { const c = getCore(); if(c) c.stopContinuousNavigation(); }); 
                    this.container.append(prevButton, nextButton);

                    // --- Tools Separator ---
                    if (settings.floatingNav_showAutoLoad || settings.floatingNav_showBatchCopy) {
                        const separator = U.createStyledElement('div', { height: '4px' });
                        this.container.appendChild(separator);
                    }

                    // --- Auto-Load Button ---
                    if (settings.floatingNav_showAutoLoad) {
                        this.btnAutoLoad = U.createStyledElement('button', {}, { title: T.tooltipAutoLoadStart });
                        // Default Icon: Arrow Down
                        this.btnAutoLoad.innerHTML = `<svg viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></svg>`;
                        this.btnAutoLoad.addEventListener('click', () => {
                            const loader = getLoader();
                            if (loader) {
                                if (loader.state.isRunning) loader.stop('autoLoad_status_stopped');
                                else loader.start();
                            }
                        });
                        this.container.appendChild(this.btnAutoLoad);
                    }

                    // --- Batch Copy Button ---
                    if (settings.floatingNav_showBatchCopy) {
                        const btnBatchCopy = U.createStyledElement('button', {}, { title: T.tooltipBatchCopy });
                        // Icon: Clipboard
                        btnBatchCopy.innerHTML = `<svg viewBox="0 0 24 24"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"></path></svg>`;
                        btnBatchCopy.addEventListener('click', () => {
                             const loader = getLoader();
                             if (loader) loader.copyAllPosts();
                        });
                        this.container.appendChild(btnBatchCopy);
                    }
                    
                    document.body.appendChild(this.container); 
                    this.updateVisibility(); 
                    window.addEventListener('historyChange', this.updateVisibility.bind(this)); 
                    new MutationObserver(U.throttle(this.updateVisibility.bind(this), 200)).observe(document.body, { childList: true, subtree: true }); 
                    this.isInitialized = true; 
                }, 
                deinit() { 
                    if (this.container) { 
                        this.container.remove(); 
                        this.container = null; 
                        this.btnAutoLoad = null;
                    } 
                    this.isInitialized = false; 
                },
                updateButtonState(state) {
                    if (!this.btnAutoLoad) return;
                    const T = this.app.state.T;
                    if (state === 'start') {
                        // Change to Stop Icon (Square)
                        this.btnAutoLoad.innerHTML = `<svg viewBox="0 0 24 24" fill="#e02424"><path d="M6 6h12v12H6z"></path></svg>`;
                        this.btnAutoLoad.title = T.tooltipAutoLoadStop;
                        this.btnAutoLoad.style.borderColor = '#e02424';
                    } else {
                        // Revert to Start Icon (Arrow Down)
                        this.btnAutoLoad.innerHTML = `<svg viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></svg>`;
                        this.btnAutoLoad.title = T.tooltipAutoLoadStart;
                        this.btnAutoLoad.style.borderColor = '#ddd';
                    }
                },
                updateVisibility() { 
                    if (!this.container) return; 
                    const isVisible = this.app.utils.isFeedPage() && !document.querySelector(this.app.config.SELECTORS.GLOBAL.DIALOG); 
                    this.container.style.display = isVisible ? 'flex' : 'none'; 
                } 
            },

            wheelNavigator: { 
                app: null, 
                isCoolingDown: false, 
                init(app) { 
                    this.app = app; 
                    if (!this.app.state.settings.wheelNavEnabled) return; 
                    document.addEventListener('wheel', this.handleWheel.bind(this), { passive: false }); 
                }, 
                handleWheel(event) { 
                    if (this.isCoolingDown) return; 
                    if (!this.app.utils.isFeedPage() || document.querySelector(this.app.config.SELECTORS.GLOBAL.DIALOG)) return; 
                    const modifierKey = this.app.state.settings.wheelNavModifier; 
                    if (modifierKey !== 'none' && !event[modifierKey]) return; 
                    event.preventDefault(); 
                    event.stopPropagation(); 
                    const direction = event.deltaY > 0 ? 'next' : 'prev'; 
                    if (this.app.modules.postNavigatorCore) {
                        this.app.modules.postNavigatorCore.navigateToPost(direction); 
                    }
                    this.isCoolingDown = true; 
                    setTimeout(() => { this.isCoolingDown = false; }, this.app.state.settings.continuousNavInterval); 
                } 
            },

            clickToFocusNavigator: { 
                app: null, 
                init(app) { 
                    this.app = app; 
                    const settings = this.app.state.settings; 
                    if (!settings.keyboardNavEnabled && !settings.floatingNavEnabled && !settings.wheelNavEnabled) return; 
                    document.body.addEventListener('click', this.handleClick.bind(this)); 
                }, 
                handleClick(event) { 
                    const target = event.target; 
                    if (target.closest('a, button, [role="button"], input, textarea') || window.getSelection().toString().length > 0) return; 
                    const post = target.closest('[role="article"][aria-posinset]'); 
                    if (!post) return; 
                    if (post.closest(this.app.config.SELECTORS.GLOBAL.DIALOG)) return; 
                    
                    const Core = this.app.modules.postNavigatorCore; 
                    if (!Core) return;

                    const C = this.app.config; 
                    const currentHighlighted = document.querySelector(`.${C.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS}`); 
                    if (currentHighlighted && currentHighlighted !== post) { 
                        currentHighlighted.classList.remove(C.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS); 
                    } 
                    post.classList.add(C.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS); 
                    const posts = Core.getSortedPosts(); 
                    const newIndex = posts.findIndex(p => p === post); 
                    if (newIndex !== -1) Core.currentPostIndex = newIndex; 
                } 
            },

            postHeaderTools: {
                app: null,
                isProcessingClick: false,
                isModalOpening: false,
                observer: null,
                
                // SVG Icons definitions
                icons: {
                    smart: `<svg viewBox="0 0 32 32" width="16" height="16"><path fill="#F5C33B" d="M18,11a1,1,0,0,1-1,1,5,5,0,0,0-5,5,1,1,0,0,1-2,0,5,5,0,0,0-5-5,1,1,0,0,1,0-2,5,5,0,0,0,5-5,1,1,0,0,1,2,0,5,5,0,0,0,5,5A1,1,0,0,1,18,11Z"/><path fill="#F5C33B" d="M19,24a1,1,0,0,1-1,1,2,2,0,0,0-2,2,1,1,0,0,1-2,0,2,2,0,0,0-2-2,1,1,0,0,1,0-2,2,2,0,0,0,2-2,1,1,0,0,1,2,0,2,2,0,0,0,2,2A1,1,0,0,1,19,24Z"/><path fill="#F5C33B" d="M28,17a1,1,0,0,1-1,1,4,4,0,0,0-4,4,1,1,0,0,1-2,0,4,4,0,0,0-4-4,1,1,0,0,1,0-2,4,4,0,0,0,4-4,1,1,0,0,1,2,0,4,4,0,0,0,4,4A1,1,0,0,1,28,17Z"/></svg>`,
                    direct: `<svg viewBox="0 0 24 24" width="16" height="16"><path fill="#1877F2" d="M13.29 9.29l-4 4a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l4-4a1 1 0 0 0-1.42-1.42z"/><path fill="#1877F2" d="M12.28 17.4L11 18.67a4.2 4.2 0 0 1-5.58.4 4 4 0 0 1-.27-5.93l1.42-1.43a1 1 0 0 0 0-1.42 1 1 0 0 0-1.42 0l-1.27 1.28a6.15 6.15 0 0 0-.67 8.07 6.06 6.06 0 0 0 9.07.6l1.42-1.42a1 1 0 0 0-1.42-1.42z"/><path fill="#1877F2" d="M19.66 3.22a6.18 6.18 0 0 0-8.13.68L10.45 5a1.09 1.09 0 0 0-.17 1.61 1 1 0 0 0 1.42 0L13 5.3a4.17 4.17 0 0 1 5.57-.4 4 4 0 0 1 .27 5.95l-1.42 1.43a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l1.42-1.42a6.06 6.06 0 0 0-.6-9.06z"/></svg>`,
                    processing: `<svg viewBox="0 0 24 24" width="16" height="16" class="gm-spin"><path fill="#1877F2" d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"></path></svg>`,
                    success: `<svg viewBox="0 0 24 24" width="16" height="16"><path fill="#42B72A" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></svg>`,
                    failure: `<svg viewBox="0 0 24 24" width="16" height="16"><path fill="#FA383E" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></svg>`,
                    copy: `<svg viewBox="0 0 24 24" width="16" height="16"><path fill="#65676B" d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>`,
                },

                init(app) {
                    if (!this.app) this.app = app;
                    if (this.observer) return;
                    this.startObserver();
                },

                deinit() {
                    if (this.observer) {
                        this.observer.disconnect();
                        this.observer = null;
                    }
                    this.cleanupButtons();
                },

                // --- Feature 1: Permalink Copier Logic (Standalone) ---
                async handlePermalinkClick(event, button) {
                    if (this.isProcessingClick) return;
                    event.preventDefault(); event.stopPropagation();
                    
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const postEl = button.closest(`[data-${C_TOOLS.PROCESSED_MARKER}]`);
                    if (!postEl) return;

                    this.isProcessingClick = true;
                    button.style.pointerEvents = 'none';
                    const originalContent = button.innerHTML;
                    const T = this.app.state.T;
                    const settings = this.app.state.settings;

                    const iconWrapper = button.querySelector('.gm-icon-wrapper');
                    if (iconWrapper) iconWrapper.innerHTML = this.icons.processing;

                    try {
                        const contentType = this.determinePostContentType(postEl);
                        const useSmartFetch = settings.copier_useSmartLink && contentType === 'standard';
                        const fetcher = useSmartFetch ? this.fetchPermalinkInBackground(postEl) : Promise.resolve(this.getPermalinkDirectlyFromElement(postEl));
                        const result = await fetcher || { url: null, method: 'unknown_failure' };
                        const { url: dataToCopy, method } = result;

                        if (dataToCopy) {
                            GM_setClipboard(dataToCopy);
                            const linkHTML = `<a href="${dataToCopy}" target="_blank" rel="noopener noreferrer">${dataToCopy}</a>`;
                            const successMessage = T.copier_notificationPermalinkCopied.replace('{url}', linkHTML).replace(/\n/g, '<br>');
                            this.app.modules.toastNotifier.show(successMessage, 'success', 5000);
                            await this.animateButtonFeedback(button, 'success', originalContent);
                        } else {
                            const errorKey = method.includes('no_source') ? 'copier_notificationErrorNoSourceUrl'
                                           : method.includes('timeout') ? 'copier_notificationErrorTimeout'
                                           : 'copier_notificationErrorGeneric';
                            this.app.modules.toastNotifier.show(T[errorKey], 'failure');
                            await this.animateButtonFeedback(button, 'failure', originalContent);
                        }
                    } catch (error) {
                        console.error(`${this.app.config.LOG_PREFIX} [Tools] Error:`, error);
                        this.app.modules.toastNotifier.show(T.copier_notificationErrorGeneric, 'failure');
                        await this.animateButtonFeedback(button, 'failure', originalContent);
                    } finally {
                        this.isProcessingClick = false;
                    }
                },

                // --- Feature 2: Smart Copy Content Logic ---
                async handleCopyContentClick(event, button) {
                    if (this.isProcessingClick) return;
                    event.preventDefault(); event.stopPropagation();
                    
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const postEl = button.closest(this.app.config.SELECTORS.GLOBAL.POST_CONTAINER);
                    const T = this.app.state.T;

                    this.isProcessingClick = true;
                    button.style.pointerEvents = 'none';
                    const originalContent = button.innerHTML;
                    
                    const iconWrapper = button.querySelector('.gm-icon-wrapper');
                    if (iconWrapper) iconWrapper.innerHTML = this.icons.processing;

                    try {
                        const text = await this.extractPostMetadata(postEl, { includeOrder: false });
                        
                        if (text) {
                            GM_setClipboard(text);
                            this.app.modules.toastNotifier.show(T.copier_copyContentSuccess, 'success');
                            await this.animateButtonFeedback(button, 'success', originalContent);
                        } else {
                            this.app.modules.toastNotifier.show(T.copier_notificationContentNotFound, 'failure');
                            await this.animateButtonFeedback(button, 'failure', originalContent);
                        }
                    } catch (error) {
                        console.error(`${this.app.config.LOG_PREFIX} [Tools] Copy Content Error:`, error);
                        this.app.modules.toastNotifier.show(T.copier_copyContentFailed, 'failure');
                        await this.animateButtonFeedback(button, 'failure', originalContent);
                    } finally {
                        this.isProcessingClick = false;
                    }
                },

                // --- Core Text Extraction Logic (Metadata + Topology) ---
                async extractPostMetadata(postEl, options = {}) {
                    if (!postEl) return null;
                    const settings = this.app.state.settings;
                    const T = this.app.state.T;
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    
                    // --- 1. Define Tracking Parameters to Remove (US > TW > JP Optimized) ---
                    const trackingParams = [
                        // [Global/US] Meta Ecosystem
                        'fb_content_id', 'encrypted_payload', 'channel_type',
                        'fbclid', 'ref', 'ref_id', 'h', '__cft__', '__tn__', '__eep__',
                        'igsh', 'xmt',
                        
                        // [Global/US] Google & YouTube
                        'si', 'feature',
                        'gclid', 'gclsrc', 'dclid', '_ga', '_gl',
                        'srsltid', 'gbraid', 'wbraid',
                        
                        // [US] X (Twitter) & Reddit
                        's', 't', 'ref_src', 'twclid',
                        'share_id', 'ref_campaign', 'ref_source',
                        
                        // [US/Global] E-Commerce
                        'ref_', 'pf_rd_r', 'pf_rd_p', 'pf_rd_m', // Amazon
                        'mkcid', 'mkrid', '_trkparms', 'ssuid', // eBay
                        'shpxid', // Shopify
                        
                        // [TW/JP] Regional
                        'ldtag_cl', // LINE
                        'smtt', 'is_from_login', 'stm_source', 'stm_medium', // Shopee
                        'yj_r', '_ly_c', '_ly_r', // Yahoo JP
                        'scid', // Rakuten
                        
                        // [Global] Social & Generic
                        'tt_from', 'tt_medium', 'tt_content', '_r', '_t', // TikTok
                        'li_fat_id', 'trk', // LinkedIn
                        'epik', 'pp', // Pinterest
                        'sc_cid', 'snap_campaign_id', // Snapchat
                        'mc_cid', 'mc_eid', 'mkt_tok', // Mailchimp/Marketo
                        '_hsenc', '_hsmi', // HubSpot
                        'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id',
                        'cjevent', 'msclkid'
                    ];

                    // Helper: Sanitize URL object in place
                    const sanitizeUrlParams = (urlObj) => {
                        const keysToDelete = [];
                        urlObj.searchParams.forEach((_, key) => {
                            const shouldDelete = trackingParams.some(tp => key === tp || key.startsWith(`${tp}[`));
                            if (shouldDelete) keysToDelete.push(key);
                        });
                        keysToDelete.forEach(k => urlObj.searchParams.delete(k));
                        return keysToDelete.length > 0;
                    };

                    // --- NEW Helper: Smart Text Extraction (Distinguishes Lines vs Paragraphs) ---
                    const getFormattedText = (root) => {
                        let text = '';
                        if (!root) return text;

                        // Utility to ensure string ends with specific count of newlines
                        const ensureNewlines = (count) => {
                            if (text.length === 0) return; 
                            let current = 0;
                            for (let i = text.length - 1; i >= 0; i--) {
                                if (text[i] === '\n') current++;
                                else break;
                            }
                            while (current < count) {
                                text += '\n';
                                current++;
                            }
                        };

                        const isBlock = (node) => {
                            const tag = node.tagName?.toUpperCase();
                            return ['DIV', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI', 'BLOCKQUOTE', 'SECTION', 'ARTICLE'].includes(tag);
                        };

                        const hasBlockChildren = (node) => {
                            for (let child of node.children) {
                                if (isBlock(child)) return true;
                            }
                            return false;
                        };

                        const traverse = (node) => {
                            if (node.nodeType === Node.TEXT_NODE) {
                                text += node.textContent;
                            } else if (node.nodeType === Node.ELEMENT_NODE) {
                                if (node.getAttribute('aria-hidden') === 'true' || node.style.display === 'none') return;

                                const isBlockElem = isBlock(node);
                                const isContainer = isBlockElem && hasBlockChildren(node);
                                
                                if (isBlockElem) ensureNewlines(1);

                                if (node.tagName === 'BR') {
                                    text += '\n';
                                } else {
                                    node.childNodes.forEach(traverse);
                                }

                                if (isBlockElem) {
                                    if (isContainer) ensureNewlines(2);
                                    else ensureNewlines(1);
                                }
                            }
                        };

                        traverse(root);
                        return text.trim();
                    };

                    // Define separator
                    const SECTION_SEPARATOR = options.isBatch ? '\n---\n' : '\n-----------------------------------\n';

                    const contentContainer = postEl.querySelector(C_TOOLS.CONTENT_BODY);

                    // --- 2. Expand Content (Read More) ---
                    const expandKeywords = this.app.config.TEXT_EXPANDER.TARGETS;
                    if (contentContainer) {
                        const expandBtn = Array.from(contentContainer.querySelectorAll(C_TOOLS.EXPAND_BTN))
                            .find(btn => expandKeywords.some(kw => btn.textContent.trim().includes(kw)) && btn.offsetParent !== null);
                        if (expandBtn) {
                            await new Promise((resolve) => {
                                expandBtn.click();
                                setTimeout(resolve, 150);
                            });
                        }
                    }

                    // --- 3. Build Metadata Parts ---
                    const parts = [];

                    // [Meta] Post Link
                    if (settings.copy_includeMetadata && settings.copy_meta_url) {
                        let linkUrl = null;
                        if (!options.forceRawLink && settings.copier_useSmartLink) {
                            const contentType = this.determinePostContentType(postEl);
                            if (contentType === 'standard') {
                                const result = await this.fetchPermalinkInBackground(postEl);
                                if (result && result.url) linkUrl = result.url;
                            }
                        }
                        if (!linkUrl) {
                            const direct = this.getPermalinkDirectlyFromElement(postEl);
                            linkUrl = direct.url;
                        }
                        if (linkUrl) parts.push(linkUrl + '\n');
                    }

                    // [Meta] Order & Author
                    if (settings.copy_includeMetadata) {
                        let headerLine = '';
                        if (options.includeOrder) {
                            const order = postEl.getAttribute('aria-posinset');
                            if (order) headerLine += `[#${order}] `;
                        }
                        if (settings.copy_meta_author_name) {
                            const authorEl = postEl.querySelector('div[data-ad-rendering-role="profile_name"] h2 strong a, div[data-ad-rendering-role="profile_name"] h2 a');
                            if (authorEl) headerLine += authorEl.textContent.trim();
                        }
                        if (headerLine) parts.push(headerLine);
                    }

                    // [Meta] Date & Stats & Image Count & Video Duration
                    if (settings.copy_includeMetadata) {
                        let infoLine = '';
                        
                        // Date
                        if (settings.copy_meta_date) {
                            const timeLink = this.findTimestampLink(postEl);
                            let dateText = '';
                            if (timeLink) {
                                dateText = timeLink.getAttribute('aria-label');
                                if (!dateText) dateText = timeLink.textContent.trim();
                                if (dateText && dateText.length > 30 && !/\d/.test(dateText)) dateText = '';
                            }
                            if (dateText) infoLine += dateText;
                        }

                        // Stats (Interactions)
                        if (settings.copy_meta_stats) {
                            const stats = [];
                            const toolbar = postEl.querySelector('[role="toolbar"]');
                            if (toolbar) {
                                const directContainer = toolbar.parentElement;
                                const footerArea = toolbar.closest('div[role="article"] > div > div > div > div > div') || directContainer;
                                const unitGroup = '[KkMm萬億万\\u842c\\u5104\\u4e07]';
                                const numberPatternStr = `[\\d,.]+\\s*(${unitGroup})?(?:人)?`;
                                const numberRegex = new RegExp(numberPatternStr);

                                // Total Reactions
                                let totalReactionText = '';
                                const reactionKeywords = ['All reactions:', '所有心情:', 'すべてのリアクション:', 'All reactions'];
                                const findTotalByLabel = (container) => {
                                    if (!container) return null;
                                    const candidates = Array.from(container.children);
                                    const label = candidates.find(el => reactionKeywords.some(kw => el.textContent.includes(kw)));
                                    if (label && label.nextElementSibling) return label.nextElementSibling.textContent.trim();
                                    return null;
                                };
                                totalReactionText = findTotalByLabel(directContainer) || findTotalByLabel(footerArea);
                                if (!totalReactionText && footerArea) {
                                    const reactionIcon = footerArea.querySelector('[aria-label*="reaction"], [aria-label*="心情"], [aria-label*="リアクション"]');
                                    if (reactionIcon && !reactionIcon.closest('div[role="article"] div[role="article"]')) {
                                        const containerText = reactionIcon.closest('div')?.textContent || '';
                                        const m = containerText.match(numberRegex); 
                                        if (m) totalReactionText = m[0].replace(/\s+|人/g, '');
                                    }
                                }

                                // Detailed Reactions
                                const detailedStats = [];
                                if (settings.copy_meta_stats_detailed && footerArea) {
                                    const reactionButtons = footerArea.querySelectorAll('[aria-label]');
                                    const reactionTerms = ['Like', 'Love', 'Care', 'Haha', 'Wow', 'Sad', 'Angry', '讚', '大心', '加油', '哈', '哇', '嗚', '怒', '超いいね!', 'いいね!', '大切だね', 'うけるね', 'すごいね', '悲しいね', 'ひどいね'];
                                    const reactionRegex = new RegExp(`(${reactionTerms.join('|')})[::]\\s*(${numberPatternStr})`, 'i');
                                    const emojiMap = { 'like': '👍', '讚': '👍', 'love': '❤️', '大心': '❤️', 'care': '🫂', '加油': '🫂', 'haha': '😆', '哈': '😆', 'wow': '😮', '哇': '😮', 'sad': '😢', '嗚': '😢', 'angry': '😡', '怒': '😡', 'いいね!': '👍', '超いいね!': '❤️', '大切だね': '🫂', 'うけるね': '😆', 'すごいね': '😮', '悲しいね': '😢', 'ひどいね': '😡' };
                                    reactionButtons.forEach(btn => {
                                        const label = btn.getAttribute('aria-label');
                                        if (!label) return;
                                        const match = label.match(reactionRegex);
                                        if (match) {
                                            const type = match[1]; 
                                            const count = match[2].replace(/\s+|人/g, ''); 
                                            let emoji = emojiMap[type] || emojiMap[type.toLowerCase()];
                                            if (emoji) detailedStats.push(`${emoji} ${count}`);
                                        }
                                    });
                                }
                                if (totalReactionText && settings.copy_meta_stats_total) {
                                    let rStr = `${totalReactionText} ${T.stats_label_reaction}`;
                                    if (detailedStats.length > 0) rStr += ` (${detailedStats.join(' | ')})`;
                                    stats.push(rStr);
                                } else if (detailedStats.length > 0) stats.push(detailedStats.join(' | '));

                                // Comments & Shares
                                if (footerArea) {
                                    const icons = Array.from(footerArea.querySelectorAll('i[data-visualcompletion="css-img"]'));
                                    let commentCount = '', shareCount = '';
                                    const extractCountFromIcon = (icon) => {
                                        let current = icon.parentElement;
                                        for (let i = 0; i < 4; i++) {
                                            if (!current) break;
                                            const text = current.textContent.trim();
                                            if (text && new RegExp(`^${numberPatternStr}$`).test(text)) return text.replace(/\s+|人/g, '');
                                            const m = text.match(numberRegex); if (m) return m[0].replace(/\s+|人/g, '');
                                            const numberSpan = current.querySelector('span:not(:has(*))');
                                            if (numberSpan) { const mSpan = numberSpan.textContent.trim().match(numberRegex); if (mSpan) return mSpan[0].replace(/\s+|人/g, ''); }
                                            current = current.parentElement;
                                        } return null;
                                    };
                                    for (const icon of icons) {
                                        const bgPos = icon.style.backgroundPosition; if (!bgPos) continue;
                                        const match = bgPos.match(/0px\s+(-?\d+)px/); if (!match) continue;
                                        const yPos = parseInt(match[1], 10);
                                        if (Math.abs(yPos - (-1037)) < 5 && !commentCount) commentCount = extractCountFromIcon(icon);
                                        else if (Math.abs(yPos - (-1054)) < 5 && !shareCount) shareCount = extractCountFromIcon(icon);
                                    }
                                    if (!commentCount || !shareCount) {
                                        const footerButtons = Array.from(footerArea.querySelectorAll('[role="button"]'));
                                        for (const btn of footerButtons) {
                                            if (btn.closest('div[role="article"] div[role="article"]')) continue;
                                            if (btn.hasAttribute('aria-label') && /Like|Love|讚|怒|いいね/.test(btn.getAttribute('aria-label'))) continue;
                                            const txt = btn.textContent.trim();
                                            if (txt && new RegExp(`^${numberPatternStr}$`).test(txt)) { if (!commentCount) commentCount = txt.replace(/\s+|人/g, ''); else if (!shareCount) shareCount = txt.replace(/\s+|人/g, ''); }
                                        }
                                    }
                                    if (commentCount) stats.push(`💬 ${commentCount}`);
                                    if (shareCount) stats.push(`↗️ ${shareCount}`);
                                }
                            }
                            if (stats.length > 0) infoLine += (infoLine ? ' • ' : '') + stats.join(' | ');
                        }

                        // --- Feature: Image Count (w/ +N fix) ---
                        if (settings.copy_meta_image_count) {
                            const uniqueImageIds = new Set();
                            let extraHiddenImages = 0;
                            const imageLinks = postEl.querySelectorAll('a[href*="/photo/"][href*="fbid="], a[href*="photo.php"][href*="fbid="]');
                            imageLinks.forEach(link => {
                                if (link.querySelector('img')) {
                                    const href = link.getAttribute('href');
                                    const fbidMatch = href.match(/fbid=(\d+)/);
                                    if (fbidMatch) uniqueImageIds.add(fbidMatch[1]);
                                    const candidates = link.querySelectorAll('div');
                                    for (const cand of candidates) {
                                        const txt = cand.textContent.trim();
                                        if (/^\+\d+$/.test(txt)) {
                                            const num = parseInt(txt.substring(1), 10);
                                            if (!isNaN(num)) extraHiddenImages = num;
                                            break; 
                                        }
                                    }
                                }
                            });
                            const totalImages = uniqueImageIds.size + extraHiddenImages - (extraHiddenImages > 0 ? 1 : 0);
                            if (totalImages > 0) {
                                const label = T.image_count_label || 'Images';
                                infoLine += (infoLine ? ' • ' : '') + `[${label}: ${totalImages}]`;
                            }
                        }

                        // --- Feature: Video/Reel Duration ---
                        if (settings.copy_meta_video_duration) {
                            let duration = null;
                            let typeLabel = T.video_duration_label || 'Video'; 

                            const reelLink = postEl.querySelector('a[href*="/reel/"]');
                            const videoLink = postEl.querySelector('a[href*="/videos/"], a[href*="/watch/"]');
                            if (reelLink) typeLabel = T.label_reel || 'Reel';
                            else if (videoLink) typeLabel = T.label_video || 'Video';

                            const candidates = Array.from(postEl.querySelectorAll('div, span'));
                            
                            // Strategy A: "Current / Total" (Player UI)
                            const playerTimePattern = /(\d+(?::\d+)+)\s*\/\s*(\d+(?::\d+)+)/;
                            for (const el of candidates) {
                                if (el.textContent.includes('/')) {
                                    const m = el.textContent.match(playerTimePattern);
                                    if (m) { duration = m[2]; break; }
                                }
                            }

                            // Strategy B: Standalone Badge (Thumbnail)
                            if (!duration && (reelLink || videoLink)) {
                                const badgePattern = /^(\d+(?::\d+)+)$/;
                                for (const el of candidates) {
                                    const txt = el.textContent.trim();
                                    if (el.closest(C_TOOLS.CONTENT_BODY)) continue;
                                    if (badgePattern.test(txt)) { duration = txt; break; }
                                }
                            }

                            if (duration) {
                                infoLine += (infoLine ? ' • ' : '') + `[${typeLabel}: ${duration}]`;
                            }
                        }
                        
                        if (infoLine) parts.push(infoLine);
                    }

                    if (parts.length > 0) parts.push(SECTION_SEPARATOR);

                    // --- 4. Post Content (Topology Extraction & Sanitization) ---
                    if (contentContainer) {
                        let targetContainer = contentContainer.cloneNode(true);

                        // Feature: Emoji Restoration
                        if (settings.copier_includeEmojis) {
                            const images = targetContainer.querySelectorAll('img[src*="emoji"][alt], img[alt]');
                            images.forEach(img => {
                                const src = img.getAttribute('src') || '';
                                const alt = img.getAttribute('alt');
                                if (alt && (src.includes('emoji') || img.className.includes('emoji') || src.includes('fbcdn.net'))) {
                                    img.replaceWith(document.createTextNode(alt));
                                }
                            });
                        }

                        // Feature: Link Expansion, Sanitization, and Hashtag/Mention Handling
                        const links = targetContainer.querySelectorAll('a[href]');

                        links.forEach(link => {
                            const href = link.getAttribute('href');
                            if (!href) return;

                            let fullUrl = href;
                            
                            // Decode Shim
                            if (href.includes('l.facebook.com/l.php')) {
                                try {
                                    const urlObj = new URL(href, window.location.origin);
                                    const uParam = urlObj.searchParams.get('u');
                                    if (uParam) fullUrl = decodeURIComponent(uParam);
                                } catch (e) {}
                            }

                            // Sanitize Params
                            try {
                                const urlObj = new URL(fullUrl, window.location.origin); 
                                const changed = sanitizeUrlParams(urlObj);
                                fullUrl = urlObj.href; 
                            } catch (e) {}

                            // Hashtag Decode
                            const isHashtag = href.includes('/hashtag/');
                            if (isHashtag) {
                                try {
                                    fullUrl = decodeURIComponent(fullUrl);
                                } catch (e) {}
                            }

                            // --- [NEW Logic] Smart "Best of Both Worlds" Formatting ---
                            const originalText = link.textContent.trim();
                            const isUrlText = /^(https?:\/\/|www\.|[a-z0-9.-]+\.[a-z]{2,})/i.test(originalText) || originalText.includes('...');
                            const isMention = !isHashtag && !isUrlText;

                            if (isHashtag) {
                                if (settings.copier_expandHashtags) {
                                    link.textContent = `${originalText} (${fullUrl})`;
                                }
                                return;
                            }

                            if (isMention) {
                                if (settings.copier_expandMentions) {
                                    link.textContent = `${originalText} (${fullUrl})`;
                                }
                                return;
                            }

                            link.textContent = fullUrl;
                        });

                        // [FIX] Use local getFormattedText to handle block lines correctly (Smart Spacing)
                        const bodyText = getFormattedText(targetContainer);
                        if (bodyText) parts.push(bodyText);
                    } else {
                        parts.push('[No Text Content]');
                    }

                    // --- 5. Link Preview ---
                    if (settings.copy_includeMetadata && settings.copy_meta_link_preview) {
                        // [FIX] REMOVED [target="_blank"] selector to support internal FB links (like Groups)
                        const previewLinks = Array.from(postEl.querySelectorAll('a[role="link"]'));
                        
                        // Strategy 1: Standard External Link (data-ad-rendering-role)
                        let previewLink = previewLinks.find(a => 
                            a.querySelector('[data-ad-rendering-role="title"]') && 
                            !a.closest(C_TOOLS.CONTENT_BODY)
                        );

                        // Strategy 2 (Internal Entity): Groups/Events without standard attributes
                        // Look for specific visual clues of a Facebook Card (e.g. line-clamp style on title)
                        if (!previewLink) {
                            previewLink = previewLinks.find(a => {
                                if (a.closest(C_TOOLS.CONTENT_BODY)) return false;
                                const hasStyledTitle = a.querySelector('[style*="webkit-line-clamp"]');
                                return hasStyledTitle;
                            });
                        }

                        if (previewLink) {
                            parts.push(SECTION_SEPARATOR);
                            
                            // Extract Title (Try standard role, then styled text)
                            let title = previewLink.querySelector('[data-ad-rendering-role="title"]')?.textContent.trim();
                            if (!title) {
                                const styledTitle = previewLink.querySelector('[style*="webkit-line-clamp"]');
                                if (styledTitle) title = styledTitle.textContent.trim();
                            }

                            // Extract Meta/Description
                            let meta = previewLink.querySelector('[data-ad-rendering-role="meta"]')?.textContent.trim();
                            if (!meta && title) {
                                // Fallback: Try to find text nodes that are NOT the title
                                const candidates = Array.from(previewLink.querySelectorAll('span'));
                                for (const span of candidates) {
                                    const txt = span.textContent.trim();
                                    if (txt && txt !== title && txt.length > 2 && !txt.includes('http')) {
                                        meta = txt;
                                        break; 
                                    }
                                }
                            }

                            const desc = previewLink.querySelector('[data-ad-rendering-role="description"]')?.textContent.trim();
                            
                            let href = previewLink.href;
                            
                            // Clean the preview link
                            try {
                                const urlObj = new URL(href);
                                if (urlObj.hostname.includes('facebook.com') && urlObj.searchParams.has('u')) {
                                    href = decodeURIComponent(urlObj.searchParams.get('u'));
                                }
                                // Apply Sanitization
                                const cleanUrlObj = new URL(href, window.location.origin);
                                sanitizeUrlParams(cleanUrlObj);
                                href = cleanUrlObj.toString();
                            } catch(e) {}

                            if (title) parts.push(`➤ ${T.preview_label_title}: ${title}`);
                            if (meta) parts.push(`➤ ${T.preview_label_source}: ${meta}`);
                            if (desc) parts.push(`➤ ${T.preview_label_desc}: ${desc}`);
                            parts.push(`➤ ${T.preview_label_link}: ${href}`);
                        }
                    }

                    return parts.join('\n');
                },

                extractTextByTopology(container) {
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    
                    // Feature Detection: Background Image Post
                    // Detect specific style pattern: background-image present + centered text usually
                    const hasBgImage = container.querySelector('div[style*="background-image"]');
                    
                    if (hasBgImage) {
                        // Strategy: Get all potential text blocks, then deduplicate content.
                        // Background posts often render text twice: once for visual, once for a11y (aria-hidden).
                        // We cannot rely on 'x6s0dn4' class, so we use content equality check.
                        
                        const candidates = Array.from(container.querySelectorAll('div[dir="auto"], div[style*="text-align: center"]'));
                        // Filter for leaf-ish nodes that actually contain text
                        const textBlocks = candidates.filter(el => {
                            // Must have text
                            if (!el.innerText.trim()) return false;
                            // Must not contain other block candidates (ensure it's a leaf node)
                            return !el.querySelector('div[dir="auto"]');
                        });

                        const uniqueText = [];
                        const seenContent = new Set();

                        for (const block of textBlocks) {
                            const text = block.innerText.trim();
                            if (!text) continue;
                            if (seenContent.has(text)) continue; // Skip duplicates
                            
                            seenContent.add(text);
                            uniqueText.push(text);
                        }

                        if (uniqueText.length > 0) {
                            return uniqueText.join('\n');
                        }
                        
                        // Fallback
                        return container.innerText.trim();
                    }

                    // Standard Topology Extraction (for normal posts)
                    const rawBlocks = container.querySelectorAll(C_TOOLS.TEXT_BLOCKS);
                    const leafBlocks = Array.from(rawBlocks).filter(el => {
                        return el.querySelectorAll(C_TOOLS.TEXT_BLOCKS).length === 0 && el.innerText.trim().length > 0;
                    });

                    if (leafBlocks.length === 0) return container.innerText.trim();

                    let finalString = leafBlocks[0].innerText.trim();
                    for (let i = 1; i < leafBlocks.length; i++) {
                        const prevBlock = leafBlocks[i-1];
                        const currBlock = leafBlocks[i];
                        const currText = currBlock.innerText.trim();
                        
                        const isSibling = currBlock.parentElement === prevBlock.parentElement;
                        const separator = isSibling ? '\n' : '\n\n';
                        
                        finalString += separator + currText;
                    }
                    return finalString;
                },

                // --- Shared Helpers ---
                async animateButtonFeedback(button, status, originalContent) {
                    const T = this.app.state.T;
                    const settings = this.app.state.settings;
                    const iconWrapper = button.querySelector('.gm-icon-wrapper');
                    if (iconWrapper) iconWrapper.innerHTML = status === 'success' ? this.icons.success : this.icons.failure;
                    
                    button.style.backgroundColor = status === 'success' ? 'var(--positive-background)' : 'var(--negative-background)';
                    await this.app.utils.delay(1200);
                    button.style.pointerEvents = 'auto';
                    button.style.backgroundColor = 'transparent';
                    button.innerHTML = originalContent;
                },

                // --- Injection Logic ---
                reEvaluateAllButtons() {
                    this.cleanupButtons();
                    this.scanNodeForPosts(document.body);
                },
                cleanupButtons() {
                    const C = this.app.config.SELECTORS.POST_TOOLS;
                    document.querySelectorAll(`[data-${C.PROCESSED_MARKER}]`).forEach(el => {
                        el.removeAttribute(`data-${C.PROCESSED_MARKER}`);
                        el.querySelectorAll(`.gm-tools-wrapper`).forEach(wrapper => wrapper.remove());
                    });
                },
                scanNodeForPosts(node) {
                    if (node.nodeType !== Node.ELEMENT_NODE) return;
                    const C_GLOBAL = this.app.config.SELECTORS.GLOBAL;
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const SCAN_TARGETS = `${C_GLOBAL.POST_CONTAINER}, ${C_GLOBAL.DIALOG}`;
                    const targets = node.matches(SCAN_TARGETS) ? [node] : [];
                    targets.push(...node.querySelectorAll(SCAN_TARGETS));

                    new Set(targets).forEach(target => {
                        if (target.hasAttribute(`data-${C_TOOLS.PROCESSED_MARKER}`)) return;
                        if (target.matches(C_GLOBAL.POST_CONTAINER) && !target.parentElement.closest(C_GLOBAL.POST_CONTAINER)) {
                            const headerEl = target.querySelector(C_TOOLS.FEED_POST_HEADER);
                            if (headerEl) this.addButtonsToPost(target, headerEl, false);
                        } else if (target.matches(C_GLOBAL.DIALOG)) {
                            this.processDialogPost(target);
                        }
                    });
                },
                processDialogPost(dialogEl) {
                    this.isModalOpening = true;
                    let observer;
                    const C_GLOBAL = this.app.config.SELECTORS.GLOBAL;
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const cleanup = () => {
                        if (observer) observer.disconnect();
                        clearTimeout(timeoutId);
                        setTimeout(() => { this.isModalOpening = false; }, 200);
                    };
                    const timeoutId = setTimeout(cleanup, 3000);
                    const findAndProcessHeader = () => {
                        const postEl = dialogEl.querySelector(C_GLOBAL.POST_CONTAINER);
                        if (postEl) {
                            const headerEl = postEl.querySelector(C_TOOLS.DIALOG_POST_HEADER);
                            if (headerEl) {
                                this.addButtonsToPost(postEl, headerEl, true);
                                cleanup();
                                return true;
                            }
                        }
                        return false;
                    };
                    if (findAndProcessHeader()) return;
                    observer = new MutationObserver(findAndProcessHeader);
                    observer.observe(dialogEl, { childList: true, subtree: true });
                },
                addButtonsToPost(postEl, headerEl, isDialog = false) {
                    const settings = this.app.state.settings;
                    const C = this.app.config.SELECTORS.POST_TOOLS;
                    
                    if (!settings.permalinkCopierEnabled && !settings.enableCopyContentButton) return;
                    if (!headerEl?.parentElement || headerEl.parentElement.querySelector(`.gm-tools-wrapper`)) return;

                    postEl.setAttribute(`data-${C.PROCESSED_MARKER}`, 'true');
                    const insertionPoint = headerEl.parentElement;
                    // Ensure flexible layout to accommodate new buttons
                    Object.assign(insertionPoint.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' });

                    const wrapper = this.app.utils.createStyledElement('div', { display: 'flex', alignItems: 'center' }, { className: 'gm-tools-wrapper' });
                    if (isDialog) wrapper.style.marginRight = '16px';

                    // 1. Copy Content Button (Left) - Now Smart
                    if (settings.enableCopyContentButton) {
                        const copyBtn = this.createButton('copy-content', this.icons.copy, this.app.state.T.copier_copyContent);
                        wrapper.appendChild(copyBtn);
                    }

                    // 2. Permalink Button (Right) - Icon Only
                    if (settings.permalinkCopierEnabled) {
                        const contentType = this.determinePostContentType(postEl);
                        const isSmart = settings.copier_useSmartLink && contentType === 'standard';
                        const icon = isSmart ? this.icons.smart : this.icons.direct;
                        const title = isSmart ? this.app.state.T.copier_fetchPermalinkSmart : this.app.state.T.copier_fetchPermalinkDirect;
                        const permalinkBtn = this.createButton('permalink', icon, title);
                        wrapper.appendChild(permalinkBtn);
                    }

                    insertionPoint.appendChild(wrapper);
                },
                createButton(action, svgIcon, title) {
                    const C = this.app.config.SELECTORS.POST_TOOLS;
                    
                    const clickHandler = (e) => {
                        if (action === 'permalink') {
                            this.handlePermalinkClick(e, e.currentTarget);
                        } else if (action === 'copy-content') {
                            this.handleCopyContentClick(e, e.currentTarget);
                        }
                    };

                    return this.app.utils.createStyledElement('div', {
                        cursor: 'pointer', backgroundColor: 'transparent', color: 'var(--secondary-text)',
                        lineHeight: '1', marginLeft: '8px', border: '1px solid var(--media-inner-border)',
                        transition: 'all 0.15s ease-out', userSelect: 'none',
                        width: '28px', height: '28px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center'
                    }, {
                        className: C.BUTTON_CLASS,
                        role: 'button',
                        tabIndex: 0,
                        title: title,
                        'data-action': action,
                        innerHTML: `<span class="gm-icon-wrapper">${svgIcon}</span>`,
                        on: {
                            click: clickHandler,
                            mouseover: (e) => { if (e.currentTarget.style.pointerEvents !== 'none') e.currentTarget.style.backgroundColor = 'var(--hover-overlay)'; },
                            mouseout: (e) => { if (e.currentTarget.style.pointerEvents !== 'none') e.currentTarget.style.backgroundColor = 'transparent'; }
                        }
                    });
                },

                // --- Legacy Logic: Determine URL Type ---
                determinePostContentType(postEl) {
                    if (!postEl) return 'unknown';
                    const timestampUrl = this.getPermalinkFromTimestamp(postEl);
                    if (!timestampUrl) {
                        const hasReel = postEl.querySelector('a[href*="/reel/"], video');
                        if (hasReel) return 'reel';
                        const hasVideo = postEl.querySelector('a[href*="/videos/"], a[href*="/watch/"]');
                        if (hasVideo) return 'video';
                        return 'standard';
                    }
                    try {
                        const { pathname } = new URL(timestampUrl);
                        if (pathname.includes('/reel/')) return 'reel';
                        if (pathname.includes('/videos/') || pathname.includes('/watch/')) return 'video';
                        if (pathname.includes('/events/')) return 'event';
                        return 'standard';
                    } catch (e) { return 'standard'; }
                },
                findTimestampLink(postEl) {
                    if (!postEl) return null;
                    const candidates = Array.from(postEl.querySelectorAll('a[role="link"]'));
                    for (const link of candidates) {
                        const href = link.href || '';
                        const label = link.getAttribute('aria-label') || '';
                        const text = link.textContent || '';
                        const hasTimeReference = /\d/.test(label) || /[0-9dhmw]/.test(text);
                        const hasPostReference = href.includes('/posts/') || href.includes('/videos/') || href.includes('/reel/') || href.includes('fbid=') || href.includes('story_fbid=') || href.includes('/events/');
                        if (hasTimeReference && hasPostReference) return link;
                    }
                    return null;
                },
                getPermalinkFromTimestamp(postEl) {
                    try {
                        const timeLink = this.findTimestampLink(postEl);
                        if (!timeLink?.href) return null;
                        const rawUrl = new URL(timeLink.href, window.location.origin);
                        const searchParams = rawUrl.searchParams;
                        const basePath = `${rawUrl.protocol}//${rawUrl.host}${rawUrl.pathname}`;
                        if (rawUrl.pathname.includes('/watch/')) {
                            const videoId = searchParams.get('v');
                            if (videoId) return `${basePath}?v=${videoId}`;
                        } else if (rawUrl.pathname.includes('permalink.php') || rawUrl.search.includes('story_fbid=')) {
                            const storyFbid = searchParams.get('story_fbid');
                            const id = searchParams.get('id');
                            if (storyFbid && id) return `${basePath}?story_fbid=${storyFbid}&id=${id}`;
                        } else if (rawUrl.pathname.includes('photo.php') || searchParams.has('fbid')) {
                            const fbid = searchParams.get('fbid');
                            const setId = searchParams.get('set');
                            if (fbid && setId) return `${basePath}?fbid=${fbid}&set=${setId}`;
                            if (fbid) return `${basePath}?fbid=${fbid}`;
                        } else if (rawUrl.search.includes('multi_permalinks=')) {
                            const permalinkId = searchParams.get('multi_permalinks');
                            return basePath.replace(/\/$/, '') + `/posts/${permalinkId}/`;
                        }
                        return basePath;
                    } catch (e) { return null; }
                },
                getAnyPostUrl(postEl) {
                    if (!postEl) return null;
                    const linkSelectors = ['a[href*="/photo/"]', 'a[href*="fbid="]', 'a[href*="/reel/"]', 'a[href*="/videos/"]', 'a[href*="/watch/"]', 'a[href*="/events/"]'];
                    for (const selector of linkSelectors) {
                        const linkEl = postEl.querySelector(selector);
                        if (linkEl?.href && !linkEl.href.includes('/profile.php')) return linkEl.href;
                    }
                    const timeElement = this.findTimestampLink(postEl);
                    if (timeElement?.href) return timeElement.href;
                    return null;
                },
                getSourceUrlForWorker(postEl) { return this.getPermalinkFromTimestamp(postEl) || this.getAnyPostUrl(postEl); },
                startObserver() { this.observer = new MutationObserver(mutations => mutations.forEach(m => m.addedNodes.forEach(n => this.scanNodeForPosts(n))) ); this.scanNodeForPosts(document.body); this.observer.observe(document.body, { childList: true, subtree: true }); },
                fetchPermalinkInBackground(postEl) {
                    return new Promise((resolve) => {
                        const sourceUrl = this.getSourceUrlForWorker(postEl);
                        if (!sourceUrl) { return resolve({ url: null, method: 'worker_no_source_url' }); }
                        const taskId = `fpc_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
                        const workerUrl = new URL(sourceUrl);
                        workerUrl.searchParams.set(this.app.config.WORKER_PARAM, taskId);
                        let listenerId;
                        const timeoutId = setTimeout(() => {
                            if (listenerId) GM_removeValueChangeListener(listenerId);
                            resolve({ url: null, method: 'worker_timeout' });
                        }, 8000);
                        listenerId = GM_addValueChangeListener(taskId, (name, oldVal, newVal, remote) => {
                            if (remote && typeof newVal.permalink !== 'undefined') {
                                clearTimeout(timeoutId);
                                GM_removeValueChangeListener(listenerId);
                                GM_setValue(taskId, { resolved: true }); 
                                resolve({ url: newVal.permalink, method: "worker_" + newVal.method });
                            }
                        });
                        GM_openInTab(workerUrl.href, { active: false, insert: true, setParent: true });
                    });
                },
                getPermalinkDirectlyFromElement(postEl) {
                    const dirtyUrl = this.getSourceUrlForWorker(postEl);
                    if (!dirtyUrl) return { url: null, method: 'direct_no_url' };
                    try {
                        const urlObj = new URL(dirtyUrl);
                        urlObj.search = ''; 
                        const cleanUrl = urlObj.href.replace(/\/$/, '');
                        return { url: cleanUrl, method: 'direct_cleaned' };
                    } catch (e) { return { url: null, method: 'direct_parse_error' }; }
                },
            },
        },

        async handleWorkerTask() {
            const urlParams = new URLSearchParams(window.location.search);
            const taskId = urlParams.get(this.config.WORKER_PARAM);
            if (!taskId) return;
            document.body.style.display = 'none';
            const settings = {};
            this.modules.settingsManager.definitions
                .filter(def => def.group === 'tools')
                .forEach(def => settings[def.key] = GM_getValue(def.key, def.defaultValue));
            const extractId = url => url ? (url.match(/(?:posts|videos|reel|v|story_fbid|multi_permalinks)(?:[=/].*?)(\d{15,})/)?.[1] || null) : null;
            const extractUser = url => url ? (url.match(/facebook\.com\/([a-zA-Z0-9.]+)\/(?:posts|videos|reels)\//)?.[1] || null) : null;
            const formatPermalink = info => {
                if (!info?.postId) return null;
                const format = settings.copier_permalinkFormat;
                if (format === 'shortest') return `https://fb.com/${info.postId}`;
                if (format === 'full' && info.canonicalUrl) try { const u = new URL(info.canonicalUrl); u.search = ''; return u.href.replace(/\/$/, ''); } catch (e) {}
                if (format === 'username' && info.username && info.username !== 'profile.php') return `https://www.facebook.com/${info.username}/posts/${info.postId}`;
                if (info.profileId) return `https://www.facebook.com/${info.profileId}/posts/${info.postId}`;
                return `https://www.facebook.com/posts/${info.postId}`;
            };
            const findNestedValue = (obj, key) => (obj && typeof obj === 'object') ? (key in obj ? obj[key] : Object.values(obj).reduce((acc, v) => acc ?? findNestedValue(v, key), undefined)) : undefined;
            const getFromRelay = () => { for (const script of document.querySelectorAll('script[type="application/json"]')) try { const d = JSON.parse(script.textContent); const id = findNestedValue(d, 'storyID'); if (id) { const ids = atob(id).match(/(\d+)/g); if (ids?.length >= 2) return { profileId: ids[0], postId: ids[1], method: 'relay' }; } } catch (e) {} return null; };
            const getFromHead = () => { const url = document.querySelector('link[rel="canonical"]')?.href; if (!url) return null; return { postId: extractId(url), username: extractUser(url), canonicalUrl: url, method: 'head' }; };
            const getFromBody = () => { for (const script of document.querySelectorAll('script[type="application/json"]')) try { if (!script.textContent.includes('debug_info') && !script.textContent.includes('share_fbid')) continue; const d = JSON.parse(script.textContent); const dbg = findNestedValue(d, 'debug_info'); if (dbg) { const ids = atob(dbg).match(/(\d+)/g); if (ids?.length >= 2) return { profileId: ids[0], postId: ids[1], method: 'body_debug' }; } const fbid = findNestedValue(d, 'share_fbid'); if (fbid) return { postId: fbid, method: 'body_fbid' }; } catch (e) {} return null; };
            const collectInfo = () => [getFromRelay(), getFromHead(), getFromBody()].reduce((acc, info) => ({ ...info, ...acc }), {});
            const determinePermalink = () => { const info = collectInfo(); return info.postId ? { url: formatPermalink(info), method: info.method || 'unknown' } : { url: null }; };
            const findWithRetry = async (timeout = 5000) => {
                const start = Date.now();
                while (Date.now() - start < timeout) {
                    const result = determinePermalink();
                    if (result.url) return result;
                    await this.utils.delay(350);
                }
                return { url: null, method: 'worker_timeout' };
            };
            let listenerId;
            const fallbackUrl = window.location.href.split('?')[0];
            try {
                const result = await findWithRetry();
                const permalink = result.url ?? fallbackUrl;
                const method = result.method || 'fallback';
                console.log(`${this.config.LOG_PREFIX} [Worker] Task ${taskId} completed. Method: ${method}, URL: ${permalink}`);
                GM_setValue(taskId, { permalink, method });
            } catch (error) {
                GM_setValue(taskId, { permalink: fallbackUrl, method: 'error_fallback' });
            } finally {
                const closeTimeout = setTimeout(() => { if (listenerId) GM_removeValueChangeListener(listenerId); window.close(); }, 5000);
                listenerId = GM_addValueChangeListener(taskId, (name, oldVal, newVal, remote) => {
                    if (remote && newVal.resolved) { clearTimeout(closeTimeout); GM_removeValueChangeListener(listenerId); window.close(); }
                });
            }
        },

        init() {
            const lang = navigator.language.toLowerCase();
            if (lang.startsWith('ja')) this.state.T = this.config.STRINGS.ja;
            else if (lang.startsWith('zh')) this.state.T = this.config.STRINGS['zh-TW'];
            else this.state.T = this.config.STRINGS.en;

            this.modules.interceptor.init(this);
            this.modules.historyInterceptor.init(this);
            this.modules.settingsManager.init(this);

            window.addEventListener('DOMContentLoaded', () => {
                for (const moduleName in this.modules) {
                    const module = this.modules[moduleName];
                    const isPreloaded = ['interceptor', 'historyInterceptor', 'settingsManager'].includes(moduleName);

                    if (typeof module.init === 'function' && !isPreloaded) {
                        try {
                            if (moduleName === 'floatingNavigator' && !this.state.settings.floatingNavEnabled) continue;
                            if (moduleName === 'errorRecovery' && !this.state.settings.errorRecoveryEnabled) continue;
                            if (moduleName === 'transparencyActions' && !this.state.settings.transparencyButtonsEnabled) continue;
                            if (moduleName === 'idRevealer' && !this.state.settings.idRevealerEnabled) continue;
                            if (moduleName === 'contentExpander' && !this.state.settings.expandContentEnabled) continue;
                            module.init(this);
                        } catch (error) {
                            console.error(`${this.config.LOG_PREFIX} Failed to initialize module '${moduleName}':`, error);
                        }
                    }
                }
                console.log(`${this.config.LOG_PREFIX} All modules initialized.`);
            });
        },
    };

    if (window.location.search.includes(app.config.WORKER_PARAM)) {
        window.addEventListener('DOMContentLoaded', () => app.handleWorkerTask());
    } else {
        if (app.utils.isLoggedIn()) {
            console.log(`${app.config.LOG_PREFIX} Logged-in state detected. Script terminated.`);
            return;
        }
        console.log(`${app.config.LOG_PREFIX} Logged-out state detected. Script active.`);
        app.init();
    }
})();