// ==UserScript== // @name B站个人面板美化 // @namespace http://tampermonkey.net/ // @version 10.0 // @description 磨砂面板 + 昵称拖拽布局 + HARD-CORE标签 + 深浅双主题(独立版) // @author Anonymity // @match *://*.bilibili.com/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; // ====================== 配置 ====================== const STORAGE = { SPACE_STYLE: 'bili_space_style_enabled', TARGET_UID: 'bili_space_target_uid', CUSTOM_CONFIG: 'bili_custom_panel_config', NICKNAME_LAYOUT: 'bili_nickname_layout', LIST_BEAUTY_CONFIG: 'bili_list_beauty_config' }; // ====================== 列表管理器美化共享工具 ====================== const LIST_BG_DB_NAME = 'BiliListManagerDB'; const LIST_BG_DB_VERSION = 1; const LIST_BG_STORE = 'bg_images'; function isListManagerLoaded() { const _g = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; return _g.__biliListManagerLoaded === true || !!document.getElementById('bili-force-trigger'); } function getCollectionIdFromPage() { try { // 和1.js逻辑保持一致:从DOM读取合集BV列表 const container = document.querySelector('.video-pod__list'); if (container) { const items = container.querySelectorAll('[data-key]'); const bvs = new Set(); items.forEach(el => { const bv = el.getAttribute('data-key'); if (bv && bv.startsWith('BV')) bvs.add(bv); }); if (bvs.size > 0) { return 'list_' + Array.from(bvs).sort().join('|'); } } // 单个视频:从URL获取BV号 const bvMatch = location.href.match(/\/video\/(BV[0-9a-zA-Z]+)/); if (bvMatch) return 'single_' + bvMatch[1]; return null; } catch(e) { return null; } } function getListBeautyConfig() { try { const saved = JSON.parse(localStorage.getItem(STORAGE.LIST_BEAUTY_CONFIG) || '{}'); return { frostedGlass: saved.frostedGlass !== false, glassBlur: saved.glassBlur || 12, drawerOpacity: saved.drawerOpacity !== undefined ? saved.drawerOpacity : 0.85, defaultBgOpacity: saved.defaultBgOpacity !== undefined ? saved.defaultBgOpacity : 0.15, bgSize: saved.bgSize || 'cover', bgPosition: saved.bgPosition || 'center' }; } catch(e) { return { frostedGlass: true, glassBlur: 12, drawerOpacity: 0.85, defaultBgOpacity: 0.15, bgSize: 'cover', bgPosition: 'center' }; } } function saveListBeautyConfig(cfg) { localStorage.setItem(STORAGE.LIST_BEAUTY_CONFIG, JSON.stringify(cfg)); } function openListBgDB() { return new Promise((resolve, reject) => { const req = indexedDB.open(LIST_BG_DB_NAME, LIST_BG_DB_VERSION); req.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains(LIST_BG_STORE)) { db.createObjectStore(LIST_BG_STORE, { keyPath: 'id' }); } }; req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } async function saveListBgImage(id, imageData, opacity) { try { const db = await openListBgDB(); return new Promise((resolve) => { const tx = db.transaction(LIST_BG_STORE, 'readwrite'); const store = tx.objectStore(LIST_BG_STORE); store.put({ id, imageData, opacity, uploadTime: Date.now() }); tx.oncomplete = () => { if (window.__biliListRefreshBg) window.__biliListRefreshBg(); resolve(true); }; tx.onerror = () => resolve(false); }); } catch(e) { return false; } } async function deleteListBgImage(id) { try { const db = await openListBgDB(); return new Promise((resolve) => { const tx = db.transaction(LIST_BG_STORE, 'readwrite'); const store = tx.objectStore(LIST_BG_STORE); store.delete(id); tx.oncomplete = () => { if (window.__biliListRefreshBg) window.__biliListRefreshBg(); resolve(true); }; tx.onerror = () => resolve(false); }); } catch(e) { return false; } } // ====================== 个人面板背景图 IndexedDB ====================== const PANEL_BG_DB_NAME = 'BiliPanelBgDB'; const PANEL_BG_DB_VERSION = 1; const PANEL_BG_STORE = 'bg_image'; const PANEL_BG_KEY = 'panel_bg'; function openPanelBgDB() { return new Promise((resolve, reject) => { const req = indexedDB.open(PANEL_BG_DB_NAME, PANEL_BG_DB_VERSION); req.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains(PANEL_BG_STORE)) { db.createObjectStore(PANEL_BG_STORE, { keyPath: 'id' }); } }; req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } async function savePanelBgImage(imageData, opts = {}) { try { const db = await openPanelBgDB(); return new Promise((resolve) => { const tx = db.transaction(PANEL_BG_STORE, 'readwrite'); const store = tx.objectStore(PANEL_BG_STORE); store.put({ id: PANEL_BG_KEY, imageData, uploadTime: Date.now(), ...opts }); tx.oncomplete = () => resolve(true); tx.onerror = () => resolve(false); }); } catch(e) { return false; } } async function getPanelBgImage() { try { const db = await openPanelBgDB(); return new Promise((resolve) => { const tx = db.transaction(PANEL_BG_STORE, 'readonly'); const req = tx.objectStore(PANEL_BG_STORE).get(PANEL_BG_KEY); req.onsuccess = () => resolve(req.result || null); req.onerror = () => resolve(null); }); } catch(e) { return null; } } async function deletePanelBgImage() { try { const db = await openPanelBgDB(); return new Promise((resolve) => { const tx = db.transaction(PANEL_BG_STORE, 'readwrite'); const store = tx.objectStore(PANEL_BG_STORE); store.delete(PANEL_BG_KEY); tx.oncomplete = () => resolve(true); tx.onerror = () => resolve(false); }); } catch(e) { return false; } } const DEFAULT_PANEL_CONFIG = { bgWatermarkNum: '6', bgRotate: -6, bgOpacityDark: 0.15, bgOpacityLight: 0.01, bgBreathAnim: true, bgFrosted: false, glassBlurDark: 12, glassBlurLight: 12, wmSizeDark: 600, wmSizeLight: 500, wmColorDark: '#B4B4B4', wmColorLight: '#505050', showHardCoreTag: true, hardCoreText: '硬核会员', hardCoreDarkBorder: '#FF69B4', hardCoreLightBorder: '#66ccff', primaryDark: '#FF69B4', primaryLight: '#66ccff', panelBgOpacity: 0.3, panelBgImageSet: false }; const DEFAULT_NICKNAME_LAYOUT = { effect: 'none', effectColor: '#FF69B4', effectSpeed: 5, columns: [{ id: 'col1', width: '100%', blocks: [ { id: 'b1', text: 'Sunday', x: -5, y: -10, color: '#FF69B4', fontSize: 22, rotation: -2 }, { id: 'b2', text: ' 大好き', x: 5, y: 15, color: '#FF69B4', fontSize: 20, rotation: 2 }, { id: 'b3', text: 'が', x: 40, y: 30, color: '#FF69B4', fontSize: 28, rotation: 5 } ] }] }; let spaceEnabled = GM_getValue(STORAGE.SPACE_STYLE, true); let targetUid = GM_getValue(STORAGE.TARGET_UID, '327239767'); let config = GM_getValue(STORAGE.CUSTOM_CONFIG, { ...DEFAULT_PANEL_CONFIG }); config = { ...DEFAULT_PANEL_CONFIG, ...config }; // 内存中保存面板背景图 dataURL(从IndexedDB加载,不放进GM存储) let panelBgDataUrl = null; let panelBgSize = 'cover'; let panelBgPos = 'center'; let panelBgPosX = 50, panelBgPosY = 50; // 迁移:如果旧版本把图片存在config里,迁移到DB并清除GM中的大对象 if (config.panelBgImage) { const legacyImg = config.panelBgImage; delete config.panelBgImage; config.panelBgImageSet = true; panelBgDataUrl = legacyImg; savePanelBgImage(legacyImg); GM_setValue(STORAGE.CUSTOM_CONFIG, config); } // 启动时从DB加载面板背景图(含位置/尺寸/透明度) getPanelBgImage().then(entry => { if (entry && entry.imageData) { panelBgDataUrl = entry.imageData; if (entry.bgSize) panelBgSize = entry.bgSize; if (entry.bgPosition) { panelBgPos = entry.bgPosition; const m = entry.bgPosition.match(/(-?\d+(?:\.\d+)?)%\s+(-?\d+(?:\.\d+)?)%/); if (m) { panelBgPosX = parseFloat(m[1]); panelBgPosY = parseFloat(m[2]); } } if (entry.opacity !== undefined) config.panelBgOpacity = entry.opacity; if (config.panelBgImageSet === undefined) config.panelBgImageSet = true; rebuild(); } }); let nicknameLayout = GM_getValue(STORAGE.NICKNAME_LAYOUT, null); if (!nicknameLayout) { nicknameLayout = { ...DEFAULT_NICKNAME_LAYOUT }; GM_setValue(STORAGE.NICKNAME_LAYOUT, nicknameLayout); } let styleEl = null; let editorEl = null; let configPanelEl = null; let editorBackup = null; let tagInserted = false; let observer = null; // ====================== 工具 ====================== function throttle(fn, delay) { let timer = null; return (...args) => { if (!timer) { fn(...args); timer = setTimeout(() => timer = null, delay); } }; } function uid() { return 'uid_' + Math.random().toString(36).substr(2, 9); } function saveConfig() { // 剥离可能残留的大字段,避免GM存储超限导致保存失败 const toSave = { ...config }; delete toSave.panelBgImage; GM_setValue(STORAGE.CUSTOM_CONFIG, toSave); rebuild(); } function saveLayout() { GM_setValue(STORAGE.NICKNAME_LAYOUT, nicknameLayout); applyNickname(); } // ====================== 主题检测 ====================== function getTheme() { const html = document.documentElement; const dt = html.getAttribute('data-theme'); if (dt === 'light' || dt === 'dark') return dt; if (html.classList.contains('bili_dark') || html.classList.contains('dark')) return 'dark'; if (html.classList.contains('light')) return 'light'; const ls = localStorage.getItem('bilibili_theme'); if (ls === 'light' || ls === 'dark') return ls; try { const bodyBg = getComputedStyle(document.body).backgroundColor; if (bodyBg && bodyBg !== 'rgba(0, 0, 0, 0)') { const nums = bodyBg.replace(/[^\d,]/g, '').split(',').map(Number); if (nums.length >= 3) { const brightness = nums[0] * 0.299 + nums[1] * 0.587 + nums[2] * 0.114; return brightness < 128 ? 'dark' : 'light'; } } const textColor = getComputedStyle(html).color; const tnums = textColor.replace(/[^\d,]/g, '').split(',').map(Number); if (tnums.length >= 3) { const tb = tnums[0] * 0.299 + tnums[1] * 0.587 + tnums[2] * 0.114; return tb < 128 ? 'dark' : 'light'; } } catch(e) {} return 'dark'; } function observeHtmlTheme() { const html = document.documentElement; const mo = new MutationObserver(() => applyThemeClass()); mo.observe(html, { attributes: true, attributeFilter: ['data-theme', 'class'] }); } function applyThemeClass() { const panel = document.querySelector('.avatar-panel-popover'); if (!panel) return; panel.classList.remove('theme-dark', 'theme-light'); panel.classList.add('theme-' + getTheme()); } // ====================== CSS 生成(关键修改:极透明背景 + 隐藏水印) ====================== function hex2rgb(h) { return `${parseInt(h.slice(1,3),16)},${parseInt(h.slice(3,5),16)},${parseInt(h.slice(5,7),16)}`; } function buildCSS() { const uidDark = `a.nickname-item.dark[href="//space.bilibili.com/${targetUid}"]`; const uidLight = `a.nickname-item.light[href="//space.bilibili.com/${targetUid}"]`; const { bgWatermarkNum, bgRotate, bgOpacityDark, bgOpacityLight, bgBreathAnim, bgFrosted, glassBlurDark, glassBlurLight, wmSizeDark, wmSizeLight, wmColorDark, wmColorLight, hardCoreDarkBorder, hardCoreLightBorder, primaryDark, primaryLight } = config; const pd = hex2rgb(primaryDark); const pl = hex2rgb(primaryLight); return ` /* ====== 核心变量(html data-theme 驱动) ====== */ html[data-theme="dark"] .avatar-panel-popover, html.dark .avatar-panel-popover, html.bili_dark .avatar-panel-popover { --panel-bg: rgba(16,16,22,0.04); /* 极透明 */ --panel-text: #fff; --panel-shadow: 0 2px 10px rgba(0,0,0,0.25),0 12px 40px rgba(0,0,0,0.48),inset 0 0 0 1px rgba(255,255,255,0.07); --panel-hover-shadow: 0 2px 10px rgba(0,0,0,0.25),0 12px 40px rgba(0,0,0,0.48),0 0 26px 7px rgba(${pd},0.36),inset 0 0 0 1px rgba(${pd},0.12); --logout-bg: #2E0017; --logout-hover-shadow: 0 4px 16px rgba(${pd},0.4); } html[data-theme="light"] .avatar-panel-popover, html.light .avatar-panel-popover { --panel-bg: rgba(252,252,250,0.04); /* 极透明 */ --panel-text: #2d2d2d; --panel-shadow: 0 4px 16px rgba(0,0,0,0.08),0 12px 40px rgba(0,0,0,0.15),inset 0 0 0 1px rgba(0,0,0,0.05); --panel-hover-shadow: 0 4px 16px rgba(0,0,0,0.08),0 12px 40px rgba(0,0,0,0.15),0 0 26px 7px rgba(${pl},0.36),inset 0 0 0 1px rgba(${pd},0.1); --logout-bg: linear-gradient(135deg,${primaryLight},#47b8f0); --logout-hover-shadow: 0 4px 16px rgba(${pl},0.3); } /* ====== 装饰变量(panel class 驱动) ====== */ .theme-dark.avatar-panel-popover, .avatar-panel-popover.theme-dark { --panel-bg: rgba(16,16,22,0.04); --panel-text: #fff; --panel-shadow: 0 2px 10px rgba(0,0,0,0.25),0 12px 40px rgba(0,0,0,0.48),inset 0 0 0 1px rgba(255,255,255,0.07); --panel-hover-shadow: 0 2px 10px rgba(0,0,0,0.25),0 12px 40px rgba(0,0,0,0.48),0 0 26px 7px rgba(${pd},0.36),inset 0 0 0 1px rgba(${pd},0.12); --logout-bg: #2E0017; --logout-hover-shadow: 0 4px 16px rgba(${pd},0.4); --wm-color: rgba(${hex2rgb(wmColorDark)},${bgOpacityDark}); --wm-size: ${wmSizeDark}px; --hardcore-border: ${hardCoreDarkBorder}; --item-bg: rgba(255,255,255,0.04); --item-shadow: 0 4px 12px rgba(0,0,0,0.3),0 1px 3px rgba(0,0,0,0.15); --glass-blur: ${glassBlurDark}px; --link-icon-fill: #ccc; --split-line-bg: linear-gradient(90deg,transparent,rgba(255,255,255,0.12),transparent); --level-bar-bg: #1E1E1E; --level-inner-bg: #555; --primary: ${primaryDark}; --primary-rgb: ${pd}; --avatar-shadow: drop-shadow(0 0 12px rgba(0,221,255,0.6)); --avatar-hover-shadow: drop-shadow(0 0 20px ${primaryDark}); --single-link-hover-bg: rgba(${pd},0.1); --coin-text: inherit; --coin-text-secondary: inherit; --count-hover-color: ${primaryDark}; } .theme-light.avatar-panel-popover, .avatar-panel-popover.theme-light { --panel-bg: rgba(252,252,250,0.04); --panel-text: #2d2d2d; --panel-shadow: 0 4px 16px rgba(0,0,0,0.08),0 12px 40px rgba(0,0,0,0.15),inset 0 0 0 1px rgba(0,0,0,0.05); --panel-hover-shadow: 0 4px 16px rgba(0,0,0,0.08),0 12px 40px rgba(0,0,0,0.15),0 0 26px 7px rgba(${pl},0.36),inset 0 0 0 1px rgba(${pd},0.1); --logout-bg: linear-gradient(135deg,${primaryLight},#47b8f0); --logout-hover-shadow: 0 4px 16px rgba(${pl},0.3); --wm-color: rgba(${hex2rgb(wmColorLight)},${bgOpacityLight}); --wm-size: ${wmSizeLight}px; --hardcore-border: ${hardCoreLightBorder}; --item-bg: rgba(0,0,0,0.03); --item-shadow: 0 2px 8px rgba(0,0,0,0.06),0 1px 2px rgba(0,0,0,0.04); --glass-blur: ${glassBlurLight}px; --link-icon-fill: #555; --split-line-bg: linear-gradient(90deg,transparent,rgba(0,0,0,0.06),transparent); --level-bar-bg: #E0E0E0; --level-inner-bg: #aaa; --primary: ${primaryLight}; --primary-rgb: ${pl}; --avatar-shadow: drop-shadow(0 0 12px rgba(0,221,255,0.4)); --avatar-hover-shadow: drop-shadow(0 0 20px ${primaryLight}); --single-link-hover-bg: rgba(${pd},0.08); --coin-text: #2d2d2d; --coin-text-secondary: #666; --count-hover-color: ${primaryLight}; } /* ====== panel 本体 ====== */ .avatar-panel-popover { position:relative; z-index:1; background: var(--panel-bg) !important; color: var(--panel-text) !important; box-shadow: var(--panel-shadow) !important; animation: pfPopoverFadeIn 0.25s cubic-bezier(0.2,0,0.2,1); } .avatar-panel-popover::before { content:"${String(bgWatermarkNum).replace(/\\/g,'\\\\').replace(/"/g,'\\"').replace(/[\r\n]+/g,'')}"; position:absolute; inset:0; font-size:var(--wm-size); font-weight:bold; display:flex; align-items:center; justify-content:center; color:var(--wm-color); transform:rotate(${bgRotate}deg); user-select:none; pointer-events:none; z-index:-1; border-radius:16px; clip-path:inset(0 round 16px); ${bgBreathAnim ? 'animation:pfBgBreath 12s ease-in-out infinite;' : ''} } .avatar-panel-popover:hover { box-shadow: var(--panel-hover-shadow) !important; } .avatar-panel-popover * { -webkit-font-smoothing:antialiased !important; } ${bgFrosted ? ` /* ====== 玻璃特效 ====== */ .v-popover-content.avatar-popover, .v-popover.is-bottom { background: transparent !important; } .avatar-panel-popover { backdrop-filter: blur(var(--glass-blur)) !important; -webkit-backdrop-filter: blur(var(--glass-blur)) !important; } ` : ''} /* ====== 子元素(全用 var) ====== */ .vip-entry-containter { display:none !important; } .icon { display:none; } .senior { display:none !important; } /* 昵称 */ ${uidDark},${uidLight} { display:inline-flex !important; justify-content:space-between !important; width:85px !important; height:38px !important; font-size:0 !important; color:transparent !important; align-items:center !important; line-height:1 !important; margin:0 !important; padding:0 !important; transform:translateX(65px) translateY(-20px) !important; text-decoration:none !important; overflow:visible !important; position:relative !important; } ${uidDark}::before,${uidLight}::before, ${uidDark}::after,${uidLight}::after { content:none !important; } /* HARD-CORE */ .hard-core-tag { width:fit-content !important; margin:16px auto 20px !important; padding:5px 16px !important; border-radius:8px !important; font-size:13px !important; font-weight:500; letter-spacing:1px; white-space:nowrap; pointer-events:none; border:1px solid var(--hardcore-border) !important; color: var(--panel-text) !important; } /* 数据卡片 */ .coins-item, .counts-item { background: var(--item-bg) !important; box-shadow: var(--item-shadow) !important; border-radius:12px; } .coin-item__num, .count-num { color: var(--coin-text); } .coin-item__text, .count-text { color: var(--coin-text-secondary); } .counts-item .single-count-item { transition:all 1.5s ease; cursor:pointer; } .counts-item .single-count-item:hover { transform:scale(1.1); color: var(--count-hover-color); } .counts-item .single-count-item:hover .count-num { color: var(--count-hover-color); } /* 链接图标 */ .link-icon path { fill: var(--link-icon-fill) !important; } /* 分割线 */ .split-line { background: var(--split-line-bg) !important; } /* 退出 */ .logout-item { margin-top:16px; padding:16px; border-radius:12px; transition:all 0.3s ease; background: var(--logout-bg) !important; } .logout-item:hover { filter:brightness(1.15); transform:translateY(-2px); box-shadow: var(--logout-hover-shadow); } .logout-item span { font-size:16px; font-weight:500; letter-spacing:1px; } .theme-dark .logout-item span { color: ${primaryDark} !important; } html[data-theme="dark"] .logout-item .link-icon path, html.dark .logout-item .link-icon path, html.bili_dark .logout-item .link-icon path { fill: #fff !important; } .theme-light .logout-item span { color: #fff !important; } html[data-theme="light"] .logout-item .link-icon path, html.light .logout-item .link-icon path { fill: ${primaryLight} !important; } /* 等级条 */ .level-item { position:relative; transition:transform 0.3s ease; z-index:10; } .level-item:hover { transform:scale(1.08); } .level-progress { background: var(--level-bar-bg) !important; border-radius:3px; height:6px; overflow:visible; } .level-progress__inner { height:100%; background: var(--level-inner-bg) !important; border-radius:3px; transition:all 0.4s ease; position:relative; } .level-progress__inner::after { content:''; position:absolute; right:-10px; top:50%; transform:translateY(-50%); width:20px; height:20px; background:radial-gradient(circle,#8a2be2,transparent); opacity:0; transition:opacity 0.3s; filter:blur(2px); } .level-item:hover .level-progress__inner::after { opacity:1; } .level-item:hover .level-progress__inner { background: var(--primary); box-shadow: 0 0 10px var(--primary); } .level-intro { display:none; } /* 大头像 */ .big-avatar { margin:0 auto 12px; display:block; filter: var(--avatar-shadow); transition:all 0.3s ease; } .big-avatar:hover { transform:scale(1.05) translateY(-4px); filter: var(--avatar-hover-shadow); } /* 菜单(面板内部) */ .avatar-panel-popover .single-link-item:hover { background: var(--single-link-hover-bg) !important; border-left:3px solid var(--primary) !important; color: var(--primary); } /* 子菜单弹窗 */ .v-popover.is-right { overflow:visible; z-index:9999; } html[data-theme="dark"] .sub-links-item, html.dark .sub-links-item, html.bili_dark .sub-links-item { border-radius:12px; backdrop-filter:blur(12px); background:rgba(16,16,22,0.85); color:#fff; box-shadow:0 8px 24px rgba(0,0,0,0.4); } html[data-theme="light"] .sub-links-item, html.light .sub-links-item { border-radius:12px; backdrop-filter:blur(12px); background:rgba(252,252,250,0.88); color:#2d2d2d; box-shadow:0 8px 24px rgba(0,0,0,0.12); } html[data-theme="dark"] .sub-links-item .single-link-item:hover, html.dark .sub-links-item .single-link-item:hover, html.bili_dark .sub-links-item .single-link-item:hover { background:rgba(${pd},0.1); color:${primaryDark}; } html[data-theme="light"] .sub-links-item .single-link-item:hover, html.light .sub-links-item .single-link-item:hover { background:rgba(${pd},0.08); color:${primaryDark}; } /* 关键帧 */ @keyframes pfPopoverFadeIn { from { opacity:0; transform:translateY(-12px) scale(0.97); } to { opacity:1; transform:translateY(0) scale(1); } } @keyframes pfBgBreath { 0%,100% { opacity:0.1; } 50% { opacity:0.5; } } .level-item__bar--next:hover { animation:pfCharge 0.6s infinite ease-in-out; } @keyframes pfCharge { 0%,100% { transform:scale(1) rotate(0deg); filter:drop-shadow(0 0 5px #ff0000); } 50% { transform:scale(1.25) rotate(0deg); filter:drop-shadow(0 0 15px #ff0000); } } #top { font-size: 0; /* 隐藏原有文字 */ } #top::before { content: "⚠️"; font-size: initial; /* 恢复图标字号 */ } #contents span { font-size: 20px; color: #2266dd; } `; } function applyPanelBackground() { const panel = document.querySelector('.avatar-panel-popover'); if (!panel) return; // 移除旧背景层 const oldBg = panel.querySelector('.panel-custom-bg-layer'); if (oldBg) oldBg.remove(); // 用内存中的 dataURL(从IndexedDB加载),不存GM存储 // 只要DB里有图就显示,不依赖panelBgImageSet标志(避免刷新后标志丢失导致不显示) const imgData = panelBgDataUrl; if (imgData) { const bgLayer = document.createElement('div'); bgLayer.className = 'panel-custom-bg-layer'; bgLayer.style.cssText = ` position:absolute; inset:0; z-index:0; pointer-events:none; background-image:url("${imgData}"); background-size:${panelBgSize}; background-position:${panelBgPos}; background-repeat:no-repeat; opacity:${config.panelBgOpacity}; `; panel.style.position = 'relative'; panel.insertBefore(bgLayer, panel.firstChild); // 确保内容在背景上层 Array.from(panel.children).forEach(child => { if (!child.classList.contains('panel-custom-bg-layer')) { const pos = getComputedStyle(child).position; if (pos === 'static') child.style.position = 'relative'; if (!child.style.zIndex) child.style.zIndex = '1'; } }); } } // ====================== rebuild 等函数 ====================== function rebuild() { cleanupDOM(); if (styleEl) styleEl.remove(); styleEl = document.createElement('style'); styleEl.id = 'bili-panel-style'; styleEl.textContent = buildCSS(); document.head.appendChild(styleEl); applyThemeClass(); applyPanelBackground(); if (config.showHardCoreTag) insertTag(); else removeTag(); applyNickname(); } function cleanupDOM() { document.querySelectorAll('.nickname-item').forEach(el => { el.style.fontSize = ''; el.style.color = ''; }); document.querySelectorAll('.nickname-layout-container').forEach(c => c.remove()); document.querySelectorAll('.panel-custom-bg-layer').forEach(c => c.remove()); removeTag(); } function fullCleanup() { cleanupDOM(); if (styleEl) { styleEl.remove(); styleEl = null; } tagInserted = false; } function insertTag() { if (tagInserted || !config.showHardCoreTag) return; const panel = document.querySelector('.avatar-panel-popover'); const nickEl = panel?.querySelector('.nickname-item'); const coinEl = panel?.querySelector('.coins-item'); if (!panel || !nickEl || !coinEl) return; let tag = panel.querySelector('.hard-core-tag'); if (!tag) { tag = document.createElement('div'); tag.className = 'hard-core-tag'; nickEl.parentNode.insertBefore(tag, coinEl); } tag.textContent = config.hardCoreText; tagInserted = true; } function removeTag() { const tag = document.querySelector('.hard-core-tag'); if (tag) tag.remove(); tagInserted = false; } function applyNickname() { // 注入特效动画CSS(只注入一次) if (!document.getElementById('bili-nick-effect-style')) { const s = document.createElement('style'); s.id = 'bili-nick-effect-style'; s.textContent = ` @keyframes nickRainbowFlow { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } } @keyframes nickMonoFlow { 0% { background-position: 0% 50%; } 100% { background-position: 200% 50%; } } @keyframes nickGlowPulse { 0%, 100% { filter: brightness(1) drop-shadow(0 0 4px currentColor); opacity: 0.9; } 50% { filter: brightness(1.3) drop-shadow(0 0 10px currentColor) drop-shadow(0 0 18px currentColor); opacity: 1; } } .nickname-layout-container { --nick-duration: 3s; } .nickname-layout-container.nick-effect-rainbow .nickname-block { background: linear-gradient(90deg, #ff0000, #ff9900, #33ff00, #00ffff, #0066ff, #9900ff, #ff00ff, #ff0000); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent !important; -webkit-text-fill-color: transparent; animation: nickRainbowFlow var(--nick-duration) linear infinite; will-change: background-position; transform: translateZ(0); } .nickname-layout-container.nick-effect-mono .nickname-block { background: linear-gradient(90deg, transparent 0%, currentColor 40%, currentColor 60%, transparent 100%); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent !important; -webkit-text-fill-color: transparent; animation: nickMonoFlow var(--nick-duration) linear infinite; will-change: background-position; transform: translateZ(0); } .nickname-layout-container.nick-effect-glow .nickname-block { animation: nickGlowPulse var(--nick-duration) ease-in-out infinite; will-change: filter, opacity; transform: translateZ(0); } `; document.head.appendChild(s); } const panel = document.querySelector('.avatar-panel-popover'); if (!panel) return; document.querySelectorAll('.nickname-layout-container').forEach(c => c.remove()); // 兼容旧配置 if (!nicknameLayout.effect) nicknameLayout.effect = 'none'; if (!nicknameLayout.effectColor) nicknameLayout.effectColor = '#FF69B4'; if (!nicknameLayout.effectSpeed) nicknameLayout.effectSpeed = 5; // 计算动画时长:速度1→8秒,速度10→1秒 const duration = (9 - nicknameLayout.effectSpeed * 0.8) + 's'; const nickItems = panel.querySelectorAll('.nickname-item.dark, .nickname-item.light'); nickItems.forEach(nickItem => { nickItem.style.fontSize = '0'; nickItem.style.color = 'transparent'; const container = document.createElement('div'); container.className = 'nickname-layout-container'; container.style.cssText = 'position:absolute; inset:0; display:flex; pointer-events:none;'; container.style.setProperty('--nick-duration', duration); // 添加特效类和颜色 if (nicknameLayout.effect && nicknameLayout.effect !== 'none') { container.classList.add('nick-effect-' + nicknameLayout.effect); // 单色流光和荧光需要设置主颜色 if (nicknameLayout.effect === 'mono' || nicknameLayout.effect === 'glow') { container.style.color = nicknameLayout.effectColor; } } // 先收集所有块,计算最大宽度用于延迟计算 const allBlocks = []; if (nicknameLayout?.columns) { let colOffsetX = 0; nicknameLayout.columns.forEach(col => { const colDiv = document.createElement('div'); colDiv.className = 'nickname-col'; colDiv.style.cssText = `position:relative; width:${col.width}; height:100%; flex-shrink:0;`; if (col.blocks) { col.blocks.forEach(block => { allBlocks.push({ block, colDiv, colOffsetX, x: block.x }); }); } container.appendChild(colDiv); }); // 计算整体流动的总宽度(用于动画延迟同步) // 找到所有块的最左x和最右x,总范围约350px足够覆盖 const flowRange = 400; // 为每个块创建元素,设置动画延迟实现整体连贯流动 allBlocks.forEach(({ block, colDiv }) => { const span = document.createElement('span'); span.className = 'nickname-block'; span.textContent = block.text; span.style.cssText = ` position:absolute; left:${block.x}px; top:${block.y}px; color:${block.color}; font-size:${block.fontSize}px; transform:rotate(${block.rotation}deg) translateZ(0); white-space:pre; pointer-events:none; line-height:1; font-weight:bold; `; // 流光特效:根据x位置设置负动画延迟,实现整体连贯流动 if ((nicknameLayout.effect === 'rainbow' || nicknameLayout.effect === 'mono')) { // 从左到右延迟递减,x=0时delay=0,x越大delay越负(光从左流向右) const delay = -(block.x / flowRange) * parseFloat(duration); span.style.animationDelay = delay + 's'; } colDiv.appendChild(span); }); nickItem.appendChild(container); } }); } function toggle() { spaceEnabled = !spaceEnabled; GM_setValue(STORAGE.SPACE_STYLE, spaceEnabled); if (spaceEnabled) rebuild(); else fullCleanup(); } function updateUid() { const val = prompt('请输入目标UID(纯数字)', targetUid); if (val && /^\d+$/.test(val.trim())) { targetUid = val.trim(); GM_setValue(STORAGE.TARGET_UID, targetUid); rebuild(); } else if (val !== null) alert('UID必须为纯数字'); } // ====================== 编辑器 ====================== function openEditor() { if (editorEl) { editorEl.style.display = 'block'; return; } // 兼容旧配置 if (!nicknameLayout.effect) nicknameLayout.effect = 'none'; if (!nicknameLayout.effectColor) nicknameLayout.effectColor = '#FF69B4'; editorBackup = JSON.parse(JSON.stringify(nicknameLayout)); const panel = document.createElement('div'); panel.id = 'bili-nickname-editor'; panel.style.cssText = ` position:fixed; top:30px; left:50%; transform:translateX(-50%); width:900px; max-width:95vw; background:rgba(20,20,30,0.95); backdrop-filter:blur(16px); color:#fff; border-radius:14px; box-shadow:0 8px 32px rgba(0,0,0,0.6); z-index:100000; padding:20px; font-size:13px; max-height:92vh; overflow-y:auto; `; panel.innerHTML = `

