Block Site

专注模式:拒绝花哨,采用高对比度实心按钮,修复按钮不显示的问题。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Block Site
// @namespace    http://tampermonkey.net/
// @version      3.6
// @description  专注模式:拒绝花哨,采用高对比度实心按钮,修复按钮不显示的问题。
// @author       werflala
// @license      MIT
// @match        *://*/*
// @connect      open.iciba.com
// @connect      iciba.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // ================= 🎛️ 布局与样式控制台 =================

    const CONFIG = {
        text: {
            title: "STAY FOCUSED", // 大写更醒目
            subtitle: "Time is money, friend."
        },

        layout: {
            // --- 整体位置 ---
            verticalOffset: "-20px",

            // --- 卡片尺寸 ---
            cardWidth: "1000px",
            cardMinHeight: "380px",    // 增加最小高度,确保空间充足
            cardPadding: "50px",

            // --- 字体大小 ---
            titleSize: "48px",
            quoteSize: "32px",
            cnSize: "18px"
        },

        colors: {
            background: "#f0f2f5",     // 浅灰背景
            cardBg: "#FFFFFF",         // 纯白卡片
            textMain: "#000000",       // 纯黑文字 (对比度最高)
            textSub: "#555555",        // 深灰副文

            // 【关键修改】按钮颜色 - 高对比度
            btnPrimaryBg: "#000000",   // 主按钮:纯黑背景
            btnPrimaryText: "#FFFFFF", // 主按钮:纯白文字

            btnSecBorder: "#000000",   // 次按钮:黑色边框
            btnSecText: "#000000"      // 次按钮:黑色文字
        },

        fonts: {
            display: 'Arial, "Helvetica Neue", Helvetica, sans-serif', // 用最通用的字体,防止加载失败
            quote: 'Georgia, serif'
        }
    };

    // ===============================================================

    const API_URL = "https://open.iciba.com/dsapi/";
    const LOCAL_QUOTES = [
        { content: "The only way to do great work is to love what you do.", note: "做伟大的工作的唯一途径是热爱你所做的事情。" },
        { content: "Stay hungry, stay foolish.", note: "求知若饥,虚心若愚。" }
    ];

    // CSS 修改版:实心、描边、防遮挡
    const UI_CSS = `
        :root {
            --bg-color: ${CONFIG.colors.background};
            --card-bg: ${CONFIG.colors.cardBg};
            --text-main: ${CONFIG.colors.textMain};
            --text-sub: ${CONFIG.colors.textSub};
        }

        body, html { margin: 0; padding: 0; height: 100%; overflow: hidden; }

        #block-focus-container {
            display: flex; flex-direction: column; justify-content: center; align-items: center;
            height: 100vh;
            font-family: ${CONFIG.fonts.display};
            background-color: var(--bg-color); color: var(--text-main);
            padding-top: ${CONFIG.layout.verticalOffset};
            z-index: 2147483647; /* 确保层级最高 */
        }

        .header-section {
            text-align: center;
            margin-bottom: 40px;
            flex-shrink: 0;
        }
        .motto-title {
            font-size: ${CONFIG.layout.titleSize};
            font-weight: 900; /* 特粗 */
            letter-spacing: 1px; margin-bottom: 10px; color: var(--text-main);
            text-transform: uppercase;
        }
        .motto-sub { font-size: 20px; color: var(--text-sub); font-weight: bold; }

        /* 卡片区域 */
        .quote-card {
            background: var(--card-bg);
            width: 85%;
            max-width: ${CONFIG.layout.cardWidth};
            min-height: ${CONFIG.layout.cardMinHeight};
            padding: ${CONFIG.layout.cardPadding};

            /* 实线边框,轮廓分明 */
            border: 2px solid #000000;
            border-radius: 12px;
            box-shadow: 10px 10px 0px rgba(0,0,0,0.2); /* 硬阴影,不模糊 */

            text-align: center;
            display: flex; flex-direction: column; justify-content: space-between; align-items: center;
            position: relative;
        }

        .card-date {
            font-size: 14px; color: var(--text-sub); font-weight: bold;
            letter-spacing: 1px; margin-bottom: 20px; text-transform: uppercase;
            flex-shrink: 0; border-bottom: 2px solid #eee; padding-bottom: 5px; width: 100%;
        }

        /* 文字区域 */
        .quote-text-area {
            flex-grow: 1;
            display: flex; flex-direction: column; justify-content: center;
            margin-bottom: 30px;
            width: 100%;
        }

        .eng-sentence {
            font-family: ${CONFIG.fonts.quote};
            font-size: ${CONFIG.layout.quoteSize};
            margin-bottom: 20px;
            color: var(--text-main); font-weight: bold; line-height: 1.3;
        }
        .cn-sentence {
            font-size: ${CONFIG.layout.cnSize};
            color: var(--text-sub); font-weight: normal;
        }

        /* 按钮组 - 强制可见 */
        .btn-group {
            display: flex; gap: 20px; justify-content: center; align-items: center;
            flex-shrink: 0; /* 禁止被压缩 */
            width: 100%;
            padding-top: 20px;
            border-top: 1px solid #eee; /* 顶部分割线,防止混淆 */
        }

        /* 主按钮:纯黑实心 */
        .action-btn-primary {
            background-color: ${CONFIG.colors.btnPrimaryBg} !important;
            color: ${CONFIG.colors.btnPrimaryText} !important;
            padding: 12px 35px;
            border-radius: 6px; /* 小圆角,更硬朗 */
            border: 2px solid #000000;
            cursor: pointer;
            font-size: 16px; font-weight: bold;
            transition: all 0.1s;
            opacity: 1 !important; /* 强制不透明 */
            visibility: visible !important; /* 强制可见 */
        }
        .action-btn-primary:hover {
            background-color: #333 !important;
            transform: translate(2px, 2px); /* 按下效果 */
        }

        /* 次按钮:白底黑框 */
        .action-btn-secondary {
            background: #fff !important;
            color: ${CONFIG.colors.btnSecText} !important;
            padding: 12px 25px;
            border-radius: 6px;
            border: 2px solid ${CONFIG.colors.btnSecBorder}; /* 强制黑框 */
            cursor: pointer;
            font-size: 16px; font-weight: bold;
            display: flex; align-items: center; gap: 8px;
            opacity: 1 !important;
            visibility: visible !important;
        }
        .action-btn-secondary:hover {
            background: #f0f0f0 !important;
        }

        .focus-url { position: fixed; bottom: 20px; color: #999; font-size: 12px; font-weight: bold; background: white; padding: 5px 10px; border: 1px solid #ccc;}

        .spinner { width: 30px; height: 30px; border: 4px solid #eee; border-top-color: #000; border-radius: 50%; animation: spin 0.8s linear infinite; margin: 0 auto; }
        @keyframes spin { to { transform: rotate(360deg); } }

        /* 设置弹窗 - 高对比度 */
        #block-site-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 2147483647; display: flex; justify-content: center; align-items: center; }
        #block-site-settings-box { background: #fff; padding: 30px; border-radius: 10px; width: 450px; border: 4px solid #000; }
        .bs-title { font-size: 24px; font-weight: bold; margin-bottom: 20px; color: #000; text-align: center; border-bottom: 2px solid #000; padding-bottom: 10px;}
        .bs-textarea { width: 100%; height: 160px; padding: 10px; border: 2px solid #000; font-family: monospace; font-size: 14px; color: #000; background: #fff; resize: vertical; outline: none; margin-bottom: 20px; }
        .bs-btn { padding: 10px 30px; cursor: pointer; border-radius: 5px; font-size: 16px; font-weight: bold; border: 2px solid #000; }
        .bs-save { background: #000; color: #fff; float: right; }
        .bs-close { background: #fff; color: #000; float: right; margin-right: 15px;}
    `;

    // ================= 逻辑区域 (核心) =================

    function getRandomDate() {
        const start = new Date(2018, 0, 1);
        const end = new Date();
        return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toISOString().split('T')[0];
    }

    function getBlockList() { return GM_getValue("blockList", []); }

    function checkBlock() {
        const list = getBlockList();
        const currentUrl = window.location.href;
        const currentHostname = window.location.hostname;
        for (let rule of list) {
            if (!rule) continue;
            if (currentHostname.includes(rule) || currentUrl.includes(rule)) {
                doBlockAction(rule);
                break;
            }
        }
    }

    let audioPlayer = null;
    function playAudio(url) {
        if(!url) { alert("暂无语音"); return; }
        let secureUrl = url.startsWith('http://') ? url.replace('http://', 'https://') : url;
        if(audioPlayer) audioPlayer.pause();
        audioPlayer = new Audio(secureUrl);
        audioPlayer.play().catch(e => alert("无法播放: 请点击浏览器地址栏权限设置,允许自动播放声音。"));
    }

    function fetchOnlineQuote(callback) {
        GM_xmlhttpRequest({
            method: "GET", url: `${API_URL}?date=${getRandomDate()}`,
            onload: function(res) {
                try {
                    const data = JSON.parse(res.responseText);
                    callback(data && data.content ? data : null);
                } catch(e) { callback(null); }
            },
            onerror: function() { callback(null); }
        });
    }

    function doBlockAction(matchedRule) {
        window.stop();
        const htmlContent = `
            <div id="block-focus-container">
                <div class="header-section">
                    <div class="motto-title">${CONFIG.text.title}</div>
                    <div class="motto-sub">${CONFIG.text.subtitle}</div>
                </div>

                <div class="quote-card">
                    <div class="card-date" id="q-date">Daily Wisdom</div>
                    <div id="loading-spinner" class="spinner"></div>

                    <div id="quote-content" style="display:none; width: 100%; height: 100%; flex-direction: column; justify-content: space-between; align-items: center;">

                        <div class="quote-text-area">
                            <div id="q-en" class="eng-sentence"></div>
                            <div id="q-cn" class="cn-sentence"></div>
                        </div>

                        <div class="btn-group">
                             <button id="btn-audio" class="action-btn-secondary">
                                <span style="font-size:18px;">🔊</span> Listen
                             </button>
                             <button id="btn-next" class="action-btn-primary">NEXT QUOTE ➜</button>
                        </div>
                    </div>
                </div>
                <div class="focus-url">Blocked: ${window.location.hostname}</div>
            </div>
        `;

        const initUI = () => {
            document.body.innerHTML = htmlContent;
            GM_addStyle(UI_CSS);
            document.getElementById('btn-next').addEventListener('click', () => loadQuote());
            loadQuote();
        };
        if(document.body) initUI();
        else window.addEventListener('DOMContentLoaded', initUI);
    }

    function loadQuote() {
        const spinner = document.getElementById('loading-spinner');
        const contentBox = document.getElementById('quote-content');

        spinner.style.display = 'block';
        contentBox.style.display = 'none';

        const updateView = (data) => {
            spinner.style.display = 'none';
            contentBox.style.display = 'flex'; // Flex 布局

            document.getElementById('q-en').innerText = data.content;
            document.getElementById('q-cn').innerText = data.note;
            document.getElementById('q-date').innerText = data.dateline || "DAILY QUOTE";

            const btnAudio = document.getElementById('btn-audio');
            const newBtn = btnAudio.cloneNode(true);
            btnAudio.parentNode.replaceChild(newBtn, btnAudio);

            if(data.tts) {
                newBtn.style.display = 'flex';
                newBtn.onclick = () => playAudio(data.tts);
            } else {
                newBtn.style.display = 'none';
            }
        };

        fetchOnlineQuote((data) => {
            if(data) updateView(data);
            else {
                const r = LOCAL_QUOTES[Math.floor(Math.random() * LOCAL_QUOTES.length)];
                updateView({ content: r.content, note: r.note, dateline: "OFFLINE", tts: null });
            }
        });
    }

    function showSettings() {
        if (document.getElementById('block-site-settings-overlay')) return;
        GM_addStyle(UI_CSS);
        const list = getBlockList();
        const div = document.createElement('div');
        div.id = 'block-site-settings-overlay';
        div.innerHTML = `
            <div id="block-site-settings-box">
                <div class="bs-title">Focus List</div>
                <textarea class="bs-textarea" id="bs-input" placeholder="Enter domains...">${list.join('\n')}</textarea>
                <div style="overflow: hidden;">
                    <button class="bs-btn bs-close" id="bs-btn-close">Cancel</button>
                    <button class="bs-btn bs-save" id="bs-btn-save">Save</button>
                </div>
            </div>
        `;
        document.body.appendChild(div);
        document.getElementById('bs-btn-close').onclick = () => div.remove();
        document.getElementById('bs-btn-save').onclick = () => {
            const val = document.getElementById('bs-input').value;
            GM_setValue("blockList", [...new Set(val.split('\n').map(s => s.trim()).filter(s => s))]);
            div.remove();
            location.reload();
        };
    }

    GM_registerMenuCommand("Settings / 设置", showSettings);
    checkBlock();

})();