// ==UserScript== // @name 夜间护眼助手 // @version 5.27.45 // @author 未名 // @description 智能暗色主题切换 · 多模式支持(含专用模式、豆沙绿/深暖色)· 黑名单/白名单/强制启用 · 闪烁抑制 · 亮度检测防误判 · 预加载/即时模式/强制恢复 · 可拖拽面板 · 完整主题 · 浓度滑块单独折叠 · 白天模式可自由加入/移除全局切换循环(设置面板全面修复) // @license MIT // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMSAxMi43OUE5IDkgMCAwIDEgMTEuMjEgMyA3IDcgMCAwIDAgMTUgOS4yOUE3IDcgMCAwIDEgMjEgMTIuNzl6Ii8+PC9zdmc+ // @match *://*/* // @noframes // @run-at document-start // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_deleteValue // @grant GM_listValues // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNOTMuNSA5NC42YzEwLjYgMCAyMC4zLTMuMyAyOC4yLTkgOC4zIDIyLjUtMzAuMiAzOC42LTU2IDM4LjYtMzIuNyAwLTU5LjMtMjUuOC01OS4zLTU3LjdzMjYuNi01Ny43IDU5LjMtNTcuN2gyLjJDNjAuMiA4LjMgNTEuMiAyMi44IDUxLjIgMzkuMmMwIDI1LjcgMjEuNCA0Ni42IDQ3LjggNDYuNnoiIGZpbGw9IiNmZmI1NzgiLz48cG9seWdvbiBwb2ludHM9IjY0LDQwIDY5LjI5LDU2LjcyIDg2LjgyLDU2LjU4IDcyLjU2LDY2Ljc4IDc4LjExLDgzLjQyIDY0LDczIDQ5Ljg5LDgzLjQyIDU1LjQ0LDY2Ljc4IDQxLjE4LDU2LjU4IDU4LjcxLDU2LjcyIiBmaWxsPSIjNDQ0Ii8+PC9zdmc+ // ==/UserScript== (function() { 'use strict'; // ==================== 全局变量 ==================== let greenOverlay = null; let warmOverlay = null; let filterStyle = null; let menuCommands = []; let navigationTimer = null; let styleIntegrityTimer = null; let currentSettingsHost = null; let currentSettingsShadow = null; // 样式缓存 let cachedCSS = new Map(); let earlyInjected = false; let preloadRetryCount = 0; const MAX_PRELOAD_RETRY = 30; let preloadTimer = null; // ==================== 配置定义 ==================== const CONFIG = { STYLE_ID: 'darkmode-master-style', ANIMATION_ID: 'darkmode-animations', FILTER_SVG_ID: 'eye-protect-filter-svg', FILTER_STYLE_ID: 'eye-protect-filter-style', CACHE_TTL: 5000, DEBOUNCE_DELAY: 200, CLEANUP_INTERVAL: 90000, MAX_CACHE_SIZE: 40, MEMORY_CHECK_INTERVAL: 30000, EARLY_APPLY_TIMEOUT: 300 }; const THEMES = Object.freeze({ classic: { 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" }, warm: { name: "🕯️ 暖光夜间", invert: 1, hue: 160, bright: 1, contrast: 0.95, saturate: 1.2, bg: "#1a1510" }, deep: { name: "🌌 深邃省电", invert: 1, hue: 180, bright: 0.9, contrast: 1.1, saturate: 0.7, bg: "#000" }, 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: [], FLICKER_SUPPRESS_LIST: [], REFRESH_ON_SAVE: true, THEME: "classic", AVOID_DARK_SITE: true, BRIGHTNESS_THRESHOLD: 70, FORCE_RECOVERY: true, SHOW_NOTIFICATION: true, PRELOAD_ENABLED: false, INSTANT_MODE: true, FLICKER_EXACT_MATCH: false, 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: ['dark'], globalGreenOpacity: 0.3, globalWarmOpacity: 0.3, currentMode: 'light', panelWidth: 720, panelHeight: null, panelLeft: '50%', panelTop: '50%', panelTransform: 'translate(-50%, -50%)', forceSpecialModeEnabled: false, forceSpecialList: [] }); const MODE_NAMES = { light: "☀️ 白天模式", dark: "🌙 夜间模式", classic: "⚫ 原版深色", soft: "🌿 柔和护眼", eyecare: "👁️ 护眼暗色", deep: "🌌 深邃省电", pure: "⚪ 纯净黑白", filter: "🎨 滤镜模式", green: "🌱 豆沙绿", warm: "🧡 深暖色" }; const VALID_MODES = ['light', 'dark', 'classic', 'soft', 'eyecare', 'deep', 'pure', 'filter', 'green', 'warm']; // ==================== 工具函数 ==================== const Util = (() => { const cache = new Map(); let cleanupTimer = null; class CacheItem { constructor(value, ttl = CONFIG.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(CONFIG.MAX_CACHE_SIZE / 2); for (let i = halfSize; i < keys.length; i++) cache.delete(keys[i]); } } function startCleanup() { if (cleanupTimer) clearInterval(cleanupTimer); cleanupTimer = setInterval(cleanupCache, CONFIG.CLEANUP_INTERVAL); } function stopCleanup() { if (cleanupTimer) { clearInterval(cleanupTimer); cleanupTimer = null; } } function migrateFlickerList() { try { let raw = GM_getValue('FLICKER_SUPPRESS_LIST'); if (!raw) return; if (Array.isArray(raw) && raw.length > 0 && typeof raw[0] === 'string') { const newList = raw.map(domain => ({ domain, addedTime: Date.now() })); GM_setValue('FLICKER_SUPPRESS_LIST', newList); } } catch(e) {} } 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() { 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; for (let el of document.body.children) { const bright = getBgBrightness(el); if (bright !== null) return bright; for (let child of el.children) { const b = getBgBrightness(child); if (b !== null) return b; } } 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; } } 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), 10); 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); }, isFlickerSuppressSite() { const host = this.getSafeHost(); const list = this.getValue('FLICKER_SUPPRESS_LIST') || []; const exactMatch = this.getValue('FLICKER_EXACT_MATCH'); for (const item of list) { let site = typeof item === 'string' ? item : item.domain; if (!site) continue; if (exactMatch) { if (host === site) return true; } else { if (host.includes(site) || site.includes(host)) return true; } } return false; }, 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 = CONFIG.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, migrateFlickerList, detectPageBrightness, dispose() { stopCleanup(); this.clearCache(); this.removeElementById(CONFIG.STYLE_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; if (styleIntegrityTimer) clearInterval(styleIntegrityTimer); if (preloadTimer) clearTimeout(preloadTimer); } }; })(); 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; } } `); Util.migrateFlickerList(); // ==================== 强制专用模式相关函数 ==================== 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 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 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 ''; let cacheKey = mode; if (mode === 'dark') cacheKey = `dark_${Util.getValue('customDark3')}`; if (cachedCSS.has(cacheKey)) return cachedCSS.get(cacheKey); 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 === 'green' || mode === 'warm') { css = null; } else if (THEMES[mode]) { css = generateFullStyleCSS(mode); } if (css !== null) { cachedCSS.set(cacheKey, css); if (cachedCSS.size > 20) { const firstKey = cachedCSS.keys().next().value; cachedCSS.delete(firstKey); } } return css; } function applyModeByMode(mode) { if (mode === 'green' || mode === 'warm') { removeFilterMode(); Util.removeElementById(CONFIG.STYLE_ID); applyOverlay(mode); return; } if (mode === 'filter') { removeOverlays(); Util.removeElementById(CONFIG.STYLE_ID); applyFilterMode(); return; } removeOverlays(); removeFilterMode(); const css = getCSSForMode(mode); if (css === undefined || css === null || css === '') { Util.removeElementById(CONFIG.STYLE_ID); } else { Util.addStyle(CONFIG.STYLE_ID, css); } } function removeOverlays() { if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove(); if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove(); greenOverlay = null; warmOverlay = null; } function applyOverlay(mode) { removeOverlays(); if (mode === 'green') { let opacity = getGreenOpacityForSite(); if (!greenOverlay || !document.body.contains(greenOverlay)) { 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;transition:background 0.2s,opacity 0.2s;mix-blend-mode:multiply;background-color:#C7EDCC;opacity:${opacity};`; document.documentElement.appendChild(greenOverlay); } else { greenOverlay.style.opacity = opacity; } } else if (mode === 'warm') { let opacity = getWarmOpacityForSite(); if (!warmOverlay || !document.body.contains(warmOverlay)) { 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;transition:background 0.2s,opacity 0.2s;mix-blend-mode:multiply;background-color:#CFB988;opacity:${opacity};`; document.documentElement.appendChild(warmOverlay); } else { warmOverlay.style.opacity = opacity; } } } function removeFilterMode() { if (filterStyle && filterStyle.parentNode) filterStyle.remove(); let svg = document.getElementById(CONFIG.FILTER_SVG_ID); if (svg) svg.remove(); filterStyle = null; } function applyFilterMode() { removeFilterMode(); let excludeList = Util.getValue('filterExcludeList'); if (excludeList.includes(location.host)) return; 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; filterStyle = document.createElement('style'); filterStyle.id = CONFIG.FILTER_STYLE_ID; filterStyle.textContent = css; document.head.appendChild(filterStyle); } // ==================== 预加载功能 ==================== function preGenerateCommonCSS() { const commonModes = ['dark', 'classic', 'soft', 'eyecare', 'deep', 'pure']; for (const mode of commonModes) { if (!cachedCSS.has(mode)) { const css = generateFullStyleCSS(mode); if (css) cachedCSS.set(mode, css); } } } function preloadTheme() { if (earlyInjected) return true; if (Util.isBlacklisted() || Util.isFlickerSuppressSite()) { earlyInjected = true; return true; } const mode = getEffectiveMode(); if (mode === 'green' || mode === 'warm' || mode === 'filter') { earlyInjected = true; return true; } const css = getCSSForMode(mode); if (css && css !== '') { if (document.head) { Util.addStyle(CONFIG.STYLE_ID, css); earlyInjected = true; return true; } else { return false; } } earlyInjected = true; return true; } function startPreload() { if (preloadTimer) clearTimeout(preloadTimer); const checkAndInject = () => { if (preloadTheme()) return; preloadRetryCount++; if (preloadRetryCount < MAX_PRELOAD_RETRY) { preloadTimer = setTimeout(checkAndInject, 10); } else { earlyInjected = true; } }; checkAndInject(); } function earlyApplyStyles() { if (earlyInjected) return; const preloadEnabled = Util.getValue('PRELOAD_ENABLED'); const instantMode = Util.getValue('INSTANT_MODE'); if (!preloadEnabled && !instantMode) return; startPreload(); } // ==================== 模式管理辅助函数 ==================== function getCurrentGlobalMode() { let mode = Util.getValue('currentMode'); if (!mode || !MODE_NAMES[mode]) mode = 'light'; return mode; } function getSiteSpecificMode(domain, ignoreTempDisable = false) { 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 getGreenOpacityForSite(domain) { let host = domain || location.host; try { let siteModesStr = Util.getValue('siteSpecificModes'); let siteModes = JSON.parse(siteModesStr); let entry = siteModes[host]; if (entry && typeof entry === 'object' && entry.mode === 'green' && typeof entry.opacity === 'number') return entry.opacity; return Util.getValue('globalGreenOpacity'); } catch(e) { return 0.3; } } function getWarmOpacityForSite(domain) { let host = domain || location.host; try { let siteModesStr = Util.getValue('siteSpecificModes'); let siteModes = JSON.parse(siteModesStr); let entry = siteModes[host]; if (entry && typeof entry === 'object' && entry.mode === 'warm' && typeof entry.opacity === 'number') return entry.opacity; return Util.getValue('globalWarmOpacity'); } catch(e) { return 0.3; } } function setSiteSpecificMode(domain, mode, opacity = null) { 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]; } else if (mode === 'green' || mode === 'warm') { let existing = siteModes[host]; let currentOpacity = (existing && typeof existing === 'object' && existing.opacity) ? existing.opacity : (mode === 'green' ? 0.3 : 0.3); if (opacity !== null) currentOpacity = Math.min(0.95, Math.max(0, opacity)); siteModes[host] = { mode: mode, opacity: currentOpacity }; } else { siteModes[host] = mode; } Util.setValue('siteSpecificModes', JSON.stringify(siteModes)); Util.showNotification(`已为 ${host} 设置专用模式: ${MODE_NAMES[mode] || mode}`, 'success', 1500); } catch(e) { console.error('设置网站专用模式出错:', e); } } function shouldApplyGlobalMode() { let globalEnable = Util.getValue('globalEnable'); let enableList = Util.getValue('enableList'); let forcedList = Util.getValue('forcedEnableList'); let host = location.host; let basicEnabled = false; if (forcedList.includes(host)) { basicEnabled = (globalEnable || enableList.includes(host)); } else { basicEnabled = (globalEnable || enableList.includes(host)); } if (!basicEnabled) return false; const inWhitelist = enableList.includes(host); const avoidDarkSite = Util.getValue('AVOID_DARK_SITE'); if (avoidDarkSite && !inWhitelist) { try { const brightness = Util.detectPageBrightness(); const threshold = Util.getValue('BRIGHTNESS_THRESHOLD'); if (brightness < threshold) return false; } catch(e) {} } return true; } function getEffectiveMode() { const host = location.host; if (Util.isBlacklisted()) 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()) { return getCurrentGlobalMode(); } return 'light'; } // ==================== 页面返回修复 ==================== function patchHistoryAPI() { try { const originalPushState = history.pushState; const originalReplaceState = history.replaceState; const forceReapply = () => { if (navigationTimer) clearTimeout(navigationTimer); navigationTimer = setTimeout(() => { if (!Util.isBlacklisted()) { StyleManager.apply(true); ObserverManager.restart(); } navigationTimer = null; }, 50); }; history.pushState = function() { originalPushState.apply(this, arguments); forceReapply(); }; history.replaceState = function() { originalReplaceState.apply(this, arguments); forceReapply(); }; window.addEventListener('popstate', forceReapply); window.addEventListener('pageshow', (event) => { if (event.persisted) forceReapply(); }); window.addEventListener('beforeunload', () => { if (history.pushState !== originalPushState) history.pushState = originalPushState; if (history.replaceState !== originalReplaceState) history.replaceState = originalReplaceState; }, { once: true }); } catch(e) {} } function initVisibilityRecovery() { const handleVisibilityChange = () => { if (!document.hidden && Util.getValue('FORCE_RECOVERY')) { const styleEl = document.getElementById(CONFIG.STYLE_ID); const mode = getEffectiveMode(); if (mode !== 'light' && mode !== 'green' && mode !== 'warm' && mode !== 'filter') { if (!styleEl) StyleManager.apply(true); } else if (mode === 'green' || mode === 'warm') { const overlay = mode === 'green' ? greenOverlay : warmOverlay; if (!overlay || !document.body.contains(overlay)) StyleManager.apply(true); } else if (mode === 'filter') { if (!filterStyle || !document.body.contains(filterStyle)) StyleManager.apply(true); } } }; document.addEventListener('visibilitychange', handleVisibilityChange); window.addEventListener('pageshow', (event) => { if (event.persisted && Util.getValue('FORCE_RECOVERY')) { StyleManager.apply(true); } }); if (styleIntegrityTimer) clearInterval(styleIntegrityTimer); styleIntegrityTimer = setInterval(() => { if (!Util.getValue('FORCE_RECOVERY')) return; if (Util.isBlacklisted()) return; const styleEl = document.getElementById(CONFIG.STYLE_ID); const mode = getEffectiveMode(); if (mode !== 'light' && mode !== 'green' && mode !== 'warm' && mode !== 'filter') { if (!styleEl) StyleManager.apply(true); } else if (mode === 'green' || mode === 'warm') { const overlay = mode === 'green' ? greenOverlay : warmOverlay; if (!overlay || !document.body.contains(overlay)) StyleManager.apply(true); } else if (mode === 'filter') { if (!filterStyle || !document.body.contains(filterStyle)) StyleManager.apply(true); } }, 1500); } // ==================== ObserverManager ==================== const ObserverManager = (() => { let mutationObserver = null, styleObserver = null, isActive = false, debounceTimer = null, isSuppressMode = false, keepAliveTimer = null, lastApplyTime = Date.now(); let observerNode = null, isDisposed = false; function handleMutations(mutations) { if (isDisposed) return; lastApplyTime = Date.now(); if (debounceTimer) clearTimeout(debounceTimer); const isSuppress = Util.isFlickerSuppressSite(); const delay = CONFIG.DEBOUNCE_DELAY; const nodeThreshold = isSuppress ? 10 : 3; debounceTimer = setTimeout(() => { if (isDisposed) return; const significantChange = mutations.some(m => m.type === 'childList' && m.addedNodes.length > nodeThreshold); if (significantChange) { if (isSuppress) { pause(); setTimeout(() => resume(), 50); } StyleManager.apply(true); } debounceTimer = null; }, delay); } function startKeepAlive() { if (isDisposed) return; if (keepAliveTimer) clearInterval(keepAliveTimer); keepAliveTimer = setInterval(() => { if (isDisposed) return; if (Date.now() - lastApplyTime > 30000) { StyleManager.apply(true); lastApplyTime = Date.now(); } }, 15000); } function stopKeepAlive() { if (keepAliveTimer) { clearInterval(keepAliveTimer); keepAliveTimer = null; } } return { start() { if (isActive || isDisposed) return; if (!document.body) { setTimeout(() => this.start(), 100); return; } isActive = true; isDisposed = false; isSuppressMode = Util.isFlickerSuppressSite(); lastApplyTime = Date.now(); observerNode = document.body; mutationObserver = new MutationObserver(handleMutations); try { mutationObserver.observe(observerNode, { childList: true, subtree: true, attributes: false, characterData: false }); } catch(e) {} if (Util.getValue('FORCE_RECOVERY')) { styleObserver = new MutationObserver((mutations) => { if (isSuppressMode || isDisposed) return; for (let m of mutations) { if (m.removedNodes.length) { for (let node of m.removedNodes) { if (node.nodeType === 1 && (node.id === CONFIG.STYLE_ID || node.id === CONFIG.FILTER_STYLE_ID || node.id === 'eye-protect-green-overlay' || node.id === 'eye-protect-warm-overlay')) { setTimeout(() => { if (!isDisposed) StyleManager.apply(true); }, 20); lastApplyTime = Date.now(); return; } } } } }); try { styleObserver.observe(document.head || document.documentElement, { childList: true, subtree: false }); } catch(e) {} } startKeepAlive(); }, stop() { isActive = false; stopKeepAlive(); if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; } if (mutationObserver) { try { mutationObserver.disconnect(); mutationObserver = null; } catch(e) {} } if (styleObserver) { try { styleObserver.disconnect(); styleObserver = null; } catch(e) {} } observerNode = null; isSuppressMode = false; }, pause() { if (mutationObserver) try { mutationObserver.disconnect(); } catch(e) {} if (styleObserver) try { styleObserver.disconnect(); } catch(e) {} stopKeepAlive(); }, resume() { if (!isActive || isDisposed) return; if (!observerNode) observerNode = document.body; startKeepAlive(); if (mutationObserver && observerNode) try { mutationObserver.observe(observerNode, { childList: true, subtree: true, attributes: false, characterData: false }); } catch(e) {} if (styleObserver && Util.getValue('FORCE_RECOVERY')) try { styleObserver.observe(document.head || document.documentElement, { childList: true, subtree: false }); } catch(e) {} }, restart() { this.stop(); setTimeout(() => this.start(), 50); }, dispose() { isDisposed = true; this.stop(); } }; })(); // ==================== 样式管理器(完全移除缓存比较) ==================== const StyleManager = (() => { function apply(force = false) { const mode = getEffectiveMode(); if (mode === 'light') { cleanAllEffects(); } else { applyModeByMode(mode); } } function cleanAllEffects() { Util.removeElementById(CONFIG.STYLE_ID); removeOverlays(); removeFilterMode(); } return { apply }; })(); // ==================== 切换模式核心函数(支持白天模式可移除) ==================== function getNextModeInCycle(currentMode, cycleModes) { let modes = (cycleModes && Array.isArray(cycleModes) && cycleModes.length > 0) ? [...cycleModes] : ['dark']; const idx = modes.indexOf(currentMode); if (idx !== -1) { const nextIdx = (idx + 1) % modes.length; return modes[nextIdx]; } else { if (currentMode === 'light') { return modes[0]; } if (modes.includes('light')) return 'light'; return modes[0]; } } function switchMode() { let oldMode = Util.getValue('currentMode') || 'light'; let cycleModes = Util.getValue('globalCycleModes'); if (!Array.isArray(cycleModes) || cycleModes.length === 0) { cycleModes = ['dark']; Util.setValue('globalCycleModes', cycleModes); } const nextMode = getNextModeInCycle(oldMode, cycleModes); Util.setValue('currentMode', nextMode); handleAutoDisableTempModeOnModeChange(nextMode, oldMode); StyleManager.apply(true); setTimeout(() => { refreshMenu(); Util.showNotification(`已切换到 ${getModeName(nextMode)}`, 'success', 1500); scheduleUIUpdate(); }, 0); } // ==================== 菜单与设置面板 ==================== 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 === 'green') return '🌱 豆沙绿'; if (typeof mode === 'object' && mode.mode === 'warm') 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(); const msg = alsoEnableForce ? '已自动关闭专用模式并开启强制专用模式' : '已自动关闭专用模式'; Util.showNotification(`${msg}(进入 ${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) { updateGlobalConcentrationPanelVisibility(currentSettingsShadow); refreshGlobalConcentrationPanel(currentSettingsShadow); updateSettingsPanelUI(currentSettingsShadow); } pendingUIUpdate = false; }); } function updateGlobalConcentrationPanelVisibility(shadowRoot) { if (!shadowRoot) return; const currentGlobalMode = getCurrentGlobalMode(); const panel = shadowRoot.querySelector('#globalConcentrationPanel'); if (panel) { if (currentGlobalMode === 'green' || currentGlobalMode === 'warm') { panel.style.display = 'block'; } else { panel.style.display = 'none'; } } } function refreshGlobalConcentrationPanel(shadowRoot) { if (!shadowRoot) return; const currentGlobalMode = getCurrentGlobalMode(); const globalGreenPanel = shadowRoot.querySelector('#globalGreenPanel'); const globalWarmPanel = shadowRoot.querySelector('#globalWarmPanel'); if (globalGreenPanel) globalGreenPanel.style.display = currentGlobalMode === 'green' ? 'block' : 'none'; if (globalWarmPanel) globalWarmPanel.style.display = currentGlobalMode === 'warm' ? 'block' : 'none'; const globalGreenSlider = shadowRoot.querySelector('#globalGreenSlider'); const globalGreenSpan = shadowRoot.querySelector('#globalGreenValue'); if (globalGreenSlider && globalGreenSpan) { const val = Util.getValue('globalGreenOpacity'); globalGreenSlider.value = val; globalGreenSpan.textContent = Math.round(val * 100); } const globalWarmSlider = shadowRoot.querySelector('#globalWarmSlider'); const globalWarmSpan = shadowRoot.querySelector('#globalWarmValue'); if (globalWarmSlider && globalWarmSpan) { const val = Util.getValue('globalWarmOpacity'); globalWarmSlider.value = val; globalWarmSpan.textContent = Math.round(val * 100); } } 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') || ['dark']; const nextMode = getNextModeInCycle(currentGlobalMode, cycleModes); const nextModeName = MODE_NAMES[nextMode] || '白天模式'; const toggleBtn = shadowRoot.querySelector('#toggleMode'); if (toggleBtn) toggleBtn.textContent = `切换 ${nextModeName}`; 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'; } } refreshGlobalConcentrationPanel(shadowRoot); } function toggleGlobal() { let current = Util.getValue('globalEnable'); Util.setValue('globalEnable', !current); StyleManager.apply(true); refreshMenu(); Util.showNotification(!current ? '已开启全局模式' : '已关闭全局模式', 'success', 1500); if (currentSettingsShadow) { updateGlobalConcentrationPanelVisibility(currentSettingsShadow); refreshGlobalConcentrationPanel(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 forcedList = Util.getValue('forcedEnableList'); let host = location.host; if (forcedList.includes(host)) { forcedList = forcedList.filter(domain => domain !== host); Util.showNotification('已取消强制启用当前网站', 'info', 1500); } else { forcedList.push(host); Util.showNotification('已强制启用当前网站', 'success', 1500); } Util.setValue('forcedEnableList', forcedList); StyleManager.apply(true); refreshMenu(); if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow); } function toggleTempDisableAllSiteModes() { let current = Util.getValue('tempDisableAllSiteModes'); let newState = !current; Util.setValue('tempDisableAllSiteModes', newState); StyleManager.apply(true); refreshMenu(); Util.showNotification(newState ? '已关闭专用模式,使用全局设置' : '已恢复专用模式', 'success', 1500); if (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) updateSettingsPanelUI(currentSettingsShadow); } function clearBlacklist() { if (confirm('确定要清空黑名单吗?这将移除所有在黑名单中的网站。')) { Util.setValue('blacklist', []); Util.showNotification('已清空黑名单', 'success', 1500); refreshMenu(); if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow); } } function clearForcedList() { if (confirm('确定要清空强制启用列表吗?')) { Util.setValue('forcedEnableList', []); Util.showNotification('已清空强制启用列表', 'success', 1500); StyleManager.apply(true); refreshMenu(); if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow); } } function addCurrentToFlickerSuppress() { let host = location.host; let list = Util.getValue('FLICKER_SUPPRESS_LIST') || []; const exists = list.some(item => { let domain = typeof item === 'string' ? item : item.domain; return domain === host; }); if (exists) { Util.showNotification('当前网站已在闪烁抑制列表中', 'info', 1500); return; } list.push({ domain: host, addedTime: Date.now() }); Util.setValue('FLICKER_SUPPRESS_LIST', list); Util.showNotification(`已将 ${host} 添加到闪烁抑制列表`, 'success', 1500); refreshMenu(); if (currentSettingsShadow) { refreshFlickerListUI(currentSettingsShadow); } } function removeFromFlickerSuppress(domain) { let list = Util.getValue('FLICKER_SUPPRESS_LIST') || []; let newList = list.filter(item => { let d = typeof item === 'string' ? item : item.domain; return d !== domain; }); Util.setValue('FLICKER_SUPPRESS_LIST', newList); Util.showNotification(`已从闪烁抑制列表移除 ${domain}`, 'success', 1500); refreshMenu(); if (currentSettingsShadow) { refreshFlickerListUI(currentSettingsShadow); } } function clearAllFlickerSuppress() { if (confirm('确定要清空整个闪烁抑制列表吗?')) { Util.setValue('FLICKER_SUPPRESS_LIST', []); Util.showNotification('已清空闪烁抑制列表', 'success', 1500); refreshMenu(); if (currentSettingsShadow) { refreshFlickerListUI(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 removeFromForceSpecial(domain) { removeFromForceSpecialList(domain); StyleManager.apply(true); refreshMenu(); if (currentSettingsShadow) { refreshForceSpecialListUI(currentSettingsShadow); updateSettingsPanelUI(currentSettingsShadow); } } function clearForceSpecial() { if (confirm('确定要清空强制专用模式列表吗?')) { clearForceSpecialList(); 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 refreshFlickerListUI(shadowRoot) { const rawList = Util.getValue('FLICKER_SUPPRESS_LIST') || []; let listObjects = rawList.map(item => { if (typeof item === 'string') return { domain: item, addedTime: Date.now() }; return item; }); const container = shadowRoot.querySelector('#flickerSuppressListContainer'); 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) removeFromFlickerSuppress(site); }); }); } } 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 createCustomSelect(container, options, currentValue, onSelect) { const selectWrapper = document.createElement('div'); selectWrapper.style.position = 'relative'; selectWrapper.style.width = '100%'; const trigger = document.createElement('div'); trigger.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border:1px solid #e0e0e0;border-radius:6px;background:#fff;color:#333;font-size:15px;cursor:pointer;'; const currentOption = options.find(opt => opt.value === currentValue) || options[0]; trigger.innerHTML = `${currentOption.label}`; const dropdown = document.createElement('div'); dropdown.style.cssText = 'position:absolute;top:100%;left:0;right:0;background:#fff;border:1px solid #e0e0e0;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.15);z-index:1000;display:none;max-height:350px;overflow-y:auto;margin-top:4px;'; const gridContainer = document.createElement('div'); gridContainer.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:10px;'; options.forEach(opt => { const item = document.createElement('div'); item.textContent = opt.label; item.style.cssText = 'padding:6px 8px;border-radius:6px;cursor:pointer;font-size:15px;transition:background 0.2s;border:1px solid #e9ecef;background:#fff;margin-bottom:2px;'; item.addEventListener('mouseenter', () => { item.style.background = '#f0f0f0'; item.style.borderColor = '#ced4da'; }); item.addEventListener('mouseleave', () => { item.style.background = '#fff'; item.style.borderColor = '#e9ecef'; }); item.addEventListener('click', () => { trigger.innerHTML = `${opt.label}`; dropdown.style.display = 'none'; onSelect(opt.value); }); gridContainer.appendChild(item); }); dropdown.appendChild(gridContainer); trigger.addEventListener('click', (e) => { e.stopPropagation(); dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none'; }); document.addEventListener('click', (e) => { if (!selectWrapper.contains(e.target)) dropdown.style.display = 'none'; }); selectWrapper.appendChild(trigger); selectWrapper.appendChild(dropdown); container.appendChild(selectWrapper); return { setValue: (val) => { const opt = options.find(o => o.value === val); if (opt) trigger.innerHTML = `${opt.label}`; } }; } // ==================== 设置面板 ==================== 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 forcedList = Util.getValue('forcedEnableList'); let cycleModes = Util.getValue('globalCycleModes'); if (!Array.isArray(cycleModes)) cycleModes = ['dark']; const validModes = ['light', 'dark', 'classic', 'soft', 'eyecare', 'deep', 'pure', 'filter', 'green', 'warm']; cycleModes = cycleModes.filter(m => validModes.includes(m)); if (cycleModes.length === 0) cycleModes = ['dark']; let host = location.host; let siteEnabled = enableList.includes(host); let isForced = forcedList.includes(host); let isBlacklisted = blacklist.includes(host); let blacklistCount = blacklist.length; let siteSpecific = getSiteSpecificMode(); let siteSpecificMode = siteSpecific ? (typeof siteSpecific === 'string' ? siteSpecific : siteSpecific.mode) : null; let tempDisableAll = Util.getValue('tempDisableAllSiteModes'); let greenOpacityValue = getGreenOpacityForSite(); let warmOpacityValue = getWarmOpacityForSite(); let globalGreenOpacity = Util.getValue('globalGreenOpacity'); let globalWarmOpacity = Util.getValue('globalWarmOpacity'); 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 flickerSuppressListRaw = Util.getValue('FLICKER_SUPPRESS_LIST') || []; 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') || 720; let panelHeight = Util.getValue('panelHeight') || null; 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 flickerListObjects = flickerSuppressListRaw.map(item => { if (typeof item === 'string') return { domain: item, addedTime: Date.now() }; return item; }); let forceSpecialListObjects = forceSpecialListRaw.map(item => { if (typeof item === 'string') return { domain: item, addedTime: Date.now() }; return item; }); // 构建循环列表HTML(支持白天模式) 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: 'classic', name: '⚫ 原版深色' }, { id: 'soft', name: '🌿 柔和护眼' }, { id: 'eyecare', name: '👁️ 护眼暗色' }, { id: 'deep', name: '🌌 深邃省电' }, { id: 'pure', name: '⚪ 纯净黑白' }, { id: 'filter', name: '🎨 滤镜模式' }, { id: 'green', name: '🌱 豆沙绿' }, { id: 'warm', 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: 'classic', name: '⚫ 原版深色' }, { id: 'soft', name: '🌿 柔和护眼' }, { id: 'eyecare', name: '👁️ 护眼暗色' }, { id: 'deep', name: '🌌 深邃省电' }, { id: 'pure', name: '⚪ 纯净黑白' }, { id: 'filter', name: '🎨 滤镜模式' }, { id: 'green', name: '🌱 豆沙绿' }, { id: 'warm', name: '🧡 深暖色' } ]; let autoModeOptionsHtml = autoModeOptions.map(opt => ``).join(''); const actualMode = getEffectiveMode(); const hasSpecial = isSpecialModeActive(); let currentModeDisplayText = getSiteModeName(actualMode); if (hasSpecial) 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: width 0.2s, height 0.2s; `; 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); } .priority-badge { display: inline-block; background: #e9ecef; border-radius: 12px; padding: 2px 8px; font-size: 11px; font-weight: normal; margin-left: 8px; } .status-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; } .status-active { background: #d4edda; color: #155724; } .status-inactive { background: #f8d7da; color: #721c24; } .drag-handle { cursor: move; user-select: none; } .resize-collapse { cursor: pointer; } .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 { display: none; margin-top: 8px; } `; shadowRoot.appendChild(style); const globalConcentrationDisplay = (currentMode === 'green' || currentMode === 'warm') ? 'block' : 'none'; const modeDescHtml = `
📖 模式说明
`; panelContainer.innerHTML = `
🛡️ 夜间护眼助手 v5.27.45
📐

