专注模式:拒绝花哨,采用高对比度实心按钮,修复按钮不显示的问题。
// ==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();
})();