Facebook Login Wall Remover

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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Facebook Login Wall Remover
// @name:en      Facebook Login Wall Remover
// @name:zh-TW   Facebook 登入牆移除器
// @name:ja      Facebook ログインウォールリムーバー
// @version      0.6.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: {
                // Verified reliable strings from testing
                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, // 5 seconds
            },
            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, tied to backend logic' },
                    { selector: 'input[name="email"]', reason: 'Email/Phone input field, also tied to backend' },
                    { selector: 'a[href*="/recover/initiate"]', reason: 'Forgot Account link, a core function' },
                    { selector: 'a[href*="/login/"]', reason: 'Any link explicitly pointing to a login page' }
                ],
                LOGGED_IN: [
                    { selector: 'input[type="search"]', reason: 'Search input field in the header' },
                    { selector: 'a[href="/friends/"]', reason: 'Friends tab in main navigation' },
                    { selector: 'a[href="/watch/?ref=tab"]', reason: 'Watch tab in main navigation' },
                    { selector: 'a[href*="/groups/"]', reason: 'Groups tab in main navigation' },
                    { selector: 'a[href*="/gaming/"]', reason: 'Gaming tab in main navigation' }
                ]
            },
            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: {
                    notificationDeadlock: 'A login prompt was hidden, but the feed can no longer load new content.\n[Pro-Tip] To prevent the feed from locking, get used to opening links in a new tab (middle-click). Please reload to continue browsing.',
                    notificationSettingsReload: 'Some settings have been updated. Please reload the page for them to take full effect.',
                    resetSettings: 'Reset Settings',
                    resetSettingsConfirm: 'Are you sure you want to reset all settings to their defaults? This action cannot be undone.',
                    notificationSettingsReset: 'Settings have been reset to default. A page reload may be required.',
                    menuResetSettings: '🚨 Reset All Settings',
                    autoOpenMediaInNewTab: 'Auto-open media in new tab (prevents deadlock)',
                    showDeadlockNotification: 'Show deadlock notification',
                    hideUselessElements: 'Hide useless UI elements (for guest)',
                    hidePostStats: 'Hide post stats (Likes, Comments counts)',
                    autoUnmuteEnabled: 'Automatically unmute videos',
                    postNumberingEnabled: 'Display post order numbers on feed',
                    expandContentEnabled: 'Auto-expand post content (See more)',
                    errorRecoveryEnabled: 'Auto-reload on error page (Button detection)',
                    transparencyButtonsEnabled: 'Show Page Transparency shortcuts (Bottom-Left)',
                    idRevealerEnabled: 'Enable ID Revealer (Click Title)',
                    idRevealerTooltip: 'Click to reveal Profile ID & Info',
                    idRevealerLinkFormat: 'ID Link Format',
                    idFormatUserID: 'User ID URL (facebook.com/id)',
                    idFormatClassic: 'Classic (profile.php?id=)',
                    idFormatUsername: 'Username (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',
                    setVolumeLabel: 'Auto-unmute volume',
                    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 for posts within the current page (or all of Facebook if on the homepage).',
                    searchTooltipPhotos: 'Search for photos within the current page.',
                    searchTooltipVideos: 'Search for videos within the current page.',
                    searchTooltipReels: 'Search all of Facebook for Reels using the current page\'s name as a keyword.',
                    searchTooltipPages: 'Search all of Facebook for Pages, people, or organizations.',
                    searchTooltipPeople: 'Search all of Facebook for personal profiles.',
                    searchTooltipGroups: 'Search all of Facebook for groups.',
                    searchTooltipGlobalPosts: 'Search all of Facebook for public posts.',
                    searchTooltipGlobalVideos: 'Search all of Facebook for videos using the internal Watch search.',
                    searchTooltipEvents: 'Search all of Facebook for events using the internal Events search.',
                    searchTooltipMarketplace: 'Search all of Facebook Marketplace for item listings.',
                    searchAllContextualTooltip: 'List all {scope} on this page using Google Search',
                    navigateToContextual: 'Go to {scope} section',
                    pinToolbar: 'Pin toolbar',
                    unpinToolbar: 'Unpin toolbar',
                    shortcutWatch: 'Go to Watch',
                    shortcutEvents: 'Go to Events',
                    shortcutMarketplace: 'Go to Marketplace',
                    settingsTitle: 'Settings',
                    saveAndClose: 'Save & Close',
                    menuSettings: '⚙️ Settings',
                    keyboardNavEnabled: 'Enable keyboard navigation',
                    keyNavNextPrimary: 'Next Post (Primary)',
                    keyNavPrevPrimary: 'Previous Post (Primary)',
                    keyNavNextSecondary: 'Next Post (Secondary)',
                    keyNavPrevSecondary: 'Previous Post (Secondary)',
                    floatingNavEnabled: 'Enable floating navigation buttons',
                    floatingNavPrevTooltip: 'Previous Post',
                    floatingNavNextTooltip: 'Next Post',
                    navigationScrollAlignment: 'Scroll alignment',
                    scrollAlignmentCenter: 'Center',
                    scrollAlignmentTop: 'Top',
                    enableSmoothScrolling: 'Enable smooth scrolling',
                    continuousNavInterval: 'Continuous navigation interval',
                    wheelNavEnabled: 'Enable mouse wheel navigation',
                    wheelNavModifier: 'Wheel navigation modifier key',
                    modifierAlt: 'Alt',
                    modifierCtrl: 'Ctrl',
                    modifierShift: 'Shift',
                    modifierNone: 'None (replaces page scroll)',
                    settingsColumnGeneral: 'General',
                    settingsColumnNavigation: 'Navigation',
                    settingsColumnTools: 'Post Tools',
                    copier_enablePermalink: 'Enable Permalink Button',
                    copier_enableCopyContent: 'Enable Copy Content 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 to clipboard:\n{url}',
                    copier_notificationErrorGeneric: 'Failed to fetch permalink.',
                    copier_notificationErrorNoSourceUrl: 'Failed: Could not find a source URL.',
                    copier_notificationErrorTimeout: 'Failed: Background fetch timed out.',
                    copier_notificationContentNotFound: '❌ Content block not found.',
                    copier_menu_useSmartLink: 'Auto-Fetch Permalinks (Smart Mode)',
                    copier_menu_showButtonText: 'Show Button Text',
                    copier_menu_permalinkFormat: 'Permalink Format',
                    copier_format_full: 'Full URL (with slug)',
                    copier_format_username: 'Username + Post ID',
                    copier_format_author_id: 'Author ID + Post ID (Most Reliable)',
                    copier_format_shortest: 'Shortest (fb.com, less compatible)',
                    tooltipAds: 'Go to Ad Library (About)',
                    tooltipTransparency: 'Go to Page transparency',
                    notificationReelSearchError: 'Cannot get current page name for Reel search.',
                    copier_includeEmojis: 'Include emojis in copied text',
                    // Auto Loader & Batch Copier
                    autoLoader_batchSize: 'Batch Auto-Load Count',
                    tooltipAutoLoadStart: 'Auto-Load Posts',
                    tooltipAutoLoadStop: 'Stop Loading',
                    tooltipBatchCopy: 'Batch Copy All Posts',
                    autoLoad_status_loading: 'Loading... ({current}/{target})',
                    autoLoad_status_retrying: 'Retrying... ({count}/{max})',
                    autoLoad_status_success: 'Auto-load complete.',
                    autoLoad_status_stopped: 'Stopped by user.',
                    autoLoad_status_deadlock: 'Deadlock detected. Stopping.',
                    batchCopy_start: 'Processing {count} posts...',
                    batchCopy_success: '✅ Copied {count} posts to clipboard.',
                    batchCopy_empty: 'No posts to copy.',
                },
                'zh-TW': {
                    notificationDeadlock: '登入提示已隱藏,動態消息將無法載入新內容。\n【提示】為避免動態消息卡住,請養成用滑鼠中鍵在新分頁開啟連結的習慣。請重新整理頁面以繼續瀏覽。',
                    notificationSettingsReload: '部分設定已更新,請重新整理頁面以完全生效。',
                    resetSettings: '重設設定',
                    resetSettingsConfirm: '您確定要將所有設定重設為預設值嗎?此操作無法復原。',
                    notificationSettingsReset: '設定已重設為預設值,部分變更可能需要重新整理頁面才能生效。',
                    menuResetSettings: '🚨 重設所有設定',
                    autoOpenMediaInNewTab: '自動在新分頁開啟媒體 (防鎖定)',
                    showDeadlockNotification: '顯示頁面鎖定通知',
                    hideUselessElements: '隱藏對訪客無用的介面元素',
                    hidePostStats: '隱藏貼文統計數據 (讚數、留言數)',
                    autoUnmuteEnabled: '自動取消影片靜音',
                    postNumberingEnabled: '在動態消息上顯示貼文順序',
                    expandContentEnabled: '自動展開貼文內容 (查看更多)',
                    errorRecoveryEnabled: '錯誤頁面自動恢復 (按鈕偵測)',
                    transparencyButtonsEnabled: '顯示粉絲專頁資訊透明度捷徑按鈕 (左下角)',
                    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: '全部資訊已複製',
                    setVolumeLabel: '自動音量大小',
                    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: '⚙️ 設定',
                    keyboardNavEnabled: '啟用鍵盤導覽',
                    keyNavNextPrimary: '下一篇 (主要按鍵)',
                    keyNavPrevPrimary: '上一篇 (主要按鍵)',
                    keyNavNextSecondary: '下一篇 (次要按鍵)',
                    keyNavPrevSecondary: '上一篇 (次要按鍵)',
                    floatingNavEnabled: '啟用浮動導覽按鈕',
                    floatingNavPrevTooltip: '上一篇貼文',
                    floatingNavNextTooltip: '下一篇貼文',
                    navigationScrollAlignment: '導覽滾動對齊',
                    scrollAlignmentCenter: '置中',
                    scrollAlignmentTop: '貼齊頂部',
                    enableSmoothScrolling: '啟用平滑捲動',
                    continuousNavInterval: '連續導覽間隔時間',
                    wheelNavEnabled: '啟用滑鼠滾輪導覽',
                    wheelNavModifier: '滾輪導覽修飾鍵',
                    modifierAlt: 'Alt',
                    modifierCtrl: 'Ctrl',
                    modifierShift: 'Shift',
                    modifierNone: '無 (取代頁面捲動)',
                    settingsColumnGeneral: '一般設定',
                    settingsColumnNavigation: '導覽設定',
                    settingsColumnTools: '貼文工具',
                    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_showButtonText: '顯示按鈕文字',
                    copier_menu_permalinkFormat: '永久連結格式',
                    copier_format_full: '完整網址 (含 Slug)',
                    copier_format_username: '使用者名稱 + 貼文 ID',
                    copier_format_author_id: '作者 ID + 貼文 ID (最可靠)',
                    copier_format_shortest: '最短連結 (fb.com, 相容性較差)',
                    tooltipAds: '前往 廣告檔案庫 (關於)',
                    tooltipTransparency: '查看 粉絲專頁資訊透明度',
                    notificationReelSearchError: '無法取得目前頁面名稱以進行連續短片搜尋。',
                    copier_includeEmojis: '複製內容包含表情符號',
                    // Auto Loader & Batch Copier
                    autoLoader_batchSize: '自動載入批次數量',
                    tooltipAutoLoadStart: '自動載入貼文',
                    tooltipAutoLoadStop: '停止載入',
                    tooltipBatchCopy: '批次複製所有貼文',
                    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: '沒有貼文可複製。',
                },
                ja: {
                    notificationDeadlock: 'ログインプロンプトが非表示になりましたが、フィードは新しいコンテンツを読み込めなくなりました。\n【ヒント】フィードがロックされないように、新しいタブでリンクを開く(中央クリック)習慣を付けてください。閲覧を続けるには、このページをリロードしてください。',
                    notificationSettingsReload: '一部の設定が更新されました。完全に有効にするには、ページをリロードしてください。',
                    resetSettings: '設定をリセット',
                    resetSettingsConfirm: 'すべての設定をデフォルトにリセットしますか?この操作は元に戻せません。',
                    notificationSettingsReset: '設定がデフォルトにリセットされました。一部の変更は、ページをリロードすると有効になります。',
                    menuResetSettings: '🚨 全ての設定をリセット',
                    autoOpenMediaInNewTab: 'メディアを新しいタブで開く (デッドロック防止)',
                    showDeadlockNotification: 'デッドロック通知を表示',
                    hideUselessElements: '不要なUI要素を非表示にする(ゲスト用)',
                    hidePostStats: '投稿の統計データを非表示 (いいね!、コメント数)',
                    autoUnmuteEnabled: '動画のミュートを自動解除',
                    postNumberingEnabled: 'フィードに投稿順序番号を表示する',
                    expandContentEnabled: '投稿の内容を自動的に展開 (さらに表示)',
                    errorRecoveryEnabled: 'エラーページ自動回復 (ボタン検出)',
                    transparencyButtonsEnabled: 'ページの透明性ショートカットを表示 (左下)',
                    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: 'すべての情報をコピーしました',
                    setVolumeLabel: '自動音量',
                    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マーケットプレイス全体で商品を検索します。',
                    searchAllContextualTooltip: 'Google検索を使用して、このページのすべての{scope}を一覧表示します',
                    navigateToContextual: '{scope}セクションに移動',
                    pinToolbar: 'ツールバーを固定',
                    unpinToolbar: 'ツールバーの固定を解除',
                    shortcutWatch: 'Watchへ移動',
                    shortcutEvents: 'イベントへ移動',
                    shortcutMarketplace: 'マーケットプレイスへ移動',
                    settingsTitle: '設定',
                    saveAndClose: '保存して閉じる',
                    menuSettings: '⚙️ 設定',
                    keyboardNavEnabled: 'キーボードナビゲーションを有効にする',
                    keyNavNextPrimary: '次の投稿 (プライマリ)',
                    keyNavPrevPrimary: '前の投稿 (プライマリ)',
                    keyNavNextSecondary: '次の投稿 (セカンダリ)',
                    keyNavPrevSecondary: '前の投稿 (セカンダリ)',
                    floatingNavEnabled: 'フローティングナビゲーションボタンを有効にする',
                    floatingNavPrevTooltip: '前の投稿',
                    floatingNavNextTooltip: '次の投稿',
                    navigationScrollAlignment: 'スクロール位置',
                    scrollAlignmentCenter: '中央',
                    scrollAlignmentTop: '上部',
                    enableSmoothScrolling: 'スムーズスクロールを有効にする',
                    continuousNavInterval: '連続ナビゲーションの間隔',
                    wheelNavEnabled: 'マウスホイールナビゲーションを有効にする',
                    wheelNavModifier: 'ホイールナビゲーションの修飾キー',
                    modifierAlt: 'Alt',
                    modifierCtrl: 'Ctrl',
                    modifierShift: 'Shift',
                    modifierNone: 'なし (ページのスクロールを置き換える)',
                    settingsColumnGeneral: '一般設定',
                    settingsColumnNavigation: 'ナビゲーション設定',
                    settingsColumnTools: '投稿ツール',
                    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_showButtonText: 'ボタンテキストを表示',
                    copier_menu_permalinkFormat: '固定リンク形式',
                    copier_format_full: '完全なURL (スラグ付き)',
                    copier_format_username: 'ユーザー名 + 投稿ID',
                    copier_format_author_id: '作成者ID + 投稿ID (最も信頼性が高い)',
                    copier_format_shortest: '最短リンク (fb.com, 互換性低)',
                    tooltipAds: '広告ライブラリへ (情報)',
                    tooltipTransparency: 'ページの透明性を表示',
                    notificationReelSearchError: 'リール検索のための現在のページ名を取得できません。',
                    copier_includeEmojis: 'コピーしたテキストに絵文字を含める',
                    // Auto Loader & Batch Copier
                    autoLoader_batchSize: '自動読み込みバッチ数',
                    tooltipAutoLoadStart: '投稿を自動読み込み',
                    tooltipAutoLoadStop: '読み込み停止',
                    tooltipBatchCopy: '全投稿を一括コピー',
                    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: 'コピーする投稿がありません。',
                },
            },
        },

        // --- 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: '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: 'copier_showButtonText', type: 'boolean', defaultValue: false, labelKey: 'copier_menu_showButtonText', group: 'tools', instant: true },
                        {
                            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'
                        },
                    ];
                    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_showButtonText':
                            // 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 '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';

                    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'),
                    };
                    const toggleGroup = (controller, isEnabled) => {
                        container.querySelectorAll(`[data-controls="${controller.id.substring(8)}"]`).forEach(control => {
                            control.style.opacity = isEnabled ? '1' : '0.5';
                            const input = control.querySelector('input, select');
                            if (input) input.disabled = !isEnabled;
                        });
                    };
                    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 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);

                    let resultText = '';
                    let successCount = 0;

                    posts.forEach((post, index) => {
                        const text = this.getPostText(post);
                        const postNum = index + 1;
                        const header = `=== Post #${postNum} ===`;
                        const content = text || '[No Text Content]';
                        const separator = '\n\n-----------------------------------\n\n';

                        resultText += `${header}\n${content}${separator}`;
                        if (text) successCount++;
                    });

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

                getPostText(postEl) {
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const contentContainer = postEl.querySelector(C_TOOLS.CONTENT_BODY);
                    if (!contentContainer) return null;

                    const clone = contentContainer.cloneNode(true);
                    
                    // Replace emoji images with alt text
                    const images = clone.querySelectorAll('img[src*="emoji"][alt], img[alt]');
                    images.forEach(img => {
                        const alt = img.getAttribute('alt');
                        if (alt) img.replaceWith(document.createTextNode(alt));
                    });

                    // Topology extraction
                    const rawBlocks = clone.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 clone.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;
                }
            },

            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; 
                    // 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(); }); 

                    // --- Tools Separator ---
                    const separator = U.createStyledElement('div', { height: '4px' });

                    // --- Auto-Load Button ---
                    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();
                        }
                    });

                    // --- Batch Copy Button ---
                    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.append(prevButton, nextButton, separator, this.btnAutoLoad, 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;
                    console.log(`${this.app.config.LOG_PREFIX} [Tools] Module initialized.`);
                    this.startObserver();
                },

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

                // --- Feature 1: Permalink Copier Logic ---
                async handlePermalinkClick(event, button) {
                    if (this.isProcessingClick) return;
                    // Stop event propagation immediately to prevent FB from intercepting
                    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;
                    if (settings.copier_showButtonText) button.querySelector('span:last-child').textContent = T.copier_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 Content Copier Logic (v0.1.5 Topology Fix + v0.1.6 Emoji) ---
                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 contentContainer = postEl ? postEl.querySelector(C_TOOLS.CONTENT_BODY) : null;
                    const T = this.app.state.T;
                    const settings = this.app.state.settings;

                    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;

                    if (!contentContainer) {
                        this.app.modules.toastNotifier.show(T.copier_notificationContentNotFound, 'failure');
                        await this.animateButtonFeedback(button, 'failure', originalContent);
                        this.isProcessingClick = false;
                        return;
                    }

                    try {
                        // Check for expand button ("See more")
                        const expandKeywords = this.app.config.TEXT_EXPANDER.TARGETS;
                        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) => {
                                let resolved = false;
                                const timeoutId = setTimeout(() => { if (!resolved) { resolved = true; resolve(); } }, 2500);
                                const observer = new MutationObserver(() => {
                                    if (!resolved) {
                                        clearTimeout(timeoutId); resolved = true; observer.disconnect();
                                        setTimeout(resolve, 150);
                                    }
                                });
                                observer.observe(contentContainer, { childList: true, subtree: true });
                                expandBtn.click();
                            });
                        }

                        // Prepare target container (Clone if emojis enabled)
                        let targetContainer = contentContainer;
                        if (settings.copier_includeEmojis) {
                            targetContainer = contentContainer.cloneNode(true);
                            // Replace emoji images with their alt text
                            const images = targetContainer.querySelectorAll('img[src*="emoji"][alt], img[alt]');
                            images.forEach(img => {
                                const src = img.getAttribute('src') || '';
                                const alt = img.getAttribute('alt');
                                // Prioritize images that look like emojis (src contains emoji.php or similar)
                                // or generally any inline image with alt text in the text body
                                if (alt && (src.includes('emoji') || img.className.includes('emoji') || src.includes('fbcdn.net'))) {
                                    img.replaceWith(document.createTextNode(alt));
                                }
                            });
                        }

                        // Extract text using Sibling Node Topology
                        const text = this.extractTextByTopology(targetContainer);
                        GM_setClipboard(text);
                        
                        this.app.modules.toastNotifier.show(T.copier_copyContentSuccess, 'success');
                        await this.animateButtonFeedback(button, 'success', 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;
                    }
                },

                // Extracts text based on DOM structure (Parent grouping)
                extractTextByTopology(container) {
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const rawBlocks = container.querySelectorAll(C_TOOLS.TEXT_BLOCKS);
                    
                    // Filter: Only leaf nodes (most nested div[dir="auto"])
                    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();
                        
                        // Core Logic: 
                        // Same parent = compact list = \n
                        // Different parent = paragraph separation = \n\n
                        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;

                    if (settings.copier_showButtonText && button.dataset.action === 'permalink') {
                        button.querySelector('span:last-child').textContent = status === 'success' ? T.copier_successPermalink : T.copier_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)
                    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)
                    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, settings.copier_showButtonText);
                        wrapper.appendChild(permalinkBtn);
                    }

                    insertionPoint.appendChild(wrapper);
                },
                createButton(action, svgIcon, title, showText = false) {
                    const C = this.app.config.SELECTORS.POST_TOOLS;
                    
                    // Direct binding to avoid propagation issues
                    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',
                        ...(showText 
                            ? { padding: '4px 8px', borderRadius: '6px', display: 'flex', alignItems: 'center' } 
                            : { 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: showText
                            ? `<span class="gm-icon-wrapper">${svgIcon}</span><span style="margin-left: 5px; font-weight: 500; font-size: 13px;">${title}</span>`
                            : `<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;
                            // postHeaderTools handles its own checks inside
                            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();
    }
})();