Ultra Galleries

Modern image gallery with highly efficient background zipping, video playback, browsing, fullscreen, and download features. Native DOM, unified pointer gestures, and zero external UI dependencies.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Ultra Galleries
// @namespace    https://sleazyfork.org/en/users/1477603-%E3%83%A1%E3%83%AA%E3%83%BC
// @version      4.3.1
// @description  Modern image gallery with highly efficient background zipping, video playback, browsing, fullscreen, and download features. Native DOM, unified pointer gestures, and zero external UI dependencies.
// @author       ntf (original), Meri/TearTyr (maintained)
// @match        *://kemono.su/*
// @match        *://*.kemono.su/*
// @match        *://coomer.su/*
// @match        *://*.coomer.su/*
// @match        *://kemono.cr/*
// @match        *://*.kemono.cr/*
// @match        *://coomer.cr/*
// @match        *://*.coomer.cr/*
// @match        *://coomer.st/*
// @match        *://*.coomer.st/*
// @match        *://nekohouse.su/*
// @match        *://*.nekohouse.su/*
// @match        *://pawchive.st/*
// @match        *://*.pawchive.st/*
// @match        *://pawchive.pw/*
// @match        *://*.pawchive.pw/*
// @icon         https://pawchive.pw/static/menu/recent.svg
// @connect      *
// @grant        GM_download
// @grant        GM.xmlHttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getResourceText
// @resource     mainCSS https://cdn.jsdelivr.net/gh/TearTyr/[email protected]/Ultra-Galleries.css
// @resource     jszipScript https://unpkg.com/[email protected]/dist/jszip.min.js
// @noframes
// ==/UserScript==

