// ==UserScript== // @name 豆包深色/浅色切换(右上角灯泡图标) // @namespace local.doubao.theme.toggle // @version 0.0.3 // @description 可拖动灯泡按钮,浅色还原原生样式,仅深色适配代码标签 // @match https://www.doubao.com/* // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; const STORAGE_KEY = 'tm_theme_override_v1'; let isDragging = false; let dragStartX = 0; let dragStartY = 0; let btnStartX = 0; let btnStartY = 0; // 注入基础样式 + 仅深色模式修改代码标签 function injectBaseStyle() { const style = document.createElement('style'); style.id = 'tm-base-theme-style'; style.textContent = ` .md-box-root { --md-box-color-fg: unset !important; } /* 仅深色模式修改 code / pre,浅色保留原样式 */ html[data-theme="dark"] code { background: #2d2d2d !important; color: #a5e948 !important; padding: 2px 6px !important; border-radius: 4px !important; } html[data-theme="dark"] pre, html[data-theme="dark"] pre code { background: #1e1e1e !important; color: #dcdcdc !important; } `; document.head.appendChild(style); } function detectColorScheme() { return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } function setDataTheme(theme) { document.documentElement.setAttribute('data-theme', theme); } function getOverrideTheme() { const v = localStorage.getItem(STORAGE_KEY); return v === 'light' || v === 'dark' ? v : null; } function setOverrideTheme(theme) { localStorage.setItem(STORAGE_KEY, theme); } function applyThemeFromState() { const override = getOverrideTheme(); const targetTheme = override ?? detectColorScheme(); setDataTheme(targetTheme); } // 创建可拖动灯泡按钮 function ensureButton() { const existing = document.getElementById('tm-theme-toggle-btn'); if (existing) return existing; const button = document.createElement('button'); button.id = 'tm-theme-toggle-btn'; button.type = 'button'; button.setAttribute('aria-label', '切换浅色/深色模式'); const icon = document.createElement('span'); icon.className = 'tm-theme-icon'; button.appendChild(icon); const style = document.createElement('style'); style.textContent = ` #tm-theme-toggle-btn{ position: fixed; top: 16px; right: 16px; width: 40px; height: 40px; box-sizing: border-box; border-radius: 50%; border: 1px solid rgba(0,0,0,.10); background: rgba(255,255,255,.78); box-shadow: 0 4px 12px rgba(0,0,0,.12); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); z-index: 2147483647; cursor: grab; user-select: none; -webkit-tap-highlight-color: transparent; display: flex; align-items: center; justify-content: center; transition: transform 0.2s ease, box-shadow 0.2s ease; } #tm-theme-toggle-btn:active { cursor: grabbing; } #tm-theme-toggle-btn:hover{ transform: translateY(-2px); } #tm-theme-toggle-btn:active{ transform: translateY(0); box-shadow: 0 2px 8px rgba(0,0,0,.14); } #tm-theme-toggle-btn:focus-visible{ outline: none; box-shadow: 0 0 0 3px rgba(59,130,246,.35), 0 4px 12px rgba(0,0,0,.12); } .tm-theme-icon { width: 22px; height: 26px; position: relative; display: block; } .tm-theme-icon::before, .tm-theme-icon::after { content: ''; position: absolute; left: 50%; transform: translateX(-50%); } .tm-theme-icon::before { top: 0; width: 16px; height: 16px; border-radius: 50% 50% 40% 40%; background: #ffd000; transition: background 0.25s ease, box-shadow 0.25s ease; box-shadow: 0 0 6px #ffd000; } .tm-theme-icon::after { bottom: 0; width: 8px; height: 6px; background: #999; border-radius: 2px; } html[data-theme="dark"] .tm-theme-icon::before { background: #666; box-shadow: none; } html[data-theme="dark"] #tm-theme-toggle-btn{ border: 1px solid rgba(255,255,255,.14); background: rgba(20,20,20,.72); box-shadow: 0 6px 16px rgba(0,0,0,.35); } @media (prefers-reduced-motion: reduce) { #tm-theme-toggle-btn{ transition: none !important; } #tm-theme-toggle-btn:hover{ transform: none; } #tm-theme-toggle-btn:active{ transform: none; } } `; document.head.appendChild(style); document.body.appendChild(button); // 拖拽逻辑 function dragStart(e) { isDragging = true; dragStartX = e.clientX; dragStartY = e.clientY; btnStartX = button.offsetLeft; btnStartY = button.offsetTop; button.style.transition = 'none'; e.preventDefault(); } function dragMove(e) { if (!isDragging) return; const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; let newLeft = btnStartX + dx; let newTop = btnStartY + dy; const winW = window.innerWidth; const winH = window.innerHeight; const btnW = button.offsetWidth; const btnH = button.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, winW - btnW)); newTop = Math.max(0, Math.min(newTop, winH - btnH)); button.style.left = newLeft + 'px'; button.style.top = newTop + 'px'; button.style.right = 'auto'; } function dragEnd() { isDragging = false; button.style.transition = ''; } button.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', dragMove); document.addEventListener('mouseup', dragEnd); document.addEventListener('mouseleave', dragEnd); return button; } // 切换主题 function toggleTheme() { const current = document.documentElement.getAttribute('data-theme') || detectColorScheme(); const next = current === 'dark' ? 'light' : 'dark'; setOverrideTheme(next); setDataTheme(next); } // 初始化 injectBaseStyle(); const button = ensureButton(); applyThemeFromState(); button.addEventListener('click', () => { if (!isDragging) toggleTheme(); }); // 监听系统配色变化 const mql = window.matchMedia?.('(prefers-color-scheme: dark)'); if (mql) { const onChange = () => { if (getOverrideTheme() !== null) return; applyThemeFromState(); }; if (mql.addEventListener) { mql.addEventListener('change', onChange); } else if (mql.addListener) { mql.addListener(onChange); } } })();