// ==UserScript== // @name Bilibili 剧集双语字幕模糊遮罩与生词本 (看剧学英语) // @namespace https://github.com/CaptionNoChinese // @version 2.1.0 // @description 在 B 站看剧学英语:无边框羽化光学模糊中文字幕,Alt+滚轮精准盲调无误触;一键截取高清剧照 + 精准时间戳定位与侧边生词复习抽屉。 // @author Antigravity // @match *://*.bilibili.com/* // @match *://bilibili.com/* // @grant GM_setValue // @grant GM_getValue // @run-at document-start // @license MIT // ==/UserScript== (function () { 'use strict'; console.log('%c[看剧学英语 v2.1.0] 启动中:无边框羽化模糊 + 一键高清快照 + 时间戳秒跳 + 清爽复习抽屉', 'background: #00aeec; color: #fff; padding: 3px 8px; border-radius: 4px; font-weight: bold;'); // 默认配置(百分比 %) const PRESET_BOTTOM = { left: 15, top: 84, width: 70, height: 7.2 }; const PRESET_TOP = { left: 15, top: 6.5, width: 70, height: 7.2 }; const DEFAULT_GLOBAL = { enabled: true, ...PRESET_BOTTOM }; const CONFIG_STORAGE_KEY = 'bili_caption_mask_v200_cfg'; const NOTES_STORAGE_KEY = 'bili_caption_notes_v200'; // 剧集/系列标识提取与标题清洗 function cleanTitle(raw) { if (!raw) return '未知视频'; return raw .replace(/-电视剧-全集.*$/, '') .replace(/-番剧-全集.*$/, '') .replace(/-电影-全集.*$/, '') .replace(/-纪录片-全集.*$/, '') .replace(/-高清正版在线观看.*$/, '') .replace(/_哔哩哔哩_bilibili.*$/, '') .replace(/_哔哩哔哩.*$/, '') .trim(); } function getSeriesInfo() { let id = 'global_default'; let title = cleanTitle(document.title); let epName = ''; try { // 番剧 / 电视剧(如《老友记》) const bgmMatch = location.pathname.match(/\/bangumi\/play\/(ss\d+|ep\d+)/); if (bgmMatch) { if (window.__INITIAL_STATE__?.mediaInfo?.season_id) { id = 'season_' + window.__INITIAL_STATE__.mediaInfo.season_id; title = cleanTitle(window.__INITIAL_STATE__.mediaInfo.title || title); } else { id = 'bgm_' + bgmMatch[1]; } epName = window.__INITIAL_STATE__?.epInfo?.title || window.__INITIAL_STATE__?.epInfo?.long_title || ''; } else { // 普通视频多 P / 合集 const bvMatch = location.pathname.match(/\/(BV[a-zA-Z0-9]+)/); if (bvMatch) { id = 'bv_' + bvMatch[1]; } const curP = document.querySelector('.cur-page, .ep-item.cursor'); if (curP) epName = curP.textContent.trim(); } } catch (e) {} return { id, title, epName }; } // 配置存储管理 let storeState = { global: { ...DEFAULT_GLOBAL }, series: {} }; function loadConfigStore() { try { let saved = null; if (typeof GM_getValue === 'function') saved = GM_getValue(CONFIG_STORAGE_KEY, null); if (!saved) saved = localStorage.getItem(CONFIG_STORAGE_KEY); if (saved) { const parsed = typeof saved === 'string' ? JSON.parse(saved) : saved; storeState = Object.assign({ global: { ...DEFAULT_GLOBAL }, series: {} }, parsed); } } catch (e) { console.warn('[字幕遮罩] 配置读取异常', e); } } function saveConfigStore() { const str = JSON.stringify(storeState); try { if (typeof GM_setValue === 'function') GM_setValue(CONFIG_STORAGE_KEY, str); } catch (e) {} try { localStorage.setItem(CONFIG_STORAGE_KEY, str); } catch (e) {} } loadConfigStore(); let currentSeriesKey = getSeriesInfo().id; let config = Object.assign({}, storeState.global, storeState.series[currentSeriesKey] || {}); function syncSeriesConfig() { const newKey = getSeriesInfo().id; if (newKey !== currentSeriesKey) { currentSeriesKey = newKey; config = Object.assign({}, storeState.global, storeState.series[currentSeriesKey] || {}); } } function saveActiveConfig() { currentSeriesKey = getSeriesInfo().id; storeState.series[currentSeriesKey] = { left: config.left, top: config.top, width: config.width, height: config.height }; storeState.global.enabled = config.enabled; saveConfigStore(); } // 生词笔记存储管理 let notesMap = {}; // { [seriesId]: [ NoteItem ] } function loadNotes() { try { let saved = null; if (typeof GM_getValue === 'function') saved = GM_getValue(NOTES_STORAGE_KEY, null); if (!saved) saved = localStorage.getItem(NOTES_STORAGE_KEY); if (saved) { notesMap = typeof saved === 'string' ? JSON.parse(saved) : saved; // 自动清理历史卡片里的旧版本 OCR 乱码字段,并规范化标题 let modified = false; for (const key in notesMap) { if (Array.isArray(notesMap[key])) { notesMap[key].forEach(note => { if (note.englishText !== undefined) { delete note.englishText; // 彻底清除旧版本遗留的乱码字段 modified = true; } if (note.seriesTitle) { const cleaned = cleanTitle(note.seriesTitle); if (cleaned !== note.seriesTitle) { note.seriesTitle = cleaned; modified = true; } } }); } } if (modified) saveNotes(); } } catch (e) { console.warn('[字幕遮罩] 笔记读取异常', e); } } function saveNotes() { const str = JSON.stringify(notesMap); try { if (typeof GM_setValue === 'function') GM_setValue(NOTES_STORAGE_KEY, str); } catch (e) {} try { localStorage.setItem(NOTES_STORAGE_KEY, str); } catch (e) {} } loadNotes(); function getNotesForCurrentSeries() { const sid = getSeriesInfo().id; return notesMap[sid] || []; } function addNote(note) { const sid = getSeriesInfo().id; if (!notesMap[sid]) notesMap[sid] = []; notesMap[sid].unshift(note); saveNotes(); updateDrawerBadge(); } function deleteNote(noteId) { const sid = getSeriesInfo().id; if (notesMap[sid]) { notesMap[sid] = notesMap[sid].filter(n => n.id !== noteId); saveNotes(); updateDrawerBadge(); renderDrawerList(); } } function updateNoteUserText(noteId, text) { const sid = getSeriesInfo().id; if (notesMap[sid]) { const item = notesMap[sid].find(n => n.id === noteId); if (item) { item.userNote = text; saveNotes(); } } } let isEditMode = false; let isAltPeeking = false; let isWheeling = false; let wheelTimer = null; let isDrawerOpen = false; // 全局样式注入 function injectStyles() { if (document.getElementById('bili-caption-mask-styles')) return; const styleEl = document.createElement('style'); styleEl.id = 'bili-caption-mask-styles'; styleEl.textContent = ` /* 遮罩主体:无边框羽化光学模糊 */ .bili-caption-blur-mask { position: absolute !important; z-index: 99 !important; box-sizing: border-box !important; background: transparent !important; backdrop-filter: blur(14px) !important; -webkit-backdrop-filter: blur(14px) !important; cursor: pointer !important; user-select: none !important; pointer-events: auto !important; /* 四周渐变羽化 */ -webkit-mask-image: linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%), linear-gradient(to bottom, transparent 0%, black 16%, black 84%, transparent 100%) !important; -webkit-mask-composite: source-in !important; mask-image: linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%), linear-gradient(to bottom, transparent 0%, black 16%, black 84%, transparent 100%) !important; mask-composite: intersect !important; transition: backdrop-filter 0.12s ease, -webkit-backdrop-filter 0.12s ease !important; } /* 鼠标悬停 或 偷瞄透出 */ .bili-caption-blur-mask:not(.in-edit-mode):not(.wheeling-active):hover, .bili-caption-blur-mask.peek-active:not(.wheeling-active) { backdrop-filter: blur(0px) !important; -webkit-backdrop-filter: blur(0px) !important; -webkit-mask-image: none !important; mask-image: none !important; } /* 滚轮微调激活状态:高亮清晰显示虚线轮廓,让用户看清移动轨迹 */ .bili-caption-blur-mask.wheeling-active { -webkit-mask-image: none !important; mask-image: none !important; outline: 2px solid #00aeec !important; background: rgba(0, 174, 236, 0.22) !important; backdrop-filter: blur(6px) !important; -webkit-backdrop-filter: blur(6px) !important; box-shadow: 0 0 16px rgba(0, 174, 236, 0.6) !important; } /* 隐藏状态 */ .bili-caption-blur-mask.mask-hidden { display: none !important; } /* 编辑模式外观 */ .bili-caption-blur-mask.in-edit-mode { -webkit-mask-image: none !important; mask-image: none !important; outline: 2px dashed #00aeec !important; background: rgba(0, 174, 236, 0.25) !important; backdrop-filter: blur(4px) !important; -webkit-backdrop-filter: blur(4px) !important; cursor: move !important; } /* 编辑模式顶部操作浮条 */ .bili-caption-mask-header { position: absolute; top: -34px; right: 0; display: none; align-items: center; gap: 8px; background: rgba(18, 18, 20, 0.95); border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 4px; padding: 3px 10px; font-size: 12px; color: #fff; box-shadow: 0 4px 12px rgba(0,0,0,0.5); white-space: nowrap; z-index: 105; pointer-events: auto; } .in-edit-mode .bili-caption-mask-header { display: flex !important; } .bili-caption-mask-btn { background: #00aeec; color: #fff; border: none; border-radius: 3px; padding: 2px 8px; font-size: 12px; cursor: pointer; line-height: 18px; transition: background 0.2s; } .bili-caption-mask-btn:hover { background: #009cd3; } /* 拉伸手柄 */ .bili-caption-resize-handle { position: absolute; width: 10px; height: 10px; background: #00aeec; border: 1.5px solid #ffffff; border-radius: 2px; display: none; z-index: 102; box-sizing: border-box; } .in-edit-mode .bili-caption-resize-handle { display: block !important; } .handle-n { top: -5px; left: 50%; transform: translateX(-50%); cursor: ns-resize; } .handle-s { bottom: -5px; left: 50%; transform: translateX(-50%); cursor: ns-resize; } .handle-w { left: -5px; top: 50%; transform: translateY(-50%); cursor: ew-resize; } .handle-e { right: -5px; top: 50%; transform: translateY(-50%); cursor: ew-resize; } .handle-nw { top: -5px; left: -5px; cursor: nwse-resize; } .handle-ne { top: -5px; right: -5px; cursor: nesw-resize; } .handle-sw { bottom: -5px; left: -5px; cursor: nesw-resize; } .handle-se { bottom: -5px; right: -5px; cursor: nwse-resize; } /* 播放器右上角常驻微透明 Dock */ .bili-caption-control-dock { position: absolute; top: 14px; right: 14px; z-index: 98; display: flex; align-items: center; gap: 6px; background: rgba(14, 14, 16, 0.75); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 18px; padding: 4px 12px; font-size: 12px; color: #e5e5e5; opacity: 0.65; transition: opacity 0.25s, transform 0.25s; cursor: default; user-select: none; pointer-events: auto; } .bili-caption-control-dock:hover { opacity: 1; transform: scale(1.02); } .bili-caption-dock-btn { background: transparent; border: none; color: #00aeec; cursor: pointer; font-size: 12px; padding: 1px 4px; border-radius: 3px; transition: color 0.15s; } .bili-caption-dock-btn:hover { color: #ffffff; text-decoration: underline; } /* 浮动 Toast 提示 */ .bili-caption-toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%); background: rgba(18, 18, 20, 0.92); backdrop-filter: blur(12px); color: #ffffff; padding: 8px 20px; border-radius: 20px; font-size: 13px; pointer-events: none; z-index: 9999999; opacity: 0; transition: opacity 0.25s ease, transform 0.25s ease; box-shadow: 0 4px 18px rgba(0,0,0,0.5); border: 1px solid rgba(255, 255, 255, 0.12); } .bili-caption-toast.show { opacity: 1; transform: translateX(-50%) translateY(6px); } /* =================== 侧边生词复习抽屉 =================== */ .bili-caption-drawer { position: fixed; top: 0; right: 0; width: 380px; height: 100vh; background: rgba(22, 23, 26, 0.96); backdrop-filter: blur(18px); box-shadow: -6px 0 25px rgba(0, 0, 0, 0.6); border-left: 1px solid rgba(255, 255, 255, 0.1); z-index: 999999; display: flex; flex-direction: column; transform: translateX(100%); transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); color: #f1f2f3; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', sans-serif; } .bili-caption-drawer.open { transform: translateX(0); } .drawer-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); } .drawer-title { font-size: 15px; font-weight: bold; display: flex; align-items: center; gap: 6px; } .drawer-header-actions { display: flex; gap: 8px; align-items: center; } .drawer-btn { background: #00aeec; border: none; color: #fff; font-size: 12px; padding: 4px 10px; border-radius: 4px; cursor: pointer; transition: background 0.2s; } .drawer-btn:hover { background: #009cd3; } .drawer-btn.btn-close { background: transparent; font-size: 16px; color: #999; padding: 2px 6px; } .drawer-btn.btn-close:hover { color: #fff; } .drawer-body { flex: 1; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column; gap: 14px; } .drawer-empty { text-align: center; color: #888; font-size: 13px; margin-top: 60px; line-height: 1.8; } /* 单张剧照卡片 */ .note-card { background: rgba(36, 38, 43, 0.85); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; transition: border-color 0.2s; } .note-card:hover { border-color: rgba(0, 174, 236, 0.4); } .note-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; font-size: 12px; color: #bbb; background: rgba(0, 0, 0, 0.25); } .note-time-badge { color: #00aeec; cursor: pointer; font-weight: 500; display: flex; align-items: center; gap: 3px; padding: 2px 7px; border-radius: 3px; background: rgba(0, 174, 236, 0.12); transition: background 0.15s; } .note-time-badge:hover { background: rgba(0, 174, 236, 0.28); text-decoration: underline; } .note-del-btn { background: transparent; border: none; color: #777; cursor: pointer; font-size: 13px; padding: 2px 4px; } .note-del-btn:hover { color: #ff5c5c; } .note-img { width: 100%; display: block; border-bottom: 1px solid rgba(255, 255, 255, 0.05); cursor: zoom-in; } .note-text-wrap { padding: 8px 12px 10px; display: flex; flex-direction: column; gap: 4px; } .note-user-input { font-size: 12px; color: #d1d5db; outline: none; padding: 5px 8px; border-radius: 4px; background: rgba(0, 0, 0, 0.2); border: 1px solid transparent; transition: all 0.2s; min-height: 24px; } .note-user-input:empty::before { content: attr(data-placeholder); color: #6b7280; } .note-user-input:focus { background: rgba(0, 0, 0, 0.4); border-color: #00aeec; } `; (document.head || document.documentElement).appendChild(styleEl); } // Toast 提示 let toastTimer = null; function showToast(text) { let toast = document.querySelector('.bili-caption-toast'); if (!toast) { toast = document.createElement('div'); toast.className = 'bili-caption-toast'; document.body.appendChild(toast); } toast.textContent = text; toast.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => { toast.classList.remove('show'); }, 1600); } // 寻找视频核心可绘制对象 function getDrawableVideoElement() { const video = document.querySelector('video'); if (video && video.videoWidth > 0) return video; const canvas = document.querySelector('.bpx-player-video-wrap canvas, .bilibili-player-video-wrap canvas'); if (canvas && canvas.width > 0) return canvas; return video || document.querySelector('bwp-video'); } // 寻找 B 站播放器核心外层容器 function findVideoWrap() { const selectors = [ '.bpx-player-video-wrap', '.bpx-player-video-area', '.squirtle-video-wrap', '.bilibili-player-video-wrap', '.bilibili-player-video', '#bilibili-player .bpx-player-video-wrap', '#playerWrap', '#player_module', '#bilibiliPlayer', '#bofqi' ]; for (const sel of selectors) { const el = document.querySelector(sel); if (el && el.clientWidth > 0 && el.clientHeight > 0) { return el; } } const media = document.querySelector('video, bwp-video'); if (media) { for (const sel of selectors) { const wrap = media.closest(sel); if (wrap) return wrap; } return media.parentElement; } return null; } // 抓取当前视频帧高清截图(优化 JPEG 体积) function captureCurrentVideoFrame(mediaEl) { if (!mediaEl) return null; try { const canvas = document.createElement('canvas'); const w = mediaEl.videoWidth || mediaEl.width || mediaEl.clientWidth || 1280; const h = mediaEl.videoHeight || mediaEl.height || mediaEl.clientHeight || 720; const scale = Math.min(1, 960 / w); canvas.width = Math.round(w * scale); canvas.height = Math.round(h * scale); const ctx = canvas.getContext('2d'); ctx.drawImage(mediaEl, 0, 0, canvas.width, canvas.height); return canvas.toDataURL('image/jpeg', 0.85); } catch (e) { console.warn('[字幕遮罩] 画面截取失败', e); return null; } } // 格式化时间 00:00 function formatTime(seconds) { if (!seconds || isNaN(seconds)) return '00:00'; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); return `${m < 10 ? '0' : ''}${m}:${s < 10 ? '0' : ''}${s}`; } // 执行一键快照采集(纯净极速模式:0延迟、无乱码) function triggerSnapshotCapture() { const media = getDrawableVideoElement(); if (!media) { showToast('⚠️ 未找到当前正在播放的画面'); return; } const info = getSeriesInfo(); const currentTime = media.currentTime || 0; const timeStr = formatTime(currentTime); // 瞬时截取当前帧画面 const snapshotUrl = captureCurrentVideoFrame(media); if (!snapshotUrl) { showToast('⚠️ 截取画面失败'); return; } const noteId = 'note_' + Date.now(); const newNote = { id: noteId, seriesId: info.id, seriesTitle: info.title, epTitle: info.epName, time: currentTime, timeStr: timeStr, imageUrl: snapshotUrl, userNote: '', createdAt: Date.now() }; addNote(newNote); renderDrawerList(); showToast(`⭐️ 剧照已收藏 [${timeStr}]!按 Alt+B 可在生词本查看`); } // 创建遮罩 DOM function createMaskElement() { const mask = document.createElement('div'); mask.className = 'bili-caption-blur-mask'; // 顶部编辑栏 const header = document.createElement('div'); header.className = 'bili-caption-mask-header'; header.innerHTML = ` ✏️ 调节中 (Alt+滚轮可微调) `; mask.appendChild(header); // 8 个拉伸 Handle const handles = ['n', 's', 'w', 'e', 'nw', 'ne', 'sw', 'se']; handles.forEach(pos => { const h = document.createElement('div'); h.className = `bili-caption-resize-handle handle-${pos}`; h.dataset.handle = pos; mask.appendChild(h); }); return mask; } // 创建右上角轻量控制徽标 function createDockElement() { const dock = document.createElement('div'); dock.className = 'bili-caption-control-dock'; const notesCount = getNotesForCurrentSeries().length; dock.innerHTML = ` 🔲 遮罩 | | | `; return dock; } function updateDrawerBadge() { const btn = document.querySelector('.btn-dock-notes'); if (btn) { const count = getNotesForCurrentSeries().length; btn.textContent = `📚 生词本 (${count})`; } } // 应用遮罩样式 function applyStyles(mask) { if (!mask) return; mask.style.left = `${config.left}%`; mask.style.top = `${config.top}%`; mask.style.width = `${config.width}%`; mask.style.height = `${config.height}%`; if (!config.enabled) { mask.classList.add('mask-hidden'); } else { mask.classList.remove('mask-hidden'); } if (isEditMode) { mask.classList.add('in-edit-mode'); } else { mask.classList.remove('in-edit-mode'); } if (isAltPeeking) { mask.classList.add('peek-active'); } else { mask.classList.remove('peek-active'); } if (isWheeling) { mask.classList.add('wheeling-active'); } else { mask.classList.remove('wheeling-active'); } const dockToggleBtn = document.querySelector('.btn-dock-toggle'); if (dockToggleBtn) { dockToggleBtn.textContent = config.enabled ? '已开启' : '已关闭'; dockToggleBtn.style.color = config.enabled ? '#00aeec' : '#aaa'; } const dockEditBtn = document.querySelector('.btn-dock-edit'); if (dockEditBtn) { dockEditBtn.textContent = isEditMode ? '完成锁定' : '调节'; } } // 绑定拖拽与拉伸 function setupDragAndResize(mask, getContainer) { let isDragging = false; let activeHandle = null; let startX = 0; let startY = 0; let initialConfig = null; mask.addEventListener('click', (e) => { if (e.target.classList.contains('btn-lock')) { e.stopPropagation(); toggleEditMode(false, getContainer()); } else if (e.target.classList.contains('btn-reset')) { e.stopPropagation(); Object.assign(config, PRESET_BOTTOM); saveActiveConfig(); applyStyles(mask); showToast('已重置为底部默认位置'); } else if (!isEditMode) { const video = document.querySelector('video, bwp-video'); if (video) { if (video.paused) video.play(); else video.pause(); } } }); mask.addEventListener('dblclick', (e) => { e.stopPropagation(); toggleEditMode(!isEditMode, getContainer()); }); mask.addEventListener('mousedown', (e) => { if (!isEditMode) return; if (e.target.closest('.bili-caption-mask-header')) return; e.preventDefault(); e.stopPropagation(); const container = getContainer(); if (!container) return; const targetHandle = e.target.dataset.handle; if (targetHandle) activeHandle = targetHandle; else isDragging = true; startX = e.clientX; startY = e.clientY; initialConfig = { ...config }; function onMouseMove(moveEvent) { const rect = container.getBoundingClientRect(); if (!rect.width || !rect.height) return; const deltaXPercent = ((moveEvent.clientX - startX) / rect.width) * 100; const deltaYPercent = ((moveEvent.clientY - startY) / rect.height) * 100; if (isDragging) { let nextLeft = initialConfig.left + deltaXPercent; let nextTop = initialConfig.top + deltaYPercent; nextLeft = Math.max(0, Math.min(100 - initialConfig.width, nextLeft)); nextTop = Math.max(0, Math.min(100 - initialConfig.height, nextTop)); config.left = parseFloat(nextLeft.toFixed(2)); config.top = parseFloat(nextTop.toFixed(2)); } else if (activeHandle) { let { left, top, width, height } = initialConfig; if (activeHandle.includes('n')) { const newTop = Math.min(top + height - 2, Math.max(0, top + deltaYPercent)); height = (top + height) - newTop; top = newTop; } if (activeHandle.includes('s')) { height = Math.max(2, Math.min(100 - top, height + deltaYPercent)); } if (activeHandle.includes('w')) { const newLeft = Math.min(left + width - 5, Math.max(0, left + deltaXPercent)); width = (left + width) - newLeft; left = newLeft; } if (activeHandle.includes('e')) { width = Math.max(5, Math.min(100 - left, width + deltaXPercent)); } config.left = parseFloat(left.toFixed(2)); config.top = parseFloat(top.toFixed(2)); config.width = parseFloat(width.toFixed(2)); config.height = parseFloat(height.toFixed(2)); } applyStyles(mask); } function onMouseUp() { isDragging = false; activeHandle = null; saveActiveConfig(); window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); } window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); }); } // 切换编辑/锁定模式 function toggleEditMode(forceState, container) { if (typeof forceState === 'boolean') isEditMode = forceState; else isEditMode = !isEditMode; const mask = (container || document).querySelector('.bili-caption-blur-mask'); if (mask) applyStyles(mask); if (isEditMode) { showToast('✏️ 已进入调节模式(拖拽遮罩或边缘手柄,双击锁定)'); } else { saveActiveConfig(); showToast('🔒 已锁定(观影模式:悬停或按住 Alt 偷瞄中文)'); } } function toggleMaskEnabled(container) { config.enabled = !config.enabled; saveActiveConfig(); const mask = (container || document).querySelector('.bili-caption-blur-mask'); if (mask) applyStyles(mask); showToast(config.enabled ? '👁️ 中文字幕遮罩:已开启' : '🙈 中文字幕遮罩:已隐藏'); } // 创建生词抽屉 DOM function createDrawerElement() { const drawer = document.createElement('div'); drawer.className = 'bili-caption-drawer'; drawer.innerHTML = `