(() => {
    'use strict';

    // ====================================================
    // Core Configuration & Constants
    // ====================================================
    const CONFIG = {
        MAX_CONCURRENT_FETCHES: 3,
        MAX_RETRIES: 5,
        RETRY_DELAY: 2000,
        MIN_SCALE: 0.05,
        MAX_SCALE: 5,
        ZOOM_STEP: 0.2,
        DEBOUNCE_DELAY: 250,
        PAN_RESISTANCE: 0.8,
        DOUBLE_TAP_THRESHOLD: 300,
        CACHE_EVICTION_COUNT: 20,
        PRELOAD_COUNT: 2,
        SCROLL_SIMULATION_BASE_TIMEOUT: 3000,
        SLIDESHOW_DELAY: 3000,
        CONTROLS_HIDE_DELAY: 3000,
        CONTEXT_MENU_HIDE_DELAY: 200,
        PRELOAD_WINDOW_BUFFER: 4,
        PROGRESS_NOTIFY_INTERVAL: 250
    };

    // ====================================================
    // Pawchive Compliance Configuration & Pacer
    // ====================================================
    const PAWCHIVE_CONFIG = {
        USER_AGENT: 'UltraGalleries/4.3.1 (+https://github.com/TearTyr/Ultra-Galleries; contact: Meri/TearTyr)',
        MIN_REQUEST_INTERVAL: 1050 // Enforces <= 1 request/second
    };

    const isPawchiveHost = (url = '') => {
        const currentHost = window.location.hostname;
        return /pawchive\.(st|pw)/i.test(currentHost) || /pawchive\.(st|pw)/i.test(url);
    };

    const RequestPacer = {
        lastRequestTime: 0,
        queue: Promise.resolve(),

        async throttle(url) {
            if (!isPawchiveHost(url)) return;
            return (this.queue = this.queue.catch(() => {}).then(async () => {
                const now = Date.now();
                const elapsed = now - this.lastRequestTime;
                if (elapsed < PAWCHIVE_CONFIG.MIN_REQUEST_INTERVAL) {
                    await Utils.delay(PAWCHIVE_CONFIG.MIN_REQUEST_INTERVAL - elapsed);
                }
                this.lastRequestTime = Date.now();
            }));
        },

        getHeaders(url) {
            const headers = {};
            if (isPawchiveHost(url)) {
                headers['User-Agent'] = PAWCHIVE_CONFIG.USER_AGENT;
            }
            return headers;
        }
    };

    const ActiveRequests = {
        bySession: new Map(),

        add(sessionId, handle) {
            if (!sessionId || !handle) return;
            if (!this.bySession.has(sessionId)) this.bySession.set(sessionId, new Set());
            this.bySession.get(sessionId).add(handle);
        },

        remove(sessionId, handle) {
            if (!sessionId) return;
            const set = this.bySession.get(sessionId);
            if (set) set.delete(handle);
        },

        abortSession(sessionId) {
            if (!sessionId) return;
            const set = this.bySession.get(sessionId);
            if (!set) return;
            set.forEach(h => { try { h.abort(); } catch { /* ignore */ } });
            this.bySession.delete(sessionId);
        },

        abortAll() {
            for (const set of this.bySession.values()) {
                set.forEach(h => { try { h.abort(); } catch { /* ignore */ } });
            }
            this.bySession.clear();
        }
    };

    const FALLBACK_POSTER = 'data:image/svg+xml;base64,' + btoa(
        '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 24 24" fill="#666"><path d="M8 5v14l11-7z"/></svg>'
    );

    const FALLBACK_ATTACHMENT = 'data:image/svg+xml;base64,' + btoa(
        '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 24 24" fill="none" stroke="#888" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>'
    );

    const BUTTONS = {
        DOWNLOAD: '【DOWNLOAD】',
        DOWNLOAD_ALL: '【DL ALL】',
        DOWNLOAD_IMAGES: '【DL IMAGES】',
        DOWNLOAD_ATTACHMENTS: '【DL FILES】',
        FULL: '【FULL】',
        HEIGHT: '【FILL HEIGHT】',
        WIDTH: '【FILL WIDTH】',
        GALLERY: '【GALLERY】',
        SETTINGS: '⚙️',
        FULLSCREEN: '⛶',
        CLOSE: '✕'
    };

    const CSS = {
        BTN: 'ug-button',
        BTN_CONTAINER: 'ug-button-container',
        NOTIF_AREA: 'ug-notification-area',
        NOTIF_CONTAINER: 'ug-notification-container',
        NOTIF_TEXT: 'ug-notification-text',
        NOTIF_CLOSE: 'ug-notification-close',
        NOTIF_REPORT: 'ug-notification-report',
        SETTINGS_BTN: 'settings-button',
        LONG_PRESS: 'ug-long-press',
        GALLERY: {
            OVERLAY: 'ug-gallery-overlay',
            CONTAINER: 'ug-gallery-container',
            EXPANDED_VIEW: 'ug-gallery-expanded-view',
            HIDE: 'ug-gallery-hide',
            TOOLBAR: 'ug-gallery-toolbar',
            ZOOM_CONTAINER: 'ug-gallery-zoom-container',
            MAIN_IMG_CONTAINER: 'ug-main-image-container',
            MAIN_IMG: 'ug-main-image',
            MAIN_VIDEO: 'ug-main-video',
            THUMBNAIL: 'ug-thumbnail',
            THUMBNAIL_WRAPPER: 'ug-thumbnail-container',
            THUMBNAIL_STRIP: 'ug-thumbnail-strip',
            NAV: 'ug-gallery-nav',
            NAV_CONTAINER: 'ug-gallery-nav-container',
            PREV: 'ug-gallery-prev',
            NEXT: 'ug-gallery-next',
            COUNTER: 'ug-gallery-counter',
            FULLSCREEN: 'ug-gallery-fullscreen',
            FULLSCREEN_OVERLAY: 'ug-fullscreen-overlay',
            STRIP_CONTAINER: 'ug-gallery-thumbnail-strip-container',
            TOOLBAR_BTN: 'ug-toolbar-button',
            CONTROLS_HIDDEN: 'ug-controls-hidden',
            GRABBING: 'ug-grabbing',
            ZOOMED: 'zoomed',
            IS_TRANSITIONING: 'is-transitioning',
            IMAGE_ERROR_MSG: 'ug-image-error-message'
        }
    };

    const isNekohouse = window.location.hostname.includes('nekohouse');

    const SELECTORS = {
        IMAGE_LINK: isNekohouse ? 'a.image-link:not(.scrape__user-profile)' : 'a.fileThumb.image-link',
        GENERIC_IMAGE_LINK: 'a[href*=".jpg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"], a[href*=".jpeg"]',
        ATTACHMENT_LINK: isNekohouse ? '.scrape__attachment-link' : '.post__attachment-link',
        POST_TITLE: isNekohouse ? '.scrape__title' : '.post__title',
        POST_USER_NAME: isNekohouse ? '.scrape__user-name' : '.post__user-name',
        MAIN_THUMBNAIL: isNekohouse ? '.scrape__thumbnail:not(.scrape__thumbnail--attachment)' : '.post__thumbnail:not(.post__thumbnail--attachment)',
        POST_ACTIONS: isNekohouse ? '.scrape__actions' : '.post__actions',
        FILE_DIVS: isNekohouse ? '.scrape__thumbnail' : '.post__thumbnail',
        VIDEO_LINK: 'a.fileThumb[href$=".mp4"], a.fileThumb[href$=".webm"], a.fileThumb[href$=".mov"], a[href$=".mp4"], a[href$=".webm"], a[href$=".mov"]',
        VIDEO_THUMBNAIL: isNekohouse ? '.scrape__video-thumbnail' : '.post__video-thumbnail'
    };

    // ====================================================
    // Native DOM Helpers
    // ====================================================
    const DOM = {
        $(selector, parent = document) {
            return parent.querySelector(selector);
        },

        $$(selector, parent = document) {
            return Array.from(parent.querySelectorAll(selector));
        },

        create(tag, attributes = {}, children = []) {
            const element = document.createElement(tag);
            for (const [key, value] of Object.entries(attributes)) {
                if (key === 'className') {
                    element.className = value;
                } else if (key === 'html') {
                    element.innerHTML = value;
                } else if (key === 'text') {
                    element.textContent = value;
                } else if (key === 'style' && typeof value === 'object') {
                    Object.assign(element.style, value);
                } else if (key.startsWith('on') && typeof value === 'function') {
                    element.addEventListener(key.slice(2).toLowerCase(), value);
                } else if (key.startsWith('data-')) {
                    element.setAttribute(key, value);
                } else {
                    element.setAttribute(key, value);
                }
            }

            const childArray = Array.isArray(children) ? children : [children];
            for (const child of childArray) {
                if (!child) continue;
                if (typeof child === 'string') {
                    element.appendChild(document.createTextNode(child));
                } else if (child instanceof Node) {
                    element.appendChild(child);
                }
            }
            return element;
        },

        scrollToCenter(container, element) {
            if (!container || !element) return;
            const offset = element.offsetLeft - (container.clientWidth / 2) + (element.clientWidth / 2);
            container.scrollTo({ left: offset, behavior: 'smooth' });
        },

        saveBlob(blob, filename) {
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            a.remove();
            setTimeout(() => URL.revokeObjectURL(url), 10000);
        }
    };

    // ====================================================
    // Built-in Native Modal System
    // ====================================================
    const UGModal = {
        confirm({ title, text, confirmText = 'Confirm', cancelText = 'Cancel', icon = 'question' }) {
            return new Promise((resolve) => {
                DOM.$('.ug-modal-overlay')?.remove();

                const iconSvgs = {
                    question: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
                    warning: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
                    info: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12.01" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>'
                };

                const opener = document.activeElement;
                const prevOverflow = document.body.style.overflow;
                document.body.style.overflow = 'hidden';

                const focusables = [];
                const confirmBtn = DOM.create('button', {
                    className: 'ug-modal-btn ug-modal-confirm',
                    text: confirmText,
                    onclick: () => close(true)
                });
                focusables.push(confirmBtn);

                let cancelBtn = null;
                if (cancelText) {
                    cancelBtn = DOM.create('button', {
                        className: 'ug-modal-btn ug-modal-cancel',
                        text: cancelText,
                        onclick: () => close(false)
                    });
                    focusables.push(cancelBtn);
                }

                const overlay = DOM.create('div', { className: 'ug-modal-overlay' }, [
                    DOM.create('div', { className: 'ug-modal-container' }, [
                        DOM.create('div', { className: `ug-modal-icon ${icon}`, html: iconSvgs[icon] || iconSvgs.info }),
                        DOM.create('h3', { className: 'ug-modal-title', text: title }),
                        DOM.create('p', { className: 'ug-modal-text', text: text }),
                        DOM.create('div', { className: 'ug-modal-actions' }, focusables)
                    ])
                ]);

                document.body.appendChild(overlay);
                requestAnimationFrame(() => overlay.classList.add('show'));
                confirmBtn.focus();

                const onKey = (e) => {
                    if (e.key === 'Escape') {
                        e.preventDefault();
                        close(false);
                        return;
                    }
                    if (e.key === 'Enter') {
                        const t = e.target;
                        const isSafeTarget = t === confirmBtn || (cancelBtn && t === cancelBtn) || t === overlay || t === document.body;
                        if (isSafeTarget && !(t instanceof HTMLTextAreaElement)) {
                            e.preventDefault();
                            close(true);
                        }
                        return;
                    }
                    if (e.key === 'Tab') {
                        const first = focusables[0];
                        const last = focusables[focusables.length - 1];
                        if (e.shiftKey && document.activeElement === first) {
                            last.focus();
                            e.preventDefault();
                        } else if (!e.shiftKey && document.activeElement === last) {
                            first.focus();
                            e.preventDefault();
                        }
                    }
                };
                window.addEventListener('keydown', onKey);

                let closed = false;
                function close(result) {
                    if (closed) return;
                    closed = true;
                    window.removeEventListener('keydown', onKey);
                    overlay.classList.remove('show');
                    document.body.style.overflow = prevOverflow;
                    setTimeout(() => {
                        overlay.remove();
                        try { opener?.focus?.(); } catch { /* ignore */ }
                    }, 200);
                    resolve({ isConfirmed: result });
                }
            });
        },

        alert(title, text, icon = 'info') {
            return UGModal.confirm({ title, text, confirmText: 'OK', cancelText: null, icon });
        },

        choose({ title, text, icon = 'question', options }) {
            return new Promise((resolve) => {
                DOM.$('.ug-modal-overlay')?.remove();

                const iconSvgs = {
                    question: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
                    warning: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
                    info: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12.01" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>'
                };

                const opener = document.activeElement;
                const prevOverflow = document.body.style.overflow;
                document.body.style.overflow = 'hidden';

                const focusables = [];
                const buttons = options.map(opt => {
                    const btn = DOM.create('button', {
                        className: `ug-modal-btn ${opt.primary ? 'ug-modal-confirm' : 'ug-modal-cancel'}`,
                        text: opt.label,
                        onclick: () => close(opt.value)
                    });
                    focusables.push(btn);
                    return btn;
                });

                const overlay = DOM.create('div', { className: 'ug-modal-overlay' }, [
                    DOM.create('div', { className: 'ug-modal-container' }, [
                        DOM.create('div', { className: `ug-modal-icon ${icon}`, html: iconSvgs[icon] || iconSvgs.info }),
                        DOM.create('h3', { className: 'ug-modal-title', text: title }),
                        DOM.create('p', { className: 'ug-modal-text', text: text }),
                        DOM.create('div', { className: 'ug-modal-actions' }, buttons)
                    ])
                ]);

                document.body.appendChild(overlay);
                requestAnimationFrame(() => overlay.classList.add('show'));
                focusables[0]?.focus();

                const onKey = (e) => {
                    if (e.key === 'Escape') {
                        e.preventDefault();
                        close(null);
                        return;
                    }
                    if (e.key === 'Tab') {
                        const first = focusables[0];
                        const last = focusables[focusables.length - 1];
                        if (e.shiftKey && document.activeElement === first) {
                            last.focus();
                            e.preventDefault();
                        } else if (!e.shiftKey && document.activeElement === last) {
                            first.focus();
                            e.preventDefault();
                        }
                    }
                };
                window.addEventListener('keydown', onKey);

                let closed = false;
                function close(value) {
                    if (closed) return;
                    closed = true;
                    window.removeEventListener('keydown', onKey);
                    overlay.classList.remove('show');
                    document.body.style.overflow = prevOverflow;
                    setTimeout(() => {
                        overlay.remove();
                        try { opener?.focus?.(); } catch { /* ignore */ }
                    }, 200);
                    resolve({ value });
                }
            });
        }
    };

    // ====================================================
    // Native IndexedDB Cache Manager
    // ====================================================
    const ImageCacheDB = {
        DB_NAME: 'UltraGalleriesCache',
        STORE_NAME: 'imageCache',
        dbPromise: null,

        init() {
            if (this.dbPromise) return this.dbPromise;
            this.dbPromise = new Promise((resolve, reject) => {
                const req = indexedDB.open(this.DB_NAME, 1);
                req.onupgradeneeded = (e) => {
                    const db = e.target.result;
                    if (!db.objectStoreNames.contains(this.STORE_NAME)) {
                        const store = db.createObjectStore(this.STORE_NAME, { keyPath: 'url' });
                        store.createIndex('cachedAt', 'cachedAt', { unique: false });
                    }
                };
                req.onsuccess = () => resolve(req.result);
                req.onerror = () => reject(req.error);
            });
            return this.dbPromise;
        },

        async get(url) {
            try {
                const db = await this.init();
                return new Promise((resolve) => {
                    const tx = db.transaction(this.STORE_NAME, 'readwrite');
                    const store = tx.objectStore(this.STORE_NAME);
                    const req = store.get(url);
                    req.onsuccess = () => {
                        const record = req.result;
                        if (record && record.blob) {
                            record.cachedAt = Date.now();
                            store.put(record);
                            resolve(record.blob);
                        } else {
                            resolve(null);
                        }
                    };
                    req.onerror = () => resolve(null);
                });
            } catch {
                return null;
            }
        },

        async put(url, blob) {
            try {
                const db = await this.init();
                return new Promise((resolve) => {
                    const tx = db.transaction(this.STORE_NAME, 'readwrite');
                    tx.objectStore(this.STORE_NAME).put({ url, blob, cachedAt: Date.now() });
                    tx.oncomplete = () => resolve(true);
                    tx.onerror = async (e) => {
                        if (e.target.error && e.target.error.name === 'QuotaExceededError') {
                            await this.evictOldest(CONFIG.CACHE_EVICTION_COUNT);
                        }
                        resolve(false);
                    };
                });
            } catch {
                return false;
            }
        },

        async evictOldest(count = 20) {
            try {
                const db = await this.init();
                const tx = db.transaction(this.STORE_NAME, 'readwrite');
                const store = tx.objectStore(this.STORE_NAME);
                const index = store.index('cachedAt');
                let deleted = 0;
                index.openCursor().onsuccess = (e) => {
                    const cursor = e.target.result;
                    if (cursor && deleted < count) {
                        cursor.delete();
                        deleted++;
                        cursor.continue();
                    }
                };
            } catch {
                // Ignore eviction errors
            }
        },

        async clear() {
            try {
                const db = await this.init();
                const tx = db.transaction(this.STORE_NAME, 'readwrite');
                tx.objectStore(this.STORE_NAME).clear();
                state.notificationType = 'success';
                state.notification = 'Persistent image cache cleared.';
            } catch {
                state.notificationType = 'error';
                state.notification = 'Error clearing cache.';
            }
        }
    };

    // ====================================================
    // Shared Mutable References
    // ====================================================
    let galleryOverlay = null;
    let loadedBlobUrls = new Map();
    const elements = {
        galleryButton: null,
        settingsButton: null
    };
    let lastFocusedElement = null;
    let focusTrapListener = null;
    let uiObserver = null;
    let lastProcessedUrl = null;

    // ====================================================
    // High-Frequency View State
    // ====================================================
    const viewState = {
        zoomScale: 1,
        imageOffset: { x: 0, y: 0 },
        zoomOrigin: { x: 0, y: 0 },
        initialScale: 1,
        pinchZoomActive: false,
        initialTouchDistance: 0,
        dragStartPosition: { x: 0, y: 0 },
        dragStartOffset: { x: 0, y: 0 }
    };

    // ====================================================
    // Utilities
    // ====================================================
    const Utils = {
        sanitizeFileName: name => String(name || '').replace(/[/\\:*?"<>|]/g, '-'),

        // Created once at script init — Intl.Collator is expensive to
        // instantiate, cheap to reuse. `numeric: true` gives us "file2 < file10";
        // `sensitivity: 'base'` ignores case so Page_1 and page_1 group together.
        _naturalCollator: new Intl.Collator(undefined, {
            numeric: true,
            sensitivity: 'base'
        }),

        // Natural-order string comparison.
        naturalCompare(a, b) {
            return Utils._naturalCollator.compare(String(a ?? ''), String(b ?? ''));
        },

        // Extract the original filename from a link, in reliability order:
        //   1. `download` attribute (most accurate)
        //   2. `?f=` query param (Pawchive/Kemono)
        //   3. URL path tail (hash — last resort)
        // Safe for non-anchor elements (e.g. raw <video>) — all reads are optional.
        extractFileName(linkEl, fallbackUrl) {
            const dl = linkEl?.getAttribute?.('download');
            if (dl && dl.trim()) return dl.trim();

            const href = linkEl?.getAttribute?.('href') || '';
            const fMatch = href.match(/[?&]f=([^&#]+)/);
            if (fMatch) {
                try { return decodeURIComponent(fMatch[1]); }
                catch { return fMatch[1]; }
            }

            try {
                return String(fallbackUrl || '').split('?')[0].split('/').pop() || '';
            } catch {
                return '';
            }
        },

        // Sort an array of media items in place per the given mode.
        // Unknown modes are a silent no-op so a bad stored value can't break loading.
        sortItems(items, mode) {
            if (!mode || mode === 'dom') return items;
            if (mode === 'filename-asc') {
                items.sort((a, b) => Utils.naturalCompare(a.fileName, b.fileName));
            } else if (mode === 'filename-desc') {
                items.sort((a, b) => Utils.naturalCompare(b.fileName, a.fileName));
            }
            return items;
        },

        getPostDate: (type = 'published') => {
            let selector;
            if (type === 'edited') selector = '.post__edited, .scrape__edited';
            else if (type === 'added' || type === 'imported') selector = '.post__added, .scrape__added';
            else selector = '.post__published, .scrape__published, time[datetime]';

            const timeEl = DOM.$(selector);
            if (timeEl) {
                const dateStr = timeEl.getAttribute('datetime') || timeEl.textContent;
                if (dateStr) {
                    const match = dateStr.match(/\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2})?)?|\d{4}-\d{2}/);
                    if (match && match[0]) {
                        return Utils.sanitizeFileName(match[0].replace(/[ T]/g, '_').replace(/:/g, '-'));
                    }
                    return Utils.sanitizeFileName(dateStr.replace(/.*?:\s*/, '').trim());
                }
            }
            return 'UnknownDate';
        },

        // Returns the post timestamp as a real Date anchored to UTC so that
        // JSZip's DOS serializer (which reads UTC components) writes the exact
        // wall-clock shown on the post page. Returns null if no valid date found.
        getPostDateObject: (type = 'published') => {
            let selector;
            if (type === 'edited') selector = '.post__edited, .scrape__edited';
            else if (type === 'added' || type === 'imported') selector = '.post__added, .scrape__added';
            else selector = '.post__published, .scrape__published, time[datetime]';

            const timeEl = DOM.$(selector);
            if (timeEl) {
                const raw = (timeEl.getAttribute('datetime') || timeEl.textContent || '').trim();
                const match = raw.match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/);
                if (match) {
                    const [, y, mo, d, h = '00', mi = '00', s = '00'] = match;
                    return new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +s));
                }
            }
            return null;
        },

        setImageStyle: (img, styles) => {
            if (!img) return;
            for (const key in styles) {
                const kebabKey = key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
                img.style.setProperty(kebabKey, styles[key], 'important');
            }
        },

        isPostPage: () => {
            const hasImages =
                DOM.$(SELECTORS.IMAGE_LINK) ||
                DOM.$(SELECTORS.GENERIC_IMAGE_LINK) ||
                DOM.$('div.post__files');

            if (hasImages) return true;

            const path = window.location.pathname;
            const patterns = [
                /\/user\/.*\/post\//,
                /\/server\/.*\/channel\//,
                /\/post\//
            ];
            return patterns.some(pattern => pattern.test(path));
        },

        delay: ms => new Promise(resolve => setTimeout(resolve, ms)),

        debounce: (func, wait) => {
            let timeout;
            return function (...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
        },

        throttle: (func, limit) => {
            let lastRan;
            let lastFunc;
            return function (...args) {
                if (!lastRan) {
                    func.apply(this, args);
                    lastRan = Date.now();
                } else {
                    clearTimeout(lastFunc);
                    lastFunc = setTimeout(() => {
                        if ((Date.now() - lastRan) >= limit) {
                            func.apply(this, args);
                            lastRan = Date.now();
                        }
                    }, limit - (Date.now() - lastRan));
                }
            };
        },

        handleMediaSrc: mediaLink => {
            let href = mediaLink.getAttribute('href') || mediaLink.querySelector('.fileThumb')?.getAttribute('href');
            if (!href && mediaLink.href) href = mediaLink.href;

            if (href) {
                href = href.split('?')[0];
                if (href.includes('/thumbnail/')) {
                    href = href.replace(/\/thumbnail\//, '/data/');
                }
                return href;
            }

            const directImg = mediaLink.querySelector('img');
            if (directImg) {
                const rawSrc = directImg.getAttribute('data-src') || directImg.src;
                if (!rawSrc) return null;

                const imgSrc = rawSrc.split('?')[0];
                if (imgSrc.includes('/data/')) return imgSrc;
                if (imgSrc.includes('/thumbnail/')) {
                    return imgSrc.replace(/\/thumbnail\//, '/data/');
                }
                return imgSrc;
            }
            return null;
        },

        ensureThumbnailsExist: () => {
            try {
                const videoLinks = DOM.$$(SELECTORS.VIDEO_LINK);
                videoLinks.forEach(videoLink => {
                    const videoThumb = videoLink.closest(SELECTORS.VIDEO_THUMBNAIL);
                    if (!videoThumb) {
                        const video = videoLink.querySelector('video');
                        if (video && video.hasAttribute('poster')) {
                            const posterUrl = video.getAttribute('poster');
                            if (videoLink.parentNode) {
                                const thumbnailContainer = DOM.create('div', {
                                    className: isNekohouse ? 'scrape__video-thumbnail' : 'post__video-thumbnail'
                                }, [
                                    DOM.create('img', {
                                        src: posterUrl,
                                        className: isNekohouse ? 'scrape__thumbnail-img' : 'post__thumbnail-img'
                                    })
                                ]);
                                videoLink.parentNode.insertBefore(thumbnailContainer, videoLink);
                            }
                        }
                    }
                });
            } catch (error) {
                console.warn('Minor error ensuring thumbnails:', error);
            }
        }
    };

    const notifyLoadProgress = Utils.throttle((message, type = 'info') => {
        state.notificationType = type;
        state.notification = message;
    }, CONFIG.PROGRESS_NOTIFY_INTERVAL);

    // ====================================================
    // Image Sizing Module
    // ====================================================
    const ImageSizing = {
        applyBestFit: (el) => {
            if (!el) return;
            Utils.setImageStyle(el, {
                maxWidth: '100%',
                maxHeight: '100%',
                width: 'auto',
                height: 'auto',
                objectFit: 'contain',
                margin: '0'
            });
        },

        applyFillHeight: (el) => {
            if (!el) return;
            Utils.setImageStyle(el, {
                maxHeight: '90vh',
                maxWidth: '100%',
                width: 'auto',
                height: '90vh',
                objectFit: 'contain'
            });
        },

        applyFillWidth: (el) => {
            if (!el) return;
            Utils.setImageStyle(el, {
                maxHeight: 'none',
                maxWidth: '100vw',
                width: '100%',
                height: 'auto',
                objectFit: 'contain'
            });
        },

        applyFullSize: (el) => {
            if (!el) return;
            Utils.setImageStyle(el, {
                maxHeight: 'none',
                maxWidth: 'none',
                height: 'auto',
                width: 'auto',
                objectFit: 'none'
            });
        }
    };

    // ====================================================
    // Zoom Boundary Helpers
    // ====================================================
    const ZoomHelper = {
        calculateHardBoundaryOffsets: (offsetX, offsetY, scale, containerRect, imageDOM) => {
            if (!imageDOM || !containerRect) return { x: offsetX, y: offsetY };

            const natW = imageDOM.naturalWidth || imageDOM.videoWidth || 0;
            const natH = imageDOM.naturalHeight || imageDOM.videoHeight || 0;
            const imgWidth = natW * scale;
            const imgHeight = natH * scale;

            let x = offsetX;
            let y = offsetY;

            if (imgWidth > containerRect.width) {
                const minX = containerRect.width - imgWidth;
                x = Math.max(minX, Math.min(x, 0));
            } else {
                x = 0;
            }

            if (imgHeight > containerRect.height) {
                const minY = containerRect.height - imgHeight;
                y = Math.max(minY, Math.min(y, 0));
            } else {
                y = 0;
            }

            return { x, y };
        }
    };

    // ====================================================
    // Unified Pointer Events & Gesture Engine
    // ====================================================
    const PointerEngine = {
        activePointers: new Map(),
        isDragging: false,
        dragStartTime: 0,
        lastUpdateTime: 0,
        velocity: { x: 0, y: 0 },
        lastPosition: { x: 0, y: 0 },
        animationFrame: null,
        inertiaAnimation: null,
        cachedImgEl: null,
        cachedZoomEl: null,
        lastTapTime: 0,

        init(containerDOM) {
            containerDOM.addEventListener('pointerdown', PointerEngine.onPointerDown, { passive: false });
            containerDOM.addEventListener('pointermove', PointerEngine.onPointerMove, { passive: false });
            containerDOM.addEventListener('pointerup', PointerEngine.onPointerUp, { passive: false });
            containerDOM.addEventListener('pointercancel', PointerEngine.onPointerUp, { passive: false });
        },

        updateTransform() {
            if (!PointerEngine.cachedImgEl && galleryOverlay) {
                const container = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
                PointerEngine.cachedImgEl = container?.querySelector('img, video');
                PointerEngine.cachedZoomEl = DOM.$('#zoom-level');
            }

            if (PointerEngine.cachedImgEl) {
                PointerEngine.cachedImgEl.style.transformOrigin = '0 0';
                PointerEngine.cachedImgEl.style.transform =
                    `translate(${viewState.imageOffset.x}px, ${viewState.imageOffset.y}px) scale(${viewState.zoomScale})`;
            }

            if (PointerEngine.cachedZoomEl) {
                PointerEngine.cachedZoomEl.textContent = `${Math.round(viewState.zoomScale * 100)}%`;
            }

            PointerEngine.animationFrame = null;
        },

        onPointerDown(e) {
            if (e.pointerType === 'mouse' && e.button !== 0) return;
            e.preventDefault();

            PointerEngine.activePointers.set(e.pointerId, e);
            const container = e.currentTarget;
            container.setPointerCapture(e.pointerId);

            if (PointerEngine.inertiaAnimation) {
                cancelAnimationFrame(PointerEngine.inertiaAnimation);
                PointerEngine.inertiaAnimation = null;
            }

            if (PointerEngine.activePointers.size === 1) {
                const now = Date.now();
                if (e.pointerType === 'touch' && now - PointerEngine.lastTapTime < CONFIG.DOUBLE_TAP_THRESHOLD) {
                    PointerEngine.handleDoubleTap(e);
                    PointerEngine.lastTapTime = 0;
                    return;
                }
                PointerEngine.lastTapTime = now;

                PointerEngine.isDragging = true;
                PointerEngine.dragStartTime = performance.now();
                PointerEngine.lastUpdateTime = PointerEngine.dragStartTime;

                viewState.dragStartPosition = { x: e.clientX, y: e.clientY };
                viewState.dragStartOffset = { x: viewState.imageOffset.x, y: viewState.imageOffset.y };
                PointerEngine.lastPosition = { x: e.clientX, y: e.clientY };
                PointerEngine.velocity = { x: 0, y: 0 };

                container.classList.add(CSS.GALLERY.GRABBING);
                container.style.willChange = 'transform';
            } else if (PointerEngine.activePointers.size === 2) {
                PointerEngine.isDragging = false;
                viewState.pinchZoomActive = true;
                const [p1, p2] = Array.from(PointerEngine.activePointers.values());
                viewState.initialTouchDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY);
                viewState.initialScale = viewState.zoomScale;

                const rect = container.getBoundingClientRect();
                viewState.zoomOrigin = {
                    x: ((p1.clientX + p2.clientX) / 2) - rect.left,
                    y: ((p1.clientY + p2.clientY) / 2) - rect.top
                };
            }
        },

        onPointerMove(e) {
            if (!PointerEngine.activePointers.has(e.pointerId)) return;
            e.preventDefault();
            PointerEngine.activePointers.set(e.pointerId, e);

            if (PointerEngine.isDragging && PointerEngine.activePointers.size === 1) {
                const currentTime = performance.now();
                const deltaTime = currentTime - PointerEngine.lastUpdateTime;

                if (deltaTime > 0) {
                    PointerEngine.velocity.x = (e.clientX - PointerEngine.lastPosition.x) / deltaTime * 16;
                    PointerEngine.velocity.y = (e.clientY - PointerEngine.lastPosition.y) / deltaTime * 16;
                }

                PointerEngine.lastPosition = { x: e.clientX, y: e.clientY };
                PointerEngine.lastUpdateTime = currentTime;

                const deltaX = e.clientX - viewState.dragStartPosition.x;
                const deltaY = e.clientY - viewState.dragStartPosition.y;

                viewState.imageOffset.x = viewState.dragStartOffset.x + deltaX;
                viewState.imageOffset.y = viewState.dragStartOffset.y + deltaY;

                if (!PointerEngine.animationFrame) {
                    PointerEngine.animationFrame = requestAnimationFrame(PointerEngine.updateTransform);
                }
            } else if (viewState.pinchZoomActive && PointerEngine.activePointers.size === 2) {
                const [p1, p2] = Array.from(PointerEngine.activePointers.values());
                const currentDistance = Math.hypot(p2.clientX - p1.clientX, p2.clientY - p1.clientY);
                if (viewState.initialTouchDistance === 0) return;

                const scaleFactor = currentDistance / viewState.initialTouchDistance;
                const newScale = Math.max(CONFIG.MIN_SCALE, Math.min(viewState.initialScale * scaleFactor, state.maxZoomScale));

                const imageX = (viewState.zoomOrigin.x - viewState.imageOffset.x) / viewState.zoomScale;
                const imageY = (viewState.zoomOrigin.y - viewState.imageOffset.y) / viewState.zoomScale;

                viewState.imageOffset.x = viewState.zoomOrigin.x - (imageX * newScale);
                viewState.imageOffset.y = viewState.zoomOrigin.y - (imageY * newScale);
                viewState.zoomScale = newScale;

                PointerEngine.updateTransform();
            }
        },

        onPointerUp(e) {
            if (PointerEngine.activePointers.has(e.pointerId)) {
                try {
                    e.currentTarget.releasePointerCapture(e.pointerId);
                } catch {
                    // Ignore capture errors
                }
                PointerEngine.activePointers.delete(e.pointerId);
            }

            if (PointerEngine.activePointers.size === 0) {
                if (PointerEngine.isDragging) {
                    PointerEngine.isDragging = false;
                    const container = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
                    if (container) {
                        container.classList.remove(CSS.GALLERY.GRABBING);
                        setTimeout(() => container.style.willChange = '', 1000);
                    }

                    if (state.inertiaEnabled && (Math.abs(PointerEngine.velocity.x) > 0.5 || Math.abs(PointerEngine.velocity.y) > 0.5)) {
                        PointerEngine.applyInertia();
                    } else {
                        PointerEngine.enforceBoundaries();
                    }
                }
                viewState.pinchZoomActive = false;
            } else if (PointerEngine.activePointers.size === 1) {
                const remaining = PointerEngine.activePointers.values().next().value;
                viewState.dragStartPosition = { x: remaining.clientX, y: remaining.clientY };
                viewState.dragStartOffset = { ...viewState.imageOffset };
                PointerEngine.lastPosition = { x: remaining.clientX, y: remaining.clientY };
                PointerEngine.isDragging = true;
            }
        },

        applyInertia() {
            const friction = 0.95;
            const minVelocity = 0.5;

            const animate = () => {
                PointerEngine.velocity.x *= friction;
                PointerEngine.velocity.y *= friction;

                viewState.imageOffset.x += PointerEngine.velocity.x;
                viewState.imageOffset.y += PointerEngine.velocity.y;

                if (Math.abs(PointerEngine.velocity.x) < minVelocity && Math.abs(PointerEngine.velocity.y) < minVelocity) {
                    PointerEngine.inertiaAnimation = null;
                    PointerEngine.enforceBoundaries();
                    return;
                }

                PointerEngine.updateTransform();
                PointerEngine.inertiaAnimation = requestAnimationFrame(animate);
            };

            PointerEngine.inertiaAnimation = requestAnimationFrame(animate);
        },

        enforceBoundaries() {
            if (!galleryOverlay) return;
            const containerDOM = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
            const imageDOM = DOM.$(`.${CSS.GALLERY.MAIN_IMG}, .${CSS.GALLERY.MAIN_VIDEO}`, containerDOM);
            if (!containerDOM || !imageDOM) return;

            const containerRect = containerDOM.getBoundingClientRect();
            const boundedOffset = ZoomHelper.calculateHardBoundaryOffsets(
                viewState.imageOffset.x,
                viewState.imageOffset.y,
                viewState.zoomScale,
                containerRect,
                imageDOM
            );

            if (boundedOffset.x !== viewState.imageOffset.x || boundedOffset.y !== viewState.imageOffset.y) {
                const duration = 300;
                const startX = viewState.imageOffset.x;
                const startY = viewState.imageOffset.y;
                const deltaX = boundedOffset.x - startX;
                const deltaY = boundedOffset.y - startY;
                const startTime = performance.now();

                const animateToBoundary = (currentTime) => {
                    const elapsed = currentTime - startTime;
                    const progress = Math.min(elapsed / duration, 1);
                    const easeProgress = 1 - Math.pow(1 - progress, 3);

                    viewState.imageOffset.x = startX + deltaX * easeProgress;
                    viewState.imageOffset.y = startY + deltaY * easeProgress;

                    PointerEngine.updateTransform();

                    if (progress < 1) {
                        requestAnimationFrame(animateToBoundary);
                    }
                };

                requestAnimationFrame(animateToBoundary);
            }
        },

        handleDoubleTap(e) {
            e.preventDefault();
            if (!galleryOverlay) return;

            const containerDOM = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
            if (!containerDOM) return;

            const rect = containerDOM.getBoundingClientRect();
            const clickX = e.clientX - rect.left;
            const clickY = e.clientY - rect.top;

            if (viewState.zoomScale > 1) {
                Zoom.resetZoom();
            } else {
                const newScale = 2.5;
                const imageX = (clickX - viewState.imageOffset.x) / viewState.zoomScale;
                const imageY = (clickY - viewState.imageOffset.y) / viewState.zoomScale;

                const newOffsetX = clickX - (imageX * newScale);
                const newOffsetY = clickY - (imageY * newScale);

                Zoom._applyTransition(containerDOM, () => {
                    viewState.imageOffset.x = newOffsetX;
                    viewState.imageOffset.y = newOffsetY;
                    viewState.zoomScale = newScale;
                    PointerEngine.updateTransform();
                });
            }
        }
    };

    // ====================================================
    // Slideshow Module
    // ====================================================
    const Slideshow = {
        interval: null,
        isActive: false,
        delay: CONFIG.SLIDESHOW_DELAY,
        pauseOnHover: true,

        init() {
            Slideshow.delay = state.slideshowDelay || CONFIG.SLIDESHOW_DELAY;
            Slideshow.pauseOnHover = state.slideshowPauseOnHover !== false;
        },

        start() {
            if (Slideshow.isActive) return;
            Slideshow.isActive = true;
            state.isSlideshowActive = true;
            Slideshow.interval = setInterval(() => Gallery.nextImage(), Slideshow.delay);
            Slideshow.showIndicator();

            if (Slideshow.pauseOnHover && galleryOverlay) {
                galleryOverlay.addEventListener('mouseenter', Slideshow.pause);
                galleryOverlay.addEventListener('mouseleave', Slideshow.resume);
            }
            Slideshow.syncControls();
            Accessibility.announce('Slideshow started');
            state.notificationType = 'info';
            state.notification = 'Slideshow started';
        },

        stop() {
            if (!Slideshow.isActive) return;
            Slideshow.isActive = false;
            state.isSlideshowActive = false;
            if (Slideshow.interval) {
                clearInterval(Slideshow.interval);
                Slideshow.interval = null;
            }
            if (galleryOverlay) {
                Slideshow.hideIndicator();
                galleryOverlay.removeEventListener('mouseenter', Slideshow.pause);
                galleryOverlay.removeEventListener('mouseleave', Slideshow.resume);
            }
            Slideshow.syncControls();
            Accessibility.announce('Slideshow stopped');
            state.notificationType = 'info';
            state.notification = 'Slideshow stopped';
        },

        pause() {
            if (Slideshow.interval && Slideshow.isActive) {
                clearInterval(Slideshow.interval);
                Slideshow.interval = null;
                Slideshow.updateIndicator(true);
                Slideshow.syncControls();
            }
        },

        resume() {
            if (!Slideshow.interval && Slideshow.isActive) {
                Slideshow.interval = setInterval(() => Gallery.nextImage(), Slideshow.delay);
                Slideshow.updateIndicator(false);
                Slideshow.syncControls();
            }
        },

        toggle() {
            Slideshow.isActive ? Slideshow.stop() : Slideshow.start();
        },

        handleButton() {
            if (!Slideshow.isActive) Slideshow.start();
            else if (Slideshow.interval) Slideshow.pause();
            else Slideshow.resume();
        },

        syncControls() {
            if (!galleryOverlay) return;
            const btn = DOM.$('#slideshow-btn', galleryOverlay);
            if (!btn) return;

            if (!Slideshow.isActive) {
                btn.innerHTML = '▶';
                btn.setAttribute('title', 'Start Slideshow (Space)');
                btn.classList.remove('slideshow-running', 'slideshow-paused');
            } else if (Slideshow.interval) {
                btn.innerHTML = '❚❚';
                btn.setAttribute('title', 'Pause Slideshow');
                btn.classList.add('slideshow-running');
                btn.classList.remove('slideshow-paused');
            } else {
                btn.innerHTML = '▶';
                btn.setAttribute('title', 'Resume Slideshow');
                btn.classList.add('slideshow-paused');
                btn.classList.remove('slideshow-running');
            }
        },

        showIndicator() {
            if (!galleryOverlay || DOM.$('.ug-slideshow-indicator', galleryOverlay)) return;
            const toolbar = DOM.$(`.${CSS.GALLERY.TOOLBAR}`, galleryOverlay);
            if (!toolbar) return;

            const indicator = DOM.create('div', { className: 'ug-slideshow-indicator' }, [
                DOM.create('span', { className: 'ug-slideshow-icon', text: '▶' }),
                DOM.create('span', { className: 'ug-slideshow-text', text: 'Slideshow' }),
                DOM.create('button', {
                    className: 'ug-slideshow-stop',
                    title: 'Stop slideshow',
                    text: '✕',
                    onclick: (e) => {
                        e.stopPropagation();
                        Slideshow.stop();
                    }
                })
            ]);
            toolbar.appendChild(indicator);
        },

        hideIndicator() {
            DOM.$('.ug-slideshow-indicator', galleryOverlay)?.remove();
        },

        updateIndicator(isPaused) {
            const indicator = DOM.$('.ug-slideshow-indicator', galleryOverlay);
            if (!indicator) return;
            const icon = DOM.$('.ug-slideshow-icon', indicator);
            if (isPaused) {
                if (icon) icon.textContent = '❚❚';
                indicator.classList.add('paused');
            } else {
                if (icon) icon.textContent = '▶';
                indicator.classList.remove('paused');
            }
        },

        setDelay(delay) {
            Slideshow.delay = delay;
            SettingsManager.saveSetting('slideshowDelay', delay);
            if (Slideshow.isActive) {
                Slideshow.stop();
                Slideshow.start();
            }
        }
    };

    // ====================================================
    // Error Handler Module
    // ====================================================
    const ErrorHandler = {
        retryAttempts: new Map(),

        async handleImageError(error, url, element = null, context = {}) {
            const retryCount = ErrorHandler.retryAttempts.get(url) || 0;
            console.error(`Image load error (${retryCount + 1}/${CONFIG.MAX_RETRIES}):`, error, url);

            if (retryCount < CONFIG.MAX_RETRIES) {
                ErrorHandler.retryAttempts.set(url, retryCount + 1);
                const delay = Math.pow(2, retryCount) * 1000;

                if (retryCount === 0) {
                    state.notificationType = 'warning';
                    state.notification = `Retrying failed image... (${retryCount + 1}/${CONFIG.MAX_RETRIES})`;
                }

                setTimeout(async () => {
                    try {
                        if (element) element.classList.add('retrying');
                        const blob = await ImageLoader.fetchWithRetry(url, state.currentLoadSessionId);

                        if (blob && element) {
                            const blobUrl = BlobManager.createUrl(blob);
                            element.src = blobUrl;
                            element.classList.remove('error', 'retrying');
                            ErrorHandler.retryAttempts.delete(url);

                            state.notificationType = 'success';
                            state.notification = 'Image loaded successfully';
                        }
                    } catch (retryError) {
                        ErrorHandler.handleImageError(retryError, url, element, context);
                    }
                }, delay);
            } else {
                ErrorHandler.showErrorPlaceholder(element, url, context);
                ErrorHandler.retryAttempts.delete(url);

                state.notificationType = 'error';
                state.notification = `Failed to load image after ${CONFIG.MAX_RETRIES} attempts`;
            }
        },

        showErrorPlaceholder(element, url, context) {
            if (!element) return;

            const errorContainer = DOM.create('div', { className: 'ug-error-container' }, [
                DOM.create('div', {
                    className: 'ug-error-icon',
                    html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>'
                }),
                DOM.create('div', { className: 'ug-error-message', text: 'Failed to load image' }),
                DOM.create('button', {
                    className: 'ug-error-retry',
                    text: 'Retry',
                    title: 'Retry loading',
                    onclick: () => {
                        if (errorContainer.parentNode) {
                            errorContainer.parentNode.replaceChild(element, errorContainer);
                        }
                        element.classList.add('loading');
                        ErrorHandler.retryAttempts.delete(url);

                        ImageLoader.loadImageAndApplyToPage(
                            context.linkElement,
                            context.galleryIndex,
                            context.posterHref,
                            context.isUniqueForGallery,
                            state.currentLoadSessionId,
                            context.itemData
                        );
                    }
                })
            ]);

            if (element.parentNode) {
                element.parentNode.replaceChild(errorContainer, element);
            }
        },

        clearRetries() {
            ErrorHandler.retryAttempts.clear();
        }
    };

    // ====================================================
    // Settings Manager
    // ====================================================
    const SettingsManager = {
        defaultSettings: {
            galleryKey: 'g',
            prevImageKey: 'k',
            nextImageKey: 'l',
            zoomEnabled: true,
            animationsEnabled: true,
            notificationsEnabled: true,
            notificationPosition: 'bottom',
            bottomStripeVisible: true,
            hideNavArrows: false,

            // Post-action bar (global) visibility
            hideGlobalGalleryButton: false,
            hideGlobalHeightButton: false,
            hideGlobalWidthButton: false,
            hideGlobalFullButton: false,
            hideGlobalDownloadAllButton: false,
            hideGlobalDownloadImagesButton: false,
            hideGlobalDownloadAttachmentsButton: false,

            // Per-thumbnail button visibility
            hideThumbnailHeightButton: false,
            hideThumbnailWidthButton: false,
            hideThumbnailFullButton: false,
            hideThumbnailDownloadButton: false,

            enablePersistentCaching: true,
            preserveFileDates: true,
            slideshowDelay: CONFIG.SLIDESHOW_DELAY,
            slideshowPauseOnHover: true,
            inertiaEnabled: true,
            maxZoomScale: 5,
            zipFileNameFormat: '{date_published}-{title}-{artistName}.zip',
            imageFileNameFormat: '{date_published}-{title}-{artistName}-{fileName}-{index}',
            autoLoadOriginals: true,
            downloadBtnText: '【DOWNLOAD】',
            downloadAllBtnText: '【DL ALL】',
            downloadImagesBtnText: '【DL IMAGES】',
            downloadAttachmentsBtnText: '【DL FILES】',
            fullBtnText: '【FULL】',
            heightBtnText: '【FILL HEIGHT】',
            widthBtnText: '【FILL WIDTH】',
            galleryBtnText: '【GALLERY】',
            sortMode: 'dom',
            currentResizeMode: 'height',
            fullscreenMode: 'native'
        },

        saveSetting: (key, value) => {
            try {
                GM_setValue(key, JSON.stringify(value));
                return true;
            } catch (error) {
                console.error('Failed to save setting:', key, error);
                return false;
            }
        },

        loadSetting: (key, defaultValue = null) => {
            try {
                const value = GM_getValue(key);
                if (value === undefined) return defaultValue;
                try {
                    return JSON.parse(value);
                } catch {
                    return value;
                }
            } catch (error) {
                console.error('Failed to load setting:', key, error);
                return defaultValue;
            }
        },

        loadAllSettings: () => {
            const settings = {};
            Object.keys(SettingsManager.defaultSettings).forEach(key => {
                settings[key] = SettingsManager.loadSetting(key, SettingsManager.defaultSettings[key]);
            });
            return settings;
        },

        saveAllSettings: (settings) => {
            let success = true;
            Object.keys(settings).forEach(key => {
                if (!SettingsManager.saveSetting(key, settings[key])) success = false;
            });
            return success;
        },

        resetToDefaults: () => SettingsManager.saveAllSettings(SettingsManager.defaultSettings),

        exportSettings: () => JSON.stringify(SettingsManager.loadAllSettings(), null, 2),

        sanitizeSetting: (key, value) => {
            const def = SettingsManager.defaultSettings[key];
            switch (key) {
                case 'slideshowDelay': {
                    const n = parseInt(value, 10);
                    return (Number.isFinite(n) && n >= 500) ? n : CONFIG.SLIDESHOW_DELAY;
                }
                case 'maxZoomScale': {
                    const n = parseFloat(value);
                    return (Number.isFinite(n) && n >= 1) ? n : 5;
                }
                case 'galleryKey':
                case 'prevImageKey':
                case 'nextImageKey':
                    return (typeof value === 'string' && value.length === 1) ? value : def;
                case 'notificationPosition':
                    return ['top', 'bottom'].includes(value) ? value : 'bottom';
                case 'fullscreenMode':
                    return ['native', 'css', 'ask'].includes(value) ? value : 'native';
                case 'sortMode':
                    return ['dom', 'filename-asc', 'filename-desc'].includes(value) ? value : 'dom';
                case 'zipFileNameFormat':
                case 'imageFileNameFormat':
                    return (typeof value === 'string' && value.trim()) ? value : def;
                default:
                    if (typeof def === 'boolean') return Boolean(value);
                    if (typeof def === 'string') return (typeof value === 'string') ? value : def;
                    return value;
            }
        },

        importSettings: (settingsJson) => {
            try {
                const settings = JSON.parse(settingsJson);
                const validatedSettings = {};

                Object.keys(SettingsManager.defaultSettings).forEach(key => {
                    const incoming = Object.prototype.hasOwnProperty.call(settings, key)
                        ? settings[key]
                        : SettingsManager.defaultSettings[key];
                    validatedSettings[key] = SettingsManager.sanitizeSetting(key, incoming);
                });

                if (SettingsManager.saveAllSettings(validatedSettings)) {
                    Object.assign(state, validatedSettings);
                    state.notificationType = 'success';
                    state.notification = 'Settings imported successfully';
                    return true;
                }
            } catch (error) {
                console.error('Failed to import settings:', error);
                state.notificationType = 'error';
                state.notification = 'Failed to import settings: Invalid format';
            }
            return false;
        }
    };

    // ====================================================
    // Accessibility Module
    // ====================================================
    const Accessibility = {
        init() {
            if (galleryOverlay) {
                galleryOverlay.setAttribute('role', 'dialog');
                galleryOverlay.setAttribute('aria-modal', 'true');
                galleryOverlay.setAttribute('aria-label', 'Image Gallery');
            }

            if (!DOM.$('.ug-sr-only')) {
                const liveRegion = DOM.create('div', {
                    className: 'ug-sr-only',
                    'aria-live': 'polite',
                    'aria-atomic': 'true'
                });
                document.body.appendChild(liveRegion);
            }
        },

        announce(message) {
            const sr = DOM.$('.ug-sr-only');
            if (!sr) return;
            sr.textContent = '';
            requestAnimationFrame(() => { sr.textContent = message; });
        }
    };

    // ====================================================
    // Blob Manager
    // ====================================================
    const BlobManager = {
        blobUrls: new Set(),

        createUrl(blob) {
            if (!blob) return '';
            const url = URL.createObjectURL(blob);
            BlobManager.blobUrls.add(url);
            return url;
        },

        revokeUrl(url) {
            if (typeof url === 'string' && url.startsWith('blob:')) {
                try {
                    URL.revokeObjectURL(url);
                    BlobManager.blobUrls.delete(url);
                } catch {
                    // Ignore revoke errors
                }
            }
        },

        revokeAll() {
            BlobManager.blobUrls.forEach(url => {
                try {
                    URL.revokeObjectURL(url);
                } catch {
                    // Ignore revoke errors
                }
            });
            BlobManager.blobUrls.clear();
        }
    };

    // ====================================================
    // State Manager & Reactive State
    // ====================================================
    const StateManager = {
        generateSessionId: () => crypto.randomUUID?.() || Date.now().toString(36) + Math.random().toString(36).slice(2),

        withSessionCheck(callback) {
            return (value, oldValue) => {
                if (state.currentLoadSessionId === null) return;
                callback(value, oldValue);
            };
        },

        createReactiveState(initialState, updateCallbacks = {}) {
            return new Proxy(initialState, {
                set(target, key, value) {
                    const oldValue = target[key];
                    target[key] = value;
                    if (updateCallbacks[key]) {
                        try {
                            updateCallbacks[key](value, oldValue);
                        } catch (e) {
                            console.error(`Error in state callback for "${key}":`, e);
                        }
                    }
                    return true;
                }
            });
        }
    };

    const state = StateManager.createReactiveState({
        zipFileNameFormat: SettingsManager.loadSetting('zipFileNameFormat', '{date_published}-{title}-{artistName}.zip'),
        imageFileNameFormat: SettingsManager.loadSetting('imageFileNameFormat', '{date_published}-{title}-{artistName}-{fileName}-{index}'),
        galleryKey: SettingsManager.loadSetting('galleryKey', 'g'),
        galleryReady: false,
        currentGalleryIndex: 0,
        currentResizeMode: SettingsManager.loadSetting('currentResizeMode', 'height'),
        isFullscreen: false,
        currentPostUrl: null,
        maxZoomScale: SettingsManager.loadSetting('maxZoomScale', 5),
        originalImageSrcs: [],
        fullSizeImageSrcs: [],
        totalImages: 0,
        loadedImages: 0,
        isLoading: false,
        isGalleryMode: false,
        isDownloading: false,
        errorCount: 0,
        currentLoadSessionId: null,
        notificationsEnabled: SettingsManager.loadSetting('notificationsEnabled', true),
        notificationPosition: SettingsManager.loadSetting('notificationPosition', 'bottom'),
        animationsEnabled: SettingsManager.loadSetting('animationsEnabled', true),
        enablePersistentCaching: SettingsManager.loadSetting('enablePersistentCaching', true),
        preserveFileDates: SettingsManager.loadSetting('preserveFileDates', true),
        notification: null,
        notificationType: 'info',
        hideNavArrows: SettingsManager.loadSetting('hideNavArrows', false),

        // Global post-action button visibility
        hideGlobalGalleryButton: SettingsManager.loadSetting('hideGlobalGalleryButton', false),
        hideGlobalHeightButton: SettingsManager.loadSetting('hideGlobalHeightButton', false),
        hideGlobalWidthButton: SettingsManager.loadSetting('hideGlobalWidthButton', false),
        hideGlobalFullButton: SettingsManager.loadSetting('hideGlobalFullButton', false),
        hideGlobalDownloadAllButton: SettingsManager.loadSetting('hideGlobalDownloadAllButton', false),
        hideGlobalDownloadImagesButton: SettingsManager.loadSetting('hideGlobalDownloadImagesButton', false),
        hideGlobalDownloadAttachmentsButton: SettingsManager.loadSetting('hideGlobalDownloadAttachmentsButton', false),

        // Thumbnail button visibility
        hideThumbnailHeightButton: SettingsManager.loadSetting('hideThumbnailHeightButton', false),
        hideThumbnailWidthButton: SettingsManager.loadSetting('hideThumbnailWidthButton', false),
        hideThumbnailFullButton: SettingsManager.loadSetting('hideThumbnailFullButton', false),
        hideThumbnailDownloadButton: SettingsManager.loadSetting('hideThumbnailDownloadButton', false),

        settingsOpen: false,
        prevImageKey: SettingsManager.loadSetting('prevImageKey', 'k'),
        nextImageKey: SettingsManager.loadSetting('nextImageKey', 'l'),
        bottomStripeVisible: SettingsManager.loadSetting('bottomStripeVisible', true),
        zoomEnabled: SettingsManager.loadSetting('zoomEnabled', true),
        controlsVisible: true,
        isDragging: false,
        lastTapTime: 0,
        zoomIndicatorVisible: true,
        inertiaEnabled: SettingsManager.loadSetting('inertiaEnabled', true),
        isSlideshowActive: false,
        autoLoadOriginals: SettingsManager.loadSetting('autoLoadOriginals', true),
        downloadBtnText: SettingsManager.loadSetting('downloadBtnText', '【DOWNLOAD】'),
        downloadAllBtnText: SettingsManager.loadSetting('downloadAllBtnText', '【DL ALL】'),
        downloadImagesBtnText: SettingsManager.loadSetting('downloadImagesBtnText', '【DL IMAGES】'),
        downloadAttachmentsBtnText: SettingsManager.loadSetting('downloadAttachmentsBtnText', '【DL FILES】'),
        fullBtnText: SettingsManager.loadSetting('fullBtnText', '【FULL】'),
        heightBtnText: SettingsManager.loadSetting('heightBtnText', '【FILL HEIGHT】'),
        widthBtnText: SettingsManager.loadSetting('widthBtnText', '【FILL WIDTH】'),
        galleryBtnText: SettingsManager.loadSetting('galleryBtnText', '【GALLERY】'),
        slideshowDelay: SettingsManager.loadSetting('slideshowDelay', CONFIG.SLIDESHOW_DELAY),
        slideshowPauseOnHover: SettingsManager.loadSetting('slideshowPauseOnHover', true),
        fullscreenMode: SettingsManager.loadSetting('fullscreenMode', 'native'),
        sortMode: SettingsManager.loadSetting('sortMode', 'dom')
    }, {
        controlsVisible(value) {
            if (galleryOverlay) {
                const toolbar = DOM.$(`.${CSS.GALLERY.TOOLBAR}`, galleryOverlay);
                const expandedView = DOM.$(`.${CSS.GALLERY.EXPANDED_VIEW}`, galleryOverlay);
                toolbar?.classList.toggle(CSS.GALLERY.CONTROLS_HIDDEN, !value);
                expandedView?.classList.toggle(CSS.GALLERY.CONTROLS_HIDDEN, !value);
            }
        },

        galleryReady(value) {
            updateGalleryButton(value);
        },

        loadedImages: StateManager.withSessionCheck((value) => {
            if (value === state.totalImages && state.totalImages > 0) {
                state.notificationType = 'success';
                state.notification = `Media Done Loading! Total: ${state.totalImages}`;
            } else if (state.totalImages > 0) {
                notifyLoadProgress(`Loading media (${value}/${state.totalImages})...`, 'info');
            }
        }),

        totalImages: StateManager.withSessionCheck((value) => {
            if (value > 0) {
                notifyLoadProgress(`Loading media (${state.loadedImages}/${value})...`, 'info');
            }
        }),

        notification(value) {
            if (value) UI.showNotification(value, state.notificationType);
            else UI.hideNotification();
        },

        notificationType(value) {
            void value;
        },

        settingsOpen(value) {
            value ? UI.showSettings() : UI.closeSettings();
        },

        isFullscreen(value) {
            if (value) {
                document.body.classList.add('ug-fullscreen');
                galleryOverlay?.classList.add(CSS.GALLERY.FULLSCREEN_OVERLAY);
            } else {
                document.body.classList.remove('ug-fullscreen');
                galleryOverlay?.classList.remove(CSS.GALLERY.FULLSCREEN_OVERLAY);
            }
            if (galleryOverlay) {
                const btn = DOM.$(`.${CSS.GALLERY.FULLSCREEN}`, galleryOverlay);
                if (btn) btn.classList.toggle('active', Boolean(value));
            }
        },

        zoomEnabled(value) {
            SettingsManager.saveSetting('zoomEnabled', value);
        },

        animationsEnabled(value) {
            SettingsManager.saveSetting('animationsEnabled', value);
            document.body.classList.toggle('ug-animations-disabled', !value);
        },

        bottomStripeVisible(value) {
            SettingsManager.saveSetting('bottomStripeVisible', value);
            if (galleryOverlay) {
                const stripContainer = DOM.$(`.${CSS.GALLERY.STRIP_CONTAINER}`, galleryOverlay);
                if (stripContainer) stripContainer.style.display = value ? 'flex' : 'none';
            }
        },

        isDragging(value) {
            if (galleryOverlay) {
                const container = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
                container?.classList.toggle(CSS.GALLERY.GRABBING, value);
            }
        },

        notificationPosition(value) {
            SettingsManager.saveSetting('notificationPosition', value);
            const notifArea = document.getElementById(CSS.NOTIF_AREA);
            if (notifArea) {
                notifArea.style.top = value === 'top' ? '10px' : 'auto';
                notifArea.style.bottom = value === 'bottom' ? '10px' : 'auto';
            }
        },

        enablePersistentCaching(value) {
            SettingsManager.saveSetting('enablePersistentCaching', value);
            if (value) ImageCacheDB.init();
        },

        currentResizeMode(value) {
            SettingsManager.saveSetting('currentResizeMode', value);
        }
    });

    function updateButtonLabels() {
        BUTTONS.DOWNLOAD = state.downloadBtnText || '【DOWNLOAD】';
        BUTTONS.DOWNLOAD_ALL = state.downloadAllBtnText || '【DL ALL】';
        BUTTONS.DOWNLOAD_IMAGES = state.downloadImagesBtnText || '【DL IMAGES】';
        BUTTONS.DOWNLOAD_ATTACHMENTS = state.downloadAttachmentsBtnText || '【DL FILES】';
        BUTTONS.FULL = state.fullBtnText || '【FULL】';
        BUTTONS.HEIGHT = state.heightBtnText || '【FILL HEIGHT】';
        BUTTONS.WIDTH = state.widthBtnText || '【FILL WIDTH】';
        BUTTONS.GALLERY = state.galleryBtnText || '【GALLERY】';

        const labels = {
            DOWNLOAD: BUTTONS.DOWNLOAD,
            DOWNLOAD_ALL: BUTTONS.DOWNLOAD_ALL,
            DOWNLOAD_IMAGES: BUTTONS.DOWNLOAD_IMAGES,
            DOWNLOAD_ATTACHMENTS: BUTTONS.DOWNLOAD_ATTACHMENTS,
            FULL: BUTTONS.FULL,
            HEIGHT: BUTTONS.HEIGHT,
            WIDTH: BUTTONS.WIDTH,
            GALLERY: BUTTONS.GALLERY
        };

        DOM.$$(`.${CSS.BTN}[data-action]`).forEach(btn => {
            const label = labels[btn.dataset.action];
            if (label) btn.textContent = label;
        });

        updateGalleryButton(state.galleryReady);
        PostActions.updateButtonVisibilityLight();
    }

    // ====================================================
    // Zoom Module
    // ====================================================
    const Zoom = {
        _applyTransition(element, action) {
            element.classList.add(CSS.GALLERY.IS_TRANSITIONING);
            action();
            let cleared = false;
            const clear = () => {
                if (!cleared) {
                    cleared = true;
                    element.classList.remove(CSS.GALLERY.IS_TRANSITIONING);
                }
            };
            element.addEventListener('transitionend', clear, { once: true });
            setTimeout(clear, 600);
        },

        applyZoom() {
            if (!galleryOverlay) return;
            const container = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
            if (!container) return;

            PointerEngine.updateTransform();
            const zoomDisplay = DOM.$('#zoom-level', galleryOverlay);
            if (zoomDisplay) {
                zoomDisplay.textContent = `${Math.round(viewState.zoomScale * 100)}%`;
            }
            container.classList.toggle(CSS.GALLERY.ZOOMED, viewState.zoomScale !== 1);
        },

        handleWheelZoom(event) {
            if (!state.zoomEnabled || !galleryOverlay) return;

            event.preventDefault();
            event.stopPropagation();

            const container = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
            if (!container) return;

            const rect = container.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) return;

            const mouseX = event.clientX - rect.left;
            const mouseY = event.clientY - rect.top;
            const delta = event.deltaY;

            const zoomFactor = delta > 0 ? (1 - CONFIG.ZOOM_STEP) : (1 + CONFIG.ZOOM_STEP);
            const newScale = Math.max(CONFIG.MIN_SCALE, Math.min(viewState.zoomScale * zoomFactor, state.maxZoomScale));

            if (newScale === viewState.zoomScale) return;

            const imageXUnderPointer = (mouseX - viewState.imageOffset.x) / viewState.zoomScale;
            const imageYUnderPointer = (mouseY - viewState.imageOffset.y) / viewState.zoomScale;

            viewState.imageOffset.x = mouseX - (imageXUnderPointer * newScale);
            viewState.imageOffset.y = mouseY - (imageYUnderPointer * newScale);
            viewState.zoomScale = newScale;

            Zoom.applyZoom();
        },

        resetZoom() {
            if (!galleryOverlay) return;
            const container = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
            if (container) {
                Zoom._applyTransition(container, () => {
                    viewState.zoomScale = 1;
                    viewState.imageOffset = { x: 0, y: 0 };
                    Zoom.applyZoom();
                });
            }
        },

        zoom(step) {
            if (!galleryOverlay) return;
            const container = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
            if (!container) return;

            const rect = container.getBoundingClientRect();
            const centerX = rect.width / 2;
            const centerY = rect.height / 2;

            const newScale = Math.max(CONFIG.MIN_SCALE, Math.min(viewState.zoomScale + step, state.maxZoomScale));

            if (viewState.zoomScale !== newScale) {
                const imageX = (centerX - viewState.imageOffset.x) / viewState.zoomScale;
                const imageY = (centerY - viewState.imageOffset.y) / viewState.zoomScale;

                const newOffsetX = centerX - (imageX * newScale);
                const newOffsetY = centerY - (imageY * newScale);

                Zoom._applyTransition(container, () => {
                    viewState.imageOffset.x = newOffsetX;
                    viewState.imageOffset.y = newOffsetY;
                    viewState.zoomScale = newScale;
                    Zoom.applyZoom();
                });
            }
        }
    };

    // ====================================================
    // Thumbnail Strip Module
    // ====================================================
    const ThumbnailStrip = {
        _contextMenuTimeout: null,
        _abortController: null,

        init() {
            if (!galleryOverlay) return;
            const strip = DOM.$('.ug-thumbnail-strip', galleryOverlay);
            if (!strip) return;

            ThumbnailStrip._abortController?.abort();
            ThumbnailStrip._abortController = new AbortController();
            const signal = ThumbnailStrip._abortController.signal;

            ThumbnailStrip.updateScrollIndicators();
            ThumbnailStrip.setupKeyboardNavigation();
            ThumbnailStrip.setupDragNavigation(signal);
            ThumbnailStrip.setupHoverPreview(signal);
            ThumbnailStrip.setupContextMenu(signal);

            strip.addEventListener('scroll', Utils.throttle(ThumbnailStrip.updateScrollIndicators, 100), { signal, passive: true });
        },

        cleanup() {
            ThumbnailStrip._abortController?.abort();
            ThumbnailStrip._abortController = null;
            ThumbnailStrip.hideContextMenu();
            DOM.$('.ug-thumbnail-zoom-preview')?.remove();
            DOM.$('.ug-slideshow-indicator', galleryOverlay)?.remove();
        },

        updateScrollIndicators() {
            if (!galleryOverlay) return;
            const strip = DOM.$('.ug-thumbnail-strip', galleryOverlay);
            if (!strip) return;
            const hasScroll = strip.scrollWidth > strip.clientWidth;
            strip.classList.toggle('no-scroll', !hasScroll);
        },

        setupKeyboardNavigation() {
            const strip = DOM.$('.ug-thumbnail-strip', galleryOverlay);
            if (!strip) return;

            strip.onkeydown = (e) => {
                const focused = e.target.closest(`.${CSS.GALLERY.THUMBNAIL_WRAPPER}`);
                if (!focused) return;
                switch (e.key) {
                    case 'ArrowLeft':
                        e.preventDefault();
                        ThumbnailStrip.navigateThumbnails('prev');
                        break;
                    case 'ArrowRight':
                        e.preventDefault();
                        ThumbnailStrip.navigateThumbnails('next');
                        break;
                    case 'Enter':
                    case ' ':
                        e.preventDefault();
                        Gallery.showExpandedView(parseInt(focused.dataset.index, 10));
                        break;
                }
            };
        },

        navigateThumbnails(direction) {
            const strip = DOM.$('.ug-thumbnail-strip', galleryOverlay);
            const current = DOM.$(`.${CSS.GALLERY.THUMBNAIL_WRAPPER}.selected`, strip);
            const currentIndex = current ? parseInt(current.dataset.index, 10) : state.currentGalleryIndex;
            const total = state.fullSizeImageSrcs.length;
            if (!total) return;

            const target = Math.max(0, Math.min(total - 1, currentIndex + (direction === 'next' ? 1 : -1)));
            const targetEl = DOM.$(`[data-index="${target}"]`, strip);
            if (targetEl) {
                targetEl.focus();
                DOM.scrollToCenter(strip, targetEl);
            } else {
                Gallery.showExpandedView(target);
            }
        },

        setupDragNavigation(signal) {
            const strip = DOM.$('.ug-thumbnail-strip', galleryOverlay);
            if (!strip) return;

            let isDragging = false;
            let startX = 0;
            let scrollLeft = 0;

            strip.addEventListener('mousedown', (e) => {
                if (e.button !== 0 || e.target.closest(`.${CSS.GALLERY.THUMBNAIL_WRAPPER}`)) return;
                isDragging = true;
                startX = e.pageX - strip.offsetLeft;
                scrollLeft = strip.scrollLeft;
                strip.style.cursor = 'grabbing';
                strip.classList.add('ug-dragging');
            }, { signal });

            window.addEventListener('mousemove', (e) => {
                if (!isDragging || !strip.isConnected) return;
                e.preventDefault();
                const x = e.pageX - strip.offsetLeft;
                strip.scrollLeft = scrollLeft - (x - startX) * 2;
            }, { signal });

            window.addEventListener('mouseup', () => {
                if (isDragging) {
                    isDragging = false;
                    strip.style.cursor = '';
                    strip.classList.remove('ug-dragging');
                }
            }, { signal });
        },

        setupHoverPreview(signal) {
            const strip = DOM.$('.ug-thumbnail-strip', galleryOverlay);
            if (!strip) return;

            let previewTimeout;
            strip.addEventListener('mouseover', (e) => {
                const thumb = e.target.closest(`.${CSS.GALLERY.THUMBNAIL_WRAPPER}`);
                if (!thumb) return;
                clearTimeout(previewTimeout);
                previewTimeout = setTimeout(() => {
                    const index = parseInt(thumb.dataset.index, 10);
                    ThumbnailStrip.showZoomPreview(thumb, index);
                }, 500);
            }, { signal });

            strip.addEventListener('mouseout', (e) => {
                if (e.target.closest(`.${CSS.GALLERY.THUMBNAIL_WRAPPER}`)) {
                    clearTimeout(previewTimeout);
                    ThumbnailStrip.hideZoomPreview();
                }
            }, { signal });
        },

        showZoomPreview(thumb, index) {
            const mediaItem = state.fullSizeImageSrcs[index];
            if (!mediaItem) return;

            DOM.$('.ug-thumbnail-zoom-preview')?.remove();

            if (mediaItem.type === 'attachment') return;

            const thumbImg = thumb.querySelector('img');
            const src = mediaItem.type === 'video' ? (mediaItem.poster || thumbImg?.src) : (thumbImg?.src || mediaItem.src);
            if (!src) return;

            const preview = DOM.create('div', { className: 'ug-thumbnail-zoom-preview' }, [
                DOM.create('img', { src })
            ]);
            document.body.appendChild(preview);

            const rect = thumb.getBoundingClientRect();
            const previewWidth = 220;
            const left = Math.min(
                Math.max(rect.left + rect.width / 2, previewWidth / 2 + 8),
                window.innerWidth - previewWidth / 2 - 8
            );

            let top;
            let transform;
            if (rect.top > 240) {
                top = rect.top - 10;
                transform = 'translate(-50%, -100%)';
            } else {
                top = rect.bottom + 10;
                transform = 'translate(-50%, 0)';
            }

            Object.assign(preview.style, {
                position: 'fixed',
                left: `${left}px`,
                top: `${top}px`,
                bottom: 'auto',
                transform,
                zIndex: '10000'
            });

            setTimeout(() => preview.classList.add('show'), 10);
        },

        hideZoomPreview() {
            const preview = DOM.$('.ug-thumbnail-zoom-preview');
            if (!preview) return;
            preview.classList.remove('show');
            setTimeout(() => preview.remove(), 300);
        },

        setupContextMenu(signal) {
            const strip = DOM.$('.ug-thumbnail-strip', galleryOverlay);
            if (!strip) return;

            strip.addEventListener('contextmenu', (e) => {
                const thumb = e.target.closest(`.${CSS.GALLERY.THUMBNAIL_WRAPPER}`);
                if (!thumb) return;
                e.preventDefault();
                ThumbnailStrip.showContextMenu(thumb, parseInt(thumb.dataset.index, 10), e.pageX, e.pageY);
            }, { signal });

            window.addEventListener('click', ThumbnailStrip.hideContextMenu, { signal });
        },

        showContextMenu(thumb, index, x, y) {
            if (ThumbnailStrip._contextMenuTimeout) {
                clearTimeout(ThumbnailStrip._contextMenuTimeout);
                ThumbnailStrip._contextMenuTimeout = null;
            }
            DOM.$('.ug-thumbnail-context-menu')?.remove();

            const menuItems = [
                { text: 'Open Image', action: () => Gallery.showExpandedView(index) },
                { text: 'Download File', action: () => DownloadManager.downloadImageByIndex(index) },
                { text: 'Copy URL', action: () => ThumbnailStrip.copyImageUrl(index) },
                { text: 'Remove from Gallery', action: () => ThumbnailStrip.removeFromGallery(index), danger: true }
            ];

            const menu = DOM.create('div', { className: 'ug-thumbnail-context-menu' },
                menuItems.map(item => DOM.create('button', {
                    className: `ug-thumbnail-context-menu-item${item.danger ? ' danger' : ''}`,
                    text: item.text,
                    onclick: (e) => {
                        e.stopPropagation();
                        item.action();
                        ThumbnailStrip.hideContextMenu();
                    }
                }))
            );

            menu.style.left = `${Math.min(x, window.innerWidth - 170)}px`;
            menu.style.top = `${Math.min(y - 10, window.innerHeight - 200)}px`;
            document.body.appendChild(menu);

            setTimeout(() => menu.classList.add('show'), 10);
        },

        hideContextMenu() {
            const menu = DOM.$('.ug-thumbnail-context-menu');
            if (!menu) return;
            menu.classList.remove('show');
            if (ThumbnailStrip._contextMenuTimeout) clearTimeout(ThumbnailStrip._contextMenuTimeout);
            ThumbnailStrip._contextMenuTimeout = setTimeout(() => menu.remove(), CONFIG.CONTEXT_MENU_HIDE_DELAY);
        },

        copyImageUrl(index) {
            const mediaItem = state.fullSizeImageSrcs[index];
            if (!mediaItem) return;
            navigator.clipboard.writeText(mediaItem.src).then(() => {
                state.notificationType = 'success';
                state.notification = 'URL copied to clipboard';
            }).catch(err => {
                console.error('Failed to copy URL:', err);
                state.notificationType = 'error';
                state.notification = 'Failed to copy URL';
            });
        },

        async removeFromGallery(index) {
            const doRemove = () => {
                state.fullSizeImageSrcs.splice(index, 1);
                state.originalImageSrcs.splice(index, 1);
                Gallery._clearPreloadCache();

                if (state.fullSizeImageSrcs.length === 0) {
                    Gallery.closeGallery();
                    return;
                }

                const strip = DOM.$(`.${CSS.GALLERY.THUMBNAIL_STRIP}`, galleryOverlay);
                const thumbToRemove = strip?.querySelector(`[data-index="${index}"]`);
                thumbToRemove?.remove();

                if (strip) {
                    DOM.$$(`.${CSS.GALLERY.THUMBNAIL_WRAPPER}`, strip).forEach(w => {
                        const i = parseInt(w.dataset.index, 10);
                        if (i > index) w.dataset.index = String(i - 1);
                    });
                }

                if (index < state.currentGalleryIndex) state.currentGalleryIndex--;
                if (state.currentGalleryIndex >= state.fullSizeImageSrcs.length) {
                    state.currentGalleryIndex = state.fullSizeImageSrcs.length - 1;
                }
                if (state.currentGalleryIndex < 0) state.currentGalleryIndex = 0;

                Gallery.showExpandedView(state.currentGalleryIndex);
                ThumbnailStrip.updateThumbnailNumbers();
                ThumbnailStrip.updateScrollIndicators();
                state.notificationType = 'info';
                state.notification = 'Item removed from gallery';
            };

            const result = await UGModal.confirm({
                title: 'Remove from gallery?',
                text: 'This only affects the current gallery view.',
                icon: 'question',
                confirmText: 'Remove'
            });

            if (result.isConfirmed) doRemove();
        },

        updateThumbnailNumbers() {
            if (!galleryOverlay) return;
            DOM.$$(`.${CSS.GALLERY.THUMBNAIL_WRAPPER}`, galleryOverlay).forEach(wrapper => {
                const index = parseInt(wrapper.dataset.index, 10);
                let numberEl = DOM.$('.ug-thumbnail-number', wrapper);
                if (!numberEl) {
                    wrapper.appendChild(DOM.create('span', { className: 'ug-thumbnail-number', text: index + 1 }));
                } else {
                    numberEl.textContent = index + 1;
                }
            });
        }
    };

    // ====================================================
    // UI Builder & Notifications
    // ====================================================
    const UI = {
        _notificationTimeoutId: null,
        _notificationHideTimeoutId: null,

        createToggleButton(name, action, disabled = false, actionName = null, scope = 'global') {
            const btn = DOM.create('button', {
                type: 'button',
                text: name,
                className: CSS.BTN,
                style: { cursor: 'pointer' },
                onclick: (e) => {
                    if (btn.classList.contains('disabled')) {
                        e.preventDefault();
                        return;
                    }
                    action(e);
                }
            });

            if (actionName) btn.dataset.action = actionName;
            if (scope) btn.dataset.scope = scope;
            if (disabled) btn.classList.add('disabled');
            return btn;
        },

        createButtonGroup(buttonsConfig) {
            const div = DOM.create('div', { className: CSS.BTN_CONTAINER });
            buttonsConfig.forEach(config => {
                let createThisButton = true;
                switch (config.name) {
                    case 'FULL': if (state.hideThumbnailFullButton) createThisButton = false; break;
                    case 'DOWNLOAD': if (state.hideThumbnailDownloadButton) createThisButton = false; break;
                    case 'HEIGHT': if (state.hideThumbnailHeightButton) createThisButton = false; break;
                    case 'WIDTH': if (state.hideThumbnailWidthButton) createThisButton = false; break;
                }
                if (!createThisButton) return;
                div.appendChild(UI.createToggleButton(config.text, config.action, false, config.name, 'thumbnail'));
            });
            return div;
        },

        createNavigationButton(direction) {
            return DOM.create('button', {
                text: direction === 'prev' ? '←' : '→',
                className: `${CSS.GALLERY.NAV} ${direction === 'prev' ? CSS.GALLERY.PREV : CSS.GALLERY.NEXT}`,
                'aria-label': direction === 'prev' ? 'Previous Image' : 'Next Image',
                onclick: direction === 'prev' ? Gallery.prevImage : Gallery.nextImage
            });
        },

        createNotificationArea() {
            const area = DOM.create('div', {
                id: CSS.NOTIF_AREA,
                className: CSS.NOTIF_AREA,
                style: {
                    top: state.notificationPosition === 'top' ? '10px' : 'auto',
                    bottom: state.notificationPosition === 'bottom' ? '10px' : 'auto'
                }
            });
            document.body.appendChild(area);
            return area;
        },

        createNotification() {
            let area = document.getElementById(CSS.NOTIF_AREA) || UI.createNotificationArea();
            const container = DOM.create('div', { id: CSS.NOTIF_CONTAINER, className: CSS.NOTIF_CONTAINER }, [
                DOM.create('div', { id: CSS.NOTIF_TEXT, className: 'ug-notification-text' }),
                DOM.create('button', {
                    id: CSS.NOTIF_CLOSE,
                    className: 'ug-notification-close',
                    text: '×',
                    onclick: () => state.notification = null
                }),
                DOM.create('a', {
                    id: CSS.NOTIF_REPORT,
                    className: 'ug-notification-report',
                    text: 'Report Issue',
                    href: 'https://github.com/TearTyr/Ultra-Galleries/issues',
                    target: '_blank'
                })
            ]);
            area.appendChild(container);
            return container;
        },

        showNotification(message, type = 'info') {
            if (!state.notificationsEnabled && !['error', 'warning'].includes(type)) return;

            let area = document.getElementById(CSS.NOTIF_AREA) || UI.createNotificationArea();
            let container = DOM.$(`.${CSS.NOTIF_CONTAINER}`, area) || UI.createNotification();

            const isAlreadyVisible = container.style.display === 'flex' && !container.classList.contains('ug-slide-out');

            if (UI._notificationTimeoutId) {
                clearTimeout(UI._notificationTimeoutId);
                UI._notificationTimeoutId = null;
            }
            if (UI._notificationHideTimeoutId) {
                clearTimeout(UI._notificationHideTimeoutId);
                UI._notificationHideTimeoutId = null;
            }

            container.classList.remove('ug-update', 'ug-slide-in', 'ug-slide-out');
            const textNode = DOM.$(`#${CSS.NOTIF_TEXT}`, container);
            if (textNode) textNode.textContent = message;
            container.className = `${CSS.NOTIF_CONTAINER} ${type}`;

            if (state.animationsEnabled) {
                if (isAlreadyVisible) {
                    container.classList.add('ug-update');
                    container.addEventListener('animationend', () => container.classList.remove('ug-update'), { once: true });
                } else {
                    container.classList.add('ug-slide-in');
                }
            }

            container.style.display = 'flex';

            if (['info', 'success'].includes(type)) {
                UI._notificationTimeoutId = setTimeout(() => {
                    state.notification = null;
                }, 5000);
            }
        },

        hideNotification() {
            const container = document.getElementById(CSS.NOTIF_CONTAINER);
            if (!container) return;

            if (UI._notificationTimeoutId) {
                clearTimeout(UI._notificationTimeoutId);
                UI._notificationTimeoutId = null;
            }
            if (UI._notificationHideTimeoutId) {
                clearTimeout(UI._notificationHideTimeoutId);
                UI._notificationHideTimeoutId = null;
            }

            if (state.animationsEnabled) {
                container.classList.add('ug-slide-out');
                container.classList.remove('ug-slide-in');
                UI._notificationHideTimeoutId = setTimeout(() => {
                    container.style.display = 'none';
                    container.classList.remove('ug-slide-out');
                    UI._notificationHideTimeoutId = null;
                }, 500);
            } else {
                container.classList.remove('ug-slide-in', 'ug-slide-out');
                container.style.display = 'none';
            }
        },

        forceHideNotification() {
            if (UI._notificationTimeoutId) clearTimeout(UI._notificationTimeoutId);
            if (UI._notificationHideTimeoutId) clearTimeout(UI._notificationHideTimeoutId);
            document.getElementById(CSS.NOTIF_CONTAINER)?.remove();
        },

        _createSettingElement(setting) {
            if (setting.type === 'header') {
                return DOM.create('h3', { className: 'ug-settings-subheader', text: setting.label });
            }
            if (setting.type === 'divider') {
                return DOM.create('hr', { className: 'ug-settings-divider' });
            }
            if (setting.type === 'description') {
                return DOM.create('p', { className: 'ug-settings-description', text: setting.label });
            }

            const div = DOM.create('div', { className: 'ug-setting-item' });
            const label = DOM.create('label', { for: setting.id, text: setting.label });

            const handleChange = (value) => {
                if (setting.stateKey) state[setting.stateKey] = value;
                if (setting.gmKey) SettingsManager.saveSetting(setting.gmKey, value);
                if (setting.onChange) setting.onChange(value);
            };

            switch (setting.type) {
                case 'checkbox': {
                    div.className = 'ug-setting-item ug-settings-checkbox-label';
                    const checkbox = DOM.create('input', {
                        type: 'checkbox',
                        id: setting.id,
                        onchange: (e) => handleChange(e.target.checked)
                    });
                    checkbox.checked = state[setting.stateKey];
                    div.append(checkbox, label);
                    break;
                }

                case 'text': {
                    div.appendChild(label);
                    const input = DOM.create('input', {
                        type: 'text',
                        id: setting.id,
                        value: state[setting.stateKey] || '',
                        maxlength: setting.maxLength || 50,
                        className: 'ug-settings-input',
                        onchange: (e) => handleChange(e.target.value)
                    });
                    div.appendChild(input);
                    break;
                }

                case 'select': {
                    div.appendChild(label);
                    const select = DOM.create('select', {
                        id: setting.id,
                        className: 'ug-settings-input',
                        onchange: (e) => handleChange(e.target.value)
                    });
                    setting.options.forEach(opt => {
                        const optEl = DOM.create('option', { value: opt.value, text: opt.text });
                        if (opt.value === state[setting.stateKey]) optEl.selected = true;
                        select.appendChild(optEl);
                    });
                    div.appendChild(select);
                    break;
                }

                case 'button':
                    return DOM.create('button', {
                        className: 'ug-button ug-settings-input',
                        text: setting.label,
                        onclick: setting.action
                    });
            }
            return div;
        },

        createSettingsUI() {
            const settingsConfig = [
                {
                    title: 'General',
                    key: 'general',
                    settings: [
                        { type: 'header', label: 'Appearance' },
                        { id: 'animationsToggle', label: 'Enable Animations', type: 'checkbox', stateKey: 'animationsEnabled', gmKey: 'animationsEnabled' },
                        { id: 'bottomStripeToggle', label: 'Show Thumbnail Strip in Gallery', type: 'checkbox', stateKey: 'bottomStripeVisible', gmKey: 'bottomStripeVisible' },
                        { type: 'divider' },
                        { type: 'header', label: 'Notifications' },
                        { id: 'notificationsEnabledToggle', label: 'Enable Notifications', type: 'checkbox', stateKey: 'notificationsEnabled', gmKey: 'notificationsEnabled' },
                        { id: 'notificationPosition', label: 'Notification Position', type: 'select', stateKey: 'notificationPosition', gmKey: 'notificationPosition',
                            options: [{ value: 'top', text: 'Top' }, { value: 'bottom', text: 'Bottom' }] },
                        { type: 'divider' },
                        { type: 'header', label: 'Fullscreen' },
                        { id: 'fullscreenMode', label: 'Fullscreen Mode', type: 'select', stateKey: 'fullscreenMode', gmKey: 'fullscreenMode',
                            options: [
                                { value: 'native', text: 'Native (browser fullscreen)' },
                                { value: 'css', text: 'CSS (styled overlay)' },
                                { value: 'ask', text: 'Ask each time' }
                            ] },
                        { type: 'divider' },
                        { type: 'header', label: 'Image Loading' },
                        { id: 'autoLoadOriginalsToggle', label: 'Auto-load Original Images', type: 'checkbox', stateKey: 'autoLoadOriginals', gmKey: 'autoLoadOriginals' }
                    ]
                },
                {
                    title: 'Gallery Viewer',
                    key: 'galleryViewer',
                    settings: [
                        { type: 'header', label: 'Navigation' },
                        { id: 'hideNavArrows', label: 'Hide Navigation Arrows in Gallery', type: 'checkbox', stateKey: 'hideNavArrows', gmKey: 'hideNavArrows', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { type: 'divider' },
                        { type: 'header', label: 'Zoom & Pan' },
                        { id: 'zoomEnabledToggle', label: 'Enable Zoom & Pan', type: 'checkbox', stateKey: 'zoomEnabled', gmKey: 'zoomEnabled' },
                        { id: 'inertiaEnabledToggle', label: 'Enable Smooth Pan Inertia', type: 'checkbox', stateKey: 'inertiaEnabled', gmKey: 'inertiaEnabled' },
                        { type: 'divider' },
                        { type: 'header', label: 'Slideshow' },
                        {
                            id: 'slideshowDelay',
                            label: 'Slideshow Delay (ms)',
                            type: 'text',
                            stateKey: 'slideshowDelay',
                            gmKey: 'slideshowDelay',
                            maxLength: 5,
                            onChange: (value) => {
                                const validDelay = parseInt(value, 10) || CONFIG.SLIDESHOW_DELAY;
                                state.slideshowDelay = validDelay;
                                Slideshow.setDelay(validDelay);
                            }
                        },
                        { id: 'slideshowPauseOnHover', label: 'Pause Slideshow on Hover', type: 'checkbox', stateKey: 'slideshowPauseOnHover', gmKey: 'slideshowPauseOnHover' }
                    ]
                },
                {
                    title: 'Downloads',
                    key: 'downloads',
                    settings: [
                        { type: 'header', label: 'Behavior' },
                        { id: 'preserveFileDatesToggle', label: 'Preserve Post Date on ZIP Files', type: 'checkbox', stateKey: 'preserveFileDates', gmKey: 'preserveFileDates' },
                        { type: 'description', label: 'ZIP entries will be stamped with the post\'s published date. Requires an archive tool that restores timestamps (7-Zip, WinRAR, Explorer).' },
                        { type: 'divider' },
                        { type: 'header', label: 'Caching' },
                        { id: 'persistentCachingToggle', label: 'Enable Persistent Image Caching', type: 'checkbox', stateKey: 'enablePersistentCaching', gmKey: 'enablePersistentCaching' },
                        { id: 'clearCacheButton', label: 'Clear Persistent Cache', type: 'button', action: () => ImageCacheDB.clear() },
                        { type: 'divider' },
                        { type: 'header', label: 'File Naming' },
                        { id: 'zipFileNameFormatInput', label: 'ZIP Filename Format', type: 'text', stateKey: 'zipFileNameFormat', gmKey: 'zipFileNameFormat', maxLength: 200 },
                        { type: 'description', label: 'Tokens: {date_published}, {date_edited}, {date_imported}, {date}, {title}, {artistName}' },
                        { id: 'imageFileNameFormatInput', label: 'Image Filename Format', type: 'text', stateKey: 'imageFileNameFormat', gmKey: 'imageFileNameFormat', maxLength: 200 },
                        { type: 'description', label: 'Tokens: same as above plus {fileName} and {index}' }
                    ]
                },
                {
                    title: 'Sorting',
                    key: 'sorting',
                    settings: [
                        { type: 'header', label: 'Media Order' },
                        { type: 'description', label: 'Fixes scrambled order — especially useful for comic posts where pages are numbered ("page_01.jpg", "page_02.jpg", …). Natural sort treats numbers as numbers, so "page_10" comes after "page_9" instead of before it.' },
                        {
                            id: 'sortModeSelect',
                            label: 'Sort Order',
                            type: 'select',
                            stateKey: 'sortMode',
                            gmKey: 'sortMode',
                            options: [
                                { value: 'dom', text: 'Default (site order)' },
                                { value: 'filename-asc', text: 'Filename: A → Z (natural)' },
                                { value: 'filename-desc', text: 'Filename: Z → A (natural)' }
                            ],
                            onChange: () => {
                                if (state.isGalleryMode) Gallery.closeGallery();
                                if (Utils.isPostPage() && !state.isLoading) {
                                    state.notificationType = 'info';
                                    state.notification = 'Applying sort order...';
                                    ImageLoader.loadImages();
                                }
                            }
                        },
                        { type: 'divider' },
                        { type: 'header', label: 'How it works' },
                        { type: 'description', label: 'Filenames are read from the post\'s download attribute, the file URL\'s ?f= parameter, or the URL itself — in that order. If none contain a usable name, items fall back to the site\'s DOM order.' }
                    ]
                },
                {
                    title: 'Post Actions',
                    key: 'postActions',
                    settings: [
                        { type: 'header', label: 'Button Visibility' },
                        { type: 'description', label: 'The bar that appears at the top of a post.' },
                        { id: 'hideGlobalGalleryButton', label: 'Hide GALLERY Button', type: 'checkbox', stateKey: 'hideGlobalGalleryButton', gmKey: 'hideGlobalGalleryButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideGlobalHeightButton', label: 'Hide FILL HEIGHT Button', type: 'checkbox', stateKey: 'hideGlobalHeightButton', gmKey: 'hideGlobalHeightButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideGlobalWidthButton', label: 'Hide FILL WIDTH Button', type: 'checkbox', stateKey: 'hideGlobalWidthButton', gmKey: 'hideGlobalWidthButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideGlobalFullButton', label: 'Hide FULL Button', type: 'checkbox', stateKey: 'hideGlobalFullButton', gmKey: 'hideGlobalFullButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideGlobalDownloadAllButton', label: 'Hide DL ALL Button', type: 'checkbox', stateKey: 'hideGlobalDownloadAllButton', gmKey: 'hideGlobalDownloadAllButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideGlobalDownloadImagesButton', label: 'Hide DL IMAGES Button', type: 'checkbox', stateKey: 'hideGlobalDownloadImagesButton', gmKey: 'hideGlobalDownloadImagesButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideGlobalDownloadAttachmentsButton', label: 'Hide DL FILES Button', type: 'checkbox', stateKey: 'hideGlobalDownloadAttachmentsButton', gmKey: 'hideGlobalDownloadAttachmentsButton', onChange: () => PostActions.updateButtonVisibilityLight() }
                    ]
                },
                {
                    title: 'Thumbnails',
                    key: 'thumbnails',
                    settings: [
                        { type: 'header', label: 'Per-Image Button Visibility' },
                        { type: 'description', label: 'The small button row that appears above each thumbnail image.' },
                        { id: 'hideThumbnailHeightButton', label: 'Hide FILL HEIGHT Button', type: 'checkbox', stateKey: 'hideThumbnailHeightButton', gmKey: 'hideThumbnailHeightButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideThumbnailWidthButton', label: 'Hide FILL WIDTH Button', type: 'checkbox', stateKey: 'hideThumbnailWidthButton', gmKey: 'hideThumbnailWidthButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideThumbnailFullButton', label: 'Hide FULL Button', type: 'checkbox', stateKey: 'hideThumbnailFullButton', gmKey: 'hideThumbnailFullButton', onChange: () => PostActions.updateButtonVisibilityLight() },
                        { id: 'hideThumbnailDownloadButton', label: 'Hide DOWNLOAD Button', type: 'checkbox', stateKey: 'hideThumbnailDownloadButton', gmKey: 'hideThumbnailDownloadButton', onChange: () => PostActions.updateButtonVisibilityLight() }
                    ]
                },
                {
                    title: 'Button Labels',
                    key: 'buttonLabels',
                    settings: [
                        { type: 'header', label: 'Download Buttons' },
                        { id: 'downloadBtnTextInput', label: 'DOWNLOAD', type: 'text', stateKey: 'downloadBtnText', gmKey: 'downloadBtnText', maxLength: 40, onChange: updateButtonLabels },
                        { id: 'downloadAllBtnTextInput', label: 'DL ALL', type: 'text', stateKey: 'downloadAllBtnText', gmKey: 'downloadAllBtnText', maxLength: 40, onChange: updateButtonLabels },
                        { id: 'downloadImagesBtnTextInput', label: 'DL IMAGES', type: 'text', stateKey: 'downloadImagesBtnText', gmKey: 'downloadImagesBtnText', maxLength: 40, onChange: updateButtonLabels },
                        { id: 'downloadAttachmentsBtnTextInput', label: 'DL FILES', type: 'text', stateKey: 'downloadAttachmentsBtnText', gmKey: 'downloadAttachmentsBtnText', maxLength: 40, onChange: updateButtonLabels },
                        { type: 'divider' },
                        { type: 'header', label: 'Sizing & View Buttons' },
                        { id: 'fullBtnTextInput', label: 'FULL', type: 'text', stateKey: 'fullBtnText', gmKey: 'fullBtnText', maxLength: 40, onChange: updateButtonLabels },
                        { id: 'heightBtnTextInput', label: 'FILL HEIGHT', type: 'text', stateKey: 'heightBtnText', gmKey: 'heightBtnText', maxLength: 40, onChange: updateButtonLabels },
                        { id: 'widthBtnTextInput', label: 'FILL WIDTH', type: 'text', stateKey: 'widthBtnText', gmKey: 'widthBtnText', maxLength: 40, onChange: updateButtonLabels },
                        { id: 'galleryBtnTextInput', label: 'GALLERY', type: 'text', stateKey: 'galleryBtnText', gmKey: 'galleryBtnText', maxLength: 40, onChange: updateButtonLabels }
                    ]
                },
                {
                    title: 'Keyboard',
                    key: 'keyboard',
                    settings: [
                        { type: 'header', label: 'Shortcuts' },
                        { type: 'description', label: 'Single letters work best. These shortcuts are disabled while typing in any input field.' },
                        { id: 'galleryKeyInput', label: 'Open / Close Gallery', type: 'text', stateKey: 'galleryKey', gmKey: 'galleryKey', maxLength: 1 },
                        { id: 'prevImageKeyInput', label: 'Previous Image', type: 'text', stateKey: 'prevImageKey', gmKey: 'prevImageKey', maxLength: 1 },
                        { id: 'nextImageKeyInput', label: 'Next Image', type: 'text', stateKey: 'nextImageKey', gmKey: 'nextImageKey', maxLength: 1 },
                        { type: 'divider' },
                        { type: 'header', label: 'Fixed Shortcuts (in Gallery)' },
                        { type: 'description', label: '← / →  Navigate   ·   + / −  Zoom   ·   0  Reset Zoom   ·   Space  Slideshow   ·   F  Fullscreen   ·   Esc  Close' }
                    ]
                },
                {
                    title: 'Advanced',
                    key: 'advanced',
                    settings: [
                        { type: 'header', label: 'Backup & Restore' },
                        {
                            id: 'exportSettingsButton',
                            label: 'Export Settings to File',
                            type: 'button',
                            action: () => {
                                const blob = new Blob([SettingsManager.exportSettings()], { type: 'application/json' });
                                DOM.saveBlob(blob, 'ultra-galleries-settings.json');
                                state.notificationType = 'success';
                                state.notification = 'Settings exported';
                            }
                        },
                        {
                            id: 'importSettingsButton',
                            label: 'Import Settings from File',
                            type: 'button',
                            action: () => {
                                const input = DOM.create('input', { type: 'file', accept: '.json' });
                                input.onchange = (e) => {
                                    const file = e.target.files[0];
                                    if (file) {
                                        const reader = new FileReader();
                                        reader.onload = (ev) => SettingsManager.importSettings(ev.target.result);
                                        reader.readAsText(file);
                                    }
                                };
                                input.click();
                            }
                        },
                        { type: 'divider' },
                        { type: 'header', label: 'Danger Zone' },
                        { type: 'description', label: 'This will erase all your customizations and reload the page.' },
                        {
                            id: 'resetSettingsButton',
                            label: 'Reset All Settings to Defaults',
                            type: 'button',
                            action: async () => {
                                const result = await UGModal.confirm({
                                    title: 'Reset settings?',
                                    text: 'All settings will return to defaults.',
                                    icon: 'warning',
                                    confirmText: 'Reset'
                                });
                                if (result.isConfirmed) {
                                    SettingsManager.resetToDefaults();
                                    location.reload();
                                }
                            }
                        }
                    ]
                }
            ];

            const headerText = DOM.create('h2', { id: 'ug-settings-main-header' });
            const body = DOM.create('div', { className: 'ug-settings-body' });
            const sidebar = DOM.create('div', { className: 'ug-sidebar-header', text: 'Settings' });
            const sidebarContainer = DOM.create('div', { className: 'ug-settings-sidebar' }, [sidebar]);

            const overlay = DOM.create('div', {
                id: 'ug-settings-overlay',
                role: 'dialog',
                'aria-modal': 'true',
                'aria-labelledby': 'ug-settings-main-header',
                className: 'ug-settings-overlay'
            }, [
                DOM.create('div', { className: 'ug-settings-container' }, [
                    sidebarContainer,
                    DOM.create('div', { className: 'ug-settings-content' }, [
                        DOM.create('div', { className: 'ug-settings-header' }, [
                            headerText,
                            DOM.create('button', {
                                className: 'ug-settings-close-btn',
                                text: BUTTONS.CLOSE,
                                onclick: () => state.settingsOpen = false
                            })
                        ]),
                        body
                    ])
                ])
            ]);

            settingsConfig.forEach(section => {
                const sectionEl = DOM.create('div', {
                    className: 'ug-settings-section',
                    'data-section-key': section.key,
                    style: { display: 'none' }
                });

                section.settings.forEach(setting => {
                    sectionEl.appendChild(UI._createSettingElement(setting));
                });
                body.appendChild(sectionEl);

                const sideBtn = DOM.create('button', {
                    className: 'ug-sidebar-button',
                    text: section.title,
                    onclick: function () {
                        DOM.$$('.ug-sidebar-button', sidebarContainer).forEach(b => b.classList.remove('active'));
                        this.classList.add('active');

                        DOM.$$('.ug-settings-section', body).forEach(s => s.style.display = 'none');
                        sectionEl.style.display = 'block';

                        headerText.textContent = section.title;
                        body.scrollTop = 0;
                    }
                });
                sidebarContainer.appendChild(sideBtn);
            });

            DOM.$('.ug-sidebar-button', sidebarContainer)?.click();
            document.body.appendChild(overlay);
        },

        showSettings() {
            lastFocusedElement = document.activeElement;
            document.getElementById('ug-settings-overlay')?.remove();

            UI.createSettingsUI();
            const overlay = document.getElementById('ug-settings-overlay');
            if (!overlay) return;

            overlay.classList.add('opening');
            const focusable = DOM.$$('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', overlay);
            const firstFocusable = focusable[0];
            const lastFocusable = focusable[focusable.length - 1];
            firstFocusable?.focus();

            focusTrapListener = (e) => {
                if (e.key !== 'Tab') return;
                if (e.shiftKey) {
                    if (document.activeElement === firstFocusable) {
                        lastFocusable.focus();
                        e.preventDefault();
                    }
                } else {
                    if (document.activeElement === lastFocusable) {
                        firstFocusable.focus();
                        e.preventDefault();
                    }
                }
            };
            document.addEventListener('keydown', focusTrapListener);
        },

        closeSettings() {
            if (focusTrapListener) {
                document.removeEventListener('keydown', focusTrapListener);
                focusTrapListener = null;
            }
            const overlay = document.getElementById('ug-settings-overlay');
            if (overlay) {
                overlay.classList.remove('opening');
                setTimeout(() => {
                    overlay.remove();
                    lastFocusedElement?.focus();
                }, 300);
            }
        }
    };

    // ====================================================
    // Gallery Core Module
    // ====================================================
    const Gallery = {
        _preloadedImageCache: {},
        _preloadingInProgress: {},
        _controlsHideTimer: null,

        _clearPreloadCache() {
            for (const index in Gallery._preloadedImageCache) {
                const cachedItem = Gallery._preloadedImageCache[index];
                if (typeof cachedItem === 'string' && cachedItem.startsWith('blob:')) {
                    BlobManager.revokeUrl(cachedItem);
                }
            }
            Gallery._preloadedImageCache = {};
            Gallery._preloadingInProgress = {};
        },

        async _fetchAndCacheImage(indexToPreload, sessionId = null) {
            if (indexToPreload < 0 || indexToPreload >= state.originalImageSrcs.length) return;
            if (Gallery._preloadedImageCache[indexToPreload] || Gallery._preloadingInProgress[indexToPreload]) return;

            const mediaItem = state.originalImageSrcs[indexToPreload];
            if (!mediaItem || mediaItem.type !== 'image') return;
            if (sessionId !== null && state.currentLoadSessionId !== sessionId) return;

            const originalImageUrl = mediaItem.src;
            if (!originalImageUrl) return;

            Gallery._preloadingInProgress[indexToPreload] = true;
            try {
                const blob = await ImageLoader.fetchWithRetry(originalImageUrl, sessionId);
                if (blob) {
                    Gallery._preloadedImageCache[indexToPreload] = BlobManager.createUrl(blob);
                }
            } catch (error) {
                console.error(`Preload failed for ${indexToPreload}`, error);
            } finally {
                delete Gallery._preloadingInProgress[indexToPreload];
            }
        },

        _preloadAdjacentImages(currentIndex) {
            const sessionId = state.currentLoadSessionId;
            const maxKeep = CONFIG.PRELOAD_COUNT + CONFIG.PRELOAD_WINDOW_BUFFER;

            for (const indexStr in Gallery._preloadedImageCache) {
                const index = parseInt(indexStr, 10);
                if (Math.abs(index - currentIndex) > maxKeep) {
                    if (typeof Gallery._preloadedImageCache[indexStr] === 'string') {
                        BlobManager.revokeUrl(Gallery._preloadedImageCache[indexStr]);
                    }
                    delete Gallery._preloadedImageCache[indexStr];
                }
            }

            for (let i = 1; i <= CONFIG.PRELOAD_COUNT; i++) {
                Gallery._fetchAndCacheImage(currentIndex + i, sessionId);
            }
            Gallery._fetchAndCacheImage(currentIndex - 1, sessionId);
        },

        async _fetchVideoForElement(videoEl, url, sessionId, token) {
            try {
                const blob = await ImageLoader.fetchBlob(url, sessionId);
                if (!videoEl.isConnected || videoEl.dataset.ugFetchToken !== token) return;
                videoEl.src = blob ? BlobManager.createUrl(blob) : url;
            } catch (err) {
                if (!videoEl.isConnected || videoEl.dataset.ugFetchToken !== token) return;
                console.warn('Ultra Galleries: video blob fetch failed, falling back:', err);
                videoEl.src = url;
            }
        },

        _releaseVideo(video) {
            if (!video || video.tagName !== 'VIDEO') return;
            try {
                video.pause();
                video.removeAttribute('src');
                video.load();
            } catch {
                // Ignore release errors
            }
        },

        _releaseMediaElements(container) {
            if (!container) return;
            DOM.$$('video', container).forEach(v => Gallery._releaseVideo(v));
        },

        safePlay(video) {
            if (!video) return;
            try {
                const p = video.play();
                if (p && typeof p.catch === 'function') {
                    p.catch(err => console.warn('Ultra Galleries: playback blocked:', err));
                }
            } catch (e) {
                console.warn('Ultra Galleries: playback error:', e);
            }
        },

        _attachVideoPlayOverlay(video, container) {
            const overlay = DOM.create('button', {
                className: 'ug-video-play-overlay',
                'aria-label': 'Play video',
                title: 'Play video',
                html: '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>',
                onclick: (e) => {
                    e.stopPropagation();
                    Gallery.safePlay(video);
                    overlay.classList.add('ug-hidden');
                }
            });

            video.addEventListener('play', () => overlay.classList.add('ug-hidden'), { once: true });
            container.appendChild(overlay);
        },

        _createExpandedViewToolbar(expandedView) {
            const toolbar = DOM.create('div', {
                className: CSS.GALLERY.TOOLBAR,
                onmousedown: e => e.stopPropagation()
            });

            const resetBtn = DOM.create('button', {
                id: 'reset-btn',
                title: 'Reset Zoom & Position',
                className: CSS.GALLERY.TOOLBAR_BTN,
                text: 'Reset',
                onclick: Zoom.resetZoom
            });

            const zoomControls = DOM.create('div', { className: 'zoom-controls' }, [
                DOM.create('button', {
                    id: 'zoom-out-btn',
                    title: 'Zoom Out',
                    className: CSS.GALLERY.TOOLBAR_BTN,
                    html: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="8" y1="11" x2="14" y2="11"></line></svg>',
                    onclick: () => Zoom.zoom(-CONFIG.ZOOM_STEP)
                }),
                DOM.create('span', { id: 'zoom-level', className: 'zoom-level', text: '100%' }),
                DOM.create('button', {
                    id: 'zoom-in-btn',
                    title: 'Zoom In',
                    className: CSS.GALLERY.TOOLBAR_BTN,
                    html: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>',
                    onclick: () => Zoom.zoom(CONFIG.ZOOM_STEP)
                })
            ]);

            const slideshowBtn = DOM.create('button', {
                id: 'slideshow-btn',
                title: 'Start Slideshow (Space)',
                className: CSS.GALLERY.TOOLBAR_BTN,
                text: '▶',
                onclick: Slideshow.handleButton
            });

            const fillHeightBtn = DOM.create('button', {
                id: 'ug-fill-height-btn',
                'aria-label': 'Fill Height',
                className: CSS.GALLERY.TOOLBAR_BTN,
                text: BUTTONS.HEIGHT,
                onclick: () => {
                    const media = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay)?.querySelector('img, video');
                    if (media) ImageSizing.applyFillHeight(media);
                }
            });

            const fullscreenBtn = DOM.create('button', {
                className: `${CSS.GALLERY.FULLSCREEN} ${CSS.GALLERY.TOOLBAR_BTN}`,
                'aria-label': 'Toggle Fullscreen',
                text: BUTTONS.FULLSCREEN,
                onclick: Gallery.toggleFullscreen
            });

            toolbar.append(resetBtn, zoomControls, slideshowBtn, fillHeightBtn, fullscreenBtn);
            expandedView.append(
                toolbar,
                DOM.create('button', {
                    className: 'ug-gallery-close-button',
                    'aria-label': 'Close Gallery',
                    text: BUTTONS.CLOSE,
                    onclick: Gallery.closeGallery
                })
            );
        },

        _createExpandedViewMainImageArea(expandedView) {
            const zoomContainer = DOM.create('div', { className: CSS.GALLERY.ZOOM_CONTAINER });
            const ambientBg = DOM.create('div', { className: 'ug-ambient-background' });
            const mainImageContainer = DOM.create('div', { className: `${CSS.GALLERY.MAIN_IMG_CONTAINER} image-container` });
            const panIndicator = DOM.create('div', {
                className: 'pan-indicator',
                html: '<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24" fill="white" opacity="0.7"><path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/></svg>'
            });

            mainImageContainer.appendChild(panIndicator);
            zoomContainer.append(ambientBg, mainImageContainer);
            expandedView.appendChild(zoomContainer);

            return { mainImageContainer };
        },

        _createExpandedViewNavigationAndCounter(expandedView) {
            const navContainer = DOM.create('div', {
                className: CSS.GALLERY.NAV_CONTAINER,
                onmousedown: e => e.stopPropagation()
            });

            if (!state.hideNavArrows) {
                navContainer.append(
                    UI.createNavigationButton('prev'),
                    UI.createNavigationButton('next')
                );
            }

            expandedView.append(
                navContainer,
                DOM.create('div', { className: `${CSS.GALLERY.COUNTER} ${CSS.GALLERY.HIDE}` })
            );
        },

        _createExpandedViewThumbnailStrip(expandedView) {
            const thumbnailStripContainer = DOM.create('div', {
                className: CSS.GALLERY.STRIP_CONTAINER,
                style: { display: state.bottomStripeVisible ? 'flex' : 'none' },
                onmousedown: e => e.stopPropagation()
            });

            const strip = DOM.create('div', { className: CSS.GALLERY.THUMBNAIL_STRIP });
            thumbnailStripContainer.appendChild(strip);
            expandedView.appendChild(thumbnailStripContainer);
            return strip;
        },

        _populateAllThumbnails(stripThumbnailsContainer) {
            stripThumbnailsContainer.replaceChildren();
            const fragment = document.createDocumentFragment();

            const fileIconSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';

            state.fullSizeImageSrcs.forEach((mediaItem, index) => {
                if (!mediaItem) return;

                const wrapper = DOM.create('div', {
                    className: CSS.GALLERY.THUMBNAIL_WRAPPER,
                    'data-index': index,
                    'aria-label': `Thumbnail ${index + 1}`,
                    tabindex: '0',
                    role: 'button',
                    onclick: () => Gallery.showExpandedView(index)
                });

                if (mediaItem.type === 'video') {
                    wrapper.appendChild(DOM.create('div', {
                        className: 'ug-play-icon',
                        html: '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>'
                    }));
                    wrapper.appendChild(DOM.create('img', {
                        src: mediaItem.poster || FALLBACK_POSTER,
                        loading: 'lazy',
                        decoding: 'async',
                        className: CSS.GALLERY.THUMBNAIL
                    }));
                } else if (mediaItem.type === 'attachment') {
                    wrapper.appendChild(DOM.create('div', {
                        className: 'ug-thumbnail-file-icon',
                        html: fileIconSvg
                    }));
                } else {
                    wrapper.appendChild(DOM.create('img', {
                        src: mediaItem.src,
                        loading: 'lazy',
                        decoding: 'async',
                        className: CSS.GALLERY.THUMBNAIL
                    }));
                }

                fragment.appendChild(wrapper);
            });
            stripThumbnailsContainer.appendChild(fragment);
        },

        _setupGalleryInteractions(expandedView, mainImageContainer) {
            mainImageContainer.addEventListener('wheel', (e) => {
                const currentItem = state.fullSizeImageSrcs[state.currentGalleryIndex];
                if (currentItem && currentItem.type === 'image') Zoom.handleWheelZoom(e);
            }, { passive: false });

            PointerEngine.init(mainImageContainer);

            const resetControlsTimer = () => {
                state.controlsVisible = true;
                clearTimeout(Gallery._controlsHideTimer);
                Gallery._controlsHideTimer = setTimeout(() => {
                    if (!PointerEngine.isDragging && !viewState.pinchZoomActive) {
                        state.controlsVisible = false;
                    }
                }, CONFIG.CONTROLS_HIDE_DELAY);
            };

            expandedView.addEventListener('pointermove', resetControlsTimer);
            expandedView.addEventListener('pointerdown', resetControlsTimer);
            expandedView.addEventListener('mousemove', resetControlsTimer);
            resetControlsTimer();
        },

        createGallery() {
            if (galleryOverlay) {
                Gallery.showExpandedView(0);
                state.isGalleryMode = true;
                return;
            }

            galleryOverlay = DOM.create('div', {
                id: 'gallery-overlay',
                className: CSS.GALLERY.OVERLAY
            });

            const galleryContainer = DOM.create('div', { className: CSS.GALLERY.CONTAINER });
            galleryOverlay.appendChild(galleryContainer);

            const expandedView = DOM.create('div', {
                className: `${CSS.GALLERY.EXPANDED_VIEW} ${CSS.GALLERY.HIDE}`
            });
            galleryContainer.appendChild(expandedView);

            Gallery._createExpandedViewToolbar(expandedView);
            const { mainImageContainer } = Gallery._createExpandedViewMainImageArea(expandedView);
            Gallery._createExpandedViewNavigationAndCounter(expandedView);
            const stripThumbnailsContainer = Gallery._createExpandedViewThumbnailStrip(expandedView);

            document.body.appendChild(galleryOverlay);

            if (state.isFullscreen) {
                document.body.classList.add('ug-fullscreen');
                galleryOverlay.classList.add(CSS.GALLERY.FULLSCREEN_OVERLAY);
            }

            Gallery._populateAllThumbnails(stripThumbnailsContainer);
            Gallery._setupGalleryInteractions(expandedView, mainImageContainer);
            Gallery.showExpandedView(0);
            state.isGalleryMode = true;
            Accessibility.init();
        },

        showExpandedView(index) {
            if (!galleryOverlay || index < 0) return;

            let mediaItem = state.fullSizeImageSrcs[index] || state.originalImageSrcs[index];
            const mainMediaContainer = DOM.$(`.${CSS.GALLERY.MAIN_IMG_CONTAINER}`, galleryOverlay);
            const ambientBackground = DOM.$('.ug-ambient-background', galleryOverlay);
            const counter = DOM.$(`.${CSS.GALLERY.COUNTER}`, galleryOverlay);
            const zoomControls = DOM.$('.zoom-controls', galleryOverlay);
            const resetBtn = DOM.$('#reset-btn', galleryOverlay);
            const fillHeightBtn = DOM.$('#ug-fill-height-btn', galleryOverlay);
            const expandedView = DOM.$(`.${CSS.GALLERY.EXPANDED_VIEW}`, galleryOverlay);

            if (!mainMediaContainer) return;
            state.currentGalleryIndex = index;

            if (!mediaItem || !mediaItem.src) {
                Gallery._releaseMediaElements(mainMediaContainer);
                mainMediaContainer.replaceChildren(
                    DOM.create('div', { className: CSS.GALLERY.IMAGE_ERROR_MSG, text: 'Loading media data...' })
                );
                expandedView?.classList.remove(CSS.GALLERY.HIDE);
                return;
            }

            Gallery._releaseMediaElements(mainMediaContainer);

            if (PointerEngine.inertiaAnimation) {
                cancelAnimationFrame(PointerEngine.inertiaAnimation);
                PointerEngine.inertiaAnimation = null;
            }
            if (PointerEngine.animationFrame) {
                cancelAnimationFrame(PointerEngine.animationFrame);
                PointerEngine.animationFrame = null;
            }

            mainMediaContainer.replaceChildren();
            mainMediaContainer.classList.remove(CSS.GALLERY.ZOOMED);

            viewState.zoomScale = 1;
            viewState.imageOffset = { x: 0, y: 0 };
            PointerEngine.cachedImgEl = null;
            PointerEngine.cachedZoomEl = null;

            if (mediaItem.type === 'image' || !mediaItem.type) {
                if (zoomControls) zoomControls.style.display = 'flex';
                if (resetBtn) resetBtn.style.display = 'block';
                if (fillHeightBtn) fillHeightBtn.style.display = 'none';

                const imageUrlToLoad = Gallery._preloadedImageCache[index] || mediaItem.src;
                if (ambientBackground) {
                    ambientBackground.style.backgroundImage = `url("${imageUrlToLoad}")`;
                }

                const mainImage = DOM.create('img', {
                    className: CSS.GALLERY.MAIN_IMG,
                    decoding: 'async',
                    ondragstart: e => e.preventDefault(),
                    onload() {
                        this.style.opacity = '1';
                        this.style.transformOrigin = '0 0';
                        ImageSizing.applyBestFit(this);
                        PointerEngine.cachedImgEl = this;
                        PointerEngine.cachedZoomEl = DOM.$('#zoom-level');
                        viewState.zoomScale = 1;
                        viewState.imageOffset = { x: 0, y: 0 };
                        Zoom.applyZoom();
                        Gallery._preloadAdjacentImages(index);
                    },
                    onerror() {
                        if (imageUrlToLoad !== mediaItem.src && !this.dataset.ugRetried) {
                            this.dataset.ugRetried = 'true';
                            this.src = mediaItem.src;
                            return;
                        }
                        mainMediaContainer.appendChild(
                            DOM.create('div', { className: CSS.GALLERY.IMAGE_ERROR_MSG, text: 'Failed to load image' })
                        );
                    }
                });

                mainImage.src = imageUrlToLoad;
                mainMediaContainer.appendChild(mainImage);

            } else if (mediaItem.type === 'video') {
                if (zoomControls) zoomControls.style.display = 'none';
                if (resetBtn) resetBtn.style.display = 'none';
                if (fillHeightBtn) fillHeightBtn.style.display = 'block';
                if (ambientBackground) ambientBackground.style.backgroundImage = '';

                const mainVideo = DOM.create('video', {
                    className: CSS.GALLERY.MAIN_VIDEO,
                    poster: mediaItem.poster,
                    controls: 'true',
                    loop: 'true',
                    preload: 'metadata',
                    playsinline: 'true',
                    ondragstart: e => e.preventDefault(),
                    onloadedmetadata() {
                        this.style.transformOrigin = '0 0';
                        Utils.setImageStyle(this, {
                            width: '100%',
                            height: '100%',
                            maxWidth: '100%',
                            maxHeight: '100%',
                            objectFit: 'contain'
                        });
                        PointerEngine.cachedImgEl = this;
                        PointerEngine.cachedZoomEl = DOM.$('#zoom-level');
                    }
                });

                mainMediaContainer.appendChild(mainVideo);

                if (isPawchiveHost(mediaItem.src)) {
                    const token = (crypto.randomUUID?.() || Math.random().toString(36));
                    mainVideo.dataset.ugFetchToken = token;
                    const sessionAtLoad = state.currentLoadSessionId;
                    Gallery._fetchVideoForElement(mainVideo, mediaItem.src, sessionAtLoad, token);
                } else {
                    mainVideo.src = mediaItem.src;
                }

                Gallery._attachVideoPlayOverlay(mainVideo, mainMediaContainer);

            } else if (mediaItem.type === 'attachment') {
                if (zoomControls) zoomControls.style.display = 'none';
                if (resetBtn) resetBtn.style.display = 'none';
                if (fillHeightBtn) fillHeightBtn.style.display = 'none';
                if (ambientBackground) ambientBackground.style.backgroundImage = '';

                const fileName = mediaItem.fileName || mediaItem.src.split('/').pop() || 'attachment';
                const fileExt = (fileName.match(/\.([a-z0-9]+)$/i) || [, 'file'])[1].toUpperCase();

                const preview = DOM.create('div', { className: 'ug-attachment-preview' }, [
                    DOM.create('div', {
                        html: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>'
                    }),
                    DOM.create('div', { className: 'ug-attachment-type', text: fileExt }),
                    DOM.create('div', { className: 'ug-attachment-info', text: fileName }),
                    DOM.create('button', {
                        className: 'ug-attachment-download-btn',
                        text: 'Download File',
                        onclick: (e) => {
                            e.stopPropagation();
                            DownloadManager.downloadImageByIndex(index);
                        }
                    })
                ]);

                mainMediaContainer.appendChild(preview);
            }

            if (counter) {
                counter.textContent = `${index + 1} / ${state.fullSizeImageSrcs.length}`;
                counter.classList.remove(CSS.GALLERY.HIDE);
            }
            expandedView?.classList.remove(CSS.GALLERY.HIDE);

            const strip = DOM.$(`.${CSS.GALLERY.THUMBNAIL_STRIP}`, galleryOverlay);
            if (strip) {
                DOM.$$('.selected', strip).forEach(s => s.classList.remove('selected'));
                const activeThumb = DOM.$(`[data-index="${index}"]`, strip);
                if (activeThumb) {
                    activeThumb.classList.add('selected');
                    DOM.scrollToCenter(strip, activeThumb);
                }
            }

            setTimeout(() => {
                ThumbnailStrip.init();
                ThumbnailStrip.updateThumbnailNumbers();
                ThumbnailStrip.updateScrollIndicators();
            }, 100);
        },

        closeGallery() {
            if (!galleryOverlay) {
                state.isGalleryMode = false;
                state.isFullscreen = false;
                Slideshow.stop();
                ThumbnailStrip.cleanup();
                return;
            }
            if (document.fullscreenElement === galleryOverlay) {
                const exit = document.exitFullscreen();
                if (exit && typeof exit.catch === 'function') exit.catch(() => {});
            }
            state.isGalleryMode = false;
            state.isFullscreen = false;
            clearTimeout(Gallery._controlsHideTimer);
            Gallery._controlsHideTimer = null;
            Slideshow.stop();
            Gallery._clearPreloadCache();
            ThumbnailStrip.cleanup();

            if (PointerEngine.inertiaAnimation) cancelAnimationFrame(PointerEngine.inertiaAnimation);
            if (PointerEngine.animationFrame) cancelAnimationFrame(PointerEngine.animationFrame);

            Gallery._releaseMediaElements(galleryOverlay);
            galleryOverlay.remove();
            galleryOverlay = null;
            PointerEngine.cachedImgEl = null;
            PointerEngine.cachedZoomEl = null;
        },

        toggleGallery() {
            if (state.isGalleryMode) {
                Gallery.closeGallery();
            } else {
                if (state.galleryReady && state.fullSizeImageSrcs.length > 0) {
                    Gallery.createGallery();
                } else if (Utils.isPostPage()) {
                    ImageLoader.loadImages();
                    state.notificationType = 'info';
                    state.notification = 'Refreshing gallery list...';
                } else {
                    state.notificationType = 'warning';
                    state.notification = 'No post page detected.';
                }
            }
        },

        async toggleFullscreen() {
            let mode = state.fullscreenMode || 'native';

            if (mode === 'ask') {
                const result = await UGModal.choose({
                    title: 'Fullscreen mode',
                    text: 'How should the gallery enter fullscreen?',
                    icon: 'question',
                    options: [
                        { label: 'Native', value: 'native', primary: true },
                        { label: 'CSS', value: 'css' }
                    ]
                });
                if (!result.value) return;
                mode = result.value;
            }

            state.isFullscreen = !state.isFullscreen;

            if (mode !== 'native') return;
            if (!galleryOverlay || typeof galleryOverlay.requestFullscreen !== 'function') return;

            if (state.isFullscreen) {
                if (!document.fullscreenElement) {
                    const req = galleryOverlay.requestFullscreen();
                    if (req && typeof req.catch === 'function') {
                        req.catch(err => {
                            console.warn('Ultra Galleries: native fullscreen failed, staying in CSS mode:', err);
                        });
                    }
                }
            } else {
                if (document.fullscreenElement === galleryOverlay) {
                    const exit = document.exitFullscreen();
                    if (exit && typeof exit.catch === 'function') exit.catch(() => {});
                }
            }
        },

        nextImage() {
            if (state.fullSizeImageSrcs.length === 0) return;
            Gallery.showExpandedView((state.currentGalleryIndex + 1) % state.fullSizeImageSrcs.length);
        },

        prevImage() {
            if (state.fullSizeImageSrcs.length === 0) return;
            Gallery.showExpandedView(
                (state.currentGalleryIndex - 1 + state.fullSizeImageSrcs.length) % state.fullSizeImageSrcs.length
            );
        }
    };

    // ====================================================
    // Image Loader Module
    // ====================================================
    const ImageLoader = {
        _inflight: new Map(),

        imageActions: {
            height: ImageSizing.applyFillHeight,
            width: ImageSizing.applyFillWidth,
            full: ImageSizing.applyFullSize
        },

        async simulateScrollDown(sessionId = null) {
            return new Promise(resolve => {
                const selectors = [
                    SELECTORS.IMAGE_LINK + ' img',
                    SELECTORS.MAIN_THUMBNAIL + ' img',
                    '.post__content img',
                    '.post__body img'
                ];
                const images = DOM.$$(selectors.join(', '));
                if (images.length === 0) { resolve(); return; }

                let loadedCount = 0;
                const checkAllLoaded = () => {
                    loadedCount++;
                    if (loadedCount >= images.length) {
                        clearTimeout(timeout);
                        observer.disconnect();
                        resolve();
                    }
                };

                const observer = new IntersectionObserver(entries => {
                    if (sessionId !== null && state.currentLoadSessionId !== sessionId) {
                        observer.disconnect();
                        clearTimeout(timeout);
                        resolve();
                        return;
                    }
                    entries.forEach(entry => {
                        if (entry.isIntersecting) {
                            observer.unobserve(entry.target);
                            checkAllLoaded();
                        }
                    });
                });

                images.forEach(img => observer.observe(img));
                const dynamicTimeout = Math.max(CONFIG.SCROLL_SIMULATION_BASE_TIMEOUT, images.length * 200);
                const timeout = setTimeout(() => {
                    observer.disconnect();
                    resolve();
                }, dynamicTimeout);
            });
        },

        async _fetchBlobInternal(url, sessionId, retries, delay) {
            try {
                await RequestPacer.throttle(url);

                return await new Promise((resolve, reject) => {
                    if (sessionId !== null && state.currentLoadSessionId !== sessionId) {
                        reject(new Error('Stale session'));
                        return;
                    }

                    let handle = null;
                    const cleanup = () => { ActiveRequests.remove(sessionId, handle); };

                    handle = GM.xmlHttpRequest({
                        method: 'GET',
                        url,
                        headers: RequestPacer.getHeaders(url),
                        responseType: 'blob',
                        timeout: 120000,
                        onload: async (response) => {
                            cleanup();
                            if (response.status === 200 || response.status === 206) {
                                const blob = response.response;
                                if (state.enablePersistentCaching) {
                                    await ImageCacheDB.put(url, blob);
                                }
                                resolve(blob);
                            } else {
                                const err = new Error(`HTTP ${response.status}`);
                                err.status = response.status;
                                reject(err);
                            }
                        },
                        onerror: (error) => { cleanup(); reject(error); },
                        ontimeout: () => { cleanup(); reject(new Error('Request timeout')); },
                        onabort: () => { cleanup(); reject(new Error('Request aborted')); }
                    });

                    ActiveRequests.add(sessionId, handle);
                });
            } catch (err) {
                if (err.message === 'Stale session' || err.message === 'Request aborted') throw err;
                if (err.status && err.status >= 400 && err.status < 500 && err.status !== 429) throw err;
                if (retries <= 0) throw err;
                await Utils.delay(delay);
                return ImageLoader._fetchBlobInternal(url, sessionId, retries - 1, delay * 1.5);
            }
        },

        fetchBlob(url, sessionId = null, retries = CONFIG.MAX_RETRIES, delay = CONFIG.RETRY_DELAY) {
            if (sessionId !== null && state.currentLoadSessionId !== sessionId) {
                return Promise.reject(new Error('Stale session'));
            }

            const key = `${sessionId ?? 'direct'}:${url}`;
            const existing = ImageLoader._inflight.get(key);
            if (existing) return existing;

            const promise = (async () => {
                if (state.enablePersistentCaching) {
                    const cachedBlob = await ImageCacheDB.get(url);
                    if (cachedBlob) return cachedBlob;
                }

                if (sessionId !== null && state.currentLoadSessionId !== sessionId) {
                    throw new Error('Stale session');
                }

                return ImageLoader._fetchBlobInternal(url, sessionId, retries, delay);
            })();

            ImageLoader._inflight.set(key, promise);

            const cleanup = () => {
                if (ImageLoader._inflight.get(key) === promise) {
                    ImageLoader._inflight.delete(key);
                }
            };
            promise.then(cleanup, cleanup);

            return promise;
        },

        fetchWithRetry(url, sessionId) {
            return ImageLoader.fetchBlob(url, sessionId);
        },

        fetchBlobDirect(url) {
            return ImageLoader.fetchBlob(url, null);
        },

        async loadImageAndApplyToPage(linkElement, galleryIndex, posterHref, isUniqueForGallery, sessionId, itemData) {
            const imgElement = linkElement.querySelector('img') || linkElement;
            if (!imgElement) {
                if (state.currentLoadSessionId === sessionId) state.loadedImages++;
                return;
            }

            if (imgElement.tagName === 'IMG' && !imgElement.classList.contains('post__image')) {
                imgElement.classList.add('post__image');
            }

            const cacheKey = itemData.originalUrl;
            let blobUrlToUse = loadedBlobUrls.get(posterHref);

            try {
                if (itemData.type !== 'attachment') {
                    if (!blobUrlToUse) {
                        if (itemData.type === 'video') {
                            const posterBlob = await ImageLoader.fetchWithRetry(posterHref, sessionId);
                            if (state.currentLoadSessionId !== sessionId) return;
                            if (!posterBlob) throw new Error('Failed to fetch poster blob');
                            blobUrlToUse = BlobManager.createUrl(posterBlob);
                        } else {
                            const blob = await ImageLoader.fetchWithRetry(cacheKey, sessionId);
                            if (state.currentLoadSessionId !== sessionId) return;
                            if (!blob) throw new Error('Failed to fetch blob');
                            blobUrlToUse = BlobManager.createUrl(blob);
                        }
                        loadedBlobUrls.set(posterHref, blobUrlToUse);
                    }

                    if (state.currentLoadSessionId !== sessionId) return;

                    if (imgElement.tagName === 'IMG') {
                        imgElement.src = blobUrlToUse;
                        imgElement.dataset.originalSrc = cacheKey;
                        imgElement.classList.add('ug-image-loaded');
                        imgElement.style.cursor = 'default';
                        const parentLink = imgElement.closest('a');
                        if (parentLink) parentLink.style.cursor = 'default';
                        ImageLoader.imageActions[state.currentResizeMode](imgElement);
                    }
                }

                if (isUniqueForGallery) {
                    if (itemData.type === 'video') {
                        state.fullSizeImageSrcs[galleryIndex] = { type: 'video', src: cacheKey, poster: blobUrlToUse };
                    } else if (itemData.type === 'attachment') {
                        state.fullSizeImageSrcs[galleryIndex] = {
                            type: 'attachment',
                            src: cacheKey,
                            poster: FALLBACK_ATTACHMENT,
                            fileName: itemData.fileName
                        };
                    } else {
                        state.fullSizeImageSrcs[galleryIndex] = { type: 'image', src: cacheKey, originalSrc: cacheKey };
                    }

                    state.originalImageSrcs[galleryIndex] = {
                        src: cacheKey,
                        type: itemData.type,
                        fileName: itemData.fileName || linkElement.getAttribute('download') || cacheKey.split('/').pop()
                    };
                }

                state.loadedImages++;
            } catch (error) {
                ErrorHandler.handleImageError(
                    error, cacheKey,
                    imgElement.tagName === 'IMG' ? imgElement : null,
                    { linkElement, galleryIndex, posterHref, isUniqueForGallery, itemData }
                );
                if (state.currentLoadSessionId === sessionId) {
                    state.loadedImages++;
                    state.errorCount++;
                }
            }
        },

        collectUniqueMediaItems(postContainer) {
            const uniqueGalleryItems = new Map();
            const targets = DOM.$$(`${SELECTORS.IMAGE_LINK}, ${SELECTORS.ATTACHMENT_LINK}, ${SELECTORS.VIDEO_LINK}, ${SELECTORS.GENERIC_IMAGE_LINK}`, postContainer);

            targets.forEach(linkElement => {
                if (linkElement.closest('.post__user-profile') || linkElement.closest('.scrape__user-profile')) return;
                if (linkElement.classList.contains('user-header__avatar')) return;

                const isVideo = linkElement.matches(SELECTORS.VIDEO_LINK) || linkElement.href?.match(/\.(mp4|webm|mov)$/i);
                let url;
                let poster;

                if (isVideo) {
                    url = linkElement.getAttribute('href')?.split('?')[0];
                    poster = linkElement.querySelector('img, video')?.getAttribute('poster') ||
                        (linkElement.querySelector('img')?.getAttribute('data-src') || linkElement.querySelector('img')?.src);
                    if (!url) return;
                    if (!poster) poster = FALLBACK_POSTER;

                    if (!uniqueGalleryItems.has(url)) {
                        uniqueGalleryItems.set(url, {
                            linkElement, originalUrl: url, posterUrl: poster, type: 'video',
                            fileName: Utils.extractFileName(linkElement, url)
                        });
                    }
                } else {
                    url = Utils.handleMediaSrc(linkElement);
                    if (!url && linkElement.href) url = linkElement.href.split('?')[0];
                    if (!url) return;

                    const isImage = /\.(jpe?g|png|gif|webp|bmp)$/i.test(url);
                    const isAttachmentLink = linkElement.matches(SELECTORS.ATTACHMENT_LINK);

                    if (isImage) {
                        if (!uniqueGalleryItems.has(url)) {
                            uniqueGalleryItems.set(url, {
                                linkElement, originalUrl: url, posterUrl: url, type: 'image',
                                fileName: Utils.extractFileName(linkElement, url)
                            });
                        }
                    } else if (isAttachmentLink) {
                        const downloadName = Utils.extractFileName(linkElement, url);
                        if (!uniqueGalleryItems.has(url)) {
                            uniqueGalleryItems.set(url, {
                                linkElement, originalUrl: url, posterUrl: FALLBACK_ATTACHMENT, type: 'attachment',
                                fileName: downloadName
                            });
                        }
                    }
                }
            });

            DOM.$$('video', postContainer).forEach(videoEl => {
                let url = videoEl.getAttribute('src') || videoEl.querySelector('source')?.getAttribute('src');
                if (url) {
                    url = url.split('?')[0];
                    if (!uniqueGalleryItems.has(url)) {
                        const poster = videoEl.getAttribute('poster') || FALLBACK_POSTER;
                        uniqueGalleryItems.set(url, {
                            linkElement: videoEl, originalUrl: url, posterUrl: poster, type: 'video',
                            fileName: Utils.extractFileName(videoEl, url)
                        });
                    }
                }
            });

            return uniqueGalleryItems;
        },

        _concurrentRunner(items, sessionId) {
            const concurrencyLimit = CONFIG.MAX_CONCURRENT_FETCHES;
            const tasks = items.map((item, index) => () => ImageLoader.loadImageAndApplyToPage(
                item.linkElement, index, item.posterUrl, true, sessionId, item
            ));

            return new Promise((resolve) => {
                let running = 0;
                let index = 0;
                const total = tasks.length;
                if (total === 0) { resolve(); return; }

                const next = () => {
                    if (state.currentLoadSessionId !== sessionId) { resolve(); return; }
                    if (index >= total) { if (running === 0) resolve(); return; }
                    const task = tasks[index++];
                    running++;
                    task().then(() => { running--; next(); }).catch(() => { running--; next(); });
                };

                for (let i = 0; i < concurrencyLimit && i < total; i++) { next(); }
            });
        },

        async loadImages() {
            const postContainer =
                DOM.$('section.site-section--post') ||
                DOM.$('section.site-section--scrape') ||
                DOM.$('.post__body') ||
                DOM.$('.post__content') ||
                document;

            if (!Utils.isPostPage() || state.isLoading) return;

            if (state.currentLoadSessionId) ActiveRequests.abortSession(state.currentLoadSessionId);

            const sessionId = StateManager.generateSessionId();
            state.currentLoadSessionId = sessionId;

            try {
                state.isLoading = true;
                await Utils.delay(16);
                if (state.currentLoadSessionId !== sessionId) return;

                const previousBlobUrls = new Map(loadedBlobUrls);
                loadedBlobUrls.clear();

                Object.assign(state, {
                    fullSizeImageSrcs: [],
                    originalImageSrcs: [],
                    loadedImages: 0,
                    errorCount: 0
                });

                const uniqueGalleryItems = ImageLoader.collectUniqueMediaItems(postContainer);
                if (state.currentLoadSessionId !== sessionId) return;

                const uniqueItems = Array.from(uniqueGalleryItems.values());
                Utils.sortItems(uniqueItems, state.sortMode);
                state.totalImages = uniqueItems.length;
                state.fullSizeImageSrcs = Array(uniqueItems.length).fill(null);
                state.originalImageSrcs = Array(uniqueItems.length).fill(null);

                uniqueItems.forEach((item, index) => {
                    if (item.type === 'video') {
                        state.fullSizeImageSrcs[index] = { type: 'video', src: item.originalUrl, poster: item.posterUrl };
                    } else if (item.type === 'attachment') {
                        state.fullSizeImageSrcs[index] = {
                            type: 'attachment',
                            src: item.originalUrl,
                            poster: FALLBACK_ATTACHMENT,
                            fileName: item.fileName
                        };
                    } else {
                        state.fullSizeImageSrcs[index] = { type: 'image', src: item.originalUrl, originalSrc: item.originalUrl };
                    }
                    state.originalImageSrcs[index] = { src: item.originalUrl, type: item.type, fileName: item.fileName };
                });

                state.galleryReady = true;
                updateGalleryButton(true);

                if (state.autoLoadOriginals) {
                    await ImageLoader.simulateScrollDown(sessionId);
                    Utils.ensureThumbnailsExist();
                    await ImageLoader._concurrentRunner(uniqueItems, sessionId);
                    if (state.currentLoadSessionId !== sessionId) return;

                    previousBlobUrls.forEach((url, key) => {
                        if (loadedBlobUrls.has(key)) BlobManager.revokeUrl(url);
                    });

                    ImageLoader.updateFinalStatus();
                    DOM.$$('img.post__image.ug-image-loaded').forEach(img => {
                        ImageLoader.imageActions[state.currentResizeMode](img);
                    });
                } else {
                    state.notificationType = 'success';
                    state.notification = `Gallery Ready (${state.totalImages} items).`;
                }
                state.isLoading = false;
            } catch (error) {
                console.error('Critical Error in ImageLoader.loadImages:', error);
                state.isLoading = false;
                state.galleryReady = true;
                updateGalleryButton(true);
            }
        },

        updateFinalStatus() {
            if (state.loadedImages >= state.totalImages && state.totalImages > 0) {
                if (state.errorCount === 0) {
                    state.notificationType = 'success';
                    state.notification = `Media Done Loading! Total: ${state.totalImages}`;
                } else {
                    state.notificationType = 'warning';
                    state.notification = `Gallery: Loaded with ${state.errorCount} error(s).`;
                }
            } else if (state.totalImages === 0) {
                state.notificationType = 'info';
                state.notification = 'No gallery images found.';
            }
        }
    };

    // ====================================================
    // CSP-Safe Background Download Manager
    // ====================================================
    const DownloadManager = {
        _worker: null,
        _workerUrl: null,

        _getPostMeta: () => ({
            title: DOM.$(SELECTORS.POST_TITLE)?.textContent?.trim() || 'Untitled',
            artistName: DOM.$(SELECTORS.POST_USER_NAME)?.textContent?.trim() || 'Unknown Artist',
            datePublished: Utils.getPostDate('published'),
            dateEdited: Utils.getPostDate('edited'),
            dateImported: Utils.getPostDate('imported'),
            publishedDate: Utils.getPostDateObject('published')
        }),

        _buildFileName: (item, index) => {
            const meta = DownloadManager._getPostMeta();
            const extMatch = item.fileName.match(/\.([a-z0-9]+)$/i);
            const correctExt = extMatch
                ? extMatch[1].toLowerCase()
                : (item.type === 'video' ? 'mp4' : item.type === 'attachment' ? 'bin' : 'jpg');
            const fileNameWithoutExt = item.fileName.replace(/\.[^/.]+$/, '');

            let formattedName = state.imageFileNameFormat
                .replace(/{date_published}/gi, meta.datePublished)
                .replace(/{date_edited}/gi, meta.dateEdited)
                .replace(/{date_imported}/gi, meta.dateImported)
                .replace(/{date}/gi, meta.datePublished)
                .replace('{title}', Utils.sanitizeFileName(meta.title))
                .replace('{artistName}', Utils.sanitizeFileName(meta.artistName))
                .replace('{fileName}', Utils.sanitizeFileName(fileNameWithoutExt))
                .replace('{index}', index + 1);

            if (!formattedName.toLowerCase().endsWith(`.${correctExt}`)) {
                formattedName += `.${correctExt}`;
            }
            return Utils.sanitizeFileName(formattedName);
        },

        downloadFile: async (url, name) => {
            await RequestPacer.throttle(url);

            return new Promise(resolve => {
                if (typeof GM_download !== 'function') {
                    resolve(false);
                    return;
                }
                try {
                    GM_download({
                        url,
                        name,
                        headers: RequestPacer.getHeaders(url),
                        onload: () => resolve(true),
                        onerror: () => resolve(false),
                        ontimeout: () => resolve(false)
                    });
                } catch (e) {
                    console.error('GM_download failed:', e);
                    resolve(false);
                }
            });
        },

        createZipWorker() {
            const jszipSource = GM_getResourceText('jszipScript') || '';
            const workerHandlerCode = `
                self.usedNames = new Set();
                self.onmessage = async (e) => {
                    const { type, data } = e.data;
                    if (type === 'init') {
                        self.zip = new self.JSZip();
                        self.totalFiles = data.totalFiles;
                        self.filesAdded = 0;
                        self.usedNames = new Set();
                    } else if (type === 'addFile') {
                        let { blob, name, date } = data;
                        let finalName = name;
                        let counter = 1;
                        const dotIndex = name.lastIndexOf('.');
                        const baseName = dotIndex > -1 ? name.substring(0, dotIndex) : name;
                        const ext = dotIndex > -1 ? name.substring(dotIndex) : '';

                        while (self.usedNames.has(finalName)) {
                            finalName = \`\${baseName}_\${counter}\${ext}\`;
                            counter++;
                        }
                        self.usedNames.add(finalName);

                        let fileOptions;
                        if (date !== null && date !== undefined) {
                            const parsedDate = (typeof date === 'number') ? new Date(date) : new Date(String(date));
                            if (!Number.isNaN(parsedDate.getTime())) {
                                fileOptions = { date: parsedDate };
                            }
                        }
                        self.zip.file(finalName, blob, fileOptions);

                        self.filesAdded++;
                        self.postMessage({ type: 'progress', message: \`Added \${self.filesAdded}/\${self.totalFiles}\` });
                    } else if (type === 'generate') {
                        self.postMessage({ type: 'progress', message: 'Bundling files... this may take a moment.' });
                        try {
                            const zipBlob = await self.zip.generateAsync({ type: 'blob', compression: 'STORE' }, (meta) => {
                                self.postMessage({ type: 'progress', message: \`Bundling... \${Math.round(meta.percent)}%\` });
                            });
                            self.postMessage({ type: 'complete', zipBlob });
                        } catch (err) {
                            self.postMessage({ type: 'error', message: err.message });
                        }
                    }
                };
            `;

            const blob = new Blob([jszipSource, '\n', workerHandlerCode], { type: 'application/javascript' });
            DownloadManager._workerUrl = URL.createObjectURL(blob);
            DownloadManager._worker = new Worker(DownloadManager._workerUrl);
            return DownloadManager._worker;
        },

        downloadAllImages: async (filter = 'all') => {
            if (state.isDownloading) {
                UGModal.alert('Download in Progress', 'A download is already running.', 'info');
                return;
            }

            const meta = DownloadManager._getPostMeta();

            let indexedItems = state.originalImageSrcs
                .map((item, index) => ({ item, index }))
                .filter(x => x.item && x.item.src);

            if (filter === 'images') {
                indexedItems = indexedItems.filter(x => x.item.type === 'image');
            } else if (filter === 'attachments') {
                indexedItems = indexedItems.filter(x => x.item.type !== 'image');
            }

            if (indexedItems.length === 0) {
                state.notificationType = 'warning';
                state.notification = filter === 'images'
                    ? 'No images found to download.'
                    : filter === 'attachments'
                        ? 'No attachments found to download.'
                        : 'No media found to download.';
                return;
            }

            let title, text, confirmText, zipSuffix, kindWord;
            if (filter === 'images') {
                title = 'Download Images?';
                text = `Create ZIP from ${indexedItems.length} image(s)?`;
                confirmText = 'Create ZIP';
                zipSuffix = 'images';
                kindWord = 'image(s)';
            } else if (filter === 'attachments') {
                title = 'Download Files?';
                text = `Create ZIP from ${indexedItems.length} attachment(s)? (videos + other files)`;
                confirmText = 'Create ZIP';
                zipSuffix = 'files';
                kindWord = 'attachment(s)';
            } else {
                title = 'Download All?';
                text = `Create ZIP from all ${indexedItems.length} item(s)? (images + videos + attachments)`;
                confirmText = 'Create ZIP';
                zipSuffix = 'all';
                kindWord = 'item(s)';
            }

            const result = await UGModal.confirm({
                title, text, icon: 'question',
                confirmText,
                cancelText: 'Cancel'
            });

            if (!result.isConfirmed) return;

            state.isDownloading = true;
            state.notificationType = 'info';
            state.notification = `Preparing ${kindWord}...`;

            const notifyProgress = Utils.throttle((message) => {
                state.notificationType = 'info';
                state.notification = message;
            }, CONFIG.PROGRESS_NOTIFY_INTERVAL);

            const worker = DownloadManager.createZipWorker();

            worker.onmessage = (e) => {
                const { type, message, zipBlob } = e.data;

                if (type === 'progress') {
                    notifyProgress(message);
                } else if (type === 'complete') {
                    const sanitizedTitle = Utils.sanitizeFileName(meta.title);
                    const sanitizedArtistName = Utils.sanitizeFileName(meta.artistName);

                    let zipFileName = state.zipFileNameFormat
                        .replace(/{date_published}/gi, meta.datePublished)
                        .replace(/{date_edited}/gi, meta.dateEdited)
                        .replace(/{date_imported}/gi, meta.dateImported)
                        .replace(/{date}/gi, meta.datePublished)
                        .replace('{artistName}', sanitizedArtistName)
                        .replace('{title}', sanitizedTitle);

                    if (filter !== 'all') {
                        if (zipFileName.toLowerCase().endsWith('.zip')) {
                            zipFileName = zipFileName.slice(0, -4) + `-${zipSuffix}.zip`;
                        } else {
                            zipFileName += `-${zipSuffix}`;
                        }
                    }
                    if (!zipFileName.toLowerCase().endsWith('.zip')) {
                        zipFileName += '.zip';
                    }

                    DOM.saveBlob(zipBlob, zipFileName);

                    state.notification = 'Download complete!';
                    state.notificationType = 'success';
                    DownloadManager.cleanupWorker();
                } else if (type === 'error') {
                    state.notificationType = 'error';
                    state.notification = `Download failed: ${message}`;
                    DownloadManager.cleanupWorker();
                }
            };

            worker.postMessage({
                type: 'init',
                data: { totalFiles: indexedItems.length }
            });

            const streamFiles = async () => {
                let index = 0;
                let running = 0;
                const concurrencyLimit = CONFIG.MAX_CONCURRENT_FETCHES;

                const postDateMs = (
                    state.preserveFileDates &&
                    meta.publishedDate instanceof Date &&
                    !Number.isNaN(meta.publishedDate.getTime())
                ) ? meta.publishedDate.getTime() : null;

                return new Promise((resolve) => {
                    const next = () => {
                        if (!state.isDownloading || index >= indexedItems.length) {
                            if (running === 0) resolve();
                            return;
                        }

                        while (running < concurrencyLimit && index < indexedItems.length) {
                            const entry = indexedItems[index++];
                            running++;

                            (async () => {
                                try {
                                    if (!state.isDownloading) return;
                                    const pathInZip = DownloadManager._buildFileName(entry.item, entry.index);
                                    let blob = await ImageCacheDB.get(entry.item.src);
                                    if (!blob) blob = await ImageLoader.fetchBlobDirect(entry.item.src);
                                    if (!state.isDownloading) return;

                                    if (blob && state.isDownloading && DownloadManager._worker) {
                                        DownloadManager._worker.postMessage({
                                            type: 'addFile',
                                            data: { blob, name: pathInZip, date: postDateMs }
                                        });
                                    }
                                } catch (e) {
                                    console.warn(`Skipping ${entry.item.src}`, e);
                                } finally {
                                    running--;
                                    next();
                                }
                            })();
                        }
                    };
                    next();
                }).then(() => {
                    if (state.isDownloading && DownloadManager._worker) {
                        DownloadManager._worker.postMessage({ type: 'generate' });
                    }
                });
            };

            streamFiles();
        },

        downloadImageByIndex: async (index) => {
            const item = state.originalImageSrcs[index];
            if (!item || !item.src) {
                state.notificationType = 'warning';
                state.notification = 'No media found at this index.';
                return;
            }

            const formattedName = DownloadManager._buildFileName(item, index);

            if (item.type === 'video' || item.type === 'attachment') {
                state.notificationType = 'info';
                state.notification = item.type === 'video'
                    ? 'Starting video download...'
                    : 'Starting file download...';
                const ok = await DownloadManager.downloadFile(item.src, formattedName);
                state.notification = ok
                    ? (item.type === 'video' ? 'Video download complete' : 'File download complete')
                    : (item.type === 'video' ? 'Video download failed' : 'File download failed');
                state.notificationType = ok ? 'success' : 'error';
            } else {
                try {
                    let blob = await ImageCacheDB.get(item.src);
                    if (!blob) blob = await ImageLoader.fetchBlobDirect(item.src);

                    if (blob) {
                        DOM.saveBlob(blob, formattedName);
                        state.notificationType = 'success';
                        state.notification = 'Download started';
                    } else {
                        state.notificationType = 'error';
                        state.notification = 'Download failed: Blob is null';
                    }
                } catch (e) {
                    state.notificationType = 'error';
                    state.notification = 'Download failed';
                    console.error(e);
                }
            }
        },

        cleanupWorker() {
            if (DownloadManager._worker) {
                DownloadManager._worker.terminate();
                DownloadManager._worker = null;
            }
            if (DownloadManager._workerUrl) {
                URL.revokeObjectURL(DownloadManager._workerUrl);
                DownloadManager._workerUrl = null;
            }
            state.isDownloading = false;
        }
    };

    // ====================================================
    // Post Actions & UI Injection
    // ====================================================
    const PostActions = {
        imageLinkClickHandler: (event) => {
            if (event.button !== 0) return;
            const clickedImageLink = event.target.closest(SELECTORS.IMAGE_LINK) || event.target.closest(SELECTORS.VIDEO_LINK);
            if (clickedImageLink) {
                event.preventDefault();
                event.stopPropagation();
            }
        },

        initPostActions() {
            try {
                let postActionsContainer = DOM.$(SELECTORS.POST_ACTIONS);
                if (!postActionsContainer) {
                    const fallbackContainer = DOM.$('.post__body') || DOM.$('.post__files');
                    if (fallbackContainer) {
                        postActionsContainer = DOM.create('div', { className: 'post__actions ug-injected-ui' });
                        fallbackContainer.prepend(postActionsContainer);
                    }
                }

                if (postActionsContainer && !DOM.$('.ug-global-actions', postActionsContainer)) {
                    const globalButtons = DOM.create('div', { className: 'ug-injected-ui ug-global-actions' });
                    elements.galleryButton = UI.createToggleButton('Loading Gallery...', Gallery.toggleGallery, true, 'GALLERY', 'global');

                    globalButtons.append(
                        UI.createToggleButton(BUTTONS.HEIGHT, () => PostActions.resizeAllImages('height'), false, 'HEIGHT', 'global'),
                        UI.createToggleButton(BUTTONS.WIDTH, () => PostActions.resizeAllImages('width'), false, 'WIDTH', 'global'),
                        UI.createToggleButton(BUTTONS.FULL, () => PostActions.resizeAllImages('full'), false, 'FULL', 'global'),
                        UI.createToggleButton(BUTTONS.DOWNLOAD_ALL, () => DownloadManager.downloadAllImages('all'), false, 'DOWNLOAD_ALL', 'global'),
                        UI.createToggleButton(BUTTONS.DOWNLOAD_IMAGES, () => DownloadManager.downloadAllImages('images'), false, 'DOWNLOAD_IMAGES', 'global'),
                        UI.createToggleButton(BUTTONS.DOWNLOAD_ATTACHMENTS, () => DownloadManager.downloadAllImages('attachments'), false, 'DOWNLOAD_ATTACHMENTS', 'global'),
                        elements.galleryButton
                    );

                    postActionsContainer.appendChild(globalButtons);

                    // Apply any previously-saved hide settings to the freshly-created buttons.
                    PostActions.updateButtonVisibilityLight();
                }

                if (!DOM.$('.settings-button-wrapper')) {
                    const settingsButton = DOM.create('button', {
                        type: 'button',
                        text: BUTTONS.SETTINGS,
                        className: 'settings-button',
                        onclick: () => state.settingsOpen = !state.settingsOpen
                    });

                    const wrapper = DOM.create('div', { className: 'settings-button-wrapper ug-injected-ui' }, [settingsButton]);
                    document.body.appendChild(wrapper);
                    elements.settingsButton = settingsButton;
                }

                const filesArea = DOM.$('div.post__files') || DOM.$('section.site-section--post') || DOM.$('.post__body');
                if (filesArea) {
                    DOM.$$(SELECTORS.FILE_DIVS, filesArea).forEach(thumbnailDiv => {
                        const imgElement = thumbnailDiv.querySelector('img');
                        if (!imgElement) return;

                        if (thumbnailDiv.querySelector(`.${CSS.BTN_CONTAINER}`)) return;

                        imgElement.classList.add('post__image');
                        const buttonGroupConfig = [
                            { text: BUTTONS.HEIGHT, action: (evt) => PostActions.resizeImage('height', evt), name: 'HEIGHT' },
                            { text: BUTTONS.WIDTH, action: (evt) => PostActions.resizeImage('width', evt), name: 'WIDTH' },
                            { text: BUTTONS.FULL, action: () => ImageLoader.imageActions.full(imgElement), name: 'FULL' },
                            {
                                text: BUTTONS.DOWNLOAD,
                                action: () => {
                                    const link = imgElement.closest('a');
                                    const originalSrc = link ? link.href.split('?')[0] : imgElement.dataset.originalSrc;
                                    const downloadIndex = state.originalImageSrcs.findIndex(item => item && item.src === originalSrc);
                                    if (downloadIndex > -1) {
                                        DownloadManager.downloadImageByIndex(downloadIndex);
                                    }
                                },
                                name: 'DOWNLOAD'
                            }
                        ];

                        const buttonGroupElement = UI.createButtonGroup(buttonGroupConfig);
                        if (buttonGroupElement.childElementCount > 0) {
                            buttonGroupElement.classList.add('ug-injected-ui');
                            thumbnailDiv.insertBefore(buttonGroupElement, thumbnailDiv.firstChild);
                        }
                    });

                    if (!filesArea.dataset.ugLeftClickHandlerAttached) {
                        filesArea.addEventListener('click', PostActions.imageLinkClickHandler);
                        filesArea.dataset.ugLeftClickHandlerAttached = 'true';
                    }
                }

                ImageLoader.loadImages();
                state.currentPostUrl = window.location.href;
            } catch (error) {
                console.error('Error initializing post actions:', error);
            }
        },

        cleanupPostActions() {
            ActiveRequests.abortAll();
            state.currentLoadSessionId = null;
            ErrorHandler.clearRetries();
            UI.forceHideNotification();

            DOM.$$('img.post__image.ug-image-loaded').forEach(img => img.classList.remove('ug-image-loaded'));
            DOM.$$('.ug-injected-ui').forEach(el => el.remove());
            document.getElementById(CSS.NOTIF_AREA)?.remove();

            const filesArea = DOM.$('div.post__files');
            if (filesArea) {
                filesArea.removeEventListener('click', PostActions.imageLinkClickHandler);
                filesArea.removeAttribute('data-ug-left-click-handler-attached');
            }

            if (state.isGalleryMode) {
                Gallery.closeGallery();
            } else if (galleryOverlay) {
                galleryOverlay.remove();
                galleryOverlay = null;
            }

            if (state.settingsOpen) {
                state.settingsOpen = false;
            } else {
                document.getElementById('ug-settings-overlay')?.remove();
            }

            BlobManager.revokeAll();
            loadedBlobUrls.clear();
            state.notification = null;

            Object.assign(state, {
                fullSizeImageSrcs: [],
                originalImageSrcs: [],
                galleryReady: false,
                loadedImages: 0,
                totalImages: 0,
                errorCount: 0,
                isLoading: false,
                currentPostUrl: null
            });

            elements.galleryButton = null;
            elements.settingsButton = null;
        },

        updateButtonVisibilityLight() {
            const globalHideMap = {
                GALLERY: state.hideGlobalGalleryButton,
                HEIGHT: state.hideGlobalHeightButton,
                WIDTH: state.hideGlobalWidthButton,
                FULL: state.hideGlobalFullButton,
                DOWNLOAD_ALL: state.hideGlobalDownloadAllButton,
                DOWNLOAD_IMAGES: state.hideGlobalDownloadImagesButton,
                DOWNLOAD_ATTACHMENTS: state.hideGlobalDownloadAttachmentsButton
            };
            const thumbnailHideMap = {
                HEIGHT: state.hideThumbnailHeightButton,
                WIDTH: state.hideThumbnailWidthButton,
                FULL: state.hideThumbnailFullButton,
                DOWNLOAD: state.hideThumbnailDownloadButton
            };

            DOM.$$(`.${CSS.BTN}[data-action]`).forEach(btn => {
                const action = btn.dataset.action;
                const scope = btn.dataset.scope || 'global';
                const map = scope === 'thumbnail' ? thumbnailHideMap : globalHideMap;
                if (Object.prototype.hasOwnProperty.call(map, action)) {
                    btn.classList.toggle('ug-hidden', !!map[action]);
                }
            });

            if (galleryOverlay) {
                DOM.$$(`.${CSS.GALLERY.NAV}`, galleryOverlay).forEach(n => {
                    n.classList.toggle('ug-hidden', state.hideNavArrows);
                });
            }
        },

        resizeAllImages(action) {
            if (!ImageLoader.imageActions[action]) return;
            state.currentResizeMode = action;
            DOM.$$('img.post__image').forEach(img => ImageLoader.imageActions[action](img));
        },

        resizeImage(action, evt) {
            if (!ImageLoader.imageActions[action]) return;
            state.currentResizeMode = action;

            const button = evt.currentTarget;
            let thumbContainer = button.closest('.post__thumbnail, .scrape__thumbnail');
            if (!thumbContainer) {
                const buttonContainer = button.closest(`.${CSS.BTN_CONTAINER}`);
                thumbContainer = buttonContainer?.nextElementSibling;
            }

            const displayedImage = thumbContainer?.querySelector('img.post__image');
            if (displayedImage) ImageLoader.imageActions[action](displayedImage);
        }
    };

    // ====================================================
    // Keyboard & Event Handlers
    // ====================================================
    const EventHandlers = {
        keyMatchesSetting: (eventKey, settingValue) => {
            return Boolean(settingValue) && String(eventKey).toLowerCase() === String(settingValue).toLowerCase();
        },

        handleGlobalKeyDown(event) {
            const activeEl = document.activeElement;
            if (activeEl && (activeEl.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(activeEl.tagName))) {
                return;
            }

            const keyLower = event.key.toLowerCase();

            if (Utils.isPostPage() && EventHandlers.keyMatchesSetting(event.key, state.galleryKey)) {
                if (!event.altKey && !event.ctrlKey && !event.metaKey) {
                    event.preventDefault();
                    if (state.galleryReady) {
                        Gallery.toggleGallery();
                    } else {
                        state.notificationType = 'info';
                        state.notification = 'Gallery content is still loading.';
                    }
                }
                return;
            }

            if (state.settingsOpen && event.key === 'Escape') {
                event.preventDefault();
                state.settingsOpen = false;
                return;
            }

            if (state.isGalleryMode && galleryOverlay) {
                const expandedView = DOM.$(`.${CSS.GALLERY.EXPANDED_VIEW}`, galleryOverlay);
                if (event.key === 'Escape') {
                    event.preventDefault();
                    Gallery.closeGallery();
                    return;
                }

                if (expandedView && !expandedView.classList.contains(CSS.GALLERY.HIDE)) {
                    if (EventHandlers.keyMatchesSetting(event.key, state.nextImageKey) || keyLower === 'arrowright') {
                        event.preventDefault();
                        Gallery.nextImage();
                    } else if (EventHandlers.keyMatchesSetting(event.key, state.prevImageKey) || keyLower === 'arrowleft') {
                        event.preventDefault();
                        Gallery.prevImage();
                    } else if (event.key === 'Home') {
                        event.preventDefault();
                        Gallery.showExpandedView(0);
                    } else if (event.key === 'End') {
                        event.preventDefault();
                        Gallery.showExpandedView(state.fullSizeImageSrcs.length - 1);
                    }

                    if (keyLower === '+' || keyLower === '=') {
                        event.preventDefault();
                        Zoom.zoom(CONFIG.ZOOM_STEP);
                    } else if (keyLower === '-') {
                        event.preventDefault();
                        Zoom.zoom(-CONFIG.ZOOM_STEP);
                    } else if (keyLower === '0') {
                        event.preventDefault();
                        Zoom.resetZoom();
                    } else if (keyLower === ' ') {
                        event.preventDefault();
                        Slideshow.toggle();
                    } else if (keyLower === 'f') {
                        event.preventDefault();
                        Gallery.toggleFullscreen();
                    }
                }
            }
        }
    };

    const updateGalleryButton = enabled => {
        if (elements.galleryButton) {
            elements.galleryButton.textContent = enabled ? BUTTONS.GALLERY : 'Loading Gallery...';
            elements.galleryButton.disabled = !enabled;
            elements.galleryButton.classList.toggle('disabled', !enabled);
        }
    };

    // ====================================================
    // SPA-Safe Injection & Initialization
    // ====================================================
    const injectUI = () => {
        try {
            const onPostPage = Utils.isPostPage();
            const postContainer =
                DOM.$('section.site-section--post') ||
                DOM.$('section.site-section--scrape') ||
                DOM.$('.post__body') ||
                DOM.$('.post__content');

            const currentUrl = window.location.href;

            if (onPostPage && postContainer) {
                if (currentUrl !== lastProcessedUrl) {
                    if (
                        DOM.$(SELECTORS.POST_ACTIONS) ||
                        DOM.$('.post__files') ||
                        DOM.$('.post__body')
                    ) {
                        PostActions.cleanupPostActions();
                        PostActions.initPostActions();
                        lastProcessedUrl = currentUrl;
                    }
                }
            } else {
                if (lastProcessedUrl !== null) {
                    PostActions.cleanupPostActions();
                    lastProcessedUrl = null;
                }
            }
        } catch (error) {
            console.error('Error in injectUI:', error);
        }
    };

    const fullCleanup = () => {
        if (uiObserver) {
            uiObserver.disconnect();
            uiObserver = null;
        }

        if (galleryOverlay && document.fullscreenElement === galleryOverlay) {
            const exit = document.exitFullscreen();
            if (exit && typeof exit.catch === 'function') exit.catch(() => {});
        }
        ActiveRequests.abortAll();
        ImageLoader._inflight.clear();
        PostActions.cleanupPostActions();
        Gallery._clearPreloadCache();
        DownloadManager.cleanupWorker();
        UI.forceHideNotification();
        ErrorHandler.clearRetries();
        ThumbnailStrip.cleanup();

        document.removeEventListener('keydown', EventHandlers.handleGlobalKeyDown);
    };

    const init = async () => {
        try {
            const cssText = GM_getResourceText('mainCSS');
            if (cssText) {
                const styleNode = GM_addStyle(cssText);
                if (styleNode && styleNode.setAttribute) {
                    styleNode.setAttribute('data-keep', '');
                } else {
                    const lastStyle = document.head.querySelector('style:last-of-type');
                    if (lastStyle) lastStyle.setAttribute('data-keep', '');
                }
            } else {
                console.warn('Ultra Galleries: Failed to load main CSS resource.');
            }

            // One-time migration from 4.2.x single-scope button-visibility keys.
            if (GM_getValue('hideFullButton') !== undefined && GM_getValue('hideThumbnailFullButton') === undefined) {
                SettingsManager.saveSetting('hideThumbnailFullButton', SettingsManager.loadSetting('hideFullButton', false));
                SettingsManager.saveSetting('hideThumbnailWidthButton', SettingsManager.loadSetting('hideWidthButton', false));
                SettingsManager.saveSetting('hideThumbnailHeightButton', SettingsManager.loadSetting('hideHeightButton', false));
                SettingsManager.saveSetting('hideThumbnailDownloadButton', SettingsManager.loadSetting('hideDownloadButton', false));
            }

            const allSettings = SettingsManager.loadAllSettings();
            Object.assign(state, allSettings);
            Slideshow.init();
            document.body.classList.toggle('ug-animations-disabled', !state.animationsEnabled);
            updateButtonLabels();
            state.notification = null;

            if (state.enablePersistentCaching) {
                ImageCacheDB.init();
            }

            document.addEventListener('keydown', EventHandlers.handleGlobalKeyDown);
            window.addEventListener('beforeunload', fullCleanup);

            let galleryWasNativeFullscreen = false;
            document.addEventListener('fullscreenchange', () => {
                const isGalleryNative = Boolean(galleryOverlay) && document.fullscreenElement === galleryOverlay;
                if (galleryWasNativeFullscreen && !isGalleryNative && state.isFullscreen) {
                    state.isFullscreen = false;
                }
                galleryWasNativeFullscreen = isGalleryNative;
            });

            const debouncedInject = Utils.debounce(injectUI, 150);

            if ('navigation' in window) {
                window.navigation.addEventListener('navigate', (e) => {
                    if (!e.canIntercept || e.hashChange) return;
                    e.intercept({
                        async handler() {
                            await Utils.delay(100);
                            injectUI();
                        }
                    });
                });
            } else {
                const originalPushState = history.pushState;
                history.pushState = function (...args) {
                    originalPushState.apply(this, args);
                    debouncedInject();
                };
                const originalReplaceState = history.replaceState;
                history.replaceState = function (...args) {
                    originalReplaceState.apply(this, args);
                    debouncedInject();
                };
                window.addEventListener('popstate', debouncedInject);
            }

            const UG_ROOT_SELECTOR = '.ug-gallery-overlay, #ug-settings-overlay, .ug-notification-area, .ug-modal-overlay, .ug-injected-ui, .ug-thumbnail-context-menu, .ug-thumbnail-zoom-preview, .settings-button-wrapper';

            uiObserver = new MutationObserver((mutations) => {
                for (const m of mutations) {
                    if (m.target?.closest?.(UG_ROOT_SELECTOR)) continue;
                    for (const node of m.addedNodes) {
                        if (node.nodeType !== 1) continue;
                        if (node.matches?.(UG_ROOT_SELECTOR)) continue;
                        if (node.matches?.('.post__files, .scrape__files, article, .post__body') ||
                            node.querySelector?.('a.fileThumb, .post__thumbnail')) {
                            debouncedInject();
                            return;
                        }
                    }
                }
            });

            uiObserver.observe(document.body, { childList: true, subtree: true });
            injectUI();
            setTimeout(() => ThumbnailStrip.updateThumbnailNumbers(), 50);
        } catch (error) {
            console.error('Error in init:', error);
        }
    };

    init();
})();