// ==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 = `
🌙
${message}
`; 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 += `
${name}
`; }); 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 => ``).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 => ``).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 = `
📖 模式说明
`; 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 = `
🛡️ 夜间护眼助手 v6.18.6
📐

📱 当前状态

当前模式:${currentModeDisplayText}
当前网站:${host}
全局开关:${globalEnable ? '✅ 已开启' : '❌ 已关闭'}
白名单状态:${siteEnabled ? '✅ 已启用' : '❌ 未启用'}
黑名单状态:${isBlacklisted ? '⛔ 已禁用' : '❌ 未禁用'}
强制启用:${isForced ? '⚡ 已强制启用' : '🚫 未强制启用'}
网站专用模式:${siteSpecificMode ? getSiteModeName(siteSpecificMode) : '默认'} ${tempDisableAll ? '(已关闭)' : ''}
🌓 深色模式状态:${darkReaderEnabled ? '✅ 已启用' : '❌ 已禁用'}

🌐 网站专用模式设置

输入域名或完整网址(如 google.com),然后选择模式,即可为该网站设置专用模式
🍃 柔暖双色设置
浓度调节
🌈 护眼主题配色:
${THEME_PRESETS.map(preset => ``).join('')}
点击预设立即切换当前网站的护眼主题配色。
${tempDisableAll ? '已关闭' : '已启用'}
${modeDescHtml}

🎨 全局护眼主题配色

当前全局模式为“🌈 护眼主题”,点击预设按钮切换所有网站(未设置专用模式)的配色。
快速预设:
${THEME_PRESETS.map(preset => ``).join('')}

🍃 全局柔暖双色设置

当前全局模式为“🍃 柔暖双色”,选择底色类型并调节浓度。
浓度调节

🔄 全局循环模式配置

自定义全局切换时循环的模式顺序
${buildCycleListHtml()}

⚙️ 专用模式高级控制

自动关闭专用模式:
🔗 同时开启强制专用模式
选择某个模式后,切换到该模式时会自动关闭“全局专用总开关”${autoDisableAlsoEnableForce ? ',并同时开启下方的“强制专用模式”总开关' : ''},离开时自动恢复。
🔒 强制专用模式
开启后,列表中的网站将强制使用其专用模式(优先级高于专用模式开关,但低于黑名单,且不受“全局专用总开关”影响)。
📋 强制专用模式列表

🛡️ 权限与过滤

⚡ 强制启用管理
⛔ 黑名单管理

⚙️ 全局设置与性能

📖 说明
🌍 全局开关
⚡ 即时模式
☀️ 白天开启
📦 预加载模式
🌙 暗色站防误判
🔄 强制恢复模式
🔔 显示通知
🌓 跟随系统
⚡ 性能优化模式
🌙 夜间强度(反色模式)
%
仅夜间模式生效
🔆 亮度检测阈值
% ▼ 展开滑块
`; // ===== 折叠功能 ===== 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 => ``).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(); })();