// ==UserScript== // @name 夜间护眼助手 // @version 5.36.11 // @author 未名 + 护眼主题扩展 // @description 智能暗色主题切换 · 多模式支持 · 黑名单/白名单/强制启用 · 闪烁抑制 · 亮度检测防误判 · 超强预加载/即时模式 · 多重强制恢复(后退/前进无闪烁)· 可拖拽面板 · 完整主题 · 浓度滑块单独折叠 · 白天模式自由加入/移除循环 · 极速无闪烁注入 · 专用模式下拉框原生样式 · 模式说明完整 · 模式排序优化 · 🍃柔暖双色浓度滑块实时生效(无需点击应用) · 浓度0%完全透明 · 类型切换立即生效 · 设置面板布局优化 · 专用模式与全局专用总开关间距调整为20px · 增强搜索引擎兼容与bfcache返回恢复 · 修复专用模式受全局/白名单控制问题 // @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,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMSAxMi43OUE5IDkgMCAwIDEgMTEuMjEgMyA3IDcgMCAwIDAgMTUgOS4yOUE3IDcgMCAwIDEgMjEgMTIuNzl6Ii8+PC9zdmc+ // @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 recoveryObserver = null; let isRecovering = false; let lastAppliedMode = null; let currentSettingsHost = null; let currentSettingsShadow = null; let headObserver = null; let immediateApplyDone = false; let bfCacheRestoring = false; let cachedCSS = new Map(); let earlyInjected = false; let preloadRetryCount = 0; const MAX_PRELOAD_RETRY = 30; let preloadTimer = null; // 强效恢复守护相关 let ensureAppliedTimer = null; let ensureAppliedRetries = 0; const MAX_ENSURE_RETRIES = 20; const ENSURE_RETRY_INTERVAL = 150; // 额外强制恢复:使用 requestAnimationFrame 循环 let rafRecoveryId = null; let lastRecoveryCheck = 0; const RECOVERY_RAF_INTERVAL = 500; // ==================== 护眼主题模式相关变量 ==================== let customModeObserver = null; let customModeActive = false; let customStyleEl = null; 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' } ]; 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 setCurrentThemeColors(bgColor, linkColor) { const host = location.host; try { let siteModesStr = Util.getValue('siteSpecificModes'); let siteModes = JSON.parse(siteModesStr); siteModes[host] = { mode: 'themecolor', colors: { bg: bgColor, link: linkColor } }; Util.setValue('siteSpecificModes', JSON.stringify(siteModes)); } catch(e) {} } 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); } customStyleEl.textContent = css; } function scanAndTagForThemeMode() { if (!customModeActive) return; const elements = document.querySelectorAll('*:not([data-theme-scanned])'); elements.forEach(el => { el.setAttribute('data-theme-scanned', 'true'); if (el.matches('video, img, canvas, svg, [class*="player"], .icon, .fa')) return; if (el.tagName === 'A') { el.setAttribute('data-theme-link', 'true'); } if (el.matches('DIV#gb-main, DIV.url.clearfix, DIV.nav-bar-v2-fixed, DIV.se-page-hd-content')) { el.setAttribute('data-theme-bg', 'true'); return; } const style = window.getComputedStyle(el); const bgColor = style.backgroundColor; const rgb = bgColor.match(/\d+/g); if (rgb && rgb.length >= 3) { const [r, g, b] = rgb.map(Number); if (r > 220 && g > 220 && b > 220) { el.setAttribute('data-theme-bg', 'true'); } } }); } function startThemeModeObserver() { if (customModeObserver) customModeObserver.disconnect(); customModeObserver = new MutationObserver(() => { if (customModeActive) scanAndTagForThemeMode(); }); if (document.body) { customModeObserver.observe(document.body, { childList: true, subtree: true }); } else { const waitForBody = setInterval(() => { if (document.body) { clearInterval(waitForBody); customModeObserver.observe(document.body, { childList: true, subtree: true }); } }, 50); } scanAndTagForThemeMode(); } 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() { removeOverlays(); removeFilterMode(); Util.removeElementById(CONFIG.STYLE_ID); const { bg, link } = getCurrentThemeColors(); updateThemeCSSVars(bg, link); customModeActive = true; if (!customModeObserver) { startThemeModeObserver(); } else { scanAndTagForThemeMode(); } } function updateThemeColors(bgColor, linkColor, saveToSite = true) { if (!customModeActive) return; updateThemeCSSVars(bgColor, linkColor); if (saveToSite) { setCurrentThemeColors(bgColor, linkColor); } else { Util.setValue('themecolorBgColor', bgColor); Util.setValue('themecolorLinkColor', linkColor); } } // ==================== 柔暖双色模式(重写,确保实时生效) ==================== let softwarmActive = false; 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() { removeFilterMode(); Util.removeElementById(CONFIG.STYLE_ID); stopThemeMode(); 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 setSoftwarmTypeForSite(type, opacityGreen = null, opacityWarm = null) { const host = location.host; try { let siteModesStr = Util.getValue('siteSpecificModes'); let siteModes = JSON.parse(siteModesStr); let existing = siteModes[host] || {}; siteModes[host] = { mode: 'softwarm', type: type, greenOpacity: opacityGreen !== null ? opacityGreen : (existing.greenOpacity !== undefined ? existing.greenOpacity : 0.3), warmOpacity: opacityWarm !== null ? opacityWarm : (existing.warmOpacity !== undefined ? existing.warmOpacity : 0.3) }; Util.setValue('siteSpecificModes', JSON.stringify(siteModes)); } catch(e) {} } 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 updateSoftwarmOpacityAndSave(opacity, type, isGlobal = false) { if (type === 'green') { if (isGlobal) { Util.setValue('softwarmGlobalGreenOpacity', opacity); } else { const 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 = opacity; Util.setValue('siteSpecificModes', JSON.stringify(siteModes)); } catch(e) {} } } else { if (isGlobal) { Util.setValue('softwarmGlobalWarmOpacity', opacity); } else { const 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 = opacity; Util.setValue('siteSpecificModes', JSON.stringify(siteModes)); } catch(e) {} } } if (softwarmActive) { if (type === 'green') { if (greenOverlay) { if (opacity > 0) greenOverlay.style.opacity = opacity; else { greenOverlay.remove(); greenOverlay = null; } } else if (opacity > 0) { updateSoftwarmOverlay(); } } else { if (warmOverlay) { if (opacity > 0) warmOverlay.style.opacity = opacity; else { warmOverlay.remove(); warmOverlay = null; } } else if (opacity > 0) { updateSoftwarmOverlay(); } } } } function stopSoftwarmMode() { softwarmActive = false; if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove(); if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove(); greenOverlay = null; warmOverlay = 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, RECOVERY_INTERVAL: 500, RECOVERY_DEBOUNCE: 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: [], FLICKER_SUPPRESS_LIST: [], REFRESH_ON_SAVE: true, THEME: "dark", 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: ['light', 'dark', 'eyecare', 'softwarm', 'themecolor', 'filter', 'soft', 'pure'], globalGreenOpacity: 0.3, globalWarmOpacity: 0.3, currentMode: 'light', panelWidth: 720, panelHeight: null, panelLeft: '50%', panelTop: '50%', panelTransform: 'translate(-50%, -50%)', forceSpecialModeEnabled: false, forceSpecialList: [], themecolorBgColor: '#C7EDCC', themecolorLinkColor: '#2E7D32', softwarmGlobalType: 'green', softwarmGlobalGreenOpacity: 0.3, softwarmGlobalWarmOpacity: 0.3 }); const MODE_NAMES = { light: "☀️ 白天模式", dark: "🌙 夜间模式", soft: "🕯️ 柔和护眼", eyecare: "👁️ 护眼暗色", pure: "⚪ 纯净黑白", filter: "🎨 滤镜模式", softwarm: "🍃 柔暖双色", themecolor: "🌈 护眼主题" }; const VALID_MODES = ['light', 'dark', 'soft', 'eyecare', 'pure', 'filter', 'softwarm', 'themecolor']; // ==================== 工具函数 ==================== 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(keys.length / 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 (recoveryObserver) recoveryObserver.disconnect(); if (headObserver) headObserver.disconnect(); if (preloadTimer) clearTimeout(preloadTimer); if (ensureAppliedTimer) clearTimeout(ensureAppliedTimer); if (rafRecoveryId) cancelAnimationFrame(rafRecoveryId); stopThemeMode(); stopSoftwarmMode(); } }; })(); 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 === 'softwarm' || mode === 'themecolor') { 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 === 'softwarm') { removeFilterMode(); Util.removeElementById(CONFIG.STYLE_ID); stopThemeMode(); applySoftwarmMode(); return; } if (mode === 'themecolor') { removeOverlays(); removeFilterMode(); Util.removeElementById(CONFIG.STYLE_ID); stopSoftwarmMode(); applyThemeMode(); return; } if (mode === 'filter') { removeOverlays(); Util.removeElementById(CONFIG.STYLE_ID); stopThemeMode(); stopSoftwarmMode(); applyFilterMode(); return; } removeOverlays(); removeFilterMode(); stopThemeMode(); stopSoftwarmMode(); 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 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', 'soft', 'pure', 'eyecare']; for (const mode of commonModes) { if (!cachedCSS.has(mode)) { const css = generateFullStyleCSS(mode); if (css) cachedCSS.set(mode, css); } } } function immediateStyleInjection() { if (immediateApplyDone) return; if (Util.isBlacklisted() || Util.isFlickerSuppressSite()) { immediateApplyDone = true; return; } const mode = getEffectiveMode(); if (mode === 'light') { immediateApplyDone = true; return; } if (mode === 'softwarm') { if (document.documentElement) { applySoftwarmMode(); immediateApplyDone = true; } else { const waitForRoot = setInterval(() => { if (document.documentElement) { clearInterval(waitForRoot); applySoftwarmMode(); immediateApplyDone = true; } }, 5); setTimeout(() => { clearInterval(waitForRoot); immediateApplyDone = true; }, 500); } return; } if (mode === 'filter') { if (document.head) { applyFilterMode(); immediateApplyDone = true; } else { if (!headObserver) { headObserver = new MutationObserver(() => { if (document.head) { headObserver.disconnect(); applyFilterMode(); immediateApplyDone = true; } }); headObserver.observe(document.documentElement, { childList: true, subtree: true }); } } return; } if (mode === 'themecolor') { if (document.head) { applyThemeMode(); immediateApplyDone = true; } else { if (!headObserver) { headObserver = new MutationObserver(() => { if (document.head) { headObserver.disconnect(); applyThemeMode(); immediateApplyDone = true; } }); headObserver.observe(document.documentElement, { childList: true, subtree: true }); } } return; } const css = getCSSForMode(mode); if (css && css !== '') { if (document.head) { Util.addStyle(CONFIG.STYLE_ID, css); immediateApplyDone = true; } else { if (!headObserver) { headObserver = new MutationObserver(() => { if (document.head) { headObserver.disconnect(); Util.addStyle(CONFIG.STYLE_ID, css); immediateApplyDone = true; } }); headObserver.observe(document.documentElement, { childList: true, subtree: true }); } } } else { immediateApplyDone = true; } } function startPreload() { if (preloadTimer) clearTimeout(preloadTimer); const checkAndInject = () => { if (earlyInjected) return; if (Util.isBlacklisted() || Util.isFlickerSuppressSite()) { earlyInjected = true; return; } const mode = getEffectiveMode(); if (mode === 'softwarm' || mode === 'filter' || mode === 'themecolor') { earlyInjected = true; return; } const css = getCSSForMode(mode); if (css && css !== '') { if (document.head) { Util.addStyle(CONFIG.STYLE_ID, css); earlyInjected = true; 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 setSiteSpecificMode(domain, mode, extra = 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 === '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) }; } else if (mode === 'themecolor') { let colors = extra && extra.colors ? extra.colors : { bg: '#C7EDCC', link: '#2E7D32' }; siteModes[host] = { mode: 'themecolor', colors: colors }; } 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 isBasicEnabled() { const host = location.host; const globalEnable = Util.getValue('globalEnable'); const enableList = Util.getValue('enableList'); return globalEnable || enableList.includes(host); } // 全局模式专用:包含亮度检测等附加条件 function shouldApplyGlobalMode() { const host = location.host; let globalEnable = Util.getValue('globalEnable'); let enableList = Util.getValue('enableList'); let forcedList = Util.getValue('forcedEnableList'); 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 && !forcedList.includes(host)) { 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'; // 专用模式必须满足基础启用条件(全局开启或白名单包含本站) 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()) { return getCurrentGlobalMode(); } return 'light'; } // ==================== 强效恢复守护 ==================== function isStyleCorrectlyApplied(mode) { if (mode === 'light') return true; if (mode === 'softwarm') { const type = getSoftwarmTypeForSite(); const opacity = (type === 'green') ? getSoftwarmGreenOpacityForSite() : getSoftwarmWarmOpacityForSite(); if (opacity <= 0) return true; if (type === 'green') { return greenOverlay && document.documentElement.contains(greenOverlay) && greenOverlay.style.opacity === String(opacity); } else { return warmOverlay && document.documentElement.contains(warmOverlay) && warmOverlay.style.opacity === String(opacity); } } if (mode === 'filter') { return filterStyle && document.head.contains(filterStyle) && filterStyle.textContent.includes('feColorMatrix'); } if (mode === 'themecolor') { return customModeActive && customStyleEl && document.head.contains(customStyleEl); } const styleEl = document.getElementById(CONFIG.STYLE_ID); if (!styleEl || !document.head.contains(styleEl)) return false; const expectedCSS = getCSSForMode(mode); if (!expectedCSS) return false; return styleEl.textContent.replace(/\s/g, '') === expectedCSS.replace(/\s/g, ''); } function forceReapplyMode() { if (Util.isBlacklisted()) return; const mode = getEffectiveMode(); if (mode === 'light') { Util.removeElementById(CONFIG.STYLE_ID); removeOverlays(); removeFilterMode(); stopThemeMode(); stopSoftwarmMode(); } else { applyModeByMode(mode); } lastAppliedMode = mode; saveStyleSnapshot(); } function startEnsureApplied() { if (ensureAppliedTimer) clearTimeout(ensureAppliedTimer); ensureAppliedRetries = 0; function checkAndApply() { if (Util.isBlacklisted()) { ensureAppliedRetries = MAX_ENSURE_RETRIES; return; } const mode = getEffectiveMode(); if (mode !== 'light' && !isStyleCorrectlyApplied(mode)) { forceReapplyMode(); } ensureAppliedRetries++; if (ensureAppliedRetries < MAX_ENSURE_RETRIES) { ensureAppliedTimer = setTimeout(checkAndApply, ENSURE_RETRY_INTERVAL); } else { ensureAppliedTimer = null; } } ensureAppliedTimer = setTimeout(checkAndApply, ENSURE_RETRY_INTERVAL); } function stopEnsureApplied() { if (ensureAppliedTimer) { clearTimeout(ensureAppliedTimer); ensureAppliedTimer = null; } } function startRafRecovery() { if (rafRecoveryId) cancelAnimationFrame(rafRecoveryId); let lastCheck = 0; function recoveryLoop(now) { if (!Util.getValue('FORCE_RECOVERY')) { rafRecoveryId = requestAnimationFrame(recoveryLoop); return; } if (now - lastCheck >= RECOVERY_RAF_INTERVAL) { lastCheck = now; if (!Util.isBlacklisted()) { const mode = getEffectiveMode(); if (mode !== 'light' && !isStyleCorrectlyApplied(mode)) { forceReapplyMode(); } } } rafRecoveryId = requestAnimationFrame(recoveryLoop); } rafRecoveryId = requestAnimationFrame(recoveryLoop); } function stopRafRecovery() { if (rafRecoveryId) { cancelAnimationFrame(rafRecoveryId); rafRecoveryId = null; } } // ==================== bfcache 无闪烁恢复核心 ==================== 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 { const css = getCSSForMode(mode); snapshot.css = css; } GM_setValue('bfcache_snapshot', snapshot); } function restoreFromSnapshot() { const snapshot = GM_getValue('bfcache_snapshot'); if (!snapshot || !snapshot.mode || snapshot.mode === 'light') { Util.removeElementById(CONFIG.STYLE_ID); removeOverlays(); removeFilterMode(); stopThemeMode(); stopSoftwarmMode(); return; } const mode = snapshot.mode; if (mode === 'softwarm') { if (snapshot.softwarmType) { Util.setValue('softwarmGlobalType', snapshot.softwarmType); Util.setValue('softwarmGlobalGreenOpacity', snapshot.softwarmGreenOpacity || 0.3); Util.setValue('softwarmGlobalWarmOpacity', snapshot.softwarmWarmOpacity || 0.3); } applySoftwarmMode(); } else if (mode === 'filter') { applyFilterMode(); } else if (mode === 'themecolor') { if (snapshot.themeColors) { updateThemeCSSVars(snapshot.themeColors.bg, snapshot.themeColors.link); customModeActive = true; if (!customModeObserver) startThemeModeObserver(); else scanAndTagForThemeMode(); } else { applyThemeMode(); } } else { const css = snapshot.css; if (css) { Util.addStyle(CONFIG.STYLE_ID, css); } else { const newCss = getCSSForMode(mode); if (newCss) Util.addStyle(CONFIG.STYLE_ID, newCss); } } lastAppliedMode = mode; } // ==================== 强制恢复增强模块 ==================== function isStyleValidForMode(mode) { if (mode === 'light') return true; if (mode === 'softwarm') { const type = getSoftwarmTypeForSite(); const opacity = (type === 'green') ? getSoftwarmGreenOpacityForSite() : getSoftwarmWarmOpacityForSite(); if (opacity <= 0) return true; if (type === 'green') { return greenOverlay && document.documentElement.contains(greenOverlay); } else { return warmOverlay && document.documentElement.contains(warmOverlay); } } if (mode === 'filter') { return filterStyle && document.head.contains(filterStyle); } if (mode === 'themecolor') { return customModeActive && customStyleEl && document.head.contains(customStyleEl); } const styleEl = document.getElementById(CONFIG.STYLE_ID); return styleEl && document.head.contains(styleEl); } function safeRecover(force = false) { if (!Util.getValue('FORCE_RECOVERY')) return; if (isRecovering) return; isRecovering = true; setTimeout(() => { try { const mode = getEffectiveMode(); if (!force && lastAppliedMode === mode && isStyleValidForMode(mode)) { isRecovering = false; return; } forceReapplyMode(); lastAppliedMode = getEffectiveMode(); } catch(e) { console.warn('强制恢复失败', e); } finally { isRecovering = false; } }, CONFIG.RECOVERY_DEBOUNCE); } function startEnhancedRecovery() { if (!Util.getValue('FORCE_RECOVERY')) return; if (styleIntegrityTimer) clearInterval(styleIntegrityTimer); styleIntegrityTimer = setInterval(() => { if (Util.isBlacklisted()) return; safeRecover(false); }, CONFIG.RECOVERY_INTERVAL); if (recoveryObserver) recoveryObserver.disconnect(); recoveryObserver = new MutationObserver((mutations) => { if (!Util.getValue('FORCE_RECOVERY')) return; if (Util.isBlacklisted()) return; for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.removedNodes.length) { for (const node of mutation.removedNodes) { if (node.nodeType === 1) { const id = node.id; if (id === CONFIG.STYLE_ID || id === CONFIG.FILTER_STYLE_ID || id === 'eye-protect-green-overlay' || id === 'eye-protect-warm-overlay' || id === 'theme-mode-core-style') { safeRecover(true); return; } } } } } }); const targetNode = document.head || document.documentElement; if (targetNode) { recoveryObserver.observe(targetNode, { childList: true, subtree: true }); } else { setTimeout(() => startEnhancedRecovery(), 100); } } // ==================== 搜索引擎专用强化恢复 ==================== function isSearchEnginePage() { const host = location.host; const searchEngines = ['google.', 'bing.', 'baidu.', 'sogou.', 'so.com', 'yandex.', 'duckduckgo.']; return searchEngines.some(se => host.includes(se)); } let searchEnginePollingTimer = null; function startSearchEnginePolling() { if (!isSearchEnginePage()) return; if (searchEnginePollingTimer) clearInterval(searchEnginePollingTimer); searchEnginePollingTimer = setInterval(() => { try { const mode = getEffectiveMode(); if (mode !== 'light' && !isStyleCorrectlyApplied(mode)) { console.log('[夜间护眼助手] 搜索引擎样式丢失,主动恢复'); forceReapplyMode(); } } catch(e) {} }, 800); } function stopSearchEnginePolling() { if (searchEnginePollingTimer) clearInterval(searchEnginePollingTimer); searchEnginePollingTimer = null; } // ==================== bfcache 返回强化 ==================== function enhanceBfcacheRecovery() { window.addEventListener('pageshow', (event) => { if (event.persisted) { console.log('[夜间护眼助手] 检测到 bfcache 恢复,强制重新应用模式'); forceReapplyMode(); for (let delay of [50, 150, 300]) { setTimeout(() => { if (!Util.isBlacklisted()) { forceReapplyMode(); ObserverManager.restart(); } }, delay); } } }); document.addEventListener('visibilitychange', () => { if (!document.hidden && Util.getValue('FORCE_RECOVERY')) { setTimeout(() => forceReapplyMode(), 30); } }); } // ==================== 页面返回修复(增强版) ==================== function patchHistoryAPIEnhanced() { try { const originalPushState = history.pushState; const originalReplaceState = history.replaceState; const reapplyAfterNavigation = () => { if (navigationTimer) clearTimeout(navigationTimer); if (!Util.isBlacklisted()) { forceReapplyMode(); ObserverManager.restart(); stopEnsureApplied(); startEnsureApplied(); if (isSearchEnginePage()) { setTimeout(() => forceReapplyMode(), 20); } } navigationTimer = setTimeout(() => { if (!Util.isBlacklisted()) { forceReapplyMode(); ObserverManager.restart(); stopEnsureApplied(); startEnsureApplied(); } navigationTimer = null; }, 100); }; history.pushState = function() { originalPushState.apply(this, arguments); reapplyAfterNavigation(); }; history.replaceState = function() { originalReplaceState.apply(this, arguments); reapplyAfterNavigation(); }; window.addEventListener('popstate', () => { reapplyAfterNavigation(); }); window.addEventListener('pagehide', () => { saveStyleSnapshot(); stopEnsureApplied(); }); window.addEventListener('beforeunload', () => { if (history.pushState !== originalPushState) history.pushState = originalPushState; if (history.replaceState !== originalReplaceState) history.replaceState = originalReplaceState; stopEnsureApplied(); }, { once: true }); } catch(e) {} } function initVisibilityRecovery() { const handleVisibilityChange = () => { if (!document.hidden && Util.getValue('FORCE_RECOVERY')) { safeRecover(true); stopEnsureApplied(); startEnsureApplied(); } }; document.addEventListener('visibilitychange', handleVisibilityChange); startEnhancedRecovery(); } // ==================== 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); } forceReapplyMode(); } debounceTimer = null; }, delay); } function startKeepAlive() { if (isDisposed) return; if (keepAliveTimer) clearInterval(keepAliveTimer); keepAliveTimer = setInterval(() => { if (isDisposed) return; if (Date.now() - lastApplyTime > 30000) { forceReapplyMode(); 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' || node.id === 'theme-mode-core-style')) { setTimeout(() => { if (!isDisposed) forceReapplyMode(); }, 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); } lastAppliedMode = mode; saveStyleSnapshot(); } function cleanAllEffects() { Util.removeElementById(CONFIG.STYLE_ID); removeOverlays(); removeFilterMode(); stopThemeMode(); stopSoftwarmMode(); GM_setValue('bfcache_snapshot', { mode: 'light' }); } 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 = ['light', 'dark', 'eyecare', 'softwarm', 'themecolor', 'filter', 'soft', 'pure']; 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 === '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(); 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) { updateGlobalSoftwarmPanelVisibility(currentSettingsShadow); updateGlobalThemePanelVisibility(currentSettingsShadow); updateSettingsPanelUI(currentSettingsShadow); } pendingUIUpdate = false; }); } function updateGlobalSoftwarmPanelVisibility(shadowRoot) { if (!shadowRoot) return; const globalSoftwarmPanel = shadowRoot.querySelector('#globalSoftwarmPanel'); if (!globalSoftwarmPanel) return; const currentGlobalMode = getCurrentGlobalMode(); globalSoftwarmPanel.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 globalThemePanel = shadowRoot.querySelector('#globalThemePanel'); if (!globalThemePanel) return; const currentGlobalMode = getCurrentGlobalMode(); globalThemePanel.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') || ['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'; } } updateGlobalSoftwarmPanelVisibility(shadowRoot); updateGlobalThemePanelVisibility(shadowRoot); } 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 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 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 = ['light', 'dark', 'eyecare', 'softwarm', 'themecolor', 'filter', 'soft', 'pure']; const validModes = ['light', 'dark', 'soft', 'eyecare', 'pure', 'filter', 'softwarm', 'themecolor']; cycleModes = cycleModes.filter(m => validModes.includes(m)); if (cycleModes.length === 0) cycleModes = ['light', 'dark', 'eyecare', 'softwarm', 'themecolor', 'filter', 'soft', 'pure']; 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 siteSpecificThemeColors = (siteSpecific && typeof siteSpecific === 'object' && siteSpecific.mode === 'themecolor') ? siteSpecific.colors : 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 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 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 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; }); 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: '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: '🌈 护眼主题' } ]; 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); } .drag-handle { cursor: move; user-select: none; } .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; } `; 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; panelContainer.innerHTML = `
🛡️ 夜间护眼助手 v5.36.11
📐

