Gelbooru Suite

Enhances Gelbooru with a categorized pop-up search, thumbnail previews, an immersive deck viewer, and more.

// ==UserScript==
// @name        Gelbooru Suite
// @namespace   GelbooruEnhancer
// @version     1.7.3
// @description Enhances Gelbooru with a categorized pop-up search, thumbnail previews, an immersive deck viewer, and more.
// @author      Testador (Refactored by Gemini)
// @match       *://gelbooru.com/*
// @icon        https://gelbooru.com/layout/gelbooru-logo.svg
// @grant       GM_download
// @grant       GM.xmlHttpRequest
// @grant       GM.getValue
// @grant       GM.setValue
// @grant       GM_registerMenuCommand
// @grant       GM_addStyle
// @grant       GM_openInTab
// @license     MIT
// @run-at      document-idle
// ==/UserScript==

/* global navigatePrev, navigateNext */

(function() {
    'use strict';

    // =================================================================================
    // CONFIGURATION AND CONSTANTS MODULE
    // =================================================================================
    const Config = {
        DEFAULT_SETTINGS: {
            DEBUG: true,
            // --- Global Toggles ---
            ENABLE_ADVANCED_SEARCH: true,
            ENABLE_PEEK_PREVIEWS: true,
            ENABLE_PEEK_LIGHTBOX: true,
            ENABLE_DECK_VIEWER: true,
            HIDE_PAGE_SCROLLBARS: true,
            ZOOM_SENSITIVITY: 1.1,
            QUICK_TAGS_LIST: '',
            BLACKLIST_TAGS: '',

            // --- Downloader ---
            ENABLE_DOWNLOADER: true,
            DOWNLOADER_SUBFOLDER: 'gelbooru',

            // --- Peek: Previews & Lightbox ---
            PREVIEW_QUALITY: 'high',
            ZOOM_SCALE_FACTOR: 2.3,
            PREVIEW_VIDEOS_MUTED: true,
            PREVIEW_VIDEOS_LOOP: true,
            AUTOPLAY_LIGHTBOX_VIDEOS: true,
            LIGHTBOX_BG_COLOR: 'rgba(0, 0, 0, 0.85)',
            SHOW_DELAY: 350,
            HIDE_DELAY: 175,

            // --- Deck: Immersive Viewer ---
            DECK_PRELOAD_AHEAD: 5,
            DECK_PRELOAD_BEHIND: 5,

            // --- Hotkeys ---
            KEY_GALLERY_NEXT_PAGE: 'ArrowRight',
            KEY_GALLERY_PREV_PAGE: 'ArrowLeft',
            KEY_PEEK_VIDEO_PLAY_PAUSE: ' ',
            KEY_PEEK_VIDEO_SEEK_BACK: 'ArrowLeft',
            KEY_PEEK_VIDEO_SEEK_FORWARD: 'ArrowRight',
            KEY_DECK_PREV_MEDIA: 'ArrowLeft',
            KEY_DECK_NEXT_MEDIA: 'ArrowRight',
            KEY_DECK_JUMP_BOX: 'Enter',
            KEY_DECK_TOGGLE_BOOKMARK: 'b',
            KEY_DECK_CLOSE_PANELS: 'Escape',

            // --- API ---
            API_KEY: '',
            USER_ID: '',
            API_BASE_URL: 'https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1',
            API_AUTOCOMPLETE_URL: 'https://gelbooru.com/index.php?page=autocomplete2&type=tag_query&limit=10',
        },
        SELECTORS: {
            SEARCH_INPUT: '#tags-search',
            THUMBNAIL_GRID_SELECTOR: '.thumbnail-container, #post-list > div, .mainBodyPadding',
            THUMBNAIL_ANCHOR_SELECTOR: '.thumbnail-preview > a',
            VIDEO_PLAYER_SELECTOR: 'main video#gelcomVideoPlayer',
            IMAGE_SELECTOR: 'main #image',
            PAGINATION_CURRENT_SELECTOR: '.pagination b',
            PREVIEW_CONTAINER_ID: 'enhancer-preview-container',
            SETTINGS_MODAL_ID: 'enhancer-settings-modal',
            ADVANCED_SEARCH_MODAL_ID: 'gbs-advanced-search-modal',
            DECK_VIEWER_ID: 'gbs-deck-viewer-overlay',
            galleryNavSubmenu: '.navSubmenu',
            postTagListItem: '#tag-list li[class*="tag-type-"]',
            postTagLink: 'a[href*="&tags="]',
        },
        STORAGE_KEYS: {
            SUITE_SETTINGS: 'gelbooruSuite_settings',
            DECK_BOOKMARKS: 'gelbooruSuite_bookmarks'
        },
        DECK_CONSTANTS: {
            POSTS_PER_PAGE: 42,
            TAG_COLORS: {
                'artist': '#AA0000',
                'character': '#00AA00',
                'copyright': '#AA00AA',
                'metadata': '#FF8800',
                'general': '#337ab7',
                'excluded': '#999999'
            },
            CURSOR_INACTIVITY_TIME: 3000,
            HISTORY_LENGTH: 10,
            CSS_CLASSES: { HIDDEN: 'hidden', ACTIVE: 'active', VISIBLE: 'visible', LOADING: 'loading', INVALID: 'invalid', GRAB: 'grab', GRABBING: 'grabbing', POINTER: 'pointer' },
        }
    };

    // =================================================================================
    // GLOBAL STATE MODULE
    // =================================================================================
    const GlobalState = {
        searchDebounceTimeout: null,
        previewsTemporarilyDisabled: false, // Used by Downloader to disable Peek
    };

    // =================================================================================
    // UTILITY, API, ZOOM & LOGGER MODULES (CORE)
    // =================================================================================
    const Utils = {
        makeRequest: function(options) {
            let xhr;
            const promise = new Promise((resolve, reject) => {
                xhr = GM.xmlHttpRequest({
                    ...options,
                    onload: (response) => (response.status >= 200 && response.status < 300) ? resolve(response) : reject(new Error(`Request failed: Status ${response.status}`)),
                    onerror: (response) => reject(new Error(`Network error: ${response.statusText}`)),
                    ontimeout: () => reject(new Error('Request timed out.'))
                });
            });
            return { promise, xhr };
        },
        getPostId: (postUrl) => new URL(postUrl).searchParams.get('id'),
        formatHotkeyForStorage: function(key) {
            return key === 'Space' ? ' ' : key.trim();
        },
        formatHotkeyForDisplay: function(key) {
            return key === ' ' ? 'Space' : key;
        },
    };

    const Logger = {
        _log: function(level, ...args) {
            if (!Settings.State.DEBUG) return;
            const prefix = '[Gelbooru Suite]';
            switch (level) {
                case 'log': console.log(prefix, ...args); break;
                case 'warn': console.warn(prefix, ...args); break;
                case 'error': console.error(prefix, ...args); break;
                default: console.log(prefix, ...args); break;
            }
        },
        log: function(...args) { this._log('log', ...args); },
        warn: function(...args) { this._log('warn', ...args); },
        error: function(...args) { this._log('error', ...args); }
    };

    const API = {
        fetchTagCategory: async function(tagName) {
            const encodedTerm = encodeURIComponent(tagName.replace(/ /g, '_'));
            const url = `${Config.DEFAULT_SETTINGS.API_AUTOCOMPLETE_URL}&limit=1&term=${encodedTerm}`;
            try {
                const { promise } = Utils.makeRequest({ method: "GET", url });
                const response = await promise;
                const data = JSON.parse(response.responseText);
                if (data && data[0] && data[0].value === tagName) {
                    return data[0].category === 'tag' ? 'general' : data[0].category;
                }
                return 'general';
            } catch (error) {
                Logger.error(`Failed to fetch category for tag "${tagName}":`, error);
                return 'general';
            }
        },
        fetchTagSuggestions: async function(term) {
            if (!term || term.length < 2) return [];
            const encodedTerm = encodeURIComponent(term.replace(/ /g, '_'));
            const url = `${Config.DEFAULT_SETTINGS.API_AUTOCOMPLETE_URL}&term=${encodedTerm}`;
            try {
                const { promise } = Utils.makeRequest({ method: "GET", url });
                const response = await promise;
                const data = JSON.parse(response.responseText);
                return data || [];
            } catch (error) {
                Logger.error("Failed to fetch tag suggestions:", error);
                return [];
            }
        },
        fetchMediaDetails: async function(postId) {
            if (!postId) throw new Error("No Post ID provided.");
            let request;
            if (Settings.State.API_KEY && Settings.State.USER_ID) {
                const apiUrl = `${Config.DEFAULT_SETTINGS.API_BASE_URL}&id=${postId}&user_id=${Settings.State.USER_ID}&api_key=${Settings.State.API_KEY}`;
                try {
                    request = Utils.makeRequest({ method: "GET", url: apiUrl });
                    Peek.State.pendingPreviewRequest = request.xhr;

                    const response = await request.promise;
                    if (response.status === 401 || response.status === 403) {
                        Settings.UI.openModal("Authentication failed. Your API Key or User ID is incorrect. Please enter valid credentials.");
                        throw new Error("Authentication failed. Please check your credentials.");
                    }
                    let data;
                    try { data = JSON.parse(response.responseText); }
                    catch (e) {
                        Logger.error("Failed to parse API response. It might not be valid JSON.", e);
                        Logger.error("Raw response text:", response.responseText);
                        throw new Error("Failed to parse API response. It might not be valid JSON.");
                    }
                    if (!data?.post?.length) throw new Error("API returned no post data or post not found.");
                    const post = data.post[0];
                    const fileUrl = post.file_url;
                    const isVideo = ['.mp4', '.webm'].some(ext => fileUrl.endsWith(ext));
                    return { url: fileUrl, type: isVideo ? 'video' : 'image' };
                } catch (error) {
                    if (error.message.includes('abort')) {
                        Logger.log(`Request for post ${postId} was aborted.`);
                        throw error;
                    }
                    Logger.warn(`[Gelbooru Suite] API request failed: ${error.message}. Attempting HTML fallback.`);
                } finally {
                    Peek.State.pendingPreviewRequest = null;
                }
            }

            try {
                Logger.log(`[Gelbooru Suite] Using HTML fallback for post ID: ${postId}`);
                const postUrl = `https://gelbooru.com/index.php?page=post&s=view&id=${postId}`;

                request = Utils.makeRequest({ method: "GET", url: postUrl });
                Peek.State.pendingPreviewRequest = request.xhr;

                const mediaData = await this.getPostData(request.promise);
                return { url: mediaData.contentUrl, type: mediaData.type };
            } catch (fallbackError) {
                Logger.error('[Gelbooru Suite] HTML fallback also failed:', fallbackError);
                throw fallbackError;
            } finally {
                Peek.State.pendingPreviewRequest = null;
            }
        },
        getPostData: async function(requestPromise) {
            const response = await requestPromise;
            const doc = new DOMParser().parseFromString(response.responseText, "text/html");
            const metaTag = doc.querySelector("meta[property='og:image']");
            const videoTag = doc.querySelector("video#gelcomVideoPlayer source");
            let contentUrl, type;
            if (videoTag && videoTag.src) {
                contentUrl = videoTag.src;
                type = 'video';
            } else if (metaTag) {
                contentUrl = metaTag.getAttribute('content');
                type = ['.mp4', '.webm'].some(ext => contentUrl.endsWith(ext)) ? 'video' : 'image';
            }
            if (contentUrl) {
                return { contentUrl, type, tags: this.parseTags(doc) };
            } else {
                throw new Error(`Media not found for post.`);
            }
        },
        fetchPage: async function(isPrev) {
            const urlToFetch = isPrev ? Deck.State.galleryData.prevPageUrl : Deck.State.galleryData.nextPageUrl;
            if (Deck.State.isLoadingNextPage || !urlToFetch) return null;
            Deck.State.isLoadingNextPage = true;
            try {
                const url = new URL(urlToFetch);
                const tags = url.searchParams.get('tags') || '';
                const pid = parseInt(url.searchParams.get('pid'), 10) || 0;
                const pageNum = Math.floor(pid / Config.DECK_CONSTANTS.POSTS_PER_PAGE);
                let apiUrl = Config.DEFAULT_SETTINGS.API_BASE_URL;
                if (Settings.State.API_KEY && Settings.State.USER_ID) {
                    apiUrl += `&api_key=${Settings.State.API_KEY}&user_id=${Settings.State.USER_ID}`;
                }
                apiUrl += `&tags=${encodeURIComponent(tags)}&pid=${pageNum}&limit=${Config.DECK_CONSTANTS.POSTS_PER_PAGE}`;
                const { promise } = Utils.makeRequest({ method: "GET", url: apiUrl });
                const response = await promise;

                let data;
                try { data = JSON.parse(response.responseText); }
                catch (e) {
                    Logger.error("Gelbooru Suite: Failed to parse JSON response.", e);
                    Logger.error("Raw response text:", response.responseText);
                    Deck.UI.showLoadingMessage("Failed to load page. You may have reached the API limit.");
                    if (isPrev) { Deck.State.galleryData.prevPageUrl = null; } else { Deck.State.galleryData.nextPageUrl = null; }
                    return null;
                }
                if (!data.post || data.post.length === 0) {
                    if (document.getElementById('viewer-main')) Deck.UI.showLoadingMessage('No more results found.');
                    Deck.State.galleryData.nextPageUrl = null;
                    return null;
                }
                const newPosts = data.post.map(p => {
                    const isVideo = p.image && ['mp4', 'webm'].includes(p.image.split('.').pop());
                    return {
                        postUrl: `https://gelbooru.com/index.php?page=post&s=view&id=${p.id}`,
                        thumbUrl: p.preview_url,
                        mediaUrl: p.file_url,
                        type: isVideo ? 'video' : 'image',
                    };
                });
                Deck.State.galleryData.posts = newPosts;
                Deck.State.galleryData.baseUrl = urlToFetch;
                const postCount = parseInt(data['@attributes'].count, 10);
                const lastPageNum = Math.floor(postCount / Config.DECK_CONSTANTS.POSTS_PER_PAGE);
                Deck.State.galleryData.lastPageNum = lastPageNum + 1;
                if (pageNum < lastPageNum) {
                    const nextUrl = new URL(urlToFetch);
                    nextUrl.searchParams.set('pid', ((pageNum + 1) * Config.DECK_CONSTANTS.POSTS_PER_PAGE).toString());
                    Deck.State.galleryData.nextPageUrl = nextUrl.href;
                } else {
                    Deck.State.galleryData.nextPageUrl = null;
                }
                if (pageNum > 0) {
                    const prevUrl = new URL(urlToFetch);
                    prevUrl.searchParams.set('pid', ((pageNum - 1) * Config.DECK_CONSTANTS.POSTS_PER_PAGE).toString());
                    Deck.State.galleryData.prevPageUrl = prevUrl.href;
                } else {
                    Deck.State.galleryData.prevPageUrl = null;
                }
                return isPrev ? newPosts.length - 1 : 0;
            } catch (error) {
                Logger.error("API Fetch Error:", error);
                if (document.getElementById('viewer-main')) Deck.UI.showLoadingMessage(error.message);
                return null;
            } finally {
                Deck.State.isLoadingNextPage = false;
            }
        },
        parseTags: function(doc) {
            const tags = {};
            doc.querySelectorAll(Config.SELECTORS.postTagListItem).forEach(li => {
                const categoryMatch = li.className.match(/tag-type-([a-z_]+)/);
                const category = categoryMatch ? categoryMatch[1] : 'general';
                const tagLink = li.querySelector(Config.SELECTORS.postTagLink);
                if (tagLink) {
                    if (!tags[category]) { tags[category] = []; }
                    tags[category].push({ name: tagLink.textContent.trim(), url: tagLink.href });
                }
            });
            return tags;
        }
    };

    const Zoom = {
        enable: function(mediaElement, options = {}) {
            const { clickAction = 'open_tab', bookmarkElement = null } = options;
            let clickTimer = null;
            let scale = 1, isPanning = false, pointX = 0, pointY = 0, start = { x: 0, y: 0 };
            const setTransform = () => {
                mediaElement.style.transform = `translate(${pointX}px, ${pointY}px) scale(${scale})`;
                mediaElement.style.transformOrigin = '0 0';
                if (bookmarkElement) { bookmarkElement.style.display = scale > 1 ? 'none' : 'flex';
                                     }
            };
            const onWheel = (e) => {
                e.preventDefault();
                const rect = mediaElement.getBoundingClientRect();
                const xs = (e.clientX - rect.left) / scale;
                const ys = (e.clientY - rect.top) / scale;
                const delta = (e.deltaY > 0) ? (1 / Settings.State.ZOOM_SENSITIVITY) : Settings.State.ZOOM_SENSITIVITY;
                const oldScale = scale;
                scale *= delta;
                if (scale < 1) {
                    scale = 1;
                    pointX = 0; pointY = 0;
                } else if (scale > 20) {
                    scale = 20;
                } else {
                    pointX += xs * oldScale - xs * scale;
                    pointY += ys * oldScale - ys * scale;
                }
                mediaElement.style.cursor = (scale > 1) ? Config.DECK_CONSTANTS.CSS_CLASSES.GRAB : Config.DECK_CONSTANTS.CSS_CLASSES.POINTER;
                setTransform();
            };
            const onMouseDown = (e) => {
                if (scale > 1 && e.button === 0) {
                    e.preventDefault();
                    isPanning = true;
                    start = { x: e.clientX - pointX, y: e.clientY - pointY };
                    mediaElement.style.cursor = Config.DECK_CONSTANTS.CSS_CLASSES.GRABBING;
                    window.addEventListener('mousemove', onMouseMove);
                    window.addEventListener('mouseup', onMouseUp);
                }
            };
            const onMouseMove = (e) => {
                if (isPanning) {
                    e.preventDefault();
                    pointX = e.clientX - start.x;
                    pointY = e.clientY - start.y;
                    setTransform();
                }
            };
            const onMouseUp = () => {
                isPanning = false;
                mediaElement.style.cursor = Config.DECK_CONSTANTS.CSS_CLASSES.GRAB;
                window.removeEventListener('mousemove', onMouseMove);
                window.removeEventListener('mouseup', onMouseUp);
            };
            const toggleZoom = (e) => {
                if (scale > 1) {
                    scale = 1;
                    pointX = 0; pointY = 0;
                    mediaElement.style.cursor = Config.DECK_CONSTANTS.CSS_CLASSES.POINTER;
                } else {
                    scale = Settings.State.ZOOM_SCALE_FACTOR || 2.3;
                    const rect = mediaElement.getBoundingClientRect();
                    const xs = (e.clientX - rect.left);
                    const ys = (e.clientY - rect.top);
                    pointX = xs - xs * scale;
                    pointY = ys - ys * scale;
                    mediaElement.style.cursor = Config.DECK_CONSTANTS.CSS_CLASSES.GRAB;
                }
                setTransform();
            };
            const onClick = (e) => {
                if (scale <= 1) {
                    e.preventDefault();
                    if (clickTimer) {
                        return;}
                    if (clickAction === 'open_tab') {
                        clickTimer = setTimeout(() => {
                            GM_openInTab(Deck.State.galleryData.posts[Deck.State.currentIndex].postUrl, { active: false, setParent: true });
                            clickTimer = null;
                        }, 300);
                    }
                }
            };
            const onDblClick = (e) => {
                if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; }
                e.preventDefault();
                toggleZoom(e);
            };
            mediaElement.addEventListener('wheel', onWheel);
            mediaElement.addEventListener('mousedown', onMouseDown);
            mediaElement.addEventListener('dblclick', onDblClick);
            if (clickAction !== 'none') { mediaElement.addEventListener('click', onClick); }
            const destroy = () => {
                clearTimeout(clickTimer);
                mediaElement.removeEventListener('wheel', onWheel);
                mediaElement.removeEventListener('mousedown', onMouseDown);
                mediaElement.removeEventListener('dblclick', onDblClick);
                if (clickAction !== 'none') { mediaElement.removeEventListener('click', onClick); }
                window.removeEventListener('mousemove', onMouseMove);
                window.removeEventListener('mouseup', onMouseUp);
            };
            return { destroy };
        }
    };

    // =================================================================================
    // SETTINGS MODULE
    // =================================================================================
    const Settings = {
        State: {},
        load: async function() {
            const savedSettings = await GM.getValue(Config.STORAGE_KEYS.SUITE_SETTINGS, {});
            this.State = { ...Config.DEFAULT_SETTINGS, ...savedSettings };
            Logger.State = this.State; // Pass settings to logger
        },
        save: async function() {
            const getHotkey = (id) => Utils.formatHotkeyForStorage(document.getElementById(id).value);
            const newSettings = {
                ENABLE_ADVANCED_SEARCH: document.getElementById('setting-advanced-search').checked,
                ENABLE_PEEK_PREVIEWS: document.getElementById('setting-peek-previews').checked,
                ENABLE_PEEK_LIGHTBOX: document.getElementById('setting-peek-lightbox').checked,
                ENABLE_DECK_VIEWER: document.getElementById('setting-deck-viewer').checked,
                ENABLE_DOWNLOADER: document.getElementById('setting-downloader').checked,
                BLACKLIST_TAGS: document.getElementById('setting-blacklist-tags').value.trim(),
                QUICK_TAGS_LIST: document.getElementById('setting-quick-tags-list').value.trim(),
                PREVIEW_QUALITY: document.getElementById('setting-preview-quality').value,
                AUTOPLAY_LIGHTBOX_VIDEOS: document.getElementById('setting-autoplay-lightbox').checked,
                HIDE_PAGE_SCROLLBARS: document.getElementById('setting-hide-scrollbars').checked,
                PREVIEW_VIDEOS_MUTED: document.getElementById('setting-preview-muted').checked,
                PREVIEW_VIDEOS_LOOP: document.getElementById('setting-preview-loop').checked,
                ZOOM_SCALE_FACTOR: Math.max(1, parseFloat(document.getElementById('setting-zoom').value) || Config.DEFAULT_SETTINGS.ZOOM_SCALE_FACTOR),
                LIGHTBOX_BG_COLOR: document.getElementById('setting-lightbox-bg').value.trim() || Config.DEFAULT_SETTINGS.LIGHTBOX_BG_COLOR,
                DECK_PRELOAD_AHEAD: Math.min(20, Math.max(0, parseInt(document.getElementById('setting-deck-preload-ahead').value, 10) || Config.DEFAULT_SETTINGS.DECK_PRELOAD_AHEAD)),
                DECK_PRELOAD_BEHIND: Math.min(20, Math.max(0, parseInt(document.getElementById('setting-deck-preload-behind').value, 10) || Config.DEFAULT_SETTINGS.DECK_PRELOAD_BEHIND)),
                ZOOM_SENSITIVITY: Math.max(0.01, parseFloat(document.getElementById('setting-zoom-sens').value) || Config.DEFAULT_SETTINGS.ZOOM_SENSITIVITY),
                KEY_GALLERY_NEXT_PAGE: getHotkey('setting-key-gallery-next') || Config.DEFAULT_SETTINGS.KEY_GALLERY_NEXT_PAGE,
                KEY_GALLERY_PREV_PAGE: getHotkey('setting-key-gallery-prev') || Config.DEFAULT_SETTINGS.KEY_GALLERY_PREV_PAGE,
                KEY_PEEK_VIDEO_PLAY_PAUSE: getHotkey('setting-key-peek-vid-play') || Config.DEFAULT_SETTINGS.KEY_PEEK_VIDEO_PLAY_PAUSE,
                KEY_PEEK_VIDEO_SEEK_FORWARD: getHotkey('setting-key-peek-vid-fwd') || Config.DEFAULT_SETTINGS.KEY_PEEK_VIDEO_SEEK_FORWARD,
                KEY_PEEK_VIDEO_SEEK_BACK: getHotkey('setting-key-peek-vid-back') || Config.DEFAULT_SETTINGS.KEY_PEEK_VIDEO_SEEK_BACK,
                KEY_DECK_NEXT_MEDIA: getHotkey('setting-key-deck-next') || Config.DEFAULT_SETTINGS.KEY_DECK_NEXT_MEDIA,
                KEY_DECK_PREV_MEDIA: getHotkey('setting-key-deck-prev') || Config.DEFAULT_SETTINGS.KEY_DECK_PREV_MEDIA,
                KEY_DECK_JUMP_BOX: getHotkey('setting-key-deck-jump') || Config.DEFAULT_SETTINGS.KEY_DECK_JUMP_BOX,
                KEY_DECK_TOGGLE_BOOKMARK: getHotkey('setting-key-deck-bookmark') || Config.DEFAULT_SETTINGS.KEY_DECK_TOGGLE_BOOKMARK,
                KEY_DECK_CLOSE_PANELS: getHotkey('setting-key-deck-close') || Config.DEFAULT_SETTINGS.KEY_DECK_CLOSE_PANELS,
                API_BASE_URL: document.getElementById('setting-api-url').value.trim() || Config.DEFAULT_SETTINGS.API_BASE_URL,
                API_KEY: document.getElementById('setting-api-key').value.trim(),
                USER_ID: document.getElementById('setting-user-id').value.trim(),
                DOWNLOADER_SUBFOLDER: document.getElementById('setting-downloader-subfolder').value.trim() || Config.DEFAULT_SETTINGS.DOWNLOADER_SUBFOLDER,
            };
            await GM.setValue(Config.STORAGE_KEYS.SUITE_SETTINGS, newSettings);
            this.State = newSettings;
            this.UI.closeModal();
            window.location.reload();
        },
        clearCredentials: async function() {
            if (confirm("Are you sure you want to clear your API Key and User ID? The page will reload.")) {
                const newSettings = { ...this.State, API_KEY: '', USER_ID: '' };
                await GM.setValue(Config.STORAGE_KEYS.SUITE_SETTINGS, newSettings);
                this.State = newSettings;
                window.location.reload();
            }
        },
        export: function() {
            const jsonString = JSON.stringify(this.State, null, 2);
            const infoEl = document.getElementById('enhancer-manage-info');
            navigator.clipboard.writeText(jsonString).then(() => {
                infoEl.textContent = 'Settings copied to clipboard!';
                infoEl.style.color = '#98c379';
                infoEl.style.display = 'block';
                setTimeout(() => { infoEl.style.display = 'none'; }, 3000);
            }).catch(err => {
                Logger.error('Failed to copy settings: ', err);
                infoEl.textContent = 'Failed to copy. See console for details.';
                infoEl.style.color = '#e06c75';
                infoEl.style.display = 'block';
            });
        },
        import: async function() {
            const textarea = document.getElementById('enhancer-import-area');
            const jsonString = textarea.value;
            const infoEl = document.getElementById('enhancer-manage-info');
            infoEl.style.display = 'block';
            if (!jsonString.trim()) {
                infoEl.textContent = 'Import field is empty.';
                infoEl.style.color = '#e06c75';
                return;
            }
            try {
                const importData = JSON.parse(jsonString);
                if (importData && typeof importData === 'object') {
                    await GM.setValue(Config.STORAGE_KEYS.SUITE_SETTINGS, importData);
                    infoEl.textContent = 'Settings imported! Page will reload...';
                    infoEl.style.color = '#98c379';
                    setTimeout(() => window.location.reload(), 1500);
                } else {
                    throw new Error("Invalid or incomplete settings format.");
                }
            } catch (error) {
                infoEl.textContent = `Import failed: ${error.message}`;
                infoEl.style.color = '#e06c75';
                Logger.error('Import error:', error);
            }
        },
        testCredentials: async function() {
            const apiKey = document.getElementById('setting-api-key').value.trim();
            const userId = document.getElementById('setting-user-id').value.trim();
            const statusEl = document.getElementById('enhancer-api-status');

            if (!apiKey || !userId) {
                statusEl.textContent = 'API Key and User ID are required.';
                statusEl.style.color = '#e5c07b';
                statusEl.style.display = 'block';
                return;
            }

            statusEl.textContent = 'Testing...';
            statusEl.style.color = '#61afef';
            statusEl.style.display = 'block';

            const testUrl = `${Config.DEFAULT_SETTINGS.API_BASE_URL}&limit=1&user_id=${userId}&api_key=${apiKey}`;
            try {
                const { promise } = Utils.makeRequest({ method: "GET", url: testUrl });
                const response = await promise;
                if (response.status === 200) {
                    statusEl.textContent = 'Success! Credentials are valid.';
                    statusEl.style.color = '#98c379';
                } else {
                    throw new Error(`Authentication failed (Status: ${response.status})`);
                }
            } catch (error) {
                Logger.error('API connection test failed:', error);
                statusEl.textContent = `Error: ${error.message}. Please check your credentials.`;
                statusEl.style.color = '#e06c75';
            }
        },
        UI: {
            _getGeneralSettingsHTML: function() {
                return `
                <div class="settings-tab-pane active" data-tab="general">
                    <div class="setting-item"><label for="setting-advanced-search">Enable Tag Editor</label><label class="toggle-switch"><input type="checkbox" id="setting-advanced-search"><span class="toggle-slider"></span></label></div>
                    <div class="setting-item"><label for="setting-peek-previews">Enable Peek Previews</label><label class="toggle-switch"><input type="checkbox" id="setting-peek-previews"><span class="toggle-slider"></span></label></div>
                    <div class="setting-item"><label for="setting-peek-lightbox">Enable Peek Lightbox</label><label class="toggle-switch"><input type="checkbox" id="setting-peek-lightbox"><span class="toggle-slider"></span></label></div>
                    <div class="setting-item"><label for="setting-deck-viewer">Enable Deck Viewer</label><label class="toggle-switch"><input type="checkbox" id="setting-deck-viewer"><span class="toggle-slider"></span></label></div>
                    <div class="setting-item"><label for="setting-downloader">Enable Downloader</label><label class="toggle-switch"><input type="checkbox" id="setting-downloader"><span class="toggle-slider"></span></label></div>
                    <div class="setting-item"><label for="setting-hide-scrollbars">Hide Page Scrollbars (Global)</label><label class="toggle-switch"><input type="checkbox" id="setting-hide-scrollbars"><span class="toggle-slider"></span></label></div>
                    <div class="setting-item"><label for="setting-zoom-sens">Zoom Sensitivity:</label><input type="number" id="setting-zoom-sens" min="0.01" step="0.01"></div>
                    <hr class="setting-divider">
                    <div class="setting-item-vertical">
                        <label for="setting-quick-tags-list">Quick Tags List (comma-separated)</label>
                        <p class="setting-note" style="text-align: left; margin: 5px 0 10px 0;">Tags for the 'Quick Tag' button in the Tag Editor modal.</p>
                        <textarea id="setting-quick-tags-list" rows="3" placeholder="Example: 1boy, rating:explicit, dark_skin ..."></textarea>
                    </div>
                    <hr class="setting-divider">
                    <div class="setting-item-vertical">
                        <label for="setting-blacklist-tags">Blacklisted Tags (space-separated)</label>
                        <p class="setting-note" style="text-align: left; margin: 5px 0 10px 0;">Tags for the 'Toggle Blacklist' button in the Tag Editor modal.</p>
                        <textarea id="setting-blacklist-tags" rows="3" placeholder="Example: muscular red_eyes pov ..."></textarea>
                    </div>
                </div>`;
            },
            _getPeekSettingsHTML: function() {
                return `
                <div class="settings-tab-pane" data-tab="peek">
                    <div class="setting-item"><label for="setting-preview-quality">Preview Quality</label><select id="setting-preview-quality"><option value="high">High (Full media)</option><option value="low">Low (Thumbnail only)</option></select></div>
                    <div class="setting-item"><label for="setting-zoom">Preview Zoom Scale Factor</label><input type="number" id="setting-zoom" step="0.1" min="1"></div>
                    <hr class="setting-divider">
                    <div class="setting-item"><label for="setting-preview-muted">Mute Preview Videos</label><label class="toggle-switch"><input type="checkbox" id="setting-preview-muted"><span class="toggle-slider"></span></label></div>
                    <div class="setting-item"><label for="setting-preview-loop">Loop Preview Videos</label><label class="toggle-switch"><input type="checkbox" id="setting-preview-loop"><span class="toggle-slider"></span></label></div>
                    <hr class="setting-divider">
                    <div class="setting-item"><label for="setting-autoplay-lightbox">Autoplay Lightbox Videos</label><label class="toggle-switch"><input type="checkbox" id="setting-autoplay-lightbox"><span class="toggle-slider"></span></label></div>
                    <div class="setting-item"><label for="setting-lightbox-bg">Lightbox Background</label><input type="text" id="setting-lightbox-bg" placeholder="e.g., rgba(0,0,0,0.85)"></div>
                </div>`;
            },
            _getDeckSettingsHTML: function() {
                return `
                <div class="settings-tab-pane" data-tab="deck">
                    <div class="setting-item"><label for="setting-deck-preload-ahead">Preload ahead:</label><input type="number" id="setting-deck-preload-ahead" min="0" max="20"></div>
                    <div class="setting-item"><label for="setting-deck-preload-behind">Preload behind:</label><input type="number" id="setting-deck-preload-behind" min="0" max="20"></div>
                </div>`;
            },
            _getHotkeysSettingsHTML: function() {
                return `
                <div class="settings-tab-pane" data-tab="hotkeys">
                    <p class="setting-note">Click on a field and press the desired key to set a hotkey.</p>
                    <h4 class="setting-subheader">Gallery Hotkeys</h4>
                    <div class="setting-item"><label for="setting-key-gallery-prev">Gallery: Prev Page</label><input type="text" id="setting-key-gallery-prev" class="hotkey-input" readonly></div>
                    <div class="setting-item"><label for="setting-key-gallery-next">Gallery: Next Page</label><input type="text" id="setting-key-gallery-next" class="hotkey-input" readonly></div>
                    <h4 class="setting-subheader">Peek Preview Video Hotkeys</h4>
                    <div class="setting-item"><label for="setting-key-peek-vid-play">Video: Play/Pause</label><input type="text" id="setting-key-peek-vid-play" class="hotkey-input" readonly></div>
                    <div class="setting-item"><label for="setting-key-peek-vid-back">Video: Seek Back</label><input type="text" id="setting-key-peek-vid-back" class="hotkey-input" readonly></div>
                    <div class="setting-item"><label for="setting-key-peek-vid-fwd">Video: Seek Forward</label><input type="text" id="setting-key-peek-vid-fwd" class="hotkey-input" readonly></div>
                    <h4 class="setting-subheader">Deck Viewer Hotkeys</h4>
                    <div class="setting-item"><label for="setting-key-deck-prev">Previous Media</label><input type="text" id="setting-key-deck-prev" class="hotkey-input" readonly></div>
                    <div class="setting-item"><label for="setting-key-deck-next">Next Media</label><input type="text" id="setting-key-deck-next" class="hotkey-input" readonly></div>
                    <div class="setting-item"><label for="setting-key-deck-jump">Open Jump Box</label><input type="text" id="setting-key-deck-jump" class="hotkey-input" readonly></div>
                    <div class="setting-item"><label for="setting-key-deck-bookmark">Toggle Bookmark</label><input type="text" id="setting-key-deck-bookmark" class="hotkey-input" readonly></div>
                    <div class="setting-item"><label for="setting-key-deck-close">Close Panels / Viewer</label><input type="text" id="setting-key-deck-close" class="hotkey-input" readonly></div>
                </div>`;
            },
            _getAdvancedSettingsHTML: function() {
                return `
                <div class="settings-tab-pane" data-tab="advanced">
                    <p class="setting-note">API keys can improve script reliability. Find them in Settings > Options (you must be logged in).</p>
                    <p class="setting-note"><strong>Security Note: Keys are stored locally and are not encrypted.</strong></p>
                    <div class="setting-item"><label for="setting-api-key">API Key</label><input type="text" id="setting-api-key" placeholder="Your API key"></div>
                    <div class="setting-item"><label for="setting-user-id">User ID</label><input type="text" id="setting-user-id" placeholder="Your user ID"></div>
                    <div class="setting-item"><label for="setting-api-url">API Base URL</label><input type="text" id="setting-api-url"></div>
                    <div class="api-test-container">
                        <button id="enhancer-test-creds">Test Connection</button>
                        <span id="enhancer-api-status" style="display:none;"></span>
                    </div>
                    <p class="enhancer-error-message" id="enhancer-auth-error" style="display:none; text-align: center;"></p>
                    <hr class="setting-divider">
                    <div class="setting-item">
                        <label for="setting-downloader-subfolder">Downloader Subfolder</label>
                        <input type="text" id="setting-downloader-subfolder" placeholder="e.g., gelbooru_downloads">
                    </div>
                    <hr class="setting-divider">
                    <div class="manage-settings-section">
                        <p class="setting-note">Export your settings for backup, or import them on another browser.</p>
                        <div class="manage-buttons">
                            <button id="enhancer-export-settings">Export to Clipboard</button>
                            <button id="enhancer-import-settings">Import and Reload</button>
                        </div>
                        <textarea id="enhancer-import-area" rows="3" placeholder="Paste your exported settings string here and click Import..."></textarea>
                        <p class="enhancer-info-message" id="enhancer-manage-info" style="display:none;"></p>
                    </div>
                </div>`;
            },
            createModal: function() {
                if (document.getElementById(Config.SELECTORS.SETTINGS_MODAL_ID)) return;
                GM_addStyle(`
                    #${Config.SELECTORS.SETTINGS_MODAL_ID}-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 100000; justify-content: center; align-items: center; font-family: sans-serif; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} { background-color: #252525; color: #eee !important; border: 1px solid #666; border-radius: 8px; z-index: 101; padding: 20px; box-shadow: 0 5px 25px #000a; width: 90%; max-width: 550px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} * { color: #eee !important; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} h2 { text-align: center; margin-top: 0; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #555; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-tabs { display: flex; margin-bottom: 15px; border-bottom: 1px solid #555; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-tab-btn { background: none; border: none; color: #aaa !important; padding: 8px 12px; cursor: pointer; font-size: 1em; border-bottom: 2px solid transparent; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-tab-btn.active { color: #fff !important; border-bottom-color: #006FFA; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-tab-content { display: grid; align-items: start; padding-top: 10px; padding-right: 10px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-tab-pane { grid-row: 1; grid-column: 1; opacity: 0; pointer-events: none; transition: opacity 0.15s ease-in-out; max-height: 65vh; overflow-y: auto; padding: 3px 15px; box-sizing: border-box; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-tab-pane.active { opacity: 1; pointer-events: auto; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item-vertical { display: flex; flex-direction: column; margin-bottom: 12px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item-vertical label { margin-bottom: 8px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item-vertical textarea, #${Config.SELECTORS.SETTINGS_MODAL_ID} #enhancer-import-area { width: 100%; box-sizing: border-box; padding: 5px; background: #333; border: 1px solid #555; color: #fff !important; border-radius: 4px; resize: vertical; min-height: 60px; margin-top: 10px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item label { color: #eee !important; margin-right: 10px; flex-shrink: 0; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item input[type=number], #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item input[type=text], #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item select { width: 155px; box-sizing: border-box; padding: 5px; background: #333; border: 1px solid #555; color: #fff !important; border-radius: 4px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-item input[type=range] { flex-grow: 1; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-note { font-size: 12px; color: #999 !important; text-align: center; margin-top: 5px; margin-bottom: 15px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-note strong { color: #daa520 !important; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-divider { border: 0; height: 1px; background: #444; margin: 20px 0; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .setting-subheader { color: #006FFA !important; border-bottom: 1px solid #444; padding-bottom: 5px; margin-top: 20px; margin-bottom: 15px; font-size: 0.9em; text-transform: uppercase; letter-spacing: 0.5px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-buttons { text-align: right; margin-top: 20px; border-top: 1px solid #555; padding-top: 15px; display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-buttons button { padding: 8px 16px; border: 1px solid #666; background-color: #444; color: #fff !important; border-radius: 5px; cursor: pointer; font-weight: bold }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .manage-buttons button { background-color: #444; color: #fff !important; border: 1px solid #666; padding: 8px 12px; border-radius: 4px; cursor: pointer; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .manage-buttons button:hover { background-color: #555; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} #enhancer-save-settings { background-color: #006FFA; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} #enhancer-clear-creds { background-color: #A43535; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} #zoom-sens-value { color: #fff !important; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .api-test-container { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 10px; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} #enhancer-test-creds { padding: 5px 10px; font-size: 0.9em; background-color: #006FFA; font-weight: bold; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} #enhancer-api-status { font-size: 0.9em; font-weight: bold; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .hotkey-input { cursor: pointer; text-align: center; }
                    #${Config.SELECTORS.SETTINGS_MODAL_ID} .hotkey-input:focus { background-color: #006FFA; color: #000 !important; font-weight: bold; }
                    .toggle-switch { position: relative; display: inline-block; width: 40px; height: 22px; }
                    .toggle-switch input { opacity: 0; width: 0; height: 0; }
                    .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #555; transition: .4s; border-radius: 22px; }
                    .toggle-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
                    input:checked + .toggle-slider { background-color: #006FFA; }
                    input:checked + .toggle-slider:before { transform: translateX(18px); }
                `);
                const modalHTML = `
                <div id="${Config.SELECTORS.SETTINGS_MODAL_ID}-overlay">
                    <div id="${Config.SELECTORS.SETTINGS_MODAL_ID}">
                        <h2>Gelbooru Suite Settings</h2>
                        <div class="settings-tabs">
                            <button class="settings-tab-btn active" data-tab="general">General</button>
                            <button class="settings-tab-btn" data-tab="peek">Peek Previews</button>
                            <button class="settings-tab-btn" data-tab="deck">Deck Viewer</button>
                            <button class="settings-tab-btn" data-tab="hotkeys">Hotkeys</button>
                            <button class="settings-tab-btn" data-tab="advanced">Advanced</button>
                        </div>
                        <div class="settings-tab-content">
                            ${this._getGeneralSettingsHTML()}
                            ${this._getPeekSettingsHTML()}
                            ${this._getDeckSettingsHTML()}
                            ${this._getHotkeysSettingsHTML()}
                            ${this._getAdvancedSettingsHTML()}
                        </div>
                        <div class="footer-container" style="text-align: right; border-top: 1px solid #555; padding-top: 15px; margin-top: 20px;">
                            <div class="settings-buttons" style="margin-top: 0; border-top: none; padding-top: 0;">
                                <button id="enhancer-clear-creds">Clear Credentials & Reload</button>
                                <button id="enhancer-save-settings">Save & Reload</button>
                                <button id="enhancer-close-settings">Close</button>
                            </div>
                        </div>
                    </div>
                </div>`;
                document.body.insertAdjacentHTML('beforeend', modalHTML);
                document.getElementById('enhancer-save-settings').addEventListener('click', Settings.save.bind(Settings));
                document.getElementById('enhancer-clear-creds').addEventListener('click', Settings.clearCredentials.bind(Settings));
                document.getElementById('enhancer-close-settings').addEventListener('click', this.closeModal);
                document.getElementById('enhancer-export-settings').addEventListener('click', Settings.export.bind(Settings));
                document.getElementById('enhancer-import-settings').addEventListener('click', Settings.import.bind(Settings));
                document.getElementById('enhancer-test-creds').addEventListener('click', Settings.testCredentials.bind(Settings));
                document.getElementById(`${Config.SELECTORS.SETTINGS_MODAL_ID}-overlay`).addEventListener('click', (e) => {
                    if (e.target.id === `${Config.SELECTORS.SETTINGS_MODAL_ID}-overlay`) this.closeModal();
                });
                const tabsContainer = document.querySelector(`#${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-tabs`);
                const panesContainer = document.querySelector(`#${Config.SELECTORS.SETTINGS_MODAL_ID} .settings-tab-content`);
                tabsContainer.addEventListener('click', (e) => {
                    if (e.target.matches('.settings-tab-btn')) {
                        const targetTab = e.target.dataset.tab;
                        if (tabsContainer.querySelector('.active')) { tabsContainer.querySelector('.active').classList.remove('active'); }
                        e.target.classList.add('active');
                        if (panesContainer.querySelector('.active')) { panesContainer.querySelector('.active').classList.remove('active'); }
                        panesContainer.querySelector(`.settings-tab-pane[data-tab="${targetTab}"]`).classList.add('active');
                    }
                });
                document.querySelectorAll('.hotkey-input').forEach(input => {
                    const handleKeyDown = (e) => {
                        e.preventDefault();
                        let key = e.key;
                        if (key === ' ') { key = 'Space'; }
                        input.value = key;
                        input.blur();
                    };
                    const handleFocus = () => {
                        input.value = 'Press a key...';
                        input.addEventListener('keydown', handleKeyDown, { once: true });
                    };
                    const handleBlur = () => {
                        if (input.value === 'Press a key...') {
                            const settingKey = input.id.replace('setting-key-', 'KEY_').toUpperCase().replace(/-/g, '_');
                            let defaultValue = Settings.State[settingKey] || Config.DEFAULT_SETTINGS[settingKey];
                            input.value = Utils.formatHotkeyForDisplay(defaultValue);
                        }
                        input.removeEventListener('keydown', handleKeyDown);
                    };
                    input.addEventListener('focus', handleFocus);
                    input.addEventListener('blur', handleBlur);
                });
            },
            openModal: function(authError = '') {
                if (!document.getElementById(Config.SELECTORS.SETTINGS_MODAL_ID)) { this.createModal(); }

                document.getElementById('setting-advanced-search').checked = Settings.State.ENABLE_ADVANCED_SEARCH;
                document.getElementById('setting-peek-previews').checked = Settings.State.ENABLE_PEEK_PREVIEWS;
                document.getElementById('setting-peek-lightbox').checked = Settings.State.ENABLE_PEEK_LIGHTBOX;
                document.getElementById('setting-deck-viewer').checked = Settings.State.ENABLE_DECK_VIEWER;
                document.getElementById('setting-downloader').checked = Settings.State.ENABLE_DOWNLOADER;
                document.getElementById('setting-blacklist-tags').value = Settings.State.BLACKLIST_TAGS;
                document.getElementById('setting-quick-tags-list').value = Settings.State.QUICK_TAGS_LIST;
                document.getElementById('setting-preview-quality').value = Settings.State.PREVIEW_QUALITY;
                document.getElementById('setting-autoplay-lightbox').checked = Settings.State.AUTOPLAY_LIGHTBOX_VIDEOS;
                document.getElementById('setting-hide-scrollbars').checked = Settings.State.HIDE_PAGE_SCROLLBARS;
                document.getElementById('setting-preview-muted').checked = Settings.State.PREVIEW_VIDEOS_MUTED;
                document.getElementById('setting-preview-loop').checked = Settings.State.PREVIEW_VIDEOS_LOOP;
                document.getElementById('setting-zoom').value = Settings.State.ZOOM_SCALE_FACTOR;
                document.getElementById('setting-lightbox-bg').value = Settings.State.LIGHTBOX_BG_COLOR;
                document.getElementById('setting-deck-preload-ahead').value = Settings.State.DECK_PRELOAD_AHEAD;
                document.getElementById('setting-deck-preload-behind').value = Settings.State.DECK_PRELOAD_BEHIND;
                document.getElementById('setting-zoom-sens').value = Settings.State.ZOOM_SENSITIVITY;
                document.getElementById('setting-key-gallery-next').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_GALLERY_NEXT_PAGE);
                document.getElementById('setting-key-gallery-prev').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_GALLERY_PREV_PAGE);
                document.getElementById('setting-key-peek-vid-play').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_PEEK_VIDEO_PLAY_PAUSE);
                document.getElementById('setting-key-peek-vid-fwd').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_PEEK_VIDEO_SEEK_FORWARD);
                document.getElementById('setting-key-peek-vid-back').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_PEEK_VIDEO_SEEK_BACK);
                document.getElementById('setting-key-deck-next').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_DECK_NEXT_MEDIA);
                document.getElementById('setting-key-deck-prev').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_DECK_PREV_MEDIA);
                document.getElementById('setting-key-deck-jump').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_DECK_JUMP_BOX);
                document.getElementById('setting-key-deck-bookmark').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_DECK_TOGGLE_BOOKMARK);
                document.getElementById('setting-key-deck-close').value = Utils.formatHotkeyForDisplay(Settings.State.KEY_DECK_CLOSE_PANELS);
                document.getElementById('setting-api-key').value = Settings.State.API_KEY;
                document.getElementById('setting-user-id').value = Settings.State.USER_ID;
                document.getElementById('setting-api-url').value = Settings.State.API_BASE_URL;
                document.getElementById('setting-downloader-subfolder').value = Settings.State.DOWNLOADER_SUBFOLDER;
                const errorMessageElement = document.getElementById('enhancer-auth-error');
                if (authError) {
                    errorMessageElement.textContent = authError;
                    errorMessageElement.style.display = 'block';
                    document.querySelector('.settings-tabs .settings-tab-btn[data-tab="advanced"]').click();
                } else {
                    errorMessageElement.style.display = 'none';
                }
                document.getElementById('enhancer-api-status').style.display = 'none';
                document.getElementById(`${Config.SELECTORS.SETTINGS_MODAL_ID}-overlay`).style.display = 'flex';
            },
            closeModal: function() {
                const overlay = document.getElementById(`${Config.SELECTORS.SETTINGS_MODAL_ID}-overlay`);
                if (overlay) overlay.style.display = 'none';
            },
        }
    };

    // =================================================================================
    // ADVANCED SEARCH MODULE
    // =================================================================================
    const AdvancedSearch = {
        init: function() {
            const originalInput = document.querySelector(Config.SELECTORS.SEARCH_INPUT);
            if (!originalInput) return;
            this.injectStyles();
            if (originalInput.form) {
                originalInput.form.classList.add('gbs-search-form');
            }
            const { openModal } = this.UI.createModal(originalInput);
            const advButton = document.createElement('button');
            advButton.type = 'button';
            advButton.textContent = 'Tag Editor';
            advButton.id = 'gbs-advanced-search-btn';
            advButton.title = 'Open Advanced Tag Editor';
            const originalSubmitButton = originalInput.form.querySelector('input[name="commit"]');
            if (originalSubmitButton) {
                originalSubmitButton.insertAdjacentElement('afterend', advButton);
            } else {
                originalInput.insertAdjacentElement('afterend', advButton);
            }
            advButton.addEventListener('click', (e) => {
                e.preventDefault();
                openModal();
            });
        },
        injectStyles: function() {
             GM_addStyle(`
                .gbs-search-form { display: flex; align-items: center; gap: 5px; }
                .gbs-search-form > p { display: contents; }
                .gbs-search-form #tags-search { flex-grow: 1; width: auto !important; }
                .gbs-search-form input[type="submit"] { flex-shrink: 0; }
                #${Config.SELECTORS.ADVANCED_SEARCH_MODAL_ID}-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 100001; justify-content: center; align-items: flex-start; padding-top: 10vh; font-family: sans-serif; }
                #${Config.SELECTORS.ADVANCED_SEARCH_MODAL_ID} { background-color: #252525; border-radius: 8px; box-shadow: 0 5px 25px rgba(0,0,0,0.5); width: 90%; max-width: 800px; padding: 10px; border: 1px solid #666; max-height: 85vh; display: flex; flex-direction: column; }
                .gbs-search-title { margin: 0 0 15px 0; font-size: 1.2em; color: #eee; text-align: center; flex-shrink: 0; }
                .gbs-input-wrapper { position: relative; flex-shrink: 0; }
                #gbs-tag-input { width: 100%; box-sizing: border-box; background: #333; border: 1px solid #555; padding: 10px; border-radius: 4px; font-size: 1em; color: #eee; }
                #gbs-tag-input:focus { border-color: #006FFA; outline: none; }
                .gbs-suggestion-container { position: absolute; top: 100%; left: 0; right: 0; background-color: #1F1F1F; border: 1px solid #555; border-top: none; z-index: 100002; max-height: 200px; overflow-y: auto; display: none; box-shadow: 0 4px 6px rgba(0,0,0,0.2); }
                .gbs-suggestion-item { font-size: 1.08em; padding: 3px 10px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; color: #ddd; }
                .gbs-suggestion-item:hover { background-color: #E6E6FA; }
                .gbs-suggestion-label { display: flex; align-items: center; gap: 8px; }
                .gbs-suggestion-category { font-size: 0.8em; color: #999; text-transform: capitalize; }
                .gbs-suggestion-count { font-size: 0.9em; }
                #gbs-category-sections-wrapper { overflow-y: auto; max-height: 60vh; margin-top: 15px; padding-right: 15px; }
                .gbs-category-section { margin-bottom: 10px; }
                .gbs-category-title { color: #eee; margin: 0 0 8px 0; padding-bottom: 4px; border-bottom: 2px solid; font-size: 1em; text-transform: capitalize; }
                .gbs-pill-container { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 0; min-height: 20px; }
                .gbs-tag-pill { display: inline-flex; align-items: center; color: white; padding: 5px 10px; border-radius: 15px; font-size: 0.9em; font-weight: bold; }
                .gbs-remove-tag-btn { margin-left: 8px; cursor: pointer; font-style: normal; font-weight: bold; line-height: 1; padding: 2px; font-size: 1.2em; opacity: 0.7; }
                .gbs-remove-tag-btn:hover { opacity: 1; }
                .gbs-modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 15px; border-top: 1px solid #555; padding-top: 15px; flex-shrink: 0; }
                .gbs-modal-button, .gbs-modal-button-primary { padding: 8px 16px; border: 1px solid #666; border-radius: 4px; cursor: pointer; font-weight: bold; }
                .gbs-modal-button { background-color: #444; color: #fff; }
                .gbs-modal-button-primary { background-color: #006FFA; color: white; border-color: #006FFA; }
                #gbs-advanced-search-btn { margin-left: 0px; padding: 7px 15px; vertical-align: top; cursor: pointer; background: #333333; color: #EEEEEE; border: 1px solid #555555; font-weight: bold; }
                #gbs-advanced-search-btn:hover { background: #555; }
                #gbs-quick-tags-toggle-btn { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); color: #aaa; cursor: pointer; transition: color 0.2s; font-size: 1.8em }
                #gbs-quick-tags-toggle-btn:hover,
                #gbs-quick-tags-toggle-btn:active,
                #gbs-quick-tags-toggle-btn.active { color: #daa520 !important; }
                #gbs-quick-tags-panel { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
                #gbs-quick-tags-panel.hidden { display: none; }
                .gbs-modal-quick-tag { background-color: #4a4a4a; color: #ddd; border: 1px solid #555; padding: 4px 8px; border-radius: 50px; font-size: 1em; cursor: pointer; transition: background-color .2s, border-color .2s; user-select: none; }
                .gbs-modal-quick-tag:hover { background-color: #daa520; border-color: #daa520; color: white; }
            `);
        },
        UI: {
            createModal: function(originalInput) {
                if (document.getElementById(Config.SELECTORS.ADVANCED_SEARCH_MODAL_ID)) return;
                const modalOverlay = document.createElement('div');
                modalOverlay.id = `${Config.SELECTORS.ADVANCED_SEARCH_MODAL_ID}-overlay`;

                const modalPanel = document.createElement('div');
                modalPanel.id = Config.SELECTORS.ADVANCED_SEARCH_MODAL_ID;
                const CATEGORIES = ['artist', 'character', 'copyright', 'metadata', 'general', 'excluded'];
                let categorySectionsHTML = '';
                CATEGORIES.forEach(cat => {
                    const title = cat.charAt(0).toUpperCase() + cat.slice(1);
                    categorySectionsHTML += `
                        <div class="gbs-category-section" id="gbs-section-${cat}">
                            <h4 class="gbs-category-title" style="border-color: ${Config.DECK_CONSTANTS.TAG_COLORS[cat]}">${title}</h4>
                            <div class="gbs-pill-container" id="gbs-pill-container-${cat}"></div>
                        </div>
                    `;
                });
                modalPanel.innerHTML = `
                    <h3 class="gbs-search-title">Advanced Tag Editor</h3>
                    <div class="gbs-input-wrapper">
                        <input type="text" id="gbs-tag-input" placeholder="Type a tag, use '-' to exclude. Press space for '_'...">
                        <i class="fas fa-tag" id="gbs-quick-tags-toggle-btn" title="Show Quick Tags"></i>
                        <div class="gbs-suggestion-container" id="gbs-main-suggestion-container"></div>
                    </div>
                    <div id="gbs-quick-tags-panel" class="hidden"></div>
                    <div id="gbs-category-sections-wrapper">${categorySectionsHTML}</div>
                    <div class="gbs-modal-actions">
                        <button id="gbs-blacklist-toggle" class="gbs-modal-button" style="margin-right: auto;">Toggle Blacklist</button>
                        <button id="gbs-search-apply" class="gbs-modal-button-primary">Apply & Search</button>
                        <button id="gbs-search-close" class="gbs-modal-button">Close</button>
                    </div>
                `;
                modalOverlay.appendChild(modalPanel);
                document.body.appendChild(modalOverlay);

                const tagInput = modalPanel.querySelector('#gbs-tag-input');
                const suggestionBox = modalPanel.querySelector('#gbs-main-suggestion-container');
                const applyBtn = modalPanel.querySelector('#gbs-search-apply');
                const closeBtn = modalPanel.querySelector('#gbs-search-close');
                const blacklistToggleBtn = modalPanel.querySelector('#gbs-blacklist-toggle');

                const syncToOriginalInput = () => {
                    const allPills = modalPanel.querySelectorAll('.gbs-tag-pill');
                    const tags = Array.from(allPills).map(pill => pill.dataset.value);
                    originalInput.value = tags.join(' ');
                };
                const updateBlacklistButton = () => {
                    const blacklistTags = (Settings.State.BLACKLIST_TAGS || '').trim().split(/\s+/).filter(Boolean);
                    if (blacklistTags.length === 0) {
                        blacklistToggleBtn.style.display = 'none';
                        return;
                    }
                    blacklistToggleBtn.style.display = '';
                    const tagsAsPills = Array.from(modalPanel.querySelectorAll('.gbs-tag-pill')).map(pill => pill.dataset.value);
                    const negativeBlacklistTags = blacklistTags.map(t => `-${t}`);
                    const areTagsActive = negativeBlacklistTags.every(negTag => tagsAsPills.includes(negTag));
                    if (areTagsActive) {
                        blacklistToggleBtn.textContent = 'Remove Blacklist Tags';
                    } else {
                        blacklistToggleBtn.textContent = 'Add Blacklist Tags';
                    }
                };
                const _determineTagInfo = async (rawTag) => {
                    const isNegative = rawTag.startsWith('-');
                    let processedTag = (isNegative ? rawTag.substring(1) : rawTag).trim();
                    let category = 'general';
                    let finalTagName = processedTag;

                    const parts = processedTag.split(':');
                    if (parts.length > 1 && Object.keys(Config.DECK_CONSTANTS.TAG_COLORS).includes(parts[0])) {
                        category = parts[0];
                        finalTagName = parts.slice(1).join(':');
                    } else {
                        category = await API.fetchTagCategory(processedTag);
                    }

                    if (isNegative) category = 'excluded';
                    return {
                        fullValue: (isNegative ? '-' : '') + finalTagName,
                        tagName: finalTagName,
                        category: category
                    };
                };

                const _createPillElement = (tagInfo) => {
                    const pill = document.createElement('span');
                    pill.className = 'gbs-tag-pill';
                    pill.textContent = tagInfo.tagName.replace(/_/g, ' ');
                    pill.dataset.value = tagInfo.fullValue;
                    pill.style.backgroundColor = Config.DECK_CONSTANTS.TAG_COLORS[tagInfo.category] || '#777';

                    const removeBtn = document.createElement('i');
                    removeBtn.className = 'gbs-remove-tag-btn';
                    removeBtn.textContent = '×';
                    removeBtn.onclick = () => {
                        pill.remove();
                        syncToOriginalInput();
                        updateBlacklistButton();
                    };

                    pill.appendChild(removeBtn);
                    return pill;
                };

                const addPill = async (rawTag) => {
                    if (!rawTag || rawTag.trim() === '') return;
                    const tagInfo = await _determineTagInfo(rawTag.trim());
                    if (modalPanel.querySelector(`.gbs-tag-pill[data-value="${tagInfo.fullValue}"]`)) {
                        return;
                    }
                    const pillContainer = modalPanel.querySelector(`#gbs-pill-container-${tagInfo.category}`);
                    if (!pillContainer) {
                        Logger.warn(`Could not find pill container for category: ${tagInfo.category}`);
                        return;
                    }
                    const pillElement = _createPillElement(tagInfo);
                    pillContainer.appendChild(pillElement);
                    syncToOriginalInput();
                    updateBlacklistButton();
                };

                const toggleBlacklistTags = () => {
                    const blacklistTags = (Settings.State.BLACKLIST_TAGS || '').trim().split(/\s+/).filter(Boolean);
                    if (blacklistTags.length === 0) {
                        alert('Your blacklist is empty. Please add tags in the Suite Settings.');
                        return;
                    }
                    const tagsAsPills = Array.from(modalPanel.querySelectorAll('.gbs-tag-pill'));
                    const pillValues = tagsAsPills.map(pill => pill.dataset.value);
                    const negativeBlacklistTags = blacklistTags.map(t => `-${t}`);
                    const areTagsActive = negativeBlacklistTags.every(negTag => pillValues.includes(negTag));
                    if (areTagsActive) {
                        tagsAsPills.forEach(pill => {
                            if (negativeBlacklistTags.includes(pill.dataset.value)) {
                                pill.remove();
                            }
                        });
                    } else {
                        negativeBlacklistTags.forEach(negTag => {
                            if (!pillValues.includes(negTag)) {
                                addPill(negTag);
                            }
                        });
                    }
                    syncToOriginalInput();
                    updateBlacklistButton();
                };
                blacklistToggleBtn.addEventListener('click', toggleBlacklistTags);

                const quickTagsBtn = modalPanel.querySelector('#gbs-quick-tags-toggle-btn');
                const quickTagsPanel = modalPanel.querySelector('#gbs-quick-tags-panel');
                const quickTags = Settings.State.QUICK_TAGS_LIST.split(',').map(t => t.trim()).filter(t => t.length > 0);
                if (quickTags.length > 0) {
                    const fragment = document.createDocumentFragment();
                    quickTags.forEach(tag => {
                        const tagEl = document.createElement('span');
                        tagEl.className = 'gbs-modal-quick-tag';
                        tagEl.textContent = tag.replace(/_/g, ' ');
                        tagEl.dataset.tag = tag;
                        tagEl.addEventListener('click', () => {
                            addPill(tag);
                        });
                        fragment.appendChild(tagEl);
                    });
                    quickTagsPanel.appendChild(fragment);
                } else {
                    quickTagsBtn.style.display = 'none';
                }

                quickTagsBtn.addEventListener('click', () => {
                    quickTagsPanel.classList.toggle('hidden');
                    quickTagsBtn.classList.toggle('active', !quickTagsPanel.classList.contains('hidden'));
                });


                const openModal = () => {
                    modalPanel.querySelectorAll('.gbs-pill-container').forEach(c => { c.innerHTML = '' });
                    const currentTags = originalInput.value.trim().split(/\s+/).filter(Boolean);
                    currentTags.forEach(tag => addPill(tag));
                    modalOverlay.style.display = 'flex';
                    tagInput.focus();
                    updateBlacklistButton();
                };
                const closeModal = () => { modalOverlay.style.display = 'none'; };
                tagInput.addEventListener('keydown', e => {
                    if (e.key === ' ') {
                        e.preventDefault();
                        tagInput.value += '_';
                    }
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        const finalTag = tagInput.value.trim().replace(/ /g, '_');
                        addPill(finalTag);
                        tagInput.value = '';
                        suggestionBox.style.display = 'none';
                    }
                });
                tagInput.addEventListener('input', () => {
                    clearTimeout(GlobalState.searchDebounceTimeout);
                    const term = tagInput.value.trim();
                    if (term.length < 2) {
                        suggestionBox.style.display = 'none';
                        return;
                    }
                    GlobalState.searchDebounceTimeout = setTimeout(async () => {
                        const suggestions = await API.fetchTagSuggestions(term);
                        suggestionBox.innerHTML = '';
                        if (suggestions.length > 0) {
                            suggestionBox.style.display = 'block';

                            const fragment = document.createDocumentFragment();
                            suggestions.forEach(sugg => {
                                const item = document.createElement('div');
                                item.className = 'gbs-suggestion-item';

                                const labelSpan = document.createElement('span');
                                labelSpan.className = 'gbs-suggestion-label';

                                const nameSpan = document.createElement('span');
                                let category = sugg.category === 'tag' ? 'general' : sugg.category;
                                const color = Config.DECK_CONSTANTS.TAG_COLORS[category] || Config.DECK_CONSTANTS.TAG_COLORS.general;
                                nameSpan.style.color = color;
                                nameSpan.textContent = sugg.label.replace(/_/g, ' ');
                                const categorySpan = document.createElement('span');
                                categorySpan.className = 'gbs-suggestion-category';
                                categorySpan.textContent = `[${sugg.category}]`;

                                labelSpan.append(nameSpan, categorySpan);

                                const countSpan = document.createElement('span');
                                countSpan.className = 'gbs-suggestion-count';
                                countSpan.style.color = color;
                                countSpan.textContent = parseInt(sugg.post_count).toLocaleString();

                                item.append(labelSpan, countSpan);

                                item.onmousedown = (e) => {
                                    e.preventDefault();
                                    e.stopPropagation();
                                    let tagToAdd = sugg.value;
                                    if (tagInput.value.trim().startsWith('-')) {
                                        tagToAdd = '-' + tagToAdd;
                                    }
                                    addPill(tagToAdd);
                                    tagInput.value = '';
                                    suggestionBox.style.display = 'none';
                                    tagInput.focus();
                                };
                                fragment.appendChild(item);
                            });
                            suggestionBox.appendChild(fragment);
                        } else {
                            suggestionBox.style.display = 'none';
                        }
                    }, 250);
                });
                tagInput.addEventListener('blur', () => setTimeout(() => { suggestionBox.style.display = 'none'; }, 150));

                closeBtn.addEventListener('click', closeModal);
                modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) closeModal(); });
                applyBtn.addEventListener('click', () => {
                    syncToOriginalInput();
                    closeModal();
                    originalInput.form.submit();
                });
                return { openModal };
            }
        }
    };

    // =================================================================================
    // PEEK: PREVIEWS & LIGHTBOX MODULE
    // =================================================================================
    const Peek = {
        State: {
            currentThumb: null,
            hideTimeout: null,
            showTimeout: null,
            dynamicSeekTime: 5,
            previewElements: null,
            lastHoveredThumb: null,
            pendingPreviewRequest: null,
            thumbnailListenerCleanups: new Map()
        },
        init: function() {
            this.injectStyles();
            document.querySelectorAll(Config.SELECTORS.THUMBNAIL_GRID_SELECTOR).forEach(this.initializeThumbnailFeatures.bind(this));
            const observer = new MutationObserver((mutations) => {
                if (GlobalState.previewsTemporarilyDisabled) return;

                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === 1) {
                            if (node.matches(Config.SELECTORS.THUMBNAIL_GRID_SELECTOR)) this.initializeThumbnailFeatures(node);
                            node.querySelectorAll(Config.SELECTORS.THUMBNAIL_GRID_SELECTOR).forEach(this.initializeThumbnailFeatures.bind(this));
                        }
                    });
                    mutation.removedNodes.forEach(node => {
                        if (node.nodeType === 1) {
                            if (node.matches(Config.SELECTORS.THUMBNAIL_GRID_SELECTOR)) this.cleanupThumbnailFeatures(node);
                            node.querySelectorAll(Config.SELECTORS.THUMBNAIL_GRID_SELECTOR).forEach(this.cleanupThumbnailFeatures.bind(this));
                        }
                    });
                });
            });
            observer.observe(document.body, { childList: true, subtree: true });

            window.addEventListener('pagehide', this.cleanupAllFeatures.bind(this));
        },
        initLightbox: function() {
            this.injectStyles();
            const contentElement = document.querySelector(Config.SELECTORS.IMAGE_SELECTOR) || document.querySelector(Config.SELECTORS.VIDEO_PLAYER_SELECTOR);
            if (contentElement) {
                Object.assign(contentElement.style, { cursor: 'zoom-in' });
                this.UI.createLightboxForContent(contentElement);
            }
        },
        injectStyles: function() {
            GM_addStyle(`
                .thumbnail-preview img { border-radius: 8px !important; }
                .thumbnail-preview img.gbs-animated-img { box-shadow: 0 0 0 2.5px #C2185B !important; }
                #${Config.SELECTORS.PREVIEW_CONTAINER_ID} { position: fixed !important; z-index: 99999 !important; opacity: 0; transform: translate(-50%, -50%) scale(1); pointer-events: none; background-color: #000; border-radius: 8px; box-shadow: 0 0 4px 4px rgba(0,0,0,0.6); overflow: hidden; transition: transform 0.35s ease-out, opacity 0s linear 0.35s; }
                #${Config.SELECTORS.PREVIEW_CONTAINER_ID}:focus { outline: none; }
                #${Config.SELECTORS.PREVIEW_CONTAINER_ID}.show { opacity: 1; transform: translate(-50%, -50%) scale(${Settings.State.ZOOM_SCALE_FACTOR}); pointer-events: auto; cursor: pointer; transition: transform 0.35s ease-out, opacity 0.35s ease-out; }
                .enhancer-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; transition: opacity 0.2s ease-in-out; background-color: transparent; border: none; outline: none; }
                .enhancer-seekbar-container { position: absolute; bottom: 0; left: 0; width: 100%; height: 20px; display: none; justify-content: center; align-items: center; }
                #${Config.SELECTORS.PREVIEW_CONTAINER_ID}.video-active .enhancer-seekbar-container { display: flex; }
                .enhancer-seekbar { opacity: 0; transition: opacity 0.2s ease-out; pointer-events: none; width: 95%; margin: 0; height: 5.5px; -webkit-appearance: none; appearance: none; background: rgba(255,255,255,0.15); outline: none; border-radius: 4px; border: none; }
                .enhancer-seekbar-container:hover .enhancer-seekbar { opacity: 1; pointer-events: auto; }
                .enhancer-seekbar::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 8px; height: 8px; background: #fff; cursor: pointer; border-radius: 50%; }
                .enhancer-seekbar::-moz-range-thumb { width: 9.5px; height: 9.5px; background: #fff; cursor: pointer; border-radius: 50%; border: none; }
                #enhancer-lightbox-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; z-index: 20000; }
                .enhancer-lightbox-content {  position: relative; z-index: 20002; max-width: 95vw !important; max-height: 95vh !important; width: auto !important; height: auto !important; object-fit: contain !important; transition: transform .1s linear; box-shadow: 0 0 30px rgba(0,0,0,0.5); }
                main #image.fit-width, main video#gelcomVideoPlayer { width: auto !important; margin: 0 !important; max-width: 70vw !important; max-height: 78vh !important; object-fit: contain !important; }
                .enhancer-lightbox-hotzone { position: fixed; top: 20; height: 33vh; width: 50%; z-index: 20001; }
                .enhancer-hotzone-left { left: 0; cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.95)' stroke='rgba(0,0,0,0.5)' stroke-width='1' d='M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'/%3E%3C/svg%3E") 16 16, auto; }
                .enhancer-hotzone-right { right: 0; cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.95)' stroke='rgba(0,0,0,0.5)' stroke-width='1' d='M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z'/%3E%3C/svg%3E") 16 16, auto; }
            `);
        },
        initializeThumbnailFeatures: function(grid) {
            if (grid.dataset.enhancerInitialized) return;
            grid.dataset.enhancerInitialized = 'true';

            Logger.log('Initializing Peek features for grid:', grid);

            if (!this.State.previewElements) {
                this.UI.createPreviewElement();
            }

            const thumbnailClickHandler = function(event) {
                if (Downloader.State.isSelectionModeActive) {
                    return;
                }

                event.preventDefault();
                event.stopPropagation();
                GM_openInTab(this.href, { active: false, setParent: true });
            };

            // Decorate thumbnails (e.g., for animated gifs)
            grid.querySelectorAll(Config.SELECTORS.THUMBNAIL_ANCHOR_SELECTOR).forEach(thumbLink => {
                const img = thumbLink.querySelector('img');
                if (img) {
                    const title = img.getAttribute('title') || '';
                    const isAnimated = title.includes('animated_gif') || title.includes('animated_png');
                    const isVideo = img.classList.contains('webm');
                    if (isAnimated && !isVideo) img.classList.add('gbs-animated-img');
                }
                thumbLink.addEventListener('click', thumbnailClickHandler, { capture: true });
            });

            // Strip titles to prevent native tooltips from conflicting with Peek
            grid.querySelectorAll('[title]').forEach(el => el.removeAttribute('title'));
            const stripTitleHandler = e => {
                const el = e.target.closest('[title]');
                if (el) el.removeAttribute('title');
            };
            grid.addEventListener('mouseover', stripTitleHandler, true);

            const observer = new MutationObserver(mutations => {
                for (const mutation of mutations) {
                    if (mutation.type === 'attributes' && mutation.attributeName === 'title') {
                        mutation.target.removeAttribute('title');
                    } else if (mutation.type === 'childList') {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType === 1) {
                                if (node.hasAttribute?.('title')) node.removeAttribute('title');
                                node.querySelectorAll?.('[title]').forEach(el => el.removeAttribute('title'));
                            }
                        });
                    }
                }
            });
            observer.observe(grid, { subtree: true, childList: true, attributes: true, attributeFilter: ['title'] });

            grid.gbsThumbnailClickHandler = thumbnailClickHandler;

            const cleanup = this.UI.setupThumbnailEventListeners(grid);
            this.State.thumbnailListenerCleanups.set(grid, {
                cleanupFunc: () => cleanup(stripTitleHandler),
                observer
            });
        },
        cleanupThumbnailFeatures: function(grid) {
            if (!grid.dataset.enhancerInitialized) return;
            Logger.log('Cleaning up Peek features for grid:', grid);

            if (grid.gbsThumbnailClickHandler) {
                grid.querySelectorAll(Config.SELECTORS.THUMBNAIL_ANCHOR_SELECTOR).forEach(thumbLink => {
                    thumbLink.removeEventListener('click', grid.gbsThumbnailClickHandler, { capture: true });
                });
                delete grid.gbsThumbnailClickHandler;
            }

            const cleanupData = this.State.thumbnailListenerCleanups.get(grid);
            if (cleanupData) {
                cleanupData.cleanupFunc();
                cleanupData.observer.disconnect();
                this.State.thumbnailListenerCleanups.delete(grid);
            }

            if (this.State.thumbnailListenerCleanups.size === 0) {
                this.UI.destroyPreviewElement();
            }
            delete grid.dataset.enhancerInitialized;
        },
        cleanupAllFeatures: function() {
            Logger.log('Page is hiding. Cleaning up all residual Peek features.');
            for (const grid of this.State.thumbnailListenerCleanups.keys()) {
                this.cleanupThumbnailFeatures(grid);
            }
        },
        UI: {
            createPreviewElement: function() {
                const previewContainer = document.createElement('div');
                previewContainer.id = Config.SELECTORS.PREVIEW_CONTAINER_ID;
                previewContainer.setAttribute('tabindex', '-1');
                previewContainer.innerHTML = `<img class="enhancer-layer low-res-img"><img class="enhancer-layer high-res-img"><video class="enhancer-layer video-layer" playsinline></video><div class="enhancer-error-message"></div><div class="enhancer-seekbar-container"><input type="range" class="enhancer-seekbar" value="0" step="0.1"></div>`;
                document.body.appendChild(previewContainer);

                const elements = {
                    previewContainer,
                    lowResImg: previewContainer.querySelector('.low-res-img'),
                    highResImg: previewContainer.querySelector('.high-res-img'),
                    videoLayer: previewContainer.querySelector('.video-layer'),
                    errorMessage: previewContainer.querySelector('.enhancer-error-message'),
                    seekBarContainer: previewContainer.querySelector('.enhancer-seekbar-container'),
                    seekBar: previewContainer.querySelector('.enhancer-seekbar'),
                    handlers: {}
                };
                Peek.State.previewElements = elements;
                return elements;
            },
            destroyPreviewElement: function() {
                if (!Peek.State.previewElements) return;
                Logger.log('Destroying Peek Preview element and its listeners.');

                if (Peek.State.pendingPreviewRequest) {
                    Peek.State.pendingPreviewRequest.abort();
                    Peek.State.pendingPreviewRequest = null;
                }

                const { previewContainer, handlers } = Peek.State.previewElements;
                previewContainer.removeEventListener('mouseenter', handlers.stopHideTimer);
                previewContainer.removeEventListener('mouseleave', handlers.startHideTimer);
                previewContainer.removeEventListener('click', handlers.handlePreviewClick);
                previewContainer.removeEventListener('keydown', handlers.handlePreviewKeyDown);
                Peek.State.previewElements.videoLayer.removeEventListener('timeupdate', handlers.handleVideoTimeUpdate);
                Peek.State.previewElements.seekBar.removeEventListener('input', handlers.handleSeekBarInput);
                Peek.State.previewElements.seekBarContainer.removeEventListener('click', handlers.handleSeekBarContainerClick);
                window.removeEventListener('scroll', handlers.handleInstantHideOnScroll);
                if (previewContainer) {
                    previewContainer.remove();
                }

                Peek.State.previewElements = null;
            },
            hidePreview: function() {
                if (!Peek.State.previewElements) return;
                if (Peek.State.pendingPreviewRequest) {
                    Peek.State.pendingPreviewRequest.abort();
                    Peek.State.pendingPreviewRequest = null;
                }

                const { previewContainer, videoLayer } = Peek.State.previewElements;
                videoLayer.pause();
                videoLayer.removeAttribute('src');
                videoLayer.load();
                previewContainer.classList.remove('show', 'video-active', 'error', 'loading');
                previewContainer.blur();
                Peek.State.currentThumb = null;
            },
            updatePreviewPositionAndSize: function(thumb, previewContainer) {
                const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
                const thumbImg = thumb.querySelector('img');
                if (!thumbImg) return;
                const thumbRect = thumb.getBoundingClientRect();
                const initialWidth = thumbRect.width;
                const initialHeight = thumbRect.height;
                const finalWidth = initialWidth * Settings.State.ZOOM_SCALE_FACTOR;
                const finalHeight = initialHeight * Settings.State.ZOOM_SCALE_FACTOR;
                const margin = 10;
                const rect = thumb.getBoundingClientRect();
                let idealTop = rect.top + (rect.height / 2);
                let idealLeft = rect.left + (rect.width / 2);
                idealLeft = clamp(idealLeft, finalWidth / 2 + margin, window.innerWidth - finalWidth / 2 - margin);
                idealTop = clamp(idealTop, finalHeight / 2 + margin, window.innerHeight - finalHeight / 2 - margin);
                Object.assign(previewContainer.style, {
                    width: `${initialWidth}px`,
                    height: `${initialHeight}px`,
                    top: `${idealTop}px`,
                    left: `${idealLeft}px`
                });
            },
            showPreview: async function(thumb) {
                if (!Peek.State.previewElements) return;
                if (Peek.State.pendingPreviewRequest) {
                    Peek.State.pendingPreviewRequest.abort();
                    Peek.State.pendingPreviewRequest = null;
                }

                Peek.State.currentThumb = thumb;
                const { previewContainer, lowResImg, highResImg, videoLayer, errorMessage } = Peek.State.previewElements;
                const thumbImg = thumb.querySelector('img');
                if (!thumbImg) return;
                previewContainer.className = '';
                errorMessage.textContent = '';

                requestAnimationFrame(() => {
                    this.updatePreviewPositionAndSize(thumb, previewContainer);
                });
                lowResImg.src = thumbImg.src;
                lowResImg.style.opacity = '1';
                highResImg.src = "";
                highResImg.style.display = 'none';
                highResImg.style.opacity = '0';
                videoLayer.style.opacity = '0';
                videoLayer.pause();
                previewContainer.classList.add('show');
                if (Settings.State.PREVIEW_QUALITY === 'low') return;

                previewContainer.classList.add('loading');
                try {
                    const postId = Utils.getPostId(thumb.href);
                    const media = await API.fetchMediaDetails(postId);

                    if (Peek.State.currentThumb !== thumb) return;

                    previewContainer.classList.remove('loading');
                    if (media.type === 'video') {
                        videoLayer.src = media.url;
                        videoLayer.loop = Settings.State.PREVIEW_VIDEOS_LOOP;
                        videoLayer.muted = Settings.State.PREVIEW_VIDEOS_MUTED;
                        videoLayer.play().catch(() => {});
                        videoLayer.onloadedmetadata = () => {
                            if (Peek.State.currentThumb === thumb) {
                                const seekStep = videoLayer.duration * 0.05;
                                Peek.State.dynamicSeekTime = Math.max(1, Math.min(seekStep, 10));
                                videoLayer.style.opacity = '1';
                                previewContainer.classList.add('video-active');
                                previewContainer.focus();
                            }
                        };
                    } else {
                        highResImg.src = media.url;
                        highResImg.onload = () => {
                            if (Peek.State.currentThumb === thumb) {
                                highResImg.style.display = 'block';
                                highResImg.style.opacity = '1';
                            }
                        };
                    }
                } catch (error) {
                    if (error.message.includes('abort')) return;
                    if (Peek.State.currentThumb === thumb) {
                        previewContainer.classList.remove('loading');
                        previewContainer.classList.add('error');
                        errorMessage.textContent = error.message;
                    }
                }
            },
            setupThumbnailEventListeners: function(thumbnailGrid) {
                if (!Peek.State.previewElements) return () => {};
                const self = Peek; // Reference to the Peek module
                const { handlers, previewContainer, videoLayer, seekBarContainer, seekBar } = self.State.previewElements;
                handlers.handleInstantHideOnScroll = () => {
                    if (!self.State.previewElements?.previewContainer || !self.State.currentThumb) { return; }
                    const { previewContainer } = self.State.previewElements;
                    previewContainer.style.transition = 'none';
                    self.UI.hidePreview();
                    setTimeout(() => {
                        if (previewContainer) { previewContainer.style.transition = ''; }
                    }, 50);
                };
                handlers.startHideTimer = () => {
                    clearTimeout(self.State.showTimeout);
                    clearTimeout(self.State.hideTimeout);
                    self.State.hideTimeout = setTimeout(() => self.UI.hidePreview(), Settings.State.HIDE_DELAY);
                };
                handlers.stopHideTimer = () => { clearTimeout(self.State.hideTimeout); };
                handlers.handleGridMouseOver = (e) => {
                    const thumb = e.target.closest(Config.SELECTORS.THUMBNAIL_ANCHOR_SELECTOR);
                    if (thumb) {
                        handlers.stopHideTimer();
                        if (self.State.lastHoveredThumb && self.State.lastHoveredThumb !== thumb) {
                            const oldTitle = self.State.lastHoveredThumb.dataset.originalTitle;
                            if (oldTitle) self.State.lastHoveredThumb.setAttribute('title', oldTitle);
                        }
                        if (thumb.hasAttribute('title')) {
                            thumb.dataset.originalTitle = thumb.getAttribute('title');
                            thumb.removeAttribute('title');
                        }
                        self.State.lastHoveredThumb = thumb;
                        if (self.State.currentThumb !== thumb) {
                            if (self.State.currentThumb) self.UI.hidePreview();
                            self.State.showTimeout = setTimeout(() => self.UI.showPreview(thumb), Settings.State.SHOW_DELAY);
                        }
                    } else if (!previewContainer.matches(':hover')) {
                        handlers.startHideTimer();
                    }
                };
                handlers.handleGridMouseLeave = () => {
                    if (self.State.lastHoveredThumb) {
                        const oldTitle = self.State.lastHoveredThumb.dataset.originalTitle;
                        if (oldTitle) self.State.lastHoveredThumb.setAttribute('title', oldTitle);
                        self.State.lastHoveredThumb = null;
                    }
                    handlers.startHideTimer();
                };
                handlers.handlePreviewClick = () => { if (self.State.currentThumb?.href) GM_openInTab(self.State.currentThumb.href, { active: false, setParent: true }); };
                handlers.handlePreviewKeyDown = e => {
                    if (self.State.currentThumb && videoLayer.style.opacity === '1') {
                        const hotkeys = [Settings.State.KEY_PEEK_VIDEO_SEEK_BACK, Settings.State.KEY_PEEK_VIDEO_SEEK_FORWARD, Settings.State.KEY_PEEK_VIDEO_PLAY_PAUSE];
                        if (hotkeys.includes(e.key)) e.preventDefault();
                        if (e.key === Settings.State.KEY_PEEK_VIDEO_SEEK_BACK) videoLayer.currentTime -= self.State.dynamicSeekTime;
                        else if (e.key === Settings.State.KEY_PEEK_VIDEO_SEEK_FORWARD) videoLayer.currentTime += self.State.dynamicSeekTime;
                        else if (e.key === Settings.State.KEY_PEEK_VIDEO_PLAY_PAUSE) videoLayer.paused ? videoLayer.play() : videoLayer.pause();
                    }
                };
                handlers.handleVideoTimeUpdate = () => {
                    if (videoLayer.duration) {
                        if (seekBar.max != videoLayer.duration) seekBar.max = videoLayer.duration;
                        seekBar.value = videoLayer.currentTime;
                    }
                };
                handlers.handleSeekBarInput = e => { e.stopPropagation(); videoLayer.currentTime = seekBar.value; };
                handlers.handleSeekBarContainerClick = e => e.stopPropagation();
                thumbnailGrid.addEventListener('mouseover', handlers.handleGridMouseOver);
                thumbnailGrid.addEventListener('mouseleave', handlers.handleGridMouseLeave);
                previewContainer.addEventListener('mouseenter', handlers.stopHideTimer);
                previewContainer.addEventListener('mouseleave', handlers.startHideTimer);
                previewContainer.addEventListener('click', handlers.handlePreviewClick);
                previewContainer.addEventListener('keydown', handlers.handlePreviewKeyDown);
                videoLayer.addEventListener('timeupdate', handlers.handleVideoTimeUpdate);
                seekBar.addEventListener('input', handlers.handleSeekBarInput);
                seekBarContainer.addEventListener('click', handlers.handleSeekBarContainerClick);
                window.addEventListener('scroll', handlers.handleInstantHideOnScroll, { passive: true });
                return (stripTitleHandler) => {
                    Logger.log(`Cleaning up event listeners for grid:`, thumbnailGrid);
                    thumbnailGrid.removeEventListener('mouseover', handlers.handleGridMouseOver);
                    thumbnailGrid.removeEventListener('mouseleave', handlers.handleGridMouseLeave);
                    thumbnailGrid.removeEventListener('mouseover', stripTitleHandler, true);
                };
            },
            createLightboxForContent: function(contentElement) {
                let zoomDestroyer = null;
                let wrapperToRemove = null;
                if (contentElement && contentElement.parentElement.classList.contains('enhancer-reopen-wrapper')) {
                    wrapperToRemove = contentElement.parentElement;
                }
                if (!contentElement || document.getElementById('enhancer-lightbox-overlay')) return;
                const originalParent = wrapperToRemove ? wrapperToRemove.parentElement : contentElement.parentElement;
                const originalNextSibling = wrapperToRemove ? wrapperToRemove.nextElementSibling : contentElement.nextElementSibling;
                const originalStyles = { cursor: contentElement.style.cursor, maxWidth: contentElement.style.maxWidth, maxHeight: contentElement.style.maxHeight };
                const overlay = document.createElement('div');
                overlay.id = 'enhancer-lightbox-overlay';
                overlay.style.backgroundColor = Settings.State.LIGHTBOX_BG_COLOR;
                document.body.appendChild(overlay);
                const originalNavPrev = document.querySelector('a[onclick="navigatePrev();"]');
                const originalNavNext = document.querySelector('a[onclick="navigateNext();"]');
                if (originalNavPrev && originalNavNext) {
                    const leftHotzone = document.createElement('div');
                    leftHotzone.className = 'enhancer-lightbox-hotzone enhancer-hotzone-left';
                    leftHotzone.onclick = () => {
                        if (typeof navigatePrev === 'function') navigatePrev();
                    };
                    overlay.appendChild(leftHotzone);
                    const rightHotzone = document.createElement('div');
                    rightHotzone.className = 'enhancer-lightbox-hotzone enhancer-hotzone-right';
                    rightHotzone.onclick = () => {
                        if (typeof navigateNext === 'function') navigateNext();
                    };
                    overlay.appendChild(rightHotzone);
                }
                contentElement.classList.add('enhancer-lightbox-content');
                if (wrapperToRemove) {
                    overlay.appendChild(contentElement);
                    wrapperToRemove.remove();
                } else {
                    overlay.appendChild(contentElement);
                }
                if (contentElement.tagName === 'VIDEO' && Settings.State.AUTOPLAY_LIGHTBOX_VIDEOS) {
                    contentElement.muted = false;
                    contentElement.play().catch(() => {});
                }
                if (contentElement.tagName === 'IMG') {
                    zoomDestroyer = Zoom.enable(contentElement, { clickAction: 'none' });
                }
                const closeLightbox = () => {
                    if (zoomDestroyer) zoomDestroyer.destroy();
                    contentElement.style.transform = '';
                    contentElement.style.cursor = '';
                    if (originalParent) {
                        if (contentElement.tagName === 'VIDEO') {
                            const videoWrapper = document.createElement('div');
                            videoWrapper.className = 'enhancer-reopen-wrapper';
                            videoWrapper.style.position = 'relative';
                            videoWrapper.style.display = 'inline-block';
                            videoWrapper.style.lineHeight = '0';
                            const clickOverlay = document.createElement('div');
                            clickOverlay.style.position = 'absolute';
                            clickOverlay.style.top = '0';
                            clickOverlay.style.left = '0';
                            clickOverlay.style.width = '100%';
                            clickOverlay.style.height = '100%';
                            clickOverlay.style.cursor = 'zoom-in';
                            clickOverlay.style.zIndex = '1';
                            videoWrapper.appendChild(contentElement);
                            videoWrapper.appendChild(clickOverlay);
                            if (originalNextSibling) {
                                originalParent.insertBefore(videoWrapper, originalNextSibling);
                            } else {
                                originalParent.appendChild(videoWrapper);
                            }
                            clickOverlay.addEventListener('click', () => {
                                Peek.UI.createLightboxForContent(contentElement);
                            }, { once: true });
                        } else {
                            if (originalNextSibling) {
                                originalParent.insertBefore(contentElement, originalNextSibling);
                            } else {
                                originalParent.appendChild(contentElement);
                            }
                            contentElement.addEventListener('click', () => Peek.UI.createLightboxForContent(contentElement), { once: true });
                        }
                        Object.assign(contentElement.style, originalStyles);
                        contentElement.classList.remove('enhancer-lightbox-content');
                    }
                    overlay.remove();
                    document.removeEventListener('keydown', escapeHandler);
                };
                const escapeHandler = e => { if (e.key === "Escape") closeLightbox(); };
                overlay.addEventListener('click', e => { if (e.target === overlay) closeLightbox(); });
                document.addEventListener('keydown', escapeHandler);
            }
        }
    };

    // =================================================================================
    // DECK: IMMERSIVE VIEWER MODULE
    // =================================================================================
    const Deck = {
        State: {
            galleryData: { posts: [], startIndex: 0, nextPageUrl: null, prevPageUrl: null, baseUrl: '', lastPageNum: 1 },
            currentIndex: 0,
            isLoadingNextPage: false,
            navigationHistory: [],
            bookmarks: [],
            lastNavigationDirection: 1,
            deckKeyDownHandler: null,
        },
        initGalleryButton: function() {
            const submenu = document.querySelector(Config.SELECTORS.galleryNavSubmenu);
            if (!submenu) {
                Logger.error("Deck Error: Could not find the navigation submenu to attach the viewer button.");
                return;
            }
            const viewerButton = document.createElement('a');
            viewerButton.href = '#';
            viewerButton.textContent = 'Open Deck';
            viewerButton.style.cssText = 'color:#006FFA;font-weight:700';
            submenu.appendChild(viewerButton);
            viewerButton.addEventListener('click', async (e) => {
                e.preventDefault();
                if (viewerButton.textContent.includes('Loading')) return;
                const current = document.querySelector(Config.SELECTORS.PAGINATION_CURRENT_SELECTOR);
                this.State.galleryData.nextPageUrl = window.location.href;
                this.State.galleryData.prevPageUrl = current?.previousElementSibling?.href || null;
                this.State.galleryData.baseUrl = window.location.href;
                viewerButton.textContent = 'Loading API...';
                try {
                    const newIndex = await API.fetchPage(false);
                    if (newIndex === null) {
                        alert("Deck Error: API returned no posts for this page. Check your tags or API credentials.");
                        return;
                    }
                    this.run();
                } catch (error) {
                    alert(`Deck Error: Failed to fetch initial data from API. ${error.message}`);
                    Logger.error(error);
                } finally {
                    viewerButton.textContent = 'Open Deck';
                }
            });
        },
        run: async function() {
            await this.loadBookmarks();
            this.UI.setupViewer();
            this.UI.updateBookmarkCounter();
            if (this.State.galleryData.posts && this.State.galleryData.posts.length > 0) {
                this.updateHistory(this.getCurrentPageNum());
                this.loadMedia(this.State.currentIndex);
                this.State.deckKeyDownHandler = this.handleKeyDown.bind(this);
                document.addEventListener('keydown', this.State.deckKeyDownHandler);
            } else {
                this.UI.showLoadingMessage("Error: Gallery data is empty or corrupt.");
            }
        },
        close: function() {
            const finalDeckUrl = this.State.galleryData.baseUrl;
            const currentBrowserUrl = window.location.href;
            if (finalDeckUrl && finalDeckUrl !== currentBrowserUrl) {
                window.location.href = finalDeckUrl;
            } else {
                if (this.State.deckKeyDownHandler) {
                    document.removeEventListener('keydown', this.State.deckKeyDownHandler);
                    this.State.deckKeyDownHandler = null;
                }
                this.UI.destroyViewer();
            }
        },
        loadMedia: async function(index) {
            if (index >= this.State.galleryData.posts.length) { await this._handlePageTransition(false); return; }
            if (index < 0) { await this._handlePageTransition(true); return; }
            const post = this.State.galleryData.posts[index];
            this.State.lastNavigationDirection = index > this.State.currentIndex ? 1 : (index < this.State.currentIndex ? -1 : this.State.lastNavigationDirection || 1);
            if (post.isBroken) { this.loadMedia(index + this.State.lastNavigationDirection); return; }
            this.State.currentIndex = index;
            this.manageMemory();
            if (post.mediaUrl && post.tags) {
                this.UI.displayMedia(post.mediaUrl, post.type, post.tags);
            } else {
                const loadingEl = this.UI.mainContainer?.querySelector('.loading-text');
                if (loadingEl) { loadingEl.textContent = "Loading..."; loadingEl.style.display = 'block'; }
                try {
                    const { promise } = Utils.makeRequest({ method: "GET", url: post.postUrl });
                    const result = await API.getPostData(promise);
                    post.mediaUrl = result.contentUrl;
                    post.tags = result.tags;
                    if (this.State.currentIndex === index) { this.UI.displayMedia(post.mediaUrl, post.type, post.tags); }
                } catch (error) {
                    Logger.error(error.message);
                    post.isBroken = true;
                    if (this.State.currentIndex === index) { this.loadMedia(this.State.currentIndex + this.State.lastNavigationDirection); }
                }
            }
            this.UI.updateThumbnails();
            this.preloadMedia(this.State.currentIndex);
        },
        _handlePageTransition: async function(isPrev) {
            this.UI.showLoadingMessage('Loading ' + (isPrev ? 'previous' : 'next') + ' page...');
            const newIndex = await API.fetchPage(isPrev);
            if (newIndex !== null) { this.loadMedia(newIndex); }
        },
        preloadMedia: function(centerIndex) {
            const aheadLimit = Math.min(centerIndex + 1 + Settings.State.DECK_PRELOAD_AHEAD, this.State.galleryData.posts.length);
            for (let i = centerIndex + 1; i < aheadLimit; i++) { this.fetchAndCache(i); }
            const behindLimit = Math.max(0, centerIndex - Settings.State.DECK_PRELOAD_BEHIND);
            for (let i = centerIndex - 1; i >= behindLimit; i--) { this.fetchAndCache(i); }
        },
        fetchAndCache: async function(index) {
            const post = this.State.galleryData.posts[index];
            if (post && (!post.mediaUrl || !post.tags) && !post.isBroken) {
                try {
                    const { promise } = Utils.makeRequest({ method: "GET", url: post.postUrl });
                    const result = await API.getPostData(promise);
                    post.mediaUrl = result.contentUrl;
                    post.tags = result.tags;
                    if (result.type === 'image') { new Image().src = result.contentUrl; }
                    this.UI.updateThumbnails();
                } catch (error) {
                    post.isBroken = true;
                    this.UI.updateThumbnails();
                    Logger.error(`Failed to pre-cache post ${index}: ${error.message}`);
                }
            }
        },
        retryFetch: async function(index) {
            const post = this.State.galleryData.posts[index];
            if (!post || !post.isBroken) return;
            Logger.log(`Retrying fetch for post index: ${index}`);
            post.isBroken = false;
            post.mediaUrl = null;
            post.tags = null;
            post.type = null;
            const thumb = document.querySelector(`.thumb-container[data-index="${index}"]`);
            if (thumb) {
                thumb.classList.remove('broken-thumb');
                thumb.classList.add('loading');
                const retryIcon = thumb.querySelector('.retry-icon');
                if (retryIcon) retryIcon.style.display = 'none';
            }
            await this.fetchAndCache(index);
            if (thumb) { thumb.classList.remove('loading'); }
        },
        manageMemory: function() {
            const centerIndex = this.State.currentIndex;
            const buffer = 2;
            const lowerBound = centerIndex - (Settings.State.DECK_PRELOAD_BEHIND * buffer);
            const upperBound = centerIndex + (Settings.State.DECK_PRELOAD_AHEAD * buffer);
            this.State.galleryData.posts.forEach((post, index) => {
                if (post.mediaUrl && (index < lowerBound || index > upperBound)) {
                    post.mediaUrl = null;
                    post.tags = null;
                }
            });
        },
        getCurrentPageNum: function() {
            try {
                const url = new URL(this.State.galleryData.baseUrl);
                const pid = parseInt(url.searchParams.get('pid'), 10) || 0;
                return Math.floor(pid / Config.DECK_CONSTANTS.POSTS_PER_PAGE) + 1;
            } catch { return 1; }
        },
        updateHistory: function(pageNum) {
            if (this.State.navigationHistory[0] === pageNum) { return; }
            const existingIndex = this.State.navigationHistory.indexOf(pageNum);
            if (existingIndex > -1) { this.State.navigationHistory.splice(existingIndex, 1); }
            this.State.navigationHistory.unshift(pageNum);
            if (this.State.navigationHistory.length > Config.DECK_CONSTANTS.HISTORY_LENGTH) { this.State.navigationHistory.pop(); }
        },
        performJump: async function(pageNum) {
            if (!pageNum || pageNum <= 0 || !this.State.galleryData.baseUrl) return;
            this.updateHistory(this.getCurrentPageNum());
            const pid = (pageNum - 1) * Config.DECK_CONSTANTS.POSTS_PER_PAGE;
            const url = new URL(this.State.galleryData.baseUrl);
            url.searchParams.set('pid', pid.toString());
            this.State.galleryData.nextPageUrl = url.href;
            this.UI.showLoadingMessage('Jumping to page ' + pageNum + '...');
            const newIndex = await API.fetchPage(false);
            if (newIndex !== null) {
                this.updateHistory(pageNum);
                this.loadMedia(newIndex);
            }
        },
        handleKeyDown: function(e) {
            const jumpOverlay = document.getElementById('jump-box-overlay');
            const settingsOverlay = document.getElementById(`${Config.SELECTORS.SETTINGS_MODAL_ID}-overlay`);
            const bookmarksOverlay = document.getElementById('bookmarks-overlay');
            const key = e.key;
            const deckHotkeys = {
                prev: Settings.State.KEY_DECK_PREV_MEDIA,
                next: Settings.State.KEY_DECK_NEXT_MEDIA,
                jump: Settings.State.KEY_DECK_JUMP_BOX,
                bookmark: Settings.State.KEY_DECK_TOGGLE_BOOKMARK,
                close: Settings.State.KEY_DECK_CLOSE_PANELS,
            };
            const isPopupOpen = (jumpOverlay && !jumpOverlay.classList.contains('hidden')) ||
                (settingsOverlay && settingsOverlay.style.display !== 'none') ||
                (bookmarksOverlay && !bookmarksOverlay.classList.contains('hidden'));
            if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
                if (key === deckHotkeys.close) { this.UI.toggleJumpBox(false); }
                return;
            }
            if (key === deckHotkeys.close) {
                e.preventDefault();
                if (isPopupOpen) {
                    this.UI.toggleJumpBox(false);
                    Settings.UI.closeModal();
                    this.UI.toggleBookmarksPanel(false);
                } else {
                    this.close();
                }
                return;
            }
            if (isPopupOpen) return;
            if (key === deckHotkeys.prev) { e.preventDefault(); this.loadMedia(this.State.currentIndex - 1); }
            else if (key === deckHotkeys.next) { e.preventDefault(); this.loadMedia(this.State.currentIndex + 1); }
            else if (key === deckHotkeys.jump) { e.preventDefault(); this.UI.toggleJumpBox(true); }
            else if (key === deckHotkeys.bookmark) { e.preventDefault(); this.toggleBookmark(); }
        },
        loadBookmarks: async function() {
            try {
                const savedBookmarks = await GM.getValue(Config.STORAGE_KEYS.DECK_BOOKMARKS, '[]');
                this.State.bookmarks = JSON.parse(savedBookmarks);
            } catch (e) {
                Logger.error("Deck: Could not load or parse bookmarks.", e);
                this.State.bookmarks = [];
            }
        },
        saveBookmarks: async function() {
            try {
                const jsonString = JSON.stringify(this.State.bookmarks);
                await GM.setValue(Config.STORAGE_KEYS.DECK_BOOKMARKS, jsonString);
            } catch (e) {
                Logger.error("Deck: Failed to save bookmarks.", e);
            }
        },
        toggleBookmark: function() {
            const post = this.State.galleryData.posts[this.State.currentIndex];
            if (!post) return;
            const postId = Utils.getPostId(post.postUrl);
            const bookmarkIndex = this.State.bookmarks.findIndex(b => b.id === postId);
            if (bookmarkIndex > -1) { this.State.bookmarks.splice(bookmarkIndex, 1); } else { this.State.bookmarks.push({ id: postId, postUrl: post.postUrl, thumbUrl: post.thumbUrl }); }
            this.updateUIAfterBookmarkChange();
        },
        removeBookmark: function(postId) {
            const bookmarkIndex = this.State.bookmarks.findIndex(b => b.id === postId);
            if (bookmarkIndex > -1) { this.State.bookmarks.splice(bookmarkIndex, 1); }
            this.updateUIAfterBookmarkChange();
        },
        updateUIAfterBookmarkChange: function() {
            this.UI.renderBookmarkedThumbnails();
            this.UI.updateBookmarkButtonState();
            this.UI.updateBookmarkCounter();
            this.saveBookmarks();
        },
        UI: {
            mainContainer: null,
            thumbContainer: null,
            tagsList: null,
            inactivityTimer: null,
            escapeHandler: null,
            _getDeckBaseStyles: function() { return `
                #${Config.SELECTORS.DECK_VIEWER_ID} { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #111 !important; z-index: 99998; display: flex; flex-direction: column; font-family: sans-serif; }
                .hide-cursor, .hide-cursor * { cursor: none !important; }
                #viewer-main { position: relative; flex-grow: 1; display: flex; align-items: center; justify-content: center; overflow: hidden; }
                #viewer-main:focus { outline: none; }
                .media-slot { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.1s ease-in-out; z-index: 5; pointer-events: none; }
                .media-slot.active { opacity: 1; z-index: 10; pointer-events: auto; }
                .media-wrapper { position: relative; display: inline-block; vertical-align: middle; line-height: 0; }
                .media-wrapper > img, .media-wrapper > video { max-width: 98vw; max-height: 88vh; object-fit: contain; }
                #viewer-main img { cursor: pointer; transform-origin: 0 0; transition: transform .1s linear; }
                .loading-text { color: #fff; text-align: center; padding: 50px; font-size: 1.5em; z-index: 100; position: absolute; }
            `;
            },
            _getDeckUIComponentsStyles: function() { return `
                .nav-btn { position: absolute; top: 50%; transform: translateY(-50%); background: 0; color: #fff; border: none; font-size: 120px; cursor: pointer; opacity: 0; transition: opacity .2s; user-select: none; z-index: 20; text-shadow: 0 0 10px #000; }
                #prev-btn { left: 100px; }
                #next-btn { right: 100px; }
                .nav-hotzone { position: absolute; top: 50%; transform: translateY(-50%); height: 300px; width: 15%; z-index: 15; }
                #left-hotzone { left: 0; }
                #right-hotzone { right: 0; }
                #left-hotzone:hover ~ #prev-btn, #prev-btn:hover, #right-hotzone:hover ~ #next-btn, #next-btn:hover { opacity: .6; }
                #bottom-bar { display: flex; align-items: center; background-color: #222; padding: 5px; flex-shrink: 0; height: 12vh; min-height: 80px; box-sizing: border-box; opacity: .2; transition: opacity .3s ease-in-out; z-index: 25; }
                #bottom-bar.visible { opacity: 1; }
                #jump-to-page-btn, #bookmarks-panel-btn { position: relative; width: 50px; height: 50px; margin: 0 10px; background-color: #444; color: #fff; border: 1px solid #666; border-radius: 5px; font-size: 24px; cursor: pointer; flex-shrink: 0; }
                #jump-to-page-btn:hover, #bookmarks-panel-btn:hover { background-color: #555; }
                #bookmark-count { position: absolute; bottom: 2px; right: 4px; font-size: 11px; font-weight: bold; background-color: #d00; color: #fff; border-radius: 50%; width: 16px; height: 16px; line-height: 16px; text-align: center; }
                #viewer-thumbnails { flex-grow: 1; text-align: center; height: 100%; overflow-x: auto; white-space: nowrap; box-sizing: border-box; }
                .thumb-container { position: relative; display: inline-block; height: 90%; width: 75px; margin: 0 4px; border: 2px solid transparent; cursor: pointer; vertical-align: middle; overflow: hidden; border-radius: 4px; }
                .thumb-img { height: 100%; width: 100%; display: block; object-fit: cover; }
                .thumb-container:hover { border-color: #fff; }
                .thumb-container.active { border-color: #006FFA; }
                .thumb-container.broken-thumb { border-color: #f55; opacity: 0.6; }
                .thumb-container.broken-thumb .thumb-img { filter: grayscale(1); }
                .video-icon { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: #fffa; font-size: 30px; text-shadow: 0 0 8px #0008; pointer-events: none; }
                .retry-icon { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff; font-size: 30px; text-shadow: 0 0 8px #000; z-index: 2; }
                .thumb-container.broken-thumb:hover .retry-icon { display: block; }
                #tags-menu { position: fixed; left: -300px; top: 0; width: 300px; height: 88vh; background-color: #000d; color: #fff; transition: left .3s; z-index: 30; padding: 10px; box-sizing: border-box; overflow-y: auto; }
                body:not(.tag-menu-disabled) #tags-trigger:hover + #tags-menu, #tags-menu:hover { left: 0; }
                #tags-trigger { position: fixed; left: 0; top: 0; width: 2px; height: 88vh; z-index: 29; }
                #tags-list { list-style: none; padding: 0; margin: 0; }
                .media-action-btn { position: absolute; z-index: 25; color: #fff !important; text-decoration: none; user-select: none; opacity: 0; transition: opacity .2s; background: rgba(0,0,0,0.4); border-radius: 5px; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; font-size: 20px; text-shadow: 0 0 10px #000; cursor: pointer; border: none; }
                .open-post-btn { top: 10px; right: 10px; }
                #bookmark-btn { top: 10px; left: 10px; }
                .media-wrapper:hover .media-action-btn { opacity: 0.7; }
                .media-action-btn:hover { opacity: 1; }
            `;
            },
            _getDeckOverlaysStyles: function() { return `
                #bookmarks-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 99999; display: flex; justify-content: center; align-items: center; }
                #bookmarks-overlay.hidden { display: none; }
                #bookmarks-panel { width: 80vw; max-width: 1200px; height: 80vh; display: flex; flex-direction: column; background-color: #252525; color: #eee; border: 1px solid #666; border-radius: 8px; z-index: 100000; padding: 20px; box-sizing: border-box;}
                #bookmarks-panel h3 { margin: 0 0 15px 0; padding-bottom: 10px; border-bottom: 1px solid #666; text-align: center; }
                #bookmarked-thumbs-list { flex-grow: 1; overflow-y: auto; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; align-content: flex-start; }
                #bookmarked-thumbs-list .thumb-container { height: 150px; width: 150px; }
                .empty-list-msg { color: #888; text-align: center; width: 100%; align-self: center; }
                .bookmark-delete-btn { position: absolute; top: 0; right: 0; width: 24px; height: 24px; background: rgba(0,0,0,0.6); color: #fff; border: none; cursor: pointer; font-size: 14px; opacity: 0; transition: opacity .2s; border-radius: 0 4px 0 4px; }
                #bookmarked-thumbs-list .thumb-container:hover .bookmark-delete-btn { opacity: 1; }
                .bookmark-delete-btn:hover { background: #A43535; }
                #bookmark-actions { display: flex; align-items: center; gap: 10px; margin-top: auto; padding-top: 15px; border-top: 1px solid #555; }
                #bookmark-import-area { flex-grow: 1; height: 40px; background: #333; color: #fff; border: 1px solid #555; border-radius: 4px; padding: 5px; box-sizing: border-box; resize: none; min-width: 0; }
                .bookmark-buttons { display: flex; gap: 10px; }
                .bookmark-buttons button { background-color: #444; color: #fff; border: 1px solid #666; padding: 8px 12px; border-radius: 4px; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; }
                .bookmark-buttons button:hover { background-color: #555; }
                #bookmark-info-msg { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); font-size: 12px; color: #999; height: 14px; transition: color .3s; }
                @keyframes shake { 10%, 90% { transform: translate(-1px, 0); } 20%, 80% { transform: translate(2px, 0); } 30%, 50%, 70% { transform: translate(-4px, 0); } 40%, 60% { transform: translate(4px, 0); } }
                #jump-box-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 99999; display: flex; justify-content: center; align-items: center; opacity: 1; transition: opacity 0.2s; }
                #jump-box-overlay.hidden { opacity: 0; pointer-events: none; }
                #jump-box { background-color: #252525; padding: 20px; border-radius: 8px; border: 1px solid #666; box-shadow: 0 5px 25px #000a; width: auto; min-width: 480px; text-align: center; position: relative; padding-bottom: 70px; }
                #jump-box.invalid .jump-controls { animation: shake 0.5s; }
                #jump-box.invalid #jump-input { border-color: #f55; }
                .jump-controls { display: flex; align-items: center; justify-content: center; gap: 15px; padding: 5px; margin-top: 10px; transition: border-color .2s; }
                #jump-box.loading .jump-controls, #jump-box.loading #pagination-bar, #jump-box.loading #jump-history { display: none; }
                #jump-box.loading #jump-spinner { display: flex; }
                #jump-input { width: 180px; font-size: 1.4em; padding: 8px; background: #333; border: 1px solid #555; color: #fff; border-radius: 4px; text-align: center; }
                #jump-spinner { display: none; font-size: 2em; padding: 20px; justify-content: center; align-items: center; }
                #jump-history { margin-top: 20px; border-top: 1px solid #444; padding-top: 15px; }
                #jump-history h4 { margin: 0 0 10px 0; font-size: 0.9em; color: #aaa; text-align: left; }
                .history-list { display: flex; justify-content: center; flex-wrap: wrap; gap: 10px; }
                .history-item { background-color: #383838; color: #ddd; border: 1px solid #555; padding: 5px 10px; border-radius: 4px; cursor: pointer; transition: background-color .2s; }
                .history-item:hover { background-color: #4a4a4a; }
                #exit-deck-btn { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); padding: 8px 40px; background-color: #A43535; color: #fff; border: 1px solid #A43535; border-radius: 4px; cursor: pointer; font-weight: bold; white-space: nowrap; }
                #exit-deck-btn:hover { background-color: #e74c3c; }
                #pagination-bar { display: flex; justify-content: center; align-items: center; gap: 5px; margin-bottom: 15px; font-size: 1.1em; font-weight: bold; }
                .pagination-btn { background: #383838; color: #ddd; border: 1px solid #555; padding: 6px 12px; border-radius: 4px; cursor: pointer; transition: background-color .2s; font-weight: normal; }
                .pagination-btn:hover { background-color: #4a4a4a; }
                .pagination-btn.active { background-color: #006FFA; color: #fff !important; border-color: #006FFA; font-weight: bold; }
                .pagination-ellipsis { color: #777; padding: 0 5px; font-weight: bold; }
            `;
            },
            setupViewer: function() {
                if (document.getElementById(Config.SELECTORS.DECK_VIEWER_ID)) return;
                const deckStyles = `${this._getDeckBaseStyles()} ${this._getDeckUIComponentsStyles()} ${this._getDeckOverlaysStyles()}`;
                GM_addStyle(deckStyles);
                const viewerHTML = `<div id="viewer-main" tabindex="-1"><div class="media-slot"></div><div class="media-slot"></div><div class="loading-text">Pre-loading gallery...</div><div id="left-hotzone" class="nav-hotzone"></div><div id="right-hotzone" class="nav-hotzone"></div><button id="prev-btn" class="nav-btn"><i class="fas fa-chevron-left"></i></button><button id="next-btn" class="nav-btn"><i class="fas fa-chevron-right"></i></button></div><div id="bottom-bar"><button id="bookmarks-panel-btn" title="Session Bookmarks"><i class="far fa-bookmark"></i><span id="bookmark-count">0</span></button><div id="viewer-thumbnails"></div><button id="jump-to-page-btn" title="Go to page (Enter)"><i class="fas fa-map-marker-alt"></i></button></div><div id="tags-trigger"></div><div id="tags-menu"><h3>Tags</h3><ul id="tags-list"></ul></div><div id="jump-box-overlay" class="hidden"><div id="jump-box"><div id="pagination-bar"></div><div class="jump-controls"><input type="text" id="jump-input" placeholder="(+10, -5, 123...)"></div><div id="jump-history"></div><div id="jump-spinner" class="hidden"><i class="fas fa-spinner fa-spin"></i></div><button id="exit-deck-btn" title="Close the Deck Viewer">Exit Deck</button></div></div><div id="bookmarks-overlay" class="hidden"><div id="bookmarks-panel"><h3>Bookmarks</h3><div id="bookmarked-thumbs-list"></div><div id="bookmark-actions"><textarea id="bookmark-import-area" placeholder="Paste exported bookmark data here..."></textarea><div class="bookmark-buttons"><button id="bookmark-export-btn" title="Copy current bookmarks to clipboard"><i class="fas fa-clipboard"></i> Export</button><button id="bookmark-import-btn" title="Merge bookmarks from text area into current session"><i class="fas fa-paste"></i> Import</button></div></div><p id="bookmark-info-msg"></p></div></div>`;
                const viewerOverlay = document.createElement('div');
                viewerOverlay.id = Config.SELECTORS.DECK_VIEWER_ID;
                viewerOverlay.innerHTML = viewerHTML;
                document.body.appendChild(viewerOverlay);
                document.body.style.overflow = 'hidden';
                this.mainContainer = viewerOverlay.querySelector('#viewer-main');
                this.thumbContainer = viewerOverlay.querySelector('#viewer-thumbnails');
                this.tagsList = viewerOverlay.querySelector('#tags-list');
                this.setupDeckEventListeners();
            },
            destroyViewer: function() {
                clearTimeout(this.inactivityTimer);
                const viewerOverlay = document.getElementById(Config.SELECTORS.DECK_VIEWER_ID);
                if (viewerOverlay) { viewerOverlay.remove(); }
                document.body.style.overflow = '';
                this.mainContainer = null;
                this.thumbContainer = null;
                this.tagsList = null;
            },
            setupDeckEventListeners: function() {
                const self = Deck; // Reference to the Deck module
                const jumpBtn = document.getElementById('jump-to-page-btn');
                if (jumpBtn) jumpBtn.addEventListener('click', () => this.toggleJumpBox(true));
                const jumpOverlay = document.getElementById('jump-box-overlay');
                if (jumpOverlay) {
                    const jumpInput = document.getElementById('jump-input');
                    jumpOverlay.addEventListener('click', (e) => { if (e.target === jumpOverlay) this.toggleJumpBox(false); });
                    if (jumpInput) {
                        jumpInput.addEventListener('keydown', (e) => {
                            if (e.key === 'Enter') {
                                e.stopPropagation();
                                if (jumpInput.value.trim() === '') { this.toggleJumpBox(false); } else { this.processJumpRequest(jumpInput.value); }
                            } else if (e.key === 'Escape') {
                                e.stopPropagation();
                                this.toggleJumpBox(false);
                            }
                        });
                    }
                }
                const exitDeckBtn = document.getElementById('exit-deck-btn');
                if (exitDeckBtn) { exitDeckBtn.addEventListener('click', () => { self.close(); }); }
                const bookmarksBtn = document.getElementById('bookmarks-panel-btn');
                if (bookmarksBtn) bookmarksBtn.addEventListener('click', () => this.toggleBookmarksPanel(true));
                const bookmarksOverlay = document.getElementById('bookmarks-overlay');
                if (bookmarksOverlay) {
                    bookmarksOverlay.addEventListener('click', (e) => { if (e.target === bookmarksOverlay) this.toggleBookmarksPanel(false); });
                    document.getElementById('bookmark-export-btn').addEventListener('click', this.handleBookmarkExport);
                    document.getElementById('bookmark-import-btn').addEventListener('click', this.handleBookmarkImport.bind(this));
                }
                if (this.thumbContainer) {
                    this.thumbContainer.addEventListener('wheel', (event) => {
                        if (event.currentTarget.scrollWidth > event.currentTarget.clientWidth) {
                            event.preventDefault();
                            event.currentTarget.scrollLeft += event.deltaY;
                        }
                    }, { passive: false });
                }
                const bottomBarInteractables = [document.getElementById('jump-to-page-btn'), document.getElementById('bookmarks-panel-btn'), document.getElementById('viewer-thumbnails')];
                bottomBarInteractables.forEach(elem => {
                    if (elem) {
                        elem.addEventListener('mouseenter', () => document.getElementById(Config.SELECTORS.DECK_VIEWER_ID).classList.add('tag-menu-disabled'));
                        elem.addEventListener('mouseleave', () => document.getElementById(Config.SELECTORS.DECK_VIEWER_ID).classList.remove('tag-menu-disabled'));
                    }
                });
                const prevBtn = document.getElementById('prev-btn');
                if (prevBtn) prevBtn.onclick = () => self.loadMedia(self.State.currentIndex - 1);
                const nextBtn = document.getElementById('next-btn');
                if (nextBtn) nextBtn.onclick = () => self.loadMedia(self.State.currentIndex + 1);
                this.setupCursorEvents();
            },
            displayMedia: function(mediaUrl, type, tags) {
                const loadingEl = this.mainContainer.querySelector('.loading-text');
                if (loadingEl) loadingEl.style.display = 'none';
                this.updateTagsList(tags);
                const self = Deck; // Reference to the Deck module

                if (type === 'image') {
                    const slots = this.mainContainer.querySelectorAll('.media-slot');
                    const activeSlot = this.mainContainer.querySelector('.media-slot.active');
                    const inactiveSlot = Array.from(slots).find(s => !s.classList.contains('active')) || slots[0];
                    if (!inactiveSlot) { return; }
                    inactiveSlot.innerHTML = '';
                    const mediaWrapper = document.createElement('div');
                    mediaWrapper.className = 'media-wrapper';
                    const mediaElement = document.createElement('img');
                    const bookmarkBtn = document.createElement('button');
                    bookmarkBtn.id = 'bookmark-btn';
                    bookmarkBtn.className = 'media-action-btn';
                    bookmarkBtn.title = `Bookmark (${Settings.State.KEY_DECK_TOGGLE_BOOKMARK.toUpperCase()})`;
                    bookmarkBtn.innerHTML = '<i class="far fa-star"></i>';
                    bookmarkBtn.onclick = () => self.toggleBookmark();
                    Zoom.enable(mediaElement, { bookmarkElement: bookmarkBtn });
                    mediaWrapper.appendChild(mediaElement);
                    mediaWrapper.appendChild(bookmarkBtn);
                    inactiveSlot.appendChild(mediaWrapper);
                    const swapBuffers = () => {
                        inactiveSlot.classList.add('active');
                        if (activeSlot) {
                            activeSlot.classList.remove('active');
                            setTimeout(() => { if (!activeSlot.classList.contains('active')) { activeSlot.innerHTML = ''; } }, 200);
                        }
                        this.updateBookmarkButtonState();
                    };
                    mediaElement.onload = swapBuffers;
                    mediaElement.onerror = () => Logger.error("Failed to load image:", mediaUrl);
                    mediaElement.src = mediaUrl;
                    if (mediaElement.complete) { swapBuffers(); }
                } else if (type === 'video') {
                    const slots = this.mainContainer.querySelectorAll('.media-slot');
                    slots.forEach(slot => { slot.classList.remove('active'); slot.innerHTML = ''; });
                    const targetSlot = slots[0];
                    if (!targetSlot) { return; }
                    const mediaWrapper = document.createElement('div');
                    mediaWrapper.className = 'media-wrapper';
                    const mediaElement = document.createElement('video');
                    mediaElement.autoplay = true;
                    mediaElement.loop = true;
                    mediaElement.controls = true;
                    mediaElement.src = mediaUrl;
                    const openPostBtn = document.createElement('button');
                    openPostBtn.className = 'open-post-btn media-action-btn';
                    openPostBtn.innerHTML = '<i class="fas fa-external-link-alt"></i>';
                    openPostBtn.title = 'Open post page in new tab';
                    openPostBtn.addEventListener('click', (e) => {
                        e.preventDefault();
                        GM_openInTab(self.State.galleryData.posts[self.State.currentIndex].postUrl, { active: false, setParent: true });
                    });
                    mediaWrapper.appendChild(openPostBtn);
                    const bookmarkBtn = document.createElement('button');
                    bookmarkBtn.id = 'bookmark-btn';
                    bookmarkBtn.className = 'media-action-btn';
                    bookmarkBtn.title = `Bookmark (${Settings.State.KEY_DECK_TOGGLE_BOOKMARK.toUpperCase()})`;
                    bookmarkBtn.innerHTML = '<i class="far fa-star"></i>';
                    bookmarkBtn.onclick = () => self.toggleBookmark();
                    mediaWrapper.appendChild(mediaElement);
                    mediaWrapper.appendChild(bookmarkBtn);
                    targetSlot.appendChild(mediaWrapper);
                    targetSlot.classList.add('active');
                    this.updateBookmarkButtonState();
                }
            },
            handleBookmarkExport: function() {
                const infoMsg = document.getElementById('bookmark-info-msg');
                infoMsg.textContent = '';
                if (Deck.State.bookmarks.length === 0) {
                    infoMsg.textContent = 'Nothing to export.';
                    setTimeout(() => { infoMsg.textContent = ''; }, 3000);
                    return;
                }
                const jsonString = JSON.stringify(Deck.State.bookmarks, null, 2);
                navigator.clipboard.writeText(jsonString).then(() => {
                    infoMsg.textContent = `Exported ${Deck.State.bookmarks.length} bookmarks to clipboard!`;
                    setTimeout(() => { infoMsg.textContent = ''; }, 3000);
                }).catch(err => {
                    Logger.error("Failed to export bookmarks:", err);
                    infoMsg.textContent = 'Failed to copy to clipboard.';
                    setTimeout(() => { infoMsg.textContent = ''; }, 3000);
                });
            },
            handleBookmarkImport: function() {
                const infoMsg = document.getElementById('bookmark-info-msg');
                infoMsg.textContent = '';
                const textarea = document.getElementById('bookmark-import-area');
                const jsonString = textarea.value;
                if (!jsonString.trim()) {
                    infoMsg.textContent = 'Import field is empty.';
                    setTimeout(() => { infoMsg.textContent = ''; }, 3000);
                    return;
                }
                try {
                    const importedData = JSON.parse(jsonString);
                    if (!Array.isArray(importedData) || (importedData.length > 0 && (!importedData[0].id || !importedData[0].postUrl || !importedData[0].thumbUrl))) {
                        throw new Error("Invalid data format.");
                    }
                    const existingIds = new Set(Deck.State.bookmarks.map(b => b.id));
                    let newBookmarksAdded = 0;
                    importedData.forEach(newBookmark => {
                        if (!existingIds.has(newBookmark.id)) {
                            Deck.State.bookmarks.push(newBookmark);
                            newBookmarksAdded++;
                        }
                    });
                    Deck.updateUIAfterBookmarkChange();
                    textarea.value = '';
                    infoMsg.textContent = `Import complete. ${newBookmarksAdded} new bookmark(s) added.`;
                    setTimeout(() => { infoMsg.textContent = ''; }, 3000);
                } catch (error) {
                    Logger.error("Failed to import bookmarks:", error);
                    infoMsg.textContent = `Import failed: ${error.message}`;
                    setTimeout(() => { infoMsg.textContent = ''; }, 3000);
                }
            },
            toggleBookmarksPanel: function(show) {
                const overlay = document.getElementById('bookmarks-overlay');
                if (show === false || !overlay.classList.contains(Config.DECK_CONSTANTS.CSS_CLASSES.HIDDEN)) {
                    overlay.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.HIDDEN);
                } else {
                    this.renderBookmarkedThumbnails();
                    overlay.classList.remove(Config.DECK_CONSTANTS.CSS_CLASSES.HIDDEN);
                }
            },
            renderBookmarkedThumbnails: function() {
                const list = document.getElementById('bookmarked-thumbs-list');
                list.innerHTML = '';
                if (Deck.State.bookmarks.length === 0) {
                    list.innerHTML = `<p class="empty-list-msg">No posts bookmarked.</p>`;
                    return;
                }
                const fragment = document.createDocumentFragment();
                Deck.State.bookmarks.forEach(bookmark => {
                    const container = document.createElement('div');
                    container.className = 'thumb-container';
                    container.title = `Post ID: ${bookmark.id}\nClick to open in new tab.`;
                    container.onclick = () => GM_openInTab(bookmark.postUrl, { active: true, setParent: true });
                    const thumbImg = document.createElement('img');
                    thumbImg.className = 'thumb-img';
                    thumbImg.src = bookmark.thumbUrl;
                    const deleteBtn = document.createElement('button');
                    deleteBtn.className = 'bookmark-delete-btn';
                    deleteBtn.innerHTML = '<i class="fas fa-times"></i>';
                    deleteBtn.title = 'Remove bookmark';
                    deleteBtn.onclick = (e) => {
                        e.stopPropagation();
                        Deck.removeBookmark(bookmark.id);
                    };
                    container.append(thumbImg, deleteBtn);
                    fragment.appendChild(container);
                });
                list.appendChild(fragment);
            },
            updateBookmarkCounter: function() {
                const countEl = document.getElementById('bookmark-count');
                if (!countEl) return;
                const btnEl = document.getElementById('bookmarks-panel-btn');
                if (!btnEl) return;
                const iconEl = btnEl.querySelector('i');
                const count = Deck.State.bookmarks.length;
                countEl.textContent = count;
                if (count > 0) {
                    btnEl.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.ACTIVE);
                    if (iconEl) iconEl.style.color = '#daa520';
                } else {
                    btnEl.classList.remove(Config.DECK_CONSTANTS.CSS_CLASSES.ACTIVE);
                    if (iconEl) iconEl.style.color = '';
                }
            },
            updateBookmarkButtonState: function() {
                const btn = document.querySelector('.media-slot.active #bookmark-btn');
                if (!btn) return;
                const icon = btn.querySelector('i');
                const currentPost = Deck.State.galleryData.posts[Deck.State.currentIndex];
                if (!currentPost) return;
                const currentId = Utils.getPostId(currentPost.postUrl);
                const isBookmarked = Deck.State.bookmarks.some(b => b.id === currentId);
                if (isBookmarked) {
                    btn.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.ACTIVE);
                    icon.className = 'fas fa-star';
                    icon.style.color = '#daa520';
                } else {
                    btn.classList.remove(Config.DECK_CONSTANTS.CSS_CLASSES.ACTIVE);
                    icon.className = 'far fa-star';
                    icon.style.color = '';
                }
            },
            toggleJumpBox: function(show) {
                const jumpOverlay = document.getElementById('jump-box-overlay');
                const jumpInput = document.getElementById('jump-input');
                const jumpBox = document.getElementById('jump-box');
                jumpBox.classList.remove(Config.DECK_CONSTANTS.CSS_CLASSES.LOADING, Config.DECK_CONSTANTS.CSS_CLASSES.INVALID);
                if (show === false || !jumpOverlay.classList.contains(Config.DECK_CONSTANTS.CSS_CLASSES.HIDDEN)) {
                    jumpOverlay.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.HIDDEN);
                    jumpInput.value = '';
                    if (this.mainContainer) this.mainContainer.focus();
                } else {
                    jumpOverlay.classList.remove(Config.DECK_CONSTANTS.CSS_CLASSES.HIDDEN);
                    const currentPage = Deck.getCurrentPageNum();
                    const lastPage = Deck.State.galleryData.lastPageNum;
                    this.renderPagination(currentPage, lastPage);
                    this.updateHistoryDisplay();
                    jumpInput.focus();
                }
            },
            processJumpRequest: function(value) {
                const jumpBox = document.getElementById('jump-box');
                jumpBox.classList.remove(Config.DECK_CONSTANTS.CSS_CLASSES.INVALID);
                let targetPage = 0;
                const inputStr = String(value).trim();
                if (inputStr.startsWith('+') || inputStr.startsWith('-')) {
                    const relative = parseInt(inputStr, 10);
                    if (!isNaN(relative)) { targetPage = Deck.getCurrentPageNum() + relative; }
                } else {
                    const absolute = parseInt(inputStr, 10);
                    if (!isNaN(absolute)) { targetPage = absolute; }
                }
                if (targetPage > 0) {
                    jumpBox.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.LOADING);
                    setTimeout(() => {
                        this.toggleJumpBox(false);
                        Deck.performJump(targetPage);
                    }, 200);
                } else {
                    jumpBox.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.INVALID);
                    setTimeout(() => jumpBox.classList.remove(Config.DECK_CONSTANTS.CSS_CLASSES.INVALID), 500);
                }
            },
            updateHistoryDisplay: function() {
                const historyContainer = document.getElementById('jump-history');
                historyContainer.innerHTML = '';
                if (Deck.State.navigationHistory.length === 0) return;
                const title = document.createElement('h4');
                title.textContent = 'History:';
                historyContainer.appendChild(title);
                const list = document.createElement('div');
                list.className = 'history-list';

                const fragment = document.createDocumentFragment();
                Deck.State.navigationHistory.forEach(pageNum => {
                    const item = document.createElement('button');
                    item.className = 'history-item';
                    item.textContent = `${pageNum}`;
                    item.onclick = () => this.processJumpRequest(pageNum);

                    fragment.appendChild(item);
                });
                list.appendChild(fragment);
                historyContainer.appendChild(list);
            },
            generatePagination: function(current, total) {
                if (total <= 1) return [];
                if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
                const pages = new Set([1, total, current]);
                for (let i = -2; i <= 2; i++) {
                    const page = current + i;
                    if (page > 1 && page < total) pages.add(page);
                }
                const sortedPages = Array.from(pages).sort((a, b) => a - b);
                const result = [];
                let last = 0;
                for (const page of sortedPages) {
                    if (page > last + 1) result.push('...');
                    result.push(page);
                    last = page;
                }
                return result;
            },
            renderPagination: function(current, total) {
                const container = document.getElementById('pagination-bar');
                container.innerHTML = '';
                const pages = this.generatePagination(current, total);
                if (pages.length === 0) {
                    const fallbackText = document.createElement('span');
                    fallbackText.className = 'pagination-ellipsis';
                    fallbackText.textContent = `Current Page: ${current}`;
                    container.appendChild(fallbackText);
                    return;
                }
                const fragment = document.createDocumentFragment();
                pages.forEach(page => {
                    if (page === '...') {
                        const ellipsis = document.createElement('span');
                        ellipsis.className = 'pagination-ellipsis';
                        ellipsis.textContent = '...';
                        fragment.appendChild(ellipsis);
                    } else {
                        const pageBtn = document.createElement('button');
                        pageBtn.className = 'pagination-btn';
                        if (page === current) pageBtn.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.ACTIVE);
                        pageBtn.textContent = page;
                        pageBtn.onclick = () => this.processJumpRequest(page);
                        fragment.appendChild(pageBtn);
                    }
                });
                container.appendChild(fragment);
            },
            updateThumbnails: function() {
                this.thumbContainer.innerHTML = '';
                const fragment = document.createDocumentFragment();
                const self = Deck; // Reference to Deck module
                Deck.State.galleryData.posts.forEach((post, index) => {
                    const container = document.createElement('div');
                    container.className = 'thumb-container';
                    container.dataset.index = index;
                    container.onclick = () => self.loadMedia(index);
                    if (index === Deck.State.currentIndex) { container.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.ACTIVE); }
                    const thumbImg = document.createElement('img');
                    thumbImg.className = 'thumb-img';
                    thumbImg.src = post.thumbUrl;
                    container.appendChild(thumbImg);
                    const icon = document.createElement('i');
                    icon.className = 'fas fa-play-circle video-icon';
                    if (post.type === 'video') { icon.style.display = 'block'; }
                    container.appendChild(icon);
                    if (post.isBroken) {
                        container.classList.add('broken-thumb');
                        const retryIcon = document.createElement('i');
                        retryIcon.className = 'fas fa-sync-alt retry-icon';
                        retryIcon.title = 'Retry loading this post';
                        retryIcon.onclick = (e) => {
                            e.stopPropagation();
                            self.retryFetch(index);
                        };
                        container.appendChild(retryIcon);
                    }
                    fragment.appendChild(container);
                });
                this.thumbContainer.appendChild(fragment);

                const activeThumb = this.thumbContainer.querySelector(`.${Config.DECK_CONSTANTS.CSS_CLASSES.ACTIVE}`);
                if (activeThumb) { setTimeout(() => activeThumb.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }), 0); }
            },
            updateTagsList: function(tags) {
                this.tagsList.innerHTML = '';
                if (!tags) return;

                const fragment = document.createDocumentFragment();
                const categoryOrder = ['artist', 'character', 'copyright', 'metadata', 'general'];
                categoryOrder.forEach(category => {
                    if (tags[category]) {
                        const h4 = document.createElement('h4');
                        h4.textContent = category.replace(/_/g, ' ');
                        fragment.appendChild(h4);
                        tags[category].forEach(tag => {
                            const li = document.createElement('li');
                            const a = document.createElement('a');
                            a.href = tag.url;
                            a.textContent = tag.name;
                            a.target = '_blank';
                            const color = Config.DECK_CONSTANTS.TAG_COLORS[category.replace(/ /g, '_')] || Config.DECK_CONSTANTS.TAG_COLORS.general;
                            a.style.setProperty('color', color, 'important');
                            li.appendChild(a);
                            fragment.appendChild(li);
                        });
                    }
                });
                this.tagsList.appendChild(fragment);
            },
            showLoadingMessage: function(message) {
                if (!this.mainContainer) return;
                const loadingEl = this.mainContainer.querySelector('.loading-text');
                if (loadingEl) {
                    loadingEl.textContent = message;
                    loadingEl.style.display = 'block';
                }
                this.mainContainer.querySelectorAll('.media-slot').forEach(slot => slot.classList.remove('active'));
                if (this.thumbContainer) { this.thumbContainer.innerHTML = ''; }
            },
            setupCursorEvents: function() {
                const viewerOverlay = document.getElementById(Config.SELECTORS.DECK_VIEWER_ID);
                if (!viewerOverlay) return;
                viewerOverlay.addEventListener('mousemove', this.handleMouseMove.bind(this));
                viewerOverlay.addEventListener('mousedown', this.resetCursorTimer.bind(this));
                this.resetCursorTimer();
            },
            resetCursorTimer: function() {
                const viewerOverlay = document.getElementById(Config.SELECTORS.DECK_VIEWER_ID);
                if (!viewerOverlay) return;
                viewerOverlay.classList.remove('hide-cursor');
                clearTimeout(this.inactivityTimer);
                this.inactivityTimer = setTimeout(() => viewerOverlay.classList.add('hide-cursor'), Config.DECK_CONSTANTS.CURSOR_INACTIVITY_TIME);
            },
            handleMouseMove: function(e) {
                const bottomBar = document.getElementById('bottom-bar');
                if (bottomBar && e.clientY > window.innerHeight * 0.88) {
                    bottomBar.classList.add(Config.DECK_CONSTANTS.CSS_CLASSES.VISIBLE);
                } else if (bottomBar) {
                    bottomBar.classList.remove(Config.DECK_CONSTANTS.CSS_CLASSES.VISIBLE);
                }
                this.resetCursorTimer();
            },
        }
    };

    // =================================================================================
    // DOWNLOADER MODULE
    // =================================================================================
    const Downloader = {
        State: {
            isSelectionModeActive: false,
            isDownloading: false,
            isCancelled: false,
            downloadQueue: new Set(),
            processingQueue: [],
            failedDownloads: new Set(),
            downloadStatus: new Map()
        },

        // --- Session Management ---
        resetState: function() {
            this.State.isDownloading = false;
            this.State.isCancelled = false;
            this.State.processingQueue = [];
            this.State.failedDownloads.clear();
            this.State.downloadStatus.clear();
        },

        // --- Core Download Logic ---
        downloadSinglePost: async function(thumbAnchor) {
            const pId = Utils.getPostId(thumbAnchor.href);
            if (this.State.downloadQueue.has(pId)) return;
            this.State.downloadQueue.add(pId);
            this.State.downloadStatus.set(pId, 'downloading');

            const { progressOverlay, circleFG, circumference } = this.UI.createProgressCircle();
            thumbAnchor.appendChild(progressOverlay);

            try {
                this.UI.updateThumbnailFeedback(thumbAnchor, 'downloading');
                await new Promise(r => setTimeout(r, 150));
                if (this.State.isCancelled) throw new Error('Cancelled before start');

                const media = await API.fetchMediaDetails(pId);
                const ext = new URL(media.url).pathname.split('.').pop() || 'jpg';
                const filename = `${Settings.State.DOWNLOADER_SUBFOLDER}/post_${pId}.${ext}`;

                const { promise } = Utils.makeRequest({
                    method: "HEAD",
                    timeout: 15000,
                    url: media.url,
                });
                const headResponse = await promise;
                const headers = headResponse.responseHeaders;

                const totalSizeMatch = headers.match(/content-length:\s*(\d+)/i);
                const totalSize = totalSizeMatch ? parseInt(totalSizeMatch[1], 10) : 0;
                if (totalSize === 0) throw new Error('Could not determine file size.');

                await new Promise((resolve, reject) => {
                    GM_download({
                        url: media.url,
                        name: filename,
                        onprogress: (progress) => {
                            if (this.State.isCancelled) {
                                return reject(new Error('Cancelled during download'));
                            }
                            const percentComplete = progress.loaded / totalSize;
                            const offset = circumference * (1 - percentComplete);
                            circleFG.style.strokeDashoffset = offset;
                        },
                        onload: () => {
                            if (this.State.isCancelled) return reject(new Error('Cancelled on complete'));
                            this.UI.updateThumbnailFeedback(thumbAnchor, 'success');
                            this.State.downloadStatus.set(pId, 'success');
                            resolve();
                        },
                        onerror: (err) => reject(err),
                        ontimeout: () => reject(new Error('Timeout'))
                    });
                });
            } catch (er) {
                 if (er.message && er.message.toLowerCase().includes('cancelled')) {
                    this.UI.updateThumbnailFeedback(thumbAnchor, null);
                    this.State.downloadStatus.set(pId, 'cancelled');
                    Logger.log(`Download for post ${pId} cancelled.`);
                } else {
                    this.UI.updateThumbnailFeedback(thumbAnchor, 'error');
                    this.State.downloadStatus.set(pId, 'error');
                    Logger.error(`Download failed for post ID ${pId}:`, er);
                    this.State.failedDownloads.add(pId);
                }
                throw er;
            } finally {
                progressOverlay.remove();
                this.State.downloadQueue.delete(pId);
            }
        },
        downloadWithRetries: async function(thumb, maxAttempts = 3) {
            let lastError = null;
            for (let attempt = 1; attempt <= maxAttempts; attempt++) {
                try {
                    await this.downloadSinglePost(thumb);
                    return;
                } catch (err) {
                    lastError = err;
                    if (err.message && err.message.toLowerCase().includes('cancelled')) {
                        throw err;
                    }
                    Logger.warn(`Download attempt ${attempt}/${maxAttempts} failed for post ${Utils.getPostId(thumb.href)}.`);
                    if (attempt < maxAttempts) {
                        await new Promise(r => setTimeout(r, 2000 * attempt));
                    }
                }
            }
            throw lastError;
        },
        startDownloadAllProcess: async function() {
            if (this.State.isDownloading) {
                this.State.isCancelled = true;
                const btn = document.getElementById('gbs-fab-download-all');
                if (btn) {
                    btn.querySelector('.gbs-fab-text').textContent = 'Cancelling...';
                    btn.disabled = true;
                }
                return;
            }

            const allThumbs = Array.from(document.querySelectorAll(Config.SELECTORS.THUMBNAIL_ANCHOR_SELECTOR));
            if (allThumbs.length === 0) {
                Logger.warn('No items on the page to download.');
                return;
            }

            const blocker = document.createElement('div');
            blocker.id = 'gbs-page-blocker';
            document.body.appendChild(blocker);
            setTimeout(() => { blocker.style.opacity = '1'; }, 10);

            this.resetState();

            this.State.isDownloading = true;
            this.State.isCancelled = false;
            document.getElementById('gbs-fab-select').disabled = true;
            this.UI.updateDownloadAllButton(true);
            this.UI.toggleMenuTriggerCursor(true);
            this.UI.showProgressBar(true);
            this.UI.updateProgressBar(0, 0, allThumbs.length);
            this.UI.resetThumbnailsFeedback();

            this.State.processingQueue = [...allThumbs];
            Logger.log(`Starting download for ${allThumbs.length} posts.`);

            let s = 0, e = 0;
            const t = allThumbs.length;
            const MAX_CONCURRENCY = 6;
            this.UI.updateProgressBar(s, e, t);

            const processQueue = async () => {
                while (this.State.processingQueue.length > 0 && !this.State.isCancelled) {
                    const thumb = this.State.processingQueue.shift();
                    try {
                        await this.downloadWithRetries(thumb);
                        s++;
                    } catch(err) {
                        if (!err.message?.toLowerCase().includes('cancelled')) {
                            e++;
                        }
                    } finally {
                        this.UI.updateProgressBar(s, e, t);
                        await new Promise(r => setTimeout(r, 300 + Math.random() * 500));
                    }
                }
            };

            const workers = Array.from({ length: Math.min(MAX_CONCURRENCY, this.State.processingQueue.length) }, processQueue);
            await Promise.allSettled(workers);
            this.finishDownloadProcess(s, e, t);
        },
        finishDownloadProcess: function(s, e, t) {
            const blocker = document.getElementById('gbs-page-blocker');
            if (blocker) blocker.remove();

            if (this.State.isCancelled) {
                Logger.log('Download process cancelled by user.');
            } else {
                Logger.log(`Download process completed. Success: ${s}, Errors: ${e}, Total: ${t}`);
            }

            this.UI.showCompletionModal(Array.from(this.State.failedDownloads));
            this.UI.toggleActionButtons(true);
            document.getElementById('gbs-fab-download-all').disabled = false;
            this.UI.updateDownloadAllButton(false);
            this.UI.toggleMenuTriggerCursor(false);
            this.UI.showProgressBar(false);
            this.resetState();
        },
        UI: {
            toggleActionButtons: (enable) => {
                document.getElementById('gbs-fab-select').disabled = !enable;
                document.getElementById('gbs-fab-download-all').disabled = !enable;
            },
            showProgressBar: (show) => {
                const el = document.getElementById('gbs-progress-bar-container');
                if (el) {
                    el.style.display = show ? 'flex' : 'none';
                    if (show) Downloader.UI.updateProgressBar(0, 0, 1);
                }
            },
            updateProgressBar: (s, e, t) => {
                const p = t > 0 ? ((s + e) / t) * 100 : 0;
                const fill = document.querySelector('#gbs-progress-bar-container .gbs-progress-bar-fill');
                const text = document.querySelector('#gbs-progress-bar-container .gbs-progress-bar-text');
                if (!fill || !text) return;
                fill.style.width = `${p}%`;
                if (e > 0) {
                    fill.style.backgroundColor = '#A43535';
                    text.textContent = `Downloading... (${s}/${t-e}) (Errors: ${e})`;
                } else {
                    fill.style.backgroundColor = '#008450';
                    text.textContent = `Downloading... (${s}/${t})`;
                }
            },
            updateDownloadAllButton: (isDownloading) => {
                const btn = document.getElementById('gbs-fab-download-all');
                if (!btn) return;
                const btnText = btn.querySelector('.gbs-fab-text');
                if (isDownloading) {
                    btnText.textContent = 'Cancel';
                    btn.classList.add('gbs-btn-cancel');
                } else {
                    btnText.textContent = 'Download All';
                    btn.classList.remove('gbs-btn-cancel');
                }
            },
            updateThumbnailFeedback: (thumb, status) => {
                if (thumb) {
                    thumb.classList.remove('gbs-thumb-selected', 'gbs-thumb-success', 'gbs-thumb-error', 'gbs-thumb-downloading');
                    if (status) thumb.classList.add(`gbs-thumb-${status}`);
                }
            },
            resetThumbnailsFeedback: () => {
                document.querySelectorAll('.gbs-thumb-success, .gbs-thumb-error, .gbs-thumb-selected, .gbs-thumb-downloading').forEach(el => {
                    el.classList.remove('gbs-thumb-success', 'gbs-thumb-error', 'gbs-thumb-selected', 'gbs-thumb-downloading');
                });
            },
            toggleMenuTriggerCursor: (isBlocked) => {
                document.getElementById('gbs-downloader-trigger')?.classList.toggle('is-blocked', isBlocked);
            },
            createProgressCircle: function() {
                const svgNS = "http://www.w3.org/2000/svg";
                const progressOverlay = document.createElement('div');
                progressOverlay.className = 'gbs-progress-overlay';
                const svg = document.createElementNS(svgNS, 'svg');
                svg.setAttribute('viewBox', '0 0 50 50');
                svg.style.width = '60%'; svg.style.height = '60%';
                const circleBG = document.createElementNS(svgNS, 'circle');
                circleBG.setAttribute('cx', '25'); circleBG.setAttribute('cy', '25'); circleBG.setAttribute('r', '20'); circleBG.setAttribute('fill', 'transparent'); circleBG.setAttribute('stroke-width', '5');
                circleBG.setAttribute('class', 'gbs-progress-circle-bg');
                const circleFG = circleBG.cloneNode();
                circleFG.setAttribute('class', 'gbs-progress-circle-fg');
                const circumference = 2 * Math.PI * 20;
                Object.assign(circleFG.style, { strokeDasharray: circumference, strokeDashoffset: circumference });
                svg.append(circleBG, circleFG);
                progressOverlay.appendChild(svg);
                return { progressOverlay, circleFG, circumference };
            },
            showCompletionModal: function(failedIds) {
                if (failedIds.length === 0) {
                    alert('All downloads on this page completed successfully!');
                    return;
                }
                if (document.getElementById('gbs-completion-modal')) return;
                const modalHTML = `
                    <div id="gbs-completion-modal-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 100000; display: flex; align-items: center; justify-content: center; font-family: sans-serif;">
                        <div id="gbs-completion-modal" style="background-color: #252525; color: #eee; padding: 20px; border-radius: 8px; width: 90%; max-width: 500px; text-align: center;">
                            <h3 style="margin-top: 0; border-bottom: 1px solid #555; padding-bottom: 10px;">Download Process Finished</h3>
                            <p>The following posts could not be downloaded. You can copy the IDs for a manual check:</p>
                            <textarea readonly style="width: 95%; height: 150px; background: #333; color: #fff; border: 1px solid #555; resize: none; margin-top: 10px;">${failedIds.join(' ')}</textarea>
                            <button id="gbs-completion-close" style="margin-top: 15px; padding: 8px 16px; background-color: #007BFF; color: white; border: none; border-radius: 5px; cursor: pointer;">Close</button>
                        </div>
                    </div>`;
                document.body.insertAdjacentHTML('beforeend', modalHTML);
                document.getElementById('gbs-completion-close').addEventListener('click', () => {
                    document.getElementById('gbs-completion-modal-overlay').remove();
                });
            },
        },
        toggleSelectionMode: function() {
            if (this.State.isDownloading) return;
            this.State.isSelectionModeActive = !this.State.isSelectionModeActive;
            document.body.classList.toggle('gbs-selection-active', this.State.isSelectionModeActive);
            this.UI.toggleMenuTriggerCursor(this.State.isSelectionModeActive);

            const selectButton = document.getElementById('gbs-fab-select');
            selectButton.querySelector('.gbs-fab-text').textContent = this.State.isSelectionModeActive ? 'Cancel' : 'Download (Select)';
            selectButton.classList.toggle('active', this.State.isSelectionModeActive);

            GlobalState.previewsTemporarilyDisabled = this.State.isSelectionModeActive;
            const grids = document.querySelectorAll(Config.SELECTORS.THUMBNAIL_GRID_SELECTOR);
            if (this.State.isSelectionModeActive) {
                Logger.log("Selection mode activated. Disabling Peek Previews.");
                grids.forEach(grid => Peek.cleanupThumbnailFeatures(grid));
            } else {
                Logger.log("Selection mode deactivated. Re-enabling Peek Previews.");
                grids.forEach(grid => Peek.initializeThumbnailFeatures(grid));
                this.UI.resetThumbnailsFeedback();
            }
        },
        handleThumbnailClick: function(event) {
            if (!this.State.isSelectionModeActive) return;
            const thumbAnchor = event.target.closest(Config.SELECTORS.THUMBNAIL_ANCHOR_SELECTOR);
            if (!thumbAnchor) return;
            event.preventDefault();
            event.stopPropagation();
            this.downloadSinglePost(thumbAnchor);
        },
        injectUI: function() {
            GM_addStyle(`
                #gbs-downloader-wrapper { position: fixed; top: 50%; left: 0; transform: translateY(-50%); z-index: 9998; }
                #gbs-downloader-trigger { width: 10px; height: 100px; background-color: #252525; border-radius: 0 5px 5px 0; cursor: pointer; border: 1px solid #333; border-left: none; transition: background-color 0.2s ease; }
                #gbs-downloader-wrapper:hover #gbs-downloader-trigger { background-color: #007BFF; }
                #gbs-downloader-wrapper.menu-open #gbs-downloader-trigger { background-color: #A43535; }
                #gbs-downloader-trigger.is-blocked { cursor: not-allowed; }
                #gbs-action-list { position: absolute; top: 50%; left: 100%; transform: translateY(-50%) scale(0.95); margin-left: 15px; display: flex; flex-direction: column; align-items: flex-start; opacity: 0; transition: opacity 0.2s ease, transform 0.2s ease; pointer-events: none; }
                #gbs-downloader-wrapper.menu-open #gbs-action-list { opacity: 1; transform: translateY(-50%) scale(1); pointer-events: auto; }
                .gbs-fab-action-btn { min-width: 160px; justify-content: center; background-color:#343a40; color:white; border:none; border-radius:5px; padding:10px 15px; margin-bottom: 10px; cursor:pointer; display:flex; align-items:center; box-shadow:0 2px 5px rgba(0,0,0,0.2); white-space:nowrap; transition: background-color 0.2s; }
                .gbs-fab-action-btn:hover { background-color: #007BFF; }
                .gbs-fab-action-btn:disabled { opacity:0.6; cursor:not-allowed; background-color: #343a40; }
                .gbs-fab-action-btn .gbs-fab-text { font-weight:bold; }
                #gbs-fab-select.active { background-color:#A43535; color: white !important; }
                #gbs-fab-select.active:hover { background-color:#e74c3c; }
                .gbs-btn-cancel { background-color:#A43535 !important; }
                .gbs-btn-cancel:hover { background-color:#e74c3c !important; }
                .gbs-selection-active body, .gbs-selection-active .thumbnail-preview a { cursor: crosshair !important; }
                .thumbnail-preview > a { display:block; position:relative; transition:transform 0.2s, box-shadow 0.2s; }
                .gbs-thumb-downloading { transform:scale(0.95); border-radius: 8px !important; overflow:hidden; outline: 4px solid #EFB700 !important; }
                .gbs-thumb-success { border-radius: 8px !important; overflow:hidden; outline: 4px solid #008450 !important; }
                .gbs-thumb-error { border-radius: 8px !important; overflow:hidden; outline: 4px solid #B81D13 !important; }
                .gbs-thumb-success::after, .gbs-thumb-error::after { content:''; position:absolute; top:0; left:0; width:100%; height:100%; display:flex; align-items:center; justify-content:center; font-size:50px; color:white; text-shadow:0 0 5px black; }
                .gbs-thumb-success::after { background-color:rgba(40, 167, 69, 0.7); content:'✔'; }
                .gbs-thumb-error::after { background-color:rgba(220, 53, 69, 0.7); content:'✖'; }
                #gbs-progress-bar-container { position:fixed; bottom:0; left:0; width:100%; height:25px; background-color:#333; z-index:99999; display:none; align-items:center; border-top:1px solid #555; }
                .gbs-progress-bar-fill { background-color:#008450; height:100%; width:0%; transition:width 0.3s ease-in-out; }
                .gbs-progress-bar-text { position:absolute; width:100%; text-align:center; color:white; font-weight:bold; text-shadow:1px 1px 1px #000; z-index:10; }
                .gbs-progress-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; pointer-events: none; background-color: rgba(0,0,0,0.5); opacity: 0; transition: opacity 0.2s ease-in-out; }
                .gbs-thumb-downloading .gbs-progress-overlay { opacity: 1; }
                .gbs-progress-circle-bg { stroke: rgba(255,255,255,0.2); }
                .gbs-progress-circle-fg { stroke: #EFB700; transform: rotate(-90deg); transform-origin: 50% 50%; transition: stroke-dashoffset 0.1s linear; }
                #gbs-page-blocker { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); cursor: progress; z-index: 9997; opacity: 0; transition: opacity 0.3s ease-in-out; }
            `);

            document.body.insertAdjacentHTML('beforeend', `
                <div id="gbs-downloader-wrapper">
                    <div id="gbs-downloader-trigger"></div>
                    <div id="gbs-action-list">
                        <button id="gbs-fab-download-all" class="gbs-fab-action-btn"><span class="gbs-fab-text">Download All</span></button>
                        <button id="gbs-fab-select" class="gbs-fab-action-btn"><span class="gbs-fab-text">Download (Select)</span></button>
                    </div>
                </div>
                <div id="gbs-progress-bar-container" style="display: none;"><div class="gbs-progress-bar-text"></div><div class="gbs-progress-bar-fill"></div></div>
            `);
        },
        setupEventListeners: function() {
            const wrapper = document.getElementById('gbs-downloader-wrapper');
            const menuTrigger = document.getElementById('gbs-downloader-trigger');

            menuTrigger.addEventListener('click', (event) => {
                if (this.State.isDownloading || this.State.isSelectionModeActive) return;
                event.stopPropagation();
                wrapper.classList.toggle('menu-open');
                if (!wrapper.classList.contains('menu-open')) {
                    this.UI.showProgressBar(false);
                    this.UI.resetThumbnailsFeedback();
                }
            });

            document.getElementById('gbs-fab-download-all').addEventListener('click', () => this.startDownloadAllProcess());
            document.getElementById('gbs-fab-select').addEventListener('click', () => this.toggleSelectionMode());
            document.body.addEventListener('click', (e) => this.handleThumbnailClick(e), true);
        },
        init: function() {
            this.injectUI();
            this.setupEventListeners();
            Logger.log('Downloader: Side-drawer UI initialized.');
        }
    };

    // =================================================================================
    // MAIN APPLICATION ORCHESTRATOR
    // =================================================================================
    const App = {
        addGlobalStyles: function() {
            let scrollbarCss = '';
            if (Settings.State.HIDE_PAGE_SCROLLBARS) {
                scrollbarCss = `html, body { scrollbar-width: none !important; -ms-overflow-style: none !important; } html::-webkit-scrollbar, body::-webkit-scrollbar { display: none !important; }`;
            }
            if (!document.querySelector('link[href*="font-awesome"]')) {
                const faLink = document.createElement('link');
                faLink.rel = 'stylesheet';
                faLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css';
                document.head.appendChild(faLink);
            }
             GM_addStyle(scrollbarCss);
        },
        setupGalleryHotkeys: function() {
            document.addEventListener('keydown', e => {
                if (document.getElementById(Config.SELECTORS.DECK_VIEWER_ID)) return;
                const activeEl = document.activeElement;
                if (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA' || activeEl === document.getElementById(Config.SELECTORS.PREVIEW_CONTAINER_ID) || activeEl.closest(`#${Config.SELECTORS.ADVANCED_SEARCH_MODAL_ID}`)) return;

                const currentPageElement = document.querySelector(Config.SELECTORS.PAGINATION_CURRENT_SELECTOR);
                if (!currentPageElement) return;
                let targetLink = null;
                if (e.key === Settings.State.KEY_GALLERY_NEXT_PAGE) {
                    targetLink = currentPageElement.nextElementSibling;
                } else if (e.key === Settings.State.KEY_GALLERY_PREV_PAGE) {
                    targetLink = currentPageElement.previousElementSibling;
                }
                if (targetLink?.tagName === 'A') {
                    window.location.href = targetLink.href;
                }
            });
        },
        async init() {
            await Settings.load();

            this.addGlobalStyles();
            GM_registerMenuCommand('Suite Settings', () => Settings.UI.openModal());

            // --- Page Type Detection ---
            const isGalleryPage = !!document.querySelector(Config.SELECTORS.THUMBNAIL_GRID_SELECTOR);
            const contentElement = document.querySelector(Config.SELECTORS.IMAGE_SELECTOR) || document.querySelector(Config.SELECTORS.VIDEO_PLAYER_SELECTOR);
            const isPostPage = contentElement && !contentElement.closest('.thumbnail-preview');

            // --- Module Initialization ---
            if (Settings.State.ENABLE_ADVANCED_SEARCH) {
                AdvancedSearch.init();
            }

            if (isGalleryPage) {
                this.setupGalleryHotkeys();

                if (Settings.State.ENABLE_PEEK_PREVIEWS) {
                    Peek.init();
                }
                if (Settings.State.ENABLE_DECK_VIEWER) {
                    Deck.initGalleryButton();
                }
                if (Settings.State.ENABLE_DOWNLOADER) {
                    Downloader.init();
                }
            }

            if (isPostPage && Settings.State.ENABLE_PEEK_LIGHTBOX) {
                Peek.initLightbox();
            }

            Logger.log("Gelbooru Suite initialized.");
        }
    };

    // =================================================================================
    // SCRIPT ENTRY POINT
    // =================================================================================
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', App.init.bind(App));
    } else {
        App.init();
    }
})();