// ==UserScript==
// @name 夜间护眼助手(含 DarkReader 引擎 · 即时生效强化版)
// @version 6.18.6
// @author 未名 + 护眼主题扩展 + 极速引擎 + DarkReader 整合
// @description 智能暗色主题切换 · 多模式支持 · 黑名单/白名单/强制启用 · 亮度检测防误判 · 极速预加载 · 即时应用 · 永不消失恢复 · 完整设置面板 · 返回即强制恢复 · 轮询守护 · 集成 DarkReader 引擎 · 性能优化 · 设置即时生效 · DarkReader 无延迟
// @match *://*/*
// @noframes
// @run-at document-start
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_notification
// @grant GM_xmlhttpRequest
// @require https://cdn.jsdelivr.net/npm/darkreader@4.9.128/darkreader.min.js
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNOTMuNSA5NC42YzEwLjYgMCAyMC4zLTMuMyAyOC4yLTkgOC4zIDIyLjUtMzAuMiAzOC42LTU2IDM4LjYtMzIuNyAwLTU5LjMtMjUuOC01OS4zLTU3LjdzMjYuNi01Ny43IDU5LjMtNTcuN2gyLjJDNjAuMiA4LjMgNTEuMiAyMi44IDUxLjIgMzkuMmMwIDI1LjcgMjEuNCA0Ni42IDQ3LjggNDYuNnoiIGZpbGw9IiNmZmI1NzgiLz48cG9seWdvbiBwb2ludHM9IjY0LDQwIDY5LjI5LDU2LjcyIDg2LjgyLDU2LjU4IDcyLjU2LDY2Ljc4IDc4LjExLDgzLjQyIDY0LDczIDQ5Ljg5LDgzLjQyIDU1LjQ0LDY2Ljc4IDQxLjE4LDU2LjU4IDU4LjcxLDU2LjcyIiBmaWxsPSIjNDQ0Ii8+PC9zdmc+
// ==/UserScript==
(function() {
'use strict';
// ==================== 性能配置 ====================
const PERFORMANCE = {
GUARDIAN_INTERVAL: 1500,
POLL_INTERVAL: 1500,
CACHE_TTL: 10000,
MUTATION_DEBOUNCE: 200,
};
// ==================== 配置定义 ====================
const CONFIG = {
STYLE_ID: 'darkmode-master-style',
PRELOAD_ID: 'darkmode-preload',
ANIMATION_ID: 'darkmode-animations',
FILTER_SVG_ID: 'eye-protect-filter-svg',
FILTER_STYLE_ID: 'eye-protect-filter-style',
MAX_CACHE_SIZE: 40,
EARLY_APPLY_TIMEOUT: 200
};
const THEMES = Object.freeze({
dark: { name: "🌙 夜间模式", invert: 1, hue: 180, bright: 1, contrast: 1, saturate: 1, bg: "#000" },
soft: { name: "🕯️ 柔和护眼", invert: 1, hue: 180, bright: 0.95, contrast: 0.9, saturate: 0.8, bg: "#121212" },
pure: { name: "⚪ 纯净黑白", invert: 1, hue: 180, bright: 1, contrast: 0.85, saturate: 0.5, bg: "#000" },
eyecare: { name: "👁️ 护眼暗色", invert: 1, hue: 180, bright: 0.92, contrast: 0.95, saturate: 0.9, bg: "#111" }
});
const DEFAULT_CONFIG = Object.freeze({
blacklist: [],
REFRESH_ON_SAVE: false,
THEME: "dark",
AVOID_DARK_SITE: true,
BRIGHTNESS_THRESHOLD: 70,
FORCE_RECOVERY: true,
SHOW_NOTIFICATION: true,
PRELOAD_ENABLED: true,
INSTANT_MODE: true,
globalEnable: false,
enableList: [],
forcedEnableList: [],
runDuringDay: true,
darkAuto: false,
customDark3: '90',
dark3Exclude: 'img, .img, video, [style*="background"][style*="url"], svg, .video-player, .player, [class*="player"], [class*="Player"], [id*="player"], [id*="Player"], .plyr, .jw-player, .video-js',
filterExcludeList: ['youku.com', 'v.youku.com', 'www.douyu.com', 'www.iqiyi.com', 'vip.iqiyi.com', 'mail.qq.com', 'live.kuaishou.com'],
siteSpecificModes: '{}',
tempDisableAllSiteModes: false,
autoDisableTempMode: '',
autoDisableAlsoEnableForceSpecial: false,
globalCycleModes: [],
globalGreenOpacity: 0.3,
globalWarmOpacity: 0.3,
currentMode: 'none',
panelWidth: 400,
panelHeight: 450,
panelLeft: '50%',
panelTop: '50%',
panelTransform: 'translate(-50%, -50%)',
forceSpecialModeEnabled: false,
forceSpecialList: [],
themecolorBgColor: '#C7EDCC',
themecolorLinkColor: '#2E7D32',
softwarmGlobalType: 'green',
softwarmGlobalGreenOpacity: 0.3,
softwarmGlobalWarmOpacity: 0.3,
performanceMode: true
});
const MODE_NAMES = {
none: "⚪ 无模式",
light: "☀️ 白天模式",
dark: "🌙 夜间模式",
soft: "🕯️ 柔和护眼",
eyecare: "👁️ 护眼暗色",
pure: "⚪ 纯净黑白",
filter: "🎨 滤镜模式",
softwarm: "🍃 柔暖双色",
themecolor: "🌈 护眼主题",
darkreader: "🌓 深色模式"
};
const VALID_MODES = ['none', 'light', 'dark', 'soft', 'eyecare', 'pure', 'filter', 'softwarm', 'themecolor', 'darkreader'];
const THEME_PRESETS = [
{ id: 'bean', name: '豆沙绿', bg: '#C7EDCC', link: '#2E7D32' },
{ id: 'cyan', name: '青草蓝', bg: '#DCEFF0', link: '#006064' },
{ id: 'parchment', name: '羊皮纸', bg: '#F4ECD8', link: '#8B4513' },
{ id: 'warm', name: '暖阳黄', bg: '#FAF9DE', link: '#E65100' },
{ id: 'sakura', name: '樱花粉', bg: '#FDE2E4', link: '#C2185B' }
];
const DARK_READER_OPTIONS = {
brightness: 100,
contrast: 90,
sepia: 0,
grayscale: 10,
darkSchemeBackgroundColor: '#1a1a2e',
darkSchemeTextColor: '#e0e0e0',
};
// ==================== 工具函数 ====================
const Util = (() => {
const cache = new Map();
let cleanupTimer = null;
class CacheItem {
constructor(value, ttl = PERFORMANCE.CACHE_TTL) {
this.value = value;
this.expires = Date.now() + ttl;
}
isValid() { return Date.now() < this.expires; }
}
function cleanupCache() {
const now = Date.now();
const keysToDelete = [];
for (const [key, item] of cache) if (now >= item.expires) keysToDelete.push(key);
keysToDelete.forEach(key => cache.delete(key));
if (cache.size > CONFIG.MAX_CACHE_SIZE) {
const keys = Array.from(cache.keys());
const halfSize = Math.floor(keys.length / 2);
for (let i = halfSize; i < keys.length; i++) cache.delete(keys[i]);
}
}
function startCleanup() { if (cleanupTimer) clearInterval(cleanupTimer); cleanupTimer = setInterval(cleanupCache, 30000); }
function stopCleanup() { if (cleanupTimer) { clearInterval(cleanupTimer); cleanupTimer = null; } }
function getBgBrightness(el) {
if (!el) return null;
try {
const style = window.getComputedStyle(el);
if (style.backgroundImage && style.backgroundImage !== 'none') return null;
const bg = style.backgroundColor;
if (!bg || bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') return null;
const rgb = bg.match(/\d+/g);
if (!rgb || rgb.length < 3) return null;
return rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722;
} catch { return null; }
}
function detectPageBrightness() {
if (document.readyState !== 'complete') return 255;
try {
const htmlBright = getBgBrightness(document.documentElement);
if (htmlBright !== null) return htmlBright;
if (!document.body) return 255;
const bodyBright = getBgBrightness(document.body);
if (bodyBright !== null) return bodyBright;
let count = 0;
for (let el of document.body.children) {
if (count++ > 5) break;
const bright = getBgBrightness(el);
if (bright !== null) return bright;
}
const text = window.getComputedStyle(document.body).color;
const rgb = text.match(/\d+/g);
if (rgb && rgb.length >= 3) {
return rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722;
}
return 255;
} catch { return 255; }
}
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
function throttleRAF(fn) {
let pending = false;
return function(...args) {
if (pending) return;
pending = true;
requestAnimationFrame(() => {
fn.apply(this, args);
pending = false;
});
};
}
return {
getValue(key) {
try {
let val = GM_getValue(key);
if (val !== undefined) return val;
return DEFAULT_CONFIG[key];
} catch { return DEFAULT_CONFIG[key]; }
},
setValue(key, value) { try { GM_setValue(key, value); cache.delete(key); } catch(e) {} },
deleteValue(key) { try { GM_deleteValue(key); cache.delete(key); } catch(e) {} },
addStyle(id, css) {
try {
let styleEl = document.getElementById(id);
if (styleEl) {
if (styleEl.textContent === css) return true;
styleEl.textContent = css;
return true;
}
if (!document.head) {
setTimeout(() => this.addStyle(id, css), 5);
return false;
}
const style = document.createElement('style');
style.id = id;
style.textContent = css;
document.head.appendChild(style);
return true;
} catch(e) { return false; }
},
removeElementById(id) { const el = document.getElementById(id); if (el && el.parentNode) el.parentNode.removeChild(el); },
isBlacklisted() {
const host = this.getSafeHost();
return this.getValue('blacklist').includes(host);
},
getSafeHost() { try { return location.host || 'unknown'; } catch { return 'unknown'; } },
showNotification: (() => {
let timeout = null, notificationElement = null;
const cleanup = () => {
if (notificationElement && notificationElement.parentNode) notificationElement.parentNode.removeChild(notificationElement);
notificationElement = null;
if (timeout) { clearTimeout(timeout); timeout = null; }
};
const fn = (message, type = 'info', duration = 3000) => {
if (!Util.getValue('SHOW_NOTIFICATION')) return;
try {
cleanup();
const colors = {
info: { bg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', border: '#ff6b6b' },
success: { bg: 'linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%)', border: '#00b09b' },
warning: { bg: 'linear-gradient(135deg, #fad961 0%, #f76b1c 100%)', border: '#f39c12' },
error: { bg: 'linear-gradient(135deg, #f43b47 0%, #453a94 100%)', border: '#c0392b' }
};
notificationElement = document.createElement('div');
notificationElement.style.cssText = `
position: fixed; top: 20px; right: 20px;
background: ${colors[type].bg}; color: white; padding: 12px 20px;
border-radius: 8px; z-index: 9999999; font-size: 14px;
font-family: 'Microsoft YaHei', system-ui, sans-serif;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
animation: slideIn 0.3s ease-out; max-width: 300px;
border-left: 4px solid ${colors[type].border};
filter: none !important;
`;
notificationElement.innerHTML = `
`;
document.body.appendChild(notificationElement);
timeout = setTimeout(() => {
if (notificationElement) {
notificationElement.style.animation = 'fadeOut 0.3s ease-out forwards';
setTimeout(cleanup, 300);
}
}, duration);
} catch(e) {}
};
fn.cancel = cleanup;
return fn;
})(),
setCache(key, value, ttl = PERFORMANCE.CACHE_TTL) { cache.set(key, new CacheItem(value, ttl)); },
getCache(key) { const item = cache.get(key); if (item && item.isValid()) return item.value; if (item) cache.delete(key); return null; },
clearCache() { cache.clear(); },
startCleanup,
stopCleanup,
detectPageBrightness,
debounce,
throttleRAF,
dispose() {
stopCleanup();
this.clearCache();
this.removeElementById(CONFIG.STYLE_ID);
this.removeElementById(CONFIG.PRELOAD_ID);
this.removeElementById(CONFIG.ANIMATION_ID);
this.removeElementById(CONFIG.FILTER_STYLE_ID);
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = warmOverlay = null;
stopThemeMode();
stopSoftwarmMode();
Guardian.stop();
}
};
})();
Util.addStyle(CONFIG.ANIMATION_ID, `
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes fadeOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
`);
// ==================== 跨域 CSS 加载 ====================
function setupFetchMethod() {
if (typeof DarkReader === 'undefined' || !DarkReader.setFetchMethod) return;
try {
DarkReader.setFetchMethod((url) => {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
onload: (resp) => resolve(new Response(resp.responseText, {
status: resp.status || 200,
headers: { 'Content-Type': 'text/css' },
})),
onerror: () => resolve(new Response('', { status: 200, headers: { 'Content-Type': 'text/css' } })),
ontimeout: () => resolve(new Response('', { status: 200, headers: { 'Content-Type': 'text/css' } })),
});
});
});
} catch (e) {}
}
if (typeof DarkReader !== 'undefined') {
setupFetchMethod();
} else {
const checkDR = setInterval(() => {
if (typeof DarkReader !== 'undefined') {
clearInterval(checkDR);
setupFetchMethod();
}
}, 50);
}
// ==================== 全局变量 ====================
let greenOverlay = null;
let warmOverlay = null;
let filterStyle = null;
let customStyleEl = null;
let customModeActive = false;
let customModeObserver = null;
let softwarmActive = false;
let menuCommands = [];
let currentSettingsHost = null;
let currentSettingsShadow = null;
let cachedCSS = new Map();
// ==================== 模式具体函数 ====================
function getCurrentThemeColors() {
const host = location.host;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (entry && typeof entry === 'object' && entry.mode === 'themecolor' && entry.colors) {
return { bg: entry.colors.bg, link: entry.colors.link };
}
} catch(e) {}
return {
bg: Util.getValue('themecolorBgColor') || '#C7EDCC',
link: Util.getValue('themecolorLinkColor') || '#2E7D32'
};
}
function updateThemeCSSVars(bgColor, linkColor) {
const css = `
:root {
--theme-bg: ${bgColor} !important;
--theme-link: ${linkColor} !important;
}
[data-theme-bg="true"] {
background-color: var(--theme-bg) !important;
background-image: none !important;
}
[data-theme-link="true"] {
color: var(--theme-link) !important;
text-decoration: none !important;
}
`;
if (!customStyleEl) {
customStyleEl = document.createElement('style');
customStyleEl.id = 'theme-mode-core-style';
document.head.appendChild(customStyleEl);
} else if (!customStyleEl.parentNode) {
document.head.appendChild(customStyleEl);
}
customStyleEl.textContent = css;
}
function scanAndTagForThemeMode() {
if (!customModeActive) return;
// 增量扫描由 MutationObserver 处理
}
function startThemeModeObserver() {
if (customModeObserver) customModeObserver.disconnect();
customModeObserver = new MutationObserver(Util.throttleRAF(() => {
if (customModeActive) scanAndTagForThemeMode();
}));
if (document.body) {
customModeObserver.observe(document.body, { childList: true, subtree: false });
} else {
const waitForBody = setInterval(() => {
if (document.body) {
clearInterval(waitForBody);
customModeObserver.observe(document.body, { childList: true, subtree: false });
}
}, 50);
}
if (document.body) {
const els = document.body.querySelectorAll('*:not([data-theme-scanned])');
let count = 0;
for (const el of els) {
if (count++ > 100) break;
el.setAttribute('data-theme-scanned', 'true');
}
}
}
function stopThemeMode() {
customModeActive = false;
if (customModeObserver) {
customModeObserver.disconnect();
customModeObserver = null;
}
if (customStyleEl && customStyleEl.parentNode) customStyleEl.remove();
customStyleEl = null;
document.querySelectorAll('[data-theme-bg], [data-theme-link], [data-theme-scanned]').forEach(el => {
el.removeAttribute('data-theme-bg');
el.removeAttribute('data-theme-link');
el.removeAttribute('data-theme-scanned');
});
}
function applyThemeMode() {
const { bg, link } = getCurrentThemeColors();
updateThemeCSSVars(bg, link);
customModeActive = true;
if (!customModeObserver) {
startThemeModeObserver();
}
}
function updateSoftwarmOverlay() {
if (!softwarmActive) return;
const type = getSoftwarmTypeForSite();
const opacity = (type === 'green') ? getSoftwarmGreenOpacityForSite() : getSoftwarmWarmOpacityForSite();
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = null;
warmOverlay = null;
if (opacity > 0) {
const target = document.documentElement;
if (!target) return;
if (type === 'green') {
greenOverlay = document.createElement('div');
greenOverlay.id = 'eye-protect-green-overlay';
greenOverlay.style.cssText = `position:fixed;top:0;left:0;right:0;bottom:0;z-index:2147483500;pointer-events:none;mix-blend-mode:multiply;background-color:#C7EDCC;opacity:${opacity};`;
target.appendChild(greenOverlay);
} else {
warmOverlay = document.createElement('div');
warmOverlay.id = 'eye-protect-warm-overlay';
warmOverlay.style.cssText = `position:fixed;top:0;left:0;right:0;bottom:0;z-index:2147483500;pointer-events:none;mix-blend-mode:multiply;background-color:#CFB988;opacity:${opacity};`;
target.appendChild(warmOverlay);
}
}
}
function applySoftwarmMode() {
softwarmActive = true;
updateSoftwarmOverlay();
}
function getSoftwarmTypeForSite() {
const host = location.host;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (entry && typeof entry === 'object' && entry.mode === 'softwarm' && entry.type) {
return entry.type;
}
} catch(e) {}
return Util.getValue('softwarmGlobalType') || 'green';
}
function getSoftwarmGreenOpacityForSite() {
const host = location.host;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (entry && typeof entry === 'object' && entry.mode === 'softwarm' && entry.greenOpacity !== undefined) {
return entry.greenOpacity;
}
} catch(e) {}
return Util.getValue('softwarmGlobalGreenOpacity');
}
function getSoftwarmWarmOpacityForSite() {
const host = location.host;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (entry && typeof entry === 'object' && entry.mode === 'softwarm' && entry.warmOpacity !== undefined) {
return entry.warmOpacity;
}
} catch(e) {}
return Util.getValue('softwarmGlobalWarmOpacity');
}
function stopSoftwarmMode() {
softwarmActive = false;
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = null;
warmOverlay = null;
}
function removeFilterMode() {
if (filterStyle && filterStyle.parentNode) filterStyle.remove();
Util.removeElementById(CONFIG.FILTER_SVG_ID);
filterStyle = null;
}
function applyFilterMode() {
let excludeList = Util.getValue('filterExcludeList');
if (excludeList.includes(location.host)) return false;
Util.removeElementById(CONFIG.FILTER_STYLE_ID);
if (!document.getElementById(CONFIG.FILTER_SVG_ID)) {
let svgDom = ' ';
let div = document.createElementNS('http://www.w3.org/2000/svg', 'div');
div.innerHTML = svgDom;
let frag = document.createDocumentFragment();
while (div.firstChild) frag.appendChild(div.firstChild);
document.head.appendChild(frag);
}
let isFirefox = /Firefox/i.test(navigator.userAgent);
let filter = isFirefox ?
`filter: url('data:image/svg+xml;utf8, #eye-protect-filter') !important;` :
'-webkit-filter: url(#eye-protect-filter) !important; filter: url(#eye-protect-filter) !important;';
let reverseFilter = isFirefox ?
`filter: url('data:image/svg+xml;utf8, #eye-protect-filter-reverse') !important;` :
'-webkit-filter: url(#eye-protect-filter-reverse) !important; filter: url(#eye-protect-filter-reverse) !important;';
let css = `
@media screen {
html { ${filter} scrollbar-color: #454a4d #202324; }
img, video, iframe, canvas, :not(object):not(body) > embed, object, svg image, [style*="background:url"], [style*="background-image:url"], [style*="background: url"], [style*="background-image: url"], [background], twitterwidget, .no-dark-mode, .sr-reader, .sr-backdrop { ${reverseFilter} }
[style*="background:url"] *, [style*="background-image:url"] *, [style*="background: url"] *, [style*="background-image: url"] *, input, [background] *, img[src^="https://s0.wp.com/latex.php"], twitterwidget .NaturalImage-image { -webkit-filter: none !important; filter: none !important; }
html { text-shadow: 0 0 0 !important; }
.no-filter, :-webkit-full-screen, :-webkit-full-screen *, :-moz-full-screen, :-moz-full-screen *, :fullscreen, :fullscreen * { -webkit-filter: none !important; filter: none !important; }
::-webkit-scrollbar { background-color: #202324; color: #aba499; }
::-webkit-scrollbar-thumb { background-color: #454a4d; }
::-webkit-scrollbar-thumb:hover { background-color: #575e62; }
::-webkit-scrollbar-thumb:active { background-color: #484e51; }
::-webkit-scrollbar-corner { background-color: #181a1b; }
html { background: #fff !important; }
}
`;
if (filterStyle && filterStyle.textContent === css) return true;
filterStyle = document.createElement('style');
filterStyle.id = CONFIG.FILTER_STYLE_ID;
filterStyle.textContent = css;
document.head.appendChild(filterStyle);
return true;
}
function removeOverlays() {
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = null;
warmOverlay = null;
}
// ==================== DarkReader 引擎辅助(立即执行,无延迟) ====================
function enableDarkReader(options) {
if (typeof DarkReader === 'undefined') return;
try {
DarkReader.enable(options);
} catch(e) {}
}
function disableDarkReader() {
if (typeof DarkReader === 'undefined') return;
try {
DarkReader.disable();
} catch(e) {}
}
// 强制重置 DarkReader(先禁用再启用)
function resetDarkReader(options) {
if (typeof DarkReader === 'undefined') return;
try {
DarkReader.disable();
// 微任务确保 disable 完成后再 enable
Promise.resolve().then(() => {
DarkReader.enable(options);
});
} catch(e) {}
}
function getDarkReaderEnabledForCurrentSite() {
const host = location.hostname || 'unknown';
const key = 'darkMode_' + host.replace(/[^a-zA-Z0-9.-]/g, '');
let enabled = GM_getValue(key);
if (enabled === undefined) enabled = true;
return enabled;
}
function setDarkReaderEnabledForCurrentSite(value) {
const host = location.hostname || 'unknown';
const key = 'darkMode_' + host.replace(/[^a-zA-Z0-9.-]/g, '');
GM_setValue(key, value);
}
function toggleDarkReaderForCurrentSite() {
const current = getDarkReaderEnabledForCurrentSite();
setDarkReaderEnabledForCurrentSite(!current);
return !current;
}
// ==================== 样式生成 ====================
function generateFullStyleCSS(mode) {
if (mode === 'light') return '';
if (mode === 'dark') {
let style_30 = Util.getValue('customDark3') || '90';
let dark3Exclude = Util.getValue('dark3Exclude');
let isFirefox = /Firefox/i.test(navigator.userAgent);
let filterValue = `invert(${style_30}%)`;
let baseCSS = isFirefox ?
`html { filter: ${filterValue} !important; background-image: url(); text-shadow: 0 0 0 !important; }` :
`html { filter: ${filterValue} !important; text-shadow: 0 0 0 !important; }`;
let excludeCSS = `${dark3Exclude} { filter: invert(1) !important; }`;
let scrollbarCSS = `
::-webkit-scrollbar { height: 12px !important; width: 12px !important; }
::-webkit-scrollbar-thumb { border-radius: 0; border-color: transparent; border-style: dashed; background-color: #3f4752 !important; background-clip: padding-box; transition: background-color .32s ease-in-out; }
::-webkit-scrollbar-corner { background: #202020 !important; }
::-webkit-scrollbar-track { background-color: #22272e !important; }
::-webkit-scrollbar-thumb:hover { background: #3f4752 !important; }
`;
return baseCSS + excludeCSS + scrollbarCSS;
}
if (mode === 'filter') return '';
if (THEMES[mode]) {
const theme = THEMES[mode];
let inv = theme.invert;
let hue = theme.hue;
let bright = theme.bright;
let contrast = theme.contrast;
let saturate = theme.saturate;
let bg = theme.bg;
const filter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`;
const mediaFilter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`;
let css = `html{filter:${filter}!important;background:${bg};will-change:filter,background;}`;
css += `img, video, iframe, canvas, [style*="background-image"]{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `img[src*=".svg"], svg{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `video::-webkit-media-controls-panel, video::-webkit-media-controls{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `picture, source{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
return css;
}
return '';
}
function generateCodePageCSS() {
let style_30 = Util.getValue('customDark3') || '90';
let isFirefox = /Firefox/i.test(navigator.userAgent);
let baseCSS = isFirefox ?
`html { filter: invert(${style_30}%) !important; background-image: url(); background-color: #0d1117 !important; text-shadow: 0 0 0 !important; }` :
`html { filter: invert(${style_30}%) !important; background-color: #0d1117 !important; text-shadow: 0 0 0 !important; }`;
let excludeCSS = `img, svg, canvas, iframe, .icon, .logo, .avatar, [src*=".svg"], [src*=".png"], [src*=".jpg"], [src*=".jpeg"], [src*=".gif"] { filter: invert(1) !important; }`;
let codeCSS = `
body, html { background-color: #0d1117 !important; color: #c9d1d9 !important; }
pre, code, .hljs, .CodeMirror, .code-viewer, .code-area, .editor-container,
.container, .main-container, .wrapper, .content, .code-container, .script-container,
.page-content, .main-content, div[style*="font-family:monospace"],
div[style*="font-family: monospace"] {
background-color: #0d1117 !important;
color: #c9d1d9 !important;
border-color: #30363d !important;
}
.header, .navbar, .nav, .top-bar, .breadcrumb, .nav-bar {
background-color: #161b22 !important;
border-color: #30363d !important;
color: #c9d1d9 !important;
}
.line-numbers, .line-number, .linenumber, .ln, .lnum {
background-color: #161b22 !important;
color: #8b949e !important;
border-right-color: #30363d !important;
}
.keyword, .function, .class-name, .operator, .punctuation, .string,
.comment, .variable, .constant, .built_in, .attr-name, .selector, .property {
color: #d2a8ff !important;
}
.string, [class*="key"] { color: #79c0ff !important; }
.number { color: #56d364 !important; }
.boolean, .null { color: #ff7b72 !important; }
::-webkit-scrollbar { background-color: #0d1117 !important; }
::-webkit-scrollbar-thumb { background-color: #30363d !important; border-radius: 6px; }
a { color: #58a6ff !important; }
button, .btn, .button, input[type="button"], input[type="submit"] {
background-color: #238636 !important;
color: #ffffff !important;
border-color: #2ea043 !important;
}
::selection { background-color: #1c6b48 !important; color: #ffffff !important; }
`;
return baseCSS + excludeCSS + codeCSS;
}
function isJsonOrCodePage() {
const url = location.href;
if (url.endsWith('.js') || url.includes('.js?') || url.endsWith('.user.js') ||
url.endsWith('.json') || url.includes('.json?') || url.includes('.json#')) {
return true;
}
if (url.includes('raw.githubusercontent.com') || url.includes('gh-proxy.org') ||
url.includes('scriptcat.org/code/') || url.includes('updategf.qytechs.cn/scripts/')) {
return true;
}
const contentType = document.contentType || '';
if (contentType.includes('application/json') || contentType.includes('text/json')) {
return true;
}
if (document.body) {
const text = document.body.textContent.trim();
if ((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']'))) {
try { JSON.parse(text); return true; } catch(e) {}
}
}
return false;
}
function getCSSForMode(mode) {
if (mode === 'light') return '';
if (mode === 'darkreader') return null;
let cacheKey = mode;
if (mode === 'dark') cacheKey = `dark_${Util.getValue('customDark3')}`;
const cached = Util.getCache(cacheKey);
if (cached !== null) return cached;
let css = '';
const isCodePage = isJsonOrCodePage();
if (isCodePage) {
css = generateCodePageCSS();
} else if (mode === 'dark') {
css = generateFullStyleCSS('dark');
} else if (mode === 'filter') {
css = null;
} else if (mode === 'softwarm' || mode === 'themecolor') {
css = null;
} else if (THEMES[mode]) {
css = generateFullStyleCSS(mode);
}
if (css !== null) {
Util.setCache(cacheKey, css, PERFORMANCE.CACHE_TTL);
}
return css;
}
// ==================== 模式管理 ====================
function getCurrentGlobalMode() {
let mode = Util.getValue('currentMode');
if (!mode || !MODE_NAMES[mode]) mode = 'none';
return mode;
}
function getSiteSpecificMode(domain, ignoreTempDisable) {
let host = domain || location.host;
if (!ignoreTempDisable && Util.getValue('tempDisableAllSiteModes')) return null;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (!entry) return null;
if (typeof entry === 'string') {
if (VALID_MODES.includes(entry)) return entry;
delete siteModes[host];
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
return null;
}
if (typeof entry === 'object' && entry.mode && VALID_MODES.includes(entry.mode)) return entry;
delete siteModes[host];
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
return null;
} catch(e) { return null; }
}
function setSiteSpecificMode(domain, mode, extra) {
let host = domain;
if (!host) return;
if (mode && mode !== 'default' && !VALID_MODES.includes(mode)) {
console.warn('无效的专用模式:', mode);
Util.showNotification(`模式 "${mode}" 无效,已忽略`, 'warning', 2000);
return;
}
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
if (mode === 'default' || mode === null) {
delete siteModes[host];
Util.showNotification(`已清除 ${host} 的专用模式`, 'success', 1500);
} else if (mode === 'softwarm') {
let existing = siteModes[host] || {};
siteModes[host] = {
mode: 'softwarm',
type: extra && extra.type ? extra.type : (existing.type || 'green'),
greenOpacity: extra && extra.greenOpacity !== undefined ? extra.greenOpacity : (existing.greenOpacity !== undefined ? existing.greenOpacity : 0.3),
warmOpacity: extra && extra.warmOpacity !== undefined ? extra.warmOpacity : (existing.warmOpacity !== undefined ? existing.warmOpacity : 0.3)
};
Util.showNotification(`已为 ${host} 设置专用模式: 🍃 柔暖双色`, 'success', 1500);
} else if (mode === 'themecolor') {
let colors = extra && extra.colors ? extra.colors : { bg: '#C7EDCC', link: '#2E7D32' };
siteModes[host] = { mode: 'themecolor', colors: colors };
Util.showNotification(`已为 ${host} 设置专用模式: 🌈 护眼主题`, 'success', 1500);
} else {
siteModes[host] = mode;
Util.showNotification(`已为 ${host} 设置专用模式: ${MODE_NAMES[mode] || mode}`, 'success', 1500);
}
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
} catch(e) { console.error('设置网站专用模式出错:', e); }
}
function isBasicEnabled() {
const host = location.host;
const globalEnable = Util.getValue('globalEnable');
const enableList = Util.getValue('enableList');
return globalEnable || enableList.includes(host);
}
function getForcedEnableListObjects() {
let raw = Util.getValue('forcedEnableList') || [];
return raw.map(item => {
if (typeof item === 'string') return { domain: item, addedTime: Date.now() };
return item;
});
}
function shouldApplyGlobalMode() {
const host = location.host;
let globalEnable = Util.getValue('globalEnable');
let enableList = Util.getValue('enableList');
let forcedList = getForcedEnableListObjects().map(item => item.domain);
let basicEnabled = forcedList.includes(host) ? (globalEnable || enableList.includes(host)) : (globalEnable || enableList.includes(host));
if (!basicEnabled) return false;
const currentGlobalMode = getCurrentGlobalMode();
const isUserActiveMode = (currentGlobalMode !== 'none' && currentGlobalMode !== 'light');
if (isUserActiveMode) {
return true;
}
const inWhitelist = enableList.includes(host);
const avoidDarkSite = Util.getValue('AVOID_DARK_SITE');
if (avoidDarkSite && !inWhitelist && !forcedList.includes(host)) {
try {
const brightness = Util.detectPageBrightness();
const threshold = Util.getValue('BRIGHTNESS_THRESHOLD');
if (brightness < threshold) return false;
} catch(e) {}
}
return true;
}
function isForceSpecialSite() {
if (!Util.getValue('forceSpecialModeEnabled')) return false;
const host = location.host;
const list = Util.getValue('forceSpecialList') || [];
for (const item of list) {
let domain = typeof item === 'string' ? item : item.domain;
if (domain === host) return true;
}
return false;
}
function getForceSpecialModeForSite() {
if (!isForceSpecialSite()) return null;
const host = location.host;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (!entry) return null;
if (typeof entry === 'string') {
if (VALID_MODES.includes(entry)) return entry;
return null;
}
if (typeof entry === 'object' && entry.mode && VALID_MODES.includes(entry.mode)) return entry.mode;
return null;
} catch(e) { return null; }
}
function getEffectiveMode() {
const host = location.host;
if (Util.isBlacklisted()) return 'light';
if (!isBasicEnabled()) return 'light';
const forceMode = getForceSpecialModeForSite();
if (forceMode !== null) return forceMode;
let siteSpecific = getSiteSpecificMode();
if (siteSpecific) {
if (typeof siteSpecific === 'string') return siteSpecific;
if (typeof siteSpecific === 'object' && siteSpecific.mode) return siteSpecific.mode;
}
if (shouldApplyGlobalMode()) {
let globalMode = getCurrentGlobalMode();
if (globalMode === 'none') return 'light';
if (globalMode === 'filter') {
let excludeList = Util.getValue('filterExcludeList');
if (excludeList.includes(host)) return 'light';
}
return globalMode;
}
return 'light';
}
// ==================== 核心应用(强制刷新版) ====================
function applyModeByMode(mode, force) {
// 如果不是 DarkReader 模式,确保禁用 DarkReader
if (typeof DarkReader !== 'undefined' && mode !== 'darkreader') {
disableDarkReader();
}
removeOverlays();
removeFilterMode();
stopThemeMode();
stopSoftwarmMode();
Util.removeElementById(CONFIG.STYLE_ID);
Util.removeElementById(CONFIG.PRELOAD_ID);
if (mode === 'softwarm') {
applySoftwarmMode();
} else if (mode === 'themecolor') {
applyThemeMode();
} else if (mode === 'filter') {
let excludeList = Util.getValue('filterExcludeList');
if (!excludeList.includes(location.host)) {
applyFilterMode();
}
} else if (mode === 'darkreader') {
if (typeof DarkReader !== 'undefined') {
const enabled = getDarkReaderEnabledForCurrentSite();
if (enabled) {
if (force) {
// 强制重置
resetDarkReader(DARK_READER_OPTIONS);
} else {
enableDarkReader(DARK_READER_OPTIONS);
}
} else {
disableDarkReader();
}
} else {
console.warn('DarkReader 库未加载');
}
} else {
const css = getCSSForMode(mode);
if (css === undefined || css === null || css === '') {
Util.removeElementById(CONFIG.STYLE_ID);
} else {
Util.addStyle(CONFIG.STYLE_ID, css);
}
}
}
// ==================== 快照 ====================
function saveStyleSnapshot() {
const mode = getEffectiveMode();
if (mode === 'light') {
GM_setValue('bfcache_snapshot', { mode: 'light' });
return;
}
const snapshot = { mode: mode, timestamp: Date.now() };
if (mode === 'softwarm') {
snapshot.softwarmType = getSoftwarmTypeForSite();
snapshot.softwarmGreenOpacity = getSoftwarmGreenOpacityForSite();
snapshot.softwarmWarmOpacity = getSoftwarmWarmOpacityForSite();
} else if (mode === 'filter') {
snapshot.css = null;
} else if (mode === 'themecolor') {
const { bg, link } = getCurrentThemeColors();
snapshot.themeColors = { bg, link };
} else if (mode === 'darkreader') {
snapshot.darkreaderEnabled = getDarkReaderEnabledForCurrentSite();
} else {
const css = getCSSForMode(mode);
snapshot.css = css;
}
GM_setValue('bfcache_snapshot', snapshot);
}
// ==================== 样式管理器 ====================
const StyleManager = {
apply: function(force) {
if (!document.head) {
setTimeout(() => this.apply(force), 50);
return;
}
const mode = getEffectiveMode();
if (mode === 'light') {
this.cleanAll();
} else {
applyModeByMode(mode, force || false);
}
saveStyleSnapshot();
},
cleanAll: function() {
Util.removeElementById(CONFIG.STYLE_ID);
Util.removeElementById(CONFIG.PRELOAD_ID);
removeOverlays();
removeFilterMode();
stopThemeMode();
stopSoftwarmMode();
disableDarkReader();
GM_setValue('bfcache_snapshot', { mode: 'light' });
}
};
// ==================== 守护引擎(优化版) ====================
const Guardian = (() => {
let timer = null;
let isActive = false;
let isDisposed = false;
let headObserver = null;
let ensureScheduled = false;
function ensureStyles() {
if (isDisposed) return;
const mode = getEffectiveMode();
if (mode === 'light') {
if (document.getElementById(CONFIG.STYLE_ID) ||
document.getElementById('theme-mode-core-style') ||
document.getElementById(CONFIG.FILTER_STYLE_ID) ||
greenOverlay || warmOverlay) {
StyleManager.cleanAll();
}
return;
}
if (mode === 'softwarm') {
const type = getSoftwarmTypeForSite();
const opacity = (type === 'green') ? getSoftwarmGreenOpacityForSite() : getSoftwarmWarmOpacityForSite();
if (opacity > 0) {
const target = document.documentElement;
if (!target) return;
if (type === 'green') {
if (!greenOverlay || !target.contains(greenOverlay)) {
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
greenOverlay = document.createElement('div');
greenOverlay.id = 'eye-protect-green-overlay';
greenOverlay.style.cssText = `position:fixed;top:0;left:0;right:0;bottom:0;z-index:2147483500;pointer-events:none;mix-blend-mode:multiply;background-color:#C7EDCC;opacity:${opacity};`;
target.appendChild(greenOverlay);
}
} else {
if (!warmOverlay || !target.contains(warmOverlay)) {
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
warmOverlay = document.createElement('div');
warmOverlay.id = 'eye-protect-warm-overlay';
warmOverlay.style.cssText = `position:fixed;top:0;left:0;right:0;bottom:0;z-index:2147483500;pointer-events:none;mix-blend-mode:multiply;background-color:#CFB988;opacity:${opacity};`;
target.appendChild(warmOverlay);
}
}
} else {
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = null;
warmOverlay = null;
}
Util.removeElementById(CONFIG.STYLE_ID);
Util.removeElementById(CONFIG.FILTER_STYLE_ID);
if (customModeActive) stopThemeMode();
disableDarkReader();
return;
}
if (mode === 'filter') {
let excludeList = Util.getValue('filterExcludeList');
if (!excludeList.includes(location.host)) {
if (!filterStyle || !document.head.contains(filterStyle)) {
applyFilterMode();
}
}
Util.removeElementById(CONFIG.STYLE_ID);
if (customModeActive) stopThemeMode();
if (softwarmActive) stopSoftwarmMode();
removeOverlays();
disableDarkReader();
return;
}
if (mode === 'themecolor') {
if (!customModeActive || !customStyleEl || !document.head.contains(customStyleEl)) {
applyThemeMode();
}
Util.removeElementById(CONFIG.STYLE_ID);
Util.removeElementById(CONFIG.FILTER_STYLE_ID);
if (softwarmActive) stopSoftwarmMode();
removeOverlays();
disableDarkReader();
return;
}
if (mode === 'darkreader') {
if (typeof DarkReader !== 'undefined') {
const enabled = getDarkReaderEnabledForCurrentSite();
if (enabled) {
enableDarkReader(DARK_READER_OPTIONS);
} else {
disableDarkReader();
}
}
Util.removeElementById(CONFIG.STYLE_ID);
Util.removeElementById(CONFIG.FILTER_STYLE_ID);
if (customModeActive) stopThemeMode();
if (softwarmActive) stopSoftwarmMode();
removeOverlays();
return;
}
const css = getCSSForMode(mode);
if (css && css !== '') {
let styleEl = document.getElementById(CONFIG.STYLE_ID);
if (styleEl) {
if (styleEl.textContent !== css) {
styleEl.textContent = css;
}
} else {
Util.addStyle(CONFIG.STYLE_ID, css);
}
} else {
Util.removeElementById(CONFIG.STYLE_ID);
}
if (customModeActive) stopThemeMode();
if (softwarmActive) stopSoftwarmMode();
removeOverlays();
if (filterStyle) removeFilterMode();
disableDarkReader();
}
function scheduleEnsure() {
if (ensureScheduled) return;
ensureScheduled = true;
requestAnimationFrame(() => {
ensureScheduled = false;
ensureStyles();
});
}
function startHeadObserver() {
if (headObserver) headObserver.disconnect();
headObserver = new MutationObserver(Util.debounce((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
const removed = Array.from(mutation.removedNodes);
for (const node of removed) {
if (node.nodeType === 1 && node.id) {
const ids = [CONFIG.STYLE_ID, CONFIG.PRELOAD_ID, CONFIG.FILTER_STYLE_ID, 'theme-mode-core-style', 'eye-protect-green-overlay', 'eye-protect-warm-overlay'];
if (ids.includes(node.id)) {
scheduleEnsure();
break;
}
}
}
}
}
}, PERFORMANCE.MUTATION_DEBOUNCE));
if (document.head) {
headObserver.observe(document.head, { childList: true, subtree: false });
} else {
const waitHead = setInterval(() => {
if (document.head) {
clearInterval(waitHead);
headObserver.observe(document.head, { childList: true, subtree: false });
}
}, 50);
}
}
function start() {
if (isActive || isDisposed) return;
isActive = true;
isDisposed = false;
scheduleEnsure();
if (timer) clearInterval(timer);
const interval = Util.getValue('performanceMode') ? PERFORMANCE.GUARDIAN_INTERVAL : 1000;
timer = setInterval(() => {
if (isDisposed) return;
scheduleEnsure();
}, interval);
startHeadObserver();
let frameCount = 0;
function frameCheck() {
if (isDisposed) return;
frameCount++;
if (frameCount % 3 === 0) scheduleEnsure();
requestAnimationFrame(frameCheck);
}
requestAnimationFrame(frameCheck);
}
function stop() {
isActive = false;
isDisposed = true;
if (timer) {
clearInterval(timer);
timer = null;
}
if (headObserver) {
headObserver.disconnect();
headObserver = null;
}
ensureScheduled = false;
}
function restart() {
stop();
isDisposed = false;
start();
}
return { start, stop, restart, ensureStyles: scheduleEnsure };
})();
// ==================== 被动恢复事件(优化) ====================
function bindPassiveRecovery() {
const forceRecover = Util.debounce(() => {
if (Util.isBlacklisted() || !Util.getValue('FORCE_RECOVERY')) return;
StyleManager.apply(true);
}, 100);
const immediateRecover = () => {
if (Util.isBlacklisted() || !Util.getValue('FORCE_RECOVERY')) return;
StyleManager.apply(true);
};
window.addEventListener('pageshow', function(event) {
immediateRecover();
if (event.persisted) {
[30, 100, 300, 600].forEach(delay => setTimeout(forceRecover, delay));
} else {
[30, 100, 300].forEach(delay => setTimeout(forceRecover, delay));
}
});
window.addEventListener('popstate', function() {
immediateRecover();
[30, 100, 300].forEach(delay => setTimeout(forceRecover, delay));
});
window.addEventListener('hashchange', function() {
immediateRecover();
[30, 100, 300].forEach(delay => setTimeout(forceRecover, delay));
});
document.addEventListener('visibilitychange', function() {
if (!document.hidden) {
immediateRecover();
[30, 100, 300].forEach(delay => setTimeout(forceRecover, delay));
}
});
window.addEventListener('focus', function() {
immediateRecover();
[30, 100, 300].forEach(delay => setTimeout(forceRecover, delay));
});
window.addEventListener('load', function() {
immediateRecover();
[30, 100, 300].forEach(delay => setTimeout(forceRecover, delay));
});
let pollTimer = null;
let lastPollApply = 0;
const POLL_INTERVAL = PERFORMANCE.POLL_INTERVAL;
function pollRecover() {
if (document.hidden) return;
if (Util.isBlacklisted() || !Util.getValue('FORCE_RECOVERY')) return;
if (!Util.getValue('performanceMode')) {
const now = Date.now();
if (now - lastPollApply >= POLL_INTERVAL) {
lastPollApply = now;
StyleManager.apply(true);
}
}
}
function startPolling() {
if (pollTimer) clearInterval(pollTimer);
if (Util.getValue('performanceMode')) return;
pollTimer = setInterval(pollRecover, POLL_INTERVAL);
}
function stopPolling() {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
}
startPolling();
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
} else {
if (!pollTimer && !Util.getValue('performanceMode')) {
startPolling();
}
immediateRecover();
}
});
window.addEventListener('beforeunload', function() {
stopPolling();
});
}
// ==================== 导航历史补丁 ====================
function patchHistoryAPI() {
try {
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
const applyLater = Util.debounce(() => StyleManager.apply(true), 50);
history.pushState = function() {
originalPushState.apply(this, arguments);
setTimeout(applyLater, 30);
};
history.replaceState = function() {
originalReplaceState.apply(this, arguments);
setTimeout(applyLater, 30);
};
window.addEventListener('pagehide', () => { saveStyleSnapshot(); });
window.addEventListener('beforeunload', () => {
if (history.pushState !== originalPushState) history.pushState = originalPushState;
if (history.replaceState !== originalReplaceState) history.replaceState = originalReplaceState;
}, { once: true });
} catch(e) {}
}
// ==================== 预加载管理器 ====================
const PreloadManager = (() => {
let preloadStyle = null;
let isPreloaded = false;
function generatePreloadCSS() {
const mode = getEffectiveMode();
if (mode === 'light' || mode === 'none' || mode === 'softwarm' || mode === 'themecolor' || mode === 'filter' || mode === 'darkreader') return null;
let css = getCSSForMode(mode);
if (css && css !== '') return css;
return null;
}
function injectImmediately() {
if (Util.isBlacklisted()) return false;
if (isPreloaded) return true;
const criticalCSS = generatePreloadCSS();
if (!criticalCSS) return false;
if (document.head) {
Util.removeElementById(CONFIG.PRELOAD_ID);
preloadStyle = document.createElement('style');
preloadStyle.id = CONFIG.PRELOAD_ID;
preloadStyle.textContent = criticalCSS;
preloadStyle.setAttribute('data-preload', 'true');
document.head.appendChild(preloadStyle);
isPreloaded = true;
return true;
}
return false;
}
function preload() {
if (isPreloaded) return true;
return injectImmediately();
}
function upgradeToFull() {
if (!isPreloaded) return false;
try {
const mode = getEffectiveMode();
if (mode === 'light' || mode === 'none') {
cancel();
return false;
}
let fullCSS = null;
if (mode === 'softwarm') {
applySoftwarmMode();
cancel();
return true;
} else if (mode === 'themecolor') {
applyThemeMode();
cancel();
return true;
} else if (mode === 'filter') {
applyFilterMode();
cancel();
return true;
} else if (mode === 'darkreader') {
cancel();
return false;
} else {
fullCSS = getCSSForMode(mode);
}
if (fullCSS && fullCSS !== '') {
const fullStyle = document.createElement('style');
fullStyle.id = CONFIG.STYLE_ID;
fullStyle.textContent = fullCSS;
if (preloadStyle && preloadStyle.parentNode) {
preloadStyle.parentNode.replaceChild(fullStyle, preloadStyle);
} else {
Util.removeElementById(CONFIG.STYLE_ID);
if (document.head) document.head.appendChild(fullStyle);
}
}
preloadStyle = null;
isPreloaded = false;
return true;
} catch (e) {
return false;
}
}
function cancel() {
if (preloadStyle && preloadStyle.parentNode) {
preloadStyle.parentNode.removeChild(preloadStyle);
}
preloadStyle = null;
isPreloaded = false;
}
return { preload, upgradeToFull, cancel, isPreloaded: () => isPreloaded };
})();
// ==================== 极速启动器 ====================
const SeamlessLoader = (() => {
let initialized = false;
let headObserver = null;
function waitForHeadAndInject() {
if (document.head) {
if (!initialized) {
initialized = true;
if (!Util.isBlacklisted() && Util.getValue('PRELOAD_ENABLED') && Util.getValue('INSTANT_MODE')) {
PreloadManager.preload();
StyleManager.apply(true);
Guardian.start();
}
}
return;
}
if (!headObserver) {
headObserver = new MutationObserver(() => {
if (document.head) {
headObserver.disconnect();
headObserver = null;
if (!initialized) {
initialized = true;
if (!Util.isBlacklisted() && Util.getValue('PRELOAD_ENABLED') && Util.getValue('INSTANT_MODE')) {
PreloadManager.preload();
StyleManager.apply(true);
Guardian.start();
}
}
}
});
const root = document.documentElement;
if (root) {
headObserver.observe(root, { childList: true, subtree: false });
} else {
const interval = setInterval(() => {
if (document.documentElement) {
clearInterval(interval);
headObserver.observe(document.documentElement, { childList: true, subtree: false });
}
}, 5);
}
}
let checkCount = 0;
const checkInterval = setInterval(() => {
if (document.head) {
clearInterval(checkInterval);
if (!initialized) {
initialized = true;
if (!Util.isBlacklisted() && Util.getValue('PRELOAD_ENABLED') && Util.getValue('INSTANT_MODE')) {
PreloadManager.preload();
StyleManager.apply(true);
Guardian.start();
}
}
}
checkCount++;
if (checkCount > 100) clearInterval(checkInterval);
}, 5);
}
function init() {
if (initialized) return;
if (document.head) {
initialized = true;
if (!Util.isBlacklisted() && Util.getValue('PRELOAD_ENABLED') && Util.getValue('INSTANT_MODE')) {
PreloadManager.preload();
StyleManager.apply(true);
Guardian.start();
}
return;
}
waitForHeadAndInject();
}
return { init };
})();
// ==================== 菜单与设置面板 ====================
function getNextModeInCycle(currentMode, cycleModes) {
if (!cycleModes || cycleModes.length === 0) return currentMode;
let modes = [...cycleModes];
if (currentMode === 'none') return modes[0];
const idx = modes.indexOf(currentMode);
if (idx !== -1) return modes[(idx + 1) % modes.length];
else return modes[0];
}
function switchMode() {
let oldMode = Util.getValue('currentMode') || 'none';
let cycleModes = Util.getValue('globalCycleModes');
if (!Array.isArray(cycleModes) || cycleModes.length === 0) {
Util.showNotification('请先在设置中添加模式到循环列表', 'info', 2000);
return;
}
const nextMode = getNextModeInCycle(oldMode, cycleModes);
Util.setValue('currentMode', nextMode);
handleAutoDisableTempModeOnModeChange(nextMode, oldMode);
StyleManager.apply(true);
refreshMenu();
const hasSite = getSiteSpecificMode(null, true) !== null && !Util.getValue('tempDisableAllSiteModes');
let msg = `已切换到 ${getModeName(nextMode)}`;
if (hasSite) msg += `,但当前网站专用模式(${getSiteModeName(getEffectiveMode())})仍优先使用`;
Util.showNotification(msg, 'success', 2000);
scheduleUIUpdate();
}
function clearMenu() { menuCommands.forEach(cmd => { try { GM_unregisterMenuCommand(cmd); } catch(e) {} }); menuCommands = []; }
function refreshMenu() { clearMenu(); initMenu(); }
function getModeName(mode) { return MODE_NAMES[mode] || '未知模式'; }
function getSiteModeName(mode) {
if (typeof mode === 'object' && mode.mode === 'softwarm') return '🍃 柔暖双色';
if (typeof mode === 'object' && mode.mode === 'themecolor') return '🌈 护眼主题';
return MODE_NAMES[mode] || '默认';
}
function handleAutoDisableTempModeOnModeChange(newMode, oldMode) {
const autoMode = Util.getValue('autoDisableTempMode');
if (!autoMode) return;
const alsoEnableForce = Util.getValue('autoDisableAlsoEnableForceSpecial');
if (newMode === autoMode && oldMode !== autoMode) {
let changed = false;
if (Util.getValue('tempDisableAllSiteModes') !== true) { Util.setValue('tempDisableAllSiteModes', true); changed = true; }
if (alsoEnableForce && Util.getValue('forceSpecialModeEnabled') !== true) { Util.setValue('forceSpecialModeEnabled', true); changed = true; }
if (changed) { StyleManager.apply(true); refreshMenu(); Util.showNotification(`${alsoEnableForce ? '已自动关闭专用模式并开启强制专用模式' : '已自动关闭专用模式'}(进入 ${getModeName(newMode)} 模式)`, 'info', 2000); }
} else if (oldMode === autoMode && newMode !== autoMode) {
let changed = false;
if (Util.getValue('tempDisableAllSiteModes') !== false) { Util.setValue('tempDisableAllSiteModes', false); changed = true; }
if (Util.getValue('forceSpecialModeEnabled') !== false) { Util.setValue('forceSpecialModeEnabled', false); changed = true; }
if (changed) { StyleManager.apply(true); refreshMenu(); Util.showNotification(`已恢复专用模式(离开 ${getModeName(oldMode)} 模式)`, 'info', 2000); }
}
}
let pendingUIUpdate = false;
function scheduleUIUpdate() {
if (pendingUIUpdate) return;
pendingUIUpdate = true;
requestAnimationFrame(() => {
if (currentSettingsShadow) {
updateGlobalSoftwarmPanelVisibility(currentSettingsShadow);
updateGlobalThemePanelVisibility(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
pendingUIUpdate = false;
});
}
function updateGlobalSoftwarmPanelVisibility(shadowRoot) {
if (!shadowRoot) return;
const panel = shadowRoot.querySelector('#globalSoftwarmPanel');
if (!panel) return;
const currentGlobalMode = getCurrentGlobalMode();
panel.style.display = (currentGlobalMode === 'softwarm') ? 'block' : 'none';
if (currentGlobalMode === 'softwarm') {
const type = Util.getValue('softwarmGlobalType') || 'green';
const greenContainer = shadowRoot.querySelector('#globalSoftwarmGreenContainer');
const warmContainer = shadowRoot.querySelector('#globalSoftwarmWarmContainer');
if (greenContainer && warmContainer) {
greenContainer.style.display = type === 'green' ? 'block' : 'none';
warmContainer.style.display = type === 'warm' ? 'block' : 'none';
const greenSlider = shadowRoot.querySelector('#globalSoftwarmGreenSlider');
const warmSlider = shadowRoot.querySelector('#globalSoftwarmWarmSlider');
const greenValue = shadowRoot.querySelector('#globalSoftwarmGreenValue');
const warmValue = shadowRoot.querySelector('#globalSoftwarmWarmValue');
if (greenSlider && greenValue) {
greenSlider.value = Util.getValue('softwarmGlobalGreenOpacity');
greenValue.textContent = Math.round(greenSlider.value * 100);
}
if (warmSlider && warmValue) {
warmSlider.value = Util.getValue('softwarmGlobalWarmOpacity');
warmValue.textContent = Math.round(warmSlider.value * 100);
}
}
}
}
function updateGlobalThemePanelVisibility(shadowRoot) {
if (!shadowRoot) return;
const panel = shadowRoot.querySelector('#globalThemePanel');
if (!panel) return;
const currentGlobalMode = getCurrentGlobalMode();
panel.style.display = (currentGlobalMode === 'themecolor') ? 'block' : 'none';
}
function isSpecialModeActive() {
if (Util.getValue('tempDisableAllSiteModes')) return false;
let siteSpecific = getSiteSpecificMode();
if (siteSpecific && siteSpecific !== 'default') return true;
if (isForceSpecialSite() && getForceSpecialModeForSite() !== null) return true;
return false;
}
function updateSettingsPanelUI(shadowRoot) {
if (!shadowRoot) return;
const currentGlobalMode = getCurrentGlobalMode();
const cycleModes = Util.getValue('globalCycleModes') || [];
const nextMode = getNextModeInCycle(currentGlobalMode, cycleModes);
const nextModeName = MODE_NAMES[nextMode] || '无模式';
const toggleBtn = shadowRoot.querySelector('#toggleMode');
if (toggleBtn) {
if (cycleModes.length === 0) { toggleBtn.textContent = '请先添加模式'; toggleBtn.disabled = true; }
else { toggleBtn.textContent = `切换 ${nextModeName}`; toggleBtn.disabled = false; }
}
const actualMode = getEffectiveMode();
const hasSpecial = isSpecialModeActive();
let modeDisplayText = getSiteModeName(actualMode);
if (hasSpecial) modeDisplayText += ' [专用]';
const modeDisplaySpan = shadowRoot.querySelector('.settings-section:first-child div:first-child span:last-child');
if (modeDisplaySpan) modeDisplaySpan.textContent = modeDisplayText;
const forceSpecialCheckbox = shadowRoot.querySelector('#forceSpecialEnabled');
if (forceSpecialCheckbox) forceSpecialCheckbox.checked = Util.getValue('forceSpecialModeEnabled');
const tempDisableCheckbox = shadowRoot.querySelector('#tempDisableAllSiteModes');
if (tempDisableCheckbox) {
tempDisableCheckbox.checked = Util.getValue('tempDisableAllSiteModes');
const tempDisableStatusText = shadowRoot.querySelector('#tempDisableStatusText');
if (tempDisableStatusText) {
tempDisableStatusText.textContent = Util.getValue('tempDisableAllSiteModes') ? '已关闭' : '已启用';
tempDisableStatusText.style.color = Util.getValue('tempDisableAllSiteModes') ? '#dc3545' : '#007bff';
}
}
updateGlobalSoftwarmPanelVisibility(shadowRoot);
updateGlobalThemePanelVisibility(shadowRoot);
const darkReaderStatus = shadowRoot.querySelector('#darkReaderStatus');
if (darkReaderStatus) {
const enabled = getDarkReaderEnabledForCurrentSite();
darkReaderStatus.textContent = enabled ? '✅ 已启用' : '❌ 已禁用';
darkReaderStatus.style.color = enabled ? '#28a745' : '#dc3545';
}
const perfCheckbox = shadowRoot.querySelector('#performanceMode');
if (perfCheckbox) {
perfCheckbox.checked = Util.getValue('performanceMode');
}
}
// ==================== 菜单初始化 ====================
function initMenu() {
try {
if (typeof GM_registerMenuCommand !== 'function') return;
clearMenu();
let currentMode = Util.getValue('currentMode');
let globalEnable = Util.getValue('globalEnable');
let enableList = Util.getValue('enableList');
let host = location.host;
let siteEnabled = enableList.includes(host);
let effectiveMode = getEffectiveMode();
let hasSpecial = isSpecialModeActive();
let modeDisplay = hasSpecial ? `${getSiteModeName(effectiveMode)} [专用]` : getModeName(currentMode);
menuCommands.push(GM_registerMenuCommand(modeDisplay, () => {
const cycleModes = Util.getValue('globalCycleModes');
if (!cycleModes || cycleModes.length === 0) { Util.showNotification('请先在设置中添加模式到循环列表', 'info', 2000); return; }
switchMode();
}));
menuCommands.push(GM_registerMenuCommand(globalEnable ? '🌍 全局: 开启 (点击关闭)' : '🌍 全局: 关闭 (点击开启)', () => { toggleGlobal(); }));
menuCommands.push(GM_registerMenuCommand(siteEnabled ? '✅ 本站: 启用 (点击禁用)' : '❌ 本站: 禁用 (点击启用)', () => { toggleCurrentSite(); }));
menuCommands.push(GM_registerMenuCommand('⚙️ 设置面板', () => { showSettings(); }));
} catch(e) { console.error('菜单注册失败:', e); }
}
// ==================== 设置面板 ====================
function showSettings() {
if (!document.body) { setTimeout(showSettings, 50); return; }
try {
let existingHost = document.querySelector('.eye-protect-settings-host');
if (existingHost) existingHost.remove();
if (currentSettingsHost) currentSettingsHost = null;
if (currentSettingsShadow) currentSettingsShadow = null;
let loadingPanel = document.createElement('div');
loadingPanel.className = 'eye-protect-settings-panel loading';
loadingPanel.style.cssText = `position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:300px;background:#fff;color:#333;border-radius:10px;box-shadow:0 5px 20px rgba(0,0,0,0.2);z-index:2147483647;font-family:system-ui,sans-serif;border:1px solid #e0e0e0;padding:20px;text-align:center;font-size:14px;`;
loadingPanel.innerHTML = `⏳ 正在加载设置面板...
`;
document.body.appendChild(loadingPanel);
setTimeout(() => {
let currentMode = Util.getValue('currentMode');
let globalEnable = Util.getValue('globalEnable');
let enableList = Util.getValue('enableList');
let blacklist = Util.getValue('blacklist');
let cycleModes = Util.getValue('globalCycleModes');
if (!Array.isArray(cycleModes)) cycleModes = [];
let host = location.host;
let siteEnabled = enableList.includes(host);
let isForced = getForcedEnableListObjects().some(item => item.domain === host);
let isBlacklisted = blacklist.includes(host);
let blacklistCount = blacklist.length;
let siteSpecific = getSiteSpecificMode();
let siteSpecificMode = siteSpecific ? (typeof siteSpecific === 'string' ? siteSpecific : siteSpecific.mode) : null;
let siteSpecificSoftwarm = (siteSpecific && typeof siteSpecific === 'object' && siteSpecific.mode === 'softwarm') ? siteSpecific : null;
let tempDisableAll = Util.getValue('tempDisableAllSiteModes');
let autoDisableTempMode = Util.getValue('autoDisableTempMode') || '';
let autoDisableAlsoEnableForce = Util.getValue('autoDisableAlsoEnableForceSpecial') || false;
let runDuringDay = Util.getValue('runDuringDay');
let darkAuto = Util.getValue('darkAuto');
let customDark3 = Util.getValue('customDark3');
let showNotification = Util.getValue('SHOW_NOTIFICATION');
let preloadEnabled = Util.getValue('PRELOAD_ENABLED');
let instantMode = Util.getValue('INSTANT_MODE');
let forceRecovery = Util.getValue('FORCE_RECOVERY');
let avoidDarkSite = Util.getValue('AVOID_DARK_SITE');
let brightnessThreshold = Util.getValue('BRIGHTNESS_THRESHOLD');
let panelWidth = Util.getValue('panelWidth') || 400;
let panelHeight = Util.getValue('panelHeight') || 450;
let panelLeft = Util.getValue('panelLeft') || '50%';
let panelTop = Util.getValue('panelTop') || '50%';
let panelTransform = Util.getValue('panelTransform') || 'translate(-50%, -50%)';
let forceSpecialEnabled = Util.getValue('forceSpecialModeEnabled');
let forceSpecialListRaw = Util.getValue('forceSpecialList') || [];
let globalThemeBg = Util.getValue('themecolorBgColor');
let globalThemeLink = Util.getValue('themecolorLinkColor');
let globalSoftwarmType = Util.getValue('softwarmGlobalType') || 'green';
let globalSoftwarmGreenOpacity = Util.getValue('softwarmGlobalGreenOpacity');
let globalSoftwarmWarmOpacity = Util.getValue('softwarmGlobalWarmOpacity');
let performanceMode = Util.getValue('performanceMode');
let forceSpecialListObjects = forceSpecialListRaw.map(item => {
if (typeof item === 'string') return { domain: item, addedTime: Date.now() };
return item;
});
function buildCycleListHtml() {
if (cycleModes.length === 0) return '暂无模式,请点击下方按钮添加
';
let itemsHtml = '';
cycleModes.forEach((mode, idx) => {
let name = MODE_NAMES[mode] || mode;
itemsHtml += `
`;
});
return itemsHtml;
}
const availableModes = [
{ id: 'light', name: '☀️ 白天模式' },
{ id: 'dark', name: '🌙 夜间模式' },
{ id: 'eyecare', name: '👁️ 护眼暗色' },
{ id: 'softwarm', name: '🍃 柔暖双色' },
{ id: 'themecolor', name: '🌈 护眼主题' },
{ id: 'filter', name: '🎨 滤镜模式' },
{ id: 'darkreader', name: '🌓 深色模式' },
{ id: 'soft', name: '🕯️ 柔和护眼' },
{ id: 'pure', name: '⚪ 纯净黑白' }
];
let notAddedModes = availableModes.filter(m => !cycleModes.includes(m.id));
let addOptionsHtml = notAddedModes.map(m => `${m.name} `).join('');
if (addOptionsHtml === '') addOptionsHtml = '所有模式已添加 ';
const autoModeOptions = [
{ id: '', name: '不启用' },
{ id: 'dark', name: '🌙 夜间模式' },
{ id: 'soft', name: '🕯️ 柔和护眼' },
{ id: 'eyecare', name: '👁️ 护眼暗色' },
{ id: 'pure', name: '⚪ 纯净黑白' },
{ id: 'filter', name: '🎨 滤镜模式' },
{ id: 'softwarm', name: '🍃 柔暖双色' },
{ id: 'themecolor', name: '🌈 护眼主题' },
{ id: 'darkreader', name: '🌓 深色模式' }
];
let autoModeOptionsHtml = autoModeOptions.map(opt => `${opt.name} `).join('');
const actualMode = getEffectiveMode();
const hasSpecial = isSpecialModeActive();
let currentModeDisplayText = getSiteModeName(actualMode);
if (hasSpecial) currentModeDisplayText += ' [专用]';
if (!hasSpecial && actualMode === 'light' && getCurrentGlobalMode() === 'none') currentModeDisplayText = '⚪ 无模式';
const hostDiv = document.createElement('div');
hostDiv.className = 'eye-protect-settings-host';
hostDiv.style.cssText = 'all:initial; position:fixed; top:0; left:0; width:100%; height:100%; z-index:2147483647; pointer-events:none;';
const shadowRoot = hostDiv.attachShadow({ mode: 'open' });
document.body.appendChild(hostDiv);
currentSettingsHost = hostDiv;
currentSettingsShadow = shadowRoot;
const panelContainer = document.createElement('div');
panelContainer.style.cssText = `
position:fixed; left:${panelLeft}; top:${panelTop}; transform:${panelTransform};
width:${panelWidth}px; max-width:95%; max-height:85vh;
background:#fff; color:#333; border-radius:12px;
box-shadow:0 10px 30px rgba(0,0,0,0.2);
overflow:hidden; font-family:system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
font-size:14px; line-height:1.4; pointer-events:auto;
display:flex; flex-direction:column;
transition: none;
`;
if (panelHeight) panelContainer.style.height = `${panelHeight}px`;
shadowRoot.appendChild(panelContainer);
const style = document.createElement('style');
style.textContent = `
* { box-sizing: border-box; margin: 0; padding: 0; }
.settings-content { overflow-y: auto; flex: 1; padding: 20px; }
.settings-content::-webkit-scrollbar { width: 8px; height: 8px; }
.settings-content::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
.settings-content::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
.settings-content::-webkit-scrollbar-thumb:hover { background: #a8a8a8; }
.toggle-switch { position: relative; display: inline-block; width: 50px; height: 24px; }
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #e0e0e0; border-radius: 24px; transition: 0.4s; }
.toggle-slider:before { content: ""; position: absolute; height: 16px; width: 16px; left: 4px; bottom: 4px; background-color: #fff; border-radius: 50%; transition: transform 0.4s, background-color 0.4s; }
.toggle-switch input:checked + .toggle-slider { background-color: #007bff; }
.toggle-switch input:checked + .toggle-slider:before { transform: translateX(26px); }
.temp-toggle-switch .toggle-slider { background-color: #007bff; }
.temp-toggle-switch .toggle-slider:before { transform: translateX(0); }
.temp-toggle-switch input:checked + .toggle-slider { background-color: #dc3545; }
.temp-toggle-switch input:checked + .toggle-slider:before { transform: translateX(26px); }
input[type="range"] { -webkit-appearance: none; appearance: none; height: 6px; border-radius: 3px; outline: none; }
input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 2px solid #007bff; cursor: pointer; box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
input[type="range"]::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 2px solid #007bff; cursor: pointer; }
.green-slider { background: linear-gradient(to right, #fff, #C7EDCC); }
.warm-slider { background: linear-gradient(to right, #fff, #CFB988); }
.brightness-slider { background: linear-gradient(to right, #ffffff, #000000); }
.settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 8px; }
.settings-grid .setting-item { margin-bottom: 0; }
.full-width-item { grid-column: span 2; margin-top: 8px; }
.mode-desc-list { margin: 0; padding-left: 20px; list-style: disc; }
.mode-desc-list li { margin-bottom: 6px; line-height: 1.4; font-size: 12px; }
.collapsible-desc { margin-top: 8px; padding: 8px; background: #f9f9f9; border-radius: 4px; border-left: 3px solid #007bff; cursor: pointer; }
.collapsible-desc .desc-header { font-weight: bold; display: flex; justify-content: space-between; align-items: center; }
.collapsible-desc .desc-content { margin-top: 8px; }
.preset-buttons { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.preset-buttons button { padding: 6px 12px; background: #e9ecef; border: 1px solid #ccc; border-radius: 6px; cursor: pointer; }
.softwarm-type-select { display: flex; gap: 15px; margin-bottom: 15px; align-items: center; }
.softwarm-type-select label { display: flex; align-items: center; gap: 5px; cursor: pointer; }
.foldable-header { cursor: pointer; display: flex; justify-content: space-between; align-items: center; padding: 5px 0; user-select: none; }
.foldable-header:hover { background: #f0f0f0; }
.foldable-content { margin-top: 8px; }
.sub-card { background: #fff; border-radius: 8px; border: 1px solid #e0e0e0; padding: 12px; margin-bottom: 15px; }
.sub-card:last-child { margin-bottom: 0; }
#blacklistItemsList { max-height: 126px; overflow-y: auto; }
.drag-handle { cursor: grab; user-select: none; }
.drag-handle:active { cursor: grabbing; }
#resizePanel { display: flex; flex-direction: column; gap: 8px; }
.resize-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
`;
shadowRoot.appendChild(style);
const modeDescHtml = `
⚪ 无模式 :不应用任何护眼滤镜,页面保持原样。
☀️ 白天模式 :正常亮色模式,无任何滤镜。
🌙 夜间模式 :经典反色滤镜,强度可调。
👁️ 护眼暗色 :柔和的暗色主题。
🍃 柔暖双色 :豆沙绿或深暖色护眼底色,可调节浓度。
🌈 护眼主题 :智能识别浅色背景,应用预设配色。
🎨 滤镜模式 :颜色滤镜方案。
🌓 深色模式 :使用专业的 DarkReader 库实现高质量深色模式,支持跨域 CSS 加载,每个网站独立记忆开关状态。
🕯️ 柔和护眼 :温和的反色方案。
⚪ 纯净黑白 :去饱和的黑白风格。
💻 代码页面优化 :自动应用于JSON/JS等代码页面。
🔘 全局专用总开关 :开启后所有网站的专用模式暂时失效。
`;
const siteSoftwarmCurrentType = (siteSpecificSoftwarm && siteSpecificSoftwarm.type) ? siteSpecificSoftwarm.type : 'green';
const siteSoftwarmGreenOpacity = siteSpecificSoftwarm ? siteSpecificSoftwarm.greenOpacity : globalSoftwarmGreenOpacity;
const siteSoftwarmWarmOpacity = siteSpecificSoftwarm ? siteSpecificSoftwarm.warmOpacity : globalSoftwarmWarmOpacity;
const darkReaderEnabled = getDarkReaderEnabledForCurrentSite();
panelContainer.innerHTML = `
📱 当前状态
当前模式: ${currentModeDisplayText}
当前网站: ${host}
全局开关: ${globalEnable ? '✅ 已开启' : '❌ 已关闭'}
白名单状态: ${siteEnabled ? '✅ 已启用' : '❌ 未启用'}
黑名单状态: ${isBlacklisted ? '⛔ 已禁用' : '❌ 未禁用'}
强制启用: ${isForced ? '⚡ 已强制启用' : '🚫 未强制启用'}
网站专用模式: ${siteSpecificMode ? getSiteModeName(siteSpecificMode) : '默认'} ${tempDisableAll ? '(已关闭)' : ''}
🌓 深色模式状态: ${darkReaderEnabled ? '✅ 已启用' : '❌ 已禁用'}
🌐 网站专用模式设置
输入域名或完整网址(如 google.com),然后选择模式,即可为该网站设置专用模式
域名或网址:
专用模式:
默认(跟随全局设置)
☀️ 白天模式
🌙 夜间模式
👁️ 护眼暗色
🍃 柔暖双色
🌈 护眼主题
🎨 滤镜模式
🌓 深色模式
🕯️ 柔和护眼
⚪ 纯净黑白
🌈 护眼主题配色:
${THEME_PRESETS.map(preset => `${preset.name} `).join('')}
点击预设立即切换当前网站的护眼主题配色。
${modeDescHtml}
💾 应用专用模式
🔄 重置为默认
🎨 全局护眼主题配色
当前全局模式为“🌈 护眼主题”,点击预设按钮切换所有网站(未设置专用模式)的配色。
快速预设:
${THEME_PRESETS.map(preset => `${preset.name} `).join('')}
🔄 全局循环模式配置
自定义全局切换时循环的模式顺序
${buildCycleListHtml()}
⚙️ 专用模式高级控制
选择某个模式后,切换到该模式时会自动关闭“全局专用总开关”${autoDisableAlsoEnableForce ? ',并同时开启下方的“强制专用模式”总开关' : ''},离开时自动恢复。
开启后,列表中的网站将强制使用其专用模式(优先级高于专用模式开关,但低于黑名单,且不受“全局专用总开关”影响)。
🛡️ 权限与过滤
强制启用列表中的网站需要满足全局开关或白名单才会生效(黑名单优先级最高)。
强制启用状态:
${host}:${isForced ? '已强制启用' : '未强制启用'}
当前生效条件: • 全局开关:${globalEnable ? '✅ 已开启' : '❌ 已关闭'} • 白名单:${siteEnabled ? '✅ 已启用' : '❌ 未启用'}强制启用生效: ${isForced && (globalEnable || siteEnabled) ? '✅ 是' : '❌ 否'}
${isForced ? '⚡ 取消强制启用' : '⚡ 强制启用本站'}
🗑️ 清空强制列表
黑名单中的网站将始终禁用护眼模式,优先级最高。
当前黑名单状态:${blacklistCount} 个网站
${host}:${isBlacklisted ? '⛔ 已禁用' : '❌ 未禁用'}
📌 优先级顺序(从高到低)
1. 黑名单:当前网站${isBlacklisted ? '⛔ 已禁用' : '✅ 未禁用'}
2. 强制启用:${isForced ? '⚡ 已强制启用' : '🚫 未强制启用'}
3. 全局开关:${globalEnable ? '✅ 已开启' : '❌ 已关闭'}
4. 白名单:${siteEnabled ? '✅ 已启用' : '❌ 未启用'}
当前网站最终状态: ${isBlacklisted ? '⛔ 禁用(黑名单)' : ((isForced && (globalEnable || siteEnabled)) ? '✅ 启用(强制启用生效)' : ((globalEnable || siteEnabled) ? '✅ 启用' : '❌ 禁用'))}
${isBlacklisted ? '✅ 从黑名单移除' : '⛔ 加入黑名单'}
🗑️ 清空黑名单
⚙️ 全局设置与性能
📖 说明
这些设置影响所有网站。
• 全局开关 :主开关。
• 即时模式 :DOM构建前应用暗色主题,减少闪烁。
• 白天开启 :是否在白天也启用。
• 预加载模式 :提前应用关键样式。
• 暗色站防误判 :检测到页面背景较暗时自动跳过暗色模式。
• 强制恢复模式 :样式被意外移除时自动恢复。
• 显示通知 :操作时弹出提示。
• 跟随系统 :根据系统主题自动切换。
• 夜间强度 :调整反色滤镜强度。
• 亮度检测阈值 :低于此值视为暗色站。
• 性能优化模式 :降低守护检查频率,减少CPU占用(推荐开启)。
${cycleModes.length === 0 ? '请先添加模式' : `切换 ${(MODE_NAMES[getNextModeInCycle(currentMode, cycleModes)] || '无模式')}`}
${siteEnabled ? '❌ 禁用本站' : '✅ 启用本站'}
💾 保存设置
🔄 恢复默认
🧹 清理内存
`;
// ===== 折叠功能 =====
function bindFoldableHeaders(root) {
const headers = root.querySelectorAll('.foldable-header');
headers.forEach(header => {
header.removeEventListener('click', header._foldHandler);
const handler = function(e) {
e.stopPropagation();
const targetId = this.getAttribute('data-fold-target');
if (!targetId) return;
const target = root.querySelector(`#${targetId}`);
const icon = this.querySelector('.fold-icon');
if (!target || !icon) return;
if (target.style.display === 'none' || target.style.display === '') {
target.style.display = 'block';
icon.textContent = '▼';
} else {
target.style.display = 'none';
icon.textContent = '▶';
}
};
header._foldHandler = handler;
header.addEventListener('click', handler);
const targetId = header.getAttribute('data-fold-target');
if (targetId) {
const target = root.querySelector(`#${targetId}`);
const icon = header.querySelector('.fold-icon');
if (target && icon) {
if (target.style.display !== 'block') {
target.style.display = 'none';
icon.textContent = '▶';
}
}
}
});
}
bindFoldableHeaders(shadowRoot);
refreshForceSpecialListUI(shadowRoot);
refreshForcedEnableListUI(shadowRoot);
refreshBlacklistUI(shadowRoot);
updateGlobalThemePanelVisibility(shadowRoot);
updateGlobalSoftwarmPanelVisibility(shadowRoot);
// ===== 全局开关即时应用(force=true) =====
const globalEnableCheckbox = shadowRoot.querySelector('#globalEnable');
if (globalEnableCheckbox) {
globalEnableCheckbox.addEventListener('change', function() {
Util.setValue('globalEnable', this.checked);
StyleManager.apply(true);
refreshMenu();
updateSettingsPanelUI(shadowRoot);
Util.showNotification(this.checked ? '已开启全局模式' : '已关闭全局模式', 'success', 1500);
});
}
// ===== 其他即时应用 =====
// 本站启用/禁用按钮已有 toggleCurrentSite 并调用 StyleManager.apply(true)
// ===== 预设按钮等事件 =====
THEME_PRESETS.forEach(preset => {
const btn = shadowRoot.querySelector(`#globalPreset${preset.id.charAt(0).toUpperCase() + preset.id.slice(1)}`);
if (btn) {
btn.addEventListener('click', () => {
Util.setValue('themecolorBgColor', preset.bg);
Util.setValue('themecolorLinkColor', preset.link);
if (getCurrentGlobalMode() === 'themecolor') {
const siteMode = getSiteSpecificMode();
if (!siteMode || siteMode !== 'themecolor') {
if (customModeActive) {
updateThemeCSSVars(preset.bg, preset.link);
}
}
}
Util.showNotification(`已应用预设:${preset.name}`, 'success', 1500);
});
}
});
const siteThemeContainer = shadowRoot.querySelector('#siteThemeContainer');
if (siteThemeContainer) {
siteThemeContainer.querySelectorAll('.site-theme-preset').forEach(btn => {
btn.addEventListener('click', () => {
const presetId = btn.getAttribute('data-preset');
const preset = THEME_PRESETS.find(p => p.id === presetId);
if (preset) {
const bg = preset.bg;
const link = preset.link;
if (getEffectiveMode() === 'themecolor') {
const siteMode = getSiteSpecificMode();
if (siteMode === 'themecolor') {
updateThemeCSSVars(bg, link);
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
if (siteModes[location.host] && siteModes[location.host].mode === 'themecolor') {
siteModes[location.host].colors = { bg, link };
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
}
} catch(e) {}
}
}
const hostInput = shadowRoot.querySelector('#siteDomain').value.trim();
let host = (() => {
try { let url = new URL(hostInput.startsWith('http') ? hostInput : 'http://' + hostInput); return url.host; }
catch(e) { return hostInput.replace(/^https?:\/\//i, '').split('/')[0]; }
})();
if (host) {
setSiteSpecificMode(host, 'themecolor', { colors: { bg, link } });
} else {
Util.setValue('themecolorBgColor', bg);
Util.setValue('themecolorLinkColor', link);
}
Util.showNotification(`已为网站应用预设:${preset.name}`, 'success', 1500);
}
});
});
}
const siteModeSelect = shadowRoot.querySelector('#siteModeSelect');
const siteSoftwarmContainerDiv = shadowRoot.querySelector('#siteSoftwarmContainer');
const siteThemeContainerDiv = shadowRoot.querySelector('#siteThemeContainer');
if (siteModeSelect) {
siteModeSelect.addEventListener('change', () => {
const val = siteModeSelect.value;
if (siteSoftwarmContainerDiv) siteSoftwarmContainerDiv.style.display = val === 'softwarm' ? 'block' : 'none';
if (siteThemeContainerDiv) siteThemeContainerDiv.style.display = val === 'themecolor' ? 'block' : 'none';
});
}
const globalSoftwarmTypeRadios = shadowRoot.querySelectorAll('input[name="globalSoftwarmType"]');
const globalSoftwarmGreenContainer = shadowRoot.querySelector('#globalSoftwarmGreenContainer');
const globalSoftwarmWarmContainer = shadowRoot.querySelector('#globalSoftwarmWarmContainer');
const globalSoftwarmGreenSlider = shadowRoot.querySelector('#globalSoftwarmGreenSlider');
const globalSoftwarmGreenSliderValue = shadowRoot.querySelector('#globalSoftwarmGreenSliderValue');
const globalSoftwarmGreenLabelValue = shadowRoot.querySelector('#globalSoftwarmGreenLabelValue');
const globalSoftwarmWarmSlider = shadowRoot.querySelector('#globalSoftwarmWarmSlider');
const globalSoftwarmWarmSliderValue = shadowRoot.querySelector('#globalSoftwarmWarmSliderValue');
const globalSoftwarmWarmLabelValue = shadowRoot.querySelector('#globalSoftwarmWarmLabelValue');
function updateGlobalSoftwarmUI() {
const type = shadowRoot.querySelector('input[name="globalSoftwarmType"]:checked').value;
Util.setValue('softwarmGlobalType', type);
if (globalSoftwarmGreenContainer) globalSoftwarmGreenContainer.style.display = type === 'green' ? 'block' : 'none';
if (globalSoftwarmWarmContainer) globalSoftwarmWarmContainer.style.display = type === 'warm' ? 'block' : 'none';
if (getCurrentGlobalMode() === 'softwarm') {
StyleManager.apply(true);
}
}
globalSoftwarmTypeRadios.forEach(radio => {
radio.addEventListener('change', updateGlobalSoftwarmUI);
});
if (globalSoftwarmGreenSlider) {
globalSoftwarmGreenSlider.addEventListener('input', (e) => {
let val = parseFloat(e.target.value);
let percent = Math.round(val * 100);
if (globalSoftwarmGreenSliderValue) globalSoftwarmGreenSliderValue.textContent = percent;
if (globalSoftwarmGreenLabelValue) globalSoftwarmGreenLabelValue.textContent = `(${percent}%)`;
Util.setValue('softwarmGlobalGreenOpacity', val);
const currentType = getSoftwarmTypeForSite();
if (getCurrentGlobalMode() === 'softwarm' && currentType === 'green') {
if (val > 0) {
if (greenOverlay) greenOverlay.style.opacity = val;
else updateSoftwarmOverlay();
} else {
if (greenOverlay) { greenOverlay.remove(); greenOverlay = null; }
}
}
Util.showNotification(`豆沙绿浓度已调整为 ${percent}%`, 'info', 1000);
});
}
if (globalSoftwarmWarmSlider) {
globalSoftwarmWarmSlider.addEventListener('input', (e) => {
let val = parseFloat(e.target.value);
let percent = Math.round(val * 100);
if (globalSoftwarmWarmSliderValue) globalSoftwarmWarmSliderValue.textContent = percent;
if (globalSoftwarmWarmLabelValue) globalSoftwarmWarmLabelValue.textContent = `(${percent}%)`;
Util.setValue('softwarmGlobalWarmOpacity', val);
const currentType = getSoftwarmTypeForSite();
if (getCurrentGlobalMode() === 'softwarm' && currentType === 'warm') {
if (val > 0) {
if (warmOverlay) warmOverlay.style.opacity = val;
else updateSoftwarmOverlay();
} else {
if (warmOverlay) { warmOverlay.remove(); warmOverlay = null; }
}
}
Util.showNotification(`深暖色浓度已调整为 ${percent}%`, 'info', 1000);
});
}
const siteSoftwarmContainer = shadowRoot.querySelector('#siteSoftwarmContainer');
if (siteSoftwarmContainer) {
const siteTypeRadios = siteSoftwarmContainer.querySelectorAll('input[name="siteSoftwarmType"]');
const siteGreenPanel = siteSoftwarmContainer.querySelector('#siteSoftwarmGreenPanel');
const siteWarmPanel = siteSoftwarmContainer.querySelector('#siteSoftwarmWarmPanel');
const siteGreenSlider = siteSoftwarmContainer.querySelector('#siteSoftwarmGreenSlider');
const siteGreenSliderValue = siteSoftwarmContainer.querySelector('#siteSoftwarmGreenSliderValue');
const siteWarmSlider = siteSoftwarmContainer.querySelector('#siteSoftwarmWarmSlider');
const siteWarmSliderValue = siteSoftwarmContainer.querySelector('#siteSoftwarmWarmSliderValue');
const siteGreenLabelValue = siteSoftwarmContainer.querySelector('#siteSoftwarmGreenLabelValue');
const siteWarmLabelValue = siteSoftwarmContainer.querySelector('#siteSoftwarmWarmLabelValue');
function updateSiteSoftwarmUI() {
const type = siteSoftwarmContainer.querySelector('input[name="siteSoftwarmType"]:checked').value;
if (siteGreenPanel) siteGreenPanel.style.display = type === 'green' ? 'block' : 'none';
if (siteWarmPanel) siteWarmPanel.style.display = type === 'warm' ? 'block' : 'none';
if (getEffectiveMode() === 'softwarm') {
const hostInput = shadowRoot.querySelector('#siteDomain').value.trim();
let host = (() => {
try { let url = new URL(hostInput.startsWith('http') ? hostInput : 'http://' + hostInput); return url.host; }
catch(e) { return hostInput.replace(/^https?:\/\//i, '').split('/')[0]; }
})();
if (host === location.host) {
const currentGreenOp = parseFloat(siteGreenSlider.value);
const currentWarmOp = parseFloat(siteWarmSlider.value);
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
if (!siteModes[host]) siteModes[host] = { mode: 'softwarm' };
siteModes[host].type = type;
siteModes[host].greenOpacity = currentGreenOp;
siteModes[host].warmOpacity = currentWarmOp;
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
} catch(e) {}
StyleManager.apply(true);
}
}
}
siteTypeRadios.forEach(radio => {
radio.addEventListener('change', updateSiteSoftwarmUI);
});
if (siteGreenSlider) {
siteGreenSlider.addEventListener('input', (e) => {
let val = parseFloat(e.target.value);
let percent = Math.round(val * 100);
if (siteGreenSliderValue) siteGreenSliderValue.textContent = percent;
if (siteGreenLabelValue) siteGreenLabelValue.textContent = `(${percent}%)`;
const hostInput = shadowRoot.querySelector('#siteDomain').value.trim();
let host = (() => {
try { let url = new URL(hostInput.startsWith('http') ? hostInput : 'http://' + hostInput); return url.host; }
catch(e) { return hostInput.replace(/^https?:\/\//i, '').split('/')[0]; }
})();
if (host === location.host) {
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
if (!siteModes[host]) siteModes[host] = { mode: 'softwarm', type: 'green', greenOpacity: 0.3, warmOpacity: 0.3 };
siteModes[host].greenOpacity = val;
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
if (getEffectiveMode() === 'softwarm') {
if (val > 0) {
if (greenOverlay) greenOverlay.style.opacity = val;
else updateSoftwarmOverlay();
} else {
if (greenOverlay) { greenOverlay.remove(); greenOverlay = null; }
}
}
} catch(e) {}
} else {
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
if (!siteModes[host]) siteModes[host] = { mode: 'softwarm', type: 'green', greenOpacity: 0.3, warmOpacity: 0.3 };
siteModes[host].greenOpacity = val;
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
} catch(e) {}
}
Util.showNotification(`豆沙绿浓度已调整为 ${percent}%`, 'info', 1000);
});
}
if (siteWarmSlider) {
siteWarmSlider.addEventListener('input', (e) => {
let val = parseFloat(e.target.value);
let percent = Math.round(val * 100);
if (siteWarmSliderValue) siteWarmSliderValue.textContent = percent;
if (siteWarmLabelValue) siteWarmLabelValue.textContent = `(${percent}%)`;
const hostInput = shadowRoot.querySelector('#siteDomain').value.trim();
let host = (() => {
try { let url = new URL(hostInput.startsWith('http') ? hostInput : 'http://' + hostInput); return url.host; }
catch(e) { return hostInput.replace(/^https?:\/\//i, '').split('/')[0]; }
})();
if (host === location.host) {
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
if (!siteModes[host]) siteModes[host] = { mode: 'softwarm', type: 'warm', greenOpacity: 0.3, warmOpacity: 0.3 };
siteModes[host].warmOpacity = val;
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
if (getEffectiveMode() === 'softwarm') {
if (val > 0) {
if (warmOverlay) warmOverlay.style.opacity = val;
else updateSoftwarmOverlay();
} else {
if (warmOverlay) { warmOverlay.remove(); warmOverlay = null; }
}
}
} catch(e) {}
} else {
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
if (!siteModes[host]) siteModes[host] = { mode: 'softwarm', type: 'warm', greenOpacity: 0.3, warmOpacity: 0.3 };
siteModes[host].warmOpacity = val;
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
} catch(e) {}
}
Util.showNotification(`深暖色浓度已调整为 ${percent}%`, 'info', 1000);
});
}
}
// 说明折叠等
const modeDescCollapsible = shadowRoot.querySelector('#modeDescCollapsible');
if (modeDescCollapsible) {
const descContent = modeDescCollapsible.querySelector('.desc-content');
const toggleSpan = modeDescCollapsible.querySelector('.desc-toggle');
const headerDiv = modeDescCollapsible.querySelector('.desc-header');
if (headerDiv && descContent && toggleSpan) {
descContent.style.display = 'none';
toggleSpan.textContent = '▼';
headerDiv.addEventListener('click', (e) => {
e.stopPropagation();
if (descContent.style.display === 'none') {
descContent.style.display = 'block';
toggleSpan.textContent = '▲';
} else {
descContent.style.display = 'none';
toggleSpan.textContent = '▼';
}
});
}
}
const globalDescToggle = shadowRoot.querySelector('.global-settings-desc-toggle');
const globalDesc = shadowRoot.querySelector('.global-settings-desc');
if (globalDescToggle && globalDesc) {
globalDesc.style.display = 'none';
globalDescToggle.textContent = '📖 说明';
globalDescToggle.addEventListener('click', () => {
if (globalDesc.style.display === 'none') {
globalDesc.style.display = 'block';
globalDescToggle.textContent = '📖 说明 ▲';
} else {
globalDesc.style.display = 'none';
globalDescToggle.textContent = '📖 说明';
}
});
}
const brightnessInput = shadowRoot.querySelector('#brightnessThresholdInput');
const brightnessSlider = shadowRoot.querySelector('#brightnessThresholdSlider');
const brightnessExpandBtn = shadowRoot.querySelector('#brightnessThresholdExpandBtn');
const brightnessSliderContainer = shadowRoot.querySelector('#brightnessThresholdSliderContainer');
let sliderExpanded = false;
if (brightnessExpandBtn) {
brightnessExpandBtn.addEventListener('click', () => {
sliderExpanded = !sliderExpanded;
brightnessSliderContainer.style.display = sliderExpanded ? 'block' : 'none';
brightnessExpandBtn.textContent = sliderExpanded ? '▲ 收起滑块' : '▼ 展开滑块';
});
}
const updateBrightnessThreshold = (val) => {
val = Math.min(100, Math.max(0, parseInt(val) || 0));
Util.setValue('BRIGHTNESS_THRESHOLD', val);
if (brightnessInput) brightnessInput.value = val;
if (brightnessSlider) brightnessSlider.value = val;
StyleManager.apply(true);
Util.showNotification(`亮度检测阈值已调整为 ${val}%`, 'info', 1500);
};
if (brightnessInput) {
brightnessInput.addEventListener('change', (e) => {
let val = parseInt(e.target.value);
if (isNaN(val)) val = 70;
updateBrightnessThreshold(val);
});
}
if (brightnessSlider) {
brightnessSlider.addEventListener('input', (e) => {
let val = e.target.value;
if (brightnessInput) brightnessInput.value = val;
Util.setValue('BRIGHTNESS_THRESHOLD', parseInt(val));
StyleManager.apply(true);
});
brightnessSlider.addEventListener('change', (e) => {
let val = parseInt(e.target.value);
Util.showNotification(`亮度检测阈值已调整为 ${val}%`, 'info', 1500);
});
}
function bindCycleListEvents() {
const cycleListDiv = shadowRoot.querySelector('#cycleList');
if (!cycleListDiv) return;
cycleListDiv.querySelectorAll('.cycle-up').forEach(btn => {
btn.removeEventListener('click', handleCycleUp);
btn.addEventListener('click', handleCycleUp);
});
cycleListDiv.querySelectorAll('.cycle-down').forEach(btn => {
btn.removeEventListener('click', handleCycleDown);
btn.addEventListener('click', handleCycleDown);
});
cycleListDiv.querySelectorAll('.cycle-remove').forEach(btn => {
btn.removeEventListener('click', handleCycleRemove);
btn.addEventListener('click', handleCycleRemove);
});
}
function handleCycleUp(e) {
const btn = e.currentTarget;
const itemDiv = btn.closest('.cycle-item');
if (!itemDiv) return;
const idx = parseInt(itemDiv.dataset.idx);
if (isNaN(idx) || idx === 0) return;
const newModes = [...cycleModes];
[newModes[idx-1], newModes[idx]] = [newModes[idx], newModes[idx-1]];
cycleModes = newModes;
Util.setValue('globalCycleModes', cycleModes);
refreshCycleList();
}
function handleCycleDown(e) {
const btn = e.currentTarget;
const itemDiv = btn.closest('.cycle-item');
if (!itemDiv) return;
const idx = parseInt(itemDiv.dataset.idx);
if (isNaN(idx) || idx === cycleModes.length-1) return;
const newModes = [...cycleModes];
[newModes[idx+1], newModes[idx]] = [newModes[idx], newModes[idx+1]];
cycleModes = newModes;
Util.setValue('globalCycleModes', cycleModes);
refreshCycleList();
}
function handleCycleRemove(e) {
const btn = e.currentTarget;
const itemDiv = btn.closest('.cycle-item');
if (!itemDiv) return;
const idx = parseInt(itemDiv.dataset.idx);
if (isNaN(idx)) return;
const newModes = cycleModes.filter((_, i) => i !== idx);
if (newModes.length === 0) newModes.push('none');
cycleModes = newModes;
Util.setValue('globalCycleModes', cycleModes);
refreshCycleList();
}
function refreshCycleList() {
const cycleListDiv = shadowRoot.querySelector('#cycleList');
if (cycleListDiv) {
cycleListDiv.innerHTML = buildCycleListHtml();
bindCycleListEvents();
const addModeSelect = shadowRoot.querySelector('#addModeSelect');
if (addModeSelect) {
const notAddedModes = availableModes.filter(m => !cycleModes.includes(m.id));
let newOptions = notAddedModes.map(m => `${m.name} `).join('');
if (newOptions === '') newOptions = '所有模式已添加 ';
addModeSelect.innerHTML = newOptions;
}
updateSettingsPanelUI(shadowRoot);
}
}
const addModeBtn = shadowRoot.querySelector('#addModeBtn');
if (addModeBtn) {
addModeBtn.addEventListener('click', () => {
const select = shadowRoot.querySelector('#addModeSelect');
const modeToAdd = select.value;
if (modeToAdd && !cycleModes.includes(modeToAdd)) {
cycleModes.push(modeToAdd);
Util.setValue('globalCycleModes', cycleModes);
refreshCycleList();
Util.showNotification(`已添加模式 ${MODE_NAMES[modeToAdd]} 到循环列表`, 'success', 1500);
} else {
Util.showNotification('模式已存在或无效', 'warning', 1500);
}
});
}
bindCycleListEvents();
const customDark3Input = shadowRoot.querySelector('#customDark3Input');
if (customDark3Input) {
customDark3Input.addEventListener('change', () => {
let val = parseInt(customDark3Input.value);
if (isNaN(val)) val = 90;
val = Math.min(100, Math.max(0, val));
Util.setValue('customDark3', val);
StyleManager.apply(true);
Util.showNotification(`夜间强度已调整为 ${val}%`, 'info', 1500);
});
}
const tempDisableCheckbox = shadowRoot.querySelector('#tempDisableAllSiteModes');
const tempDisableStatusText = shadowRoot.querySelector('#tempDisableStatusText');
if (tempDisableCheckbox && tempDisableStatusText) {
tempDisableCheckbox.addEventListener('change', () => {
const newState = tempDisableCheckbox.checked;
Util.setValue('tempDisableAllSiteModes', newState);
tempDisableStatusText.textContent = newState ? '已关闭' : '已启用';
tempDisableStatusText.style.color = newState ? '#dc3545' : '#007bff';
StyleManager.apply(true);
refreshMenu();
Util.showNotification(newState ? '已关闭专用模式,使用全局设置' : '已恢复专用模式', 'success', 1500);
updateSettingsPanelUI(shadowRoot);
});
}
const forceSpecialCheckbox = shadowRoot.querySelector('#forceSpecialEnabled');
if (forceSpecialCheckbox) {
forceSpecialCheckbox.addEventListener('change', () => {
const newState = forceSpecialCheckbox.checked;
Util.setValue('forceSpecialModeEnabled', newState);
StyleManager.apply(true);
refreshMenu();
Util.showNotification(newState ? '已开启强制专用模式' : '已关闭强制专用模式', 'success', 1500);
updateSettingsPanelUI(shadowRoot);
});
}
const autoDisableSelect = shadowRoot.querySelector('#autoDisableTempModeSelect');
const autoDisableAlsoCheck = shadowRoot.querySelector('#autoDisableAlsoEnableForce');
if (autoDisableSelect && autoDisableAlsoCheck) {
autoDisableSelect.addEventListener('change', () => {
Util.setValue('autoDisableTempMode', autoDisableSelect.value);
});
autoDisableAlsoCheck.addEventListener('change', () => {
Util.setValue('autoDisableAlsoEnableForceSpecial', autoDisableAlsoCheck.checked);
});
}
// 性能模式开关
const perfCheckbox = shadowRoot.querySelector('#performanceMode');
if (perfCheckbox) {
perfCheckbox.addEventListener('change', () => {
Util.setValue('performanceMode', perfCheckbox.checked);
Guardian.restart();
if (perfCheckbox.checked) {
document.dispatchEvent(new Event('visibilitychange'));
} else {
document.dispatchEvent(new Event('visibilitychange'));
}
Util.showNotification(perfCheckbox.checked ? '已启用性能优化模式' : '已禁用性能优化模式', 'info', 1500);
});
}
shadowRoot.querySelector('#closeBtn')?.addEventListener('click', () => { hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null; });
shadowRoot.querySelector('#toggleMode')?.addEventListener('click', () => {
const cycleModesNow = Util.getValue('globalCycleModes');
if (!cycleModesNow || cycleModesNow.length === 0) {
Util.showNotification('请先在设置中添加模式到循环列表', 'info', 2000);
return;
}
switchMode();
});
shadowRoot.querySelector('#toggleSite')?.addEventListener('click', () => { toggleCurrentSite(); hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null; });
shadowRoot.querySelector('#toggleForceEnable')?.addEventListener('click', () => { toggleForceEnable(); hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null; });
shadowRoot.querySelector('#clearForcedList')?.addEventListener('click', () => { clearForcedEnableList(); });
shadowRoot.querySelector('#toggleBlacklist')?.addEventListener('click', () => { toggleBlacklist(); hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null; });
shadowRoot.querySelector('#clearBlacklist')?.addEventListener('click', () => { clearBlacklist(); });
shadowRoot.querySelector('#addCurrentToForceSpecial')?.addEventListener('click', () => { addCurrentToForceSpecial(); });
shadowRoot.querySelector('#clearAllForceSpecial')?.addEventListener('click', () => { clearForceSpecial(); });
shadowRoot.querySelector('#applySiteMode')?.addEventListener('click', () => {
let rawInput = shadowRoot.querySelector('#siteDomain')?.value.trim();
if (!rawInput) { alert('请输入域名或网址'); return; }
let host = (() => {
try { let url = new URL(rawInput.startsWith('http') ? rawInput : 'http://' + rawInput); return url.host; }
catch(e) { return rawInput.replace(/^https?:\/\//i, '').split('/')[0]; }
})();
if (!host) { alert('无法解析域名'); return; }
const mode = siteModeSelect.value;
if (mode === 'default') {
setSiteSpecificMode(host, null);
} else if (mode === 'softwarm') {
const type = siteSoftwarmContainer ? siteSoftwarmContainer.querySelector('input[name="siteSoftwarmType"]:checked').value : 'green';
const greenOp = siteSoftwarmContainer ? parseFloat(siteSoftwarmContainer.querySelector('#siteSoftwarmGreenSlider').value) : 0.3;
const warmOp = siteSoftwarmContainer ? parseFloat(siteSoftwarmContainer.querySelector('#siteSoftwarmWarmSlider').value) : 0.3;
setSiteSpecificMode(host, 'softwarm', { type, greenOpacity: greenOp, warmOpacity: warmOp });
} else if (mode === 'themecolor') {
const bg = Util.getValue('themecolorBgColor');
const link = Util.getValue('themecolorLinkColor');
setSiteSpecificMode(host, 'themecolor', { colors: { bg, link } });
} else {
setSiteSpecificMode(host, mode);
}
if (host === location.host) { StyleManager.apply(true); refreshMenu(); }
hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null;
});
shadowRoot.querySelector('#resetSiteMode')?.addEventListener('click', () => {
let rawInput = shadowRoot.querySelector('#siteDomain')?.value.trim();
if (!rawInput) { alert('请输入域名或网址'); return; }
let host = (() => {
try { let url = new URL(rawInput.startsWith('http') ? rawInput : 'http://' + rawInput); return url.host; }
catch(e) { return rawInput.replace(/^https?:\/\//i, '').split('/')[0]; }
})();
if (!host) { alert('无法解析域名'); return; }
setSiteSpecificMode(host, null);
if (host === location.host) { StyleManager.apply(true); refreshMenu(); }
hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null;
});
shadowRoot.querySelector('#saveSettings')?.addEventListener('click', () => {
Util.setValue('globalEnable', shadowRoot.querySelector('#globalEnable')?.checked ?? globalEnable);
Util.setValue('runDuringDay', shadowRoot.querySelector('#runDuringDay')?.checked ?? runDuringDay);
Util.setValue('darkAuto', shadowRoot.querySelector('#darkAuto')?.checked ?? darkAuto);
let newDark3 = shadowRoot.querySelector('#customDark3Input')?.value ?? customDark3;
Util.setValue('customDark3', newDark3);
Util.setValue('SHOW_NOTIFICATION', shadowRoot.querySelector('#showNotification')?.checked ?? showNotification);
Util.setValue('PRELOAD_ENABLED', shadowRoot.querySelector('#preloadEnabled')?.checked ?? preloadEnabled);
Util.setValue('INSTANT_MODE', shadowRoot.querySelector('#instantMode')?.checked ?? instantMode);
Util.setValue('FORCE_RECOVERY', shadowRoot.querySelector('#forceRecoveryToggle')?.checked ?? forceRecovery);
Util.setValue('AVOID_DARK_SITE', shadowRoot.querySelector('#avoidDarkSite')?.checked ?? avoidDarkSite);
let thresholdVal = parseInt(shadowRoot.querySelector('#brightnessThresholdInput')?.value, 10);
if (!isNaN(thresholdVal)) Util.setValue('BRIGHTNESS_THRESHOLD', thresholdVal);
Util.setValue('performanceMode', shadowRoot.querySelector('#performanceMode')?.checked ?? performanceMode);
StyleManager.apply(true);
refreshMenu();
Util.showNotification('✅ 设置已保存并应用', 'success', 2000);
});
shadowRoot.querySelector('#resetSettings')?.addEventListener('click', () => {
if (confirm('确定要恢复所有默认设置吗?')) {
for (let key in DEFAULT_CONFIG) Util.setValue(key, DEFAULT_CONFIG[key]);
Util.setValue('currentMode', 'none');
StyleManager.apply(true);
refreshMenu();
Util.showNotification('✅ 已恢复默认设置', 'success', 2000);
hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null;
}
});
shadowRoot.querySelector('#forceGC')?.addEventListener('click', () => {
if (window.gc) window.gc();
Util.clearCache();
Util.showNotification('内存已清理', 'success', 1500);
});
// resizeToggleBtn 等事件(略)
const resizeToggleBtn = shadowRoot.querySelector('#resizeToggleBtn');
const resizePanelDiv = shadowRoot.querySelector('#resizePanel');
if (resizeToggleBtn && resizePanelDiv) {
resizeToggleBtn.addEventListener('mousedown', (e) => e.stopPropagation());
resizeToggleBtn.addEventListener('touchstart', (e) => e.stopPropagation(), { passive: false });
resizeToggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (resizePanelDiv.style.display === 'none' || resizePanelDiv.style.display === '') {
resizePanelDiv.style.display = 'flex';
} else {
resizePanelDiv.style.display = 'none';
}
});
resizeToggleBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
if (resizePanelDiv.style.display === 'none' || resizePanelDiv.style.display === '') {
resizePanelDiv.style.display = 'flex';
} else {
resizePanelDiv.style.display = 'none';
}
}, { passive: false });
}
const widthSlider = shadowRoot.querySelector('#panelWidthSlider');
const widthVal = shadowRoot.querySelector('#widthVal');
const heightSlider = shadowRoot.querySelector('#panelHeightSlider');
const heightVal = shadowRoot.querySelector('#heightVal');
const resetSizeBtn = shadowRoot.querySelector('#resetSizeBtn');
const resetPosBtn = shadowRoot.querySelector('#resetPosBtn');
const stopSliderPropagation = (e) => { e.stopPropagation(); };
if (widthSlider) {
widthSlider.addEventListener('touchstart', stopSliderPropagation);
widthSlider.addEventListener('touchmove', stopSliderPropagation);
widthSlider.addEventListener('touchend', stopSliderPropagation);
widthSlider.addEventListener('input', (e) => {
let w = parseInt(e.target.value);
if (isNaN(w)) w = 400;
w = Math.min(1000, Math.max(300, w));
panelContainer.style.width = `${w}px`;
if (widthVal) widthVal.textContent = `${w}px`;
Util.setValue('panelWidth', w);
});
widthSlider.addEventListener('change', (e) => {
let w = parseInt(e.target.value);
if (isNaN(w)) w = 400;
w = Math.min(1000, Math.max(300, w));
Util.setValue('panelWidth', w);
});
}
if (heightSlider) {
heightSlider.addEventListener('touchstart', stopSliderPropagation);
heightSlider.addEventListener('touchmove', stopSliderPropagation);
heightSlider.addEventListener('touchend', stopSliderPropagation);
heightSlider.addEventListener('input', (e) => {
let h = parseInt(e.target.value);
if (isNaN(h)) h = 450;
h = Math.min(800, Math.max(300, h));
panelContainer.style.height = `${h}px`;
if (heightVal) heightVal.textContent = `${h}px`;
Util.setValue('panelHeight', h);
});
heightSlider.addEventListener('change', (e) => {
let h = parseInt(e.target.value);
if (isNaN(h)) h = 450;
h = Math.min(800, Math.max(300, h));
Util.setValue('panelHeight', h);
});
}
if (resetSizeBtn) {
resetSizeBtn.addEventListener('click', () => {
if (widthSlider) widthSlider.value = '400';
if (heightSlider) heightSlider.value = '450';
panelContainer.style.width = '400px';
panelContainer.style.height = '450px';
if (widthVal) widthVal.textContent = '400px';
if (heightVal) heightVal.textContent = '450px';
Util.setValue('panelWidth', 400);
Util.setValue('panelHeight', 450);
Util.showNotification('已重置尺寸为 400×450px', 'info', 1500);
});
}
if (resetPosBtn) {
resetPosBtn.addEventListener('click', () => {
panelContainer.style.left = '50%';
panelContainer.style.top = '50%';
panelContainer.style.transform = 'translate(-50%, -50%)';
Util.setValue('panelLeft', '50%');
Util.setValue('panelTop', '50%');
Util.setValue('panelTransform', 'translate(-50%, -50%)');
Util.showNotification('已重置位置到屏幕中央', 'info', 1500);
});
}
const dragHandle = shadowRoot.querySelector('.drag-handle');
if (dragHandle) {
let isDragging = false;
let startX, startY;
function onDragStart(e) {
if (e.target.closest('button') || e.target.closest('input') || e.target.closest('select') ||
e.target.closest('#resizeToggleBtn') || e.target.closest('#closeBtn')) {
return;
}
e.preventDefault();
const rect = panelContainer.getBoundingClientRect();
if (panelContainer.style.transform && panelContainer.style.transform !== 'none') {
const leftPx = rect.left;
const topPx = rect.top;
panelContainer.style.left = leftPx + 'px';
panelContainer.style.top = topPx + 'px';
panelContainer.style.transform = 'none';
Util.setValue('panelLeft', leftPx + 'px');
Util.setValue('panelTop', topPx + 'px');
Util.setValue('panelTransform', 'none');
}
const clientX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
const clientY = e.type === 'touchstart' ? e.touches[0].clientY : e.clientY;
const rect2 = panelContainer.getBoundingClientRect();
startX = clientX - rect2.left;
startY = clientY - rect2.top;
isDragging = true;
document.addEventListener('mousemove', onDragMove);
document.addEventListener('mouseup', onDragEnd);
document.addEventListener('touchmove', onDragMoveTouch, { passive: false });
document.addEventListener('touchend', onDragEndTouch);
dragHandle.style.cursor = 'grabbing';
}
function onDragMove(e) {
if (!isDragging) return;
e.preventDefault();
const clientX = e.clientX;
const clientY = e.clientY;
let left = clientX - startX;
let top = clientY - startY;
left = Math.max(0, left);
top = Math.max(0, top);
panelContainer.style.left = left + 'px';
panelContainer.style.top = top + 'px';
Util.setValue('panelLeft', left + 'px');
Util.setValue('panelTop', top + 'px');
Util.setValue('panelTransform', 'none');
}
function onDragMoveTouch(e) {
if (!isDragging) return;
e.preventDefault();
const touch = e.touches[0];
const clientX = touch.clientX;
const clientY = touch.clientY;
let left = clientX - startX;
let top = clientY - startY;
left = Math.max(0, left);
top = Math.max(0, top);
panelContainer.style.left = left + 'px';
panelContainer.style.top = top + 'px';
Util.setValue('panelLeft', left + 'px');
Util.setValue('panelTop', top + 'px');
Util.setValue('panelTransform', 'none');
}
function onDragEnd(e) {
if (isDragging) {
isDragging = false;
dragHandle.style.cursor = 'grab';
document.removeEventListener('mousemove', onDragMove);
document.removeEventListener('mouseup', onDragEnd);
document.removeEventListener('touchmove', onDragMoveTouch);
document.removeEventListener('touchend', onDragEndTouch);
}
}
function onDragEndTouch(e) { onDragEnd(e); }
dragHandle.addEventListener('mousedown', onDragStart);
dragHandle.addEventListener('touchstart', onDragStart, { passive: false });
dragHandle.addEventListener('selectstart', (e) => e.preventDefault());
}
loadingPanel.remove();
}, 0);
} catch(e) { console.error('设置面板打开失败:', e); alert('设置面板打开失败'); }
}
// ==================== 切换函数 ====================
function toggleGlobal() {
let current = Util.getValue('globalEnable');
Util.setValue('globalEnable', !current);
StyleManager.apply(true);
refreshMenu();
Util.showNotification(!current ? '已开启全局模式' : '已关闭全局模式', 'success', 1500);
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function toggleCurrentSite() {
let enableList = Util.getValue('enableList');
let host = location.host;
if (enableList.includes(host)) {
enableList = enableList.filter(domain => domain !== host);
Util.setValue('enableList', enableList);
StyleManager.apply(true);
Util.showNotification('已在当前网站禁用护眼模式', 'success', 1500);
} else {
enableList.push(host);
Util.setValue('enableList', enableList);
StyleManager.apply(true);
Util.showNotification('已在当前网站启用护眼模式', 'success', 1500);
}
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function toggleForceEnable() {
let host = location.host;
let list = getForcedEnableListObjects();
const exists = list.some(item => item.domain === host);
if (exists) {
removeFromForcedEnableList(host);
Util.showNotification('已取消强制启用当前网站', 'info', 1500);
} else {
addToForcedEnableList(host);
Util.showNotification('已强制启用当前网站', 'success', 1500);
}
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForcedEnableListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
function toggleBlacklist() {
let blacklist = Util.getValue('blacklist');
let host = location.host;
if (blacklist.includes(host)) {
blacklist = blacklist.filter(domain => domain !== host);
Util.setValue('blacklist', blacklist);
Util.showNotification('已将当前网站从黑名单中移除', 'success', 1500);
StyleManager.apply(true);
} else {
blacklist.push(host);
Util.setValue('blacklist', blacklist);
Util.showNotification('已将当前网站添加到黑名单', 'info', 1500);
StyleManager.apply(true);
}
refreshMenu();
if (currentSettingsShadow) {
refreshBlacklistUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
function clearBlacklist() {
if (confirm('确定要清空黑名单吗?')) {
Util.setValue('blacklist', []);
Util.showNotification('已清空黑名单', 'success', 1500);
refreshMenu();
if (currentSettingsShadow) {
refreshBlacklistUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
}
function addCurrentToForceSpecial() {
let host = location.host;
if (addToForceSpecialList(host)) {
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
} else {
Util.showNotification('当前网站已在强制专用模式列表中', 'info', 1500);
}
}
function clearForceSpecial() {
if (confirm('确定要清空强制专用模式列表吗?')) {
clearForceSpecialList();
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
}
function removeFromForceSpecial(domain) {
removeFromForceSpecialList(domain);
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
function renderListWithCurrentFirst(listObjects, currentHost) {
if (!listObjects || listObjects.length === 0) return '暂无网站
';
let currentItem = null;
let otherItems = [];
for (let item of listObjects) {
let domain = item.domain;
if (domain === currentHost) currentItem = item;
else otherItems.push(item);
}
otherItems.sort((a, b) => b.addedTime - a.addedTime);
let allItems = [];
if (currentItem) allItems.push(currentItem);
allItems.push(...otherItems);
let html = '';
allItems.forEach(item => {
let domain = item.domain;
html += `
${escapeHtml(domain)}
删除
`;
});
return html;
}
function escapeHtml(str) { return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }); }
function refreshForceSpecialListUI(shadowRoot) {
const rawList = Util.getValue('forceSpecialList') || [];
let listObjects = rawList.map(item => { if (typeof item === 'string') return { domain: item, addedTime: Date.now() }; return item; });
const container = shadowRoot.querySelector('#forceSpecialListContainer');
if (container) {
container.innerHTML = renderListWithCurrentFirst(listObjects, location.host);
container.querySelectorAll('.remove-item').forEach(btn => {
btn.addEventListener('click', (e) => { const site = btn.getAttribute('data-site'); if (site) removeFromForceSpecial(site); });
});
}
}
function refreshForcedEnableListUI(shadowRoot) {
const listObjects = getForcedEnableListObjects();
const container = shadowRoot.querySelector('#forcedEnableListContainer');
if (container) {
container.innerHTML = renderListWithCurrentFirst(listObjects, location.host);
container.querySelectorAll('.remove-item').forEach(btn => {
btn.addEventListener('click', (e) => {
const site = btn.getAttribute('data-site');
if (site) {
removeFromForcedEnableList(site);
refreshForcedEnableListUI(shadowRoot);
StyleManager.apply(true);
refreshMenu();
updateSettingsPanelUI(shadowRoot);
}
});
});
}
}
function refreshBlacklistUI(shadowRoot) {
const blacklist = Util.getValue('blacklist') || [];
const container = shadowRoot.querySelector('#blacklistItemsList');
if (container) {
if (blacklist.length === 0) container.innerHTML = '暂无黑名单网站
';
else {
let html = '';
blacklist.forEach(site => {
html += `
${escapeHtml(site)}
删除
`;
});
container.innerHTML = html;
container.querySelectorAll('.remove-blacklist-item').forEach(btn => {
btn.addEventListener('click', (e) => {
const site = btn.getAttribute('data-site');
if (site) {
removeBlacklistSite(site);
refreshBlacklistUI(shadowRoot);
const blacklistCountSpan = shadowRoot.querySelector('#blacklistCount');
if (blacklistCountSpan) blacklistCountSpan.textContent = Util.getValue('blacklist').length;
if (site === location.host) { refreshMenu(); updateSettingsPanelUI(shadowRoot); }
}
});
});
}
}
const blacklistCountSpan = shadowRoot.querySelector('#blacklistCount');
if (blacklistCountSpan) blacklistCountSpan.textContent = blacklist.length;
}
function addToForceSpecialList(domain) {
let list = Util.getValue('forceSpecialList') || [];
const exists = list.some(item => {
let d = typeof item === 'string' ? item : item.domain;
return d === domain;
});
if (exists) return false;
list.push({ domain: domain, addedTime: Date.now() });
Util.setValue('forceSpecialList', list);
Util.showNotification(`已将 ${domain} 添加到强制专用模式列表`, 'success', 1500);
return true;
}
function removeFromForceSpecialList(domain) {
let list = Util.getValue('forceSpecialList') || [];
let newList = list.filter(item => {
let d = typeof item === 'string' ? item : item.domain;
return d !== domain;
});
Util.setValue('forceSpecialList', newList);
Util.showNotification(`已从强制专用模式列表移除 ${domain}`, 'success', 1500);
}
function clearForceSpecialList() {
Util.setValue('forceSpecialList', []);
Util.showNotification('已清空强制专用模式列表', 'success', 1500);
}
function addToForcedEnableList(domain) {
let list = getForcedEnableListObjects();
const exists = list.some(item => item.domain === domain);
if (exists) return false;
list.push({ domain: domain, addedTime: Date.now() });
Util.setValue('forcedEnableList', list);
return true;
}
function removeFromForcedEnableList(domain) {
let list = getForcedEnableListObjects();
let newList = list.filter(item => item.domain !== domain);
Util.setValue('forcedEnableList', newList);
}
function removeBlacklistSite(domain) {
let blacklist = Util.getValue('blacklist') || [];
if (!blacklist.includes(domain)) return false;
blacklist = blacklist.filter(d => d !== domain);
Util.setValue('blacklist', blacklist);
Util.showNotification(`已将 ${domain} 从黑名单中移除`, 'success', 1500);
if (domain === location.host) {
StyleManager.apply(true);
refreshMenu();
}
return true;
}
function clearForcedEnableList() {
if (confirm('确定要清空强制启用列表吗?')) {
Util.setValue('forcedEnableList', []);
Util.showNotification('已清空强制启用列表', 'success', 1500);
refreshMenu();
if (currentSettingsShadow) {
refreshForcedEnableListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
}
// ==================== 快捷键 ====================
document.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'd' && (e.ctrlKey || e.metaKey) && e.shiftKey && !e.altKey) {
e.preventDefault();
const newState = toggleDarkReaderForCurrentSite();
if (getEffectiveMode() === 'darkreader') {
StyleManager.apply(true);
Util.showNotification(`🌓 深色模式 ${newState ? '已启用' : '已禁用'}`, 'success', 1500);
} else {
Util.showNotification('当前模式不是 🌓 深色模式,切换无效', 'info', 2000);
}
refreshMenu();
}
});
// ==================== 初始化 ====================
function init() {
try {
for (let key in DEFAULT_CONFIG) {
if (GM_getValue(key) === undefined) GM_setValue(key, DEFAULT_CONFIG[key]);
}
if (GM_getValue('currentMode') === undefined) GM_setValue('currentMode', 'none');
const commonModes = ['dark', 'soft', 'pure', 'eyecare'];
for (const mode of commonModes) {
if (!cachedCSS.has(mode)) {
const css = generateFullStyleCSS(mode);
if (css) cachedCSS.set(mode, css);
}
}
initMenu();
Util.startCleanup();
patchHistoryAPI();
bindPassiveRecovery();
if (!Util.isBlacklisted()) {
SeamlessLoader.init();
}
} catch(e) { console.error('脚本初始化失败:', e); }
}
init();
})();