📱 当前状态

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

🌐 网站专用模式设置

输入域名或完整网址(如 google.com),然后选择模式,即可为该网站设置专用模式
当前浓度:${Math.round(greenOpacityValue * 100)}%
当前浓度:${Math.round(warmOpacityValue * 100)}%
${tempDisableAll ? '已关闭' : '已启用'}
${modeDescHtml}

🎨 全局浓度调节

豆沙绿浓度:
当前浓度:${Math.round(globalGreenOpacity * 100)}%
深暖色浓度:
当前浓度:${Math.round(globalWarmOpacity * 100)}%
当前全局模式为豆沙绿或深暖色时,可在此调节浓度,实时生效。

⚡ 强制启用管理

强制启用列表中的网站需要满足全局开关或白名单才会生效(黑名单优先级最高)。
强制启用状态:
${host}:${isForced ? '已强制启用' : '未强制启用'}
当前生效条件:
• 全局开关:${globalEnable ? '✅ 已开启' : '❌ 已关闭'}
• 白名单:${siteEnabled ? '✅ 已启用' : '❌ 未启用'}
强制启用生效: ${isForced && (globalEnable || siteEnabled) ? '✅ 是' : '❌ 否'}

⛔ 黑名单管理

黑名单中的网站将始终禁用护眼模式,优先级最高(高于全局开关、白名单、强制启用)。
当前黑名单状态:${blacklistCount} 个网站
${host}:${isBlacklisted ? '⛔ 已禁用' : '❌ 未禁用'}
📌 优先级顺序(从高到低)
1. 黑名单:当前网站${isBlacklisted ? '⛔ 已禁用' : '✅ 未禁用'}(命中后直接禁用,不再检查后续)
2. 强制启用:${isForced ? '⚡ 已强制启用' : '🚫 未强制启用'}(需要全局或白名单开启)
3. 全局开关:${globalEnable ? '✅ 已开启' : '❌ 已关闭'}
4. 白名单:${siteEnabled ? '✅ 已启用' : '❌ 未启用'}
当前网站最终状态:${isBlacklisted ? '⛔ 禁用(黑名单)' : ((isForced && (globalEnable || siteEnabled)) ? '✅ 启用(强制启用生效)' : ((globalEnable || siteEnabled) ? '✅ 启用' : '❌ 禁用'))}
${blacklistCount > 0 ? `
当前黑名单网站:
${blacklist.map(site => `
${site}
`).join('')}
` : ''}