📱 当前状态

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

🌐 网站专用模式设置

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

🎨 全局护眼主题配色(实时生效)

当前全局模式为“🌈 护眼主题”,点击预设按钮切换所有网站(未设置专用模式)的配色。
快速预设:
${THEME_PRESETS.map(preset => ``).join('')}
点击预设立即保存为全局默认设置,并实时刷新当前页面的护眼主题效果(无闪烁)。

🍃 全局柔暖双色设置(实时生效)

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

⚡ 强制启用管理

强制启用列表中的网站需要满足全局开关或白名单才会生效(黑名单优先级最高)。
强制启用状态:
${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 ? ',并同时开启下方的“强制专用模式”总开关' : ''},离开时自动恢复专用总开关为启用、强制专用模式为关闭。
🔒 强制专用模式
开启后,列表中的网站将强制使用其自身在“网站专用模式设置”中配置的模式(优先级高于专用模式开关,但低于黑名单,且不受“全局专用总开关”影响)。

⚡ 闪烁抑制列表

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

⚙️ 全局设置 📖 说明

🌍 全局开关
⚡ 即时模式
☀️ 白天开启
📦 预加载模式
🌙 暗色站防误判
🔄 强制恢复模式
🔔 显示通知
🌓 跟随系统
🌙 夜间强度(反色模式)
%
仅夜间模式生效,调整反色滤镜强度
🔆 亮度检测阈值
% ▼ 展开滑块
低于此值的网站视为暗色站,自动跳过暗色模式(白名单网站不受影响)
`; refreshFlickerListUI(shadowRoot); refreshForceSpecialListUI(shadowRoot); updateGlobalThemePanelVisibility(shadowRoot); updateGlobalSoftwarmPanelVisibility(shadowRoot); function bindFoldableHeaders(root) { const headers = root.querySelectorAll('.foldable-header'); headers.forEach(header => { const targetId = header.getAttribute('data-fold-target'); const target = root.querySelector(`#${targetId}`); const icon = header.querySelector('.fold-icon'); if (target && icon) { target.style.display = 'none'; icon.textContent = '▶'; header.addEventListener('click', (e) => { e.stopPropagation(); if (target.style.display === 'none') { target.style.display = 'block'; icon.textContent = '▼'; } else { target.style.display = 'none'; icon.textContent = '▶'; } }); } }); } bindFoldableHeaders(shadowRoot); function updateGlobalThemeColors(preset) { const bg = preset.bg; const link = preset.link; Util.setValue('themecolorBgColor', bg); Util.setValue('themecolorLinkColor', link); if (getCurrentGlobalMode() === 'themecolor') { const siteMode = getSiteSpecificMode(); if (!siteMode || siteMode !== 'themecolor') { if (customModeActive) { updateThemeCSSVars(bg, link); } } } Util.showNotification(`已应用预设:${preset.name}`, 'success', 1500); } THEME_PRESETS.forEach(preset => { const btn = shadowRoot.querySelector(`#globalPreset${preset.id.charAt(0).toUpperCase() + preset.id.slice(1)}`); if (btn) { btn.addEventListener('click', () => updateGlobalThemeColors(preset)); } }); const siteThemeContainer = shadowRoot.querySelector('#siteThemeContainer'); if (siteThemeContainer) { const sitePresetBtns = siteThemeContainer.querySelectorAll('.site-theme-preset'); sitePresetBtns.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') { updateThemeColors(bg, link, true); } } const hostInput = shadowRoot.querySelector('#siteDomain').value.trim(); let hostname = (() => { try { let url = new URL(hostInput.startsWith('http') ? hostInput : 'http://' + hostInput); return url.hostname; } catch(e) { return hostInput.replace(/^https?:\/\//i, '').split('/')[0]; } })(); if (hostname) { setSiteSpecificMode(hostname, 'themecolor', { colors: { bg, link } }); } else { Util.setValue('themecolorBgColor', bg); Util.setValue('themecolorLinkColor', link); } Util.showNotification(`已为网站应用预设:${preset.name}`, 'success', 1500); } }); }); } 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') { forceReapplyMode(); } } 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 hostname = (() => { try { let url = new URL(hostInput.startsWith('http') ? hostInput : 'http://' + hostInput); return url.hostname; } catch(e) { return hostInput.replace(/^https?:\/\//i, '').split('/')[0]; } })(); if (hostname === location.host) { const currentGreenOp = parseFloat(siteGreenSlider.value); const currentWarmOp = parseFloat(siteWarmSlider.value); setSoftwarmTypeForSite(type, currentGreenOp, currentWarmOp); forceReapplyMode(); } } } 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 hostname = (() => { try { let url = new URL(hostInput.startsWith('http') ? hostInput : 'http://' + hostInput); return url.hostname; } catch(e) { return hostInput.replace(/^https?:\/\//i, '').split('/')[0]; } })(); if (hostname === location.host) { updateSoftwarmOpacityAndSave(val, 'green', false); } else { try { let siteModesStr = Util.getValue('siteSpecificModes'); let siteModes = JSON.parse(siteModesStr); if (!siteModes[hostname]) siteModes[hostname] = { mode: 'softwarm', type: 'green', greenOpacity: 0.3, warmOpacity: 0.3 }; siteModes[hostname].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 hostname = (() => { try { let url = new URL(hostInput.startsWith('http') ? hostInput : 'http://' + hostInput); return url.hostname; } catch(e) { return hostInput.replace(/^https?:\/\//i, '').split('/')[0]; } })(); if (hostname === location.host) { updateSoftwarmOpacityAndSave(val, 'warm', false); } else { try { let siteModesStr = Util.getValue('siteSpecificModes'); let siteModes = JSON.parse(siteModesStr); if (!siteModes[hostname]) siteModes[hostname] = { mode: 'softwarm', type: 'warm', greenOpacity: 0.3, warmOpacity: 0.3 }; siteModes[hostname].warmOpacity = val; Util.setValue('siteSpecificModes', JSON.stringify(siteModes)); } catch(e) {} } Util.showNotification(`深暖色浓度已调整为 ${percent}%`, 'info', 1000); }); } } 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 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('light'); 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); }); } 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; } const mode = siteModeSelect.value; if (mode === 'default') { setSiteSpecificMode(hostname, 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(hostname, 'softwarm', { type, greenOpacity: greenOp, warmOpacity: warmOp }); } else if (mode === 'themecolor') { const bg = Util.getValue('themecolorBgColor'); const link = Util.getValue('themecolorLinkColor'); setSiteSpecificMode(hostname, 'themecolor', { colors: { bg, link } }); } 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); 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.36.11 启动 (增强搜索引擎兼容与bfcache恢复,修复专用模式受全局/白名单控制)'); 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(); immediateStyleInjection(); const preloadEnabled = Util.getValue('PRELOAD_ENABLED'); const instantMode = Util.getValue('INSTANT_MODE'); if (preloadEnabled || instantMode) earlyApplyStyles(); initMenu(); Util.startCleanup(); initVisibilityRecovery(); patchHistoryAPIEnhanced(); enhanceBfcacheRecovery(); startSearchEnginePolling(); if (Util.isBlacklisted()) return; const applyNow = () => { StyleManager.apply(true); ObserverManager.start(); startEnsureApplied(); startRafRecovery(); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => setTimeout(applyNow, 50)); } else { setTimeout(applyNow, 50); } window.addEventListener('beforeunload', () => { ObserverManager.dispose(); Util.dispose(); if (navigationTimer) clearTimeout(navigationTimer); if (preloadTimer) clearTimeout(preloadTimer); stopEnsureApplied(); stopRafRecovery(); stopSearchEnginePolling(); }, { 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(); })();