Civitai Larger Thumbnails + Arrow Key Navigation

Enlarges thumbnails to take up the whole screen and adds Up/Down arrow key navigation to instantly go to the next thumbnail. No more tired eyes from constantly moving your eyes from thumbnail to thumbnail.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Civitai Larger Thumbnails + Arrow Key Navigation
// @namespace   Violentmonkey Scripts
// @match       *://*.civitai.com/*
// @grant       GM_addStyle
// @version     1.21
// @author      rainlizard
// @license     MIT
// @description Enlarges thumbnails to take up the whole screen and adds Up/Down arrow key navigation to instantly go to the next thumbnail. No more tired eyes from constantly moving your eyes from thumbnail to thumbnail.
// ==/UserScript==

(function() {
    'use strict';

    let currentIndex = 0;
    let thumbnails = [];
    let isInitialized = false;

    // Function to wait for elements to appear
    function waitForElement(selector, timeout = 10000) {
        return new Promise((resolve, reject) => {
            const element = document.querySelector(selector);
            if (element) {
                resolve(element);
                return;
            }

            const observer = new MutationObserver((mutations, obs) => {
                const element = document.querySelector(selector);
                if (element) {
                    obs.disconnect();
                    resolve(element);
                }
            });

            observer.observe(document, {
                childList: true,
                subtree: true
            });

            setTimeout(() => {
                observer.disconnect();
                reject(new Error(`Element ${selector} not found within ${timeout}ms`));
            }, timeout);
        });
    }

    // Function to calculate optimal thumbnail height
    function calculateThumbnailHeight() {
        const viewportHeight = window.innerHeight;

        // Check for header height
        const headerSelectors = [
            'header',
            '[role="banner"]',
            '.header',
            '.navbar',
            'nav:first-of-type'
        ];

        let headerHeight = 0;
        for (const selector of headerSelectors) {
            const header = document.querySelector(selector);
            if (header) {
                const rect = header.getBoundingClientRect();
                if (rect.top >= -10 && rect.height > 0) {
                    headerHeight = Math.max(headerHeight, rect.height);
                }
            }
        }

        // Use full viewport height minus header
        return viewportHeight - headerHeight;
    }

    // Function to find all thumbnail cards directly
    function findThumbnails() {
        // Look for thumbnails by their specific structure
        const thumbnailElements = document.querySelectorAll('.AspectRatioImageCard_content__IGj_A');

        // Get the parent containers (the actual thumbnail divs)
        const thumbnailContainers = Array.from(thumbnailElements).map(content => {
            // The parent should be the thumbnail container
            let parent = content.parentElement;
            // Look for the container with the aspect-ratio style or class structure
            while (parent && !parent.style.aspectRatio && !parent.classList.contains('relative')) {
                parent = parent.parentElement;
            }
            return parent;
        }).filter(Boolean);

        console.log(`Found ${thumbnailContainers.length} thumbnail containers`);
        return thumbnailContainers;
    }

    // Update thumbnail list
    function updateThumbnailList() {
        thumbnails = findThumbnails();
        if (currentIndex >= thumbnails.length && thumbnails.length > 0) {
            currentIndex = thumbnails.length - 1;
        } else if (thumbnails.length === 0) {
            currentIndex = 0;
        }
    }

    // Function to update all thumbnail heights
    function updateThumbnailHeights() {
        const newHeight = calculateThumbnailHeight();
        const heightValue = `${newHeight}px`;

        // Update CSS custom property
        document.documentElement.style.setProperty('--civitai-thumbnail-height', heightValue);

        // Update existing thumbnails
        thumbnails.forEach(thumbnail => {
            if (thumbnail.classList.contains('civitai-large-thumbnail')) {
                thumbnail.style.height = heightValue;
                thumbnail.style.minHeight = heightValue;
            }
        });
    }

    // Apply CSS for large thumbnails
    GM_addStyle(`
        html, body {
            scroll-behavior: smooth !important;
        }

        body {
            overflow-x: hidden !important;
        }

        /* Container for thumbnails */
        .civitai-thumbnail-container {
            display: flex !important;
            flex-direction: column !important;
            align-items: center !important;
            width: 100% !important;
            gap: 0px !important;
        }

        /* Large thumbnail styling */
        .civitai-large-thumbnail {
            width: 100vw !important;
            max-width: 100% !important;
            height: var(--civitai-thumbnail-height, 100vh) !important;
            min-height: var(--civitai-thumbnail-height, 100vh) !important;
            display: flex !important;
            flex-direction: column !important;
            justify-content: center !important;
            align-items: center !important;
            padding: 20px !important;
            box-sizing: border-box !important;
            background-color: #1A1B1E !important;
            position: relative !important;
            margin: 0 !important;
            aspect-ratio: auto !important;
            border-radius: 0 !important;
        }

        /* Style the content area */
        .civitai-large-thumbnail .AspectRatioImageCard_content__IGj_A {
            width: 100% !important;
            height: 100% !important;
            display: flex !important;
            flex-direction: column !important;
            justify-content: center !important;
            align-items: center !important;
        }

        /* Style the link */
        .civitai-large-thumbnail .AspectRatioImageCard_linkOrClick__d_K_4 {
            width: 90% !important;
            height: 80% !important;
            display: flex !important;
            justify-content: center !important;
            align-items: center !important;
        }

        /* Style images within large thumbnails */
        .civitai-large-thumbnail .AspectRatioImageCard_image__1xNTQ {
            max-width: 100vw !important;
            max-height: 100vh !important;
            width: auto !important;
            height: auto !important;
            object-fit: contain !important;
            border-radius: 8px !important;
        }

        /* Style the footer with no background */
        .civitai-large-thumbnail .AspectRatioImageCard_footer__FOU7a {
            position: absolute !important;
            bottom: 20px !important;
            left: 20px !important;
            right: 20px !important;
            background: rgba(0, 0, 0, 0) !important;
            padding: 20px !important;
            border-radius: 10px !important;
            color: white !important;
        }

        /* Style the header with no background */
        .civitai-large-thumbnail .AspectRatioImageCard_header__Mmd__ {
            position: absolute !important;
            top: 20px !important;
            right: 20px !important;
            background: rgba(0, 0, 0, 0) !important;
            padding: 10px !important;
            border-radius: 10px !important;
        }

        /* Navigation indicator */
        .civitai-nav-indicator {
            position: fixed !important;
            bottom: 20px !important;
            right: 20px !important;
            background: rgba(0, 0, 0, 0.8) !important;
            color: white !important;
            padding: 10px 15px !important;
            border-radius: 5px !important;
            font-size: 14px !important;
            z-index: 1000 !important;
            font-family: Arial, sans-serif !important;
            opacity: 0.8 !important;
        }
    `);

    // Scroll to specific thumbnail
    function scrollToThumbnail(index) {
        updateThumbnailList();
        if (index >= 0 && index < thumbnails.length) {
            currentIndex = index;
            const targetThumbnail = thumbnails[currentIndex];
            if (targetThumbnail) {
                targetThumbnail.scrollIntoView({
                    behavior: 'smooth',
                    block: 'center',
                    inline: 'nearest'
                });

                // Update navigation indicator
                const indicator = document.querySelector('.civitai-nav-indicator');
                if (indicator) {
                    indicator.textContent = `${currentIndex + 1} / ${thumbnails.length} - ↑↓ Navigate`;
                }
            }
        }
    }

    // Keyboard navigation
    document.addEventListener('keydown', function(e) {
        updateThumbnailList();
        if (thumbnails.length === 0) return;

        if (e.key === 'ArrowDown') {
            e.preventDefault();
            if (currentIndex < thumbnails.length - 1) {
                scrollToThumbnail(currentIndex + 1);
            }
        } else if (e.key === 'ArrowUp') {
            e.preventDefault();
            if (currentIndex > 0) {
                scrollToThumbnail(currentIndex - 1);
            }
        }
    });

    // Apply large thumbnail styling to all thumbnails
    function applyLargeThumbnailStyling() {
        updateThumbnailList();

        if (thumbnails.length === 0) return;

        // Find the parent container and modify its layout
        const firstThumbnail = thumbnails[0];
        const parentContainer = firstThumbnail.parentElement;

        if (parentContainer) {
            parentContainer.classList.add('civitai-thumbnail-container');
        }

        // Apply styling to each thumbnail
        thumbnails.forEach(thumbnail => {
            thumbnail.classList.add('civitai-large-thumbnail');
        });

        // Update heights
        updateThumbnailHeights();

        console.log(`Applied large styling to ${thumbnails.length} thumbnails`);
    }

    // Function to periodically check for new content
    function checkForUpdates() {
        const newThumbnails = findThumbnails();
        if (newThumbnails.length !== thumbnails.length) {
            console.log(`Updated: Now have ${newThumbnails.length} thumbnails`);

            // Apply styling to new thumbnails
            newThumbnails.forEach(thumbnail => {
                if (!thumbnail.classList.contains('civitai-large-thumbnail')) {
                    thumbnail.classList.add('civitai-large-thumbnail');
                }
            });

            thumbnails = newThumbnails;
            updateThumbnailHeights();
        }
    }

    // Function to observe viewport changes
    function observeViewportChanges() {
        // Listen for window resize
        window.addEventListener('resize', () => {
            updateThumbnailHeights();
        });

        // Listen for scroll to detect header changes
        let scrollTimeout;
        window.addEventListener('scroll', () => {
            clearTimeout(scrollTimeout);
            scrollTimeout = setTimeout(() => {
                updateThumbnailHeights();
            }, 100);
        });
    }

    // Initialize the script
    function init() {
        console.log('Civitai Larger Thumbnails script loaded');

        // Calculate initial height
        const initialHeight = calculateThumbnailHeight();
        document.documentElement.style.setProperty('--civitai-thumbnail-height', `${initialHeight}px`);

        // Wait for the main content area to load
        waitForElement('#main')
            .then(() => {
                setTimeout(() => {
                    applyLargeThumbnailStyling();

                    if (thumbnails.length > 0) {
                        console.log(`Applied large styling to ${thumbnails.length} thumbnails`);

                        // Add navigation indicator
                        const indicator = document.createElement('div');
                        indicator.className = 'civitai-nav-indicator';
                        indicator.textContent = `1 / ${thumbnails.length} - ↑↓ Navigate`;
                        document.body.appendChild(indicator);

                        // Set up viewport observation
                        observeViewportChanges();

                        isInitialized = true;
                    }

                    // Set up periodic checks for new content
                    setInterval(checkForUpdates, 2000);
                }, 2000);
            })
            .catch(error => {
                console.error('Civitai script: Failed to find main content area', error);
            });
    }

    // Start initialization after a delay to ensure page is ready
    setTimeout(init, 1000);

})();