Page Background Controller

Advanced background protection with full gradient support

2025/11/24のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name           Page Background Controller
// @namespace      Background Color
// @description    Advanced background protection with full gradient support
// @version        3.1
// @include        http*
// @include        ftp
// @match          *://*/*
// @exclude
// @license MIT
// @grant    GM_addStyle
// ==/UserScript==

/**
 * 渐变感知背景控制器
 *
 * 新增功能:
 * - 完整的CSS渐变支持(linear、radial、conic等)
 * - 智能渐变颜色替换
 * - 保持渐变方向和样式
 * - 渐变缓存优化
 */

(function() {
    'use strict';

    // 配置
    const CONFIG = {
        // Solarized护眼色彩
        colors: {
            primary: '#FDF6E3',    // Solarized base3 (最浅)
            secondary: '#EEE8D5',  // Solarized base2 (浅)
            tertiary: '#E8DCC6',   // 介于base2和base3之间
            accent: '#DDD6C1',     // 更深一点的米色
            text: '#073642',       // Solarized base02 (深)
            link: '#268bd2'        // Solarized blue
        },

        // 渐变配置
        gradient: {
            preserveDirection: true,     // 保持渐变方向
            minColorStops: 2,           // 最少颜色停止点
            maxColorStops: 5,           // 最多颜色停止点
            blendIntensity: 0.8,        // 混合强度
            smoothTransition: true      // 平滑过渡
        },

        // 检测阈值
        thresholds: {
            lightness: 230,             // 亮度阈值
            saturation: 30,             // 饱和度阈值
            gradientComplexity: 3       // 渐变复杂度阈值
        }
    };

    // 颜色工具类
    class ColorUtils {
        // 解析各种颜色格式
        static parseColor(colorStr) {
            if (!colorStr) return null;

            // RGB/RGBA
            let match = colorStr.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/);
            if (match) {
                return {
                    r: parseInt(match[1]),
                    g: parseInt(match[2]),
                    b: parseInt(match[3]),
                    a: match[4] ? parseFloat(match[4]) : 1,
                    format: 'rgb'
                };
            }

            // HSL/HSLA
            match = colorStr.match(/hsla?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([\d.]+))?\s*\)/);
            if (match) {
                const hsl = {
                    h: parseInt(match[1]),
                    s: parseInt(match[2]),
                    l: parseInt(match[3]),
                    a: match[4] ? parseFloat(match[4]) : 1,
                    format: 'hsl'
                };
                return this.hslToRgb(hsl);
            }

            // HEX
            match = colorStr.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
            if (match) {
                return {
                    r: parseInt(match[1], 16),
                    g: parseInt(match[2], 16),
                    b: parseInt(match[3], 16),
                    a: 1,
                    format: 'hex'
                };
            }

            // 颜色名称
            const namedColors = {
                'white': { r: 255, g: 255, b: 255, a: 1 },
                'black': { r: 0, g: 0, b: 0, a: 1 },
                'red': { r: 255, g: 0, b: 0, a: 1 },
                'green': { r: 0, g: 128, b: 0, a: 1 },
                'blue': { r: 0, g: 0, b: 255, a: 1 },
                'yellow': { r: 255, g: 255, b: 0, a: 1 },
                'cyan': { r: 0, g: 255, b: 255, a: 1 },
                'magenta': { r: 255, g: 0, b: 255, a: 1 },
                'silver': { r: 192, g: 192, b: 192, a: 1 },
                'gray': { r: 128, g: 128, b: 128, a: 1 },
                'transparent': { r: 0, g: 0, b: 0, a: 0 }
            };

            if (namedColors[colorStr.toLowerCase()]) {
                return { ...namedColors[colorStr.toLowerCase()], format: 'named' };
            }

            return null;
        }

        // HSL转RGB
        static hslToRgb({ h, s, l, a }) {
            h /= 360;
            s /= 100;
            l /= 100;

            let r, g, b;

            if (s === 0) {
                r = g = b = l;
            } else {
                const hue2rgb = (p, q, t) => {
                    if (t < 0) t += 1;
                    if (t > 1) t -= 1;
                    if (t < 1/6) return p + (q - p) * 6 * t;
                    if (t < 1/2) return q;
                    if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
                    return p;
                };

                const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
                const p = 2 * l - q;
                r = hue2rgb(p, q, h + 1/3);
                g = hue2rgb(p, q, h);
                b = hue2rgb(p, q, h - 1/3);
            }

            return {
                r: Math.round(r * 255),
                g: Math.round(g * 255),
                b: Math.round(b * 255),
                a: a,
                format: 'rgb'
            };
        }

        // 判断是否为亮色
        static isLightColor(color) {
            if (!color) return false;
            const { r, g, b } = color;
            const brightness = (r * 299 + g * 587 + b * 114) / 1000;
            return brightness > CONFIG.thresholds.lightness;
        }

        // 颜色混合
        static blendColors(color1, color2, ratio = 0.5) {
            if (!color1 || !color2) return color1 || color2;

            return {
                r: Math.round(color1.r * (1 - ratio) + color2.r * ratio),
                g: Math.round(color1.g * (1 - ratio) + color2.g * ratio),
                b: Math.round(color1.b * (1 - ratio) + color2.b * ratio),
                a: color1.a * (1 - ratio) + color2.a * ratio,
                format: 'rgb'
            };
        }

        // 颜色到字符串
        static colorToString(color) {
            if (!color) return 'transparent';
            const { r, g, b, a } = color;

            if (a === 1) {
                return `rgb(${r}, ${g}, ${b})`;
            } else {
                return `rgba(${r}, ${g}, ${b}, ${a})`;
            }
        }

        // 智能替换亮色
        static replaceIfLight(color) {
            if (!this.isLightColor(color)) {
                return color; // 保持深色
            }

            const { r, g, b, a } = color;
            const brightness = (r + g + b) / 3;

            // 根据亮度选择替换色
            let replacement;
            if (brightness > 250) {
                replacement = this.parseColor(CONFIG.colors.primary);
            } else if (brightness > 240) {
                replacement = this.parseColor(CONFIG.colors.secondary);
            } else if (brightness > 220) {
                replacement = this.parseColor(CONFIG.colors.tertiary);
            } else {
                replacement = this.parseColor(CONFIG.colors.accent);
            }

            // 保持透明度
            if (replacement) {
                replacement.a = a;
            }

            return replacement;
        }
    }

    // 渐变解析器
    class GradientParser {
        // 解析CSS渐变
        static parseGradient(backgroundImage) {
            if (!backgroundImage || backgroundImage === 'none') return null;

            const gradientTypes = [
                'linear-gradient',
                'radial-gradient',
                'conic-gradient',
                'repeating-linear-gradient',
                'repeating-radial-gradient'
            ];

            for (const type of gradientTypes) {
                const regex = new RegExp(`${type}\\s*\\(([^)]+)\\)`, 'i');
                const match = backgroundImage.match(regex);

                if (match) {
                    return {
                        type: type,
                        rawParams: match[1],
                        fullMatch: match[0],
                        parsed: this.parseGradientParams(type, match[1])
                    };
                }
            }

            return null;
        }

        // 解析渐变参数
        static parseGradientParams(type, params) {
            const result = {
                direction: null,
                colorStops: [],
                shape: null,
                size: null,
                position: null
            };

            // 分割参数(处理嵌套函数)
            const parts = this.splitParams(params);

            if (type === 'linear-gradient' || type === 'repeating-linear-gradient') {
                result.direction = this.parseDirection(parts[0]);
                result.colorStops = this.parseColorStops(parts.slice(result.direction ? 1 : 0));
            } else if (type === 'radial-gradient' || type === 'repeating-radial-gradient') {
                const { shape, size, position, remaining } = this.parseRadialParams(parts);
                result.shape = shape;
                result.size = size;
                result.position = position;
                result.colorStops = this.parseColorStops(remaining);
            } else if (type === 'conic-gradient') {
                const { angle, position, remaining } = this.parseConicParams(parts);
                result.direction = angle;
                result.position = position;
                result.colorStops = this.parseColorStops(remaining);
            }

            return result;
        }

        // 分割参数(处理嵌套)
        static splitParams(params) {
            const parts = [];
            let current = '';
            let depth = 0;
            let inString = false;
            let stringChar = '';

            for (let i = 0; i < params.length; i++) {
                const char = params[i];

                if ((char === '"' || char === "'") && !inString) {
                    inString = true;
                    stringChar = char;
                } else if (char === stringChar && inString) {
                    inString = false;
                    stringChar = '';
                } else if (!inString) {
                    if (char === '(') depth++;
                    else if (char === ')') depth--;
                    else if (char === ',' && depth === 0) {
                        parts.push(current.trim());
                        current = '';
                        continue;
                    }
                }

                current += char;
            }

            if (current.trim()) {
                parts.push(current.trim());
            }

            return parts;
        }

        // 解析线性渐变方向
        static parseDirection(part) {
            if (!part) return null;

            const trimmed = part.trim().toLowerCase();

            // 角度
            if (trimmed.match(/^\d+deg$/)) {
                return trimmed;
            }

            // 关键字方向
            const directions = [
                'to top', 'to bottom', 'to left', 'to right',
                'to top left', 'to top right', 'to bottom left', 'to bottom right'
            ];

            if (directions.includes(trimmed)) {
                return trimmed;
            }

            return null;
        }

        // 解析颜色停止点
        static parseColorStops(parts) {
            const colorStops = [];

            for (const part of parts) {
                const stop = this.parseColorStop(part);
                if (stop) {
                    colorStops.push(stop);
                }
            }

            return colorStops;
        }

        // 解析单个颜色停止点
        static parseColorStop(part) {
            const trimmed = part.trim();

            // 匹配颜色+位置的模式
            // 例如: "red 0%", "#fff 50px", "rgb(255,0,0) 25%"
            const match = trimmed.match(/^(.+?)(?:\s+([\d.]+%|[\d.]+px|[\d.]+))?$/);

            if (match) {
                const color = ColorUtils.parseColor(match[1].trim());
                const position = match[2] || null;

                if (color) {
                    return {
                        color: color,
                        position: position
                    };
                }
            }

            return null;
        }

        // 解析径向渐变参数
        static parseRadialParams(parts) {
            let shape = null;
            let size = null;
            let position = null;
            let remaining = [...parts];

            // 简化实现,主要处理颜色停止点
            // 在实际使用中可以扩展更复杂的径向渐变解析

            return { shape, size, position, remaining };
        }

        // 解析圆锥渐变参数
        static parseConicParams(parts) {
            let angle = null;
            let position = null;
            let remaining = [...parts];

            // 简化实现,主要处理颜色停止点
            // 在实际使用中可以扩展更复杂的圆锥渐变解析

            return { angle, position, remaining };
        }
    }

    // 渐变转换器
    class GradientTransformer {
        // 转换渐变为护眼版本
        static transformGradient(gradientInfo) {
            if (!gradientInfo || !gradientInfo.parsed) return null;

            const { type, parsed } = gradientInfo;
            const { direction, colorStops, shape, size, position } = parsed;

            // 转换颜色停止点
            const transformedStops = this.transformColorStops(colorStops);

            if (transformedStops.length === 0) return null;

            // 重构渐变字符串
            return this.buildGradientString(type, {
                direction,
                colorStops: transformedStops,
                shape,
                size,
                position
            });
        }

        // 转换颜色停止点
        static transformColorStops(colorStops) {
            if (!colorStops || colorStops.length === 0) return [];

            const transformed = [];

            for (const stop of colorStops) {
                if (stop && stop.color) {
                    const newColor = ColorUtils.replaceIfLight(stop.color);
                    if (newColor) {
                        transformed.push({
                            color: newColor,
                            position: stop.position
                        });
                    }
                }
            }

            // 确保至少有2个颜色停止点
            if (transformed.length === 1) {
                const single = transformed[0];
                transformed.push({
                    color: single.color,
                    position: '100%'
                });
            } else if (transformed.length === 0) {
                // 如果没有有效的颜色,使用默认护眼色
                const defaultColor = ColorUtils.parseColor(CONFIG.colors.primary);
                transformed.push(
                    { color: defaultColor, position: '0%' },
                    { color: defaultColor, position: '100%' }
                );
            }

            return transformed;
        }

        // 构建渐变字符串
        static buildGradientString(type, { direction, colorStops, shape, size, position }) {
            let params = [];

            if (type === 'linear-gradient' || type === 'repeating-linear-gradient') {
                if (direction) params.push(direction);
            } else if (type === 'radial-gradient' || type === 'repeating-radial-gradient') {
                let radialParams = [];
                if (shape) radialParams.push(shape);
                if (size) radialParams.push(size);
                if (position) radialParams.push(`at ${position}`);
                if (radialParams.length > 0) {
                    params.push(radialParams.join(' '));
                }
            } else if (type === 'conic-gradient') {
                if (direction) params.push(`from ${direction}`);
                if (position) params.push(`at ${position}`);
            }

            // 添加颜色停止点
            const stopStrings = colorStops.map(stop => {
                const colorStr = ColorUtils.colorToString(stop.color);
                return stop.position ? `${colorStr} ${stop.position}` : colorStr;
            });

            params = params.concat(stopStrings);

            return `${type}(${params.join(', ')})`;
        }
    }

    // 主处理器
    class BackgroundProcessor {
        constructor() {
            this.cache = new Map();
            this.processedElements = new WeakSet();
        }

        // 处理元素背景
        processElement(element) {
            if (this.processedElements.has(element)) return;

            try {
                const computedStyle = getComputedStyle(element);
                const backgroundImage = computedStyle.backgroundImage;
                const backgroundColor = computedStyle.backgroundColor;

                let hasChanges = false;

                // 处理渐变背景
                if (backgroundImage && backgroundImage !== 'none') {
                    const newBackgroundImage = this.processBackgroundImage(backgroundImage);
                    if (newBackgroundImage !== backgroundImage) {
                        element.style.setProperty('background-image', newBackgroundImage, 'important');
                        hasChanges = true;
                    }
                }

                // 处理纯色背景
                if (backgroundColor && backgroundColor !== 'rgba(0, 0, 0, 0)') {
                    const color = ColorUtils.parseColor(backgroundColor);
                    const newColor = ColorUtils.replaceIfLight(color);

                    if (newColor && newColor !== color) {
                        const newColorStr = ColorUtils.colorToString(newColor);
                        element.style.setProperty('background-color', newColorStr, 'important');
                        hasChanges = true;
                    }
                }

                // 处理文本颜色
                const textColor = computedStyle.color;
                if (textColor) {
                    const color = ColorUtils.parseColor(textColor);
                    if (ColorUtils.isLightColor(color)) {
                        element.style.setProperty('color', CONFIG.colors.text, 'important');
                        hasChanges = true;
                    }
                }

                if (hasChanges) {
                    this.processedElements.add(element);
                }

            } catch (error) {
                console.warn('Error processing element background:', error);
            }
        }

        // 处理背景图像(渐变)
        processBackgroundImage(backgroundImage) {
            // 检查缓存
            if (this.cache.has(backgroundImage)) {
                return this.cache.get(backgroundImage);
            }

            // 分割多个背景图像
            const backgrounds = this.splitBackgrounds(backgroundImage);
            const processedBackgrounds = [];

            for (const bg of backgrounds) {
                const gradientInfo = GradientParser.parseGradient(bg.trim());

                if (gradientInfo) {
                    const transformed = GradientTransformer.transformGradient(gradientInfo);
                    processedBackgrounds.push(transformed || bg);
                } else {
                    processedBackgrounds.push(bg); // 非渐变背景保持原样
                }
            }

            const result = processedBackgrounds.join(', ');

            // 缓存结果
            this.cache.set(backgroundImage, result);

            return result;
        }

        // 分割多个背景
        splitBackgrounds(backgroundImage) {
            // 简化实现:按逗号分割,但需要注意函数内的逗号
            const backgrounds = [];
            let current = '';
            let depth = 0;

            for (let i = 0; i < backgroundImage.length; i++) {
                const char = backgroundImage[i];

                if (char === '(') {
                    depth++;
                } else if (char === ')') {
                    depth--;
                } else if (char === ',' && depth === 0) {
                    backgrounds.push(current.trim());
                    current = '';
                    continue;
                }

                current += char;
            }

            if (current.trim()) {
                backgrounds.push(current.trim());
            }

            return backgrounds;
        }

        // 批量处理元素
        processBatch(elements, startIndex = 0) {
            const batchSize = 50;
            const endIndex = Math.min(startIndex + batchSize, elements.length);

            for (let i = startIndex; i < endIndex; i++) {
                this.processElement(elements[i]);
            }

            if (endIndex < elements.length) {
                requestAnimationFrame(() => {
                    this.processBatch(elements, endIndex);
                });
            }
        }

        // 清理缓存
        cleanup() {
            if (this.cache.size > 1000) {
                this.cache.clear();
            }
        }
    }

    // CSS样式注入器
    const cssInjector = {
        injectBaseStyles() {
            const css = `
                /* 基础护眼样式 */
                html, body {
                    background-color: ${CONFIG.colors.primary} !important;
                    color: ${CONFIG.colors.text} !important;
                }

                /* 常见渐变覆盖 */
                .gradient, .bg-gradient, [class*="gradient"] {
                    background-image: linear-gradient(to bottom, ${CONFIG.colors.primary}, ${CONFIG.colors.secondary}) !important;
                }

                /* 白色渐变覆盖 */
                [style*="linear-gradient"][style*="white"],
                [style*="linear-gradient"][style*="#fff"],
                [style*="linear-gradient"][style*="rgb(255"] {
                    background-image: linear-gradient(135deg, ${CONFIG.colors.primary} 0%, ${CONFIG.colors.secondary} 50%, ${CONFIG.colors.tertiary} 100%) !important;
                }

                /* 径向渐变覆盖 */
                [style*="radial-gradient"][style*="white"],
                [style*="radial-gradient"][style*="#fff"] {
                    background-image: radial-gradient(circle, ${CONFIG.colors.secondary}, ${CONFIG.colors.primary}) !important;
                }
            `;

            try {
                GM_addStyle(css);
            } catch (e) {
                const style = document.createElement('style');
                style.textContent = css;
                (document.head || document.documentElement).appendChild(style);
            }
        }
    };

    // 主控制器
    function init() {
        console.log('🎨 Gradient-Aware Background Controller starting...');

        const processor = new BackgroundProcessor();

        // 1. 注入基础CSS
        cssInjector.injectBaseStyles();

        // 2. 处理现有元素
        requestAnimationFrame(() => {
            const elements = document.querySelectorAll('*');
            processor.processBatch(Array.from(elements));
        });

        // 3. 监听DOM变化
        const observer = new MutationObserver((mutations) => {
            const newElements = [];

            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        newElements.push(node);
                        const children = node.querySelectorAll('*');
                        newElements.push(...children);
                    }
                });
            });

            if (newElements.length > 0) {
                processor.processBatch(newElements);
            }
        });

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

        // 4. 定期清理缓存
        setInterval(() => {
            processor.cleanup();
        }, 60000);

        console.log('✅ Gradient-Aware Background Controller activated');
    }

    // 启动
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();