Douban Widescreen Optimizer

Optimize Douban layout for widescreen displays, utilizing full screen width with modern responsive design

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

Advertisement:

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

Advertisement:

// ==UserScript==
// @name         Douban Widescreen Optimizer
// @name:zh-CN   豆瓣宽屏优化
// @namespace    https://github.com/better-douban
// @version      1.0.9
// @description  Optimize Douban layout for widescreen displays, utilizing full screen width with modern responsive design
// @description:zh-CN 优化豆瓣网页在宽屏显示器下的布局,充分利用屏幕宽度,采用现代响应式设计
// @author       Better Douban Team
// @match        *://*.douban.com/*
// @match        *://douban.com/*
// @icon         https://www.douban.com/favicon.ico
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration for different page types
    const config = {
        // Global settings
        global: {
            maxWidth: '95vw',           // Use 95% of viewport width
            contentMaxWidth: '1800px',  // Absolute max to prevent excessive stretching on ultra-wide
            padding: '20px',
            gap: '24px'
        },

        // Movie/Book/Music detail pages
        subject: {
            containerWidth: '90vw',
            maxContentWidth: '1600px',
            sidebarRatio: '320px',      // Fixed sidebar width
            gridColumns: 'minmax(300px, 1fr)'
        },

        // List pages (movie list, book list, etc.)
        list: {
            containerWidth: '92vw',
            maxContentWidth: '1800px',
            gridMinSize: '280px',
            gridGap: '20px'
        },

        // Group discussion pages
        group: {
            containerWidth: '90vw',
            maxContentWidth: '1600px'
        },

        // User profile pages
        user: {
            containerWidth: '90vw',
            maxContentWidth: '1600px'
        }
    };

    // Detect current page type
    function getPageType() {
        const path = window.location.pathname;
        const hostname = window.location.hostname;

        // Check for movie photos/images pages first (only on movie domain)
        // Matches: /subject/{id}/photos, /subject/{id}/all_photos, /subject/{id}/photos?type=S
        if (hostname.includes('movie') && path.match(/^\/subject\/\d+\/(all_)?photos/)) return 'photos';

        if (path.match(/^\/subject\//)) return 'subject';
        if (path.match(/^\/group/)) return 'group';
        if (path.match(/^\/people/)) return 'user';
        if (path.match(/\/list/)) return 'list';
        if (hostname.includes('movie')) return 'movie';
        if (hostname.includes('book')) return 'book';
        if (hostname.includes('music')) return 'music';

        return 'global';
    }

    // Create optimized CSS styles
    function generateStyles(pageType) {
        const global = config.global;
        const pageConfig = config[pageType] || config.global;

        return `
            /* ============================================
               Douban Widescreen Optimizer - Base Styles
               Version: 1.0.9 (Pixel-Based Subject Summary Expansion)

               Key changes in v1.0.9:
               - Force summary width from the measured article width in pixels
               - Watch style/class changes that collapse summary text late
               - Reapply summary layout for several seconds after load

               Key changes in v1.0.8:
               - Append optimizer stylesheet after page styles instead of before them
               - Apply inline important widths to subject summary text nodes
               - Reapply summary layout after late DOM updates from other scripts

               Key changes in v1.0.7:
               - Expanded subject summary text nodes to fill the widened article
               - Added explicit width rules for .related-info summary internals

               Key changes in v1.0.6:
               - Rebuilt subject header as a responsive three-column grid
               - Enlarged posters on detail pages while preserving aspect ratio
               - Reduced empty middle space between poster, metadata, and rating

               Key changes in v1.0.5:
               - Added optimization for movie photo gallery pages (/subject/{id}/photos)
               - Responsive grid layout for photos with auto-fill
               - Hover effects and caption overlays
               - Configurable grid size (180px min) and gap (12px)

               Key changes in v1.0.4:
               - Fixed .aside dropping to bottom on wide screens
               - Use flexbox on .grid-16-8.clearfix for robust two-column layout
               - .article uses flex: 1 to take remaining space
               - .aside uses flex: 0 0 320px to maintain fixed width
               - Prevents sidebar from wrapping regardless of screen width

               Key changes in v1.0.3:
               - Fixed .aside sidebar positioning for compatibility with scripts1.json and scripts2.json
               - Float-based layout for maximum compatibility

               Key changes in v1.0.2:
               - Move rating section to right side of detail pages
               - Float-based positioning for rating box

               Key changes in v1.0.1:
               - More specific CSS selectors to avoid breaking layout
               - Only target top-level containers (#wrapper, #content)
               ============================================ */

            /* Only target top-level containers - be very specific */
            #wrapper {
                max-width: ${pageConfig.maxContentWidth || global.contentMaxWidth} !important;
                width: ${pageConfig.containerWidth || global.maxWidth} !important;
                min-width: 1200px !important;
            }

            #content {
                max-width: 100% !important;
                width: 100% !important;
            }

            /* Grid layout container - only the main one */
            #content .grid-16-8.clearfix,
            .grid-16-8.clearfix {
                max-width: 100% !important;
                width: 100% !important;
                display: grid !important;
                grid-template-columns: minmax(0, 1fr) 320px !important;
                gap: 24px !important;
                align-items: flex-start !important;
            }

            /* Main article column in grid-16-8 layout */
            #content .grid-16-8.clearfix > .article,
            .grid-16-8.clearfix > .article {
                width: 100% !important;
                min-width: 0 !important;
                max-width: none !important;
                box-sizing: border-box !important;
            }

            /* Sidebar in grid-16-8 layout - fixed width, never shrinks */
            #content .grid-16-8.clearfix > .aside,
            .grid-16-8.clearfix > .aside {
                width: 320px !important;
                min-width: 320px !important;
                max-width: 320px !important;
            }

            /* ============================================
               Subject Detail Page Layout Optimization
               Move rating section to right sidebar area

               IMPORTANT: Use float-based layout for maximum compatibility
               with other Douban enhancement scripts (scripts1.json, scripts2.json)
               ============================================ */

            /* Subject header: poster + metadata + rating.
               On movie pages Douban wraps #mainpic and #info inside
               .subject.clearfix, while #interest_sectl is a sibling inside
               .subjectwrap.clearfix. Target the real wrapper so all three
               regions can share one row. */
            #content .article .subjectwrap.clearfix {
                display: grid !important;
                grid-template-columns: minmax(210px, 260px) minmax(420px, 1fr) 300px !important;
                gap: 28px !important;
                align-items: start !important;
                width: 100% !important;
                max-width: 100% !important;
                box-sizing: border-box !important;
            }

            #content .article .subjectwrap.clearfix > .subject.clearfix {
                grid-column: 1 / 3 !important;
                display: grid !important;
                grid-template-columns: minmax(210px, 260px) minmax(420px, 1fr) !important;
                gap: 28px !important;
                align-items: start !important;
                float: none !important;
                width: 100% !important;
                max-width: 100% !important;
                min-width: 0 !important;
            }

            /* Poster image container */
            #content .article .subjectwrap.clearfix > .subject.clearfix > #mainpic {
                float: none !important;
                margin: 0 !important;
                width: 100% !important;
                max-width: 260px !important;
            }

            #content .article .subjectwrap.clearfix > .subject.clearfix > #mainpic img {
                display: block !important;
                width: 100% !important;
                max-width: 260px !important;
                height: auto !important;
            }

            /* Info section (title, director, cast, etc.) */
            #content .article .subjectwrap.clearfix > .subject.clearfix > #info,
            #content .article .subjectwrap.clearfix > .subject.clearfix > .subject-info {
                float: none !important;
                min-width: 0 !important;
                width: auto !important;
                max-width: none !important;
                overflow: visible !important;
                line-height: 1.7 !important;
            }

            /* Rating section - keep it as a stable right column in the subject header */
            #content .article .subjectwrap.clearfix > #interest_sectl {
                grid-column: 3 !important;
                float: none !important;
                clear: none !important;
                margin: 0 !important;
                width: 300px !important;
                min-width: 300px !important;
                max-width: 300px !important;
                box-sizing: border-box !important;
            }

            /* Ensure content below clears floats properly */
            .article > .indent,
            .article > div.indent,
            .related-info {
                clear: both !important;
                padding-top: 20px !important;
            }

            #content .grid-16-8.clearfix > .article > .indent {
                width: 100% !important;
                max-width: 100% !important;
                box-sizing: border-box !important;
            }

            /* Summary text must expand with the widened article, not keep
               Douban's original narrow prose measure inside the full section. */
            #content .grid-16-8.clearfix > .article .related-info,
            #content .grid-16-8.clearfix > .article .related-info .indent,
            #content .grid-16-8.clearfix > .article .related-info #link-report-intra,
            #content .grid-16-8.clearfix > .article .related-info .short,
            #content .grid-16-8.clearfix > .article .related-info .all,
            #content .grid-16-8.clearfix > .article .related-info span[property="v:summary"] {
                width: 100% !important;
                max-width: 100% !important;
                box-sizing: border-box !important;
            }

            #content .grid-16-8.clearfix > .article .related-info .short,
            #content .grid-16-8.clearfix > .article .related-info .all:not(.hidden),
            #content .grid-16-8.clearfix > .article .related-info span[property="v:summary"] {
                display: block !important;
                line-height: 1.75 !important;
            }

            #content .grid-16-8.clearfix > .article .related-info .all.hidden {
                display: none !important;
            }

            /* ============================================
               Photo Gallery Page Optimization
               For movie domain only: /subject/{id}/photos, /subject/{id}/all_photos

               Supports:
               - /subject/{id}/photos
               - /subject/{id}/all_photos
               - /subject/{id}/photos?type=S (stills)
               - /subject/{id}/photos?type=W (wallpapers)
               - /subject/{id}/photos?type=P (posters)

               Actual structure (verified via browser):
               - <ul class="poster-col3 clearfix"> contains photo items
               - Parent is <article> element
               ============================================ */

            /* Photo gallery container - expand to full width (movie photos pages only) */
            article > ul.poster-col3 {
                max-width: 100% !important;
                width: 100% !important;
            }

            /* Photo grid - responsive masonry layout */
            ul.poster-col3 {
                display: grid !important;
                grid-template-columns: repeat(auto-fill, minmax(${pageConfig.gridMinSize || '180px'}, 1fr)) !important;
                gap: ${pageConfig.gridGap || '12px'} !important;
                width: 100% !important;
                padding: 0 !important;
                margin: 0 !important;
                list-style: none !important;
            }

            /* Individual photo items */
            ul.poster-col3 li {
                position: relative !important;
                overflow: hidden !important;
                border-radius: 4px !important;
                background-color: #f5f5f5 !important;
            }

            /* Photo images - maintain aspect ratio */
            ul.poster-col3 img {
                width: 100% !important;
                height: auto !important;
                display: block !important;
                object-fit: cover !important;
                transition: transform 0.3s ease !important;
            }

            /* Hover effect for photos */
            ul.poster-col3 li:hover img {
                transform: scale(1.05) !important;
            }

            /* Photo pagination controls */
            .paginator {
                margin-top: 24px !important;
                text-align: center !important;
            }

            /* Grid layout improvements for list pages - be specific */
            ul.grid-view,
            .subject-list ul {
                display: grid !important;
                grid-template-columns: repeat(auto-fill, minmax(${pageConfig.gridMinSize || '280px'}, 1fr)) !important;
                gap: ${pageConfig.gridGap || global.gap} !important;
                width: 100% !important;
                padding: 0 !important;
                margin: 0 !important;
            }

            /* Card/item styling for better visual hierarchy */
            .subject-item,
            li.item,
            div.item,
            .card,
            .info-wrapper {
                transition: transform 0.2s ease, box-shadow 0.2s ease !important;
            }

            .subject-item:hover,
            li.item:hover,
            div.item:hover {
                transform: translateY(-2px) !important;
            }

            /* Image optimization for high-DPI displays */
            img[src*="photo"],
            img[class*="cover"],
            .poster img {
                object-fit: cover !important;
                width: 100% !important;
                height: auto !important;
            }

            /* Text readability - only for main content paragraphs */
            .article p,
            .article-content p {
                font-size: 15px !important;
                line-height: 1.7 !important;
            }

            /* Header navigation optimization */
            #db-global-nav,
            #header,
            nav {
                position: sticky !important;
                top: 0 !important;
                z-index: 1000 !important;
                backdrop-filter: blur(8px) !important;
            }

            /* Table responsiveness */
            table {
                width: 100% !important;
                overflow-x: auto !important;
                display: block !important;
            }

            /* Form elements scaling */
            input[type="text"],
            input[type="search"],
            textarea,
            select {
                max-width: 100% !important;
                font-size: 14px !important;
            }

            /* Modal and overlay centering */
            .modal,
            .dialog,
            div[class*="modal"],
            div[class*="dialog"] {
                max-width: 90vw !important;
                margin: auto !important;
            }

            /* Video player container */
            .video-player,
            iframe[src*="video"],
            video {
                width: 100% !important;
                max-width: 100% !important;
                aspect-ratio: 16 / 9 !important;
            }

            /* Comment section - don't force width changes */
            .review-list,
            .comments-list {
                width: 100% !important;
            }

            /* Rating and score display */
            .rating,
            .score,
            [class*="rating"],
            [class*="score"] {
                font-size: 16px !important;
            }

            /* Tag clouds and categories */
            .tags,
            [class*="tag"] {
                display: flex !important;
                flex-wrap: wrap !important;
                gap: 8px !important;
            }

            /* Footer spacing */
            footer,
            #footer,
            div[id*="footer"] {
                padding: 40px 20px !important;
                text-align: center !important;
            }

            /* Responsive breakpoints - conservative approach */
            @media (max-width: 1200px) {
                #wrapper {
                    width: 95vw !important;
                    max-width: 1200px !important;
                    min-width: auto !important;
                }

                #content .article .subjectwrap.clearfix {
                    grid-template-columns: minmax(180px, 220px) minmax(320px, 1fr) 280px !important;
                    gap: 22px !important;
                }

                #content .article .subjectwrap.clearfix > .subject.clearfix {
                    grid-template-columns: minmax(180px, 220px) minmax(320px, 1fr) !important;
                    gap: 22px !important;
                }

                #content .article .subjectwrap.clearfix > .subject.clearfix > #mainpic,
                #content .article .subjectwrap.clearfix > .subject.clearfix > #mainpic img {
                    max-width: 220px !important;
                }

                #content .article .subjectwrap.clearfix > #interest_sectl {
                    width: 280px !important;
                    min-width: 280px !important;
                    max-width: 280px !important;
                }
            }

            @media (max-width: 768px) {
                #wrapper {
                    width: 100vw !important;
                    min-width: auto !important;
                }

                .grid-16-8.clearfix > .article,
                .grid-16-8.clearfix > .aside {
                    width: 100% !important;
                    float: none !important;
                }

                #content .grid-16-8.clearfix,
                .grid-16-8.clearfix {
                    grid-template-columns: 1fr !important;
                }

                #content .article .subjectwrap.clearfix {
                    display: grid !important;
                    grid-template-columns: 1fr !important;
                    gap: 16px !important;
                }

                #content .article .subjectwrap.clearfix > .subject.clearfix {
                    grid-column: 1 !important;
                    grid-template-columns: minmax(120px, 34vw) minmax(0, 1fr) !important;
                    gap: 16px !important;
                }

                #content .article .subjectwrap.clearfix > .subject.clearfix > #mainpic,
                #content .article .subjectwrap.clearfix > .subject.clearfix > #mainpic img {
                    max-width: 180px !important;
                }

                #content .article .subjectwrap.clearfix > #interest_sectl {
                    grid-column: 1 !important;
                    width: 100% !important;
                    min-width: 0 !important;
                    max-width: none !important;
                }
            }

            /* Dark mode support (if available) */
            @media (prefers-color-scheme: dark) {
                body,
                #wrapper,
                .article {
                    background-color: var(--color-bg, #1a1a1a) !important;
                    color: var(--color-text, #e0e0e0) !important;
                }
            }

            /* Print optimization */
            @media print {
                #wrapper,
                .grid-16-8 {
                    width: 100% !important;
                    max-width: none !important;
                }

                nav,
                aside,
                footer,
                .ads {
                    display: none !important;
                }
            }

            /* Accessibility improvements */
            *:focus-visible {
                outline: 2px solid var(--color-focus, #4a90e2) !important;
                outline-offset: 2px !important;
            }

            /* Smooth scrolling */
            html {
                scroll-behavior: smooth !important;
            }

            /* Selection styling */
            ::selection {
                background-color: rgba(74, 144, 226, 0.2) !important;
                color: inherit !important;
            }
        `;
    }

    // Inject styles into the page
    function injectStyles() {
        const pageType = getPageType();
        const styles = generateStyles(pageType);
        const existing = document.getElementById('douban-widescreen-optimizer');
        if (existing) {
            existing.remove();
        }

        const styleElement = document.createElement('style');
        styleElement.id = 'douban-widescreen-optimizer';
        styleElement.type = 'text/css';
        styleElement.textContent = styles;

        // Append at the end so optimizer rules win over Douban and late user styles.
        document.head.appendChild(styleElement);

        console.log(`[Douban Widescreen Optimizer] Applied optimizations for page type: ${pageType}`);
    }

    function applySubjectSummaryLayout() {
        if (getPageType() !== 'subject') return;

        const article = document.querySelector('#content .grid-16-8.clearfix > .article');
        const summary = document.querySelector('#content .grid-16-8.clearfix > .article .related-info');
        if (!article || !summary) return;

        const articleRect = article.getBoundingClientRect();
        const articleStyle = window.getComputedStyle(article);
        const horizontalPadding = parseFloat(articleStyle.paddingLeft || '0') + parseFloat(articleStyle.paddingRight || '0');
        const summaryWidth = Math.max(600, Math.floor(articleRect.width - horizontalPadding));
        const summaryWidthPx = `${summaryWidth}px`;

        const selectors = [
            '#content .grid-16-8.clearfix > .article .related-info',
            '#content .grid-16-8.clearfix > .article .related-info .indent',
            '#content .grid-16-8.clearfix > .article .related-info #link-report-intra',
            '#content .grid-16-8.clearfix > .article .related-info .short',
            '#content .grid-16-8.clearfix > .article .related-info .all',
            '#content .grid-16-8.clearfix > .article .related-info span[property="v:summary"]'
        ];

        document.querySelectorAll(selectors.join(',')).forEach((element) => {
            element.style.setProperty('width', summaryWidthPx, 'important');
            element.style.setProperty('min-width', summaryWidthPx, 'important');
            element.style.setProperty('max-width', summaryWidthPx, 'important');
            element.style.setProperty('box-sizing', 'border-box', 'important');
            element.style.setProperty('float', 'none', 'important');
            element.style.setProperty('clear', 'both', 'important');
            element.style.setProperty('margin-left', '0', 'important');
            element.style.setProperty('margin-right', '0', 'important');
            element.style.setProperty('white-space', 'normal', 'important');
            element.style.setProperty('word-break', 'normal', 'important');
            element.style.setProperty('overflow-wrap', 'break-word', 'important');
        });

        document.querySelectorAll(
            '#content .grid-16-8.clearfix > .article .related-info .short, ' +
            '#content .grid-16-8.clearfix > .article .related-info .all:not(.hidden), ' +
            '#content .grid-16-8.clearfix > .article .related-info span[property="v:summary"]'
        ).forEach((element) => {
            element.style.setProperty('display', 'block', 'important');
            element.style.setProperty('line-height', '1.75', 'important');
        });

        document.querySelectorAll('#content .grid-16-8.clearfix > .article .related-info .all.hidden').forEach((element) => {
            element.style.setProperty('display', 'none', 'important');
        });
    }

    function scheduleSubjectSummaryLayout() {
        applySubjectSummaryLayout();
        window.setTimeout(applySubjectSummaryLayout, 300);
        window.setTimeout(applySubjectSummaryLayout, 1200);
        let reapplyCount = 0;
        const intervalId = window.setInterval(() => {
            applySubjectSummaryLayout();
            reapplyCount += 1;
            if (reapplyCount >= 20) {
                window.clearInterval(intervalId);
            }
        }, 500);
    }

    function observeSubjectSummaryChanges() {
        if (getPageType() !== 'subject') return;

        const summary = document.querySelector('#content .grid-16-8.clearfix > .article .related-info');
        if (!summary || summary.dataset.doubanWidescreenObserved === '1') return;

        summary.dataset.doubanWidescreenObserved = '1';
        let pending = false;
        const observer = new MutationObserver(() => {
            if (pending) return;
            pending = true;
            window.requestAnimationFrame(() => {
                pending = false;
                applySubjectSummaryLayout();
            });
        });

        observer.observe(summary, {
            subtree: true,
            childList: true,
            attributes: true,
            attributeFilter: ['style', 'class']
        });
    }

    // Handle dynamic content loading (SPA-like behavior)
    function observeDOMChanges() {
        const observer = new MutationObserver((mutations) => {
            let shouldReapply = false;

            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length > 0) {
                    // Check if new content containers were added
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.matches && node.matches('#wrapper, #content, .grid-16-8, main, article')) {
                                shouldReapply = true;
                            }
                        }
                    });
                }
            });

            if (shouldReapply) {
                injectStyles();
                scheduleSubjectSummaryLayout();
                observeSubjectSummaryChanges();
            }
        });

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

    // Initialize when DOM is ready
    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                injectStyles();
                scheduleSubjectSummaryLayout();
                observeSubjectSummaryChanges();
                observeDOMChanges();
            });
        } else {
            injectStyles();
            scheduleSubjectSummaryLayout();
            observeSubjectSummaryChanges();
            observeDOMChanges();
        }
    }

    // Start the optimizer
    init();

})();