// ==UserScript== // @name B站个人面板美化 // @namespace http://tampermonkey.net/ // @version 1.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' }; const DEFAULT_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' }; const DEFAULT_NICKNAME_LAYOUT = { 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_CONFIG }); config = { ...DEFAULT_CONFIG, ...config }; 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_setValue(STORAGE.CUSTOM_CONFIG, config); 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:"${bgWatermarkNum}"; 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 0.3s ease; cursor:pointer; } .counts-item .single-count-item:hover { transform:scale(1.3); 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); } } `; } // ====================== 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(); 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()); 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() { const panel = document.querySelector('.avatar-panel-popover'); if (!panel) return; document.querySelectorAll('.nickname-layout-container').forEach(c => c.remove()); 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;'; if (nicknameLayout?.columns) { nicknameLayout.columns.forEach(col => { const colDiv = document.createElement('div'); colDiv.className = 'nickname-col'; colDiv.style.cssText = `position:relative; width:${col.width}; height:100%;`; if (col.blocks) { col.blocks.forEach(block => { 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); white-space:pre; pointer-events:none; line-height:1; font-weight:bold; `; colDiv.appendChild(span); }); } container.appendChild(colDiv); }); 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; } editorBackup = JSON.parse(JSON.stringify(nicknameLayout)); const panel = document.createElement('div'); panel.id = 'bili-nickname-editor'; panel.style.cssText = ` position:fixed; top:50px; left:50%; transform:translateX(-50%); width:700px; max-width:95vw; background:rgba(20,20,30,0.92); backdrop-filter:blur(14px); color:#fff; border-radius:14px; box-shadow:0 6px 24px rgba(0,0,0,0.5); z-index:100000; padding:16px; font-size:13px; max-height:90vh; overflow-y:auto; `; panel.innerHTML = `

昵称拖拽布局编辑器

`; document.body.appendChild(panel); editorEl = panel; renderCols(); renderPreview(); bindEditorEvents(); document.getElementById('addColBtn').onclick = () => { nicknameLayout.columns.push({ id: uid(), width: '100%', blocks: [] }); 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: 5, color: '#FF69B4', fontSize: 20, rotation: 0 }); renderPreview(); renderCols(); }; document.getElementById('saveNickBtn').onclick = () => { saveLayout(); closeEditor(true); }; document.getElementById('cancelNickBtn').onclick = () => { nicknameLayout = JSON.parse(JSON.stringify(editorBackup)); applyNickname(); closeEditor(false); }; } 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 = ''; const idx = activeColIdx(); const col = nicknameLayout.columns[idx]; if (!col) return; const colDiv = document.createElement('div'); colDiv.style.cssText = 'position:relative; width:100%; height:100%;'; 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.2); padding:1px 2px; background:rgba(0,0,0,0.3); `; span.dataset.blockId = block.id; span.addEventListener('mousedown', onDragStart); span.addEventListener('click', e => { e.stopPropagation(); selectBlock(block.id); }); colDiv.appendChild(span); }); preview.appendChild(colDiv); } let dragState = null; function onDragStart(e) { e.preventDefault(); const preview = document.getElementById('editorPreview'); const rect = preview.getBoundingClientRect(); const idx = activeColIdx(); const block = nicknameLayout.columns[idx].blocks.find(b => b.id === e.target.dataset.blockId); if (!block) return; dragState = { block, 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; dragState.block.x = Math.max(-40, Math.min(85+40, dragState.origX + e.clientX - dragState.startX)); dragState.block.y = Math.max(-40, Math.min(38+40, dragState.origY + e.clientY - dragState.startY)); renderPreview(); applyNickname(); } function onDragEnd() { window.removeEventListener('mousemove', onDragMove); window.removeEventListener('mouseup', onDragEnd); dragState = null; } function selectBlock(blockId) { const idx = activeColIdx(); const block = nicknameLayout.columns[idx].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 = idx; } function bindEditorEvents() { ['blockText','blockColor','blockSize','blockRotate'].forEach(id => { document.getElementById(id).oninput = () => { const p = document.getElementById('blockProps'); const block = nicknameLayout.columns[parseInt(p.dataset.colIndex)]?.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) || 20; block.rotation = parseFloat(document.getElementById('blockRotate').value) || 0; renderPreview(); 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 panel = document.createElement('div'); panel.id = 'bili-custom-config-panel'; panel.style.cssText = ` position:fixed; top:80px; left:20px; width:360px; 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:80vh; overflow-y:auto; `; panel.innerHTML = `

个性化面板配置

px
px
px
px
`; 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; document.getElementById('saveConfig').onclick = () => { config.bgWatermarkNum = document.getElementById('bgNum').value.slice(0,2); 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; saveConfig(); alert('已保存'); }; document.getElementById('resetConfig').onclick = () => { if (confirm('恢复默认配置?')) { config = {...DEFAULT_CONFIG}; saveConfig(); } }; 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(); 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(); } }, 2000); })(); /****************************************************************************** * 主题系统架构说明 —— 修改前必读 * ============================================================================ * * 一、背景: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 切换,不重建