ZeroTolerance Grid View (fixed)

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

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey 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 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         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);

})();