✨ 昵称拖拽布局编辑器

预览区(拖动文字调整位置,点击文字编辑属性,所有分栏并排显示,和实际效果一致)
`; document.body.appendChild(panel); editorEl = panel; // 初始化特效选择 document.getElementById('nickEffect').value = nicknameLayout.effect; updateEffectColorVisibility(); renderCols(); renderPreview(); bindEditorEvents(); document.getElementById('addColBtn').onclick = () => { nicknameLayout.columns.push({ id: uid(), width: `${Math.floor(100/(nicknameLayout.columns.length+1))}%`, blocks: [] }); // 平均调整所有列宽 const avgW = Math.floor(100/nicknameLayout.columns.length); nicknameLayout.columns.forEach(c => c.width = avgW + '%'); renderCols(); renderPreview(); }; document.getElementById('addBlockBtn').onclick = () => { if (!nicknameLayout.columns.length) { alert('请先添加分栏'); return; } const idx = activeColIdx(); nicknameLayout.columns[idx].blocks.push({ id: uid(), text: '新文本', x: 10, y: 10, color: nicknameLayout.effectColor !== '#000000' ? nicknameLayout.effectColor : '#FF69B4', fontSize: 22, rotation: 0 }); renderPreview(); renderCols(); }; document.getElementById('saveNickBtn').onclick = () => { saveLayout(); closeEditor(true); }; document.getElementById('cancelNickBtn').onclick = () => { nicknameLayout = JSON.parse(JSON.stringify(editorBackup)); applyNickname(); closeEditor(false); }; // 特效切换 document.getElementById('nickEffect').onchange = (e) => { nicknameLayout.effect = e.target.value; updateEffectColorVisibility(); applyNickname(); }; document.getElementById('nickEffectColor').oninput = (e) => { nicknameLayout.effectColor = e.target.value; applyNickname(); }; document.getElementById('nickEffectSpeed').oninput = (e) => { nicknameLayout.effectSpeed = parseInt(e.target.value); document.getElementById('effectSpeedVal').textContent = nicknameLayout.effectSpeed; applyNickname(); }; } function updateEffectColorVisibility() { const row = document.getElementById('effectColorRow'); if (!row) return; row.style.display = nicknameLayout.effect === 'rainbow' ? 'none' : 'flex'; } function closeEditor(saved) { if (!editorEl) return; if (!saved) { nicknameLayout = JSON.parse(JSON.stringify(editorBackup)); applyNickname(); } editorEl.remove(); editorEl = null; } function activeColIdx() { const a = document.querySelector('#editorColumns .col-tag.active'); return a ? parseInt(a.dataset.index) : 0; } function renderCols() { const c = document.getElementById('editorColumns'); if (!c) return; c.innerHTML = ''; nicknameLayout.columns.forEach((col, i) => { const tag = document.createElement('div'); tag.className = 'col-tag' + (i === 0 ? ' active' : ''); tag.dataset.index = i; tag.style.cssText = 'padding:4px 8px; background:#3a3a5c; border-radius:4px; cursor:pointer; margin-right:4px; font-size:12px; display:flex; align-items:center;'; tag.textContent = `列${i+1} (${col.blocks.length})`; tag.onclick = () => { document.querySelectorAll('#editorColumns .col-tag').forEach(t => t.classList.remove('active')); tag.classList.add('active'); renderPreview(); }; const del = document.createElement('span'); del.textContent = '×'; del.style.cssText = 'margin-left:6px; color:#f66; cursor:pointer;'; del.onclick = e => { e.stopPropagation(); if (nicknameLayout.columns.length <= 1) return alert('至少保留一个分栏'); nicknameLayout.columns.splice(i, 1); renderCols(); renderPreview(); }; tag.appendChild(del); c.appendChild(tag); }); } function renderPreview() { const preview = document.getElementById('editorPreview'); if (!preview) return; preview.innerHTML = ''; // Word式:所有分栏并排显示,每列之间加分隔线 nicknameLayout.columns.forEach((col, colIdx) => { const colDiv = document.createElement('div'); colDiv.className = 'nickname-editor-col'; colDiv.style.cssText = ` position:relative; width:${col.width}; height:100%; border-right:${colIdx < nicknameLayout.columns.length-1 ? '1px dashed rgba(255,255,255,0.15)' : 'none'}; `; colDiv.dataset.colIndex = colIdx; // 点击空白区域取消选中 colDiv.addEventListener('click', e => { if (e.target === colDiv) { document.getElementById('blockProps').style.display = 'none'; } }); col.blocks.forEach(block => { const span = document.createElement('span'); span.textContent = block.text; span.style.cssText = ` position:absolute; left:${block.x}px; top:${block.y}px; color:${block.color}; font-size:${block.fontSize}px; transform:rotate(${block.rotation}deg); white-space:pre; cursor:move; user-select:none; font-weight:bold; line-height:1; border:1px solid rgba(255,255,255,0.25); padding:2px 3px; background:rgba(0,0,0,0.4); border-radius:2px; `; span.dataset.blockId = block.id; span.dataset.colIndex = colIdx; span.addEventListener('mousedown', onDragStart); span.addEventListener('click', e => { e.stopPropagation(); selectBlock(block.id, colIdx); // 切换当前列为选中列 document.querySelectorAll('#editorColumns .col-tag').forEach(t => t.classList.remove('active')); const tag = document.querySelector(`#editorColumns .col-tag[data-index="${colIdx}"]`); if (tag) tag.classList.add('active'); }); colDiv.appendChild(span); }); preview.appendChild(colDiv); }); } let dragState = null; function onDragStart(e) { e.preventDefault(); e.stopPropagation(); const preview = document.getElementById('editorPreview'); const rect = preview.getBoundingClientRect(); const colIdx = parseInt(e.target.dataset.colIndex); const block = nicknameLayout.columns[colIdx].blocks.find(b => b.id === e.target.dataset.blockId); if (!block) return; dragState = { block, colIdx, startX: e.clientX, startY: e.clientY, origX: block.x, origY: block.y, rect }; window.addEventListener('mousemove', onDragMove); window.addEventListener('mouseup', onDragEnd); } function onDragMove(e) { if (!dragState) return; // 扩大拖动范围,允许拖到列外±80px dragState.block.x = Math.max(-80, Math.min(200+80, dragState.origX + e.clientX - dragState.startX)); dragState.block.y = Math.max(-80, Math.min(80+80, dragState.origY + e.clientY - dragState.startY)); renderPreview(); // 保持选中状态 selectBlock(dragState.block.id, dragState.colIdx); applyNickname(); } function onDragEnd() { window.removeEventListener('mousemove', onDragMove); window.removeEventListener('mouseup', onDragEnd); dragState = null; } function selectBlock(blockId, colIdx) { if (colIdx === undefined) colIdx = activeColIdx(); const block = nicknameLayout.columns[colIdx]?.blocks.find(b => b.id === blockId); if (!block) return; const p = document.getElementById('blockProps'); p.style.display = 'block'; document.getElementById('blockText').value = block.text; document.getElementById('blockColor').value = block.color; document.getElementById('blockSize').value = block.fontSize; document.getElementById('blockRotate').value = block.rotation; p.dataset.blockId = blockId; p.dataset.colIndex = colIdx; // 高亮选中块 document.querySelectorAll('#editorPreview span').forEach(s => { s.style.outline = s.dataset.blockId === blockId ? '2px solid #ff69b4' : 'none'; }); } function bindEditorEvents() { ['blockText','blockColor','blockSize','blockRotate'].forEach(id => { document.getElementById(id).oninput = () => { const p = document.getElementById('blockProps'); const ci = parseInt(p.dataset.colIndex); const block = nicknameLayout.columns[ci]?.blocks.find(b => b.id === p.dataset.blockId); if (!block) return; block.text = document.getElementById('blockText').value; block.color = document.getElementById('blockColor').value; block.fontSize = parseInt(document.getElementById('blockSize').value) || 22; block.rotation = parseFloat(document.getElementById('blockRotate').value) || 0; renderPreview(); selectBlock(block.id, ci); applyNickname(); }; }); document.getElementById('deleteBlockBtn').onclick = () => { const p = document.getElementById('blockProps'); const ci = parseInt(p.dataset.colIndex); const col = nicknameLayout.columns[ci]; if (!col) return; col.blocks = col.blocks.filter(b => b.id !== p.dataset.blockId); p.style.display = 'none'; renderCols(); renderPreview(); applyNickname(); }; } // ====================== 配置面板 ====================== function openConfig() { if (configPanelEl) { configPanelEl.style.display = 'block'; return; } const listMgrExists = isListManagerLoaded(); const currentCid = getCollectionIdFromPage(); const beautyCfg = getListBeautyConfig(); const panel = document.createElement('div'); panel.id = 'bili-custom-config-panel'; panel.style.cssText = ` position:fixed; top:80px; left:20px; width:420px; background:rgba(16,16,22,0.85); backdrop-filter:blur(14px); color:#fff; padding:16px; border-radius:14px; box-shadow:0 6px 24px rgba(0,0,0,0.4); z-index:99999; font-size:13px; max-height:85vh; overflow-y:auto; `; let listSectionHtml = ''; if (listMgrExists) { listSectionHtml = `