🔄 全局循环模式配置

自定义全局切换时循环的模式顺序(可自由添加/移除白天模式,列表顺序即切换顺序)。
${buildCycleListHtml()}

⚙️ 专用模式高级控制

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

⚡ 闪烁抑制列表

添加到列表中的网站将启用保守模式,避免页面闪烁。列表按添加时间倒序排列,当前网站始终置顶。

⚙️ 全局设置 📖 说明

🌍 全局开关
⚡ 即时模式
☀️ 白天开启
📦 预加载模式
🌙 暗色站防误判
🔄 强制恢复模式
🔔 显示通知
🌓 跟随系统
🌙 夜间强度(反色模式)
%
仅夜间模式生效,调整反色滤镜强度
🔆 亮度检测阈值
% ▼ 展开滑块
低于此值的网站视为暗色站,自动跳过暗色模式(白名单网站不受影响)
`; // 初始化自定义下拉组件 const customSelectContainer = shadowRoot.querySelector('#customSelectContainer'); const modeOptions = [ { value: 'default', label: '默认(跟随全局设置)' }, { value: 'light', label: '☀️ 白天模式' }, { value: 'dark', label: '🌙 夜间模式' }, { value: 'filter', label: '🎨 滤镜模式' }, { value: 'classic', label: '⚫ 原版深色' }, { value: 'soft', label: '🌿 柔和护眼' }, { value: 'eyecare', label: '👁️ 护眼暗色' }, { value: 'deep', label: '🌌 深邃省电' }, { value: 'pure', label: '⚪ 纯净黑白' }, { value: 'green', label: '🌱 豆沙绿' }, { value: 'warm', label: '🧡 深暖色' } ]; let currentSiteModeValue = siteSpecificMode || 'default'; const customSelect = createCustomSelect(customSelectContainer, modeOptions, currentSiteModeValue, (value) => { currentSiteModeValue = value; const greenContainer = shadowRoot.querySelector('#greenOpacityContainer'); const warmContainer = shadowRoot.querySelector('#warmOpacityContainer'); if (greenContainer) greenContainer.style.display = value === 'green' ? 'block' : 'none'; if (warmContainer) warmContainer.style.display = value === 'warm' ? 'block' : 'none'; if (value === 'green') { const greenContent = greenContainer?.querySelector('.green-concentration-content'); const greenToggle = greenContainer?.querySelector('.green-concentration-toggle'); if (greenContent && greenToggle) { greenContent.style.display = 'none'; greenToggle.textContent = '▶'; } } else if (value === 'warm') { const warmContent = warmContainer?.querySelector('.warm-concentration-content'); const warmToggle = warmContainer?.querySelector('.warm-concentration-toggle'); if (warmContent && warmToggle) { warmContent.style.display = 'none'; warmToggle.textContent = '▶'; } } }); refreshFlickerListUI(shadowRoot); refreshForceSpecialListUI(shadowRoot); refreshGlobalConcentrationPanel(shadowRoot); // 折叠说明事件绑定 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) { headerDiv.addEventListener('click', (e) => { e.stopPropagation(); if (descContent.style.display === 'none') { descContent.style.display = 'block'; toggleSpan.textContent = '▲'; } else { descContent.style.display = 'none'; toggleSpan.textContent = '▼'; } }); descContent.style.display = 'none'; toggleSpan.textContent = '▼'; } } const globalDescToggle = shadowRoot.querySelector('.global-settings-desc-toggle'); const globalDesc = shadowRoot.querySelector('.global-settings-desc'); if (globalDescToggle && globalDesc) { globalDescToggle.addEventListener('click', () => { if (globalDesc.style.display === 'none') { globalDesc.style.display = 'block'; globalDescToggle.textContent = '📖 说明 ▲'; } else { globalDesc.style.display = 'none'; globalDescToggle.textContent = '📖 说明'; } }); globalDesc.style.display = 'none'; } // 亮度检测阈值交互 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 (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 (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); const newModes = cycleModes.filter((_, i) => i !== idx); 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(); // 全局浓度面板控制及折叠功能(只折叠滑块容器) function updateGlobalConcentrationPanelVisibility() { const currentGlobalMode = getCurrentGlobalMode(); const panel = shadowRoot.querySelector('#globalConcentrationPanel'); if (panel) { if (currentGlobalMode === 'green' || currentGlobalMode === 'warm') { panel.style.display = 'block'; } else { panel.style.display = 'none'; } } } updateGlobalConcentrationPanelVisibility(); const observerModeChange = new MutationObserver(() => updateGlobalConcentrationPanelVisibility()); observerModeChange.observe(shadowRoot, { subtree: true, attributes: true, attributeFilter: ['data-currentmode'] }); // 全局浓度折叠按钮 (只折叠滑块容器) const globalToggle = shadowRoot.querySelector('.global-concentration-toggle'); const globalGreenSliderContainer = shadowRoot.querySelector('#globalGreenSliderContainer'); const globalWarmSliderContainer = shadowRoot.querySelector('#globalWarmSliderContainer'); if (globalToggle && globalGreenSliderContainer && globalWarmSliderContainer) { globalGreenSliderContainer.style.display = 'none'; globalWarmSliderContainer.style.display = 'none'; globalToggle.textContent = '▶'; globalToggle.addEventListener('click', () => { const currentGlobalMode = getCurrentGlobalMode(); if (currentGlobalMode === 'green') { if (globalGreenSliderContainer.style.display === 'none') { globalGreenSliderContainer.style.display = 'block'; globalToggle.textContent = '▼'; } else { globalGreenSliderContainer.style.display = 'none'; globalToggle.textContent = '▶'; } } else if (currentGlobalMode === 'warm') { if (globalWarmSliderContainer.style.display === 'none') { globalWarmSliderContainer.style.display = 'block'; globalToggle.textContent = '▼'; } else { globalWarmSliderContainer.style.display = 'none'; globalToggle.textContent = '▶'; } } }); } // 网站专用浓度折叠按钮 const greenToggle = shadowRoot.querySelector('.green-concentration-toggle'); const greenContent = shadowRoot.querySelector('.green-concentration-content'); if (greenToggle && greenContent) { greenToggle.addEventListener('click', (e) => { e.stopPropagation(); if (greenContent.style.display === 'none') { greenContent.style.display = 'block'; greenToggle.textContent = '▼'; } else { greenContent.style.display = 'none'; greenToggle.textContent = '▶'; } }); } const warmToggle = shadowRoot.querySelector('.warm-concentration-toggle'); const warmContent = shadowRoot.querySelector('.warm-concentration-content'); if (warmToggle && warmContent) { warmToggle.addEventListener('click', (e) => { e.stopPropagation(); if (warmContent.style.display === 'none') { warmContent.style.display = 'block'; warmToggle.textContent = '▼'; } else { warmContent.style.display = 'none'; warmToggle.textContent = '▶'; } }); } // 豆沙绿/深暖色浓度滑块(网站专用) const greenOpacitySlider = shadowRoot.querySelector('#greenOpacity'); const greenOpacitySpan = shadowRoot.querySelector('#greenOpacityValue'); if (greenOpacitySlider && greenOpacitySpan) { greenOpacitySlider.addEventListener('input', (e) => { let val = parseFloat(e.target.value); greenOpacitySpan.textContent = Math.round(val * 100); }); } const warmOpacitySlider = shadowRoot.querySelector('#warmOpacity'); const warmOpacitySpan = shadowRoot.querySelector('#warmOpacityValue'); if (warmOpacitySlider && warmOpacitySpan) { warmOpacitySlider.addEventListener('input', (e) => { let val = parseFloat(e.target.value); warmOpacitySpan.textContent = Math.round(val * 100); }); } // 全局浓度滑块 const globalGreenSlider = shadowRoot.querySelector('#globalGreenSlider'); const globalGreenSpan = shadowRoot.querySelector('#globalGreenValue'); if (globalGreenSlider && globalGreenSpan) { globalGreenSlider.addEventListener('input', (e) => { let val = parseFloat(e.target.value); globalGreenSpan.textContent = Math.round(val * 100); Util.setValue('globalGreenOpacity', val); StyleManager.apply(true); }); } const globalWarmSlider = shadowRoot.querySelector('#globalWarmSlider'); const globalWarmSpan = shadowRoot.querySelector('#globalWarmValue'); if (globalWarmSlider && globalWarmSpan) { globalWarmSlider.addEventListener('input', (e) => { let val = parseFloat(e.target.value); globalWarmSpan.textContent = Math.round(val * 100); Util.setValue('globalWarmOpacity', val); StyleManager.apply(true); }); } // 夜间强度 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); }); } // 按钮事件 shadowRoot.querySelector('#closeBtn')?.addEventListener('click', () => { hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null; }); shadowRoot.querySelector('#toggleMode')?.addEventListener('click', () => { 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', () => { clearForcedList(); }); shadowRoot.querySelector('#toggleBlacklist')?.addEventListener('click', () => { toggleBlacklist(); hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null; }); shadowRoot.querySelector('#clearBlacklist')?.addEventListener('click', () => { clearBlacklist(); }); shadowRoot.querySelector('#addCurrentToFlicker')?.addEventListener('click', () => { addCurrentToFlickerSuppress(); }); shadowRoot.querySelector('#clearAllFlicker')?.addEventListener('click', () => { clearAllFlickerSuppress(); }); 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 hostname = (() => { try { let url = new URL(rawInput.startsWith('http') ? rawInput : 'http://' + rawInput); return url.hostname; } catch(e) { return rawInput.replace(/^https?:\/\//i, '').split('/')[0]; } })(); if (!hostname) { alert('无法解析域名'); return; } let mode = currentSiteModeValue; let greenOp = greenOpacitySlider ? parseFloat(greenOpacitySlider.value) : null; let warmOp = warmOpacitySlider ? parseFloat(warmOpacitySlider.value) : null; if (mode === 'default') setSiteSpecificMode(hostname, null); else if (mode === 'green') setSiteSpecificMode(hostname, 'green', greenOp); else if (mode === 'warm') setSiteSpecificMode(hostname, 'warm', warmOp); else setSiteSpecificMode(hostname, mode); if (hostname === 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 hostname = (() => { try { let url = new URL(rawInput.startsWith('http') ? rawInput : 'http://' + rawInput); return url.hostname; } catch(e) { return rawInput.replace(/^https?:\/\//i, '').split('/')[0]; } })(); if (!hostname) { alert('无法解析域名'); return; } setSiteSpecificMode(hostname, null); if (hostname === 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('FLICKER_EXACT_MATCH', shadowRoot.querySelector('#flickerExactMatch')?.checked ?? false); 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); let globalGreen = globalGreenSlider ? parseFloat(globalGreenSlider.value) : globalGreenOpacity; let globalWarm = globalWarmSlider ? parseFloat(globalWarmSlider.value) : globalWarmOpacity; Util.setValue('globalGreenOpacity', globalGreen); Util.setValue('globalWarmOpacity', globalWarm); Util.setValue('autoDisableTempMode', autoDisableSelect ? autoDisableSelect.value : ''); Util.setValue('autoDisableAlsoEnableForceSpecial', autoDisableAlsoCheck ? autoDisableAlsoCheck.checked : false); Util.setValue('forceSpecialModeEnabled', forceSpecialCheckbox ? forceSpecialCheckbox.checked : false); StyleManager.apply(true); refreshMenu(); Util.showNotification('✅ 设置已保存', 'success', 2000); if (Util.getValue('REFRESH_ON_SAVE')) location.reload(); else { hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null; } }); shadowRoot.querySelector('#resetSettings')?.addEventListener('click', () => { if (confirm('确定要恢复所有默认设置吗?')) { for (let key in DEFAULT_CONFIG) Util.setValue(key, DEFAULT_CONFIG[key]); Util.setValue('currentMode', 'light'); StyleManager.apply(true); refreshMenu(); Util.showNotification('✅ 已恢复默认设置', 'success', 2000); if (Util.getValue('REFRESH_ON_SAVE')) location.reload(); else { hostDiv.remove(); currentSettingsHost = null; currentSettingsShadow = null; } } }); shadowRoot.querySelector('#forceGC')?.addEventListener('click', () => { if (window.gc) window.gc(); Util.clearCache(); Util.showNotification('内存已清理', 'success', 1500); }); // ==================== 拖拽移动功能 ==================== const dragHandle = shadowRoot.querySelector('.drag-handle'); let isDragging = false; let startMouseX = 0, startMouseY = 0; let startLeft = 0, startTop = 0; const onDragMove = (clientX, clientY) => { if (!isDragging) return; let deltaX = clientX - startMouseX; let deltaY = clientY - startMouseY; let newLeft = startLeft + deltaX; let newTop = startTop + deltaY; newLeft = Math.min(window.innerWidth - panelContainer.offsetWidth, Math.max(0, newLeft)); newTop = Math.min(window.innerHeight - panelContainer.offsetHeight, Math.max(0, newTop)); panelContainer.style.left = `${newLeft}px`; panelContainer.style.top = `${newTop}px`; panelContainer.style.transform = 'none'; }; const onMouseMove = (e) => onDragMove(e.clientX, e.clientY); const onTouchMove = (e) => { e.preventDefault(); if (e.touches.length) onDragMove(e.touches[0].clientX, e.touches[0].clientY); }; const onDragEnd = () => { if (isDragging) { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onDragEnd); document.removeEventListener('touchmove', onTouchMove); document.removeEventListener('touchend', onDragEnd); document.body.style.userSelect = ''; document.body.style.overflow = ''; const left = panelContainer.style.left; const top = panelContainer.style.top; Util.setValue('panelLeft', left); Util.setValue('panelTop', top); Util.setValue('panelTransform', 'none'); } }; const onDragStart = (clientX, clientY) => { if (isDragging) return; isDragging = true; startMouseX = clientX; startMouseY = clientY; const rect = panelContainer.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; document.body.style.userSelect = 'none'; document.body.style.overflow = 'hidden'; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onDragEnd); document.addEventListener('touchmove', onTouchMove, { passive: false }); document.addEventListener('touchend', onDragEnd); }; dragHandle.addEventListener('mousedown', (e) => { if (e.target.closest('#resizeToggleBtn') || e.target.closest('#closeBtn')) return; e.preventDefault(); onDragStart(e.clientX, e.clientY); }); dragHandle.addEventListener('touchstart', (e) => { if (e.target.closest('#resizeToggleBtn') || e.target.closest('#closeBtn')) return; e.preventDefault(); if (e.touches.length) onDragStart(e.touches[0].clientX, e.touches[0].clientY); }, { passive: false }); // ==================== 面板尺寸调整 ==================== const resizeToggleBtn = shadowRoot.querySelector('#resizeToggleBtn'); const resizePanelDiv = shadowRoot.querySelector('#resizePanel'); if (resizeToggleBtn && resizePanelDiv) { resizeToggleBtn.addEventListener('click', () => { resizePanelDiv.style.display = resizePanelDiv.style.display === 'none' ? 'flex' : 'none'; }); } 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 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 = 720; w = Math.min(1000, Math.max(400, 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 = 720; w = Math.min(1000, Math.max(400, 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 = 600; h = Math.min(800, Math.max(400, 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 = 600; h = Math.min(800, Math.max(400, h)); Util.setValue('panelHeight', h); }); } if (resetSizeBtn) { resetSizeBtn.addEventListener('click', () => { if (widthSlider) widthSlider.value = '720'; if (heightSlider) heightSlider.value = '600'; panelContainer.style.width = '720px'; panelContainer.style.height = ''; if (widthVal) widthVal.textContent = '720px'; if (heightVal) heightVal.textContent = '600px'; Util.setValue('panelWidth', 720); Util.setValue('panelHeight', null); Util.setValue('panelLeft', '50%'); Util.setValue('panelTop', '50%'); Util.setValue('panelTransform', 'translate(-50%, -50%)'); panelContainer.style.left = '50%'; panelContainer.style.top = '50%'; panelContainer.style.transform = 'translate(-50%, -50%)'; }); } loadingPanel.remove(); }, 0); } catch(e) { console.error('设置面板打开失败:', e); alert('设置面板打开失败'); } } // ==================== 菜单栏逻辑 ==================== 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 = ''; if (hasSpecial) { modeDisplay = `${getSiteModeName(effectiveMode)} [专用]`; } else { modeDisplay = getSiteModeName(currentMode); } menuCommands.push(GM_registerMenuCommand(modeDisplay, () => { if (hasSpecial) { showSettings(); } else { switchMode(); } })); menuCommands.push(GM_registerMenuCommand(globalEnable ? '🌍 全局: 开启 (点击关闭)' : '🌍 全局: 关闭 (点击开启)', () => { toggleGlobal(); })); menuCommands.push(GM_registerMenuCommand(siteEnabled ? '✅ 本站: 启用 (点击禁用)' : '❌ 本站: 禁用 (点击启用)', () => { toggleCurrentSite(); })); menuCommands.push(GM_registerMenuCommand('⚙️ 设置面板', () => { showSettings(); })); console.log('夜间护眼助手菜单已注册'); } catch(e) { console.error('菜单注册失败:', e); } } // ==================== 初始化 ==================== function init() { try { console.log('夜间护眼助手 v5.27.45 启动'); 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', 'light'); preGenerateCommonCSS(); const preloadEnabled = Util.getValue('PRELOAD_ENABLED'); const instantMode = Util.getValue('INSTANT_MODE'); if (preloadEnabled || instantMode) { earlyApplyStyles(); } initMenu(); Util.startCleanup(); initVisibilityRecovery(); if (Util.isBlacklisted()) return; const applyNow = () => { StyleManager.apply(true); ObserverManager.start(); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => setTimeout(applyNow, 50)); } else { setTimeout(applyNow, 50); } patchHistoryAPI(); window.addEventListener('beforeunload', () => { ObserverManager.dispose(); Util.dispose(); if (navigationTimer) clearTimeout(navigationTimer); if (preloadTimer) clearTimeout(preloadTimer); }, { once: true }); if (navigator.deviceMemory && navigator.deviceMemory < 4) { CONFIG.CACHE_TTL = 2000; CONFIG.MAX_CACHE_SIZE = 15; CONFIG.DEBOUNCE_DELAY = 400; CONFIG.MEMORY_CHECK_INTERVAL = 15000; } } catch(e) { console.error('脚本初始化失败:', e); } } init(); })();