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.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

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.8.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',
            TRACKING_PARAMS: [
                'fb_content_id', 'encrypted_payload', 'channel_type',
                'fbclid', 'ref', 'ref_id', 'h', '__cft__', '__tn__', '__eep__',
                'igsh', 'xmt',
                'si', 'feature',
                'gclid', 'gclsrc', 'dclid', '_ga', '_gl',
                'srsltid', 'gbraid', 'wbraid',
                's', 't', 'ref_src', 'twclid',
                'share_id', 'ref_campaign', 'ref_source',
                'ref_', 'pf_rd_r', 'pf_rd_p', 'pf_rd_m',
                'mkcid', 'mkrid', '_trkparms', 'ssuid',
                'shpxid',
                'ldtag_cl',
                'smtt', 'is_from_login', 'stm_source', 'stm_medium',
                'yj_r', '_ly_c', '_ly_r',
                'scid',
                'tt_from', 'tt_medium', 'tt_content', '_r', '_t',
                'li_fat_id', 'trk',
                'epik', 'pp',
                'sc_cid', 'snap_campaign_id',
                'mc_cid', 'mc_eid', 'mkt_tok',
                '_hsenc', '_hsmi',
                'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id',
                'cjevent', 'msclkid'
            ],
            SCROLL_RESTORER_CONFIG: {
                CORRECTION_DURATION: 250,
                CORRECTION_FREQUENCY: 16,
                WATCHER_FREQUENCY: 150,
                MODAL_GRACE_PERIOD: 300,
            },
            AUTO_LOADER: {
                POLL_INTERVAL: 300,
                MAX_WAIT_TIME: 3500,
                MIN_COOLDOWN: 1000,
                MAX_RETRIES: 3,
            },
            ERROR_RECOVERY: {
                RELOAD_BUTTON_LABELS: [
                    "Reload Page", "重新載入頁面", "ページを更新",
                    "Volver a cargar página", "Ricarica la pagina", "Seite neu laden",
                    "Actualiser la Page", "Обновить страницу", "Sayfayı Yenile",
                    "페이지 새로 고침", "Tải lại trang"
                ],
                MAX_RETRIES: 2,
                STORAGE_KEY: 'fblwr_retry_state',
            },
            ADS_LIB: {
                KEY_TARGET_ID: 'fblwr_ads_target_id',
                KEY_INT_ACTION: 'fblwr_int_action',
                INITIAL_DELAY: 300,
                POLL_INTERVAL: 250,
                MAX_ATTEMPTS: 20,
            },
            TRANSPARENCY: {
                SEE_ALL_BUTTONS: [
                    'See all transparency information',
                    '查看所有透明度資訊',
                    '透明性に関する情報をすべて見る'
                ]
            },
            TEXT_EXPANDER: {
                TARGETS: [
                    "See more",
                    "查看更多",
                    "さらに表示",
                    "See More", "閱讀更多", "もっと見る"
                ],
                SCOPE_SELECTOR: 'div[role="article"]',
                BUTTON_SELECTOR: 'div[role="button"]',
                PROCESSED_ATTR: 'data-gm-expanded',
            },
            TIMELINE: {
                WIDTH_COLLAPSED: '24px',
                WIDTH_MAX_LIMIT: '260px',
                DOT_SIZE: '8px',
                ROW_HEIGHT: '20px',
            },
            LOGIN_STATE_MARKERS: {
                LOGGED_OUT: [
                    { selector: 'form#login_form', reason: 'Primary login form element' },
                    { selector: 'input[name="pass"]', reason: 'Password input field' },
                    { selector: 'a[href*="/recover/initiate"]', reason: 'Forgot Account link' },
                    { selector: 'a[href*="/login/"]', reason: 'Login link' }
                ],
                LOGGED_IN: [
                    { selector: 'input[type="search"]', reason: 'Search input field' },
                    { selector: 'a[href="/friends/"]', reason: 'Friends tab' }
                ]
            },
            SELECTORS: {
                GLOBAL: {
                    POST_CONTAINER: 'div[role="article"]',
                    MODAL_CONTAINER: 'div.__fb-light-mode',
                    DIALOG: '[role="dialog"]',
                    LOGIN_FORM: 'form#login_form, form[id="login_popup_cta_form"]',
                    MEDIA_LINK: `a[href*="/photo"], a[href*="fbid="], a[href*="/videos/"], a[href*="/watch/"], a[href*="/reel/"]`,
                    CLOSE_BUTTON: [
                        "Close", "關閉", "閉じる", "Cerrar", "Fermer", "Schließen", "Fechar", "Chiudi", "Sluiten", "Закрыть", "Kapat", "Zamknij",
                        "Tutup", "Đóng", "ปิด", "Zatvori", "Zavrieť", "Zavřít", "Bezárás", "Stäng", "Luk", "Lukk", "Sulje", "Κλείσιμο",
                        "Închide", "إغلاق", "סגור"
                    ].map(label => `[aria-label="${label}"][role="button"]`).join(', ') + ', div[role="button"]:has(i[data-visualcompletion="css-img"])',
                },
                NAVIGATOR: {
                    HIGHLIGHT_CLASS: 'gm-post-highlight',
                    TIMELINE_LINK_CANDIDATES: 'a[role="link"]',
                },
                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"]'
                }
            },
            CONSTANTS: {
                SPRITE_OFFSETS: {
                    COMMENT: -1037,
                    SHARE: -1054,
                    TOLERANCE: 10
                },
                OBSERVER: {
                    ROOT_MARGIN: '-45% 0px -45% 0px',
                    THRESHOLD: 0.01
                }
            },
            TIMEOUTS: {
                THROTTLE_DEFAULT: 250,
                THROTTLE_SCROLL: 100,
                THROTTLE_UI: 200, // For visibility updates
                DEBOUNCE_SEARCH: 400,
                INITIAL_DELAY: 50,
                RETRY_INTERVAL: 200,
                MAX_RETRY_DURATION: 3000,
                SCROLL_LOCK: 800
            },
            UI: {
                Z_INDEX: {
                    FLOATING_NAV: 9990,
                    TRANSPARENCY: 9990,
                    TIMELINE: 9995,
                    TOOLBAR: 9998,
                    HOVER_TRIGGER: 9999,
                    HOVER_HINT: 10000,
                    TOAST: 99999,
                    MODAL_BACKDROP: 99998,
                    MODAL_FOREGROUND: 99999,
                    ID_BADGE: 99999
                },
                DIMENSIONS: {
                    BADGE_MIN_WIDTH: '280px',
                    BADGE_MAX_WIDTH: '320px',
                    FLOATING_NAV_BOTTOM: '20px',
                    FLOATING_NAV_RIGHT: '35px'
                },
                HEATMAP_THRESHOLDS: [0.95, 0.85, 0.75, 0.65, 0.55, 0.45, 0.35, 0.20, 0.10]
            },
            SETTINGS_DEFAULTS: {
                AUTO_OPEN_MEDIA: true,
                HIDE_USELESS: true,
                HIDE_STATS: false,
                SHOW_DEADLOCK: true,
                ERROR_RECOVERY: true,
                TRANSPARENCY_BTNS: true,
                ID_REVEALER: true,
                ID_LINK_FORMAT: 'userid',
                AUTO_UNMUTE: true,
                UNMUTE_VOLUME: 25,
                POST_NUMBERING: true,
                EXPAND_CONTENT: true,
                KEY_NAV: true,
                KEY_NEXT_PRI: 'j',
                KEY_PREV_PRI: 'k',
                KEY_NEXT_SEC: 'ArrowRight',
                KEY_PREV_SEC: 'ArrowLeft',
                FLOATING_NAV: true,
                SHOW_AUTO_LOAD: true,
                SHOW_BATCH_COPY: true,
                BATCH_SIZE: 20,
                TIMELINE_NAV: true,
                NAV_HIGHLIGHTER: true,
                TIMELINE_HEATMAP: true,
                WHEEL_NAV: true,
                WHEEL_MODIFIER: 'shiftKey',
                SCROLL_ALIGNMENT: 'top',
                SMOOTH_SCROLL: true,
                NAV_INTERVAL: 500,
                PERMALINK_COPIER: true,
                COPY_CONTENT_BTN: true,
                SMART_LINK: true,
                INCLUDE_EMOJIS: true,
                BATCH_HEADER: true,
                EXPAND_HASHTAGS: false,
                EXPAND_MENTIONS: true,
                META_MASTER: true,
                META_URL: true,
                META_ORDER: true,
                META_AUTHOR: true,
                META_AUTHOR_LINK: true,
                META_DATE: true,
                META_PREVIEW: true,
                META_IMG_COUNT: true,
                META_VID_DURATION: true,
                META_STATS: true,
                META_STATS_TOTAL: true,
                META_STATS_DETAILED: true,
                PERMALINK_FORMAT: 'author_id'
            },
            STRINGS: {
                en: {
                    // --- General ---
                    notificationDeadlock: 'A login prompt was hidden, but the feed can no longer load new content.\n[Tip] To prevent this, open links in a new tab (Middle-Click). Please reload to continue.',
                    notificationSettingsReload: 'Some settings updated. Please reload.',
                    resetSettings: 'Reset Settings',
                    resetSettingsConfirm: 'Reset all settings to default?',
                    notificationSettingsReset: 'Settings reset.',
                    menuResetSettings: '🚨 Reset All Settings',
                    autoOpenMediaInNewTab: 'Auto-open media in new tab',
                    showDeadlockNotification: 'Show deadlock notification',
                    hideUselessElements: 'Hide useless UI elements',
                    hidePostStats: 'Hide post stats (Likes, Comments)',
                    autoUnmuteEnabled: 'Auto unmute videos',
                    setVolumeLabel: 'Auto-unmute volume',
                    postNumberingEnabled: 'Display post numbers',
                    expandContentEnabled: 'Auto-expand content',
                    errorRecoveryEnabled: 'Auto-reload on error',
                    transparencyButtonsEnabled: 'Show Page Transparency shortcuts',

                    // --- ID Revealer ---
                    idRevealerEnabled: 'Enable ID Revealer (Click Title)',
                    idRevealerTooltip: 'Click to reveal ID & Info',
                    idRevealerLinkFormat: 'ID Link Format',
                    idFormatUserID: 'User ID URL (facebook.com/id)',
                    idFormatClassic: 'Classic Profile URL (profile.php?id=)',
                    idFormatUsername: 'Username URL (Current URL)',
                    id_copy_all: 'Copy All Info',
                    id_label_user: 'User ID',
                    id_label_page: 'Page ID',
                    id_label_meta: 'Profile ID',
                    profile_name_label: 'Name',
                    profile_url_label: 'Profile URL',
                    copy_success_generic: '{label} Copied',
                    all_copied: 'All Info Copied',

                    // --- Search Bar ---
                    searchPlaceholder: 'Search...',
                    searchButton: 'Search',
                    searchGroupContextual: 'Search Current Page',
                    searchGroupGlobal: 'Search All of Facebook',
                    searchScopePosts: 'Posts',
                    searchScopePhotos: 'Photos',
                    searchScopeVideos: 'Videos',
                    searchScopeReels: 'Reels',
                    searchScopePages: 'Pages',
                    searchScopePeople: 'People',
                    searchScopeGroups: 'Groups',
                    searchScopeGlobalVideos: 'Videos',
                    searchScopeGlobalPosts: 'Posts',
                    searchScopeEvents: 'Events',
                    searchScopeMarketplace: 'Marketplace',
                    searchTooltipPosts: 'Search current page posts (or global if on Home)',
                    searchTooltipPhotos: 'Search photos on this page',
                    searchTooltipVideos: 'Search videos on this page',
                    searchTooltipReels: 'Search Reels using "Page Name" + "Keyword"',
                    searchTooltipPages: 'Search for Pages, Public Figures, or Organizations',
                    searchTooltipPeople: 'Search for People across Facebook',
                    searchTooltipGroups: 'Search for Groups across Facebook',
                    searchTooltipGlobalPosts: 'Search for Public Posts across Facebook',
                    searchTooltipGlobalVideos: 'Search all videos on Facebook Watch',
                    searchTooltipEvents: 'Search for Events',
                    searchTooltipMarketplace: 'Search Marketplace items',
                    searchAllContextualTooltip: 'Search {scope} on this page via Google',
                    navigateToContextual: 'Go to {scope} section',
                    pinToolbar: 'Pin',
                    unpinToolbar: 'Unpin',
                    shortcutWatch: 'Watch',
                    shortcutEvents: 'Events',
                    shortcutMarketplace: 'Marketplace',

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

                    // --- Navigation ---
                    keyboardNavEnabled: 'Enable keyboard navigation',
                    navHighlighterEnabled: 'Highlight active post border',
                    keyNavNextPrimary: 'Next (J)',
                    keyNavPrevPrimary: 'Prev (K)',
                    keyNavNextSecondary: 'Next (Right)',
                    keyNavPrevSecondary: 'Prev (Left)',
                    floatingNavEnabled: 'Enable floating buttons',
                    floatingNavPrevTooltip: 'Previous Post',
                    floatingNavNextTooltip: 'Next Post',
                    timelineHeatmapEnabled: 'Show Interaction Heatmap (Color Dots)',
                    timelineNavEnabled: 'Enable Timeline Navigator',
                    navigationScrollAlignment: 'Alignment',
                    scrollAlignmentCenter: 'Center',
                    scrollAlignmentTop: 'Top',
                    enableSmoothScrolling: 'Smooth Scroll',
                    continuousNavInterval: 'Nav Interval',
                    wheelNavEnabled: 'Wheel Nav',
                    wheelNavModifier: 'Modifier',
                    modifierAlt: 'Alt',
                    modifierCtrl: 'Ctrl',
                    modifierShift: 'Shift',
                    modifierNone: 'None',

                    // --- Tools (Copier & AutoLoad) ---
                    copier_enablePermalink: 'Enable Permalink Icon',
                    copier_enableCopyContent: 'Enable Smart Copy Button',
                    copier_fetchPermalinkSmart: 'Permalink (Smart)',
                    copier_fetchPermalinkDirect: 'Permalink (Direct)',
                    copier_copyContent: 'Copy Post Content',
                    copier_copyContentSuccess: '✅ Content Copied',
                    copier_copyContentFailed: '❌ Copy Failed',
                    copier_processing: 'Processing...',
                    copier_successPermalink: '✅ Copied',
                    copier_failure: '❌ Failed',
                    copier_notificationPermalinkCopied: 'Permalink copied:\n{url}',
                    copier_notificationErrorGeneric: 'Failed to fetch.',
                    copier_notificationErrorNoSourceUrl: 'No source URL.',
                    copier_notificationErrorTimeout: 'Fetch timed out.',
                    copier_notificationContentNotFound: '❌ Content not found.',
                    copier_menu_useSmartLink: 'Use Smart Link (Async)',
                    copier_menu_permalinkFormat: 'Permalink Format',
                    copier_format_full: 'Full URL',
                    copier_format_username: 'Username+ID',
                    copier_format_author_id: 'AuthorID+ID',
                    copier_format_shortest: 'Shortest',
                    copier_includeEmojis: 'Include emojis',
                    copier_expandHashtags: 'Expand Hashtags to URL',
                    copier_expand_mentions: 'Expand Mentions to URL',

                    autoLoader_batchSize: 'Batch Count',
                    tooltipAutoLoadStart: 'Auto-Load',
                    tooltipAutoLoadStop: 'Stop',
                    tooltipBatchCopy: 'Batch Copy',
                    batchCopy_includeHeader: 'Include Header in Batch Copy',
                    autoLoad_status_loading: 'Loading... ({current}/{target})',
                    autoLoad_status_retrying: 'Retrying...',
                    autoLoad_status_success: 'Done.',
                    autoLoad_status_stopped: 'Stopped.',
                    autoLoad_status_deadlock: 'Deadlock.',
                    batchCopy_start: 'Processing {count} posts...',
                    batchCopy_success: '✅ Copied {count} posts.',
                    batchCopy_empty: 'No posts.',
                    floatingNav_showAutoLoad: 'Show Auto-Load',
                    floatingNav_showBatchCopy: 'Show Batch Copy',

                    // --- Copy Metadata ---
                    copy_includeMetadata: 'Include Metadata',
                    copy_meta_url: 'Include Link (Top)',
                    copy_meta_order: 'Include Order [#]',
                    copy_meta_author_name: 'Include Author Name',
                    copy_meta_author_link: 'Include Author Link',
                    copy_meta_date: 'Include Date',
                    copy_meta_stats: 'Include Stats (Master)',
                    copy_meta_stats_total: 'Include Total Count (1.9K)',
                    copy_meta_stats_detailed: 'Include Details (👍❤️)',
                    copy_meta_link_preview: 'Include Link Preview',
                    copy_meta_image_count: 'Include Image Count',
                    image_count_label: 'Images',
                    copy_meta_video_duration: 'Include Video/Reel Duration',
                    label_reel: 'Reel',
                    label_video: 'Video',
                    stats_label_like: 'Like',
                    stats_label_comment: 'Comment',
                    stats_label_share: 'Share',
                    stats_label_reaction: 'Reactions',
                    preview_label_title: 'Title',
                    preview_label_source: 'Source',
                    preview_label_desc: 'Desc',
                    preview_label_link: 'Link',

                    // --- Other Tooltips ---
                    tooltipAds: 'Ads Library',
                    tooltipTransparency: 'Transparency',
                    notificationReelSearchError: 'Page name not found',
                },
                'zh-TW': {
                    // --- 一般設定 ---
                    notificationDeadlock: '登入提示已隱藏,動態消息將無法載入新內容。\n【提示】為避免動態消息卡住,請養成用滑鼠中鍵在新分頁開啟連結的習慣。請重新整理頁面以繼續瀏覽。',
                    notificationSettingsReload: '部分設定已更新,請重新整理頁面以完全生效。',
                    resetSettings: '重設設定',
                    resetSettingsConfirm: '您確定要將所有設定重設為預設值嗎?此操作無法復原。',
                    notificationSettingsReset: '設定已重設為預設值,部分變更可能需要重新整理頁面才能生效。',
                    menuResetSettings: '🚨 重設所有設定',
                    autoOpenMediaInNewTab: '自動在新分頁開啟媒體 (防鎖定)',
                    showDeadlockNotification: '顯示頁面鎖定通知',
                    hideUselessElements: '隱藏對訪客無用的介面元素',
                    hidePostStats: '隱藏貼文統計數據 (讚數、留言數)',
                    autoUnmuteEnabled: '自動取消影片靜音',
                    setVolumeLabel: '自動音量大小',
                    postNumberingEnabled: '在動態消息上顯示貼文順序',
                    expandContentEnabled: '自動展開貼文內容 (查看更多)',
                    errorRecoveryEnabled: '錯誤頁面自動恢復 (按鈕偵測)',
                    transparencyButtonsEnabled: '顯示粉絲專頁資訊透明度捷徑按鈕 (左下角)',

                    // --- ID Revealer ---
                    idRevealerEnabled: '啟用 ID 顯示器 (點擊標題)',
                    idRevealerTooltip: '點擊以顯示 Profile ID 與資訊',
                    idRevealerLinkFormat: 'ID 連結格式',
                    idFormatUserID: 'User ID 格式 (facebook.com/id)',
                    idFormatClassic: '經典格式 (profile.php?id=)',
                    idFormatUsername: '使用者名稱 (當前網址)',
                    id_copy_all: '複製全部資訊',
                    id_label_user: 'User ID',
                    id_label_page: 'Page ID',
                    id_label_meta: 'Profile ID',
                    profile_name_label: '專頁名稱',
                    profile_url_label: 'Profile URL',
                    copy_success_generic: '已複製 {label}',
                    all_copied: '全部資訊已複製',

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

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

                    // --- 導覽功能 ---
                    keyboardNavEnabled: '啟用鍵盤導覽',
                    navHighlighterEnabled: '啟用使用中貼文的高亮邊框',
                    keyNavNextPrimary: '下一篇 (主要按鍵)',
                    keyNavPrevPrimary: '上一篇 (主要按鍵)',
                    keyNavNextSecondary: '下一篇 (次要按鍵)',
                    keyNavPrevSecondary: '上一篇 (次要按鍵)',
                    floatingNavEnabled: '啟用浮動導覽按鈕',
                    floatingNavPrevTooltip: '上一篇貼文',
                    floatingNavNextTooltip: '下一篇貼文',
                    timelineHeatmapEnabled: '顯示互動熱點圖 (彩色圓點)',
                    timelineNavEnabled: '啟用時間軸導覽 (右側捷徑)',
                    navigationScrollAlignment: '導覽滾動對齊',
                    scrollAlignmentCenter: '置中',
                    scrollAlignmentTop: '貼齊頂部',
                    enableSmoothScrolling: '啟用平滑捲動',
                    continuousNavInterval: '連續導覽間隔時間',
                    wheelNavEnabled: '啟用滑鼠滾輪導覽',
                    wheelNavModifier: '滾輪導覽修飾鍵',
                    modifierAlt: 'Alt',
                    modifierCtrl: 'Ctrl',
                    modifierShift: 'Shift',
                    modifierNone: '無 (取代頁面捲動)',

                    // --- 複製與載入工具 ---
                    copier_enablePermalink: '啟用 永久連結按鈕 (僅圖示)',
                    copier_enableCopyContent: '啟用 複製內容按鈕 (智慧)',
                    copier_fetchPermalinkSmart: '永久連結 (智慧)',
                    copier_fetchPermalinkDirect: '永久連結 (直接)',
                    copier_copyContent: '複製貼文內容',
                    copier_copyContentSuccess: '✅ 內容已複製',
                    copier_copyContentFailed: '❌ 複製失敗',
                    copier_processing: '處理中...',
                    copier_successPermalink: '✅ 已複製',
                    copier_failure: '❌ 失敗',
                    copier_notificationPermalinkCopied: '永久連結已複製:\n{url}',
                    copier_notificationErrorGeneric: '獲取永久連結失敗。',
                    copier_notificationErrorNoSourceUrl: '失敗:找不到來源網址。',
                    copier_notificationErrorTimeout: '失敗:背景處理逾時。',
                    copier_notificationContentNotFound: '❌ 找不到內容區塊。',
                    copier_menu_useSmartLink: '複製內容時使用智慧連結 (需等待)',
                    copier_menu_permalinkFormat: '永久連結格式',
                    copier_format_full: '完整網址 (含 Slug)',
                    copier_format_username: '使用者名稱 + 貼文 ID',
                    copier_format_author_id: '作者 ID + 貼文 ID (最可靠)',
                    copier_format_shortest: '最短連結 (fb.com, 相容性較差)',
                    copier_includeEmojis: '複製內容包含表情符號',
                    copier_expandHashtags: '將 Hashtag 展開為網址',
                    copier_expand_mentions: '將提及對象展開為網址',

                    autoLoader_batchSize: '自動載入批次數量',
                    tooltipAutoLoadStart: '自動載入貼文',
                    tooltipAutoLoadStop: '停止載入',
                    tooltipBatchCopy: '批次複製所有貼文',
                    batchCopy_includeHeader: '批次複製包含頁首資訊',
                    autoLoad_status_loading: '載入中... ({current}/{target})',
                    autoLoad_status_retrying: '重試中... ({count}/{max})',
                    autoLoad_status_success: '自動載入完成。',
                    autoLoad_status_stopped: '使用者手動停止。',
                    autoLoad_status_deadlock: '偵測到阻擋,載入已停止。',
                    batchCopy_start: '正在處理 {count} 篇貼文...',
                    batchCopy_success: '✅ 已複製 {count} 篇貼文。',
                    batchCopy_empty: '沒有貼文可複製。',
                    floatingNav_showAutoLoad: '顯示 自動載入按鈕',
                    floatingNav_showBatchCopy: '顯示 批次複製按鈕',

                    // --- 複製中繼資料 ---
                    copy_includeMetadata: '複製內容包含中繼資料',
                    copy_meta_url: '包含貼文連結 (置頂)',
                    copy_meta_order: '包含貼文順序 [#xx]',
                    copy_meta_author_name: '包含發文者名稱',
                    copy_meta_author_link: '包含發文者連結 (個人檔案)',
                    copy_meta_date: '包含發文時間',
                    copy_meta_stats: '包含互動統計',
                    copy_meta_stats_total: '包含總數 (1.9K 心情)',
                    copy_meta_stats_detailed: '包含詳細心情 (👍❤️)',
                    copy_meta_link_preview: '包含連結預覽資訊 (標題/來源/摘要)',
                    copy_meta_image_count: '包含圖片數量統計',
                    image_count_label: '圖片',
                    copy_meta_video_duration: '包含影片/Reels 時長',
                    label_reel: 'Reel',
                    label_video: '影片',
                    stats_label_like: '讚',
                    stats_label_comment: '留言',
                    stats_label_share: '分享',
                    stats_label_reaction: '心情',
                    preview_label_title: '標題',
                    preview_label_source: '來源',
                    preview_label_desc: '摘要',
                    preview_label_link: '連結',

                    // --- 其他提示 ---
                    tooltipAds: '前往 廣告檔案庫 (關於)',
                    tooltipTransparency: '查看 粉絲專頁資訊透明度',
                    notificationReelSearchError: '無法取得目前頁面名稱以進行連續短片搜尋。',
                },
                ja: {
                    // --- 一般 ---
                    notificationDeadlock: 'ログインプロンプトが非表示になりましたが、フィードは新しいコンテンツを読み込めなくなりました。\n【ヒント】フィードがロックされないように、新しいタブでリンクを開く(中央クリック)習慣を付けてください。閲覧を続けるには、このページをリロードしてください。',
                    notificationSettingsReload: '一部の設定が更新されました。完全に有効にするには、ページをリロードしてください。',
                    resetSettings: '設定をリセット',
                    resetSettingsConfirm: 'すべての設定をデフォルトにリセットしますか?この操作は元に戻せません。',
                    notificationSettingsReset: '設定がデフォルトにリセットされました。一部の変更は、ページをリロードすると有効になります。',
                    menuResetSettings: '🚨 全ての設定をリセット',
                    autoOpenMediaInNewTab: 'メディアを新しいタブで開く (デッドロック防止)',
                    showDeadlockNotification: 'デッドロック通知を表示',
                    hideUselessElements: '不要なUI要素を非表示にする(ゲスト用)',
                    hidePostStats: '投稿の統計データを非表示 (いいね!、コメント数)',
                    autoUnmuteEnabled: '動画のミュートを自動解除',
                    setVolumeLabel: '自動音量',
                    postNumberingEnabled: 'フィードに投稿順序番号を表示する',
                    expandContentEnabled: '投稿の内容を自動的に展開 (さらに表示)',
                    errorRecoveryEnabled: 'エラーページ自動回復 (ボタン検出)',
                    transparencyButtonsEnabled: 'ページの透明性ショートカットを表示 (左下)',

                    // --- ID Revealer ---
                    idRevealerEnabled: 'ID表示機能を有効にする(タイトルをクリック)',
                    idRevealerTooltip: 'クリックしてプロフィールIDと情報を表示',
                    idRevealerLinkFormat: 'IDリンク形式',
                    idFormatUserID: 'User ID形式 (facebook.com/id)',
                    idFormatClassic: 'クラシック (profile.php?id=)',
                    idFormatUsername: 'ユーザー名 (現在のURL)',
                    id_copy_all: 'すべての情報をコピー',
                    id_label_user: 'User ID',
                    id_label_page: 'Page ID',
                    id_label_meta: 'Profile ID',
                    profile_name_label: 'プロフィール名',
                    profile_url_label: 'Profile URL',
                    copy_success_generic: '{label}をコピーしました',
                    all_copied: 'すべての情報をコピーしました',

                    // --- 検索バー ---
                    searchPlaceholder: '検索...',
                    searchButton: '検索',
                    searchGroupContextual: '現在のページを検索',
                    searchGroupGlobal: 'Facebook全体を検索',
                    searchScopePosts: '投稿',
                    searchScopePhotos: '写真',
                    searchScopeVideos: '動画',
                    searchScopeReels: 'リール',
                    searchScopePages: 'ページ',
                    searchScopePeople: '人物',
                    searchScopeGroups: 'グループ',
                    searchScopeGlobalVideos: '動画',
                    searchScopeGlobalPosts: '投稿',
                    searchScopeEvents: 'イベント',
                    searchScopeMarketplace: 'マーケット',
                    searchTooltipPosts: '現在のページの投稿を検索(ホーム画面の場合は全体)',
                    searchTooltipPhotos: '現在のページの写真内を検索',
                    searchTooltipVideos: '現在のページの動画内を検索',
                    searchTooltipReels: '「ページ名」+「キーワード」でリールを検索',
                    searchTooltipPages: 'Facebook全体でページ・著名人・組織を検索',
                    searchTooltipPeople: 'Facebook全体で人物を検索',
                    searchTooltipGroups: 'Facebook全体でグループを検索',
                    searchTooltipGlobalPosts: 'Facebook全体で公開投稿を検索',
                    searchTooltipGlobalVideos: 'Facebook Watch機能で動画を検索',
                    searchTooltipEvents: 'イベントを検索',
                    searchTooltipMarketplace: 'Marketplaceで商品を検索',
                    searchAllContextualTooltip: 'Googleを使ってこのページの {scope} を検索',
                    navigateToContextual: '{scope} セクションへ移動',
                    pinToolbar: '固定',
                    unpinToolbar: '固定解除',
                    shortcutWatch: 'Watchへ',
                    shortcutEvents: 'イベントへ',
                    shortcutMarketplace: 'マーケットへ',

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

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

                    // --- ツール (コピー & 自動読み込み) ---
                    copier_enablePermalink: '固定リンクボタンを有効にする (アイコンのみ)',
                    copier_enableCopyContent: '内容コピーボタンを有効にする (スマート)',
                    copier_fetchPermalinkSmart: '固定リンク (スマート)',
                    copier_fetchPermalinkDirect: '固定リンク (直接)',
                    copier_copyContent: '投稿内容をコピー',
                    copier_copyContentSuccess: '✅ コピーしました',
                    copier_copyContentFailed: '❌ 失敗しました',
                    copier_processing: '処理中...',
                    copier_successPermalink: '✅ コピーしました',
                    copier_failure: '❌ 失敗',
                    copier_notificationPermalinkCopied: '固定リンクをクリップボードにコピーしました:\n{url}',
                    copier_notificationErrorGeneric: '固定リンクの取得に失敗しました。',
                    copier_notificationErrorNoSourceUrl: '失敗:ソースURLが見つかりません。',
                    copier_notificationErrorTimeout: '失敗:取得タイムアウト。',
                    copier_notificationContentNotFound: '❌ コンテンツが見つかりませんでした。',
                    copier_menu_useSmartLink: 'コンテンツコピー時にスマートリンクを使用 (待機あり)',
                    copier_menu_permalinkFormat: '固定リンク形式',
                    copier_format_full: '完全なURL',
                    copier_format_username: 'ユーザー名+ID',
                    copier_format_author_id: '作者ID+ID (推奨)',
                    copier_format_shortest: '短縮 (fb.com)',
                    copier_includeEmojis: '絵文字を含める',
                    copier_expandHashtags: 'ハッシュタグをURLに展開',
                    copier_expand_mentions: 'メンションをURLに展開',

                    autoLoader_batchSize: '自動読み込みバッチ数',
                    tooltipAutoLoadStart: '投稿を自動読み込み',
                    tooltipAutoLoadStop: '読み込み停止',
                    tooltipBatchCopy: '全投稿を一括コピー',
                    batchCopy_includeHeader: '一括コピーにヘッダーを含める',
                    autoLoad_status_loading: '読み込み中... ({current}/{target})',
                    autoLoad_status_retrying: '再試行中... ({count}/{max})',
                    autoLoad_status_success: '自動読み込み完了。',
                    autoLoad_status_stopped: 'ユーザーにより停止。',
                    autoLoad_status_deadlock: 'ブロックを検出。停止します。',
                    batchCopy_start: '{count} 件の投稿を処理中...',
                    batchCopy_success: '✅ {count} 件の投稿をコピーしました。',
                    batchCopy_empty: 'コピーする投稿がありません。',
                    floatingNav_showAutoLoad: '自動読み込みボタンを表示',
                    floatingNav_showBatchCopy: '一括コピーボタンを表示',

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

                    // --- その他 ---
                    tooltipAds: '広告ライブラリ',
                    tooltipTransparency: 'ページの透明性',
                    notificationReelSearchError: 'ページ名が見つかりません。',
                },
            },
        },

        // --- STYLES ---
        styles: {
            get CORE() {
                const C = app.config;
                return [
                    // --- 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: ${C.UI.DIMENSIONS.FLOATING_NAV_BOTTOM}; right: ${C.UI.DIMENSIONS.FLOATING_NAV_RIGHT}; z-index: ${C.UI.Z_INDEX.FLOATING_NAV}; 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; }`,
                    // --- Timeline Navigator ---
                    `#gm-timeline-container { position: fixed; right: 0; top: 10vh; bottom: 10vh; z-index: ${C.UI.Z_INDEX.TIMELINE}; background: rgba(255, 255, 255, 0.1); border-top-left-radius: 12px; border-bottom-left-radius: 12px; padding: 12px 0; backdrop-filter: blur(2px); display: flex; flex-direction: column; align-items: flex-end; gap: 2px; width: fit-content; max-width: 28px; transition: max-width 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), background-color 0.2s, box-shadow 0.2s; box-shadow: -1px 0 4px rgba(0,0,0,0.02); overflow-y: auto; overflow-x: hidden; scrollbar-width: none; }`,
                    `#gm-timeline-container:hover { max-width: ${C.TIMELINE.WIDTH_MAX_LIMIT}; background: rgba(255, 255, 255, 0.98); box-shadow: -4px 0 16px rgba(0,0,0,0.12); }`,
                    `#gm-timeline-container::-webkit-scrollbar { display: none; }`,
                    `.gm-timeline-row { display: flex; align-items: center; justify-content: flex-end; width: 100%; height: ${C.TIMELINE.ROW_HEIGHT}; padding-right: 8px; padding-left: 14px; cursor: pointer; user-select: none; flex-shrink: 0; white-space: nowrap; }`,
                    `.gm-timeline-info { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-right: 12px; opacity: 0; transform: translateX(10px); transition: opacity 0.2s ease, transform 0.2s ease; pointer-events: none; }`,
                    `#gm-timeline-container:hover .gm-timeline-info { opacity: 1; transform: translateX(0); transition-delay: 0.05s; }`,
                    `.gm-timeline-sort-btn { width: 24px; height: 24px; border-radius: 50%; background: transparent; border: none; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; margin-bottom: 4px; transition: transform 0.2s; opacity: 0.6; user-select: none; }`,
                    `.gm-timeline-sort-btn:hover { opacity: 1; background-color: rgba(0,0,0,0.05); transform: scale(1.1); }`,
                    `.gm-label-time { font-size: 11px; color: #65676B; font-weight: 400; letter-spacing: 0.3px; }`,

                    `.gm-label-idx { font-size: 11px; color: #B0B3B8; min-width: 28px; text-align: right; font-variant-numeric: tabular-nums; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }`,
                    `.gm-label-idx.milestone-text { color: #050505; font-weight: 700; font-size: 12px; }`,
                    `.gm-timeline-dot { width: ${C.TIMELINE.DOT_SIZE}; height: ${C.TIMELINE.DOT_SIZE}; border-radius: 50%; background-color: #B0B3B8; transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.2s; flex-shrink: 0; }`,
                    `.gm-timeline-row:hover .gm-timeline-dot { transform: scale(1.5); }`,
                    `.gm-timeline-row:hover .gm-label-idx { color: #1877F2; font-weight: 700; }`,
                    `.gm-timeline-row:hover .gm-timeline-info { opacity: 1; transform: translateX(0); }`,
                    `.gm-timeline-row.active .gm-label-idx { color: #1877F2; font-weight: 700; }`,

                    // --- Timeline Heatmap Colors (9-Tier Spectrum) ---
                    `.gm-timeline-dot[data-heat="1"] { background-color: #B2DFDB !important; }`,
                    `.gm-timeline-dot[data-heat="2"] { background-color: #80CBC4 !important; }`,
                    `.gm-timeline-dot[data-heat="3"] { background-color: #26A69A !important; }`,
                    `.gm-timeline-dot[data-heat="4"] { background-color: #66BB6A !important; }`,
                    `.gm-timeline-dot[data-heat="5"] { background-color: #D4E157 !important; }`,
                    `.gm-timeline-dot[data-heat="6"] { background-color: #FFEE58 !important; }`,
                    `.gm-timeline-dot[data-heat="7"] { background-color: #FFCA28 !important; }`,
                    `.gm-timeline-dot[data-heat="8"] { background-color: #FB8C00 !important; }`,
                    `.gm-timeline-dot[data-heat="9"] { background-color: #F44336 !important; box-shadow: 0 0 4px rgba(244, 67, 54, 0.5); }`,

                    // --- Active State (Ring Style) ---
                    `.gm-timeline-row.active .gm-timeline-dot { transform: scale(1.4); border: 2px solid #ffffff; box-shadow: 0 0 0 2px #1877F2, 0 2px 6px rgba(0,0,0,0.3) !important; }`,

                    // --- Toolbar & Search ---
                    `.gm-toolbar { position: fixed; top: 0; left: 0; width: 100%; padding: 4px 16px; background-color: #FFFFFF; z-index: ${C.UI.Z_INDEX.TOOLBAR}; 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: ${C.UI.Z_INDEX.HOVER_HINT}; 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: ${C.UI.Z_INDEX.HOVER_TRIGGER}; }`,

                    // --- 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: ${C.UI.Z_INDEX.TOAST}; 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: ${C.UI.Z_INDEX.TRANSPARENCY}; 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: ${C.UI.Z_INDEX.MODAL_BACKDROP}; }`,
                    `.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: ${C.UI.Z_INDEX.MODAL_FOREGROUND}; 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; }`
                ].join('\n');
            },
            get HIDE_USELESS() {
                const C = app.config;
                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"])';
                return [
                    `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; }`
                ].join('\n');
            },
            get HIDE_STATS() {
                return `div:has(> div > span[role="toolbar"]) { display: none !important; }`;
            }
        },

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

        // --- UTILITY FUNCTIONS ---
        utils: {
            /**
             * Checks if the user is currently logged in based on DOM markers.
             * @returns {boolean} True if logged in, false otherwise.
             */
            isLoggedIn() {
                const banner = document.querySelector('div[role="banner"]');
                if (!banner) 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;
                return false;
            },
            /**
             * Creates a throttled version of a function that only invokes at most once per every `delay` milliseconds.
             * @param {Function} func - The function to throttle.
             * @param {number} delay - The number of milliseconds to throttle executions to.
             * @returns {Function} - The throttled function.
             */
            throttle(func, delay) {
                let timeoutId = null;
                return (...args) => {
                    if (timeoutId === null) {
                        timeoutId = setTimeout(() => {
                            func.apply(this, args);
                            timeoutId = null;
                        }, delay);
                    }
                };
            },
            /**
             * Creates a debounced function that delays invoking `func` until after `wait` milliseconds have elapsed since the last time the debounced function was invoked.
             * @param {Function} func - The function to debounce.
             * @param {number} wait - The number of milliseconds to delay.
             * @returns {Function} - The debounced function.
             */
            debounce(func, wait) {
                let timeout;
                return function (...args) {
                    const context = this;
                    clearTimeout(timeout);
                    timeout = setTimeout(() => func.apply(context, args), wait);
                };
            },
            /**
             * Utility to create a DOM element with styles and properties.
             * @param {string} tag - The HTML tag name.
             * @param {Object} [styles={}] - CSS styles to apply.
             * @param {Object} [properties={}] - Properties to set (including 'on' for events and 'children').
             * @returns {HTMLElement} - The created element.
             */
            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;
            },
            /**
             * Determines if the current page is a "feed" page where posts are displayed.
             * @returns {boolean} True if it is a feed page.
             */
            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]);
            },
            /**
             * Returns a promise that resolves after the specified milliseconds.
             * @param {number} ms - Milliseconds to delay.
             * @returns {Promise<void>}
             */
            delay: ms => new Promise(res => setTimeout(res, ms)),

            /**
             * Opens a URL in a new tab, handling background tabs if supported.
             * @param {Event} event - The triggering event.
             * @param {string} url - The URL to open.
             */
            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');
            },
            /**
             * Checks if an element is currently visible in the viewport and not hidden.
             * @param {HTMLElement} el - The element to check.
             * @returns {boolean} True if visible.
             */
            isVisible(el) {
                if (!el) return false;
                return el.offsetParent !== null && window.getComputedStyle(el).display !== 'none';
            },

            /**
             * Scrolls the window to the specified element, respecting user settings for alignment and animation.
             * @param {HTMLElement} element - The target element.
             * @param {boolean|null} [forceSmooth=null] - Force smooth scrolling if true/false, or use settings if null.
             */
            scrollToElement(element, forceSmooth = null) {
                if (!element) return;

                const settings = app.state.settings;
                const alignCenter = settings.navigationScrollAlignment === 'center';
                const isSmooth = forceSmooth !== null ? forceSmooth : settings.enableSmoothScrolling;
                const behavior = isSmooth ? 'smooth' : 'auto';

                if (alignCenter) {
                    element.scrollIntoView({ behavior, block: 'center' });
                } else {
                    // Top Alignment Logic
                    let offset = 10; // Base buffer (px)

                    // Determine if Toolbar is Pinned
                    // The pinned state is NOT in app.state.settings, it resides in the searchBar module.
                    let isPinned = true; // Default to true if unknown

                    if (app.modules.searchBar && typeof app.modules.searchBar.state.isPinned !== 'undefined') {
                        isPinned = app.modules.searchBar.state.isPinned;
                    } else {
                        // Fallback: Read directly from storage if module isn't ready
                        isPinned = GM_getValue('isSearchBarPinned', true);
                    }

                    // Only reserve space if the user has actually PINNED the toolbar.
                    // If Unpinned, we ignore it (offset = 10), even if it's momentarily visible.
                    if (isPinned) {
                        const toolbar = document.querySelector('.gm-toolbar');
                        // Use actual height if available, otherwise fallback to standard height (45px)
                        // to prevent jumping if the DOM is rebuilding.
                        if (toolbar && toolbar.offsetHeight > 0) {
                            offset += toolbar.offsetHeight;
                        } else {
                            offset += 46; // Standard height fallback
                        }
                    }

                    const elementRectTop = element.getBoundingClientRect().top;
                    const absoluteElementTop = elementRectTop + window.pageYOffset;
                    const targetScrollY = absoluteElementTop - offset;

                    window.scrollTo({
                        top: targetScrollY,
                        behavior: behavior
                    });
                }
            },

            /**
             * Removes known tracking parameters from a URL object.
             * @param {URL} urlObj - The URL object to clean.
             * @returns {boolean} True if any parameters were removed.
             */
            cleanUrlParams(urlObj) {
                if (!urlObj || !urlObj.searchParams) return false;
                const trackingParams = app.config.TRACKING_PARAMS;
                const keysToDelete = [];
                urlObj.searchParams.forEach((_, key) => {
                    const shouldDelete = trackingParams.some(tp => key === tp || key.startsWith(`${tp}[`));
                    if (shouldDelete) keysToDelete.push(key);
                });
                keysToDelete.forEach(k => urlObj.searchParams.delete(k));
                return keysToDelete.length > 0;
            },

            /**
             * Smartly extracts text from a DOM element, preserving block topology (paragraphs/headers) with newlines.
             * @param {HTMLElement} root - The root element to extract text from.
             * @returns {string} The formatted text.
             */
            smartTextExtract(root) {
                let text = '';
                if (!root) return text;

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

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

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

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

                        const isBlockElem = isBlock(node);
                        const isContainer = isBlockElem && hasBlockChildren(node);

                        if (isBlockElem) ensureNewlines(1);

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

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

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

        // --- CORE MODULES ---
        modules: {
            settingsManager: {
                app: null,
                registeredCommands: [],
                definitions: null,
                _getDefinitions() {
                    const D = this.app.config.SETTINGS_DEFAULTS;
                    const generalSettings = [
                        { key: 'autoOpenMediaInNewTab', type: 'boolean', defaultValue: D.AUTO_OPEN_MEDIA, labelKey: 'autoOpenMediaInNewTab', group: 'general' },
                        { key: 'hideUselessElements', type: 'boolean', defaultValue: D.HIDE_USELESS, labelKey: 'hideUselessElements', group: 'general', needsReload: true },
                        { key: 'hidePostStats', type: 'boolean', defaultValue: D.HIDE_STATS, labelKey: 'hidePostStats', group: 'general', instant: true },
                        { key: 'showDeadlockNotification', type: 'boolean', defaultValue: D.SHOW_DEADLOCK, labelKey: 'showDeadlockNotification', group: 'general' },
                        { key: 'errorRecoveryEnabled', type: 'boolean', defaultValue: D.ERROR_RECOVERY, labelKey: 'errorRecoveryEnabled', group: 'general' },
                        { key: 'transparencyButtonsEnabled', type: 'boolean', defaultValue: D.TRANSPARENCY_BTNS, labelKey: 'transparencyButtonsEnabled', group: 'general' },
                        { key: 'idRevealerEnabled', type: 'boolean', defaultValue: D.ID_REVEALER, labelKey: 'idRevealerEnabled', group: 'general' },
                        {
                            key: 'idRevealerLinkFormat', type: 'select', defaultValue: D.ID_LINK_FORMAT, labelKey: 'idRevealerLinkFormat',
                            options: [
                                { value: 'userid', labelKey: 'idFormatUserID' }, { value: 'classic', labelKey: 'idFormatClassic' }, { value: 'username', labelKey: 'idFormatUsername' }
                            ],
                            group: 'general'
                        },
                        { key: 'autoUnmuteEnabled', type: 'boolean', defaultValue: D.AUTO_UNMUTE, labelKey: 'autoUnmuteEnabled', group: 'general' },
                        { key: 'autoUnmuteVolume', type: 'range', defaultValue: D.UNMUTE_VOLUME, labelKey: 'setVolumeLabel', options: { min: 0, max: 100, step: 5, unit: '%' }, group: 'general' },
                        { key: 'postNumberingEnabled', type: 'boolean', defaultValue: D.POST_NUMBERING, labelKey: 'postNumberingEnabled', group: 'general' },
                        { key: 'expandContentEnabled', type: 'boolean', defaultValue: D.EXPAND_CONTENT, labelKey: 'expandContentEnabled', group: 'general' },
                    ];
                    const navigationSettings = [
                        { key: 'keyboardNavEnabled', type: 'boolean', defaultValue: D.KEY_NAV, labelKey: 'keyboardNavEnabled', group: 'navigation' },
                        { key: 'keyNavNextPrimary', type: 'text', defaultValue: D.KEY_NEXT_PRI, labelKey: 'keyNavNextPrimary', group: 'navigation' },
                        { key: 'keyNavPrevPrimary', type: 'text', defaultValue: D.KEY_PREV_PRI, labelKey: 'keyNavPrevPrimary', group: 'navigation' },
                        { key: 'keyNavNextSecondary', type: 'text', defaultValue: D.KEY_NEXT_SEC, labelKey: 'keyNavNextSecondary', group: 'navigation' },
                        { key: 'keyNavPrevSecondary', type: 'text', defaultValue: D.KEY_PREV_SEC, labelKey: 'keyNavPrevSecondary', group: 'navigation' },
                        { key: 'floatingNavEnabled', type: 'boolean', defaultValue: D.FLOATING_NAV, labelKey: 'floatingNavEnabled', group: 'navigation', instant: true },
                        { key: 'floatingNav_showAutoLoad', type: 'boolean', defaultValue: D.SHOW_AUTO_LOAD, labelKey: 'floatingNav_showAutoLoad', group: 'navigation', instant: true },
                        { key: 'floatingNav_showBatchCopy', type: 'boolean', defaultValue: D.SHOW_BATCH_COPY, labelKey: 'floatingNav_showBatchCopy', group: 'navigation', instant: true },
                        { key: 'autoLoadBatchSize', type: 'range', defaultValue: D.BATCH_SIZE, labelKey: 'autoLoader_batchSize', options: { min: 10, max: 100, step: 5, unit: '' }, group: 'navigation' },

                        { key: 'timelineNavEnabled', type: 'boolean', defaultValue: D.TIMELINE_NAV, labelKey: 'timelineNavEnabled', group: 'navigation', instant: true },
                        { key: 'navHighlighterEnabled', type: 'boolean', defaultValue: D.NAV_HIGHLIGHTER, labelKey: 'navHighlighterEnabled', group: 'navigation' },
                        { key: 'timelineHeatmapEnabled', type: 'boolean', defaultValue: D.TIMELINE_HEATMAP, labelKey: 'timelineHeatmapEnabled', group: 'navigation' },

                        { key: 'wheelNavEnabled', type: 'boolean', defaultValue: D.WHEEL_NAV, labelKey: 'wheelNavEnabled', group: 'navigation' },
                        { key: 'wheelNavModifier', type: 'select', defaultValue: D.WHEEL_MODIFIER, 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: D.SCROLL_ALIGNMENT, labelKey: 'navigationScrollAlignment', options: [{ value: 'center', labelKey: 'scrollAlignmentCenter' }, { value: 'top', labelKey: 'scrollAlignmentTop' }], group: 'navigation' },
                        { key: 'enableSmoothScrolling', type: 'boolean', defaultValue: D.SMOOTH_SCROLL, labelKey: 'enableSmoothScrolling', group: 'navigation' },
                        { key: 'continuousNavInterval', type: 'range', defaultValue: D.NAV_INTERVAL, labelKey: 'continuousNavInterval', options: { min: 0, max: 1000, step: 50, unit: 'ms' }, group: 'navigation' },
                    ];
                    const toolsSettings = [
                        // Buttons
                        { key: 'permalinkCopierEnabled', type: 'boolean', defaultValue: D.PERMALINK_COPIER, labelKey: 'copier_enablePermalink', group: 'tools', instant: true },
                        { key: 'enableCopyContentButton', type: 'boolean', defaultValue: D.COPY_CONTENT_BTN, labelKey: 'copier_enableCopyContent', group: 'tools', instant: true },

                        // Copy Behavior
                        { key: 'copier_useSmartLink', type: 'boolean', defaultValue: D.SMART_LINK, labelKey: 'copier_menu_useSmartLink', group: 'tools', instant: true },
                        { key: 'copier_includeEmojis', type: 'boolean', defaultValue: D.INCLUDE_EMOJIS, labelKey: 'copier_includeEmojis', group: 'tools', instant: true },
                        { key: 'batchCopy_includeHeader', type: 'boolean', defaultValue: D.BATCH_HEADER, labelKey: 'batchCopy_includeHeader', group: 'tools' },
                        { key: 'copier_expandHashtags', type: 'boolean', defaultValue: D.EXPAND_HASHTAGS, labelKey: 'copier_expandHashtags', group: 'tools' },
                        { key: 'copier_expandMentions', type: 'boolean', defaultValue: D.EXPAND_MENTIONS, labelKey: 'copier_expand_mentions', group: 'tools' },

                        // Metadata Included (Master)
                        { key: 'copy_includeMetadata', type: 'boolean', defaultValue: D.META_MASTER, labelKey: 'copy_includeMetadata', group: 'tools', instant: true },

                        // Metadata Details
                        { key: 'copy_meta_url', type: 'boolean', defaultValue: D.META_URL, labelKey: 'copy_meta_url', group: 'tools' },
                        { key: 'copy_meta_order', type: 'boolean', defaultValue: D.META_ORDER, labelKey: 'copy_meta_order', group: 'tools' },
                        { key: 'copy_meta_author_name', type: 'boolean', defaultValue: D.META_AUTHOR, labelKey: 'copy_meta_author_name', group: 'tools' },
                        { key: 'copy_meta_author_link', type: 'boolean', defaultValue: D.META_AUTHOR_LINK, labelKey: 'copy_meta_author_link', group: 'tools' },
                        { key: 'copy_meta_date', type: 'boolean', defaultValue: D.META_DATE, labelKey: 'copy_meta_date', group: 'tools' },
                        { key: 'copy_meta_link_preview', type: 'boolean', defaultValue: D.META_PREVIEW, labelKey: 'copy_meta_link_preview', group: 'tools' },
                        { key: 'copy_meta_image_count', type: 'boolean', defaultValue: D.META_IMG_COUNT, labelKey: 'copy_meta_image_count', group: 'tools' },
                        { key: 'copy_meta_video_duration', type: 'boolean', defaultValue: D.META_VID_DURATION, labelKey: 'copy_meta_video_duration', group: 'tools' },

                        // Stats Group
                        { key: 'copy_meta_stats', type: 'boolean', defaultValue: D.META_STATS, labelKey: 'copy_meta_stats', group: 'tools' },
                        { key: 'copy_meta_stats_total', type: 'boolean', defaultValue: D.META_STATS_TOTAL, labelKey: 'copy_meta_stats_total', group: 'tools' },
                        { key: 'copy_meta_stats_detailed', type: 'boolean', defaultValue: D.META_STATS_DETAILED, labelKey: 'copy_meta_stats_detailed', group: 'tools' },

                        // Format
                        {
                            key: 'copier_permalinkFormat', type: 'select', defaultValue: D.PERMALINK_FORMAT, 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 = this._getDefinitions();
                    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 TN = this.app.modules.timelineNavigator;
                    const SI = this.app.modules.styleInjector;
                    const ER = this.app.modules.errorRecovery;
                    const TA = this.app.modules.transparencyActions;
                    const CE = this.app.modules.contentExpander;
                    const IR = this.app.modules.idRevealer;
                    switch (key) {
                        case 'permalinkCopierEnabled':
                        case 'enableCopyContentButton':
                        case 'copier_useSmartLink':
                        case 'copier_includeEmojis':
                        case 'copy_includeMetadata':
                            // Always re-evaluate buttons if any tool setting changes
                            if (PHT) PHT.reEvaluateAllButtons();
                            break;
                        case 'floatingNavEnabled':
                            if (newValue) FN.init(this.app); else FN.deinit();
                            break;
                        case 'timelineNavEnabled':
                            if (newValue) TN.init(this.app); else TN.deinit();
                            break;
                        case 'floatingNav_showAutoLoad':
                        case 'floatingNav_showBatchCopy':
                            if (FN && this.app.state.settings.floatingNavEnabled) {
                                FN.deinit();
                                FN.init(this.app);
                            }
                            break;
                        case 'hidePostStats':
                            SI.updateStatsBarVisibility(newValue);
                            break;
                        case 'errorRecoveryEnabled':
                            if (newValue) ER.init(this.app);
                            break;
                        case 'transparencyButtonsEnabled':
                            if (newValue) TA.init(this.app); else TA.deinit();
                            break;
                        case 'idRevealerEnabled':
                            if (newValue) IR.init(this.app);
                            break;
                        case 'expandContentEnabled':
                            if (newValue) CE.init(this.app);
                            break;
                    }
                },
                resetAllSettings() {
                    this.definitions.forEach(def => {
                        const oldValue = this.app.state.settings[def.key];
                        const newValue = def.defaultValue;
                        if (oldValue !== newValue) {
                            this.updateSetting(def.key, newValue);
                            if (def.instant) this.handleSettingChange(def.key, newValue, oldValue);
                        }
                    });
                },
            },

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

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

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

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

                    // --- Dependency & Layout Logic ---
                    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';

                    // Metadata Group Indentation
                    const metaKeys = [
                        'copy_meta_url', 'copy_meta_order',
                        'copy_meta_author_name', 'copy_meta_author_link',
                        'copy_meta_date', 'copy_meta_stats',
                        'copy_meta_link_preview', 'copy_meta_image_count', 'copy_meta_video_duration'
                    ];
                    if (metaKeys.includes(def.key)) {
                        wrapper.dataset.controls = 'copy_includeMetadata';
                        wrapper.style.paddingLeft = '16px';
                        wrapper.style.fontSize = '0.92em';
                        wrapper.style.borderLeft = '2px solid #eee';
                    }

                    // Stats Sub-Group Indentation
                    if (['copy_meta_stats_total', 'copy_meta_stats_detailed'].includes(def.key)) {
                        wrapper.dataset.controls = 'copy_meta_stats';
                        wrapper.style.paddingLeft = '32px';
                        wrapper.style.fontSize = '0.9em';
                        wrapper.style.borderLeft = '2px solid #eee';
                    }

                    // Floating Nav Sub-Group
                    if (['floatingNav_showAutoLoad', 'floatingNav_showBatchCopy'].includes(def.key)) {
                        wrapper.dataset.controls = 'floatingNavEnabled';
                        wrapper.style.paddingLeft = '16px';
                        wrapper.style.borderLeft = '2px solid #eee';
                    }

                    return wrapper;
                },
                _setupDependentControls(container) {
                    const controllers = {
                        autoUnmuteEnabled: container.querySelector('#setting-autoUnmuteEnabled'),
                        keyboardNavEnabled: container.querySelector('#setting-keyboardNavEnabled'),
                        permalinkCopierEnabled: container.querySelector('#setting-permalinkCopierEnabled'),
                        idRevealerEnabled: container.querySelector('#setting-idRevealerEnabled'),
                        floatingNavEnabled: container.querySelector('#setting-floatingNavEnabled'),
                        copy_includeMetadata: container.querySelector('#setting-copy_includeMetadata'),
                        copy_meta_stats: container.querySelector('#setting-copy_meta_stats'),
                    };
                    const toggleGroup = (controller, isEnabled) => {
                        container.querySelectorAll(`[data-controls="${controller.id.substring(8)}"]`).forEach(control => {
                            // Don't hide completely to preserve layout, just disable
                            control.style.opacity = isEnabled ? '1' : '0.5';
                            const input = control.querySelector('input, select');
                            if (input) {
                                input.disabled = !isEnabled;
                                // Recursively handle nested dependencies (e.g. stats children)
                                const nestedController = controllers[input.id.substring(8)];
                                if (nestedController && !isEnabled) {
                                    toggleGroup(nestedController, false);
                                } else if (nestedController && isEnabled) {
                                    toggleGroup(nestedController, input.checked);
                                }
                            }
                        });
                    };
                    const updateAll = () => Object.values(controllers).forEach(c => c && toggleGroup(c, c.checked));
                    Object.values(controllers).forEach(c => c && c.addEventListener('input', updateAll));
                    updateAll();
                },
                /**
                 * Creates and displays the settings modal.
                 * @private
                 */
                _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: '&times;', 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();
                },

                /**
                 * Closes the settings modal.
                 */
                close() {
                    if (this.modalContainer) {
                        this.modalContainer.backdrop.style.display = 'none';
                        this.modalContainer.modal.style.display = 'none';
                    }
                },

                /**
                 * Saves current settings and closes the modal, triggering reloads if necessary.
                 */
                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: {
                /**
                 * Initializes the scroll event interceptor to prevent login popups caused by scrolling.
                 */
                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: {
                /**
                 * Patches History API to dispatch custom events on navigation changes.
                 */
                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,
                /**
                 * Initializes the toast notifier.
                 * @param {Object} app - The main application instance.
                 */
                init(app) { this.app = app; },

                /**
                 * Shows a toast notification.
                 * @param {string} message - The message to display.
                 * @param {string} [type='success'] - 'success' or 'failure'.
                 * @param {number} [duration=4000] - Duration in milliseconds.
                 */
                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);

                    const hideTimer = setTimeout(() => {
                        toast.classList.remove('gm-toast-visible');
                        setTimeout(() => toast.remove(), 500);
                    }, duration);

                    return {
                        remove: () => {
                            clearTimeout(hideTimer);
                            toast.remove();
                        }
                    };
                }
            },

            styleInjector: {
                app: null,
                statsStyleElement: null,

                /**
                 * Initializes the style injector, applying base styles and user preferences.
                 * @param {Object} app - The main application instance.
                 */
                init(app) {
                    this.app = app;
                    const settings = this.app.state.settings;

                    // 1. Core Styles
                    let css = this.app.styles.CORE;

                    // 2. Hide Useless Elements
                    if (settings.hideUselessElements) {
                        css += '\n' + this.app.styles.HIDE_USELESS;
                    }

                    const styleElement = this.app.utils.createStyledElement('style', {}, { textContent: css });
                    document.head.append(styleElement);

                    // 3. Handle Stats Hiding
                    this.updateStatsBarVisibility(settings.hidePostStats);
                },

                /**
                 * Updates visibility of post statistics (likes/comments) based on settings.
                 * @param {boolean} shouldHide - Whether to hide the stats bar.
                 */
                updateStatsBarVisibility(shouldHide) {
                    if (shouldHide) {
                        if (!this.statsStyleElement) {
                            this.statsStyleElement = this.app.utils.createStyledElement('style', {}, { textContent: this.app.styles.HIDE_STATS });
                            document.head.append(this.statsStyleElement);
                        }
                    } else {
                        if (this.statsStyleElement) {
                            this.statsStyleElement.remove();
                            this.statsStyleElement = null;
                        }
                    }
                }
            },

            // Link Intervention Module
            // explicitly ignores context menu clicks to prevent unwanted auto-open behavior.
            linkIntervention: {
                app: null,

                /**
                 * Initializes the link intervention module.
                 * @param {Object} app - The main application instance.
                 */
                init(app) {
                    this.app = app;
                    // Use capture phase (true) to intercept events before Facebook's React handlers
                    document.addEventListener('click', this.handleClick.bind(this), true);
                    document.addEventListener('auxclick', this.handleClick.bind(this), true); // For middle-click

                    // Re-enable context menu (Right-click)
                    document.addEventListener('contextmenu', (e) => e.stopPropagation(), true);
                },

                /**
                 * Handles click events to intervene on links (tracking removal, auto-open media).
                 * @param {MouseEvent} event 
                 */
                handleClick(event) {
                    // Critical: Ignore Right Clicks (button 2) to allow Context Menu
                    if (event.button === 2) return;

                    // Ignore clicks inside specific UI elements
                    const target = event.target;
                    if (target.closest('[role="banner"], [role="navigation"], .gm-floating-nav')) return;

                    const link = target.closest('a');
                    if (!link || !link.href) return;

                    const settings = this.app.state.settings;
                    const isMedia = /\/photo\/?\?|\/photos?\/|\/videos?\/|\/reel\/|\/watch\/|fbid=/.test(link.href);

                    // Exclude album lists or profile tabs from being treated as single media
                    const isList = /\/photos_by\/|\/photos_albums/.test(link.href);

                    // --- Feature 1: Auto-open Media in New Tab (Prevents Login Wall) ---
                    // Only apply this intervention on LEFT CLICK (button 0)
                    if (event.button === 0 && settings.autoOpenMediaInNewTab && isMedia && !isList) {
                        // Prevent Facebook's default SPA navigation which triggers the login wall
                        event.preventDefault();
                        event.stopPropagation();

                        const cleanUrl = this.cleanUrl(link.href);
                        this.app.utils.smartOpen(event, cleanUrl);
                        return;
                    }

                    // --- Feature 2: Link Sanitization on Click ---
                    // Covers: Middle Click (button 1), Ctrl+Click, or target="_blank"
                    if (link.target === '_blank' || event.ctrlKey || event.metaKey || event.button === 1) {
                        const clean = this.cleanUrl(link.href);
                        if (clean !== link.href) link.href = clean;
                    }
                },

                /**
                 * Removes tracking parameters from the URL.
                 * @param {string} url - The URL to clean.
                 * @returns {string} The cleaned URL.
                 */
                cleanUrl(url) {
                    try {
                        const urlObj = new URL(url);

                        // Handle Facebook's "l.php" redirect wrapper
                        if (urlObj.hostname.includes('l.facebook.com') && urlObj.searchParams.has('u')) {
                            const realUrl = decodeURIComponent(urlObj.searchParams.get('u'));
                            return this.cleanUrl(realUrl); // Recursively clean the real URL
                        }

                        // Remove standard tracking parameters
                        // Uses shared utility logic from app.utils which uses app.config.TRACKING_PARAMS
                        this.app.utils.cleanUrlParams(urlObj);

                        // Specific cleanup for internal Facebook links
                        if (urlObj.hostname.includes('facebook.com')) {
                            // Keep only essential parameters
                            const keep = ['id', 'fbid', 'story_fbid', 'v', 'set', 'p', 'pb', 'q', 'query'];
                            const keys = Array.from(urlObj.searchParams.keys());
                            keys.forEach(key => {
                                if (!keep.includes(key) && !key.startsWith('qp')) {
                                    urlObj.searchParams.delete(key);
                                }
                            });
                        }
                        return urlObj.href;
                    } catch (e) {
                        return url;
                    }
                }
            },

            domCleaner: {
                app: null,
                /**
                 * Initializes the DOM cleaner to handle login dialogs and other obstructions.
                 * @param {Object} app - The main application instance.
                 */
                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();
                                            }
                                        }
                                        // Trigger restore after modal handling
                                        if (this.app.modules.scrollRestorer) this.app.modules.scrollRestorer.triggerRestore();
                                        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,
                /**
                 * Initializes the content expander to automatically click "Read more" buttons.
                 * @param {Object} app - The main application instance.
                 */
                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
                },
                // Verified Selectors: Used to intelligently trigger loading
                selectors: [
                    { selector: 'a[href*="/more/"][role="button"]' },
                    { selector: '[role="progressbar"]' },
                    { selector: '[data-visualcompletion="loading-state"]' }, // Skeleton Loader
                    { selector: '.uiMorePagerLoader' },
                    { selector: 'div[role="button"]:not([aria-label*="Create"]):not([aria-label*="Search"])' }
                ],

                /**
                 * Initializes the content auto-loader.
                 * @param {Object} app - The main application instance.
                 */
                init(app) { this.app = app; },

                /**
                 * Starts the auto-loading process.
                 */
                async start() {
                    if (this.state.isRunning) return;

                    const T = this.app.state.T;
                    const currentPosts = this.app.modules.postNavigatorCore.getSortedPosts().length;
                    const batchSize = this.app.state.settings.autoLoadBatchSize || 20;

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

                /**
                 * Stops the auto-loading process.
                 * @param {string} [reasonKey=''] - Localization key for the stop reason.
                 */
                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] || reasonKey, type);
                    }
                },

                /**
                 * Main auto-loading loop.
                 */
                async loop() {
                    const C = this.app.config.AUTO_LOADER;
                    const T = this.app.state.T;

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

                        const currentCount = this.app.modules.postNavigatorCore.getSortedPosts().length;

                        // Check if target reached
                        if (currentCount >= this.state.targetCount) {
                            this.stop('autoLoad_status_success');
                            return;
                        }

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

                        // 3. Update UI
                        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();

                        // 4. Active Trigger
                        this.triggerLoad();

                        // 5. Wait for content
                        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');
                                return;
                            }
                        }
                    }
                },

                triggerLoad() {
                    for (const target of this.selectors) {
                        const elements = document.querySelectorAll(target.selector);
                        const el = elements.length > 0 ? elements[elements.length - 1] : null;

                        if (el && el.offsetParent !== null) {
                            try {
                                const mouseEvent = new MouseEvent('mouseover', {
                                    bubbles: true,
                                    cancelable: true
                                });
                                el.dispatchEvent(mouseEvent);
                                el.click();
                            } catch (e) {
                                // Ignore errors
                            }
                            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; }

                            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() {
                    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;
                    }
                    const noSnippet = document.querySelector('div[data-nosnippet]');
                    if (noSnippet && U.isVisible(noSnippet)) return true;
                    const forms = document.querySelectorAll(C.GLOBAL.LOGIN_FORM);
                    for (const form of forms) {
                        if (U.isVisible(form) && !form.closest('[role="banner"]')) return true;
                    }
                    return false;
                },

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

                    // UX Improvement: Delayed "Processing" toast to prevent flashing on fast operations
                    let progressToast = null;
                    const progressTimer = setTimeout(() => {
                        progressToast = this.app.modules.toastNotifier.show(T.batchCopy_start.replace('{count}', posts.length), 'success');
                    }, 500);

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

                    for (const post of posts) {
                        const text = await PHT.extractPostMetadata(post, {
                            forceRawLink: true,
                            includeOrder: settings.copy_meta_order,
                            isBatch: true
                        });

                        if (text) {
                            bodyText += text + BATCH_SEPARATOR;
                            successCount++;
                        }
                    }

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

                    try {
                        await GM_setClipboard(finalText);

                        clearTimeout(progressTimer);
                        if (progressToast && typeof progressToast.remove === 'function') progressToast.remove();

                        this.app.modules.toastNotifier.show(T.batchCopy_success.replace('{count}', successCount), 'success');
                    } catch (err) {
                        clearTimeout(progressTimer);
                        if (progressToast && typeof progressToast.remove === 'function') progressToast.remove();

                        console.error(err);
                        this.app.modules.toastNotifier.show(T.copier_failure, 'failure');
                    }
                },
            },

            errorRecovery: {
                app: null,
                /**
                 * Initializes the error recovery module to monitor and fix broken pages.
                 * @param {Object} app - The main application instance.
                 */
                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,
                /**
                 * Initializes the transparency actions module (Transparency & Ad Library buttons).
                 * @param {Object} app - The main application instance.
                 */
                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);
                },

                /**
                 * Cleans up resources when the module is disabled or re-initialized.
                 */
                deinit() {
                    if (this.container) this.container.remove();
                    if (this.interval) clearInterval(this.interval);
                },

                /**
                 * Extracts the page ID from the page source or embedded JSON.
                 * @returns {string|null} The Page ID or null if not found.
                 * @private
                 */
                _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;
                },

                /**
                 * Updates the floating button UI based on the current page state.
                 */
                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';
                },

                /**
                 * Helper to create a floating transparency button.
                 * @private
                 */
                _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,
                /**
                 * Initializes the Ads Library handler to automate clicking through the library interface.
                 * @param {Object} app - The main application instance.
                 */
                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);
                    }
                },

                /**
                 * Removes blocking dialogs in the Ads Library.
                 * @private
                 */
                _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,
                /**
                 * Initializes the internal transparency handler to expanded transparency sections.
                 * @param {Object} app - The main application instance.
                 */
                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,

                /**
                 * Initializes the ID Revealer module.
                 * @param {Object} app - The main application instance.
                 */
                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());
                },

                /**
                 * Injects the ID revealer trigger into the profile header (H1).
                 */
                inject() {
                    // 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);
                        }
                    });
                },

                /**
                 * Extracts User/Page/Meta IDs from the page metadata and source.
                 * @returns {Map<string, string>} A map of label to ID.
                 */
                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;
                },

                /**
                 * Generates a link to the profile/page based on user preferences.
                 * @param {string} id - The ID to link to.
                 * @returns {string} The formatted URL.
                 */
                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}`;
                },

                /**
                 * Shows the ID badge for the given element.
                 * @param {HTMLElement} h1 - The profile header element.
                 */
                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.
                 * @private
                 */
                _extractText(node) {
                    let text = '';
                    for (const child of node.childNodes) {
                        if (child.nodeType === Node.TEXT_NODE) {
                            text += child.textContent;
                        }
                    }
                    return text.trim();
                },

                /**
                 * Creates the badge UI and appends it to the DOM.
                 * @param {HTMLElement} h1 - The profile header.
                 * @param {Map} idMap - The extracted IDs.
                 * @private
                 */
                createBadgeUI(h1, idMap) {
                    const U = this.app.utils;
                    const T = this.app.state.T;

                    const container = U.createStyledElement('div', {
                        position: 'fixed', zIndex: this.app.config.UI.Z_INDEX.ID_BADGE, 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: this.app.config.UI.DIMENSIONS.BADGE_MIN_WIDTH, maxWidth: this.app.config.UI.DIMENSIONS.BADGE_MAX_WIDTH, 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;
                },

                /**
                 * Updates the badge position relative to the header.
                 */
                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`;
                },

                /**
                 * Closes the currently active badge.
                 */
                closeBadge() {
                    if (this.activeBadge) {
                        this.activeBadge.remove();
                        this.activeBadge = null;
                        this.targetH1 = null;
                    }
                }
            },

            postNavigatorCore: {
                app: null,
                currentPostIndex: -1,
                isRetrying: false,
                continuousNavInterval: null,
                /**
                 * Initializes the core post navigator.
                 * @param {Object} app - The main application instance.
                 */
                init(app) {
                    this.app = app;
                },

                /**
                 * Retrieves all valid posts in the timeline, sorted by position.
                 * @returns {HTMLElement[]} Array of post elements.
                 */
                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;
                },

                /**
                 * Starts continuous auto-scrolling navigation in a specific direction.
                 * @param {string} direction - 'next' or 'prev'.
                 */
                startContinuousNavigation(direction) {
                    this.stopContinuousNavigation();
                    this.updateActivePost(direction === 'next' ? this.currentPostIndex + 1 : this.currentPostIndex - 1, 'manual');
                    const interval = this.app.state.settings.continuousNavInterval;
                    this.continuousNavInterval = setInterval(() => {
                        this.updateActivePost(direction === 'next' ? this.currentPostIndex + 1 : this.currentPostIndex - 1, 'manual');
                    }, interval);
                },

                /**
                 * Stops any active continuous navigation.
                 */
                stopContinuousNavigation() {
                    if (this.continuousNavInterval) {
                        clearInterval(this.continuousNavInterval);
                        this.continuousNavInterval = null;
                    }
                },

                /**
                 * Updates the currently active post index and handles scrolling/highlighting.
                 * @param {number} index - The new index to set as active.
                 * @param {string} [source='manual'] - Source of navigation ('manual', 'timeline', etc).
                 */
                updateActivePost(index, source = 'manual', forceSmooth = null) {
                    const posts = this.getSortedPosts();
                    if (posts.length === 0) return;

                    if (index >= posts.length) {
                        if (source === 'manual') {
                            this.triggerLoadAndRetry();
                            return;
                        }
                        index = posts.length - 1;
                    }
                    if (index < 0) index = 0;

                    if (index === this.currentPostIndex && source !== 'manual') return;

                    this.currentPostIndex = index;
                    const targetPost = posts[this.currentPostIndex];

                    this.app.state.lastActivePost = targetPost;

                    // 1. Update Visual Highlight (Feed) - Conditional
                    const C = this.app.config;
                    const settings = this.app.state.settings;

                    // Always remove old highlight to keep clean
                    const currentHighlighted = document.querySelector(`.${C.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS}`);
                    if (currentHighlighted && currentHighlighted !== targetPost) {
                        currentHighlighted.classList.remove(C.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS);
                    }

                    // Only add new highlight if setting is enabled
                    if (targetPost && settings.navHighlighterEnabled) {
                        targetPost.classList.add(C.SELECTORS.NAVIGATOR.HIGHLIGHT_CLASS);
                    }

                    // 2. Update Timeline UI (If module active)
                    if (this.app.modules.timelineNavigator) {
                        this.app.modules.timelineNavigator.setActive(index);
                    }

                    // 3. Scroll Page (If source requires it)
                    if (source === 'manual' || source === 'timeline') {
                        this.app.utils.scrollToElement(targetPost, forceSmooth);
                    }
                },

                /**
                 * Triggers page scroll to load more posts and retries navigation.
                 */
                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.updateActivePost(initialPostCount, 'manual');
                            return;
                        }
                        if (Date.now() - startTime > this.app.config.TIMEOUTS.MAX_RETRY_DURATION) {
                            clearInterval(retryInterval);
                            this.isRetrying = false;
                            console.log(`${this.app.config.LOG_PREFIX} Failed to load new posts.`);
                        }
                    }, this.app.config.TIMEOUTS.RETRY_INTERVAL);
                },

                /**
                 * Navigates to the next or previous post relative to current.
                 * @param {string} direction - 'next' or 'prev'.
                 */
                navigateToPost(direction) {
                    this.updateActivePost(direction === 'next' ? this.currentPostIndex + 1 : this.currentPostIndex - 1, 'manual');
                }
            },

            // --- Timeline Navigator Module ---
            timelineNavigator: {
                app: null,
                container: null,
                observer: null,
                isManualScrolling: false,
                scrollTimeout: null,
                cachedMaxLogScore: 0,
                scrollTimeout: null,
                cachedMaxLogScore: 0,
                lastUrl: '',
                sortMode: 'time', // 'time' or 'heat'

                TOTAL_REACTION_KEYWORDS: [
                    '所有心情:', 'All reactions:', 'すべてのリアクション:'
                ],

                /**
                 * Initializes the timeline navigator.
                 * @param {Object} app - The main application instance.
                 */
                init(app) {
                    this.app = app;
                    this.lastUrl = window.location.href;
                    this.createUI();
                    this.initObserver();
                    this.startWatcher();
                    this.refresh();
                },

                /**
                 * Cleans up the timeline navigator.
                 */
                deinit() {
                    if (this.container) this.container.remove();
                    if (this.observer) this.observer.disconnect();
                },

                /**
                 * Creates the timeline container in the DOM.
                 */
                createUI() {
                    const U = this.app.utils;
                    this.container = U.createStyledElement('div', {}, { id: 'gm-timeline-container' });
                    document.body.appendChild(this.container);
                },
                /**
                 * Refreshes the timeline UI, rebuilding points based on current posts.
                 */
                refresh(force = false) {
                    if (!this.container) return;
                    if (this.lastUrl !== window.location.href) {
                        this.cachedMaxLogScore = 0;
                        this.lastUrl = window.location.href;
                    }

                    const Core = this.app.modules.postNavigatorCore;
                    const settings = this.app.state.settings;
                    const posts = Core.getSortedPosts();
                    if (!force && this.container.childElementCount === posts.length + 1) return; // +1 for sort button

                    this.container.innerHTML = '';


                    const sortBtn = this.app.utils.createStyledElement('button', {}, {
                        className: 'gm-timeline-sort-btn',
                        innerHTML: this.sortMode === 'time' ? '⏱️' : '🔥',
                        title: this.app.state.T.timelineSortTooltip || 'Switch Sort Mode',
                        on: {
                            click: (e) => {
                                e.stopPropagation();
                                this.sortMode = this.sortMode === 'time' ? 'heat' : 'time';
                                this.refresh(true); // Force refresh to re-sort
                            }
                        }
                    });
                    this.container.appendChild(sortBtn);

                    const useHeatmap = settings.timelineHeatmapEnabled;
                    let displayItems = posts.map((post, index) => ({
                        post,
                        originalIndex: index,
                        score: useHeatmap ? this.getPostScore(post) : 0,
                        logScore: 0
                    }));

                    if (useHeatmap) {
                        let currentBatchMax = 0;
                        displayItems.forEach(item => {
                            const logVal = Math.log10(item.score + 1);
                            item.logScore = logVal;
                            if (logVal > currentBatchMax) currentBatchMax = logVal;
                        });

                        if (currentBatchMax > this.cachedMaxLogScore) {
                            this.cachedMaxLogScore = currentBatchMax;
                        }
                    }


                    if (this.sortMode === 'heat') {
                        displayItems.sort((a, b) => b.score - a.score);
                    }

                    displayItems.forEach((item, displayIndex) => {
                        const { post, originalIndex, score, logScore } = item;


                        let labelContent = '';
                        let idxLabel = '';
                        let isMilestone = false;

                        if (this.sortMode === 'heat') {
                            labelContent = `<span class="gm-label-time" style="font-weight:600;color:#E41E3F">${this.formatMetric(score)}</span>`;
                            idxLabel = displayIndex + 1; // Rank
                            isMilestone = idxLabel <= 3; // Highlight Top 3
                        } else {
                            labelContent = `<span class="gm-label-time">${this.extractTime(post)}</span>`;
                            idxLabel = originalIndex + 1; // Sequence
                            isMilestone = idxLabel % 10 === 0;
                        }

                        const row = this.app.utils.createStyledElement('div', {}, { className: `gm-timeline-row ${isMilestone ? 'milestone' : ''}` });

                        if (this.sortMode === 'time' && originalIndex === Core.currentPostIndex) {
                            row.classList.add('active');
                        }

                        const info = this.app.utils.createStyledElement('div', {}, {
                            className: 'gm-timeline-info',
                            innerHTML: `${labelContent}<span class="gm-label-idx ${isMilestone ? 'milestone-text' : ''}">#${idxLabel}</span>`
                        });

                        const dot = this.app.utils.createStyledElement('div', {}, { className: 'gm-timeline-dot' });

                        if (useHeatmap && this.cachedMaxLogScore > 1) {
                            const ratio = logScore / this.cachedMaxLogScore;
                            const thresholds = this.app.config.UI.HEATMAP_THRESHOLDS;
                            let heatLevel = 0;
                            for (let i = 0; i < thresholds.length; i++) {
                                if (ratio > thresholds[i]) {
                                    heatLevel = 9 - i;
                                    break;
                                }
                            }
                            if (heatLevel > 0) dot.dataset.heat = heatLevel;
                        }

                        row.appendChild(info);
                        row.appendChild(dot);

                        row.addEventListener('click', (e) => {
                            e.stopPropagation();
                            this.handleDotClick(originalIndex); // Jump to original post

                            // Manually highlight this row in Heat Mode
                            if (this.sortMode === 'heat') {
                                Array.from(this.container.children).forEach(child => child.classList.remove('active'));
                                row.classList.add('active');
                            }
                        });

                        this.container.appendChild(row);
                        if (this.observer && this.sortMode === 'time') this.observer.observe(post);
                    });
                },

                /**
                 * Calculates an engagement score for a post to determine heatmap color.
                 * @param {HTMLElement} postEl 
                 * @returns {number} The calculated score.
                 */
                getPostScore(postEl) {
                    let score = 0;

                    // 1. Total Reactions
                    const totalReactions = this.extractTotalReactions(postEl);
                    if (totalReactions > 0) {
                        score += totalReactions;
                    } else {
                        // Fallback: Aria Label
                        const ariaNodes = postEl.querySelectorAll('[aria-label]');
                        for (const node of ariaNodes) {
                            if (node.tagName === 'A') continue;
                            const label = node.getAttribute('aria-label');
                            if (label && /[::]\s*[\d,.]+/.test(label)) {
                                score += this.parseMetric(label);
                                break;
                            }
                        }
                    }

                    // 2. Deep Interactions (Comments & Shares)
                    score += this.extractIconStats(postEl);

                    return score;
                },

                // Smart Skip for Numbering (Already verified working)
                /**
                 * Extracts total reaction count from the post footer.
                 * @private
                 * @param {HTMLElement} postEl - The post element.
                 * @returns {number} The total reaction count.
                 */
                extractTotalReactions(postEl) {
                    const candidates = postEl.querySelectorAll('div');
                    for (const div of candidates) {
                        const text = div.textContent.trim();
                        if (this.TOTAL_REACTION_KEYWORDS.some(kw => text.includes(kw))) {
                            let nextEl = div.nextElementSibling;
                            let attempts = 0;
                            while (nextEl && attempts < 3) {
                                const rawValue = nextEl.textContent.replace(/\u00a0/g, ' ').trim();
                                if (!rawValue.includes('#') && (rawValue.length > 3 || /[KkMm萬億万]/.test(rawValue) || parseFloat(rawValue.replace(/,/g, '')) > 500)) {
                                    const val = this.parseMetric(rawValue);
                                    if (val > 0) return val;
                                }
                                nextEl = nextEl.nextElementSibling;
                                attempts++;
                            }
                        }
                    }
                    return 0;
                },

                /**
                 * Extracts deep interaction stats (comments/shares) using sprite position analysis.
                 * @private
                 * @param {HTMLElement} postEl - The post element.
                 * @returns {number} The calculated score based on interactions.
                 */
                extractIconStats(postEl) {
                    let localScore = 0;
                    const icons = postEl.querySelectorAll('i[data-visualcompletion="css-img"]');
                    const SPRITES = this.app.config.CONSTANTS.SPRITE_OFFSETS;

                    icons.forEach(icon => {
                        const style = icon.getAttribute('style') || '';
                        const match = style.match(/background-position:\s*0px\s+(-?\d+)px/);
                        if (match) {
                            const yPos = parseInt(match[1], 10);
                            const isComment = Math.abs(yPos - SPRITES.COMMENT) < SPRITES.TOLERANCE;
                            const isShare = Math.abs(yPos - SPRITES.SHARE) < SPRITES.TOLERANCE;

                            if (isComment || isShare) {
                                const text = this.findNearbyNumber(icon);
                                const val = this.parseMetric(text);
                                if (val > 0) {
                                    // Weighted: Share x3, Comment x2
                                    localScore += val * (isShare ? 3 : 2);
                                }
                            }
                        }
                    });
                    return localScore;
                },

                // Strict Single-Number Check to prevent grabbing parent rows
                /**
                 * Finds a nearby text node containing a number (e.g., stats count).
                 * @private
                 * @param {HTMLElement} element - The starting element (usually an icon).
                 * @returns {string} The found text, or empty string.
                 */
                findNearbyNumber(element) {
                    let p = element.parentElement;
                    for (let i = 0; i < 4; i++) { // Increased depth limit slightly
                        if (!p) break;
                        const text = p.textContent.trim();

                        // Check how many distinct number groups exist in this text
                        // e.g., "1.9K 153" has 2 matches. "2K" has 1 match.
                        const matches = text.match(/(\d+(?:[.,]\d+)?)\s*([KkMm萬億万]?)/g);

                        // Only accept if exactly ONE number group is found
                        // This forces the loop to ignore the "Total + Comment + Share" wrapper row
                        if (matches && matches.length === 1) {
                            // Secondary check: Ensure the text is CLEAN (mostly digits/units)
                            // Allows for small noise but rejects large blocks of text
                            if (/^[\d.,KkMm萬億万\s]+$/.test(text)) {
                                return text;
                            }
                        }
                        p = p.parentElement;
                    }
                    return '';
                },

                /**
                 * Parses a metric string (e.g. "1.5K") into a number.
                 * @param {string} str 
                 * @returns {number} The parsed value.
                 */
                /**
                 * Formats a number into a compact metric string (e.g. 1.2k).
                 * @param {number} num 
                 * @returns {string} The formatted string.
                 */
                formatMetric(num) {
                    if (num >= 1000000) return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
                    if (num >= 1000) return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
                    return num.toString();
                },

                parseMetric(str) {
                    if (!str) return 0;
                    const match = str.match(/(\d+(?:[.,]\d+)?)\s*([KkMm萬億万]?)/);
                    if (!match) return 0;
                    let num = parseFloat(match[1].replace(/,/g, ''));
                    const unit = match[2].toLowerCase();
                    if (unit === 'k') num *= 1000;
                    else if (unit === 'm') num *= 1000000;
                    else if (unit === '萬' || unit === '万') num *= 10000;
                    else if (unit === '億') num *= 100000000;
                    return Math.floor(num);
                },

                extractTime(postEl) {
                    const links = Array.from(postEl.querySelectorAll('span > a[role="link"]'));
                    for (const link of links) {
                        if (link.closest('h2, h3, h4, strong, b')) continue;
                        if (link.closest('[data-ad-rendering-role="profile_name"]')) continue;
                        if (link.closest('[data-ad-rendering-role="story_message"]')) continue;
                        if (link.closest('ul, li')) continue;

                        const text = link.textContent.trim();
                        if (!text) continue;
                        if (text.length > 50) continue;

                        const enMonthRegex = /^(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d+/i;
                        if (enMonthRegex.test(text)) return text;

                        if (/^\d/.test(text)) {
                            const shortUnitRegex = /^\d+\s*([mhdswy]|分鐘|小時|天|分|時間|日)$/;
                            if (shortUnitRegex.test(text)) return text;
                            if (/[年月]/.test(text) || text.includes(' at ') || text.includes(',')) return text;
                        }
                    }
                    return '';
                },

                /**
                 * Initializes the intersection observer to track post visibility.
                 * @private
                 */
                initObserver() {
                    const CONF = this.app.config.CONSTANTS.OBSERVER;
                    const options = {
                        root: null,
                        rootMargin: CONF.ROOT_MARGIN,
                        threshold: CONF.THRESHOLD
                    };
                    this.observer = new IntersectionObserver((entries) => {
                        if (this.isManualScrolling) return;
                        entries.forEach(entry => {
                            if (entry.isIntersecting) {
                                const Core = this.app.modules.postNavigatorCore;
                                const posts = Core.getSortedPosts();
                                const idx = posts.indexOf(entry.target);
                                if (idx !== -1) {
                                    Core.updateActivePost(idx, 'scroll');
                                }
                            }
                        });
                    }, options);
                },

                /**
                 * Sets the active timeline point.
                 * @param {number} index - The index of the active post.
                 */
                setActive(index) {
                    if (!this.container) return;
                    if (this.sortMode === 'heat') return; // Disable auto-active in Heat Mode

                    const rows = this.container.children;
                    // Skip the first child (button)
                    const rowOffset = 1;

                    for (let i = rowOffset; i < rows.length; i++) {
                        // rows[i] corresponds to post index i - rowOffset
                        if ((i - rowOffset) === index) {
                            rows[i].classList.add('active');
                            this.ensureVisible(rows[i]);
                        } else {
                            rows[i].classList.remove('active');
                        }
                    }
                },

                /**
                 * Ensures the active row is visible within the timeline container.
                 */
                ensureVisible(row) {
                    if (this.container.matches(':hover')) return;
                    const cRect = this.container.getBoundingClientRect();
                    const rRect = row.getBoundingClientRect();
                    if (rRect.top < cRect.top + 20 || rRect.bottom > cRect.bottom - 20) {
                        row.scrollIntoView({ behavior: 'smooth', block: 'center' });
                    }
                },

                /**
                 * Handles clicks on timeline dots to navigate to the corresponding post.
                 * @param {number} index - The index of the post to navigate to.
                 */
                handleDotClick(index) {
                    this.isManualScrolling = true;
                    // Force instant scrolling (forceSmooth = false) if in Heat Mode to prevent motion sickness
                    const forceSmooth = this.sortMode === 'heat' ? false : null;
                    this.app.modules.postNavigatorCore.updateActivePost(index, 'timeline', forceSmooth);
                    if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
                    this.scrollTimeout = setTimeout(() => { this.isManualScrolling = false; }, this.app.config.TIMEOUTS.SCROLL_LOCK);
                },

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

            scrollRestorer: {
                app: null,
                /**
                 * Initializes the scroll restorer module.
                 * @param {Object} app - The main application instance.
                 */
                init(app) {
                    this.app = app;
                    // Listen for ANY click (Close button or Backdrop)
                    document.body.addEventListener('click', this.handleInteraction.bind(this), true);
                    // Listen for ESC key
                    document.addEventListener('keydown', (e) => {
                        if (e.key === 'Escape') this.handleInteraction();
                    }, true);
                },

                /**
                 * Triggers scroll restoration specifically when modal disappears.
                 */
                triggerRestore() {
                    const lastPost = this.app.state.lastActivePost;
                    // Verify post still exists and is in DOM
                    if (lastPost && document.body.contains(lastPost)) {
                        // Restore instantly (false) to snap back to position
                        // This will now use app.utils.scrollToElement which respects Alignment settings
                        this.app.utils.scrollToElement(lastPost, false);
                    }
                },

                /**
                 * Handles user interactions that might close a modal.
                 */
                handleInteraction() {
                    // 1. Check if a dialog currently exists
                    const dialogSelector = this.app.config.SELECTORS.GLOBAL.DIALOG;
                    const dialogBefore = document.querySelector(dialogSelector);

                    if (dialogBefore && this.app.utils.isVisible(dialogBefore)) {
                        // 2. Wait a short moment to let FB process the event (remove/hide modal)
                        setTimeout(() => {
                            const dialogAfter = document.querySelector(dialogSelector);
                            // 3. If dialog is gone (or hidden), trigger restore
                            if (!dialogAfter || !this.app.utils.isVisible(dialogAfter)) {
                                this.triggerRestore();
                            }
                        }, 150); // Slight buffer for DOM updates
                    }
                }
            },

            autoUnmuter: {
                app: null,
                /**
                 * Initializes the auto-unmuter module to handle video volume.
                 * @param {Object} app - The main application instance.
                 */
                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,
                /**
                 * Initializes the post numbering module.
                 * @param {Object} app - The main application instance.
                 */
                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,
                    showTimer: null,
                    wasDialogVisible: false,
                    suppressShow: false,
                    isUpdating: false,
                },
                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;
                },

                /**
                 * Initializes the Search Bar module.
                 * @param {Object} app - The main application instance.
                 */
                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();
                    }, this.app.config.TIMEOUTS.INITIAL_DELAY);
                },
                /**
                 * Retrieves the current page name from the h1 element.
                 * @returns {string|null} The page name or null if not found.
                 * @private
                 */
                _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();
                },

                /**
                 * Creates the search bar UI elements.
                 * @returns {Object} map of created elements.
                 * @private
                 */
                _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(), this.app.config.TIMEOUTS.THROTTLE_SCROLL); });
                    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(), this.app.config.TIMEOUTS.THROTTLE_SCROLL), { passive: true });
                    window.addEventListener('historyChange', () => this.updateVisibility());

                    // Throttle set to 200ms to allow smooth modal transitions (Prevents Layout Thrashing)
                    new MutationObserver(this.app.utils.throttle(() => this.updateVisibility(), this.app.config.TIMEOUTS.THROTTLE_UI)).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);
                },

                /**
                 * Updates the visibility of the search bar based on page context and scroll.
                 */
                updateVisibility() {
                    if (this.state.isUpdating) return;

                    requestAnimationFrame(() => {
                        this.state.isUpdating = true;

                        const C = this.app.config;
                        const currentPath = window.location.pathname;
                        const dialogExists = !!document.querySelector(C.SELECTORS.GLOBAL.DIALOG);

                        // 1. Detection: Dialog Closed
                        if (this.state.wasDialogVisible && !dialogExists) {
                            if (!this.state.isPinned) {
                                this.state.suppressShow = true;
                                if (this.state.showTimer) {
                                    clearTimeout(this.state.showTimer);
                                    this.state.showTimer = null;
                                }
                                this._hideBar();

                                // Schedule recovery
                                setTimeout(() => {
                                    this.state.suppressShow = false;
                                    // Re-run visibility check to restore 'visible' state if appropriate
                                    this.updateVisibility();
                                }, 400);
                            }
                        }
                        this.state.wasDialogVisible = dialogExists;

                        // 2. Determine Visibility
                        let isDisallowed = ['/photo/', '/videos/', '/reel/', '/posts/', '/watch/'].some(p => currentPath.startsWith(p)) || dialogExists;

                        if (this.state.suppressShow) {
                            isDisallowed = true; // Treats it as disallowed, thus 'hidden'
                        }

                        const newVisibility = isDisallowed ? 'hidden' : 'visible';

                        // 3. Apply Visibility
                        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);
                        }

                        // ... Contextual Search Logic ...
                        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 = '';

                        // 4. Position Handling
                        // If pinned and not disallowed, show it.
                        if (this.state.isPinned && !isDisallowed) {
                            this._showBar();
                        } else {
                            this._handleScroll();
                        }

                        this._updateSearchButtonState();

                        setTimeout(() => { this.state.isUpdating = false; }, 0);
                    });
                },

                _handleScroll(force = false) {
                    if (this.state.isPinned) {
                        if (force) this._showBar();
                        return;
                    }

                    if (this.state.suppressShow) {
                        if (this.state.showTimer) { clearTimeout(this.state.showTimer); this.state.showTimer = null; }
                        this._hideBar();
                        return;
                    }

                    const currentScrollY = window.scrollY;
                    const isAtTop = currentScrollY < 10;

                    if (!this.state.isAtTop && isAtTop) {
                        this.state.isAtTop = true;

                        if (this.state.showTimer) clearTimeout(this.state.showTimer);
                        this.state.showTimer = setTimeout(() => {
                            if (!this.state.suppressShow) {
                                this._showBar();
                                this.elements.hoverHint.classList.remove('gm-visible');
                                if (this.elements.toolbar.contains(document.activeElement)) document.activeElement.blur();
                            }
                            this.state.showTimer = null;
                        }, 200);
                    }
                    else if (this.state.isAtTop && !isAtTop) {
                        this.state.isAtTop = false;
                        if (this.state.showTimer) { clearTimeout(this.state.showTimer); this.state.showTimer = null; }

                        if (!this.elements.toolbar.contains(document.activeElement)) {
                            this._hideBar();
                        }
                        this.elements.hoverHint.classList.add('gm-visible');
                    }

                    if (force) {
                        if (isAtTop) {
                            if (this.state.showTimer) { clearTimeout(this.state.showTimer); this.state.showTimer = null; }
                            this._showBar();
                            this.elements.hoverHint.classList.remove('gm-visible');
                        } else {
                            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) {
                        if (this.state.showTimer) { clearTimeout(this.state.showTimer); this.state.showTimer = null; }
                        this._showBar();
                        hoverHint.classList.remove('gm-visible');
                    } else {
                        this._handleScroll();
                    }
                },
                _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,
                /**
                 * Initializes the keyboard navigator.
                 * @param {Object} app - The main application instance.
                 */
                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,
                /**
                 * Initializes the floating navigation widget.
                 * @param {Object} app - The main application instance.
                 */
                init(app) {
                    if (this.isInitialized) return;
                    this.app = app;
                    if (!this.app.state.settings.floatingNavEnabled) return;
                    const T = this.app.state.T;
                    const U = this.app.utils;
                    const settings = this.app.state.settings;
                    // Dynamic reference
                    const getCore = () => this.app.modules.postNavigatorCore;
                    const getLoader = () => this.app.modules.contentAutoLoader;

                    this.container = U.createStyledElement('div', {}, { className: 'gm-floating-nav' });

                    // --- Navigation Buttons ---
                    const prevButton = U.createStyledElement('button', {}, { title: T.floatingNavPrevTooltip });
                    prevButton.innerHTML = `<svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"></path></svg>`;
                    prevButton.addEventListener('mousedown', () => { const c = getCore(); if (c) c.startContinuousNavigation('prev'); });
                    prevButton.addEventListener('mouseleave', () => { const c = getCore(); if (c) c.stopContinuousNavigation(); });

                    const nextButton = U.createStyledElement('button', {}, { title: T.floatingNavNextTooltip });
                    nextButton.innerHTML = `<svg viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"></path></svg>`;
                    nextButton.addEventListener('mousedown', () => { const c = getCore(); if (c) c.startContinuousNavigation('next'); });
                    nextButton.addEventListener('mouseleave', () => { const c = getCore(); if (c) c.stopContinuousNavigation(); });

                    document.body.addEventListener('mouseup', () => { const c = getCore(); if (c) c.stopContinuousNavigation(); });
                    this.container.append(prevButton, nextButton);

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

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

                    // --- Batch Copy Button ---
                    if (settings.floatingNav_showBatchCopy) {
                        const btnBatchCopy = U.createStyledElement('button', {}, { title: T.tooltipBatchCopy });
                        // Icon: Clipboard
                        btnBatchCopy.innerHTML = `<svg viewBox="0 0 24 24"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"></path></svg>`;
                        btnBatchCopy.addEventListener('click', () => {
                            const loader = getLoader();
                            if (loader) loader.copyAllPosts();
                        });
                        this.container.appendChild(btnBatchCopy);
                    }

                    document.body.appendChild(this.container);
                    this.updateVisibility();
                    window.addEventListener('historyChange', this.updateVisibility.bind(this));
                    new MutationObserver(U.throttle(this.updateVisibility.bind(this), 200)).observe(document.body, { childList: true, subtree: true });
                    this.isInitialized = true;
                },
                deinit() {
                    if (this.container) {
                        this.container.remove();
                        this.container = null;
                        this.btnAutoLoad = null;
                    }
                    this.isInitialized = false;
                },
                updateButtonState(state) {
                    if (!this.btnAutoLoad) return;
                    const T = this.app.state.T;
                    if (state === 'start') {
                        // Change to Stop Icon (Square)
                        this.btnAutoLoad.innerHTML = `<svg viewBox="0 0 24 24" fill="#e02424"><path d="M6 6h12v12H6z"></path></svg>`;
                        this.btnAutoLoad.title = T.tooltipAutoLoadStop;
                        this.btnAutoLoad.style.borderColor = '#e02424';
                    } else {
                        // Revert to Start Icon (Arrow Down)
                        this.btnAutoLoad.innerHTML = `<svg viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></svg>`;
                        this.btnAutoLoad.title = T.tooltipAutoLoadStart;
                        this.btnAutoLoad.style.borderColor = '#ddd';
                    }
                },
                updateVisibility() {
                    if (!this.container) return;
                    const isVisible = this.app.utils.isFeedPage() && !document.querySelector(this.app.config.SELECTORS.GLOBAL.DIALOG);
                    this.container.style.display = isVisible ? 'flex' : 'none';
                }
            },

            wheelNavigator: {
                app: null,
                isCoolingDown: false,
                /**
                 * Initializes the mouse wheel navigator.
                 * @param {Object} app - The main application instance.
                 */
                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,
                /**
                 * Initializes the click-to-focus navigator.
                 * @param {Object} app - The main application instance.
                 */
                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) {
                        // Update active index silently to sync timeline
                        Core.updateActivePost(newIndex, 'scroll');
                    }
                }
            },

            postHeaderTools: {
                app: null,
                isProcessingClick: false,
                isModalOpening: false,
                observer: null,

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

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

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

                // --- Feature 1: Permalink Copier Logic (Standalone) ---
                /**
                 * Handles clicks on the permalink copy button.
                 * @param {Event} event - The click event.
                 * @param {HTMLElement} button - The button element.
                 */
                async handlePermalinkClick(event, button) {
                    if (this.isProcessingClick) return;
                    event.preventDefault(); event.stopPropagation();

                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const postEl = button.closest(`[data-${C_TOOLS.PROCESSED_MARKER}]`);
                    if (!postEl) return;

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

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

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

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

                // --- Feature 2: Smart Copy Content Logic ---
                /**
                 * Handles clicks on the copy content button.
                 * @param {Event} event - The click event.
                 * @param {HTMLElement} button - The button element.
                 */
                async handleCopyContentClick(event, button) {
                    if (this.isProcessingClick) return;
                    event.preventDefault(); event.stopPropagation();

                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const postEl = button.closest(this.app.config.SELECTORS.GLOBAL.POST_CONTAINER);
                    const T = this.app.state.T;

                    this.isProcessingClick = true;
                    button.style.pointerEvents = 'none';
                    const originalContent = button.innerHTML;

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

                    try {
                        const text = await this.extractPostMetadata(postEl, { includeOrder: false });

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

                // --- Core Text Extraction Logic (Metadata + Topology) ---
                /**
                 * Extracts all metadata and content from a post element.
                 * @param {HTMLElement} postEl - The post container element.
                 * @param {Object} [options={}] - Extraction options.
                 * @returns {Promise<string>} The formatted text content.
                 */
                async extractPostMetadata(postEl, options = {}) {
                    if (!postEl) return null;
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    const contentContainer = postEl.querySelector(C_TOOLS.CONTENT_BODY);

                    // 1. Expand Content
                    await this._expandReadMore(contentContainer);

                    // 2. Build Metadata Header
                    const metadataParts = await this._buildMetadataHeader(postEl, options);

                    // 3. Process Content Body
                    const bodyText = this._processContentBody(contentContainer);
                    if (bodyText) metadataParts.push(bodyText);
                    else metadataParts.push('[No Text Content]');

                    // 4. Link Preview
                    const previewText = this._extractLinkPreview(postEl);
                    if (previewText) metadataParts.push(previewText);

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

                /**
     * Expands "Read More" links to ensure full text availability.
     * @private
     */
                async _expandReadMore(contentContainer) {
                    if (!contentContainer) return;
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;
                    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) => {
                            expandBtn.click();
                            setTimeout(resolve, 150);
                        });
                    }
                },

                /**
     * Builds the metadata header with author, date, and stats.
     * @private
     */
                async _buildMetadataHeader(postEl, options) {
                    const settings = this.app.state.settings;
                    const T = this.app.state.T;
                    const parts = [];
                    const SECTION_SEPARATOR = options.isBatch ? '\n---\n' : '\n-----------------------------------\n';

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

                    // [Meta] Order, Author Name, Author Link (Combined)
                    if (settings.copy_includeMetadata) {
                        let headerLine = '';

                        // Order
                        if (options.includeOrder) {
                            const order = postEl.getAttribute('aria-posinset');
                            if (order) headerLine += `[#${order}] `;
                        }

                        // Author Name
                        const authorEl = postEl.querySelector('div[data-ad-rendering-role="profile_name"] h2 strong a, div[data-ad-rendering-role="profile_name"] h2 a');
                        if (settings.copy_meta_author_name && authorEl) {
                            headerLine += authorEl.textContent.trim();
                        }

                        // Author Link (Mention Style)
                        if (settings.copy_meta_author_link && authorEl && authorEl.href) {
                            try {
                                const authorUrlObj = new URL(authorEl.href);
                                this.app.utils.cleanUrlParams(authorUrlObj);
                                let authorUrl = authorUrlObj.href;
                                if (!authorUrl.includes('profile.php')) {
                                    authorUrl = authorUrl.replace(/\/$/, '');
                                }

                                if (headerLine) {
                                    headerLine += ` (${authorUrl})`;
                                } else {
                                    headerLine += authorUrl;
                                }
                            } catch (e) {
                                if (headerLine) headerLine += ` (${authorEl.href})`;
                                else headerLine += authorEl.href;
                            }
                        }

                        if (headerLine) parts.push(headerLine);
                    }

                    // [Meta] Date & Stats & Image Count & Video Duration
                    if (settings.copy_includeMetadata) {
                        let infoLine = '';

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

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

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

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

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

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

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

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

                            const candidates = Array.from(postEl.querySelectorAll('div, span'));

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

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

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

                        if (infoLine) parts.push(infoLine);
                    }

                    if (parts.length > 0) parts.push(SECTION_SEPARATOR);
                    return parts;
                },

                /**
                 * Processes the main content body, restoring emojis and formatting text.
                 * @private
                 */
                _processContentBody(contentContainer) {
                    if (!contentContainer) return '';
                    const settings = this.app.state.settings;
                    let targetContainer = contentContainer.cloneNode(true);

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

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

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

                        let fullUrl = href;

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

                        // Sanitize Params
                        try {
                            const urlObj = new URL(fullUrl, window.location.origin);
                            this.app.utils.cleanUrlParams(urlObj);
                            fullUrl = urlObj.href;
                        } catch (e) { }

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


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

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

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

                        link.textContent = fullUrl;
                    });

                    return this.app.utils.smartTextExtract(targetContainer);
                },

                /**
                 * Extracts link preview information including title, description, and source.
                 * @private
                 */
                _extractLinkPreview(postEl) {
                    const settings = this.app.state.settings;
                    const T = this.app.state.T;
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;

                    // --- 5. Link Preview ---
                    if (settings.copy_includeMetadata && settings.copy_meta_link_preview) {
                        const previewLinks = Array.from(postEl.querySelectorAll('a[role="link"]'));
                        let previewLink = null;

                        // Identify the Title Element first (Anchor point)
                        const titleEl = postEl.querySelector('[data-ad-rendering-role="title"]');

                        // Strategy 1: ID Association (Robust for Overlay Links)
                        // New FB structure: Content <div id="A"> + Link <a aria-labelledby="A">
                        if (titleEl) {
                            const contentContainer = titleEl.closest('div[id]');
                            if (contentContainer) {
                                const id = contentContainer.id;
                                // Look for specific sibling link referencing this content
                                const linkedAnchor = postEl.querySelector(`a[aria-labelledby="${id}"]`);
                                if (linkedAnchor) previewLink = linkedAnchor;
                            }
                        }

                        // Strategy 2: Standard Overlay (Ancestor search)
                        if (!previewLink && titleEl) {
                            const container = titleEl.closest('div[class*="x"]'); // Generic container
                            if (container && container.parentElement) {
                                // Search in parent's scope for the overlay link
                                const overlay = container.parentElement.querySelector('a[href][role="link"]');
                                if (overlay && overlay.getAttribute('href')) previewLink = overlay;
                            }
                        }

                        // Strategy 3: Standard Nesting (Old FB)
                        if (!previewLink) {
                            previewLink = previewLinks.find(a =>
                                a.querySelector('[data-ad-rendering-role="title"]') &&
                                !a.closest(C_TOOLS.CONTENT_BODY)
                            );
                        }

                        // Strategy 4: Visual Fallback
                        if (!previewLink) {
                            previewLink = previewLinks.find(a => {
                                if (a.closest(C_TOOLS.CONTENT_BODY)) return false;
                                return a.querySelector('[style*="webkit-line-clamp"]');
                            });
                        }

                        // Extract Data
                        if (titleEl || previewLink) {
                            const parts = [];
                            const SECTION_SEPARATOR = '\n-----------------------------------\n';
                            parts.push(SECTION_SEPARATOR);

                            // 1. Title
                            let title = '';
                            if (titleEl) {
                                title = titleEl.textContent.trim();
                            } else if (previewLink) {
                                const styledTitle = previewLink.querySelector('[style*="webkit-line-clamp"]');
                                if (styledTitle) title = styledTitle.textContent.trim();
                            }

                            // 2. Source / Meta (Enhanced for Small Previews)
                            let meta = '';
                            const metaNode = postEl.querySelector('[data-ad-rendering-role="meta"]');

                            if (metaNode) {
                                meta = metaNode.textContent.trim();
                            } else if (titleEl) {
                                let curr = titleEl;
                                // Traverse up a few levels to find the "row" wrapper
                                for (let i = 0; i < 5; i++) {
                                    if (!curr || curr === postEl) break;
                                    const prev = curr.previousElementSibling;
                                    // If we find a previous sibling with text that isn't an image/icon
                                    if (prev && prev.textContent.trim().length > 0 && !prev.querySelector('img')) {
                                        meta = prev.textContent.trim();
                                        break;
                                    }
                                    curr = curr.parentElement;
                                }
                            }
                            if (meta.length > 30) meta = '';

                            // 3. Description
                            const descNode = postEl.querySelector('[data-ad-rendering-role="description"]');
                            const desc = descNode ? descNode.textContent.trim() : '';

                            // 4. Link URL
                            let href = previewLink ? previewLink.getAttribute('href') : '';
                            if (href) {
                                try {
                                    const urlObj = new URL(href, window.location.origin);
                                    if (urlObj.hostname.includes('facebook.com') && urlObj.searchParams.has('u')) {
                                        href = decodeURIComponent(urlObj.searchParams.get('u'));
                                    }
                                    // Sanitization
                                    const cleanUrlObj = new URL(href, window.location.origin);
                                    this.app.utils.cleanUrlParams(cleanUrlObj);
                                    href = cleanUrlObj.toString();
                                } catch (e) { }
                            }

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

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

                /**
                 * Extracts text content using layout topology, handling background posts uniquely.
                 * @param {HTMLElement} container - The container to extract text from.
                 * @returns {string} The extracted text.
                 */
                extractTextByTopology(container) {
                    const C_TOOLS = this.app.config.SELECTORS.POST_TOOLS;

                    // Feature Detection: Background Image Post
                    // Detect specific style pattern: background-image present + centered text usually
                    const hasBgImage = container.querySelector('div[style*="background-image"]');

                    if (hasBgImage) {
                        // Strategy: Get all potential text blocks, then deduplicate content.
                        // Background posts often render text twice: once for visual, once for a11y (aria-hidden).
                        // We cannot rely on 'x6s0dn4' class, so we use content equality check.

                        const candidates = Array.from(container.querySelectorAll('div[dir="auto"], div[style*="text-align: center"]'));
                        // Filter for leaf-ish nodes that actually contain text
                        const textBlocks = candidates.filter(el => {
                            // Must have text
                            if (!el.innerText.trim()) return false;
                            // Must not contain other block candidates (ensure it's a leaf node)
                            return !el.querySelector('div[dir="auto"]');
                        });

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

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

                            seenContent.add(text);
                            uniqueText.push(text);
                        }

                        if (uniqueText.length > 0) {
                            return uniqueText.join('\n');
                        }

                        // Fallback
                        return container.innerText.trim();
                    }

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

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

                    let finalString = leafBlocks[0].innerText.trim();
                    for (let i = 1; i < leafBlocks.length; i++) {
                        const prevBlock = leafBlocks[i - 1];
                        const currBlock = leafBlocks[i];
                        const currText = currBlock.innerText.trim();

                        const isSibling = currBlock.parentElement === prevBlock.parentElement;
                        const separator = isSibling ? '\n' : '\n\n';

                        finalString += separator + currText;
                    }
                    return finalString;
                },

                // --- Shared Helpers ---
                async animateButtonFeedback(button, status, originalContent) {
                    const T = this.app.state.T;
                    const settings = this.app.state.settings;
                    const iconWrapper = button.querySelector('.gm-icon-wrapper');
                    if (iconWrapper) iconWrapper.innerHTML = status === 'success' ? this.icons.success : this.icons.failure;

                    button.style.backgroundColor = status === 'success' ? 'var(--positive-background)' : 'var(--negative-background)';
                    await this.app.utils.delay(1200);
                    button.style.pointerEvents = 'auto';
                    button.style.backgroundColor = 'transparent';
                    button.innerHTML = originalContent;
                },

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

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

                    if (!settings.permalinkCopierEnabled && !settings.enableCopyContentButton) return;
                    if (!headerEl?.parentElement || headerEl.parentElement.querySelector(`.gm-tools-wrapper`)) return;

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

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

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

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

                    insertionPoint.appendChild(wrapper);
                },
                createButton(action, svgIcon, title) {
                    const C = this.app.config.SELECTORS.POST_TOOLS;

                    const clickHandler = (e) => {
                        if (action === 'permalink') {
                            this.handlePermalinkClick(e, e.currentTarget);
                        } else if (action === 'copy-content') {
                            this.handleCopyContentClick(e, e.currentTarget);
                        }
                    };

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

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

        async handleWorkerTask() {
            const urlParams = new URLSearchParams(window.location.search);
            const taskId = urlParams.get(this.config.WORKER_PARAM);
            if (!taskId) return;

            // Worker Mode: Hide everything immediately
            document.body.style.display = 'none';

            // Robustness Fix: Direct access to required settings
            // We avoid initializing the entire app or settingsManager to prevent race conditions or dependency errors in the worker context/tab.
            const permalinkFormat = GM_getValue('copier_permalinkFormat', 'author_id');

            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;

                if (permalinkFormat === 'shortest') return `https://fb.com/${info.postId}`;

                if (permalinkFormat === 'full' && info.canonicalUrl) {
                    try {
                        const u = new URL(info.canonicalUrl);
                        u.search = '';
                        return u.href.replace(/\/$/, '');
                    } catch (e) { }
                }

                if (permalinkFormat === 'username' && info.username && info.username !== 'profile.php') {
                    return `https://www.facebook.com/${info.username}/posts/${info.postId}`;
                }

                // Default / 'author_id'
                if (info.profileId) return `https://www.facebook.com/${info.profileId}/posts/${info.postId}`;
                return `https://www.facebook.com/posts/${info.postId}`; // Fallback if no profile ID
            };

            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';

                GM_setValue(taskId, { permalink, method });
            } catch (error) {
                console.error(`${this.config.LOG_PREFIX} [Worker] Critical Error:`, error);
                // Report error back to main tab via method field
                GM_setValue(taskId, { permalink: fallbackUrl, method: 'error_exception' });
            } finally {
                // Safety cleanup: Close tab after 5s if main tab doesn't acknowledge
                const closeTimeout = setTimeout(() => {
                    if (listenerId) GM_removeValueChangeListener(listenerId);
                    window.close();
                }, 5000);

                // Wait for main tab to acknowledge receipt (resolved: true)
                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 === 'timelineNavigator' && !this.state.settings.timelineNavEnabled) 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;

                            // Initialize the Link Intervention module
                            if (moduleName === 'linkIntervention') {
                                module.init(this);
                                continue;
                            }

                            module.init(this);
                        } catch (error) {
                            console.error(`${this.config.LOG_PREFIX} Failed to initialize module '${moduleName}':`, error);
                        }
                    }
                }
                console.log(`${this.config.LOG_PREFIX} All modules initialized.`);
            });
        },
    };

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