ZeroTolerance Grid View (fixed)

Shows all photos in a true grid overlay (fixed selectors) on ZeroToleranceFilms photosets.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         ZeroTolerance Grid View (fixed)
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Shows all photos in a true grid overlay (fixed selectors) on ZeroToleranceFilms photosets.
// @match        https://members.zerotolerancefilms.com/*
// @license      GPL-3.0
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let currentCarouselImages = [];
    let gridScrollPosition = 0;

    // Hide the React overlay container (once we've pulled URLs)
    function hideDefaultCarousel() {
        const overlay = document.getElementById('pageOverlaySlot');
        if (overlay) overlay.style.display = 'none';
    }

    // Pull URLs from the overlay
    function extractCarouselImages() {
        const overlay = document.getElementById('pageOverlaySlot');
        const imgs = overlay.querySelectorAll('img');
        return Array.from(imgs).map(i => i.src).filter((v,i,a)=> v && a.indexOf(v)===i);
    }

    // Step through overlay arrows only
    function loadAllCarouselImages(callback) {
        const overlay = document.getElementById('pageOverlaySlot');
        const img = overlay.querySelector('img');
        if (!loadAllCarouselImages.startSrc && img) {
            loadAllCarouselImages.startSrc = img.src;
        }
        const nextArrow = overlay.querySelector('a.next-Link:not(.disabled-Link)');
        // if we’ve looped back to the first src, we’re done
        if (loadAllCarouselImages.called && img && img.src === loadAllCarouselImages.startSrc) {
            return callback();
        }
        loadAllCarouselImages.called = true;
        if (nextArrow) {
            nextArrow.click();
            setTimeout(() => loadAllCarouselImages(callback), 500);
        } else {
            callback();
        }
    }

    // Build the grid overlay
    function createOverlayContainer(images) {
        let c = document.getElementById('ztGridOverlay');
        if (c) c.remove();

        c = document.createElement('div');
        c.id = 'ztGridOverlay';
        Object.assign(c.style, {
            position:'fixed', top:0, left:0,
            width:'100vw', height:'100vh',
            backgroundColor:'rgba(0,0,0,0.95)',
            backgroundColor:'black',
            display:'grid',
            gridTemplateColumns:'repeat(auto-fit, minmax(600px,1fr))',
            gap:'10px', padding:'20px', overflowY:'auto',
            zIndex:9999
        });
        c.addEventListener('scroll', ()=> {
            gridScrollPosition = c.scrollTop;
        });

        images.forEach((src,i) => {
            const cell = document.createElement('div');
            Object.assign(cell.style, {
                height:'562.5px', overflow:'hidden',
                display:'flex', alignItems:'center', justifyContent:'center'
            });
            const img = document.createElement('img');
            img.src = src;
            Object.assign(img.style, {
                maxWidth:'100%', maxHeight:'100%',
                objectFit:'contain', cursor:'pointer'
            });
            img.addEventListener('click', ()=> {
                c.remove();
                document.getElementById('ztGridClose').remove();
                createCustomCarousel(i);
            });
            cell.appendChild(img);
            c.appendChild(cell);
        });

        document.body.appendChild(c);
        // restore scroll
        setTimeout(()=> c.scrollTop = gridScrollPosition, 0);

        const btn = document.createElement('button');
        btn.id = 'ztGridClose';
        btn.textContent = 'Close Grid';
        Object.assign(btn.style, {
            position:'fixed', top:'20px', right:'20px',
            padding:'10px 20px', fontSize:'16px',
            cursor:'pointer', zIndex:10000
        });
        btn.addEventListener('click', ()=> {
            c.remove();
            btn.remove();
        });
        document.body.appendChild(btn);
    }

    // Build a simple zoom/drag carousel
    function createCustomCarousel(startIndex) {
        const overlay = document.getElementById('pageOverlaySlot');
        // hide the React version
        hideDefaultCarousel();

        let idx = startIndex, scale=1, tx=0, ty=0, sx, sy;
        const wrapper = document.createElement('div');
        Object.assign(wrapper.style, {
            position:'fixed',top:0,left:0,
            width:'100vw',height:'100vh',
            backgroundColor:'black',
            display:'flex',alignItems:'center',justifyContent:'center',
            overflow:'hidden',zIndex:10000
        });

        const img = document.createElement('img');
        img.src = currentCarouselImages[idx];
        Object.assign(img.style, {
            maxWidth:'90%', maxHeight:'90%',
            objectFit:'contain', cursor:'grab',
            transformOrigin:'0 0'
        });
        img.draggable = false;

        // wheel zoom
        img.addEventListener('wheel', e => {
            e.preventDefault();
            const r = e.deltaY > 0 ? -0.1 : 0.1;
            const newScale = Math.max(1, scale + r);
            const rect = img.getBoundingClientRect();
            const ox = e.clientX - rect.left, oy = e.clientY - rect.top;
            const ratio = newScale/scale;
            tx = ox - ratio*(ox - tx);
            ty = oy - ratio*(oy - ty);
            scale = newScale;
            img.style.transform = `translate(${tx}px,${ty}px) scale(${scale})`;
        });

        // drag
        img.addEventListener('mousedown', e => {
            e.preventDefault();
            sx = e.clientX - tx;
            sy = e.clientY - ty;
            document.addEventListener('mousemove', ondrag);
            document.addEventListener('mouseup', offdrag);
        });
        function ondrag(e) {
            tx = e.clientX - sx;
            ty = e.clientY - sy;
            img.style.transform = `translate(${tx}px,${ty}px) scale(${scale})`;
        }
        function offdrag() {
            document.removeEventListener('mousemove', ondrag);
            document.removeEventListener('mouseup', offdrag);
        }

        wrapper.appendChild(img);

        ['‹','›'].forEach((sym,dirIdx)=> {
            const btn = document.createElement('button');
            btn.textContent = sym;
            Object.assign(btn.style, {
                position:'absolute',
                [dirIdx===0?'left':'right']:'20px',
                top:'50%', transform:'translateY(-50%)',
                fontSize:'30px', background:'transparent',
                border:'none', color:'white', cursor:'pointer',
                zIndex:10001
            });
            btn.addEventListener('click', ()=> {
                scale=1; tx=0; ty=0;
                idx = (idx + (dirIdx===0?-1:1) + currentCarouselImages.length) % currentCarouselImages.length;
                img.src = currentCarouselImages[idx];
                img.style.transform = 'translate(0,0) scale(1)';
            });
            wrapper.appendChild(btn);
        });

        const close = document.createElement('button');
        close.textContent = 'Close Carousel';
        Object.assign(close.style, {
            position:'fixed', top:'20px', right:'20px',
            padding:'10px 20px', fontSize:'16px',
            cursor:'pointer', zIndex:10002
        });
        close.addEventListener('click', ()=> {
            wrapper.remove();
            close.remove();
            createOverlayContainer(currentCarouselImages);
        });

        document.body.appendChild(wrapper);
        document.body.appendChild(close);
    }

    // Main toggle routine
    function toggleOverlay() {
        // first, walk the React overlay
        loadAllCarouselImages(() => {
            const images = extractCarouselImages();
            currentCarouselImages = images.slice();
            if (images.length) {
                // now hide React and show our grid
                hideDefaultCarousel();
                createOverlayContainer(images);
            } else {
                alert('No carousel images found.');
            }
        });
    }

    // Ensure overlay is open, then toggle
    function launchGridOverlay() {
        const overlayImg = document.querySelector('#pageOverlaySlot img');
        if (overlayImg) {
            toggleOverlay();
        } else {
            // click first thumbnail to open React lightbox
            const thumb = document.querySelector('.ListingGrid-ListingGridItem img');
            if (!thumb) return alert('No gallery images found.');
            thumb.click();
            const iv = setInterval(() => {
                if (document.querySelector('#pageOverlaySlot img')) {
                    clearInterval(iv);
                    toggleOverlay();
                }
            }, 300);
        }
    }

    // Inject the “Grid View” button next to the title
    function addGridButton() {
        const sel = 'h1.Title.PhotosetGallery-PhotosetTitle-Title';
        const insert = () => {
            const h1 = document.querySelector(sel);
            if (h1 && !document.getElementById('ztGridBtn')) {
                const btn = document.createElement('button');
                btn.id = 'ztGridBtn';
                btn.textContent = 'Grid View';
                Object.assign(btn.style, {
                    marginLeft:'20px', padding:'8px 16px',
                    fontSize:'16px', cursor:'pointer',
                    backgroundColor:'yellow', border:'1px solid #999',
                    borderRadius:'4px', fontWeight:'bold'
                });
                btn.addEventListener('click', launchGridOverlay);
                h1.parentNode.insertBefore(btn, h1.nextSibling);
                return true;
            }
            return false;
        };
        if (!insert()) {
            const obs = new MutationObserver((m,o)=> {
                if (insert()) o.disconnect();
            });
            obs.observe(document.body,{childList:true,subtree:true});
            setInterval(insert, 500);
        }
    }

    if (document.readyState!=='loading') addGridButton();
    else document.addEventListener('DOMContentLoaded', addGridButton);

})();