Post Gallery

Transforms post list into a gallery.

اعتبارا من 21-09-2025. شاهد أحدث إصدار.

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

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

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

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

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

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

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

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

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

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         Post Gallery
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Transforms post list into a gallery.
// @author       remuru
// @match        *://kemono.cr/*
// @match        *://coomer.st/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      kemono.cr
// @connect      coomer.st
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const DEFAULT_CONFIG = {
        LAYOUT: {
            GRID_GAP: "16px",
            GRID_COLUMN_COUNT: 5
        }
    };
    const MAX_CONCURRENT_DOWNLOADS = 10;
    let settings;
    const currentDomain = window.location.hostname;

    function loadSettings() {
        const savedSettings = localStorage.getItem('gallerySettings');
        settings = savedSettings ? {
            ...{
                gridColumnCount: DEFAULT_CONFIG.LAYOUT.GRID_COLUMN_COUNT
            },
            ...JSON.parse(savedSettings)
        } : {
            gridColumnCount: DEFAULT_CONFIG.LAYOUT.GRID_COLUMN_COUNT
        };
    }

    function saveSettings() {
        localStorage.setItem('gallerySettings', JSON.stringify(settings));
    }

    function addGlobalStyles() {
        const STYLES = `
            body { position: relative; }
            body.lightbox-open { overflow: hidden; }
            .post-card { width: 100% !important; margin: 0 !important; break-inside: avoid; background: rgba(30, 32, 34, 0.8); border-radius: 8px; overflow: hidden; height: auto !important; transition: transform 0.2s ease, box-shadow 0.2s ease; }
            .post-card:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
            #gallery-loader { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 20px; border-radius: 8px; z-index: 10001; display: flex; align-items: center; font-family: sans-serif; }
            .loading-spinner { width: 20px; height: 20px; border: 3px solid #fff; border-radius: 50%; border-top-color: transparent; animation: spin 1s linear infinite; margin-right: 10px; }
            @keyframes spin { to { transform: rotate(360deg); } }

            .post-card__slider-container { position: relative; width: 100%; height: 0; padding-bottom: 125%; background-color: #000; overflow: hidden; }
            .slider-wrapper { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; transition: transform 0.4s ease-in-out; }
            .slider-media-item { width: 100%; height: 100%; object-fit: cover; flex-shrink: 0; display: block; background-color: #000; }
            .slider-btn { position: absolute;  z-index: 10; background-color: rgba(20, 20, 20, 0.6); color: white; border: none; border-radius: 50%; width: 32px; height: 32px; font-size: 20px; line-height: 32px; text-align: center; cursor: pointer; opacity: 0; transition: opacity 0.2s ease, background-color 0.2s ease; user-select: none; }
            .slider-btn-centreY{top: 50%; transform: translateY(-50%);}
            .post-card:hover .slider-btn { opacity: 1; }
            .slider-btn:hover { background-color: rgba(0, 0, 0, 0.8); }
            .slider-btn-prev { left: 8px; }
            .slider-btn-next { right: 8px; }
            .slider-btn-zoom { left: 8px; width: 32px; height: 32px; font-size: 18px; line-height:32px; }
            .slider-counter { position: absolute; top: 8px; right: 8px; z-index: 10; background-color: rgba(20, 20, 20, 0.7); color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: bold; user-select: none; }

            #gallery-paginator-settings { position: relative; margin-left: 20px; } #gallery-settings-toggle { background: #3a3f44; color: #ddd; border: 1px solid #555; border-radius: 4px; padding: 5px 10px; cursor: pointer; font-size: 14px; } #gallery-settings-toggle:hover { background: #4a4f54; }
            #gallery-settings-dropdown { display: none; position: absolute; margin-top:10px; left: 50%; transform: translateX(-50%); margin-bottom: 10px; background-color: rgba(30, 32, 34, 0.95); padding: 8px; border-radius: 8px; z-index: 1000; border: 1px solid #444; width: 220px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); }
            #gallery-settings-dropdown input[type=range] { width: 100%; }

            #gallery-lightbox { display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, 0.9); z-index: 11000; }
            .lightbox-container { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; }
            .lightbox-wrapper { width: 100%; height: 100%; display: flex; transition: transform 0.4s ease-in-out; }
            .lightbox-slide { width: 100%; height: 100%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
            .lightbox-media-item { max-width: 95vw; max-height: 95vh; object-fit: contain; }
            .lightbox-btn { position: absolute; top: 50%; transform: translateY(-50%); z-index: 11002; background-color: rgba(20, 20, 20, 0.6); color: white; border: none; border-radius: 50%; width: 48px; height: 48px; font-size: 28px; cursor: pointer; transition: background-color 0.2s ease; user-select: none; }
            .lightbox-btn:hover { background-color: rgba(0, 0, 0, 0.8); }
            .lightbox-prev-btn { left: 15px; } .lightbox-next-btn { right: 15px; }
            .lightbox-close-btn { top: 15px; right: 15px; transform: none; }
            .lightbox-fullscreen-btn { top: 15px; right: 75px; transform: none; }
            .lightbox-counter { position: absolute; bottom: 15px; left: 50%; transform: translateX(-50%); background-color: rgba(20, 20, 20, 0.7); color: white; padding: 4px 12px; border-radius: 16px; font-size: 16px; user-select: none; }
        `;
        GM_addStyle(STYLES);
        const dynamicStyles = document.createElement('style');
        dynamicStyles.id = 'dynamic-gallery-styles';
        document.head.appendChild(dynamicStyles);
    }

    function updateGridStyle() {
        const dynamicStyles = document.getElementById('dynamic-gallery-styles');
        if (dynamicStyles) {
            dynamicStyles.innerHTML = `.card-list--legacy .card-list__items { display: grid !important; grid-template-columns: repeat(${settings.gridColumnCount}, 1fr); gap: ${DEFAULT_CONFIG.LAYOUT.GRID_GAP}; padding-top: ${DEFAULT_CONFIG.LAYOUT.GRID_GAP}; width: 100%; margin: 0 auto; }`;
        }
    }

    function createPaginatorSettings() {
        if (document.getElementById('gallery-paginator-settings')) return;
        const paginatorMenu = document.querySelector('.paginator menu');
        if (!paginatorMenu) return;
        const container = document.createElement('div');
        container.id = 'gallery-paginator-settings';
        container.innerHTML = `<button id="gallery-settings-toggle">Columns: <span id="gallery-column-count-display">${settings.gridColumnCount}</span></button><div id="gallery-settings-dropdown"><input type="range" id="column-count-slider" min="1" max="12" step="1" value="${settings.gridColumnCount}"></div>`;
        paginatorMenu.insertAdjacentElement('afterend', container);
        const toggleBtn = container.querySelector('#gallery-settings-toggle');
        const dropdown = container.querySelector('#gallery-settings-dropdown');
        const slider = container.querySelector('#column-count-slider');
        const display = container.querySelector('#gallery-column-count-display');
        toggleBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
        });
        document.addEventListener('click', (e) => {
            if (!container.contains(e.target)) {
                dropdown.style.display = 'none';
            }
        });
        slider.addEventListener('input', () => {
            settings.gridColumnCount = parseInt(slider.value, 10);
            display.textContent = settings.gridColumnCount;
            updateGridStyle();
        });
        slider.addEventListener('change', saveSettings);
    }

    function createLightbox() {
        if (document.getElementById('gallery-lightbox')) return;
        const lightbox = document.createElement('div');
        lightbox.id = 'gallery-lightbox';
        lightbox.innerHTML = `
            <button class="lightbox-btn lightbox-close-btn">&times;</button>
            <button class="lightbox-btn lightbox-fullscreen-btn">&#x26F6;</button>
            <div class="lightbox-container">
                <button class="lightbox-btn lightbox-prev-btn">&#10094;</button>
                <div class="lightbox-wrapper"></div>
                <button class="lightbox-btn lightbox-next-btn">&#10095;</button>
            </div>
            <div class="lightbox-counter"></div>
        `;
        document.body.appendChild(lightbox);
        lightbox.querySelector('.lightbox-close-btn').addEventListener('click', closeLightbox);
        lightbox.querySelector('.lightbox-fullscreen-btn').addEventListener('click', () => toggleFullscreen(lightbox));
    }

    function createMediaElement(slide, slideIndex, currentIndex) {
        if (!slide || slide.childElementCount > 0) {
            return;
        }

        const { type, fullSrc, src } = slide.dataset;
        let mediaElement;

        if (type === 'video') {
            mediaElement = document.createElement('video');
            mediaElement.src = src;
            Object.assign(mediaElement, {
                controls: true,
                autoplay: slideIndex === currentIndex,
                muted: true,
                loop: true
            });
        } else {
            mediaElement = document.createElement('img');
            mediaElement.src = fullSrc;
        }

        mediaElement.className = 'lightbox-media-item';
        slide.appendChild(mediaElement);
    }

    function preloadLightboxSlides(wrapper, currentIndex, PRELOAD_WINDOW) {
        const slides = wrapper.children;
        const totalSlides = slides.length;

        for (let i = currentIndex - PRELOAD_WINDOW; i <= currentIndex + PRELOAD_WINDOW; i++) {
            const slideIndex = (i + totalSlides) % totalSlides;
            const slide = slides[slideIndex];
            createMediaElement(slide, slideIndex, currentIndex);
        }
    }

    function openLightbox(mediaData, startingIndex) {
        const lightbox = document.getElementById('gallery-lightbox');
        const wrapper = lightbox.querySelector('.lightbox-wrapper');
        if (!lightbox || !wrapper) return;

        wrapper.innerHTML = '';
        mediaData.forEach(item => {
            const slide = document.createElement('div');
            slide.className = 'lightbox-slide';
            slide.addEventListener('click', (e) => {
                if (e.target === slide) closeLightbox();
            });
            slide.dataset.type = item.type;
            if (item.type === 'video') {
                slide.dataset.src = item.src;
            } else {
                slide.dataset.fullSrc = item.full;
            }
            wrapper.appendChild(slide);
        });
        preloadLightboxSlides(wrapper, startingIndex, 0)
        setupLightboxSlider(wrapper, startingIndex);
        document.body.classList.add('lightbox-open');
        lightbox.style.display = 'block';
    }

    function closeLightbox() {
        const lightbox = document.getElementById('gallery-lightbox');
        if (!lightbox) return;
        document.body.classList.remove('lightbox-open');
        lightbox.style.display = 'none';
        lightbox.querySelector('.lightbox-wrapper').innerHTML = '';
        if (document.fullscreenElement) document.exitFullscreen();
    }

    function toggleFullscreen(element) {
        if (!document.fullscreenElement) {
            element.requestFullscreen().catch(err => alert(`Error: ${err.message}`));
        } else {
            document.exitFullscreen();
        }
    }

    function showLoader() {
        if (!document.getElementById('gallery-loader')) {
            const l = document.createElement('div');
            l.id = 'gallery-loader';
            l.innerHTML = `<div class="loading-spinner"></div><span>Gallery: Loading...</span>`;
            document.body.appendChild(l);
        }
    }

    function hideLoader() {
        const l = document.getElementById('gallery-loader');
        if (l) l.remove();
    }

    function determinePageContext() {
        const pathname = window.location.pathname;
        const searchParams = new URLSearchParams(window.location.search);
        const query = searchParams.get('q');
        const userProfileMatch = pathname.match(/^\/([^/]+)\/user\/([^/]+)$/);
        if (userProfileMatch && !query) return {
            type: 'profile',
            service: userProfileMatch[1],
            userId: userProfileMatch[2]
        };
        if (userProfileMatch && query) return {
            type: 'user_search',
            service: userProfileMatch[1],
            userId: userProfileMatch[2],
            query
        };
        if (pathname === '/posts/popular') {
            return {
                type: 'popular_posts',
                date: searchParams.get('date') || 'none',
                period: searchParams.get('period') || 'recent'
            };
        };
        if (pathname === '/posts') return {
            type: 'global_search',
            query: query || null
        };
        console.error('Video Filter: Unknown page structure for context.', pathname, window.location.search);
        return null;
    }

    function buildApiUrl(context, offset) {
        let baseApiUrl = `https://${currentDomain}/api/v1`;
        let queryParams = [];
        switch (context.type) {
            case 'profile':
                if (offset > 0) queryParams.push(`o=${offset}`);
                return `${baseApiUrl}/${context.service}/user/${context.userId}/posts?${queryParams.join('&')}`;
            case 'user_search':
                queryParams.push(`q=${encodeURIComponent(context.query)}`);
                if (offset > 0) queryParams.push(`o=${offset}`);
                return `${baseApiUrl}/${context.service}/user/${context.userId}/posts?${queryParams.join('&')}`;
            case 'global_search':
                if (offset > 0) queryParams.push(`o=${offset}`);
                if (context.query) queryParams.push(`q=${encodeURIComponent(context.query)}`);
                return `${baseApiUrl}/posts?${queryParams.join('&')}`;
            case 'popular_posts':
                if (context.date != 'none' && context.period != "recent") queryParams.push(`date=${encodeURIComponent(context.date)}`);
                if (offset > 0) queryParams.push(`o=${offset}`);
                queryParams.push(`period=${encodeURIComponent(context.period)}`);
                return `${baseApiUrl}/posts/popular?${queryParams.join('&')}`;
            default:
                return null;
        }
    }

    function fetchData(apiUrl) {
        console.log('apiUrl', apiUrl);
        const headers = { "Accept": "text/css", "Referer": window.location.href, "User-Agent": navigator.userAgent, "X-Requested-With": "XMLHttpRequest" };
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: apiUrl,
                headers: headers,
                onload: resp => {
                    if (resp.status >= 200 && resp.status < 300) {
                        try {
                            resolve(JSON.parse(resp.responseText));
                        } catch (e) {
                            reject(`Error parsing JSON: ${e.message}`);
                        }
                    } else {
                        reject(`API request failed: ${resp.status}`);
                    }
                },
                onerror: resp => reject(`API request error: ${resp.statusText || 'Network error'}`)
            });
        });
    }

    function processApiData(posts) {
        const mediaMap = new Map();
        for (const post of posts) {
            if (!post.id) continue;
            const postVideos = [],
                postImages = [],
                allFiles = [post.file, ...post.attachments].filter(f => f && f.path);
            for (const file of allFiles) {
                if (!file.path) continue;
                const n = file.name ? file.name.toLowerCase() : '',
                    u = `https://${currentDomain}/data${file.path}`;
                if (['.mp4', '.webm', '.mov', '.m4v'].some(e => n.endsWith(e))) postVideos.push({
                    type: 'video',
                    src: u
                });
                else if (['.png', '.jpg', '.jpeg', '.gif', '.webp'].some(e => n.endsWith(e))) postImages.push({
                    type: 'image',
                    thumbnail: u.replace(`https://${currentDomain}/`, `https://img.${currentDomain}/thumbnail/`),
                    full: u
                });
            }
            const uniqueVideos = Array.from(new Map(postVideos.map(v => [v.src, v])).values()),
                uniqueImages = Array.from(new Map(postImages.map(img => [img.full, img])).values()),
                posterUrl = uniqueImages.length > 0 ? uniqueImages[0].thumbnail : null;
            uniqueVideos.forEach(v => v.poster = posterUrl);
            mediaMap.set(post.id, {
                media: [...uniqueVideos, ...uniqueImages]
            });
        }
        return mediaMap;
    }

    function setupLightboxSlider(wrapper, startingIndex) {
        const lightbox = document.getElementById('gallery-lightbox');
        const prevBtn = lightbox.querySelector('.lightbox-prev-btn');
        const nextBtn = lightbox.querySelector('.lightbox-next-btn');
        const counter = lightbox.querySelector('.lightbox-counter');
        const slides = wrapper.children;
        const totalSlides = slides.length;
        let currentIndex = startingIndex;

        if (totalSlides <= 1) {
            if (prevBtn) prevBtn.style.display = 'none';
            if (nextBtn) nextBtn.style.display = 'none';
            if (counter) counter.style.display = 'none';
            updateView();
            return;
        } else {
            if (prevBtn) prevBtn.style.display = 'block';
            if (nextBtn) nextBtn.style.display = 'block';
            if (counter) counter.style.display = 'block';
        }


        function updateView(previousIndex) {
            wrapper.style.transform = `translateX(-${currentIndex * 100}%)`;
            preloadLightboxSlides(wrapper, currentIndex, 2);
            if (counter) counter.textContent = `${1 + parseInt(currentIndex)} / ${totalSlides}`;

            if (previousIndex !== undefined && previousIndex !== currentIndex) {
                const prevSlide = slides[previousIndex];
                const prevMedia = prevSlide ? prevSlide.querySelector('video') : null;
                if (prevMedia) prevMedia.pause();
            }

            const currentSlide = slides[currentIndex];
            const currentMedia = currentSlide ? currentSlide.querySelector('video') : null;
            if (currentMedia) currentMedia.play().catch(() => {});
        }

        const clickHandler = (direction) => {
            const previousIndex = currentIndex;
            if (direction === -1) {
                currentIndex = (currentIndex - 1 + totalSlides) % totalSlides;
            } else {
                currentIndex = (currentIndex + 1) % totalSlides;
            }

            if (previousIndex !== currentIndex) {
                updateView(previousIndex);
            }
        };

        prevBtn.onclick = (e) => {
            e.stopPropagation();
            clickHandler(-1);
        };
        nextBtn.onclick = (e) => {
            e.stopPropagation();
            clickHandler(1);
        };

        updateView();
    }

    function setupCardSlider(container) {
        const wrapper = container.querySelector('.slider-wrapper');
        const prevBtn = container.querySelector('.slider-btn-prev');
        const nextBtn = container.querySelector('.slider-btn-next');
        const counter = container.querySelector('.slider-counter');
        const mediaElements = Array.from(wrapper.children);
        const totalSlides = mediaElements.length;
        let currentIndex = 0;
        let loadedCount = 1;
        const PRELOAD_CHUNK_SIZE = 5;

        if (!prevBtn || !nextBtn) return;

        if (totalSlides <= 1) {
            prevBtn.style.display = 'none';
            nextBtn.style.display = 'none';
            return;
        }

        const updateView = () => {
            wrapper.style.transform = `translateX(-${currentIndex * 100}%)`;
            container.dataset.currentIndex = currentIndex;
            if (counter) counter.textContent = `${currentIndex + 1} / ${totalSlides}`;
        };

        const clickHandler = (direction) => {
            if (direction === -1) {
                currentIndex = (currentIndex - 1 + totalSlides) % totalSlides;
            } else if (direction === 1) {
                currentIndex = (currentIndex + 1) % totalSlides;
            } else {
                return;
            }

            if (currentIndex >= loadedCount - 1) {
                const loadUpTo = Math.min(currentIndex + PRELOAD_CHUNK_SIZE, totalSlides);
                for (let i = loadedCount; i < loadUpTo; i++) {
                    const img = mediaElements[i];
                    if (img && img.tagName === 'IMG' && !img.src) {
                        img.src = img.dataset.thumbnailSrc;
                    }
                }
                if (loadUpTo > loadedCount) loadedCount = loadUpTo;
            }
            updateView();
        };

        prevBtn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            clickHandler(-1);
        };
        nextBtn.onclick = (e) => {
            e.preventDefault();
            e.stopPropagation();
            clickHandler(1);
        };
        updateView();
    }


    function updateDOM(mediaMap) {
        document.querySelectorAll('.post-card:not([data-gallery-processed])').forEach(card => {
            card.dataset.galleryProcessed = 'true';
            const postId = card.dataset.id;
            if (!postId) return;

            const data = mediaMap.get(postId);
            if (!data || !data.media || data.media.length === 0) return;

            const mediaItems = data.media;
            const originalContainer = card.querySelector('.post-card__image-container, .post-card__video-container');
            const newMediaContainer = document.createElement('div');
            newMediaContainer.className = 'post-card__slider-container';

            const wrapper = document.createElement('div');
            wrapper.className = 'slider-wrapper';

            mediaItems.forEach((item, index) => {
                let mediaElement;
                if (item.type === 'video') {
                    mediaElement = document.createElement('video');
                    Object.assign(mediaElement, {
                        loop: true,
                        muted: true,
                        preload: "metadata",
                        controls: true
                    });
                    if (item.poster) mediaElement.poster = item.poster;
                    mediaElement.dataset.src = item.src;
                    lazyLoadObserver.observe(mediaElement);
                } else {
                    mediaElement = document.createElement('img');
                    if (index === 0) {
                        mediaElement.src = item.thumbnail;
                    } else {
                        mediaElement.dataset.thumbnailSrc = item.thumbnail;
                    }
                    mediaElement.dataset.fullSrc = item.full;
                }
                mediaElement.className = 'slider-media-item';
                wrapper.appendChild(mediaElement);
            });
            newMediaContainer.appendChild(wrapper);

            let buttonsHTML = '';
            if (mediaItems.length > 1) {
                buttonsHTML += `<button class="slider-btn slider-btn-prev slider-btn-centreY">&#10094;</button><button class="slider-btn slider-btn-next slider-btn-centreY">&#10095;</button><div class="slider-counter">1 / ${mediaItems.length}</div>`;
            }
            buttonsHTML += `<button class="slider-btn slider-btn-zoom" title="View in Lightbox">&#x26F6;</button>`;
            newMediaContainer.insertAdjacentHTML('beforeend', buttonsHTML);

            if (originalContainer) originalContainer.replaceWith(newMediaContainer);
            else {
                const header = card.querySelector('.post-card__header');
                if (header) header.insertAdjacentElement('afterend', newMediaContainer);
                else card.prepend(newMediaContainer);
            }

            const zoomBtn = newMediaContainer.querySelector('.slider-btn-zoom');
            if (zoomBtn) {
                zoomBtn.addEventListener('click', (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    const currentIndex = newMediaContainer.dataset.currentIndex || 0;
                    openLightbox(mediaItems, currentIndex);
                });
            }

            if (mediaItems.length > 1) {
                setupCardSlider(newMediaContainer);
            }

            const firstVideo = newMediaContainer.querySelector('video');
            if (firstVideo) {
                card.addEventListener('mouseenter', () => {
                    firstVideo.play().catch(() => {});
                });
                card.addEventListener('mouseleave', () => {
                    firstVideo.pause();
                });
            }

        });
    }

    let downloadQueue = [];
    let activeDownloads = 0;

    function getVideoMimeType(u) {
        const e = u.split('.').pop().toLowerCase();
        switch (e) {
            case 'mp4':
            case 'm4v':
                return 'video/mp4';
            case 'webm':
                return 'video/webm';
            case 'mov':
                return 'video/quicktime';
            default:
                return 'video/mp4';
        }
    }

    function startVideoLoad(v) {
        const onComplete = () => {
            activeDownloads--;
            processQueue();
        };
        v.addEventListener('loadeddata', onComplete, {
            once: true
        });
        v.addEventListener('error', () => {
            console.error('Video load error:', v.dataset.src);
            onComplete();
        }, {
            once: true
        });
        const s = document.createElement('source');
        s.src = v.dataset.src;
        s.type = getVideoMimeType(v.dataset.src);
        v.appendChild(s);
        v.load();
    }

    function processQueue() {
        while (activeDownloads < MAX_CONCURRENT_DOWNLOADS && downloadQueue.length > 0) {
            activeDownloads++;
            const v = downloadQueue.shift();
            startVideoLoad(v);
        }
    }
    const lazyLoadObserver = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                const m = entry.target;
                observer.unobserve(m);
                if (m.tagName === 'VIDEO') {
                    downloadQueue.push(m);
                    processQueue();
                }
            }
        });
    }, {
        rootMargin: "200px"
    });

    async function runScript() {
        const postContainer = document.querySelector('.card-list__items');
        if (!postContainer || postContainer.dataset.galleryInitialized) return;
        postContainer.dataset.galleryInitialized = 'true';

        showLoader();
        createPaginatorSettings();
        try {
            const context = determinePageContext();
            const apiUrl = buildApiUrl(context, new URLSearchParams(window.location.search).get('o') || 0);
            if (!apiUrl) throw new Error("Could not build API URL.");
            const apiResponse = await fetchData(apiUrl);
            const postsArray = Array.isArray(apiResponse) ? apiResponse : (apiResponse && Array.isArray(apiResponse.posts)) ? apiResponse.posts : [];
            if (postsArray.length === 0) {
                hideLoader();
                return;
            }
            updateDOM(processApiData(postsArray));
        } catch (error) {
            console.error("Gallery script failed:", error);
        } finally {
            hideLoader();
            console.log('runScript!!!!!!!!');
        }
    }

    function setupNavigationObserver() {
        const originalPushState = history.pushState;
        const main = document.querySelector('#main');
        history.pushState = function(...args) {
            originalPushState.apply(this, args);
            setTimeout(runScript, 100);
        };
        window.addEventListener('popstate', runScript);
        new MutationObserver(() => {
            if (document.querySelector('.card-list__items')) {
                runScript();
            }
        }).observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    function initialize() {
        loadSettings();
        addGlobalStyles();
        createLightbox();
        updateGridStyle();
        (document.readyState === 'loading') ? document.addEventListener('DOMContentLoaded', runScript): runScript();
        setupNavigationObserver();
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') closeLightbox();
        });
    }
    initialize();

})();