Web-Font-Rendering-Core

全端统一交互:强制单行横向滚动预览,PC/移动端体验一致。支持全量画廊与静默坏图过滤。

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Web-Font-Rendering-Core
// @namespace    http://tampermonkey.net/
// @version      2.6.0
// @description  全端统一交互:强制单行横向滚动预览,PC/移动端体验一致。支持全量画廊与静默坏图过滤。
// @author       Gemini-Adaptive
// @license      MIT
// @match        *://t66y.com/*
// @match        *://www.t66y.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

;(() => {
    const countRegex = /\d+[pP]/;

    const style = document.createElement('style');
    style.textContent = `
        /* --- 1. 统一列表预览容器 --- */
        .sys-list-container {
            display: flex;
            gap: 12px;
            margin: 15px 0;
            padding: 5px 2px 15px 2px; /* 底部预留空间给滚动条 */
            clear: both;
            flex-wrap: nowrap;
            overflow-x: auto;
            overflow-y: hidden;
            -webkit-overflow-scrolling: touch;
            /* 改善 PC 端滚动条样式,不隐藏它以便于鼠标拖动 */
            scrollbar-width: thin;
            scrollbar-color: #555 #222;
        }

        /* Webkit 滚动条美化 (PC端) */
        .sys-list-container::-webkit-scrollbar {
            height: 6px;
        }
        .sys-list-container::-webkit-scrollbar-track {
            background: #1a1a1a;
            border-radius: 10px;
        }
        .sys-list-container::-webkit-scrollbar-thumb {
            background: #444;
            border-radius: 10px;
        }
        .sys-list-container::-webkit-scrollbar-thumb:hover {
            background: #666;
        }

        /* --- 2. 统一缩略图尺寸 --- */
        .sys-list-thumb {
            height: 280px; /* 统一高度,兼顾视觉冲击力与排版 */
            width: auto;
            flex: 0 0 auto;
            border-radius: 8px;
            object-fit: cover;
            background: #222;
            cursor: zoom-in;
            box-shadow: 0 4px 12px rgba(0,0,0,0.4);
            transition: transform 0.2s;
        }
        .sys-list-thumb:hover {
            transform: translateY(-2px);
        }

        /* --- 3. 查看器与画廊 (保持高质交互) --- */
        .sys-viewer-overlay {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0, 0, 0, 0.98); display: flex; flex-direction: column;
            align-items: center; justify-content: center; z-index: 999999;
            opacity: 0; pointer-events: none; transition: opacity 0.3s ease;
            outline: none; touch-action: none;
        }
        .sys-viewer-overlay.active { opacity: 1; pointer-events: auto; }
        .sys-viewer-stage { flex: 1; width: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
        .sys-viewer-node { max-width: 98%; max-height: 98%; object-fit: contain; transition: transform 0.3s cubic-bezier(0.2, 0, 0.2, 1), opacity 0.3s; opacity: 0; will-change: transform; cursor: zoom-out; }
        .sys-viewer-overlay.active .sys-viewer-node.loaded { opacity: 1; }

        .sys-viewer-error { display: none; color: #888; text-align: center; }
        .sys-viewer-overlay.img-error .sys-viewer-error { display: block; }
        .sys-viewer-overlay.img-error .sys-viewer-node { display: none; }

        .sys-viewer-gallery {
            width: 100%; height: 110px; background: rgba(0,0,0,0.8);
            display: flex; gap: 8px; padding: 10px; overflow-x: auto;
            border-top: 1px solid rgba(255,255,255,0.1); backdrop-filter: blur(15px);
            scrollbar-width: none;
        }
        .sys-viewer-gallery::-webkit-scrollbar { display: none; }
        .sys-gallery-item { height: 90px; width: auto; flex: 0 0 auto; border-radius: 4px; cursor: pointer; border: 2px solid transparent; transition: all 0.2s; opacity: 0.5; object-fit: cover; }
        .sys-gallery-item.current { border-color: #fff; opacity: 1; transform: scale(1.05); }

        .sys-loader { position: absolute; width: 40px; height: 40px; border: 3px solid rgba(255,255,255,0.1); border-radius: 50%; border-top-color: #fff; animation: sys-spin 0.8s linear infinite; display: none; }
        .sys-viewer-overlay.loading .sys-loader { display: block; }
        @keyframes sys-spin { to { transform: rotate(360deg); } }
    `;
    document.head.appendChild(style);

    // --- 结构初始化与逻辑 (逻辑部分保持健壮) ---
    const overlay = document.createElement('div');
    overlay.className = 'sys-viewer-overlay';
    overlay.tabIndex = 0;
    const stage = document.createElement('div');
    stage.className = 'sys-viewer-stage';
    const gallery = document.createElement('div');
    gallery.className = 'sys-viewer-gallery';
    const loader = document.createElement('div');
    loader.className = 'sys-loader';
    const errorTip = document.createElement('div');
    errorTip.className = 'sys-viewer-error';
    errorTip.innerHTML = '<div style="font-size:30px;">⚠️</div>加载失败';
    const bigImg = document.createElement('img');
    bigImg.className = 'sys-viewer-node';

    stage.append(loader, errorTip, bigImg);
    overlay.append(stage, gallery);
    document.body.appendChild(overlay);

    let currentSrcList = [];
    let currentImgIndex = -1;
    let startX = 0, deltaX = 0, isDragging = false;

    const setupImageErrorFilter = (imgNode) => {
        imgNode.onerror = () => { imgNode.style.display = 'none'; };
    };

    const updateViewer = (index) => {
        if (index < 0 || index >= currentSrcList.length) {
            bigImg.style.transform = `translateX(0px)`;
            return;
        }
        currentImgIndex = index;
        overlay.classList.add('loading');
        overlay.classList.remove('img-error');
        bigImg.classList.remove('loaded');
        bigImg.style.transition = 'none';
        bigImg.style.transform = `translateX(0px) scale(0.98)`;
        bigImg.src = currentSrcList[currentImgIndex];

        Array.from(gallery.children).forEach((item, idx) => {
            const isCurrent = idx === currentImgIndex;
            item.classList.toggle('current', isCurrent);
            if (isCurrent) item.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
        });
    };

    const releaseView = () => {
        overlay.classList.remove('active', 'loading', 'img-error');
        document.body.style.overflow = '';
        setTimeout(() => { bigImg.src = ''; gallery.innerHTML = ''; }, 300);
    };

    // 事件绑定
    bigImg.onclick = (e) => { e.stopPropagation(); releaseView(); };
    overlay.onclick = (e) => { if(e.target === stage || e.target === overlay) releaseView(); };
    overlay.onkeydown = (e) => {
        if (e.key === 'Escape') releaseView();
        if (e.key === 'ArrowLeft') updateViewer(currentImgIndex - 1);
        if (e.key === 'ArrowRight') updateViewer(currentImgIndex + 1);
    };

    // 触摸滑动逻辑
    stage.addEventListener('touchstart', (e) => {
        if (e.touches.length > 1) return;
        startX = e.touches[0].clientX;
        deltaX = 0; isDragging = true;
        bigImg.style.transition = 'none';
    }, { passive: false });

    stage.addEventListener('touchmove', (e) => {
        if (!isDragging) return;
        deltaX = e.touches[0].clientX - startX;
        if (Math.abs(deltaX) > 5) {
            e.preventDefault();
            bigImg.style.transform = `translateX(${deltaX}px)`;
        }
    }, { passive: false });

    stage.addEventListener('touchend', () => {
        if (!isDragging) return;
        isDragging = false;
        bigImg.style.transition = 'transform 0.3s cubic-bezier(0.2, 0, 0.2, 1)';
        if (Math.abs(deltaX) > window.innerWidth * 0.15) {
            deltaX > 0 ? updateViewer(currentImgIndex - 1) : updateViewer(currentImgIndex + 1);
        } else {
            bigImg.style.transform = `translateX(0px)`;
        }
    }, { passive: false });

    bigImg.onload = () => { overlay.classList.remove('loading', 'img-error'); bigImg.classList.add('loaded'); bigImg.style.transform = `translateX(0px) scale(1)`; };
    bigImg.onerror = () => { overlay.classList.remove('loading'); overlay.classList.add('img-error'); };

    const openViewer = async (targetSrc, containerSelector, imgSelector) => {
        const container = document.querySelector(containerSelector);
        let imgs = container ? Array.from(container.querySelectorAll(imgSelector)) : [];
        currentSrcList = imgs.map(img => img.getAttribute('ess-data') || img.src).filter(s => s && !s.includes('adblo_ck'));

        if (currentSrcList.length < 15 && container && container.dataset.fullurl) {
             overlay.classList.add('loading');
             try {
                const res = await fetch(container.dataset.fullurl);
                const text = await res.text();
                const doc = new DOMParser().parseFromString(text, 'text/html');
                const area = doc.querySelector('#conttpc');
                if (area) {
                    const fullImgs = Array.from(area.querySelectorAll('img[ess-data]')).map(el => {
                         let s = el.getAttribute("ess-data");
                         return s.startsWith('//') ? 'https:' + s : s;
                    }).filter(s => s && !s.includes('adblo_ck'));
                    if (fullImgs.length > currentSrcList.length) currentSrcList = fullImgs;
                }
             } catch (e) {}
             overlay.classList.remove('loading');
        }

        currentImgIndex = currentSrcList.indexOf(targetSrc);
        if (currentImgIndex === -1) currentImgIndex = 0;

        if (currentSrcList.length > 0) {
            gallery.innerHTML = '';
            currentSrcList.forEach((src, idx) => {
                const thumb = document.createElement('img');
                thumb.className = 'sys-gallery-item';
                thumb.src = src;
                thumb.onclick = (e) => { e.stopPropagation(); updateViewer(idx); };
                setupImageErrorFilter(thumb);
                gallery.appendChild(thumb);
            });
            document.body.style.overflow = 'hidden';
            overlay.classList.add('active');
            overlay.focus();
            updateViewer(currentImgIndex);
        }
    };

    async function fetchPreview(item, url, title) {
        if (!countRegex.test(title) || item.dataset.proc === "1") return;
        item.dataset.proc = "1";
        try {
            const res = await fetch(url);
            const text = await res.text();
            const doc = new DOMParser().parseFromString(text, 'text/html');
            const area = doc.querySelector('#conttpc');
            if (!area) return;
            const allImgs = Array.from(area.querySelectorAll('img[ess-data]'));
            if (!allImgs.length) return;

            const box = document.createElement("div");
            box.className = "sys-list-container";
            box.dataset.fullurl = url;

            allImgs.slice(0, 12).forEach(el => {
                let s = el.getAttribute("ess-data");
                if (s.startsWith('//')) s = 'https:' + s;
                const t = document.createElement("img");
                t.src = s;
                t.className = "sys-list-thumb";
                t.loading = "lazy";
                t.onclick = (e) => {
                    e.preventDefault(); e.stopPropagation();
                    openViewer(s, `.sys-list-container[data-fullurl="${url}"]`, '.sys-list-thumb');
                };
                setupImageErrorFilter(t);
                box.appendChild(t);
            });
            item.querySelector("h3").after(box);
        } catch (e) {}
    }

    // --- 初始化 ---
    const loc = window.location.href;
    if (loc.includes('/htm_data/')) {
        setTimeout(() => {
            const root = document.querySelector('#conttpc, .tpc_content');
            if (!root) return;
            root.querySelectorAll('img').forEach(img => {
                const src = img.getAttribute('ess-data') || img.src;
                if (!src || src.includes('adblo_ck') || src.includes('common/')) return;
                img.classList.add('sys-detail-node');
                img.onclick = (e) => { e.preventDefault(); openViewer(src, '#conttpc, .tpc_content', 'img.sys-detail-node'); };
            });
        }, 500);
    } else {
        const obs = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const a = entry.target.querySelector("h3 > a");
                    if (a) fetchPreview(entry.target, a.href, a.innerText);
                    obs.unobserve(entry.target);
                }
            });
        }, { rootMargin: '800px' });
        document.querySelectorAll("tr.tac, tr.tal").forEach(r => { if (r.querySelector("h3 > a")) obs.observe(r); });
    }
})();