LPSG Video Unlocker

Unlocks hidden videos and adds a premium gallery.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         LPSG Video Unlocker
// @namespace    CurlyWurly
// @version      5.4.7
// @description  Unlocks hidden videos and adds a premium gallery.
// @author       CurlyWurly
// @match        https://www.lpsg.com/*
// @icon         data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pjxzdmcgdmlld0JveD0iMCAwIDI1NiAyNTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3QgZmlsbD0ibm9uZSIgaGVpZ2h0PSIyNTYiIHdpZHRoPSIyNTYiLz48cGF0aCBkPSJNOTMuMiwxMjIuOEE3MC4zLDcwLjMsMCwwLDEsODgsOTZhNzIsNzIsMCwxLEsNzIsNzIsNzAuMyw3MC4zLDAsMCwxLTI2LjgtNS4yaDBMMTIwLDE3Nkg5NnYyNEg3MnYyMEgzMlYxODRsNjEuMi02MS4yWiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkZENzAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMTYiLz48Y2lyY2xlIGN4PSIxODAiIGN5PSI3NiIgcj0iMTIiIGZpbGw9IiNGRkQ3MDAiLz48L3N2Zz4=
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
        (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);

    const config = {
        volume: 0.1,
        formats: ['mp4', 'm4v', 'mov'],
        glassBase: 'rgba(20, 20, 20, 0.85)',
        glassBorder: '1px solid rgba(255, 255, 255, 0.1)',
        highlight: '#FFD700',
        maxRetries: 3,
        retryDelay: 1000
    };

    const styles = `
        :root {
            --pg-glass: ${config.glassBase};
            --pg-border: ${config.glassBorder};
            --pg-highlight: ${config.highlight};
            --pg-text: #fff;
        }

        .p-navgroup-link--iconic--media {
            color: inherit;
        }

        #pg-container {
            position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
            background: rgba(0,0,0,0.92);
            backdrop-filter: blur(15px);
            z-index: 99999;
            display: flex; flex-direction: column;
            opacity: 0; transition: opacity 0.3s ease;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            overscroll-behavior: none;
        }
        #pg-container.pg-visible { opacity: 1; }

        .pg-header {
            padding: 15px 20px;
            display: flex; justify-content: space-between; align-items: center;
            background: rgba(0,0,0,0.4);
            border-bottom: var(--pg-border);
            flex-shrink: 0;
        }
        .pg-filters { display: flex; gap: 10px; }
        .pg-filter-btn {
            background: transparent; border: var(--pg-border); color: #aaa;
            padding: 6px 16px; border-radius: 20px; cursor: pointer;
            font-size: 13px; transition: all 0.2s;
        }
        .pg-filter-btn.active, .pg-filter-btn:hover {
            background: rgba(255, 215, 0, 0.15); border-color: var(--pg-highlight); color: #fff;
        }
        .pg-close {
            background: none; border: none; color: #fff; font-size: 24px; cursor: pointer;
        }

        .pg-gallery-scroll {
            flex-grow: 1; overflow-y: auto; padding: 20px;
            scrollbar-width: thin; scrollbar-color: #444 transparent;
            overscroll-behavior: contain;
            -webkit-overflow-scrolling: touch;
        }
        .pg-grid {
            column-count: 2; column-gap: 15px;
            width: 100%; max-width: 1600px; margin: 0 auto;
        }
        @media (min-width: 768px) { .pg-grid { column-count: 3; } }
        @media (min-width: 1200px) { .pg-grid { column-count: 4; } }
        @media (min-width: 1600px) { .pg-grid { column-count: 5; } }

        .pg-item {
            break-inside: avoid; margin-bottom: 15px;
            position: relative; border-radius: 8px; overflow: hidden;
            border: var(--pg-border); background: #111;
            cursor: pointer; transition: transform 0.2s;
        }
        .pg-item:hover { transform: scale(1.02); z-index: 2; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }

        .pg-item img, .pg-item video {
            width: 100%; height: auto; display: block; object-fit: cover;
        }

        .pg-preview-video {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            object-fit: cover; z-index: 1;
            pointer-events: none;
        }

        .pg-badge {
            position: absolute; top: 8px; right: 8px;
            background: rgba(0,0,0,0.7); color: #fff;
            padding: 3px 8px; border-radius: 4px; font-size: 10px;
            font-weight: bold; pointer-events: none;
            backdrop-filter: blur(4px); z-index: 5;
        }
        .pg-badge.video { color: var(--pg-highlight); }

        .pg-status-overlay {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(20, 20, 20, 0.85);
            display: flex; flex-direction: column;
            align-items: center; justify-content: center;
            z-index: 4;
            backdrop-filter: blur(2px);
            opacity: 1; transition: opacity 0.3s;
        }
        .pg-status-text {
            color: #ccc; font-size: 12px; margin-top: 5px;
            text-align: center;
        }
        .pg-status-icon { font-size: 20px; }
        .pg-status-retry { color: var(--pg-highlight); animation: pg-pulse 1.5s infinite; }
        .pg-status-broken { color: #ff5555; }

        @keyframes pg-pulse { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } }

        .pg-lazy-img {
            opacity: 0;
            transition: opacity 0.4s ease-in-out;
        }
        .pg-lazy-img.pg-loaded {
            opacity: 1;
        }

        #pg-lightbox {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.95);
            display: flex; flex-direction: column;
            z-index: 100000; opacity: 0; pointer-events: none; transition: opacity 0.3s;
        }
        #pg-lightbox.active { opacity: 1; pointer-events: auto; }

        .pg-lb-content {
            flex-grow: 1; display: flex; align-items: center; justify-content: center;
            overflow: hidden; padding: 20px; position: relative;
        }
        .pg-lb-media {
            max-width: 100%; max-height: 100%;
            object-fit: contain; box-shadow: 0 0 30px rgba(0,0,0,0.5);
            border-radius: 4px;
        }

        .pg-lb-header {
            position: absolute; top: 0; left: 0; right: 0;
            padding: 15px; display: flex; justify-content: center;
            z-index: 10; background: linear-gradient(to bottom, rgba(0,0,0,0.7), transparent);
        }
        .pg-lb-counter { font-size: 14px; color: #fff; background: rgba(0,0,0,0.4); padding: 4px 12px; border-radius: 20px; }

        .pg-lb-controls {
            position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%);
            display: flex; gap: 20px; align-items: center;
            background: rgba(30,30,30,0.8); padding: 10px 25px; border-radius: 30px;
            border: var(--pg-border); backdrop-filter: blur(10px);
            z-index: 10;
            opacity: 0.2;
            transition: opacity 0.3s ease, background 0.3s;
        }
        .pg-lb-controls:hover { opacity: 1; background: rgba(30,30,30,0.95); }

        .pg-ctrl-btn {
            background: none; border: none; color: #fff; font-size: 20px; cursor: pointer;
            padding: 5px 10px; transition: color 0.2s;
            display: flex; align-items: center; justify-content: center;
        }
        .pg-ctrl-btn:hover { color: var(--pg-highlight); }
        .pg-ctrl-btn:disabled { color: #444; cursor: default; }

        @keyframes pg-play-appear {
            from { opacity: 0; transform: translate(-50%, -50%) scale(0.7); }
            to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
        }

        .pg-ios-play-btn {
            position: absolute; top: 50%; left: 50%;
            transform: translate(-50%, -50%);
            width: 80px; height: 80px;
            background: rgba(255, 255, 255, 0.12);
            backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
            border-radius: 50%;
            display: flex; align-items: center; justify-content: center;
            border: 1.5px solid rgba(255, 255, 255, 0.2);
            cursor: pointer;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
            transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
                        background 0.25s ease,
                        box-shadow 0.25s ease;
            animation: pg-play-appear 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) both;
        }
        .pg-ios-play-btn:hover {
            transform: translate(-50%, -50%) scale(1.12);
            background: rgba(255, 255, 255, 0.22);
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
        }
        .pg-ios-play-btn:active {
            transform: translate(-50%, -50%) scale(0.95);
        }
        .pg-ios-play-btn svg {
            filter: drop-shadow(0 1px 3px rgba(0,0,0,0.3));
            margin-left: 3px;
        }

        .pg-loader {
            position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
            width: 30px; height: 30px; border: 3px solid #333; border-top-color: var(--pg-highlight);
            border-radius: 50%; animation: spin 1s linear infinite;
        }
        @keyframes spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
    `;

    const styleSheet = document.createElement("style");
    styleSheet.innerText = styles;
    document.head.appendChild(styleSheet);

    // --- SVG Icons ---
    const gridIconSvg = `<svg viewBox="0 0 24 24" width="22" height="22" stroke="white" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>`;
    const warnIconSvg = `<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`;
    const brokenIconSvg = `<svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>`;

    // --- Unlock Logic ---
    function unlockVideos() {
        const easterEggPoster = document.getElementsByClassName("video-easter-egg-poster");

        [...easterEggPoster].forEach(poster => {
            const img = poster.querySelector('img');
            if (!img) return;

            const imageUrl = img.src;
            const sourceElements = config.formats.map(format => {
                let videoUrl = imageUrl.replace("attachments/posters", "video")
                    .replace("/lsvideo/thumbnails", "lsvideo/videos")
                    .replace(/\.(jpg|jpeg|png)$/i, `.${format}`);

                let mimeType = format === 'mp4' ? 'video/mp4' : (format === 'mov' ? 'video/quicktime' : 'video/x-m4v');
                return `<source src="${videoUrl}" type="${mimeType}">`;
            }).join('');

            const videoHTML = `
                <div class="newVideoDiv" style="display: inline-block; max-width: 100%;">
                    <video class="message-cell--main-video" controls playsinline preload="metadata"
                           poster="${imageUrl}" style="max-width: 100%; width: auto; max-height: 80vh; display: block; margin: 0 auto;">
                        ${sourceElements}
                        <div class="bbMediaWrapper-fallback">Video format not supported.</div>
                    </video>
                </div>`;

            poster.insertAdjacentHTML('afterend', videoHTML);
            poster.remove();
        });

        document.querySelectorAll('.video-easter-egg-blocker, .video-easter-egg-overlay').forEach(el => el.remove());

        document.querySelectorAll('video').forEach(v => {
            if (!v.dataset.volSet) {
                v.volume = config.volume;
                v.dataset.volSet = "true";
            }
        });

        document.querySelectorAll('img[loading="lazy"]').forEach(img => {
            img.loading = 'eager';
            if (img.dataset.src) img.src = img.dataset.src;
        });
    }

    // --- State Management ---
    let galleryState = {
        media: [],
        filteredMedia: [],
        currentIndex: 0,
        filter: 'all',
        container: null,
        lightbox: null
    };

    // --- Scroll Lock ---
    let savedScrollY = 0;

    function lockBodyScroll() {
        savedScrollY = window.scrollY || document.documentElement.scrollTop || 0;
        document.body.style.position = 'fixed';
        document.body.style.top = `-${savedScrollY}px`;
        document.body.style.left = '0';
        document.body.style.right = '0';
        document.body.style.width = '100%';
        document.body.style.overflow = 'hidden';
    }

    function unlockBodyScroll() {
        document.body.style.position = '';
        document.body.style.top = '';
        document.body.style.left = '';
        document.body.style.right = '';
        document.body.style.width = '';
        document.body.style.overflow = '';
        window.scrollTo(0, savedScrollY);
    }

    // --- Sequential Retry Logic ---
    const retryQueue = [];
    let isRetrying = false;

    function queueRetry(imgElement, fullSrc, attemptNum) {
        retryQueue.push({ img: imgElement, src: fullSrc, attempt: attemptNum });
        if (!isRetrying) {
            processRetryQueue();
        }
    }

    function processRetryQueue() {
        if (retryQueue.length === 0) {
            isRetrying = false;
            return;
        }

        isRetrying = true;
        const item = retryQueue.shift();
        const dynamicDelay = config.retryDelay * item.attempt;

        setTimeout(() => {
            const separator = item.src.includes('?') ? '&' : '?';
            const timestamp = Date.now();
            item.img.src = `${item.src}${separator}retry=${timestamp}`;
            processRetryQueue();
        }, dynamicDelay);
    }

    function setStatusOverlay(cardElement, type, text) {
        let overlay = cardElement.querySelector('.pg-status-overlay');
        if (!overlay) {
            overlay = document.createElement('div');
            overlay.className = 'pg-status-overlay';
            cardElement.appendChild(overlay);
        }

        if (type === 'clear') {
            overlay.remove();
            return;
        }

        const iconHtml = type === 'retry'
            ? `<div class="pg-status-icon pg-status-retry">${warnIconSvg}</div>`
            : `<div class="pg-status-icon pg-status-broken">${brokenIconSvg}</div>`;

        const txtClass = type === 'broken' ? 'pg-status-broken' : 'pg-status-retry';

        overlay.innerHTML = `
            ${iconHtml}
            <div class="pg-status-text ${txtClass}">${text}</div>
        `;
    }

    // --- Lazy Loading ---
    let lazyImageObserver;

    function initLazyObserver() {
        if (lazyImageObserver) lazyImageObserver.disconnect();

        const options = {
            root: document.querySelector('.pg-gallery-scroll'),
            rootMargin: '200px',
            threshold: 0.01
        };

        lazyImageObserver = new IntersectionObserver((entries, observer) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    const src = img.dataset.src;
                    if (src) {
                        img.src = src;
                        observer.unobserve(img);
                    }
                }
            });
        }, options);

        document.querySelectorAll('.pg-lazy-img').forEach(img => {
            lazyImageObserver.observe(img);
        });
    }

    function scanMedia() {
        const videos = Array.from(document.querySelectorAll('.message-cell--main video')).map(v => {
            const posterSrc = v.getAttribute('data-poster') || v.getAttribute('poster') || v.poster;
            return {
                type: 'video',
                el: v,
                src: v.currentSrc || v.querySelector('source')?.src,
                poster: posterSrc,
                width: v.videoWidth || 300,
                height: v.videoHeight || 169,
                id: v.src || Math.random().toString(36)
            };
        });

        const posterUrls = new Set(videos.map(v => v.poster));

        const images = Array.from(document.querySelectorAll('.message-cell--main img'))
            .filter(img => {
                const notPoster = !posterUrls.has(img.src);
                const isLinkedFile = img.closest('.file.file--linked');
                const isAttachment = img.src.includes('/attachments/') || img.src.includes('/data/attachments/');
                if (img.width < 50 && img.height < 50) return false;
                return (isLinkedFile || isAttachment) && notPoster;
            })
            .map(img => {
                const parentLink = img.closest('a');
                let fullUrl = img.src;

                const w = parseInt(img.getAttribute('width') || img.naturalWidth || 0);
                const h = parseInt(img.getAttribute('height') || img.naturalHeight || 0);

                if (parentLink && parentLink.href) {
                    if (parentLink.href.includes('/attachments/')) {
                        fullUrl = parentLink.href;
                    }
                    else if (parentLink.href.match(/\.(jpg|jpeg|png|gif|webp)$/i)) {
                        fullUrl = parentLink.href;
                    }
                }

                const isGif = fullUrl.toLowerCase().endsWith('.gif');

                return {
                    type: 'image',
                    subType: isGif ? 'gif' : 'img',
                    el: img,
                    src: fullUrl,
                    thumb: img.src,
                    width: w,
                    height: h,
                    id: fullUrl
                };
            });

        return [...videos, ...images];
    }

    function buildGalleryUI() {
        if (document.getElementById('pg-container')) return;

        const container = document.createElement('div');
        container.id = 'pg-container';

        container.innerHTML = `
            <div class="pg-header">
                <div class="pg-filters">
                    <button class="pg-filter-btn active" data-filter="all">All (<span id="pg-count-all">0</span>)</button>
                    <button class="pg-filter-btn" data-filter="image">Images</button>
                    <button class="pg-filter-btn" data-filter="video">Videos</button>
                </div>
                <button class="pg-close">✖</button>
            </div>
            <div class="pg-gallery-scroll">
                <div class="pg-grid" id="pg-grid"></div>
            </div>

            <div id="pg-lightbox">
                <div class="pg-lb-header">
                    <span class="pg-lb-counter"></span>
                </div>
                <div class="pg-lb-content" id="pg-lb-stage">
                    <div class="pg-loader"></div>
                </div>
                <div class="pg-lb-controls">
                    <button class="pg-ctrl-btn" id="pg-prev">❮</button>
                    <button class="pg-ctrl-btn" id="pg-grid-view">${gridIconSvg}</button>
                    <button class="pg-ctrl-btn" id="pg-next">❯</button>
                </div>
            </div>
        `;

        document.body.appendChild(container);
        galleryState.container = container;
        galleryState.lightbox = container.querySelector('#pg-lightbox');

        container.querySelectorAll('.pg-filter-btn').forEach(btn => {
            btn.onclick = (e) => {
                container.querySelectorAll('.pg-filter-btn').forEach(b => b.classList.remove('active'));
                e.target.classList.add('active');
                galleryState.filter = e.target.dataset.filter;
                renderGrid();
            };
        });

        container.querySelector('.pg-close').onclick = closeGallery;
        container.querySelector('#pg-grid-view').onclick = closeLightbox;
        container.querySelector('#pg-prev').onclick = () => navLightbox(-1);
        container.querySelector('#pg-next').onclick = () => navLightbox(1);

        document.addEventListener('keydown', handleKeyInput);

        container.addEventListener('wheel', (e) => {
            const galleryScroll = container.querySelector('.pg-gallery-scroll');
            const lb = container.querySelector('#pg-lightbox');
            const isLbOpen = lb && lb.classList.contains('active');

            if (isLbOpen) {
                e.preventDefault();
                return;
            }

            if (galleryScroll) {
                e.preventDefault();
                galleryScroll.scrollTop += e.deltaY;
            }
        }, { passive: false });

        container.addEventListener('touchmove', (e) => {
            const galleryScroll = container.querySelector('.pg-gallery-scroll');
            const lb = container.querySelector('#pg-lightbox');
            const isLbOpen = lb && lb.classList.contains('active');

            if (isLbOpen) {
                e.preventDefault();
                return;
            }

            if (galleryScroll && galleryScroll.contains(e.target)) {
                return;
            }

            e.preventDefault();
        }, { passive: false });

        let touchStartX = 0;
        let touchEndX = 0;
        const stage = document.getElementById('pg-lb-stage');
        stage.addEventListener('touchstart', e => touchStartX = e.changedTouches[0].screenX);
        stage.addEventListener('touchend', e => {
            touchEndX = e.changedTouches[0].screenX;
            if (touchEndX < touchStartX - 50) navLightbox(1);
            if (touchEndX > touchStartX + 50) navLightbox(-1);
        });
    }

    function reorderForColumns(items, numCols) {
        const result = [];
        for (let col = 0; col < numCols; col++) {
            for (let i = col; i < items.length; i += numCols) {
                result.push(items[i]);
            }
        }
        return result;
    }

    function getColumnCount() {
        const w = window.innerWidth;
        if (w >= 1600) return 5;
        if (w >= 1200) return 4;
        if (w >= 768) return 3;
        return 2;
    }

    function renderGrid() {
        const grid = document.getElementById('pg-grid');
        grid.innerHTML = '';

        galleryState.filteredMedia = galleryState.media.filter(m =>
            galleryState.filter === 'all' || m.type === galleryState.filter
        );

        document.getElementById('pg-count-all').innerText = galleryState.filteredMedia.length;

        const numCols = getColumnCount();
        const displayOrder = reorderForColumns(galleryState.filteredMedia, numCols);

        displayOrder.forEach((item) => {
            const index = galleryState.filteredMedia.indexOf(item);
            const card = document.createElement('div');
            card.className = 'pg-item';

            let content = '';
            let badge = '';
            let imageNode = null;

            if (item.type === 'video') {
                const poster = item.poster ? item.poster : '';
                content = poster ? `<img src="${poster}" loading="lazy">` : `<div style="height:150px; display:flex; align-items:center; justify-content:center; background:#222; color:#555;">No Thumb</div>`;
                badge = '<span class="pg-badge video">VIDEO</span>';
            } else {
                imageNode = document.createElement('img');
                imageNode.className = 'pg-lazy-img';
                imageNode.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
                imageNode.dataset.src = item.thumb;
                imageNode.dataset.fullSrc = item.src;
                imageNode.dataset.retries = 0;

                if (item.width > 0 && item.height > 0) {
                    imageNode.style.aspectRatio = `${item.width} / ${item.height}`;
                } else {
                    imageNode.style.minHeight = "200px";
                }

                imageNode.onload = function () {
                    this.classList.add('pg-loaded');
                    setStatusOverlay(card, 'clear');
                };

                imageNode.onerror = function () {
                    const fullSrc = this.dataset.fullSrc;
                    let retries = parseInt(this.dataset.retries || '0');

                    if (retries < config.maxRetries) {
                        retries++;
                        this.dataset.retries = retries;
                        setStatusOverlay(card, 'retry', `Retrying (${retries}/${config.maxRetries})`);
                        queueRetry(this, fullSrc, retries);
                    } else {
                        setStatusOverlay(card, 'broken', 'Image Broken');
                        this.classList.add('pg-loaded');
                    }
                };

                content = '';
                badge = item.subType === 'gif' ? '<span class="pg-badge">GIF</span>' : '<span class="pg-badge">IMG</span>';
            }

            card.innerHTML = `${badge}${content}`;

            if (imageNode) {
                card.appendChild(imageNode);
            }

            card.onclick = () => openLightbox(index);

            // Video hover preview — desktop only, skipped entirely on iOS
            if (item.type === 'video' && !isIOS) {
                let previewVideo = null;
                let hoverTimeout = null;

                card.onmouseenter = () => {
                    hoverTimeout = setTimeout(() => {
                        previewVideo = document.createElement('video');
                        previewVideo.className = 'pg-preview-video';
                        previewVideo.src = item.src;
                        previewVideo.muted = true;
                        previewVideo.loop = true;
                        previewVideo.playsInline = true;
                        card.appendChild(previewVideo);
                        previewVideo.play().catch(() => { });
                    }, 200);
                };

                card.onmouseleave = () => {
                    if (hoverTimeout) clearTimeout(hoverTimeout);
                    if (previewVideo) {
                        previewVideo.pause();
                        previewVideo.src = '';
                        previewVideo.remove();
                        previewVideo = null;
                    }
                };
            }

            grid.appendChild(card);
        });

        setTimeout(initLazyObserver, 50);
    }

    function openGallery() {
        galleryState.media = scanMedia();
        if (galleryState.media.length === 0) {
            alert("No media found on this page.");
            return;
        }
        buildGalleryUI();
        renderGrid();

        lockBodyScroll();

        requestAnimationFrame(() => {
            document.getElementById('pg-container').classList.add('pg-visible');
        });
    }

    function closeGallery() {
        const c = document.getElementById('pg-container');
        if (c) {
            c.classList.remove('pg-visible');
            setTimeout(() => c.remove(), 300);
            unlockBodyScroll();
            document.removeEventListener('keydown', handleKeyInput);
            if (lazyImageObserver) lazyImageObserver.disconnect();

            retryQueue.length = 0;
            isRetrying = false;
        }
    }

    function openLightbox(index) {
        galleryState.currentIndex = index;
        const lb = document.getElementById('pg-lightbox');
        lb.classList.add('active');
        updateLightboxContent();
    }

    function closeLightbox() {
        const lb = document.getElementById('pg-lightbox');
        const stage = document.getElementById('pg-lb-stage');
        const existingVideo = stage.querySelector('video');
        if (existingVideo) existingVideo.pause();
        lb.classList.remove('active');
    }

    function navLightbox(direction) {
        const newIndex = galleryState.currentIndex + direction;
        if (newIndex >= 0 && newIndex < galleryState.filteredMedia.length) {
            galleryState.currentIndex = newIndex;
            updateLightboxContent();
        }
    }

    function updateLightboxContent() {
        const item = galleryState.filteredMedia[galleryState.currentIndex];
        const stage = document.getElementById('pg-lb-stage');
        const counter = document.querySelector('.pg-lb-counter');
        const prevBtn = document.getElementById('pg-prev');
        const nextBtn = document.getElementById('pg-next');

        prevBtn.disabled = galleryState.currentIndex === 0;
        nextBtn.disabled = galleryState.currentIndex === galleryState.filteredMedia.length - 1;
        counter.innerText = `${galleryState.currentIndex + 1} / ${galleryState.filteredMedia.length}`;

        stage.innerHTML = '<div class="pg-loader"></div>';

        if (item.type === 'video') {
            if (isIOS) {
                // iOS: poster + play overlay → opens native AVPlayer
                const wrapper = document.createElement('div');
                wrapper.style.cssText = 'position:relative; display:flex; align-items:center; justify-content:center; max-width:100%; max-height:100%; cursor:pointer;';

                if (item.poster) {
                    const img = document.createElement('img');
                    img.src = item.poster;
                    img.className = 'pg-lb-media';
                    img.onload = () => stage.querySelector('.pg-loader')?.remove();
                    img.onerror = () => stage.querySelector('.pg-loader')?.remove();
                    wrapper.appendChild(img);
                } else {
                    stage.querySelector('.pg-loader')?.remove();
                    wrapper.style.width = '80%';
                    wrapper.style.height = '60%';
                    wrapper.style.background = '#111';
                    wrapper.style.borderRadius = '8px';
                }

                const playBtn = document.createElement('div');
                playBtn.className = 'pg-ios-play-btn';
                playBtn.innerHTML = `<svg viewBox="0 0 24 24" width="32" height="32" fill="white"><path d="M8 5v14l11-7z"/></svg>`;

                playBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    window.open(item.src, '_blank');
                });
                wrapper.addEventListener('click', (e) => {
                    window.open(item.src, '_blank');
                });

                wrapper.appendChild(playBtn);
                stage.appendChild(wrapper);
            } else {
                const mediaEl = document.createElement('video');
                mediaEl.className = 'pg-lb-media';
                mediaEl.src = item.src;
                mediaEl.controls = true;
                mediaEl.playsInline = true;
                mediaEl.autoplay = true;
                mediaEl.volume = config.volume;
                if (item.poster) mediaEl.poster = item.poster;
                mediaEl.oncanplay = () => stage.querySelector('.pg-loader')?.remove();
                mediaEl.onerror = () => stage.querySelector('.pg-loader')?.remove();
                stage.appendChild(mediaEl);
            }
        } else {
            const mediaEl = document.createElement('img');
            mediaEl.className = 'pg-lb-media';
            mediaEl.src = item.src;
            mediaEl.onload = () => stage.querySelector('.pg-loader')?.remove();
            mediaEl.onerror = () => {
                stage.querySelector('.pg-loader')?.remove();
                stage.insertAdjacentHTML('beforeend', '<div style="color:#fff; text-align:center;">Failed to load full size image.</div>');
            };
            stage.appendChild(mediaEl);
        }
    }

    function handleKeyInput(e) {
        const lb = document.getElementById('pg-lightbox');
        const isLbOpen = lb && lb.classList.contains('active');

        if (e.key === 'Escape') {
            if (isLbOpen) closeLightbox();
            else closeGallery();
        } else if (isLbOpen) {
            if (e.key === 'ArrowLeft') navLightbox(-1);
            if (e.key === 'ArrowRight') navLightbox(1);
        }
    }

    function createLaunchButton() {
        const navGroups = document.querySelectorAll('.p-navgroup.p-account');

        navGroups.forEach(navGroup => {
            if (navGroup.querySelector('.pg-gallery-launcher')) return;

            const btn = document.createElement("a");
            btn.className = "p-navgroup-link u-ripple p-navgroup-link--iconic p-navgroup-link--iconic--media pg-gallery-launcher";
            btn.title = "Open Gallery";
            btn.href = "#";

            btn.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                openGallery();
            };

            btn.innerHTML = `
                <i aria-hidden="true" class="fa fa-images"></i>
                <span class="p-navgroup-linkText">Gallery</span>
            `;

            navGroup.appendChild(btn);
        });
    }

    unlockVideos();
    setTimeout(unlockVideos, 1500);
    setTimeout(createLaunchButton, 500);
    createLaunchButton();

})();