📋 候选分P列表美化

${beautyCfg.glassBlur}px
${Math.round(beautyCfg.drawerOpacity*100)}%
背景图预览(拖动可调整位置,支持自由裁切露出空白,右下角小地图显示完整图片):
点击下方上传
背景图
${Math.round(beautyCfg.defaultBgOpacity*100)}%
${currentCid ? '当前列表:' + (currentCid.startsWith('list_') ? '多P合集' : '单个视频') : '(当前页面非视频播放页)'}
💡 所有调整实时预览,自动保存生效;背景按列表独立保存,存储在IndexedDB不占用localStorage空间
`; } else { listSectionHtml = `
⚠️ 未检测到「候选分P列表管理器」脚本,列表美化配置项已隐藏
`; } panel.innerHTML = `

个性化面板配置

px
px
px
px

🖼️ 个人面板背景

预览(拖动调整位置,支持自由裁切,右下角小地图显示完整图片):
点击下方上传
个人面板背景图
${Math.round(config.panelBgOpacity*100)}%
💡 与列表背景完全独立,图片及调整存储在IndexedDB
${listSectionHtml}
`; GM_addStyle('#bili-custom-config-panel input, #bili-custom-config-panel select, #bili-custom-config-panel textarea { background:#222 !important; color:#fff !important; border:1px solid #444; }'); document.body.appendChild(panel); configPanelEl = panel; // ========== 列表美化功能 ========== if (listMgrExists) { const _global = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; const previewEl = document.getElementById('listBgPreview'); const previewImg = document.getElementById('listBgPreviewImg'); const placeholder = document.getElementById('previewPlaceholder'); const navEl = document.getElementById('listBgNav'); const navImg = document.getElementById('listBgNavImg'); const navFrame = document.getElementById('listBgNavFrame'); let currentPreviewDataUrl = null; let currentTargetId = '__default__'; // 当前正在编辑的图片ID let imgNaturalW = 0, imgNaturalH = 0; let currentBgSize = beautyCfg.bgSize || 'cover'; let isDragging = false; let isDraggingNav = false; let dragStartX = 0, dragStartY = 0; let dragStartPosX = 50, dragStartPosY = 50; let currentPosX = 50, currentPosY = 50; const PREVIEW_W = 280, PREVIEW_H = 460; // 计算当前size下图片在容器中的渲染尺寸(仅用于拖动计算和小地图) const calcRenderSize = (sizeMode, containerW, containerH, imgW, imgH) => { if (!imgW || !imgH) return { renderW: containerW, renderH: containerH }; let renderW, renderH; if (sizeMode === 'cover') { const scale = Math.max(containerW / imgW, containerH / imgH); renderW = imgW * scale; renderH = imgH * scale; } else if (sizeMode === 'contain') { const scale = Math.min(containerW / imgW, containerH / imgH); renderW = imgW * scale; renderH = imgH * scale; } else if (sizeMode === 'stretch') { renderW = containerW; renderH = containerH; } else { renderW = imgW; renderH = imgH; } return { renderW, renderH }; }; // 更新导航小地图裁切框 const updateNavFrame = () => { if (!imgNaturalW || !imgNaturalH || !currentPreviewDataUrl) { navFrame.style.display = 'none'; return; } const { renderW, renderH } = calcRenderSize(currentBgSize, PREVIEW_W, PREVIEW_H, imgNaturalW, imgNaturalH); const imgOffsetX = (PREVIEW_W - renderW) * currentPosX / 100; const imgOffsetY = (PREVIEW_H - renderH) * currentPosY / 100; const visibleX = Math.max(0, -imgOffsetX); const visibleY = Math.max(0, -imgOffsetY); const visibleW = Math.min(renderW, PREVIEW_W - Math.max(0, imgOffsetX)); const visibleH = Math.min(renderH, PREVIEW_H - Math.max(0, imgOffsetY)); if (visibleW <= 1 || visibleH <= 1) { navFrame.style.display = 'none'; return; } const navScale = Math.min(80 / imgNaturalW, 134 / imgNaturalH); const navImgW = imgNaturalW * navScale; const navImgH = imgNaturalH * navScale; const navOffX = (84 - navImgW) / 2; const navOffY = (138 - navImgH) / 2; const scale = renderW / imgNaturalW; navFrame.style.display = 'block'; navFrame.style.left = (navOffX + visibleX / scale * navScale) + 'px'; navFrame.style.top = (navOffY + visibleY / scale * navScale) + 'px'; navFrame.style.width = Math.max(4, visibleW / scale * navScale) + 'px'; navFrame.style.height = Math.max(4, visibleH / scale * navScale) + 'px'; }; // 保存当前位置/尺寸/透明度到DB和全局配置,实时应用到面板 const saveAndApply = (updateGlobalCfg = false) => { const opacity = Number(document.getElementById('listBgOpacity').value); const position = `${currentPosX}% ${currentPosY}%`; // 1. 更新预览显示 previewImg.style.opacity = opacity; previewImg.style.backgroundSize = currentBgSize; previewImg.style.backgroundPosition = position; document.getElementById('bgOpacityVal').textContent = Math.round(opacity*100) + '%'; // 2. 更新全局配置(磨砂、面板透明度等) const globalUpdate = {}; if (updateGlobalCfg) { globalUpdate.frostedGlass = document.getElementById('listFrostedGlass').checked; globalUpdate.glassBlur = Number(document.getElementById('listGlassBlur').value); globalUpdate.drawerOpacity = Number(document.getElementById('listDrawerOpacity').value); document.getElementById('glassBlurVal').textContent = globalUpdate.glassBlur + 'px'; document.getElementById('drawerOpacityVal').textContent = Math.round(globalUpdate.drawerOpacity*100) + '%'; } globalUpdate.bgSize = currentBgSize; globalUpdate.bgPosition = position; globalUpdate.defaultBgOpacity = opacity; // 保存全局配置 const cfg = { ...getListBeautyConfig(), ...globalUpdate }; saveListBeautyConfig(cfg); // 3. 如果有正在编辑的图片,直接保存位置/尺寸/透明度到DB条目 if (currentPreviewDataUrl && _global.__biliListSaveBgImage) { _global.__biliListSaveBgImage(currentTargetId, currentPreviewDataUrl, { opacity: opacity, bgSize: currentBgSize, bgPosition: position }); } else { // 没有图片,直接调用更新函数 if (_global.__biliListUpdateBeautyCfg) _global.__biliListUpdateBeautyCfg(globalUpdate); // 触发信号元素更新(备用) let signal = document.getElementById('__bili_list_beauty_signal'); if (!signal) { signal = document.createElement('div'); signal.id = '__bili_list_beauty_signal'; signal.style.display = 'none'; document.body.appendChild(signal); } signal.setAttribute('data-version', Date.now().toString()); } // 4. 更新小地图 updateNavFrame(); }; // 加载图片获取尺寸 const loadImageSize = (dataUrl) => { return new Promise((resolve) => { const img = new Image(); img.onload = () => { imgNaturalW = img.naturalWidth; imgNaturalH = img.naturalHeight; resolve(); }; img.onerror = () => resolve(); img.src = dataUrl; }); }; // 加载背景到预览 const loadBgToPreview = async (targetId) => { currentTargetId = targetId; let bgEntry = null; const db = await openListBgDB(); bgEntry = await new Promise(resolve => { const tx = db.transaction(LIST_BG_STORE, 'readonly'); const req = tx.objectStore(LIST_BG_STORE).get(targetId); req.onsuccess = () => resolve(req.result || null); req.onerror = () => resolve(null); }); if (targetId !== '__default__' && !bgEntry) { // 当前列表没有自定义,加载默认 bgEntry = await new Promise(resolve => { const tx = db.transaction(LIST_BG_STORE, 'readonly'); const req = tx.objectStore(LIST_BG_STORE).get('__default__'); req.onsuccess = () => resolve(req.result || null); req.onerror = () => resolve(null); }); currentTargetId = '__default__'; } if (bgEntry && bgEntry.imageData) { currentPreviewDataUrl = bgEntry.imageData; previewImg.style.backgroundImage = `url("${bgEntry.imageData}")`; navImg.style.backgroundImage = `url("${bgEntry.imageData}")`; placeholder.style.display = 'none'; navEl.style.display = 'block'; previewEl.style.cursor = 'grab'; if (bgEntry.opacity !== undefined) document.getElementById('listBgOpacity').value = bgEntry.opacity; if (bgEntry.bgSize) { currentBgSize = bgEntry.bgSize; document.getElementById('listBgSize').value = bgEntry.bgSize.endsWith('px') ? 'cover' : bgEntry.bgSize; } if (bgEntry.bgPosition) { const m = bgEntry.bgPosition.match(/(-?\d+(?:\.\d+)?)%\s+(-?\d+(?:\.\d+)?)%/); if (m) { currentPosX = parseFloat(m[1]); currentPosY = parseFloat(m[2]); } } await loadImageSize(bgEntry.imageData); } else { currentPreviewDataUrl = null; imgNaturalW = 0; imgNaturalH = 0; previewImg.style.backgroundImage = 'none'; navImg.style.backgroundImage = 'none'; placeholder.style.display = 'block'; navEl.style.display = 'none'; previewEl.style.cursor = 'default'; currentPosX = 50; currentPosY = 50; } saveAndApply(true); }; // 初始化加载 const initCid = currentCid || '__default__'; loadBgToPreview(initCid); // 绑定所有控件事件 document.getElementById('listFrostedGlass').addEventListener('change', () => saveAndApply(true)); document.getElementById('listGlassBlur').addEventListener('input', () => saveAndApply(true)); document.getElementById('listDrawerOpacity').addEventListener('input', () => saveAndApply(true)); document.getElementById('listBgOpacity').addEventListener('input', () => saveAndApply(false)); document.getElementById('listBgSize').addEventListener('change', (e) => { currentBgSize = e.target.value; currentPosX = 50; currentPosY = 50; // 切换模式重置到中心 saveAndApply(false); }); // 主预览拖动 previewEl.addEventListener('mousedown', (e) => { if (e.target.closest('#listBgNav')) return; if (!currentPreviewDataUrl || !imgNaturalW) return; isDragging = true; dragStartX = e.clientX; dragStartY = e.clientY; dragStartPosX = currentPosX; dragStartPosY = currentPosY; previewEl.style.cursor = 'grabbing'; e.preventDefault(); }); // 导航拖动 navEl.addEventListener('mousedown', (e) => { if (!currentPreviewDataUrl || !imgNaturalW) return; isDraggingNav = true; dragStartX = e.clientX; dragStartY = e.clientY; dragStartPosX = currentPosX; dragStartPosY = currentPosY; e.preventDefault(); e.stopPropagation(); }); document.addEventListener('mousemove', (e) => { if (!isDragging && !isDraggingNav) return; const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; const { renderW, renderH } = calcRenderSize(currentBgSize, PREVIEW_W, PREVIEW_H, imgNaturalW, imgNaturalH); const denomX = PREVIEW_W - renderW; const denomY = PREVIEW_H - renderH; let dpx = 0, dpy = 0; if (isDraggingNav) { const navScale = Math.min(80 / imgNaturalW, 134 / imgNaturalH); const scale = renderW / imgNaturalW; const factor = navScale * scale; dpx = denomX !== 0 ? (-dx / factor) * 100 / denomX : 0; dpy = denomY !== 0 ? (-dy / factor) * 100 / denomY : 0; } else { dpx = denomX !== 0 ? (-dx) * 100 / denomX : 0; dpy = denomY !== 0 ? (-dy) * 100 / denomY : 0; } currentPosX = dragStartPosX + dpx; currentPosY = dragStartPosY + dpy; // 允许上下左右各拖出500px空白 const extraX = denomX !== 0 ? (500 / Math.abs(denomX)) * 100 : 300; const extraY = denomY !== 0 ? (500 / Math.abs(denomY)) * 100 : 300; currentPosX = Math.max(-extraX, Math.min(100 + extraX, currentPosX)); currentPosY = Math.max(-extraY, Math.min(100 + extraY, currentPosY)); saveAndApply(false); }); document.addEventListener('mouseup', () => { isDragging = false; isDraggingNav = false; previewEl.style.cursor = currentPreviewDataUrl ? 'grab' : 'default'; }); // 图片上传 const handleUpload = (input, targetId) => { input.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; if (!file.type.startsWith('image/')) { alert('请选择图片'); return; } const reader = new FileReader(); reader.onload = async (ev) => { const dataUrl = ev.target.result; await loadImageSize(dataUrl); currentPreviewDataUrl = dataUrl; currentTargetId = targetId; currentPosX = 50; currentPosY = 50; document.getElementById('listBgSize').value = 'cover'; currentBgSize = 'cover'; previewImg.style.backgroundImage = `url("${dataUrl}")`; navImg.style.backgroundImage = `url("${dataUrl}")`; placeholder.style.display = 'none'; navEl.style.display = 'block'; previewEl.style.cursor = 'grab'; saveAndApply(true); alert('背景图已上传并实时应用'); }; reader.readAsDataURL(file); input.value = ''; }); }; if (currentCid) handleUpload(document.getElementById('currentBgUpload'), currentCid); handleUpload(document.getElementById('defaultBgUpload'), '__default__'); // 清除背景 document.getElementById('clearCurrentBg').onclick = async () => { if (!currentCid) return alert('当前不是视频播放页'); if (!confirm('确定清除当前列表背景?')) return; if (_global.__biliListDeleteBgImage) await _global.__biliListDeleteBgImage(currentCid); else deleteListBgImage(currentCid); loadBgToPreview(currentCid); alert('已清除'); }; document.getElementById('clearDefaultBg').onclick = async () => { if (!confirm('确定清除默认背景?')) return; if (_global.__biliListDeleteBgImage) await _global.__biliListDeleteBgImage('__default__'); else deleteListBgImage('__default__'); loadBgToPreview(currentCid || '__default__'); alert('已清除'); }; } // ========== 个人面板背景编辑器(与列表背景完全独立) ========== const pBgPreview = document.getElementById('panelBgPreview'); const pBgPreviewImg = document.getElementById('panelBgPreviewImg'); const pBgPlaceholder = document.getElementById('panelBgPlaceholder'); const pBgNav = document.getElementById('panelBgNav'); const pBgNavImg = document.getElementById('panelBgNavImg'); const pBgNavFrame = document.getElementById('panelBgNavFrame'); const P_PREVIEW_W = 280, P_PREVIEW_H = 360; let pBgNaturalW = 0, pBgNaturalH = 0; let pBgIsDragging = false, pBgIsDraggingNav = false; let pBgDragStartX = 0, pBgDragStartY = 0; let pBgDragStartPosX = 50, pBgDragStartPosY = 50; const panelCalcRenderSize = (sizeMode, containerW, containerH, imgW, imgH) => { if (!imgW || !imgH) return { renderW: containerW, renderH: containerH }; let renderW, renderH; if (sizeMode === 'cover') { const scale = Math.max(containerW / imgW, containerH / imgH); renderW = imgW * scale; renderH = imgH * scale; } else if (sizeMode === 'contain') { const scale = Math.min(containerW / imgW, containerH / imgH); renderW = imgW * scale; renderH = imgH * scale; } else if (sizeMode === 'stretch' || sizeMode === '100% 100%') { renderW = containerW; renderH = containerH; } else { renderW = imgW; renderH = imgH; } return { renderW, renderH }; }; const updatePanelNavFrame = () => { if (!pBgNaturalW || !pBgNaturalH || !panelBgDataUrl) { pBgNavFrame.style.display = 'none'; return; } const { renderW, renderH } = panelCalcRenderSize(panelBgSize, P_PREVIEW_W, P_PREVIEW_H, pBgNaturalW, pBgNaturalH); const imgOffsetX = (P_PREVIEW_W - renderW) * panelBgPosX / 100; const imgOffsetY = (P_PREVIEW_H - renderH) * panelBgPosY / 100; const visibleX = Math.max(0, -imgOffsetX); const visibleY = Math.max(0, -imgOffsetY); const visibleW = Math.min(renderW, P_PREVIEW_W - Math.max(0, imgOffsetX)); const visibleH = Math.min(renderH, P_PREVIEW_H - Math.max(0, imgOffsetY)); if (visibleW <= 1 || visibleH <= 1) { pBgNavFrame.style.display = 'none'; return; } const navScale = Math.min(70 / pBgNaturalW, 90 / pBgNaturalH); const navImgW = pBgNaturalW * navScale; const navImgH = pBgNaturalH * navScale; const navOffX = (70 - navImgW) / 2; const navOffY = (90 - navImgH) / 2; const scale = renderW / pBgNaturalW; pBgNavFrame.style.display = 'block'; pBgNavFrame.style.left = (navOffX + visibleX / scale * navScale) + 'px'; pBgNavFrame.style.top = (navOffY + visibleY / scale * navScale) + 'px'; pBgNavFrame.style.width = Math.max(4, visibleW / scale * navScale) + 'px'; pBgNavFrame.style.height = Math.max(4, visibleH / scale * navScale) + 'px'; }; // 保存个人面板背景(预览+DB+实际面板实时应用) const savePanelBgAndApply = () => { const opacity = Number(document.getElementById('panelBgOpacity').value); const position = `${panelBgPosX}% ${panelBgPosY}%`; pBgPreviewImg.style.opacity = opacity; pBgPreviewImg.style.backgroundSize = panelBgSize; pBgPreviewImg.style.backgroundPosition = position; document.getElementById('panelBgOpacityVal').textContent = Math.round(opacity*100) + '%'; config.panelBgOpacity = opacity; panelBgPos = position; if (panelBgDataUrl) { savePanelBgImage(panelBgDataUrl, { opacity, bgSize: panelBgSize, bgPosition: position }); } applyPanelBackground(); updatePanelNavFrame(); }; const loadPanelImgSize = (dataUrl) => new Promise(resolve => { const img = new Image(); img.onload = () => { pBgNaturalW = img.naturalWidth; pBgNaturalH = img.naturalHeight; resolve(); }; img.onerror = () => resolve(); img.src = dataUrl; }); // 从DB加载个人面板背景到预览 const loadPanelBgToPreview = async () => { const entry = await getPanelBgImage(); if (entry && entry.imageData) { panelBgDataUrl = entry.imageData; pBgPreviewImg.style.backgroundImage = `url("${entry.imageData}")`; pBgNavImg.style.backgroundImage = `url("${entry.imageData}")`; pBgPlaceholder.style.display = 'none'; pBgNav.style.display = 'block'; pBgPreview.style.cursor = 'grab'; if (entry.opacity !== undefined) document.getElementById('panelBgOpacity').value = entry.opacity; if (entry.bgSize) { panelBgSize = entry.bgSize; document.getElementById('panelBgSize').value = entry.bgSize.endsWith('px') ? 'cover' : entry.bgSize; } if (entry.bgPosition) { const m = entry.bgPosition.match(/(-?\d+(?:\.\d+)?)%\s+(-?\d+(?:\.\d+)?)%/); if (m) { panelBgPosX = parseFloat(m[1]); panelBgPosY = parseFloat(m[2]); } } await loadPanelImgSize(entry.imageData); } else { panelBgDataUrl = null; pBgNaturalW = 0; pBgNaturalH = 0; pBgPreviewImg.style.backgroundImage = 'none'; pBgNavImg.style.backgroundImage = 'none'; pBgPlaceholder.style.display = 'block'; pBgNav.style.display = 'none'; pBgPreview.style.cursor = 'default'; panelBgPosX = 50; panelBgPosY = 50; } savePanelBgAndApply(); }; loadPanelBgToPreview(); // 绑定个人面板背景控件 document.getElementById('panelBgOpacity').addEventListener('input', savePanelBgAndApply); document.getElementById('panelBgSize').addEventListener('change', (e) => { panelBgSize = e.target.value; panelBgPosX = 50; panelBgPosY = 50; savePanelBgAndApply(); }); // 主预览拖动 pBgPreview.addEventListener('mousedown', (e) => { if (e.target.closest('#panelBgNav')) return; if (!panelBgDataUrl || !pBgNaturalW) return; pBgIsDragging = true; pBgDragStartX = e.clientX; pBgDragStartY = e.clientY; pBgDragStartPosX = panelBgPosX; pBgDragStartPosY = panelBgPosY; pBgPreview.style.cursor = 'grabbing'; e.preventDefault(); }); // 小地图拖动 pBgNav.addEventListener('mousedown', (e) => { if (!panelBgDataUrl || !pBgNaturalW) return; pBgIsDraggingNav = true; pBgDragStartX = e.clientX; pBgDragStartY = e.clientY; pBgDragStartPosX = panelBgPosX; pBgDragStartPosY = panelBgPosY; e.preventDefault(); e.stopPropagation(); }); document.addEventListener('mousemove', (e) => { if (!pBgIsDragging && !pBgIsDraggingNav) return; const dx = e.clientX - pBgDragStartX; const dy = e.clientY - pBgDragStartY; const { renderW, renderH } = panelCalcRenderSize(panelBgSize, P_PREVIEW_W, P_PREVIEW_H, pBgNaturalW, pBgNaturalH); const denomX = P_PREVIEW_W - renderW; const denomY = P_PREVIEW_H - renderH; let dpx = 0, dpy = 0; if (pBgIsDraggingNav) { const navScale = Math.min(70 / pBgNaturalW, 90 / pBgNaturalH); const scale = renderW / pBgNaturalW; const factor = navScale * scale; dpx = denomX !== 0 ? (-dx / factor) * 100 / denomX : 0; dpy = denomY !== 0 ? (-dy / factor) * 100 / denomY : 0; } else { dpx = denomX !== 0 ? (-dx) * 100 / denomX : 0; dpy = denomY !== 0 ? (-dy) * 100 / denomY : 0; } panelBgPosX = pBgDragStartPosX + dpx; panelBgPosY = pBgDragStartPosY + dpy; // 允许上下左右各拖出500px空白 const extraX = denomX !== 0 ? (500 / Math.abs(denomX)) * 100 : 300; const extraY = denomY !== 0 ? (500 / Math.abs(denomY)) * 100 : 300; panelBgPosX = Math.max(-extraX, Math.min(100 + extraX, panelBgPosX)); panelBgPosY = Math.max(-extraY, Math.min(100 + extraY, panelBgPosY)); savePanelBgAndApply(); }); document.addEventListener('mouseup', () => { pBgIsDragging = false; pBgIsDraggingNav = false; pBgPreview.style.cursor = panelBgDataUrl ? 'grab' : 'default'; }); // 个人面板背景上传(存入IndexedDB,不占GM存储) document.getElementById('panelBgUpload').addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; if (!file.type.startsWith('image/')) { alert('请选择图片'); return; } const reader = new FileReader(); reader.onload = async (ev) => { const dataUrl = ev.target.result; await loadPanelImgSize(dataUrl); config.panelBgImageSet = true; panelBgDataUrl = dataUrl; panelBgSize = 'cover'; panelBgPosX = 50; panelBgPosY = 50; document.getElementById('panelBgSize').value = 'cover'; pBgPreviewImg.style.backgroundImage = `url("${dataUrl}")`; pBgNavImg.style.backgroundImage = `url("${dataUrl}")`; pBgPlaceholder.style.display = 'none'; pBgNav.style.display = 'block'; pBgPreview.style.cursor = 'grab'; await savePanelBgImage(dataUrl, { opacity: Number(document.getElementById('panelBgOpacity').value), bgSize: 'cover', bgPosition: '50% 50%' }); savePanelBgAndApply(); saveConfig(); // 持久化panelBgImageSet等配置到GM(内部会rebuild),刷新后不丢失 alert('个人面板背景已上传并实时应用'); }; reader.readAsDataURL(file); e.target.value = ''; }); // 清除个人面板背景(从DB删除) document.getElementById('clearPanelBg').onclick = () => { if (!config.panelBgImageSet && !panelBgDataUrl) return alert('当前没有设置背景图'); if (!confirm('确定清除个人面板背景?')) return; config.panelBgImageSet = false; panelBgDataUrl = null; panelBgSize = 'cover'; panelBgPosX = 50; panelBgPosY = 50; pBgPreviewImg.style.backgroundImage = 'none'; pBgNavImg.style.backgroundImage = 'none'; pBgPlaceholder.style.display = 'block'; pBgNav.style.display = 'none'; pBgPreview.style.cursor = 'default'; deletePanelBgImage().then(() => { saveConfig(); // 持久化清除状态(内部会rebuild) }); }; const realtimeApply = () => { config.bgWatermarkNum = document.getElementById('bgNum').value; config.bgRotate = Number(document.getElementById('bgRotate').value) || 0; config.bgBreathAnim = document.getElementById('bgBreath').checked; config.bgFrosted = document.getElementById('bgFrosted').checked; config.glassBlurDark = Number(document.getElementById('glassBlurDark').value) || 12; config.glassBlurLight = Number(document.getElementById('glassBlurLight').value) || 12; config.wmSizeDark = Number(document.getElementById('wmSizeDark').value) || 600; config.wmSizeLight = Number(document.getElementById('wmSizeLight').value) || 500; config.wmColorDark = document.getElementById('wmColorDark').value; config.wmColorLight = document.getElementById('wmColorLight').value; config.bgOpacityDark = Number(document.getElementById('bgOpacityDark').value) || 0.15; config.bgOpacityLight = Number(document.getElementById('bgOpacityLight').value) || 0.08; config.showHardCoreTag = document.getElementById('showHC').checked; config.hardCoreText = document.getElementById('hcText').value; config.primaryDark = document.getElementById('darkPrimary').value; config.primaryLight = document.getElementById('lightPrimary').value; // 实时预览并立即持久化到GM存储(面板提示"自动保存生效"),刷新后不丢失 saveConfig(); }; // 所有控件绑定实时更新(panelBgOpacity已由个人面板背景编辑器单独处理) ['bgNum','bgRotate','wmSizeDark','wmSizeLight','bgOpacityDark','bgOpacityLight','glassBlurDark','glassBlurLight','hcText'].forEach(id => { document.getElementById(id).addEventListener('input', realtimeApply); }); ['bgBreath','bgFrosted','showHC','wmColorDark','wmColorLight','darkPrimary','lightPrimary'].forEach(id => { document.getElementById(id).addEventListener('change', realtimeApply); document.getElementById(id).addEventListener('input', realtimeApply); }); document.getElementById('saveConfig').onclick = () => { realtimeApply(); if (listMgrExists) { const _g = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; // 配置已经实时保存了,这里再确认一下 const newBeautyCfg = { frostedGlass: document.getElementById('listFrostedGlass').checked, glassBlur: Number(document.getElementById('listGlassBlur').value) || 12, drawerOpacity: Number(document.getElementById('listDrawerOpacity').value) || 0.85, defaultBgOpacity: Number(document.getElementById('listBgOpacity').value) || 0.15, bgSize: document.getElementById('listBgSize').value, bgPosition: `${currentPosX}% ${currentPosY}%` }; saveListBeautyConfig(newBeautyCfg); if (_g.__biliListRefreshBg) _g.__biliListRefreshBg(); } saveConfig(); showToast('✅ 配置已保存'); }; document.getElementById('resetConfig').onclick = () => { if (!confirm('恢复默认配置?')) return; config = { ...DEFAULT_PANEL_CONFIG }; panelBgDataUrl = null; deletePanelBgImage(); saveConfig(); rebuild(); }; document.getElementById('closeConfig').onclick = () => { panel.remove(); configPanelEl = null; }; } // ====================== 菜单 ====================== GM_registerMenuCommand(`面板美化:${spaceEnabled?'关闭':'开启'}`, toggle); GM_registerMenuCommand('设置目标UID', updateUid); GM_registerMenuCommand('昵称布局编辑器', openEditor); GM_registerMenuCommand('个性化配置面板', openConfig); // ====================== DOM 监听 ====================== function startObserver() { if (observer) return; observer = new MutationObserver(throttle(() => { if (!spaceEnabled) return; const panel = document.querySelector('.avatar-panel-popover'); if (!panel) return; applyThemeClass(); applyPanelBackground(); if (config.showHardCoreTag) insertTag(); else removeTag(); applyNickname(); }, 150)); observer.observe(document.body, { childList: true, subtree: true }); } let lastUrl = location.href; setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; tagInserted = false; if (spaceEnabled) setTimeout(rebuild, 400); } }, 1000); // ====================== 启动 ====================== observeHtmlTheme(); if (spaceEnabled) setTimeout(rebuild, 300); startObserver(); let lastTheme = getTheme(); setInterval(() => { const html = document.documentElement; const dt = html.getAttribute('data-theme'); const hasDark = html.classList.contains('dark'); const hasLight = html.classList.contains('light'); const hasBiliDark = html.classList.contains('bili_dark'); const ls = localStorage.getItem('bilibili_theme'); const current = getTheme(); const allAttrs = {}; for (let i = 0; i < html.attributes.length; i++) { allAttrs[html.attributes[i].name] = html.attributes[i].value; } const bg = getComputedStyle(document.body).backgroundColor; const rootBg = getComputedStyle(html).getPropertyValue('--app-bg').trim() || '(empty)'; const rootColor = getComputedStyle(html).color; if (current !== lastTheme) { lastTheme = current; applyThemeClass(); } }, 6000); })(); /****************************************************************************** * 主题系统架构说明 —— 修改前必读 * ============================================================================ * * 一、背景:B站主题切换机制 * - B站新版深/浅切换 不设 data-theme 属性,不给 加 dark/light class * → 唯一可靠的切换检测手段是监听 body 背景色亮度变化 * - 是 B站固定 class,深浅模式下都存在,不是主题标记 * - localStorage('bilibili_theme') 可读但 B站切换时不更新 * * 二、检测策略(getTheme 函数) * 优先级从高到低: * 1. document.documentElement.getAttribute('data-theme') * → B站某些页面(如设置页)会设此属性 * 2. html.classList.contains('light') / contains('dark') * → B站部分子页面会设 * 3. localStorage.getItem('bilibili_theme') * → 仅兜底,B站切换时不更新 * 4. 计算 document.body 背景色亮度(核心手段) * → 亮度 < 128 判定为深色,≥ 128 判定为浅色 * 5. 以上均失败 → 默认 'dark' * * 轮询(setInterval 每 2 秒)检测上述任一值变化, * 一旦检测到切换 → 调用 applyThemeClass 更新 panel 的 theme class。 * * 禁止:移除或替换 body 背景色检测;禁止将 gray 作为主题标记。 * * 三、CSS 架构(两套定义 + 两个域) * * 【第一套:html[data-theme] 驱动 — 切换无延迟】 * 选择器:html[data-theme="dark"] .avatar-panel-popover * html.dark .avatar-panel-popover * html[data-theme="light"] .avatar-panel-popover * html.light .avatar-panel-popover * 作用:当 B站设了 data-theme 或 dark/light class 时即时生效。 * 目前 B站深浅切换时不会触发此套,保留做兼容。 * * 【第二套:panel class 驱动 — 兜底主力】 * 选择器:.theme-dark.avatar-panel-popover * .theme-light.avatar-panel-popover * 作用:由 applyThemeClass 设置 panel 的 theme-dark / theme-light class。 * B站浅色模式无任何 html 标记时,此套为唯一的样式来源。 * 必须包含全部变量(核心变量 + 装饰变量),不可省略。 * * 两个域: * A. 面板内部元素(.avatar-panel-popover 的子元素) * → 使用 CSS 变量 var(--xxx),变量定义在 panel class 的 CSS 块中。 * 优点:不受 B站子元素加 .dark / .light class 影响。 * 所有 var() 必须加 !important,防止被 B站样式覆盖。 * * B. 面板外部元素(.sub-links-item 等弹窗) * → 直接使用 html[data-theme] 选择器硬编码值,不用 var()。 * 因为这些元素不在 panel 内部,继承不到 panel 上的 CSS 变量。 * * 四、绝对不能做的事 * 1. 不要删除 body 背景色检测(getTheme 第 4 步)— 它是深浅切换的唯一可靠信号 * 2. 不要将 html.gray 作为主题标记加入检测或 CSS — gray 始终存在 * 3. 不要拆掉 .theme-dark / .theme-light 中的核心变量(panel-bg, logout-bg 等) * — 它们是 B站浅色模式无 html 标记时的唯一样式来源 * 4. 不要去掉子元素样式中的 !important — B站会通过子元素 class 覆盖 * 5. 不要删除 2 秒轮询(setInterval)— MutationObserver 监听不到 body 背景色变化 * 6. 不要用两套 CSS 重新生成代替 class 切换 — 必须用 class 切换,不重建