Greasy Fork

Greasy Fork is available in English.

Bilibili Tools

字幕提取、AI总结、Notion集成、笔记保存、播放速度控制、SponsorBlock广告跳过 - 六合一工具集

当前为 2025-10-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         Bilibili Tools
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @author       geraldpeng & claude 4.5 sonnet
// @description  字幕提取、AI总结、Notion集成、笔记保存、播放速度控制、SponsorBlock广告跳过 - 六合一工具集
// @license      MIT
// @match        *://www.bilibili.com/*
// @match        *://search.bilibili.com/*
// @match        *://space.bilibili.com/*
// @match        *://*/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js
// @connect      api.bilibili.com
// @connect      aisubtitle.hdslb.com
// @connect      api.notion.com
// @connect      openrouter.ai
// @connect      bsbsb.top
// @connect      *
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function () {
	'use strict';

	const e=500,t=20,n=1500,o=500,i=500,s=100,r=2e3,a=500,l=12e4,c=2e3,d=1900,p=2e3,u=32,h="idle",g="loading",m="active",b="no-subtitle",f="error",y="subtitle:loaded",x="subtitle:failed",v="subtitle:requested",w="ai:summary:start",S="ai:summary:complete",k="ai:summary:failed",I="ai:summary:chunk",E="notion:send:start",C="notion:send:complete",A="notion:send:failed",T="ui:panel:toggle",L="ui:ball:status:change",M="video:changed",B="请用中文总结以下视频字幕内容,使用Markdown格式输出。\n\n要求:\n1. 在开头提供TL;DR(不超过50字的核心摘要)\n2. 使用标题、列表等Markdown格式组织内容\n3. 突出关键信息和要点\n\n字幕内容:\n",D={openrouter:"https://openrouter.ai/keys",openai:"https://platform.openai.com/api-keys",siliconflow:"https://cloud.siliconflow.cn/account/ak",deepseek:"https://platform.deepseek.com/api_keys",moonshot:"https://platform.moonshot.cn/console/api-keys",zhipu:"https://open.bigmodel.cn/usercenter/apikeys",yi:"https://platform.lingyiwanwu.com/apikeys",dashscope:"https://bailian.console.aliyun.com/",gemini:"https://aistudio.google.com/app/apikey"},P=[{id:"openrouter",name:"OpenRouter",url:"https://openrouter.ai/api/v1/chat/completions",apiKey:"",model:"alibaba/tongyi-deepresearch-30b-a3b:free",prompt:B,isOpenRouter:true},{id:"openai",name:"OpenAI",url:"https://api.openai.com/v1/chat/completions",apiKey:"",model:"gpt-3.5-turbo",prompt:B,isOpenRouter:false},{id:"siliconflow",name:"硅基流动",url:"https://api.siliconflow.cn/v1/chat/completions",apiKey:"",model:"Qwen/Qwen2.5-7B-Instruct",prompt:B,isOpenRouter:false},{id:"deepseek",name:"DeepSeek",url:"https://api.deepseek.com/v1/chat/completions",apiKey:"",model:"deepseek-chat",prompt:B,isOpenRouter:false},{id:"moonshot",name:"月之暗面 Kimi",url:"https://api.moonshot.cn/v1/chat/completions",apiKey:"",model:"moonshot-v1-8k",prompt:B,isOpenRouter:false},{id:"zhipu",name:"智谱AI",url:"https://open.bigmodel.cn/api/paas/v4/chat/completions",apiKey:"",model:"glm-4-flash",prompt:B,isOpenRouter:false},{id:"yi",name:"零一万物",url:"https://api.lingyiwanwu.com/v1/chat/completions",apiKey:"",model:"yi-large",prompt:B,isOpenRouter:false},{id:"dashscope",name:"阿里云百炼",url:"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",apiKey:"",model:"qwen-plus",prompt:B,isOpenRouter:false},{id:"gemini",name:"Google Gemini",url:"https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",apiKey:"",model:"gemini-1.5-flash",prompt:B,isOpenRouter:false}],N="ai_configs",z="selected_ai_config_id",_="ai_auto_summary_enabled",$="notion_api_key",q="notion_parent_page_id",V="notion_database_id",R="notion_auto_send_enabled",O="2022-06-28",F="https://api.notion.com/v1",H=/\/video\/(BV[1-9A-Za-z]{10})/,K=/BV[1-9A-Za-z]{10}/,j=/([a-f0-9]{32}|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i,G="video",U=".bpx-player-container, #bilibili-player",Y=".bpx-player-ctrl-subtitle-result",X='.bpx-player-ctrl-subtitle-close-switch[data-action="close"]',Q="h1.video-title",W={API_URL:"https://bsbsb.top/api/skipSegments",CACHE_EXPIRY:18e5,MIN_SCORE:.06,MIN_VIEWS:1e3,TAG_COLOR:"linear-gradient(135deg, #FF6B6B, #FF4D4D)",TAG_TEXT:"🔥 精选",TOP_TAG_COLOR:"linear-gradient(135deg, #FFD700, #FFA500)",TOP_TAG_TEXT:"🏆 顶级",CATEGORIES:{sponsor:{name:"广告",color:"#00d400"},selfpromo:{name:"无偿/自我推广",color:"#ffff00"},interaction:{name:"三连/订阅提醒",color:"#cc00ff"},poi_highlight:{name:"精彩时刻/重点",color:"#ff1684"},intro:{name:"过场/开场动画",color:"#00ffff"},outro:{name:"鸣谢/结束画面",color:"#0202ed"},preview:{name:"回顾/概要",color:"#008fd6"},filler:{name:"离题闲聊/玩笑",color:"#7300FF"},music_offtopic:{name:"音乐:非音乐部分",color:"#ff9900"},exclusive_access:{name:"柔性推广/品牌合作",color:"#008a5c"},mute:{name:"静音片段",color:"#B54D4B"}},DEFAULT_SETTINGS:{skipCategories:["sponsor"],showAdBadge:true,showQualityBadge:true,showProgressMarkers:true}},J=`\n  /* ==================== 小球样式 ==================== */\n  #subtitle-ball {\n    position: absolute;\n    right: -30px;\n    top: 50%;\n    transform: translateY(-50%);\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    background-color: #999;\n    cursor: pointer;\n    z-index: ${2147483647};\n    box-shadow: 0 2px 6px rgba(0,0,0,0.25);\n    transition: all 0.3s ease;\n    animation: breath-ball-normal 2s ease-in-out infinite;\n  }\n\n  #subtitle-ball:hover {\n    transform: translateY(-50%) scale(1.2);\n    box-shadow: 0 3px 10px rgba(0,0,0,0.35);\n  }\n\n  #subtitle-ball.active {\n    background-color: #feebea;\n    cursor: pointer;\n  }\n\n  #subtitle-ball.loading {\n    background-color: #3b82f6;\n    animation: breath-ball 1.2s ease-in-out infinite;\n  }\n\n  #subtitle-ball.ai-summarizing {\n    background-color: #feebea;\n    animation: breath-ball-ai 1s ease-in-out infinite;\n  }\n\n  #subtitle-ball.no-subtitle {\n    background-color: #999;\n    cursor: default;\n    opacity: 0.6;\n  }\n\n  #subtitle-ball.error {\n    background-color: #ff0000;\n    cursor: default;\n  }\n\n  @keyframes breath-ball-normal {\n    0%, 100% { transform: translateY(-50%) scale(1); }\n    50% { transform: translateY(-50%) scale(1.05); }\n  }\n\n  @keyframes breath-ball {\n    0%, 100% { transform: translateY(-50%) scale(1.1); opacity: 1; }\n    50% { transform: translateY(-50%) scale(1.4); opacity: 0.6; }\n  }\n\n  @keyframes breath-ball-ai {\n    0%, 100% { \n      transform: translateY(-50%) scale(1.3); \n      opacity: 1;\n      box-shadow: 0 0 20px rgba(254, 235, 234, 0.8);\n    }\n    50% { \n      transform: translateY(-50%) scale(1.8); \n      opacity: 0.7;\n      box-shadow: 0 0 40px rgba(254, 235, 234, 1);\n    }\n  }\n\n  /* ==================== 字幕容器样式 ==================== */\n  #subtitle-container {\n    position: absolute;\n    top: 10%;\n    left: 100%;\n    width: auto;\n    min-width: 420px;\n    max-width: 500px;\n    height: 80%;\n    background: rgba(0, 0, 0, 0.85);\n    backdrop-filter: blur(12px);\n    color: #fff;\n    border-radius: 12px;\n    font-size: 14px;\n    line-height: 1.8;\n    display: none;\n    flex-direction: column;\n    overflow: hidden;\n    box-shadow: -4px 0 24px rgba(0,0,0,0.5);\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n    z-index: ${2147483646};\n    margin-left: 10px;\n  }\n\n  #subtitle-container.show {\n    display: flex;\n  }\n\n  /* ==================== 头部样式 ==================== */\n  .subtitle-header {\n    font-size: 16px;\n    font-weight: 700;\n    padding: 20px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.2);\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    flex-shrink: 0;\n    background: rgba(254, 235, 234, 0.15);\n    color: #fff;\n    border-radius: 12px 12px 0 0;\n    user-select: none;\n    gap: 12px;\n  }\n\n  .subtitle-search-container {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    background: rgba(0, 0, 0, 0.3);\n    border-radius: 8px;\n    padding: 6px 10px;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    transition: all 0.2s;\n    max-width: 280px;\n  }\n\n  .subtitle-search-container:focus-within {\n    border-color: #feebea;\n    background: rgba(0, 0, 0, 0.4);\n    box-shadow: 0 0 0 2px rgba(254, 235, 234, 0.1);\n  }\n\n  .search-input {\n    flex: 1;\n    background: transparent;\n    border: none;\n    outline: none;\n    color: #fff;\n    font-size: 14px;\n    padding: 4px;\n  }\n\n  .search-input::placeholder {\n    color: rgba(255, 255, 255, 0.5);\n  }\n\n  .search-nav {\n    display: flex;\n    align-items: center;\n    gap: 4px;\n    flex-shrink: 0;\n  }\n\n  .search-counter {\n    font-size: 12px;\n    color: rgba(255, 255, 255, 0.6);\n    min-width: 32px;\n    text-align: center;\n  }\n\n  .search-nav-btn {\n    background: rgba(254, 235, 234, 0.2);\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 4px;\n    color: #fff;\n    cursor: pointer;\n    padding: 2px 6px;\n    font-size: 14px;\n    transition: all 0.2s;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .search-nav-btn:hover {\n    background: rgba(254, 235, 234, 0.35);\n    border-color: #feebea;\n  }\n\n  .search-nav-btn:active {\n    transform: scale(0.95);\n  }\n\n  .search-nav-btn:disabled {\n    opacity: 0.3;\n    cursor: not-allowed;\n  }\n\n  /* 搜索高亮样式 */\n  .search-highlight {\n    background-color: rgba(255, 255, 0, 0.4);\n    color: #000;\n    padding: 2px 0;\n    border-radius: 2px;\n  }\n\n  .search-highlight-current {\n    background-color: rgba(255, 165, 0, 0.6);\n    color: #000;\n    padding: 2px 0;\n    border-radius: 2px;\n    box-shadow: 0 0 4px rgba(255, 165, 0, 0.8);\n  }\n\n  .subtitle-header-actions {\n    display: flex;\n    gap: 16px;\n    align-items: center;\n  }\n\n  .subtitle-close {\n    cursor: pointer;\n    font-size: 24px;\n    line-height: 1;\n    color: rgba(255, 255, 255, 0.6);\n    opacity: 0.7;\n    transition: all 0.2s;\n  }\n\n  .subtitle-close:hover {\n    opacity: 1;\n    color: #fff;\n    transform: scale(1.1);\n  }\n\n  /* ==================== 内容区域样式 ==================== */\n  .subtitle-content {\n    flex: 1;\n    overflow-y: auto;\n    padding: 15px 20px 20px 20px;\n    background-color: transparent;\n  }\n\n  .subtitle-content::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  .subtitle-content::-webkit-scrollbar-thumb {\n    background-color: rgba(254, 235, 234, 0.4);\n    border-radius: 3px;\n  }\n  \n  .subtitle-content::-webkit-scrollbar-thumb:hover {\n    background-color: rgba(254, 235, 234, 0.6);\n  }\n  \n  .subtitle-content::-webkit-scrollbar-track {\n    background-color: rgba(255, 255, 255, 0.05);\n  }\n\n  /* ==================== 字幕列表样式 ==================== */\n  .subtitle-toggle-btn {\n    padding: 8px 12px;\n    margin-bottom: 15px;\n    background: rgba(255, 255, 255, 0.1);\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 8px;\n    color: #fff;\n    cursor: pointer;\n    font-size: 16px;\n    transition: all 0.2s;\n    display: flex;\n    align-items: center;\n    justify-content: flex-start;\n    width: 100%;\n    height: auto;\n  }\n\n  .subtitle-toggle-btn:hover {\n    background: rgba(254, 235, 234, 0.2);\n    border-color: #feebea;\n    transform: scale(1.05);\n  }\n\n  .subtitle-toggle-icon {\n    transition: transform 0.3s ease;\n    display: inline-block;\n    font-size: 12px;\n  }\n\n  .subtitle-toggle-btn.expanded .subtitle-toggle-icon {\n    transform: rotate(90deg);\n  }\n\n  .subtitle-list-container {\n    display: none;\n  }\n\n  .subtitle-list-container.expanded {\n    display: block;\n  }\n\n  .subtitle-item {\n    margin-bottom: 6px;\n    padding: 10px 12px;\n    border-radius: 8px;\n    transition: all 0.2s;\n    cursor: pointer;\n    background: rgba(255, 255, 255, 0.05);\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .subtitle-item:hover {\n    background: rgba(254, 235, 234, 0.15);\n    border-color: #feebea;\n    transform: translateX(4px);\n    box-shadow: 0 2px 8px rgba(254, 235, 234, 0.2);\n  }\n\n  .subtitle-item.current {\n    background: rgba(254, 235, 234, 0.25);\n    border-color: #feebea;\n    box-shadow: 0 2px 12px rgba(254, 235, 234, 0.3);\n  }\n\n  .subtitle-time {\n    color: rgba(255, 255, 255, 0.6);\n    font-size: 11px;\n    margin-bottom: 4px;\n    font-weight: 600;\n  }\n\n  .subtitle-text {\n    color: #e5e7eb;\n    font-size: 14px;\n    line-height: 1.6;\n  }\n\n  /* ==================== AI图标样式 ==================== */\n  .ai-icon {\n    cursor: pointer;\n    width: 24px;\n    height: 24px;\n    opacity: 0.7;\n    transition: opacity 0.2s, transform 0.2s;\n  }\n\n  .ai-icon:hover {\n    opacity: 1;\n    transform: scale(1.1);\n  }\n\n  .ai-icon.loading {\n    animation: breath-ai 1.2s ease-in-out infinite;\n    pointer-events: none;\n  }\n\n  .ai-icon.disabled {\n    opacity: 0.3;\n    pointer-events: none;\n    cursor: not-allowed;\n  }\n\n  @keyframes breath-ai {\n    0%, 100% { transform: scale(1.05); opacity: 1; }\n    50% { transform: scale(1.35); opacity: 0.5; }\n  }\n\n  /* ==================== 下载图标样式 ==================== */\n  .download-icon {\n    cursor: pointer;\n    width: 20px;\n    height: 20px;\n    opacity: 0.7;\n    transition: opacity 0.2s, transform 0.2s;\n  }\n\n  .download-icon:hover {\n    opacity: 1;\n    transform: scale(1.1);\n  }\n\n  /* ==================== Notion图标样式 ==================== */\n  .notion-icon {\n    cursor: pointer;\n    width: 24px;\n    height: 24px;\n    opacity: 0.7;\n    transition: opacity 0.2s, transform 0.2s;\n  }\n\n  .notion-icon:hover {\n    opacity: 1;\n    transform: scale(1.1);\n  }\n\n  .notion-icon.loading {\n    animation: breath-notion 1.2s ease-in-out infinite;\n  }\n\n  @keyframes breath-notion {\n    0%, 100% { transform: scale(1.05); opacity: 1; }\n    50% { transform: scale(1.35); opacity: 0.5; }\n  }\n\n  /* ==================== Toast提示样式 ==================== */\n  .notion-toast {\n    position: fixed;\n    top: 80px;\n    left: 50%;\n    transform: translateX(-50%);\n    background-color: rgba(0, 0, 0, 0.85);\n    color: white;\n    padding: 12px 24px;\n    border-radius: 8px;\n    font-size: 14px;\n    z-index: ${2147483645};\n    opacity: 0;\n    transition: opacity 0.3s;\n    box-shadow: 0 4px 12px rgba(0,0,0,0.3);\n  }\n\n  .notion-toast.show {\n    opacity: 1;\n  }\n\n  /* ==================== AI总结样式 ==================== */\n  .ai-summary-section {\n    padding: 15px;\n    margin-bottom: 15px;\n    background: rgba(254, 235, 234, 0.1);\n    border-radius: 12px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n  }\n\n  .ai-summary-title {\n    color: #fff;\n    font-size: 15px;\n    font-weight: 700;\n    margin-bottom: 12px;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    padding-bottom: 8px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.3);\n  }\n\n  .ai-summary-content {\n    color: #e5e7eb;\n    font-size: 14px;\n    line-height: 1.7;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    word-break: break-word;\n    white-space: normal;\n    max-width: 100%;\n  }\n\n  .ai-summary-loading {\n    color: rgba(255, 255, 255, 0.6);\n    font-style: italic;\n  }\n\n  /* ==================== Markdown样式 ==================== */\n  .ai-summary-content h1,\n  .ai-summary-content h2,\n  .ai-summary-content h3 {\n    color: #fff;\n    margin-top: 12px;\n    margin-bottom: 8px;\n    font-weight: 700;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n  }\n\n  .ai-summary-content h1 { font-size: 17px; }\n  .ai-summary-content h2 { font-size: 16px; }\n  .ai-summary-content h3 { font-size: 15px; }\n\n  .ai-summary-content ul,\n  .ai-summary-content ol {\n    margin: 8px 0;\n    padding-left: 20px;\n    max-width: 100%;\n  }\n\n  .ai-summary-content li {\n    margin: 4px 0;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    word-break: break-word;\n  }\n\n  .ai-summary-content p {\n    margin: 8px 0;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    word-break: break-word;\n    white-space: normal;\n  }\n\n  .ai-summary-content code {\n    background: rgba(255, 255, 255, 0.1);\n    color: #feebea;\n    padding: 3px 6px;\n    border-radius: 4px;\n    font-family: 'Courier New', monospace;\n    font-size: 13px;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .ai-summary-content pre {\n    background: rgba(0, 0, 0, 0.5);\n    padding: 12px;\n    border-radius: 8px;\n    overflow-x: auto;\n    margin: 10px 0;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    white-space: pre-wrap;\n    word-wrap: break-word;\n    max-width: 100%;\n  }\n\n  .ai-summary-content pre code {\n    background-color: transparent;\n    padding: 0;\n    border: none;\n    white-space: pre-wrap;\n    word-wrap: break-word;\n  }\n\n  .ai-summary-content blockquote {\n    border-left: 4px solid #feebea;\n    background: rgba(254, 235, 234, 0.1);\n    padding: 12px;\n    padding-left: 16px;\n    margin: 10px 0;\n    border-radius: 4px;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n  }\n\n  .ai-summary-content strong {\n    color: #fff;\n    font-weight: 700;\n  }\n\n  .ai-summary-content a {\n    color: #feebea;\n    text-decoration: underline;\n    font-weight: 600;\n  }\n  \n  .ai-summary-content a:hover {\n    color: #fff;\n  }\n\n  /* ==================== 配置模态框样式 ==================== */\n  .config-modal {\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background-color: rgba(0, 0, 0, 0.7);\n    z-index: ${2147483643};\n    display: none;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .config-modal.show {\n    display: flex;\n  }\n\n  .config-modal-content {\n    background: rgba(0, 0, 0, 0.85);\n    backdrop-filter: blur(12px);\n    border-radius: 16px;\n    padding: 0;\n    width: 700px;\n    max-width: 90%;\n    max-height: 85vh;\n    overflow: hidden;\n    box-shadow: 0 20px 60px rgba(0,0,0,0.5);\n    color: #fff;\n    display: flex;\n    flex-direction: column;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .config-modal-header {\n    font-size: 24px;\n    font-weight: 700;\n    padding: 30px 30px 20px 30px;\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    background: rgba(254, 235, 234, 0.15);\n    color: white;\n    border-radius: 16px 16px 0 0;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.2);\n  }\n  \n  .config-modal-body {\n    flex: 1;\n    overflow-y: auto;\n    padding: 30px;\n    background-color: transparent;\n  }\n\n  .config-field {\n    margin-bottom: 20px;\n  }\n\n  .config-field label {\n    display: block;\n    margin-bottom: 10px;\n    font-weight: 600;\n    color: #e5e7eb;\n    font-size: 14px;\n    display: flex;\n    align-items: center;\n    gap: 6px;\n  }\n  \n  .config-field label::before {\n    content: '•';\n    color: #feebea;\n    font-size: 18px;\n    font-weight: bold;\n  }\n\n  .config-field input,\n  .config-field textarea {\n    width: 100%;\n    padding: 12px 14px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 10px;\n    font-size: 14px;\n    box-sizing: border-box;\n    transition: all 0.2s;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n  }\n\n  .config-field input:hover,\n  .config-field textarea:hover {\n    background: rgba(255, 255, 255, 0.12);\n    border-color: rgba(254, 235, 234, 0.5);\n  }\n\n  .config-field input:focus,\n  .config-field textarea:focus {\n    outline: none;\n    border-color: #feebea;\n    box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.15);\n    background: rgba(255, 255, 255, 0.15);\n  }\n\n  .config-field input::placeholder,\n  .config-field textarea::placeholder {\n    color: rgba(255, 255, 255, 0.5);\n  }\n\n  .config-field textarea {\n    font-family: inherit;\n    resize: vertical;\n    min-height: 120px;\n    line-height: 1.6;\n  }\n  \n  .config-field input[type="checkbox"] {\n    width: auto;\n    margin-right: 8px;\n    cursor: pointer;\n  }\n\n  .config-help {\n    font-size: 12px;\n    color: rgba(255, 255, 255, 0.6);\n    margin-top: 5px;\n  }\n\n  .config-help a {\n    color: #feebea;\n    text-decoration: underline;\n  }\n\n  .config-help code {\n    background-color: rgba(255, 255, 255, 0.1);\n    padding: 2px 6px;\n    border-radius: 3px;\n    font-family: 'Courier New', monospace;\n    font-size: 11px;\n    color: #fff;\n  }\n\n  .config-help strong {\n    color: #feebea;\n  }\n\n  .config-footer {\n    display: flex;\n    gap: 12px;\n    justify-content: flex-end;\n    padding: 20px 30px;\n    background-color: rgba(0, 0, 0, 0.3);\n    border-top: 1px solid rgba(254, 235, 234, 0.2);\n    border-radius: 0 0 16px 16px;\n  }\n\n  .config-btn {\n    padding: 12px 24px;\n    border: none;\n    border-radius: 10px;\n    font-size: 14px;\n    font-weight: 600;\n    cursor: pointer;\n    transition: all 0.2s;\n    position: relative;\n    overflow: hidden;\n  }\n\n  .config-btn::before {\n    content: '';\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    width: 0;\n    height: 0;\n    border-radius: 50%;\n    background: rgba(255, 255, 255, 0.3);\n    transform: translate(-50%, -50%);\n    transition: width 0.6s, height 0.6s;\n  }\n\n  .config-btn:hover::before {\n    width: 300px;\n    height: 300px;\n  }\n\n  .config-btn-primary {\n    background: linear-gradient(135deg, #feebea 0%, #2d2d2d 100%);\n    color: #fff;\n    box-shadow: 0 4px 12px rgba(254, 235, 234, 0.3);\n  }\n\n  .config-btn-primary:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 6px 20px rgba(254, 235, 234, 0.4);\n  }\n\n  .config-btn-primary:active {\n    transform: translateY(0);\n  }\n\n  .config-btn-secondary {\n    background-color: #f3f4f6;\n    color: #6b7280;\n    border: 2px solid #e5e7eb;\n  }\n\n  .config-btn-secondary:hover {\n    background-color: #e5e7eb;\n    color: #374151;\n    border-color: #d1d5db;\n  }\n\n  .config-btn-danger {\n    background-color: #fee2e2;\n    color: #dc2626;\n    border: 2px solid #fecaca;\n  }\n\n  .config-btn-danger:hover {\n    background-color: #dc2626;\n    color: white;\n    border-color: #dc2626;\n  }\n\n  .config-status {\n    padding: 8px 12px;\n    border-radius: 6px;\n    font-size: 12px;\n    margin-top: 10px;\n  }\n\n  .config-status.success {\n    background-color: #d4edda;\n    color: #155724;\n  }\n\n  .config-status.error {\n    background-color: #f8d7da;\n    color: #721c24;\n  }\n\n  /* ==================== AI配置列表样式 ==================== */\n  .ai-config-list {\n    margin-bottom: 25px;\n    display: grid;\n    grid-template-columns: repeat(2, 1fr);\n    gap: 10px;\n  }\n\n  .ai-config-item {\n    padding: 10px 14px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 10px;\n    margin-bottom: 0;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    cursor: pointer;\n    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n    background: rgba(255, 255, 255, 0.05);\n    position: relative;\n    overflow: hidden;\n  }\n\n  .ai-config-item::before {\n    content: '';\n    position: absolute;\n    left: 0;\n    top: 0;\n    height: 100%;\n    width: 4px;\n    background: linear-gradient(135deg, #feebea 0%, #ffdbdb 100%);\n    transform: scaleY(0);\n    transition: transform 0.3s ease;\n  }\n\n  .ai-config-item:hover {\n    background: rgba(254, 235, 234, 0.15);\n    border-color: #feebea;\n    transform: translateX(4px);\n    box-shadow: 0 4px 12px rgba(254, 235, 234, 0.2);\n  }\n\n  .ai-config-item:hover::before {\n    transform: scaleY(1);\n  }\n\n  .ai-config-item.selected {\n    border-color: #feebea;\n    background: rgba(254, 235, 234, 0.2);\n    box-shadow: 0 4px 16px rgba(254, 235, 234, 0.3);\n  }\n\n  .ai-config-item.selected::before {\n    transform: scaleY(1);\n    width: 4px;\n  }\n\n  .ai-config-item-name {\n    font-weight: 600;\n    font-size: 14px;\n    color: #e5e7eb;\n  }\n\n  .ai-config-item.selected .ai-config-item-name {\n    color: #fff;\n    font-weight: 700;\n  }\n\n  .ai-config-item-actions {\n    display: flex;\n    gap: 8px;\n    z-index: 1;\n  }\n\n  .ai-config-btn-small {\n    padding: 4px 12px;\n    font-size: 12px;\n    border: none;\n    border-radius: 6px;\n    cursor: pointer;\n    transition: all 0.2s;\n    font-weight: 500;\n  }\n\n  .ai-config-btn-small:hover {\n    transform: translateY(-1px);\n    box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n  }\n\n  .ai-config-btn-small.config-btn-primary {\n    background: linear-gradient(135deg, #feebea 0%, #2d2d2d 100%);\n    color: white;\n  }\n\n  .ai-config-btn-small.config-btn-secondary {\n    background-color: #f3f4f6;\n    color: #6b7280;\n  }\n\n  .ai-config-btn-small.config-btn-secondary:hover {\n    background-color: #fee2e2;\n    color: #dc2626;\n  }\n\n  .ai-config-form {\n    border-top: 1px solid rgba(254, 235, 234, 0.2);\n    padding-top: 25px;\n    margin-top: 10px;\n    background: rgba(0, 0, 0, 0.3);\n    padding: 25px;\n    border-radius: 12px;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .ai-config-form.hidden {\n    display: none;\n  }\n\n  .ai-config-form .config-field {\n    margin-bottom: 20px;\n  }\n\n  /* ==================== 模型选择器样式 ==================== */\n  .model-select-wrapper {\n    margin-top: 8px;\n    position: relative;\n  }\n\n  .model-search-input {\n    width: 100%;\n    padding: 10px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 6px;\n    font-size: 14px;\n    box-sizing: border-box;\n    margin-bottom: 8px;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n  }\n\n  .model-search-input:focus {\n    outline: none;\n    border-color: #feebea;\n    box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.15);\n    background: rgba(255, 255, 255, 0.15);\n  }\n\n  .model-search-input::placeholder {\n    color: rgba(255, 255, 255, 0.5);\n  }\n\n  .model-select-wrapper select {\n    width: 100%;\n    padding: 10px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 6px;\n    font-size: 14px;\n    box-sizing: border-box;\n    max-height: 200px;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n  }\n\n  .model-select-wrapper select option {\n    padding: 8px;\n    background: rgba(0, 0, 0, 0.9);\n    color: #fff;\n  }\n\n  .model-count-badge {\n    display: inline-block;\n    background: #feebea;\n    color: #1a1a1a;\n    padding: 2px 8px;\n    border-radius: 12px;\n    font-size: 12px;\n    margin-left: 8px;\n    font-weight: 600;\n  }\n\n  .model-field-with-button {\n    display: flex;\n    gap: 8px;\n    align-items: center;\n  }\n\n  .model-field-with-button input {\n    flex: 1;\n  }\n\n  .fetch-models-btn {\n    padding: 12px 20px;\n    font-size: 14px;\n    font-weight: 600;\n    border: none;\n    border-radius: 10px;\n    background: linear-gradient(135deg, #feebea 0%, #2d2d2d 100%);\n    color: white;\n    cursor: pointer;\n    white-space: nowrap;\n    transition: all 0.2s;\n    box-shadow: 0 2px 8px rgba(254, 235, 234, 0.3);\n  }\n\n  .fetch-models-btn:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 4px 12px rgba(254, 235, 234, 0.4);\n  }\n\n  .fetch-models-btn:active {\n    transform: translateY(0);\n  }\n\n  .fetch-models-btn:disabled {\n    opacity: 0.6;\n    cursor: not-allowed;\n    transform: none !important;\n    box-shadow: none;\n  }\n\n  /* ==================== 速度控制样式 ==================== */\n  .speed-control-section {\n    padding: 12px;\n    margin-bottom: 15px;\n    background: linear-gradient(135deg, #fff5f5 0%, #ffe5e5 100%);\n    border-radius: 12px;\n    border: 2px solid rgba(254, 235, 234, 0.5);\n  }\n\n  .speed-control-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 10px;\n    padding-bottom: 8px;\n    border-bottom: 2px solid rgba(254, 235, 234, 0.5);\n  }\n\n  .speed-control-title {\n    font-size: 14px;\n    font-weight: 700;\n    color: #2d2d2d;\n  }\n\n  .speed-control-display {\n    font-size: 16px;\n    font-weight: 700;\n    color: #1a1a1a;\n    font-family: monospace;\n  }\n\n  .speed-control-buttons {\n    display: flex;\n    gap: 8px;\n    margin-bottom: 10px;\n  }\n\n  .speed-btn {\n    flex: 1;\n    padding: 8px;\n    border: 2px solid #e5e7eb;\n    border-radius: 8px;\n    background: white;\n    color: #1a1a1a;\n    cursor: pointer;\n    font-size: 14px;\n    font-weight: 600;\n    transition: all 0.2s;\n  }\n\n  .speed-btn:hover {\n    background: #feebea;\n    border-color: #feebea;\n    transform: translateY(-1px);\n  }\n\n  .speed-btn-small {\n    flex: 0 0 40px;\n    font-size: 18px;\n  }\n\n  .speed-control-advanced {\n    margin-top: 8px;\n  }\n\n  .speed-toggle-volume-btn {\n    width: 100%;\n    padding: 8px;\n    border: 2px solid #e5e7eb;\n    border-radius: 8px;\n    background: white;\n    color: #6b7280;\n    cursor: pointer;\n    font-size: 12px;\n    transition: all 0.2s;\n  }\n\n  .speed-toggle-volume-btn:hover {\n    background: #fff5f5;\n    border-color: #ffe5e5;\n  }\n\n  /* ==================== 笔记面板样式 ==================== */\n  .notes-panel {\n    position: fixed;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    width: 600px;\n    max-width: 90%;\n    height: 80vh;\n    max-height: 80vh;\n    background: rgba(0, 0, 0, 0.85);\n    backdrop-filter: blur(12px);\n    border-radius: 12px;\n    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);\n    z-index: 2147483640;\n    display: none;\n    flex-direction: column;\n    overflow: hidden;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .notes-panel.show {\n    display: flex;\n  }\n\n  .notes-panel-content {\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n  }\n\n  .notes-panel-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 20px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.2);\n    background: rgba(254, 235, 234, 0.15);\n  }\n\n  .notes-panel-header h2 {\n    margin: 0;\n    font-size: 20px;\n    font-weight: 600;\n    color: #fff;\n  }\n\n  .notes-panel-close {\n    background: none;\n    border: none;\n    font-size: 24px;\n    color: rgba(255, 255, 255, 0.6);\n    cursor: pointer;\n    padding: 0;\n    width: 30px;\n    height: 30px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 50%;\n    transition: all 0.2s;\n  }\n\n  .notes-panel-close:hover {\n    background: rgba(255,255,255,0.1);\n    color: #fff;\n  }\n\n  .notes-panel-body {\n    flex: 1;\n    overflow-y: auto;\n    padding: 20px;\n  }\n\n  .notes-panel-body::-webkit-scrollbar {\n    width: 6px;\n  }\n\n  .notes-panel-body::-webkit-scrollbar-thumb {\n    background-color: rgba(254, 235, 234, 0.4);\n    border-radius: 3px;\n  }\n  \n  .notes-panel-body::-webkit-scrollbar-thumb:hover {\n    background-color: rgba(254, 235, 234, 0.6);\n  }\n  \n  .notes-panel-body::-webkit-scrollbar-track {\n    background-color: rgba(255, 255, 255, 0.05);\n  }\n\n  .notes-empty-state {\n    text-align: center;\n    padding: 60px 20px;\n    color: rgba(255, 255, 255, 0.6);\n  }\n\n  .notes-empty-icon {\n    font-size: 48px;\n    margin-bottom: 16px;\n  }\n\n  .notes-empty-hint {\n    font-size: 14px;\n    margin-top: 8px;\n  }\n\n  .note-group {\n    margin-bottom: 24px;\n  }\n\n  .note-group-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 12px;\n    padding-bottom: 8px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .note-group-title {\n    font-size: 14px;\n    font-weight: 600;\n    color: #e5e7eb;\n  }\n\n  .note-group-actions {\n    display: flex;\n    gap: 8px;\n  }\n\n  .note-group-copy-btn,\n  .note-group-delete-btn {\n    padding: 4px 12px;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 12px;\n    transition: all 0.2s;\n    border: 1px solid;\n  }\n\n  .note-group-copy-btn {\n    background: none;\n    border-color: #4A90E2;\n    color: #4A90E2;\n  }\n\n  .note-group-copy-btn:hover {\n    background: #4A90E2;\n    color: white;\n  }\n\n  .note-group-delete-btn {\n    background: none;\n    border-color: #e74c3c;\n    color: #e74c3c;\n  }\n\n  .note-group-delete-btn:hover {\n    background: #e74c3c;\n    color: white;\n  }\n\n  .note-item {\n    background: rgba(255, 255, 255, 0.05);\n    padding: 12px;\n    border-radius: 8px;\n    margin-bottom: 8px;\n    transition: background-color 0.2s;\n    border: 1px solid rgba(254, 235, 234, 0.1);\n  }\n\n  .note-item:hover {\n    background: rgba(255, 255, 255, 0.1);\n    border-color: rgba(254, 235, 234, 0.3);\n  }\n\n  .note-content {\n    color: #e5e7eb;\n    font-size: 14px;\n    line-height: 1.6;\n    margin-bottom: 8px;\n    word-break: break-word;\n    white-space: pre-wrap;\n  }\n\n  .note-footer {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n\n  .note-time {\n    font-size: 12px;\n    color: rgba(255, 255, 255, 0.5);\n  }\n\n  .note-actions {\n    display: flex;\n    gap: 8px;\n  }\n\n  .note-copy-btn,\n  .note-delete-btn {\n    background: none;\n    border: none;\n    cursor: pointer;\n    font-size: 12px;\n    padding: 4px 8px;\n    transition: color 0.2s;\n  }\n\n  .note-copy-btn {\n    color: #4A90E2;\n  }\n\n  .note-copy-btn:hover {\n    color: #357ABD;\n  }\n\n  .note-delete-btn {\n    color: #e74c3c;\n  }\n\n  .note-delete-btn:hover {\n    color: #c0392b;\n  }\n\n  /* ==================== 笔记选择保存点样式 ==================== */\n  #note-saver-blue-dot {\n    position: absolute;\n    cursor: pointer;\n    z-index: 2147483647; /* Maximum z-index */\n    display: none;\n    transition: transform 0.2s, filter 0.2s;\n    pointer-events: auto !important;\n    width: 24px;\n    height: 24px;\n  }\n\n  #note-saver-blue-dot:hover {\n    transform: scale(1.15);\n    filter: drop-shadow(0 2px 4px rgba(254, 235, 234, 0.5));\n  }\n\n  /* ==================== 字幕项保存按钮样式 ==================== */\n  .subtitle-item-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 4px;\n  }\n\n  .save-subtitle-note-btn {\n    background: linear-gradient(135deg, #feebea 0%, #2d2d2d 100%);\n    color: white;\n    border: none;\n    padding: 2px 8px;\n    border-radius: 4px;\n    font-size: 11px;\n    cursor: pointer;\n    transition: all 0.2s;\n    opacity: 0;\n  }\n\n  .subtitle-item:hover .save-subtitle-note-btn {\n    opacity: 1;\n  }\n\n  .save-subtitle-note-btn:hover {\n    transform: scale(1.05);\n  }\n\n  /* ==================== 快捷键配置样式 ==================== */\n  .shortcut-list {\n    display: flex;\n    flex-direction: column;\n    gap: 12px;\n  }\n\n  .shortcut-item {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 12px;\n    background: rgba(255, 255, 255, 0.05);\n    border-radius: 8px;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .shortcut-label {\n    font-size: 14px;\n    color: #e5e7eb;\n    font-weight: 500;\n  }\n\n  .shortcut-input-wrapper {\n    display: flex;\n    gap: 8px;\n    align-items: center;\n  }\n\n  .shortcut-input {\n    padding: 6px 12px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 6px;\n    font-size: 13px;\n    min-width: 180px;\n    text-align: center;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n    cursor: pointer;\n    transition: all 0.2s;\n  }\n\n  .shortcut-input:focus {\n    outline: none;\n    border-color: #feebea;\n    box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.15);\n    background: rgba(255, 255, 255, 0.15);\n  }\n\n  .shortcut-input.capturing {\n    border-color: #feebea;\n    background: rgba(254, 235, 234, 0.2);\n    animation: pulse-border 1s infinite;\n  }\n\n  @keyframes pulse-border {\n    0%, 100% {\n      border-color: #feebea;\n      box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.15);\n    }\n    50% {\n      border-color: #ffc9c9;\n      box-shadow: 0 0 0 3px rgba(254, 235, 234, 0.3);\n    }\n  }\n\n  .shortcut-clear-btn {\n    background: none;\n    border: none;\n    color: #999;\n    font-size: 18px;\n    cursor: pointer;\n    width: 24px;\n    height: 24px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 50%;\n    transition: all 0.2s;\n  }\n\n  .shortcut-clear-btn:hover {\n    background: #fee2e2;\n    color: #dc2626;\n  }\n\n  /* ==================== 调整大小手柄样式 ==================== */\n  .subtitle-resize-handle {\n    position: absolute;\n    bottom: 0;\n    right: 0;\n    width: 20px;\n    height: 20px;\n    cursor: nwse-resize;\n    z-index: 10;\n  }\n\n  .subtitle-resize-handle::after {\n    content: '';\n    position: absolute;\n    bottom: 4px;\n    right: 4px;\n    width: 12px;\n    height: 12px;\n    border-right: 3px solid rgba(254, 235, 234, 0.6);\n    border-bottom: 3px solid rgba(254, 235, 234, 0.6);\n    border-radius: 0 0 4px 0;\n  }\n\n  .subtitle-resize-handle:hover::after {\n    border-color: #feebea;\n  }\n\n  /* ==================== 速度控制模态框样式 ==================== */\n  .speed-control-section-large {\n    padding: 20px;\n    background: rgba(254, 235, 234, 0.1);\n    border-radius: 12px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    margin-bottom: 20px;\n  }\n\n  .speed-control-header-large {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 16px;\n    padding-bottom: 12px;\n    border-bottom: 1px solid rgba(254, 235, 234, 0.3);\n  }\n\n  .speed-control-display-large {\n    font-size: 32px;\n    font-weight: 700;\n    color: #fff;\n    font-family: monospace;\n  }\n\n  .speed-control-buttons-large {\n    display: grid;\n    grid-template-columns: repeat(4, 1fr);\n    gap: 12px;\n  }\n\n  .speed-btn-large {\n    padding: 16px;\n    border: 1px solid rgba(254, 235, 234, 0.3);\n    border-radius: 12px;\n    background: rgba(255, 255, 255, 0.1);\n    color: #fff;\n    cursor: pointer;\n    font-weight: 600;\n    transition: all 0.2s;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 4px;\n  }\n\n  .speed-btn-large:hover {\n    background: rgba(254, 235, 234, 0.2);\n    border-color: #feebea;\n    transform: translateY(-2px);\n    box-shadow: 0 4px 12px rgba(254, 235, 234, 0.3);\n  }\n\n  .speed-btn-large:active {\n    transform: translateY(0);\n  }\n\n  .speed-status-info {\n    margin-top: 12px;\n    padding: 10px;\n    background: rgba(0, 0, 0, 0.3);\n    border-radius: 8px;\n    min-height: 40px;\n    border: 1px solid rgba(254, 235, 234, 0.2);\n  }\n\n  .speed-status-item {\n    font-size: 12px;\n    color: #4CAF50;\n    font-weight: 600;\n    padding: 4px 0;\n  }\n\n  .sponsor-switch {\n    position: relative;\n    width: 48px;\n    height: 24px;\n  }\n\n  .sponsor-switch input {\n    opacity: 0;\n    width: 0;\n    height: 0;\n  }\n\n  .sponsor-switch-slider {\n    position: absolute;\n    cursor: pointer;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background-color: #cbd5e1;\n    transition: 0.3s;\n    border-radius: 24px;\n  }\n\n  .sponsor-switch-slider:before {\n    position: absolute;\n    content: "";\n    height: 18px;\n    width: 18px;\n    left: 3px;\n    bottom: 3px;\n    background-color: white;\n    transition: 0.3s;\n    border-radius: 50%;\n  }\n\n  .sponsor-switch input:checked + .sponsor-switch-slider {\n    background-color: #feebea;\n  }\n\n  .sponsor-switch input:checked + .sponsor-switch-slider:before {\n    transform: translateX(24px);\n  }\n\n  /* ==================== SponsorBlock 标签样式 ==================== */\n  .bili-quality-tag, .bili-ad-tag {\n    display: inline-flex !important;\n    align-items: center;\n    color: white !important;\n    padding: 3px 10px !important;\n    border-radius: 15px !important;\n    margin-right: 6px !important;\n    font-size: 12px !important;\n    animation: badgeSlideIn 0.3s ease-out !important;\n    position: relative;\n    z-index: 2;\n    font-weight: 500;\n    box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n    white-space: nowrap;\n    flex-shrink: 0;\n  }\n  \n  /* 只显示emoji的标签样式 */\n  .bili-quality-tag.emoji-only,\n  .bili-ad-tag.emoji-only {\n    padding: 3px 8px !important;\n    min-width: auto;\n  }\n\n  /* 视频卡片标签位置 */\n  .video-page-card-small .bili-quality-tag,\n  .video-page-card-small .bili-ad-tag,\n  .bili-video-card__wrap .bili-quality-tag,\n  .bili-video-card__wrap .bili-ad-tag {\n    position: absolute;\n    left: 8px;\n    top: 8px;\n    transform: scale(0.9);\n  }\n\n  /* UP主主页视频卡片 */\n  .up-main-video-card .bili-quality-tag,\n  .up-main-video-card .bili-ad-tag,\n  .small-item .bili-quality-tag,\n  .small-item .bili-ad-tag {\n    position: absolute !important;\n    left: 8px !important;\n    top: 8px !important;\n    z-index: 10 !important;\n    transform: scale(0.9);\n  }\n\n  .up-main-video-card .cover-container,\n  .up-main-video-card .cover,\n  .small-item .cover {\n    position: relative !important;\n  }\n\n  /* 多标签容器 */\n  .bili-tags-container {\n    display: flex;\n    flex-wrap: nowrap;\n    gap: 4px;\n    overflow: visible;\n    align-items: center;\n  }\n\n  @keyframes badgeSlideIn {\n    0% { opacity: 0; transform: translateX(-15px) scale(0.9); }\n    100% { opacity: 1; transform: translateX(0) scale(0.9); }\n  }\n\n  /* 跳过提示Toast - 视频右下角,绿色 */\n  .skip-toast {\n    position: absolute;\n    bottom: 60px;\n    right: 20px;\n    background: rgba(0, 212, 0, 0.15);\n    color: #00d400;\n    padding: 8px 16px;\n    border-radius: 4px;\n    font-size: 14px;\n    z-index: 10000;\n    animation: fadeIn 0.3s ease-out;\n    font-weight: 500;\n    backdrop-filter: blur(4px);\n    pointer-events: auto !important;\n    user-select: none;\n  }\n\n  .skip-toast.hiding {\n    animation: fadeOut 0.3s ease-out forwards;\n  }\n\n  /* 手动跳过提示 - 视频右下角 */\n  .skip-prompt {\n    position: absolute;\n    bottom: 80px;\n    right: 20px;\n    background: rgba(0, 0, 0, 0.9);\n    color: white;\n    border-radius: 8px;\n    padding: 12px 16px;\n    box-shadow: 0 4px 12px rgba(0,0,0,0.4);\n    z-index: 10000;\n    min-width: 280px;\n    animation: fadeIn 0.3s ease-out;\n    pointer-events: auto !important;\n    user-select: none;\n  }\n\n  .skip-prompt-header {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    margin-bottom: 10px;\n    font-size: 14px;\n    font-weight: 500;\n  }\n\n  .skip-prompt-icon {\n    width: 20px;\n    height: 20px;\n    flex-shrink: 0;\n  }\n\n  .skip-prompt-icon svg {\n    width: 100%;\n    height: 100%;\n  }\n\n  .skip-prompt-message {\n    flex: 1;\n  }\n\n  .skip-prompt-buttons {\n    display: flex;\n    gap: 8px;\n    justify-content: flex-end;\n  }\n\n  .skip-prompt-btn {\n    padding: 6px 14px;\n    border: none;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 13px;\n    transition: all 0.2s;\n  }\n\n  .skip-prompt-btn-primary {\n    background: #00a1d6;\n    color: white;\n  }\n\n  .skip-prompt-btn-primary:hover {\n    background: #0087b3;\n  }\n\n  .skip-prompt-btn-secondary {\n    background: rgba(255, 255, 255, 0.1);\n    color: white;\n  }\n\n  .skip-prompt-btn-secondary:hover {\n    background: rgba(255, 255, 255, 0.2);\n  }\n\n  .skip-prompt-close {\n    background: none;\n    border: none;\n    color: #999;\n    cursor: pointer;\n    font-size: 18px;\n    padding: 0;\n    width: 20px;\n    height: 20px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-left: 8px;\n  }\n\n  .skip-prompt-close:hover {\n    color: white;\n  }\n\n  .skip-prompt.hiding {\n    animation: fadeOut 0.3s ease-out forwards;\n  }\n\n  /* 进度条片段标记 */\n  #sponsorblock-preview-bar {\n    overflow: hidden;\n    padding: 0;\n    margin: 0;\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    z-index: 1;\n    pointer-events: none;\n  }\n\n  .sponsorblock-segment {\n    display: inline-block;\n    height: 100%;\n    position: absolute;\n    min-width: 1px;\n    opacity: 0.7;\n    transition: all 0.2s ease;\n    pointer-events: auto;\n    cursor: pointer;\n  }\n\n  .sponsorblock-segment:hover {\n    opacity: 0.95;\n    transform: scaleY(1.5);\n    z-index: 100;\n  }\n\n  /* 片段详情弹窗 */\n  .segment-details-popup {\n    position: fixed;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    background: rgba(0, 0, 0, 0.95);\n    color: white;\n    border-radius: 12px;\n    padding: 24px;\n    min-width: 350px;\n    max-width: 500px;\n    box-shadow: 0 8px 32px rgba(0,0,0,0.5);\n    z-index: 10002;\n    animation: popupFadeIn 0.2s ease-out;\n  }\n\n  @keyframes popupFadeIn {\n    from {\n      opacity: 0;\n      transform: translate(-50%, -45%);\n    }\n    to {\n      opacity: 1;\n      transform: translate(-50%, -50%);\n    }\n  }\n\n  .segment-details-header {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    margin-bottom: 16px;\n    padding-bottom: 12px;\n    border-bottom: 1px solid rgba(255,255,255,0.2);\n  }\n\n  .segment-details-title {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    font-size: 18px;\n    font-weight: 500;\n  }\n\n  .segment-details-close {\n    background: none;\n    border: none;\n    color: #999;\n    font-size: 24px;\n    cursor: pointer;\n    padding: 0;\n    width: 32px;\n    height: 32px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 4px;\n    transition: all 0.2s;\n  }\n\n  .segment-details-close:hover {\n    background: rgba(255,255,255,0.1);\n    color: white;\n  }\n\n  .segment-details-content {\n    margin-bottom: 16px;\n  }\n\n  .segment-details-row {\n    display: flex;\n    justify-content: space-between;\n    padding: 8px 0;\n    font-size: 14px;\n  }\n\n  .segment-details-label {\n    color: #999;\n  }\n\n  .segment-details-value {\n    color: white;\n    font-weight: 500;\n  }\n\n  .segment-details-actions {\n    display: flex;\n    gap: 12px;\n    justify-content: flex-end;\n  }\n\n  .segment-details-btn {\n    padding: 8px 16px;\n    border: none;\n    border-radius: 6px;\n    cursor: pointer;\n    font-size: 14px;\n    transition: all 0.2s;\n  }\n\n  .segment-details-btn-primary {\n    background: #00a1d6;\n    color: white;\n  }\n\n  .segment-details-btn-primary:hover {\n    background: #0087b3;\n  }\n\n  .segment-details-btn-secondary {\n    background: rgba(255,255,255,0.1);\n    color: white;\n  }\n\n  .segment-details-btn-secondary:hover {\n    background: rgba(255,255,255,0.2);\n  }\n\n  .segment-details-overlay {\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    background: rgba(0,0,0,0.3);\n    z-index: 10001;\n  }\n\n  /* SponsorBlock 设置面板样式 */\n  .sponsor-settings-section {\n    margin-bottom: 24px;\n  }\n\n  .sponsor-settings-section h3 {\n    font-size: 16px;\n    color: #e5e7eb;\n    margin: 0 0 12px 0;\n  }\n\n  .sponsor-checkbox-group {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n  }\n\n  .sponsor-checkbox-item {\n    display: flex;\n    align-items: center;\n    padding: 8px;\n    border-radius: 6px;\n    transition: background 0.2s;\n  }\n\n  .sponsor-checkbox-item:hover {\n    background: rgba(255, 255, 255, 0.05);\n  }\n\n  .sponsor-checkbox-item input[type="checkbox"] {\n    margin-right: 10px;\n    cursor: pointer;\n    width: 18px;\n    height: 18px;\n  }\n\n  .sponsor-checkbox-item label {\n    cursor: pointer;\n    flex: 1;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    color: #e5e7eb;\n  }\n\n  .category-color-dot {\n    width: 12px;\n    height: 12px;\n    border-radius: 50%;\n    display: inline-block;\n  }\n\n  .sponsor-switch-item {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 12px;\n    border-radius: 6px;\n    background: rgba(255, 255, 255, 0.05);\n    border: 1px solid rgba(254, 235, 234, 0.2);\n    margin-bottom: 8px;\n    color: #e5e7eb;\n  }\n`;const Z='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\n    <path d="M3 21L12 12L12.2 6.2L11 5M15 4V2M15 16V14M8 9H10M20 9H22M17.8 11.8L19 13M17.8 6.2L19 5" stroke="#feebea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>\n    <circle cx="12" cy="12" r="1.5" fill="#feebea"/>\n    <path d="M17 7L12 12L7 7" stroke="#feebea" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.5"/>\n  </svg>',ee='<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\n    <path d="M12 3V16M12 16L7 11M12 16L17 11" stroke="#feebea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>\n    <path d="M3 17V19C3 20.1046 3.89543 21 5 21H19C20.1046 21 21 20.1046 21 19V17" stroke="#feebea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>\n  </svg>',te='<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">\n    <path d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z" fill="#fff"/>\n    <path fill-rule="evenodd" clip-rule="evenodd" d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.724 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z" fill="#000"/>\n  </svg>';function ne(e){if(!e||"string"!=typeof e)return {valid:false,error:"URL不能为空"};if(!e.startsWith("http://")&&!e.startsWith("https://"))return {valid:false,error:"URL必须以 http:// 或 https:// 开头"};try{return new URL(e),{valid:!0,error:null}}catch(t){return {valid:false,error:"URL格式无效"}}}function oe(e){return e&&"string"==typeof e?0===e.trim().length?{valid:false,error:"API Key不能为空"}:e.length<10?{valid:false,error:"API Key长度过短,请检查是否完整"}:{valid:true,error:null}:{valid:false,error:"API Key不能为空"}}function ie(e){return e?e.bvid&&e.bvid.match(/^BV[1-9A-Za-z]{10}$/)?e.cid?{valid:true,error:null}:{valid:false,error:"CID为空"}:{valid:false,error:"BV号格式错误"}:{valid:false,error:"视频信息为空"}}const se=new class{constructor(){this.events=new Map,this.modules=new Map,this.subscriptionId=0;}on(e,t,n=null){return this.events.has(e)||this.events.set(e,[]),this.events.get(e).push(t),n&&(this.modules.has(n)||this.modules.set(n,new Set),this.modules.get(n).add({event:e,handler:t})),()=>this.off(e,t)}once(e,t){const n=(...o)=>{t(...o),this.off(e,n);};this.on(e,n);}off(e,t){if(!this.events.has(e))return;const n=this.events.get(e),o=n.indexOf(t);o>-1&&n.splice(o,1),0===n.length&&this.events.delete(e);}emit(e,...t){if(!this.events.has(e))return;const n=[...this.events.get(e)];for(const i of n)try{i(...t);}catch(o){console.error(`[EventBus] 事件 "${e}" 处理出错:`,o);}}clear(){this.events.clear(),this.modules.clear();}clearModule(e){const t=this.modules.get(e);t&&(t.forEach(({event:e,handler:t})=>{this.off(e,t);}),this.modules.delete(e));}listenerCount(e){return this.events.has(e)?this.events.get(e).length:0}getModuleStats(){const e={};return this.modules.forEach((t,n)=>{e[n]=t.size;}),e}};const re=new class{constructor(){this.reset();}reset(){this.subtitle={data:null,cache:{},capturedUrl:null},this.request={isRequesting:false,currentRequestKey:null,requestPromise:null,abortController:null},this.ai={isSummarizing:false,currentSummary:null,summaryPromise:null,abortController:null},this.notion={isSending:false,sendPromise:null},this.ui={ballStatus:h,panelVisible:false,isDragging:false,dragStart:{x:0,y:0},panelStart:{x:0,y:0}},this.video={bvid:null,cid:null,aid:null};}setVideoInfo(e){return !!ie(e).valid&&(this.video.bvid=e.bvid,this.video.cid=e.cid,this.video.aid=e.aid,true)}getVideoInfo(){return {...this.video}}getVideoKey(){return ie(e=this.video).valid?`${e.bvid}-${e.cid}`:null;var e;}setSubtitleData(e){this.subtitle.data=e;const t=this.getVideoKey();t&&(this.subtitle.cache[t]=e),e&&e.length>0&&se.emit(y,e,t);}getSubtitleData(e=null){const t=e||this.getVideoKey();return t?this.subtitle.cache[t]?this.subtitle.cache[t]:t===this.getVideoKey()?this.subtitle.data:null:this.subtitle.data}startRequest(){const e=this.getVideoKey();return e?this.request.isRequesting&&this.request.currentRequestKey===e?{success:false,reason:"已有相同视频的请求在进行中"}:this.subtitle.cache[e]?{success:false,reason:"已有缓存"}:(this.request.isRequesting&&this.cancelRequest(),this.request.isRequesting=true,this.request.currentRequestKey=e,{success:true,reason:null}):{success:false,reason:"视频信息无效"}}finishRequest(){this.request.isRequesting=false,this.request.currentRequestKey=null,this.request.requestPromise=null,this.request.abortController=null;}cancelRequest(){this.request.abortController&&this.request.abortController.abort(),this.finishRequest();}startAISummary(){return !this.ai.isSummarizing&&(this.ai.isSummarizing=true,this.ai.abortController=new AbortController,se.emit(w),true)}finishAISummary(e){this.ai.isSummarizing=false,this.ai.currentSummary=e,this.ai.summaryPromise=null,this.ai.abortController=null;const t=this.getVideoKey();t&&e&&sessionStorage.setItem(`ai-summary-${t}`,e),se.emit(S,e,t);}cancelAISummary(){this.ai.abortController&&this.ai.abortController.abort(),this.ai.isSummarizing=false,this.ai.summaryPromise=null,this.ai.abortController=null;}getAISummary(e=null){const t=e||this.getVideoKey();if(!t)return this.ai.currentSummary;const n=sessionStorage.getItem(`ai-summary-${t}`);return n||(t===this.getVideoKey()?this.ai.currentSummary:null)}setBallStatus(e){this.ui.ballStatus!==e&&(this.ui.ballStatus=e,se.emit(L,e));}getBallStatus(){return this.ui.ballStatus}togglePanel(){this.ui.panelVisible=!this.ui.panelVisible,se.emit(T,this.ui.panelVisible);}setPanelVisible(e){this.ui.panelVisible!==e&&(this.ui.panelVisible=e,se.emit(T,e));}};const ae=new class{getAIConfigs(){const e=GM_getValue(N,[]);return 0===e.length?[...P]:e}saveAIConfigs(e){GM_setValue(N,e);}getSelectedAIConfigId(){return GM_getValue(z,"openrouter")}setSelectedAIConfigId(e){GM_setValue(z,e);}getSelectedAIConfig(){const e=this.getAIConfigs(),t=this.getSelectedAIConfigId();return e.find(e=>e.id===t)||e[0]||null}addAIConfig(e){if(!(e.name&&e.url&&e.apiKey&&e.model))return {success:false,error:"所有字段都是必填的"};const t=ne(e.url);if(!t.valid)return {success:false,error:t.error};const n=oe(e.apiKey);if(!n.valid)return {success:false,error:n.error};const o=this.getAIConfigs(),i={id:Date.now().toString(),name:e.name.trim(),url:e.url.trim(),apiKey:e.apiKey.trim(),model:e.model.trim(),prompt:e.prompt||"根据以下视频字幕,用中文总结视频内容:\n\n",isOpenRouter:e.isOpenRouter||false};return o.push(i),this.saveAIConfigs(o),this.setSelectedAIConfigId(i.id),{success:true,error:null,config:i}}updateAIConfig(e,t){const n=this.getAIConfigs(),o=n.findIndex(t=>t.id===e);if(-1===o)return {success:false,error:"配置不存在"};if(t.url){const e=ne(t.url);if(!e.valid)return {success:false,error:e.error}}if(t.apiKey){const e=oe(t.apiKey);if(!e.valid)return {success:false,error:e.error}}return n[o]={...n[o],...t},this.saveAIConfigs(n),{success:true,error:null}}deleteAIConfig(e){if("openrouter"===e||"openai"===e)return {success:false,error:"预设配置不能删除"};let t=this.getAIConfigs();return t=t.filter(t=>t.id!==e),this.saveAIConfigs(t),this.getSelectedAIConfigId()===e&&this.setSelectedAIConfigId("openrouter"),{success:true,error:null}}getAIAutoSummaryEnabled(){return GM_getValue(_,true)}setAIAutoSummaryEnabled(e){GM_setValue(_,e);}getNotionConfig(){return {apiKey:GM_getValue($,""),parentPageId:GM_getValue(q,""),databaseId:GM_getValue(V,"")}}saveNotionConfig(e){if(e.apiKey){const t=oe(e.apiKey);if(!t.valid)return {success:false,error:t.error};GM_setValue($,e.apiKey.trim());}if(e.parentPageId){const t=function(e){if(!e||"string"!=typeof e)return {valid:false,cleaned:null,error:"Page ID不能为空"};let t=e.split("?")[0].split("#")[0];const n=t.match(j);return n?(t=n[1].replace(/-/g,""),t.length!==u?{valid:false,cleaned:null,error:`Page ID长度错误,需要${u}位字符`}:{valid:true,cleaned:t,error:null}):{valid:false,cleaned:null,error:"Page ID格式错误,应为32位十六进制字符"}}(e.parentPageId);if(!t.valid)return {success:false,error:t.error};GM_setValue(q,t.cleaned);}return void 0!==e.databaseId&&GM_setValue(V,e.databaseId),{success:true,error:null}}getNotionAutoSendEnabled(){return GM_getValue(R,false)}setNotionAutoSendEnabled(e){GM_setValue(R,e);}};function le(e){const t=Math.floor(e/60),n=Math.floor(e%60);return `${t.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`}function ce(){let e=null,t=null,n=null;e=function(e=window.location.href){const t=e.match(H);if(t)return t[1];const n=e.match(K);return n?n[0]:null}();try{const o=unsafeWindow.__INITIAL_STATE__;o&&o.videoData&&(e=e||o.videoData.bvid,t=o.videoData.cid||o.videoData.pages?.[0]?.cid,n=o.videoData.aid);}catch(o){}return {bvid:e,cid:t,aid:n}}function de(){let e="";try{const t=unsafeWindow.__INITIAL_STATE__;t&&t.videoData&&t.videoData.title&&(e=t.videoData.title);}catch(t){}if(!e){const t=document.querySelector(Q);t&&(e=t.textContent.trim());}return e||(e=document.title.replace(/_哔哩哔哩.*$/,"").replace(/_bilibili.*$/i,"").trim()),e||"未知视频"}function pe(e){return new Promise(t=>setTimeout(t,e))}const ue=new class{constructor(){this.capturedSubtitleUrl=null,this.setupInterceptor();}setupInterceptor(){const e=unsafeWindow.XMLHttpRequest.prototype.open,t=unsafeWindow.XMLHttpRequest.prototype.send;unsafeWindow.XMLHttpRequest.prototype.open=function(t,n){return this._url=n,e.apply(this,arguments)},unsafeWindow.XMLHttpRequest.prototype.send=function(){return this._url&&this._url.includes("aisubtitle.hdslb.com")&&(ue.capturedSubtitleUrl=this._url,re.subtitle.capturedUrl=this._url,setTimeout(()=>{ue.downloadCapturedSubtitle();},o)),t.apply(this,arguments)};}async downloadCapturedSubtitle(){if(!this.capturedSubtitleUrl)return;const e=ce();re.setVideoInfo(e);const t=re.startRequest();if(t.success){re.setBallStatus(g),se.emit(v,e);try{const t=await this._fetchSubtitle(this.capturedSubtitleUrl,e),n=function(e){if(!Array.isArray(e))return {valid:!1,error:"字幕数据格式错误"};if(0===e.length)return {valid:!1,error:"字幕数据为空"};const t=e[0];return t.from&&t.to&&t.content?{valid:!0,error:null}:{valid:!1,error:"字幕数据格式不完整"}}(t);if(!n.valid)throw new Error(n.error);re.setSubtitleData(t),re.setBallStatus(m);}catch(n){console.error("[SubtitleService] 字幕获取失败:",n),re.setBallStatus(f),se.emit(x,n.message);}finally{re.finishRequest();}}else if("已有缓存"===t.reason){const e=re.getSubtitleData();e&&(re.setBallStatus(m),se.emit(y,e,re.getVideoKey()));}}_fetchSubtitle(e,t){return new Promise((n,o)=>{GM_xmlhttpRequest({method:"GET",url:e,headers:{Accept:"application/json, text/plain, */*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"zh-CN,zh;q=0.9,en;q=0.8","Cache-Control":"no-cache",Origin:"https://www.bilibili.com",Referer:`https://www.bilibili.com/video/${t.bvid}/`,"Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"cross-site","User-Agent":navigator.userAgent},anonymous:false,onload:e=>{const i=ce();if(i.bvid===t.bvid&&i.cid===t.cid)if(200===e.status)if(e.responseText.trim().startsWith("<!DOCTYPE")||e.responseText.trim().startsWith("<html"))o(new Error("服务器返回HTML而非JSON,可能被重定向"));else try{const t=JSON.parse(e.responseText);t.body&&t.body.length>0?n(t.body):o(new Error("字幕内容为空"));}catch(s){o(new Error("解析字幕数据失败"));}else o(new Error(`请求失败: ${e.status}`));else o(new Error("视频已切换"));},onerror:()=>{o(new Error("网络请求失败"));}});})}async checkSubtitleButton(){let n=0;return new Promise(o=>{const i=setInterval(()=>{n++;document.querySelector(Y)?(clearInterval(i),this.tryActivateSubtitle(),o(true)):n>=t&&(clearInterval(i),re.setBallStatus(b),o(false));},e);})}async tryActivateSubtitle(){await pe(n),this.capturedSubtitleUrl?this.downloadCapturedSubtitle():this.triggerSubtitleSelection();}async triggerSubtitleSelection(){const e=document.querySelector(Y);if(!e)return void re.setBallStatus(b);e.click(),await pe(i);let t=document.querySelector('.bpx-player-ctrl-subtitle-language-item[data-lan="ai-zh"]');if(t||(t=document.querySelector('.bpx-player-ctrl-subtitle-language-item[data-lan*="zh"]')),!t){const e=document.querySelectorAll(".bpx-player-ctrl-subtitle-language-item");for(let n of e){const e=n.querySelector(".bpx-player-ctrl-subtitle-language-item-text");if(e&&e.textContent.includes("中文")){t=n;break}}}if(t){t.click(),await pe(s);const e=document.querySelector(X);e&&e.click(),await pe(n),this.capturedSubtitleUrl?this.downloadCapturedSubtitle():re.setBallStatus(f);}else {const t=document.querySelector(".bpx-player-ctrl-subtitle-language-item");if(t){t.click(),await pe(s);const e=document.querySelector(X);e&&e.click(),await pe(n),this.capturedSubtitleUrl?this.downloadCapturedSubtitle():re.setBallStatus(f);}else e.click(),re.setBallStatus(b);}}downloadSubtitleFile(){const e=re.getSubtitleData();if(!e||0===e.length)throw new Error("没有字幕数据可下载");const t=re.getVideoInfo(),n=de();!function(e,t,n="text/plain;charset=utf-8"){const o=new Blob([e],{type:n}),i=URL.createObjectURL(o),s=document.createElement("a");s.href=i,s.download=t,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(i);}(e.map(e=>e.content).join("\n"),`${n}_${t.bvid}_字幕.txt`);}reset(){this.capturedSubtitleUrl=null,re.subtitle.capturedUrl=null;}};const he=new class{async fetchOpenRouterModels(e,t){const n=t.replace("/chat/completions","/models"),o=await fetch(n,{headers:{Authorization:`Bearer ${e}`}});if(!o.ok)throw new Error(`获取模型列表失败: ${o.status}`);return (await o.json()).data||[]}async summarize(e,t=false){if(!re.startAISummary())throw new Error("已有总结任务在进行中");try{const t=ae.getSelectedAIConfig();if(!t)throw new Error("未找到AI配置,请先在设置中添加配置");if(!t.apiKey||""===t.apiKey.trim())throw new Error('请先配置 AI API Key\n\n请点击右上角设置按钮,选择"AI配置",然后为所选的AI服务商配置API Key');if(!t.url||!t.url.startsWith("http"))throw new Error("API URL格式错误,请在设置中检查配置");if(!t.model||""===t.model.trim())throw new Error("未配置模型,请在设置中选择AI模型");const n=e.map(e=>e.content).join("\n"),o={"Content-Type":"application/json",Authorization:`Bearer ${t.apiKey}`};t.isOpenRouter&&(o["HTTP-Referer"]=window.location.origin,o["X-Title"]="Bilibili Subtitle Extractor");const i={model:t.model,messages:[{role:"user",content:t.prompt+n}],stream:!0},s=this._streamingRequest(t.url,o,i),r=await function(e,t,n="操作超时"){return Promise.race([e,new Promise((e,o)=>setTimeout(()=>o(new Error(n)),t))])}(s,l,"AI总结超时,请稍后重试");return re.finishAISummary(r),r}catch(n){throw re.cancelAISummary(),se.emit(k,n.message),n}}async _streamingRequest(e,t,n){const o=await fetch(e,{method:"POST",headers:t,body:JSON.stringify(n),signal:re.ai.abortController?.signal});if(!o.ok){const e=await o.text();throw console.error("[AIService] API错误响应:",e),new Error(`API请求失败: ${o.status} ${o.statusText}`)}const i=o.body.getReader(),s=new TextDecoder;let r="";try{for(;;){const{done:e,value:t}=await i.read();if(e)break;const n=s.decode(t).split("\n");for(const o of n)if(o.startsWith("data: ")){const e=o.slice(6);if("[DONE]"===e)continue;try{const t=JSON.parse(e),n=t.choices[0]?.delta?.content;n&&(r+=n,se.emit(I,r));}catch(a){}}}return r}finally{i.releaseLock();}}cancelCurrentSummary(){re.cancelAISummary();}};const ge=new class{async sendSubtitle(e,t=false){const n=ae.getNotionConfig();if(!n.apiKey)throw new Error("请先配置 Notion API Key");if(!e||0===e.length)throw new Error("没有字幕数据可发送");re.notion.isSending=true,se.emit(E);try{const t=re.getVideoInfo(),o=de(),i=window.location.href.split("?")[0],s=function(){try{const e=unsafeWindow.__INITIAL_STATE__;if(e&&e.videoData&&e.videoData.owner)return e.videoData.owner.name}catch(e){}return "未知"}(),r=this._buildPageContent(t,o,i,e);let a=n.databaseId;if(!a){if(!n.parentPageId)throw new Error("请先配置目标位置(Page ID 或 Database ID)");a=n.parentPageId;}const l=await this._getDatabaseSchema(n.apiKey,a),c=this._buildProperties(l,t,o,i,s,e);await this._createPage(n.apiKey,a,c,r),n.databaseId||ae.saveNotionConfig({databaseId:a}),re.notion.isSending=!1,se.emit(C);}catch(o){throw re.notion.isSending=false,se.emit(A,o.message),o}}async createDatabase(e,t){const n={parent:{type:"page_id",page_id:t},title:[{type:"text",text:{content:"📺 Bilibili 字幕收藏"}}],properties:{"标题":{title:{}},"BV号":{rich_text:{}},"创作者":{rich_text:{}},"视频链接":{url:{}},"收藏时间":{date:{}},"字幕条数":{number:{}},"状态":{select:{options:[{name:"未总结",color:"gray"},{name:"已总结",color:"green"}]}},"总结":{rich_text:{}}}};return new Promise((t,o)=>{GM_xmlhttpRequest({method:"POST",url:`${F}/databases`,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json","Notion-Version":O},data:JSON.stringify(n),onload:e=>{if(200===e.status){const n=JSON.parse(e.responseText);t(n.id);}else {const t=this._parseNotionError(e);o(t);}},onerror:e=>{o(new Error("网络请求失败"));}});})}_getDatabaseSchema(e,t){return new Promise((n,o)=>{GM_xmlhttpRequest({method:"GET",url:`${F}/databases/${t}`,headers:{Authorization:`Bearer ${e}`,"Notion-Version":O},onload:e=>{if(200===e.status){const t=JSON.parse(e.responseText);n(t.properties);}else {const t=this._parseNotionError(e);o(t);}},onerror:()=>{o(new Error("获取数据库结构失败"));}});})}_createPage(e,t,n,o){const i={parent:{database_id:t},properties:n,children:o};return new Promise((t,n)=>{GM_xmlhttpRequest({method:"POST",url:`${F}/pages`,headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json","Notion-Version":O},data:JSON.stringify(i),onload:e=>{if(200===e.status){const n=JSON.parse(e.responseText);t(n);}else {const t=this._parseNotionError(e);n(t);}},onerror:()=>{n(new Error("创建页面失败"));}});})}_buildPageContent(e,t,n,o){const i=[{object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"📹 视频信息"}}]}},{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`视频标题:${t}`}}]}},{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`BV号:${e.bvid}`}}]}},{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`视频链接:${n}`}}]}},{object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:`字幕总数:${o.length} 条`}}]}},{object:"block",type:"divider",divider:{}},{object:"block",type:"heading_2",heading_2:{rich_text:[{type:"text",text:{content:"📝 字幕内容"}}]}}],s=[];let r="";const a=d;for(let l of o){const e=`${l.content}\n`;r.length+e.length>a?(r&&s.push({type:"text",text:{content:r}}),r=e):r+=e;}return r&&s.push({type:"text",text:{content:r}}),i.push({object:"block",type:"code",code:{rich_text:s,language:"plain text"}}),i}_buildProperties(e,t,n,o,i,s){const r={},a=Object.keys(e).find(t=>"title"===e[t].type);return a&&(r[a]={title:[{text:{content:n}}]}),Object.keys(e).forEach(n=>{const a=e[n].type,l=n.toLowerCase().replace(/\s+/g,"");if(!l.includes("bv")||"rich_text"!==a&&"text"!==a||(r[n]={rich_text:[{text:{content:t.bvid||""}}]}),!(l.includes("创作")||l.includes("作者")||l.includes("creator")||l.includes("up主"))||"rich_text"!==a&&"text"!==a||(r[n]={rich_text:[{text:{content:i}}]}),l.includes("链接")&&"url"===a&&(r[n]={url:o}),"date"===a&&("日期"===l||l.includes("收藏")||l.includes("添加")||l.includes("创建"))&&(r[n]={date:{start:(new Date).toISOString()}}),(l.includes("条数")||l.includes("数量"))&&"number"===a&&(r[n]={number:s.length}),"状态"===l||"status"===l){const e=re.getVideoKey(),t=e?re.getAISummary(e):null;"select"===a||"status"===a?r[n]={[a]:{name:t?"已总结":"未总结"}}:"rich_text"===a&&(r[n]={rich_text:[{text:{content:t?"已总结":"未总结"}}]});}if("总结"===l||"summary"===l){const e=re.getVideoKey(),t=e?re.getAISummary(e):null;"rich_text"===a&&t&&(r[n]={rich_text:[{text:{content:t.substring(0,p)}}]});}}),r}_parseNotionError(e){try{const t=JSON.parse(e.responseText);return "object_not_found"===t.code||t.message?.includes("Could not find")?new Error("找不到指定的Notion页面或数据库,请检查:\n1. ID是否正确\n2. 是否已在Notion中授权该Integration"):new Error(t.message||"未知错误")}catch(t){return new Error(`请求失败: ${e.status}`)}}},me="bilibili_subtitle_notes",be=5e3;const fe=new class{constructor(){this.blueDot=null,this.blueDotHideTimeout=null,this.savedSelectionText="",this.selectionTimeout=null;}init(){console.log("[NotesService] 初始化笔记服务...");try{this.createBlueDot(),this.initSelectionListener(),console.log("[NotesService] ✓ 笔记服务初始化成功");}catch(e){console.error("[NotesService] ✗ 初始化失败:",e);}}getAllNotes(){try{const e=localStorage.getItem(me);return e?JSON.parse(e):[]}catch(e){return console.error("读取笔记数据失败:",e),[]}}saveNotes(e){try{localStorage.setItem(me,JSON.stringify(e));}catch(t){console.error("保存笔记数据失败:",t);}}addNote(e,t){console.log(`[NotesService] 添加新笔记,内容长度: ${e.length},URL: ${t}`);try{const n={id:Date.now()+Math.random().toString(36).substr(2,9),content:e.trim(),url:t,timestamp:Date.now()},o=this.getAllNotes();return o.unshift(n),this.saveNotes(o),console.log(`[NotesService] ✓ 笔记已添加,ID: ${n.id},当前笔记总数: ${o.length}`),n}catch(n){throw console.error("[NotesService] ✗ 添加笔记失败:",n),n}}deleteNote(e){const t=this.getAllNotes().filter(t=>t.id!==e);this.saveNotes(t);}deleteNotes(e){const t=this.getAllNotes().filter(t=>!e.includes(t.id));this.saveNotes(t);}getGroupedNotes(){const e=this.getAllNotes(),t={};return e.forEach(e=>{const n=this.formatDate(e.timestamp);t[n]||(t[n]=[]),t[n].push(e);}),Object.keys(t).sort((e,n)=>{const o=t[e][0].timestamp;return t[n][0].timestamp-o}).map(e=>({date:e,notes:t[e]}))}formatDate(e){const t=new Date(e),n=new Date,o=new Date(n);if(o.setDate(o.getDate()-1),t.toDateString()===n.toDateString())return "今天";if(t.toDateString()===o.toDateString())return "昨天";return `${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}formatTime(e){const t=new Date(e);return `${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`}createBlueDot(){if(console.log("[NotesService] 创建笔记保存点元素..."),this.blueDot)return console.log("[NotesService] 笔记保存点元素已存在"),this.blueDot;try{return this.blueDot=document.createElement("div"),this.blueDot.id="note-saver-blue-dot",this.blueDot.innerHTML='\n        <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="20" height="20">\n          <path d="M20.7 5.2c.4.4.4 1.1 0 1.6l-1 1-3.3-3.3 1-1c.4-.4 1.1-.4 1.6 0l1.7 1.7zm-3.3 2.3L6.7 18.2c-.2.2-.4.3-.7.3H3c-.6 0-1-.4-1-1v-3c0-.3.1-.5.3-.7L13 3.1l3.3 3.3z" stroke="#feebea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="rgba(0,0,0,0.7)"/>\n        </svg>\n      ',this.blueDot.style.cssText="\n        position: absolute;\n        cursor: pointer;\n        z-index: 2147483647;\n        display: none;\n        transition: transform 0.2s, filter 0.2s;\n        pointer-events: auto;\n      ",this.blueDot.addEventListener("mouseenter",()=>{console.log("[NotesService] 鼠标进入保存点"),this.blueDot.style.transform="scale(1.15)",this.blueDot.style.filter="drop-shadow(0 2px 4px rgba(254, 235, 234, 0.5))";}),this.blueDot.addEventListener("mouseleave",()=>{console.log("[NotesService] 鼠标离开保存点"),this.blueDot.style.transform="scale(1)",this.blueDot.style.filter="none";}),this.blueDot.addEventListener("click",e=>this.handleBlueDotClick(e)),document.body.appendChild(this.blueDot),console.log("[NotesService] ✓ 笔记保存点元素已创建并添加到body,z-index: 2147483647"),this.blueDot}catch(e){return console.error("[NotesService] ✗ 创建笔记保存点元素失败:",e),null}}showBlueDot(e,t){console.log(`[NotesService] 显示保存点在位置 (${e}, ${t})`);try{const n=this.createBlueDot();if(!n)return void console.error("[NotesService] ✗ 无法获取保存点元素");n.style.left=`${e}px`,n.style.top=`${t}px`,n.style.display="block",console.log(`[NotesService] ✓ 保存点已显示,位置: left=${e}px, top=${t}px, z-index=${n.style.zIndex||"2147483647"}`),this.blueDotHideTimeout&&clearTimeout(this.blueDotHideTimeout),this.blueDotHideTimeout=setTimeout(()=>{console.log("[NotesService] 保存点自动隐藏超时触发"),this.hideBlueDot(),this.savedSelectionText="";},be);}catch(n){console.error("[NotesService] ✗ 显示保存点失败:",n);}}hideBlueDot(){console.log("[NotesService] 隐藏保存点");try{this.blueDot&&(this.blueDot.style.display="none",console.log("[NotesService] ✓ 保存点已隐藏")),this.blueDotHideTimeout&&(clearTimeout(this.blueDotHideTimeout),this.blueDotHideTimeout=null);}catch(e){console.error("[NotesService] ✗ 隐藏保存点失败:",e);}}handleBlueDotClick(e){console.log("[NotesService] 保存点被点击");try{if(e&&(e.preventDefault(),e.stopPropagation()),this.savedSelectionText){console.log(`[NotesService] 保存选中文本: "${this.savedSelectionText.substring(0,50)}${this.savedSelectionText.length>50?"...":""}"`);const e=this.addNote(this.savedSelectionText,window.location.href);console.log("[NotesService] ✓ 笔记已保存,ID:",e.id),this.savedSelectionText="";const t=window.getSelection();t.rangeCount>0&&(t.removeAllRanges(),console.log("[NotesService] 已清除文本选择"));}else console.warn("[NotesService] ⚠ 没有保存的选中文本");this.hideBlueDot();}catch(t){console.error("[NotesService] ✗ 处理保存点点击失败:",t);}}initSelectionListener(){console.log("[NotesService] 初始化文本选择监听器...");try{document.addEventListener("mouseup",e=>{console.log("[NotesService] mouseup 事件触发"),this.selectionTimeout&&clearTimeout(this.selectionTimeout);const t=e.clientX,n=e.clientY;console.log(`[NotesService] 鼠标位置: clientX=${t}, clientY=${n}`),this.selectionTimeout=setTimeout(()=>{try{const e=window.getSelection(),o=e.toString().trim();if(console.log(`[NotesService] 选中文本长度: ${o.length}`),o&&e.rangeCount>0){console.log(`[NotesService] 检测到文本选择: "${o.substring(0,50)}${o.length>50?"...":""}"`),this.savedSelectionText=o;const e=t+window.scrollX+10,i=n+window.scrollY+10;console.log(`[NotesService] 滚动偏移: scrollX=${window.scrollX}, scrollY=${window.scrollY}`),console.log(`[NotesService] 计算位置(鼠标附近): x=${e}, y=${i}`),this.showBlueDot(e,i);}else console.log("[NotesService] 没有选中文本或选择范围为空"),this.savedSelectionText="",this.hideBlueDot();}catch(e){console.error("[NotesService] ✗ 处理文本选择失败:",e);}},100);}),document.addEventListener("mousedown",e=>{!this.blueDot||e.target!==this.blueDot&&!this.blueDot.contains(e.target)?(console.log("[NotesService] mousedown 事件,清空选中文本并隐藏保存点"),this.savedSelectionText="",this.hideBlueDot()):console.log("[NotesService] mousedown 在保存点上,忽略");}),console.log("[NotesService] ✓ 文本选择监听器已初始化");}catch(e){console.error("[NotesService] ✗ 初始化文本选择监听器失败:",e);}}saveSubtitleNote(e){return this.addNote(e,window.location.href)}},ye=.1,xe=1.5,ve=200,we=1e3,Se=10,ke=1,Ie=100;const Ee=new class{constructor(){this.state={baseSpeed:1,isRightOptionPressed:false,isTempBoosted:false,lastKeyPressTime:{comma:0,period:0},lastOptionPressTime:0,optionDoubleClickTimer:null,volumeDetectionEnabled:false,currentVolumeThreshold:-40,isVolumeBoosted:false,mediaAnalyzers:new Map,commaPressed:false,periodPressed:false,volumeHistory:[],maxHistoryLength:100,volumeChart:null},this.observer=null;}init(){this.bindKeyboardEvents(),this.observeMediaElements(),this.applySpeedToExistingMedia();}getMediaElements(){return Array.from(document.querySelectorAll("video, audio"))}applySpeed(e){this.getMediaElements().forEach(t=>{t.playbackRate=e,this.showSpeedIndicator(t,e);});}showSpeedIndicator(e,t){const n=e.parentElement?.querySelector(".speed-indicator");n&&n.remove();const o=document.createElement("div");if(o.className="speed-indicator",o.textContent=`${t.toFixed(2)}x`,o.style.cssText="\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      background: rgba(0, 0, 0, 0.7);\n      color: white;\n      padding: 4px 8px;\n      border-radius: 4px;\n      font-size: 14px;\n      font-family: monospace;\n      z-index: 999999;\n      pointer-events: none;\n      transition: opacity 0.3s;\n    ",e.parentElement){"static"===window.getComputedStyle(e.parentElement).position&&(e.parentElement.style.position="relative"),e.parentElement.appendChild(o),setTimeout(()=>{o.style.opacity="0",setTimeout(()=>{o.remove();},300);},we);}}adjustBaseSpeed(e){this.state.baseSpeed=Math.max(.1,Math.min(Se,this.state.baseSpeed+e)),this.applySpeed(this.calculateFinalSpeed());}setBaseSpeed(e){this.state.baseSpeed=Math.max(.1,Math.min(Se,e)),this.applySpeed(this.calculateFinalSpeed());}applyTemporaryBoost(){this.state.isTempBoosted=true,this.applySpeed(this.calculateFinalSpeed());}removeTemporaryBoost(){this.state.isTempBoosted=false,this.applySpeed(this.calculateFinalSpeed());}applyPermanentBoost(){this.state.baseSpeed=Math.min(Se,this.state.baseSpeed*xe),this.applySpeed(this.calculateFinalSpeed());}resetToNormalSpeed(){this.state.baseSpeed=1,this.applySpeed(this.calculateFinalSpeed());}setToDoubleSpeed(){this.state.baseSpeed=2,this.applySpeed(this.calculateFinalSpeed());}detectDoubleClick(e){const t=Date.now(),n=this.state.lastKeyPressTime[e];return this.state.lastKeyPressTime[e]=t,t-n<ve}calculateFinalSpeed(){let e=this.state.baseSpeed;return this.state.isTempBoosted&&(e*=xe),this.state.isVolumeBoosted&&(e*=xe),Math.min(Se,e)}setupVolumeAnalyzer(e){try{if(this.state.mediaAnalyzers.has(e))return this.state.mediaAnalyzers.get(e);const t=new(window.AudioContext||window.webkitAudioContext),n=t.createAnalyser();n.fftSize=256;t.createMediaElementSource(e).connect(n),n.connect(t.destination);const o={context:t,analyser:n,dataArray:new Uint8Array(n.frequencyBinCount),intervalId:null};return this.state.mediaAnalyzers.set(e,o),o}catch(t){return console.error("创建音频分析器失败:",t),null}}getVolumeLevel(e){e.analyser.getByteFrequencyData(e.dataArray);let t=0;for(let o=0;o<e.dataArray.length;o++)t+=e.dataArray[o];const n=t/e.dataArray.length;if(0===n)return  -1/0;return 20*Math.log10(n/255)}startVolumeDetection(e){const t=this.setupVolumeAnalyzer(e);t&&(t.intervalId&&clearInterval(t.intervalId),this.createVolumeChart(e),t.intervalId=setInterval(()=>{if(!this.state.volumeDetectionEnabled||e.paused)return;const n=this.getVolumeLevel(t),o=n<this.state.currentVolumeThreshold;this.updateVolumeChart(n),o&&!this.state.isVolumeBoosted?(this.state.isVolumeBoosted=true,this.applySpeed(this.calculateFinalSpeed())):!o&&this.state.isVolumeBoosted&&(this.state.isVolumeBoosted=false,this.applySpeed(this.calculateFinalSpeed()));},Ie));}stopVolumeDetection(e){const t=this.state.mediaAnalyzers.get(e);t&&(t.intervalId&&(clearInterval(t.intervalId),t.intervalId=null),t.context&&t.context.close(),this.state.mediaAnalyzers.delete(e),this.state.volumeChart&&(this.state.volumeChart.remove(),this.state.volumeChart=null),this.state.volumeHistory=[]);}toggleVolumeDetection(){if(this.state.volumeDetectionEnabled=!this.state.volumeDetectionEnabled,this.state.volumeDetectionEnabled){this.getMediaElements().forEach(e=>{this.startVolumeDetection(e);});}else {this.getMediaElements().forEach(e=>{this.stopVolumeDetection(e);}),this.state.isVolumeBoosted&&(this.state.isVolumeBoosted=false,this.applySpeed(this.calculateFinalSpeed()));}}adjustVolumeThreshold(e){this.state.currentVolumeThreshold+=e,this.state.currentVolumeThreshold=Math.max(-100,Math.min(0,this.state.currentVolumeThreshold)),this.state.volumeChart&&(this.state.volumeChart.style.opacity="1",this.hideChartTimer&&clearTimeout(this.hideChartTimer),this.hideChartTimer=setTimeout(()=>{this.state.volumeChart&&(this.state.volumeChart.style.opacity="0");},5e3));}createVolumeChart(e){this.state.volumeChart&&this.state.volumeChart.remove();const t=document.createElement("canvas");if(t.className="volume-chart",t.width=300,t.height=150,t.style.cssText="\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      background: rgba(0, 0, 0, 0.85);\n      backdrop-filter: blur(12px);\n      border: 2px solid #feebea;\n      border-radius: 8px;\n      z-index: 999999;\n      pointer-events: none;\n      opacity: 1;\n      transition: opacity 0.3s;\n    ",e.parentElement){"static"===window.getComputedStyle(e.parentElement).position&&(e.parentElement.style.position="relative"),e.parentElement.appendChild(t);}return this.state.volumeChart=t,this.state.volumeHistory=[],this.hideChartTimer=setTimeout(()=>{t&&(t.style.opacity="0");},5e3),t}updateVolumeChart(e){if(!this.state.volumeChart)return;const t=this.state.volumeChart,n=t.getContext("2d"),o=t.width,i=t.height;this.state.volumeHistory.push(e),this.state.volumeHistory.length>this.state.maxHistoryLength&&this.state.volumeHistory.shift(),n.clearRect(0,0,o,i);const s=30,r=o-60,a=i-60,l=-60,c=e=>{const t=Math.max(l,Math.min(0,e));return i-s-(t-l)/60*a};n.strokeStyle="rgba(255, 255, 255, 0.3)",n.lineWidth=1,n.beginPath(),n.moveTo(s,s),n.lineTo(s,i-s),n.lineTo(o-s,i-s),n.stroke(),n.fillStyle="rgba(255, 255, 255, 0.6)",n.font="10px monospace",n.textAlign="right";for(let p=l;p<=0;p+=20){const e=c(p);n.fillText(`${p}dB`,25,e+3),n.strokeStyle="rgba(255, 255, 255, 0.1)",n.beginPath(),n.moveTo(s,e),n.lineTo(o-s,e),n.stroke();}n.strokeStyle="#FF5252",n.lineWidth=2,n.setLineDash([5,5]);const d=c(this.state.currentVolumeThreshold);if(n.beginPath(),n.moveTo(s,d),n.lineTo(o-s,d),n.stroke(),n.setLineDash([]),n.fillStyle="#FF5252",n.textAlign="left",n.fillText(`阈值: ${this.state.currentVolumeThreshold.toFixed(0)}dB`,o-s+5,d+3),this.state.volumeHistory.length>1){n.strokeStyle="#4CAF50",n.lineWidth=2,n.beginPath();const e=r/(this.state.maxHistoryLength-1);this.state.volumeHistory.forEach((t,o)=>{const i=s+o*e,r=c(t);0===o?n.moveTo(i,r):n.lineTo(i,r);}),n.stroke();const t=this.state.volumeHistory[this.state.volumeHistory.length-1],o=s+(this.state.volumeHistory.length-1)*e,i=c(t);n.fillStyle="#4CAF50",n.beginPath(),n.arc(o,i,3,0,2*Math.PI),n.fill(),n.fillText(`${t.toFixed(1)}dB`,o+5,i-5);}n.fillStyle="rgba(255, 255, 255, 0.8)",n.font="12px monospace",n.textAlign="center",n.fillText("响度检测",o/2,15);}bindKeyboardEvents(){document.addEventListener("keydown",e=>this.handleKeyDown(e),true),document.addEventListener("keyup",e=>this.handleKeyUp(e),true);}handleKeyDown(e){if("AltRight"!==e.code||2!==e.location){if("INPUT"!==e.target.tagName&&"TEXTAREA"!==e.target.tagName&&!e.target.isContentEditable)return "Period"===e.code?e.altKey?(e.preventDefault(),void this.adjustVolumeThreshold(ke)):!this.state.periodPressed&&(this.state.periodPressed=true,this.state.commaPressed)?(e.preventDefault(),void this.toggleVolumeDetection()):(e.preventDefault(),void(this.detectDoubleClick("period")?this.setToDoubleSpeed():this.adjustBaseSpeed(ye))):"Comma"===e.code?e.altKey?(e.preventDefault(),void this.adjustVolumeThreshold(-1)):!this.state.commaPressed&&(this.state.commaPressed=true,this.state.periodPressed)?(e.preventDefault(),void this.toggleVolumeDetection()):(e.preventDefault(),void(this.detectDoubleClick("comma")?this.resetToNormalSpeed():this.adjustBaseSpeed(-0.1))):void 0}else if(!this.state.isRightOptionPressed){this.state.isRightOptionPressed=true;const e=Date.now();e-this.state.lastOptionPressTime<ve?(this.applyPermanentBoost(),this.state.optionDoubleClickTimer&&(clearTimeout(this.state.optionDoubleClickTimer),this.state.optionDoubleClickTimer=null)):this.applyTemporaryBoost(),this.state.lastOptionPressTime=e;}}handleKeyUp(e){"AltRight"!==e.code||2!==e.location?"Period"!==e.code?"Comma"!==e.code||(this.state.commaPressed=false):this.state.periodPressed=false:this.state.isRightOptionPressed&&(this.state.isRightOptionPressed=false,this.state.optionDoubleClickTimer=setTimeout(()=>{!this.state.isRightOptionPressed&&this.state.isTempBoosted&&this.removeTemporaryBoost();},ve));}observeMediaElements(){this.observer=new MutationObserver(()=>{this.getMediaElements().forEach(e=>{const t=this.calculateFinalSpeed();Math.abs(e.playbackRate-t)>.01&&(e.playbackRate=t),this.state.volumeDetectionEnabled&&!this.state.mediaAnalyzers.has(e)&&this.startVolumeDetection(e);});}),this.observer.observe(document.body,{childList:true,subtree:true});}applySpeedToExistingMedia(){this.getMediaElements().forEach(e=>{e.playbackRate=this.state.baseSpeed;});}getCurrentSpeed(){return this.calculateFinalSpeed()}getState(){return {baseSpeed:this.state.baseSpeed,finalSpeed:this.calculateFinalSpeed(),isTempBoosted:this.state.isTempBoosted,isVolumeBoosted:this.state.isVolumeBoosted,volumeDetectionEnabled:this.state.volumeDetectionEnabled,currentVolumeThreshold:this.state.currentVolumeThreshold}}destroy(){console.log("[SpeedControl] 开始清理资源"),this.observer&&(this.observer.disconnect(),this.observer=null),this.getMediaElements().forEach(e=>{this.stopVolumeDetection(e);}),this.state.volumeChart&&(this.state.volumeChart.remove(),this.state.volumeChart=null),this.state.mediaAnalyzers.forEach((e,t)=>{e.intervalId&&clearInterval(e.intervalId),e.context&&"closed"!==e.context.state&&e.context.close().catch(e=>{console.error("[SpeedControl] 关闭 AudioContext 失败:",e);});}),this.state.mediaAnalyzers.clear(),this.hideChartTimer&&(clearTimeout(this.hideChartTimer),this.hideChartTimer=null),this.state.volumeHistory=[],this.state.volumeDetectionEnabled=false,this.state.isVolumeBoosted=false,console.log("[SpeedControl] 资源清理完成");}},Ce="sponsorblock_settings";const Ae=new class{constructor(){this.settings=this.loadSettings();}loadSettings(){const e=GM_getValue(Ce,null);return e?JSON.parse(e):{...W.DEFAULT_SETTINGS}}saveSettings(e){this.settings=e,GM_setValue(Ce,JSON.stringify(e));}get(e){return this.settings[e]}set(e,t){this.settings[e]=t,this.saveSettings(this.settings);}getAll(){return {...this.settings}}setAll(e){this.saveSettings(e);}resetToDefaults(){this.saveSettings({...W.DEFAULT_SETTINGS});}};class Te{constructor(){this.cache=new Map,this.pendingRequests=new Map;}async fetchSegments(e){const t=this.cache.get(e);if(t&&Date.now()-t.timestamp<W.CACHE_EXPIRY)return t.data;if(this.pendingRequests.has(e))return this.pendingRequests.get(e);const n=new Promise((t,n)=>{GM_xmlhttpRequest({method:"GET",url:`${W.API_URL}?videoID=${e}`,headers:{origin:"userscript-bilibili-sponsor-skip","x-ext-version":"1.0.0"},timeout:5e3,onload:o=>{try{if(404===o.status){const n=[];this.cache.set(e,{data:n,timestamp:Date.now()}),t(n);}else if(200===o.status){const n=JSON.parse(o.responseText);this.cache.set(e,{data:n,timestamp:Date.now()}),t(n);}else 400===o.status?(console.error("[SponsorBlock] 参数错误 (400)"),n(new Error("Bad request"))):429===o.status?(console.error("[SponsorBlock] 请求频繁 (429)"),n(new Error("Rate limited"))):n(new Error(`HTTP ${o.status}`));}catch(i){n(i);}},onerror:n,ontimeout:()=>n(new Error("Timeout"))});});return this.pendingRequests.set(e,n),n.finally(()=>{this.pendingRequests.delete(e);}),n}hasSegments(e){const t=this.cache.get(e);return t&&Date.now()-t.timestamp<W.CACHE_EXPIRY?t.data.length>0:null}clearCache(){this.cache.clear();}}class Le{constructor(e,t){this.api=e,this.config=t,this.video=null,this.segments=[],this.currentBVID=null,this.lastSkipTime=0,this.checkInterval=null,this.rafId=null,this.currentPrompt=null,this.promptedSegments=new Set,this.ignoredSegments=new Set,this.progressBar=null,this.markerContainer=null,this.playerObserver=null;}async init(){if(location.pathname.includes("/video/")&&(this.currentBVID=location.pathname.match(/video\/(BV\w+)/)?.[1],this.currentBVID)){await this.waitForVideo();try{this.segments=await this.api.fetchSegments(this.currentBVID),this.segments.length>0&&this.renderProgressMarkers();}catch(e){console.error("[SponsorBlock] 获取片段失败:",e),this.segments=[];}this.startMonitoring(),this.setupPlayerObserver();}}setupPlayerObserver(){const e=document.querySelector(".bpx-player-video-wrap")||document.querySelector(".bpx-player-container");e&&(this.playerObserver=new MutationObserver(()=>{this.segments.length>0&&!document.querySelector("#sponsorblock-preview-bar")&&this.renderProgressMarkers();}),this.playerObserver.observe(e,{childList:true,subtree:true}));}async waitForVideo(){return new Promise(e=>{const t=()=>{this.video=document.querySelector(G),this.video?e():setTimeout(t,500);};t();})}renderProgressMarkers(){if(!this.config.get("showProgressMarkers"))return;const e=(t=0)=>{const n=document.querySelector(".bpx-player-progress-schedule");n?(this.progressBar=n,document.querySelectorAll("#sponsorblock-preview-bar").forEach(e=>e.remove()),this.markerContainer=document.createElement("ul"),this.markerContainer.id="sponsorblock-preview-bar",n.prepend(this.markerContainer),this.video.duration&&this.video.duration>0?this.createSegmentMarkers():this.video.addEventListener("loadedmetadata",()=>{this.createSegmentMarkers();},{once:true})):t<10&&setTimeout(()=>e(t+1),1e3);};e();}createSegmentMarkers(){if(!this.markerContainer||!this.video.duration||this.video.duration<=0)return;this.markerContainer.innerHTML="";const e=this.video.duration;[...this.segments].sort((e,t)=>t.segment[1]-t.segment[0]-(e.segment[1]-e.segment[0])).forEach((t,n)=>{const o=Math.min(e,t.segment[0]),i=Math.min(e,t.segment[1]),s=o/e*100,r=100*(1-i/e),a=document.createElement("li");a.className="sponsorblock-segment",a.dataset.segmentIndex=n.toString();const l=W.CATEGORIES[t.category]||{name:t.category,color:"#999"};a.style.position="absolute",a.style.left=`${s}%`,a.style.right=`${r}%`,a.style.backgroundColor=l.color;const c=i-o;a.title=`${l.name}\n${o.toFixed(1)}s - ${i.toFixed(1)}s (${c.toFixed(1)}s)`,a.addEventListener("click",e=>{e.stopPropagation(),this.showSegmentDetails(t);}),this.markerContainer.appendChild(a);});}showSegmentDetails(e){const t=document.querySelector(".segment-details-popup");t&&(t.remove(),document.querySelector(".segment-details-overlay")?.remove());const n=W.CATEGORIES[e.category]||{name:e.category,color:"#999"},o=e.segment[1]-e.segment[0],i=le(e.segment[0]),s=le(e.segment[1]),r=document.createElement("div");r.className="segment-details-overlay",r.onclick=()=>this.closeSegmentDetails();const a=document.createElement("div");a.className="segment-details-popup",a.onclick=e=>e.stopPropagation(),a.innerHTML=`\n      <div class="segment-details-header">\n        <div class="segment-details-title">\n          <div style="width: 16px; height: 16px; background: ${n.color}; border-radius: 3px;"></div>\n          <span>${n.name}</span>\n        </div>\n        <button class="segment-details-close">×</button>\n      </div>\n      <div class="segment-details-content">\n        <div class="segment-details-row">\n          <span class="segment-details-label">开始时间</span>\n          <span class="segment-details-value">${i}</span>\n        </div>\n        <div class="segment-details-row">\n          <span class="segment-details-label">结束时间</span>\n          <span class="segment-details-value">${s}</span>\n        </div>\n        <div class="segment-details-row">\n          <span class="segment-details-label">时长</span>\n          <span class="segment-details-value">${o.toFixed(1)} 秒</span>\n        </div>\n        <div class="segment-details-row">\n          <span class="segment-details-label">投票数</span>\n          <span class="segment-details-value">${e.votes}</span>\n        </div>\n        <div class="segment-details-row">\n          <span class="segment-details-label">UUID</span>\n          <span class="segment-details-value" style="font-size: 11px; font-family: monospace;">${e.UUID.substring(0,20)}...</span>\n        </div>\n      </div>\n      <div class="segment-details-actions">\n        <button class="segment-details-btn segment-details-btn-secondary" data-action="close">\n          关闭\n        </button>\n        <button class="segment-details-btn segment-details-btn-primary" data-action="jump">\n          跳转到此片段\n        </button>\n      </div>\n    `,document.body.appendChild(r),document.body.appendChild(a),a.querySelector(".segment-details-close").onclick=()=>this.closeSegmentDetails(),a.querySelector('[data-action="close"]').onclick=()=>this.closeSegmentDetails(),a.querySelector('[data-action="jump"]').onclick=()=>{this.video&&(this.video.currentTime=e.segment[0]),this.closeSegmentDetails();};const l=e=>{"Escape"===e.key&&(this.closeSegmentDetails(),document.removeEventListener("keydown",l));};document.addEventListener("keydown",l);}closeSegmentDetails(){document.querySelector(".segment-details-popup")?.remove(),document.querySelector(".segment-details-overlay")?.remove();}startMonitoring(){if(!this.video)return;const e=()=>{this.checkAndSkip(),this.rafId=requestAnimationFrame(e);};this.rafId=requestAnimationFrame(e),console.log("[SponsorBlock] 开始监控(使用RAF)");}checkAndSkip(){if(!this.video||this.video.paused)return;const e=this.video.currentTime,t=this.config.get("skipCategories")||[];for(const n of this.segments)if(e>=n.segment[0]&&e<n.segment[1]){const e=`${n.UUID}`;if(this.ignoredSegments.has(e))continue;if(t.includes(n.category)){if(Date.now()-this.lastSkipTime<1e3)continue;const e=n.segment[1];this.video.currentTime=e,this.lastSkipTime=Date.now(),this.showSkipToast(n);break}this.promptedSegments.has(e)||(this.showSkipPrompt(n),this.promptedSegments.add(e));continue}}showSkipToast(e){const t=W.CATEGORIES[e.category]||{name:e.category},n=document.createElement("div");n.className="skip-toast",n.textContent=`已跳过 ${t.name}`,n.addEventListener("click",e=>{e.stopPropagation(),e.preventDefault();}),n.addEventListener("mousedown",e=>{e.stopPropagation(),e.preventDefault();});(document.querySelector(".bpx-player-video-wrap")||document.querySelector(".bpx-player-container")||document.body).appendChild(n),setTimeout(()=>{n.classList.add("hiding"),setTimeout(()=>{n.remove();},300);},3e3);}showSkipPrompt(e){this.closePrompt();const t=W.CATEGORIES[e.category]||{name:e.category,color:"#999"},n=document.createElement("div");n.className="skip-prompt";const o=e.segment[1]-e.segment[0],i=le(e.segment[0]),s=le(e.segment[1]);n.innerHTML=`\n      <div class="skip-prompt-header">\n        <div class="skip-prompt-icon">\n          <svg viewBox="0 0 24 24" fill="${t.color}">\n            <path d="M8 5v14l11-7z"/>\n          </svg>\n        </div>\n        <div class="skip-prompt-message">\n          跳过${t.name}?<br>\n          <small style="color: #999; font-size: 11px;">${i} - ${s}</small>\n        </div>\n        <button class="skip-prompt-close" title="关闭">×</button>\n      </div>\n      <div class="skip-prompt-buttons">\n        <button class="skip-prompt-btn skip-prompt-btn-secondary" data-action="ignore">\n          不跳过\n        </button>\n        <button class="skip-prompt-btn skip-prompt-btn-primary" data-action="skip">\n          跳过 (${o.toFixed(0)}秒)\n        </button>\n      </div>\n    `,n.addEventListener("click",e=>{e.stopPropagation();}),n.addEventListener("mousedown",e=>{e.stopPropagation();});(document.querySelector(".bpx-player-video-wrap")||document.querySelector(".bpx-player-container")||document.body).appendChild(n),this.currentPrompt=n;const r=n.querySelector('[data-action="skip"]'),a=n.querySelector('[data-action="ignore"]'),l=n.querySelector(".skip-prompt-close"),c=()=>{this.video.currentTime=e.segment[1],this.lastSkipTime=Date.now(),this.closePrompt();},d=()=>{this.closePrompt();};r.onclick=c,a.onclick=()=>{const t=`${e.UUID}`;this.ignoredSegments.add(t),this.closePrompt();},l.onclick=d;const p=e=>{"Enter"===e.key?(c(),document.removeEventListener("keydown",p)):"Escape"===e.key&&(d(),document.removeEventListener("keydown",p));};document.addEventListener("keydown",p);const u=setInterval(()=>{this.video&&this.video.currentTime>=e.segment[1]&&(this.closePrompt(),clearInterval(u));},500),h=setTimeout(()=>{this.currentPrompt===n&&this.closePrompt();},5e3);n._cleanup=()=>{clearInterval(u),clearTimeout(h),document.removeEventListener("keydown",p);};}closePrompt(){this.currentPrompt&&(this.currentPrompt._cleanup&&this.currentPrompt._cleanup(),this.currentPrompt.classList.add("hiding"),setTimeout(()=>{this.currentPrompt&&(this.currentPrompt.remove(),this.currentPrompt=null);},300));}destroy(){console.log("[SponsorBlock] 销毁播放器控制器"),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.checkInterval&&(clearInterval(this.checkInterval),this.checkInterval=null),this.closePrompt(),this.closeSegmentDetails(),this.markerContainer&&(this.markerContainer.remove(),this.markerContainer=null),this.playerObserver&&(this.playerObserver.disconnect(),this.playerObserver=null),this.promptedSegments.clear(),this.ignoredSegments.clear(),this.segments=[],this.video=null;}}const Me=new class{constructor(){this.api=new Te,this.playerController=null,this.currentURL=location.href;}async init(){location.pathname.includes("/video/")&&(this.playerController=new Le(this.api,Ae),await this.playerController.init()),this.setupURLMonitor();}setupURLMonitor(){window.addEventListener("popstate",()=>{this.handleURLChange();});const e=history.pushState,t=history.replaceState;history.pushState=(...t)=>{e.apply(history,t),this.handleURLChange();},history.replaceState=(...e)=>{t.apply(history,e),this.handleURLChange();};}handleURLChange(){const e=location.href;e!==this.currentURL&&(this.currentURL=e,this.playerController?.destroy(),this.playerController=null,location.pathname.includes("/video/")&&setTimeout(async()=>{this.playerController=new Le(this.api,Ae),await this.playerController.init();},1e3));}getAPI(){return this.api}};class Be{constructor(e){this.sponsorAPI=e,this.observer=null,this.statsCache=new Map,this.pendingRequests=new Map,this.abortController=new AbortController,this.processQueue=new Set,this.isProcessing=false;}start(){setTimeout(()=>{this.initScrollHandler(),this.initObserver(),this.checkNewCards();},800);}initScrollHandler(){let e;window.addEventListener("scroll",()=>{clearTimeout(e),e=setTimeout(()=>this.checkNewCards(),200);},{signal:this.abortController.signal});}checkNewCards(){if("hidden"===document.visibilityState)return;document.querySelectorAll("\n      .bili-video-card:not([data-quality-checked]),\n      .video-page-card-small:not([data-quality-checked]),\n      .video-page-card:not([data-quality-checked]),\n      .up-main-video-card:not([data-quality-checked]),\n      .small-item:not([data-quality-checked])\n    ").forEach(e=>{e.dataset.qualityChecked||this.processQueue.add(e);}),this.processNextBatch();}async processNextBatch(){if(this.isProcessing||0===this.processQueue.size)return;this.isProcessing=true;const e=Array.from(this.processQueue).slice(0,5);try{await Promise.all(e.map(e=>this.processCard(e)));}catch(t){}e.forEach(e=>this.processQueue.delete(e)),this.isProcessing=false,this.processQueue.size>0&&setTimeout(()=>this.processNextBatch(),100);}async processCard(e){if("true"===e.dataset.qualityChecked)return;if(!document.body.contains(e))return;e.dataset.qualityChecked="processing";const t=e.querySelector('a[href*="/video/BV"]');if(!t)return void(e.dataset.qualityChecked="true");const n=this.extractBVID(t.href);if(!n)return void(e.dataset.qualityChecked="true");const o=this.findBadgeContainer(e);if(o)try{const[t,i]=await Promise.all([this.fetchVideoStats(n).catch(()=>null),this.sponsorAPI.fetchSegments(n).catch(()=>[])]);if(!document.body.contains(e))return;const s=o.querySelector(".bili-tags-container");s&&s.remove();const r=document.createElement("div");r.className="bili-tags-container";const a=[];if(Ae.get("showQualityBadge")&&t&&this.isHighQuality(t)&&a.push(this.createQualityBadge(t)),Ae.get("showAdBadge")&&i&&i.length>0){const e=this.createSegmentBadges(i);a.push(...e);}const l=a.length>=3;a.forEach(e=>{l&&e.dataset.emoji&&e.dataset.text&&(e.textContent=e.dataset.emoji,e.classList.add("emoji-only")),r.appendChild(e);}),r.children.length>0&&(o.firstChild?o.insertBefore(r,o.firstChild):o.appendChild(r));}catch(i){}finally{document.body.contains(e)&&(e.dataset.qualityChecked="true");}else e.dataset.qualityChecked="true";}findBadgeContainer(e){return e.classList.contains("up-main-video-card")||e.classList.contains("small-item")?e.querySelector(".cover-container, .cover, .pic-box")||e:e.classList.contains("video-page-card-small")?e.querySelector(".pic-box"):e.classList.contains("video-page-card")?e.querySelector(".pic"):e.querySelector(".bili-video-card__cover, .cover, .pic, .bili-video-card__info")||e.closest(".bili-video-card")?.querySelector(".bili-video-card__cover")}isHighQuality(e){return e?.view>=W.MIN_VIEWS&&e.like/e.view>=W.MIN_SCORE}isTopQuality(e){return e?.coin>=e?.like}createQualityBadge(e){const t=document.createElement("span");return t.className="bili-quality-tag",this.isTopQuality(e)?(t.style.background=W.TOP_TAG_COLOR,t.textContent=W.TOP_TAG_TEXT,t.dataset.emoji="🏆",t.dataset.text="顶级",t.title="顶级优质视频"):(t.style.background=W.TAG_COLOR,t.textContent=W.TAG_TEXT,t.dataset.emoji="🔥",t.dataset.text="精选",t.title="精选优质视频"),t}createSegmentBadges(e){const t={};e.forEach(e=>{t[e.category]=(t[e.category]||0)+1;});const n=[],o={sponsor:{icon:"⚠️",text:"广告",color:"linear-gradient(135deg, #FF8C00, #FF6347)"},selfpromo:{icon:"📢",text:"推广",color:"linear-gradient(135deg, #FFD700, #FFA500)"},interaction:{icon:"👆",text:"三连",color:"linear-gradient(135deg, #9C27B0, #E91E63)"},poi_highlight:{icon:"⭐",text:"高光",color:"linear-gradient(135deg, #FF1493, #FF69B4)"},intro:{icon:"▶️",text:"开场",color:"linear-gradient(135deg, #00CED1, #00BFFF)"},outro:{icon:"🎬",text:"结尾",color:"linear-gradient(135deg, #1E90FF, #4169E1)"},preview:{icon:"🔄",text:"回顾",color:"linear-gradient(135deg, #00A1D6, #0087B3)"},filler:{icon:"💬",text:"闲聊",color:"linear-gradient(135deg, #9370DB, #8A2BE2)"},music_offtopic:{icon:"🎵",text:"非音乐",color:"linear-gradient(135deg, #FF8C00, #FF7F50)"},exclusive_access:{icon:"🤝",text:"合作",color:"linear-gradient(135deg, #2E8B57, #3CB371)"},mute:{icon:"🔇",text:"静音",color:"linear-gradient(135deg, #DC143C, #C71585)"}};return Object.entries(t).forEach(([e,t])=>{const i=o[e]||{icon:"📍",text:e,color:"linear-gradient(135deg, #888, #666)"},s=document.createElement("span");s.className="bili-ad-tag",s.style.background=i.color,s.dataset.emoji=t>1?`${i.icon}×${t}`:i.icon,s.dataset.text=i.text,s.textContent=`${i.icon} ${i.text}`,t>1&&(s.textContent+=` (${t})`),s.title=`包含 ${t} 个${i.text}片段`,n.push(s);}),n}extractBVID(e){try{return new URL(e).pathname.match(/video\/(BV\w+)/)?.[1]}catch{return null}}async fetchVideoStats(e){if(this.statsCache.has(e))return this.statsCache.get(e);if(this.pendingRequests.has(e))return this.pendingRequests.get(e);const t=new Promise((t,n)=>{GM_xmlhttpRequest({method:"GET",url:`https://api.bilibili.com/x/web-interface/view?bvid=${e}`,timeout:5e3,onload:o=>{try{const i=JSON.parse(o.responseText);0===i?.code&&i?.data?.stat?(this.statsCache.set(e,i.data.stat),t(i.data.stat)):n(new Error("Invalid API response"));}catch(i){n(i);}},onerror:n,ontimeout:()=>n(new Error("Timeout"))});});return this.pendingRequests.set(e,t),t.finally(()=>{this.pendingRequests.delete(e);})}initObserver(){this.observer=new MutationObserver(e=>{let t=false;for(const n of e)if(n.addedNodes.length>0){t=true;break}t&&this.checkNewCards();}),this.observer.observe(document.body,{childList:true,subtree:true});}destroy(){this.observer?.disconnect(),this.abortController.abort(),this.processQueue.clear(),this.pendingRequests.clear(),this.statsCache.clear();}}let De=null;const Pe=new class{constructor(){this.toastElement=null,this.init();}init(){this.toastElement=document.createElement("div"),this.toastElement.className="notion-toast";}showToast(e,t=c){this.toastElement.textContent=e,document.body.appendChild(this.toastElement),setTimeout(()=>this.toastElement.classList.add("show"),10),setTimeout(()=>{this.toastElement.classList.remove("show"),setTimeout(()=>{this.toastElement.parentNode&&document.body.removeChild(this.toastElement);},300);},t);}success(e){this.showToast(e);}warning(e){this.showToast(e);}error(e,t=false){this.showToast(e,3e3),t&&alert(e);}info(e){this.showToast(e);}handleError(e,t="",n=false,o=false){const i=e instanceof Error?e.message:String(e);console.error(`[Error] ${t}:`,e),n||this.error(i,o);}confirm(e){return window.confirm(e)}};const Ne=new class{renderSubtitlePanel(e){const t=re.getVideoKey();t&&re.getAISummary(t);let n=`\n      <div class="subtitle-header">\n        <div class="subtitle-search-container">\n          <input type="text" class="search-input" placeholder="搜索..." id="subtitle-search-input">\n          <div class="search-nav" id="search-nav" style="display: none;">\n            <span class="search-counter" id="search-counter">0/0</span>\n            <button class="search-nav-btn search-prev" id="search-prev" title="上一个">↑</button>\n            <button class="search-nav-btn search-next" id="search-next" title="下一个">↓</button>\n          </div>\n        </div>\n        <div class="subtitle-header-actions">\n          <span class="ai-icon ${re.ai.isSummarizing?"loading":""}" title="AI 总结">\n            ${Z}\n          </span>\n          <span class="download-icon" title="下载字幕">\n            ${ee}\n          </span>\n          <span class="notion-icon ${re.notion.isSending?"loading":""}" title="发送到 Notion">\n            ${te}\n          </span>\n          <span class="subtitle-close">×</span>\n        </div>\n      </div>\n      <div class="subtitle-content">\n        <button class="subtitle-toggle-btn" id="subtitle-toggle-btn" title="展开/收起字幕列表 (${e.length}条)">\n          <span class="subtitle-toggle-icon">►</span>\n        </button>\n        <div class="subtitle-list-container" id="subtitle-list-container">\n    `;return e.forEach((e,t)=>{const o=le(e.from);n+=`\n        <div class="subtitle-item" data-index="${t}" data-from="${e.from}" data-to="${e.to}">\n          <div class="subtitle-item-header">\n            <div class="subtitle-time">${o}</div>\n            <button class="save-subtitle-note-btn" data-content="${this.escapeHtml(e.content)}" title="保存为笔记">保存</button>\n          </div>\n          <div class="subtitle-text">${e.content}</div>\n        </div>\n      `;}),n+="\n        </div>\n      </div>\n    ",n}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}renderAISummarySection(e=null,t=false){const n=document.createElement("div");if(n.className="ai-summary-section",t)n.innerHTML='\n        <div class="ai-summary-title">\n          <span>✨ AI 视频总结</span>\n        </div>\n        <div class="ai-summary-content ai-summary-loading">正在生成总结...</div>\n      ';else if(e){const t="undefined"!=typeof marked&&marked.parse?marked.parse(e):e.replace(/\n/g,"<br>");n.innerHTML=`\n        <div class="ai-summary-title">\n          <span>✨ AI 视频总结</span>\n        </div>\n        <div class="ai-summary-content">${t}</div>\n      `;}return n}updateAISummary(e,t){const n=e.querySelector(".subtitle-content");if(!n)return;let o=n.querySelector(".ai-summary-section");if(o){const e=o.querySelector(".ai-summary-content");if(e){e.classList.remove("ai-summary-loading");const n="undefined"!=typeof marked&&marked.parse?marked.parse(t):t.replace(/\n/g,"<br>");e.innerHTML=n;}}else o=this.renderAISummarySection(t),n.insertBefore(o,n.firstChild);}createNotionConfigModal(){const e=document.createElement("div");return e.id="notion-config-modal",e.className="config-modal",e.innerHTML='\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>Notion 集成配置</span>\n        </div>\n        <div class="config-modal-body">\n          <div class="config-field">\n            <label>1️⃣ Notion API Key</label>\n            <input type="password" id="notion-api-key" placeholder="输入你的 Integration Token">\n            <div class="config-help">\n              访问 <a href="https://www.notion.so/my-integrations" target="_blank">Notion Integrations</a> 创建 Integration 并复制 Token\n            </div>\n          </div>\n          <div class="config-field">\n            <label>2️⃣ 目标位置(二选一)</label>\n            <input type="text" id="notion-parent-page-id" placeholder="Page ID 或 Database ID">\n            <div class="config-help">\n              <strong>方式A - 使用已有数据库:</strong><br>\n              从数据库 URL 中获取:<code>notion.so/<strong>abc123...</strong>?v=...</code><br>\n              脚本会直接向该数据库添加记录\n            </div>\n            <div class="config-help" style="margin-top: 8px;">\n              <strong>方式B - 自动创建数据库:</strong><br>\n              从页面 URL 中获取:<code>notion.so/My-Page-<strong>abc123...</strong></code><br>\n              首次使用会在此页面下创建数据库\n            </div>\n            <div class="config-help" style="margin-top: 8px; color: #f59e0b;">\n              ⚠️ 重要:需要在「Share」中邀请你的 Integration\n            </div>\n          </div>\n        <div class="config-field">\n          <label>\n            <input type="checkbox" id="notion-auto-send-enabled">\n            自动发送(获取字幕后自动发送到Notion)\n          </label>\n        </div>\n          <div id="notion-status-message"></div>\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-secondary" id="notion-cancel-btn">取消</button>\n          <button class="config-btn config-btn-primary" id="notion-save-btn">保存配置</button>\n        </div>\n      </div>\n    ',e}createAIConfigModal(){const e=document.createElement("div");return e.id="ai-config-modal",e.className="config-modal",e.innerHTML='\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>AI 配置管理</span>\n        </div>\n        <div class="config-modal-body">\n          <div style="margin-bottom: 20px; padding: 15px; background: linear-gradient(135deg, rgba(255, 107, 107, 0.15), rgba(255, 77, 77, 0.15)); border-radius: 10px; border-left: 4px solid #ff6b6b;">\n            <div style="font-size: 14px; color: #fff; font-weight: 600; margin-bottom: 8px;">⚠️ 首次使用必读</div>\n            <div style="font-size: 12px; color: rgba(255, 255, 255, 0.9); line-height: 1.6; margin-bottom: 8px;">\n              • 使用AI总结功能前,需要先配置API Key<br>\n              • 选择一个AI服务商,点击查看其配置,填写API Key后保存<br>\n              • 推荐使用 <strong>OpenRouter</strong>、<strong>DeepSeek</strong> 或 <strong>硅基流动</strong>(提供免费额度)\n            </div>\n            <div style="font-size: 11px; color: rgba(255, 255, 255, 0.6); margin-top: 8px;">\n              💡 提示:点击配置卡片可查看详情和获取API Key的教程链接\n            </div>\n          </div>\n          <div class="ai-config-list" id="ai-config-list"></div>\n          <div style="margin-bottom: 15px; text-align: center;">\n            <button class="config-btn config-btn-secondary" id="ai-new-config-btn" style="padding: 8px 16px; font-size: 13px;">新建配置</button>\n          </div>\n          <div class="ai-config-form hidden">\n          <div class="config-field">\n            <label>配置名称</label>\n            <input type="text" id="ai-config-name" placeholder="例如:OpenAI GPT-4">\n          </div>\n          <div class="config-field">\n            <label>API URL</label>\n            <input type="text" id="ai-config-url" placeholder="https://api.openai.com/v1/chat/completions">\n          </div>\n          <div class="config-field">\n            <label>API Key <span id="api-key-help-link" style="font-size: 11px; margin-left: 8px;"></span></label>\n            <input type="password" id="ai-config-apikey" placeholder="sk-...">\n          </div>\n          <div class="config-field">\n            <label>模型</label>\n            <div class="model-field-with-button">\n              <input type="text" id="ai-config-model" placeholder="手动输入或点击获取模型">\n              <button class="fetch-models-btn" id="fetch-models-btn">获取模型</button>\n            </div>\n            <div class="model-select-wrapper" id="model-select-wrapper" style="display:none;">\n              <input type="text" id="model-search-input" class="model-search-input" placeholder="🔍 搜索模型...">\n              <select id="model-select" size="8"></select>\n            </div>\n          </div>\n          <div class="config-field">\n            <label>\n              <input type="checkbox" id="ai-config-is-openrouter">\n              使用OpenRouter (支持获取模型列表)\n            </label>\n          </div>\n          <div class="config-field">\n            <label>提示词 (Prompt)</label>\n            <textarea id="ai-config-prompt" placeholder="根据以下视频字幕,用中文总结视频内容:"></textarea>\n          </div>\n          <div class="config-field">\n            <label>\n              <input type="checkbox" id="ai-auto-summary-enabled">\n              自动总结(获取字幕后自动触发AI总结)\n            </label>\n          </div>\n          </div>\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-danger" id="ai-delete-current-btn" style="display:none;">删除此配置</button>\n          <div style="flex: 1;"></div>\n          <button class="config-btn config-btn-secondary" id="ai-cancel-btn">取消</button>\n          <button class="config-btn config-btn-primary" id="ai-save-new-btn">添加新配置</button>\n          <button class="config-btn config-btn-primary" id="ai-update-btn" style="display:none;">更新配置</button>\n        </div>\n      </div>\n    ',e}renderAIConfigList(e){const t=ae.getAIConfigs(),n=ae.getSelectedAIConfigId();e.innerHTML=t.map(e=>{const t=e.apiKey&&""!==e.apiKey.trim(),o=t?"✅":"⚠️",i=t?"已配置":"未配置",s=t?"#4ade80":"#fbbf24";return `\n        <div class="ai-config-item ${e.id===n?"selected":""}" data-id="${e.id}">\n          <div class="ai-config-item-name">\n            ${e.name}\n            <span style="font-size: 11px; color: ${s}; margin-left: 8px;" title="API Key ${i}">\n              ${o} ${i}\n            </span>\n          </div>\n          <div class="ai-config-item-actions">\n            <button class="ai-config-btn-small config-btn-primary ai-edit-btn" data-id="${e.id}">查看</button>\n          </div>\n        </div>\n      `}).join("");}showNotionStatus(e,t=false){const n=document.getElementById("notion-status-message");n&&(n.className="config-status "+(t?"error":"success"),n.textContent=e);}};const ze=new class{constructor(){this.panel=null,this.isPanelVisible=false;}createPanel(){return this.panel||(this.panel=document.createElement("div"),this.panel.id="notes-panel",this.panel.className="notes-panel",document.body.appendChild(this.panel)),this.panel}showPanel(){const e=this.createPanel();this.renderPanel(),e.classList.add("show"),this.isPanelVisible=true;}hidePanel(){this.panel&&this.panel.classList.remove("show"),this.isPanelVisible=false;}togglePanel(){this.isPanelVisible?this.hidePanel():this.showPanel();}renderPanel(){const e=this.createPanel(),t=fe.getGroupedNotes(),n=`\n      <div class="notes-panel-content">\n        <div class="notes-panel-header">\n          <h2>我的笔记</h2>\n          <button class="notes-panel-close">×</button>\n        </div>\n        <div class="notes-panel-body">\n          ${0===t.length?this.renderEmptyState():t.map(e=>this.renderGroup(e)).join("")}\n        </div>\n      </div>\n    `;e.innerHTML=n,this.bindPanelEvents();}renderEmptyState(){return '\n      <div class="notes-empty-state">\n        <div class="notes-empty-icon">📝</div>\n        <div>还没有保存任何笔记</div>\n        <div class="notes-empty-hint">选中文字后点击粉色点即可保存</div>\n      </div>\n    '}renderGroup(e){return `\n      <div class="note-group">\n        <div class="note-group-header">\n          <div class="note-group-title">\n            ${e.date} (${e.notes.length}条)\n          </div>\n          <div class="note-group-actions">\n            <button class="note-group-copy-btn" data-date="${e.date}">\n              批量复制\n            </button>\n            <button class="note-group-delete-btn" data-date="${e.date}">\n              批量删除\n            </button>\n          </div>\n        </div>\n        <div class="note-group-items">\n          ${e.notes.map(e=>this.renderNote(e)).join("")}\n        </div>\n      </div>\n    `}renderNote(e){const t=e.content.length>200?e.content.substring(0,200)+"...":e.content;return `\n      <div class="note-item" data-note-id="${e.id}">\n        <div class="note-content">${this.escapeHtml(t)}</div>\n        <div class="note-footer">\n          <div class="note-time">${fe.formatTime(e.timestamp)}</div>\n          <div class="note-actions">\n            <button class="note-copy-btn" data-note-id="${e.id}">复制</button>\n            <button class="note-delete-btn" data-note-id="${e.id}">删除</button>\n          </div>\n        </div>\n      </div>\n    `}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}async copyToClipboard(e){try{if(navigator.clipboard&&navigator.clipboard.writeText)await navigator.clipboard.writeText(e);else {const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t);}}catch(t){console.error("复制失败:",t);}}bindPanelEvents(){const e=this.panel.querySelector(".notes-panel-close");e&&e.addEventListener("click",()=>this.hidePanel()),this.panel.querySelectorAll(".note-copy-btn").forEach(e=>{e.addEventListener("click",async e=>{const t=e.target.getAttribute("data-note-id"),n=fe.getAllNotes().find(e=>e.id===t);if(n){await this.copyToClipboard(n.content);const t=e.target.textContent;e.target.textContent="✓",setTimeout(()=>{e.target.textContent=t;},1e3);}});}),this.panel.querySelectorAll(".note-delete-btn").forEach(e=>{e.addEventListener("click",e=>{const t=e.target.getAttribute("data-note-id");fe.deleteNote(t),this.renderPanel();});}),this.panel.querySelectorAll(".note-group-copy-btn").forEach(e=>{e.addEventListener("click",async e=>{const t=e.target.getAttribute("data-date"),n=fe.getGroupedNotes().find(e=>e.date===t);if(n){const t=n.notes.map(e=>e.content).join("\n\n");await this.copyToClipboard(t);const o=e.target.textContent;e.target.textContent="✓",setTimeout(()=>{e.target.textContent=o;},1e3);}});}),this.panel.querySelectorAll(".note-group-delete-btn").forEach(e=>{e.addEventListener("click",e=>{const t=e.target.getAttribute("data-date"),n=fe.getGroupedNotes().find(e=>e.date===t);if(n&&confirm(`确定要删除 ${t} 的 ${n.notes.length} 条笔记吗?`)){const e=n.notes.map(e=>e.id);fe.deleteNotes(e),this.renderPanel();}});});}addSaveButton(e){if(e.querySelector(".save-subtitle-note-btn"))return;const t=e.querySelector(".subtitle-text")?.textContent;if(!t)return;const n=document.createElement("button");n.className="save-subtitle-note-btn",n.textContent="保存",n.title="保存此字幕为笔记",n.addEventListener("click",e=>{e.stopPropagation(),fe.saveSubtitleNote(t),n.textContent="✓",setTimeout(()=>{n.textContent="保存";},1e3);});const o=e.querySelector(".subtitle-time");o&&o.appendChild(n);}addSaveButtonsToSubtitles(e){e.querySelectorAll(".subtitle-item").forEach(e=>this.addSaveButton(e));}};const _e=new class{constructor(){this.isDragging=false,this.dragStartX=0,this.dragStartY=0,this.translateX=0,this.translateY=0,this.isResizing=false,this.resizeStartX=0,this.resizeStartY=0,this.resizeStartWidth=0,this.resizeStartHeight=0,this.searchMatches=[],this.currentMatchIndex=-1,this.searchTerm="",this.subtitleDataCache=null,this.currentHighlightedIndex=-1,this.debouncedSearch=null,this.throttledHighlight=null;}bindSubtitlePanelEvents(e){const t=e.querySelector(".subtitle-close");t&&t.addEventListener("click",()=>{re.setPanelVisible(false),e.classList.remove("show");});const n=e.querySelector(".ai-icon");n&&n.addEventListener("click",async e=>{e.stopPropagation();const t=re.getSubtitleData();if(t)try{await he.summarize(t,!1);}catch(n){Pe.handleError(n,"AI总结");}});const o=e.querySelector(".download-icon");o&&o.addEventListener("click",e=>{e.stopPropagation();try{ue.downloadSubtitleFile(),Pe.success("字幕文件已下载");}catch(t){Pe.handleError(t,"下载字幕");}});const i=e.querySelector(".notion-icon");i&&i.addEventListener("click",async e=>{e.stopPropagation();const t=re.getSubtitleData();if(t)try{await ge.sendSubtitle(t,!1);}catch(n){Pe.handleError(n,"Notion发送");}});const s=e.querySelector("#subtitle-toggle-btn"),r=e.querySelector("#subtitle-list-container");s&&r&&s.addEventListener("click",()=>{const t=r.classList.contains("expanded");r.classList.toggle("expanded"),s.classList.toggle("expanded"),t||this.scrollToCurrentSubtitle(e);});const a=e.querySelector("#subtitle-search-input");a&&(this.debouncedSearch||(this.debouncedSearch=function(e,t){let n;return function(...o){clearTimeout(n),n=setTimeout(()=>{clearTimeout(n),e(...o);},t);}}((e,t)=>{this.handleSearch(e,t);},300)),a.addEventListener("input",t=>{this.debouncedSearch(e,t.target.value);}),a.addEventListener("keydown",t=>{"Enter"===t.key&&(t.preventDefault(),this.navigateSearch(e,1));}));const l=e.querySelector("#search-prev"),c=e.querySelector("#search-next");l&&l.addEventListener("click",()=>{this.navigateSearch(e,-1);}),c&&c.addEventListener("click",()=>{this.navigateSearch(e,1);}),e.addEventListener("click",t=>{const n=t.target.closest(".save-subtitle-note-btn");if(n){t.stopPropagation();const e=n.getAttribute("data-content");return void(e&&(fe.saveSubtitleNote(e),n.textContent="✓",setTimeout(()=>{n.textContent="保存";},1e3)))}const o=t.target.closest(".subtitle-item");if(o){const t=document.querySelector(G);if(t){const n=parseFloat(o.dataset.from);e.querySelectorAll(".subtitle-item").forEach(e=>{e.classList.remove("current");}),o.classList.add("current"),t.currentTime=n;}}}),this.syncSubtitleHighlight(e);}setupDragging(e){const t=e.querySelector(".subtitle-header");t&&(t.addEventListener("mousedown",t=>{t.target.closest(".subtitle-close")||t.target.closest(".ai-icon")||t.target.closest(".download-icon")||t.target.closest(".notion-icon")||t.target.closest(".subtitle-search-container")||(this.isDragging=true,this.dragStartX=t.clientX,this.dragStartY=t.clientY,e.style.willChange="transform",t.preventDefault());}),document.addEventListener("mousemove",t=>{this.isDragging&&requestAnimationFrame(()=>{const n=t.clientX-this.dragStartX,o=t.clientY-this.dragStartY;this.translateX+=n,this.translateY+=o,this.dragStartX=t.clientX,this.dragStartY=t.clientY,e.style.transform=`translate(${this.translateX}px, ${this.translateY}px)`;});}),document.addEventListener("mouseup",()=>{this.isDragging&&(this.isDragging=false,e.style.willChange="auto",this.savePanelPosition(e));}));}setupResize(e){const t=e.querySelector(".subtitle-resize-handle");t&&(t.addEventListener("mousedown",t=>{this.isResizing=true,this.resizeStartX=t.clientX,this.resizeStartY=t.clientY,this.resizeStartWidth=e.offsetWidth,this.resizeStartHeight=e.offsetHeight,t.preventDefault(),t.stopPropagation();}),document.addEventListener("mousemove",t=>{this.isResizing&&requestAnimationFrame(()=>{const n=t.clientX-this.resizeStartX,o=t.clientY-this.resizeStartY,i=this.resizeStartWidth+n,s=this.resizeStartHeight+o,r=Math.max(300,Math.min(800,i)),a=.9*window.innerHeight,l=Math.max(400,Math.min(a,s));e.style.width=`${r}px`,e.style.maxHeight=`${l}px`;});}),document.addEventListener("mouseup",()=>{this.isResizing&&(this.isResizing=false,this.savePanelDimensions(e));}));}savePanelPosition(e){try{localStorage.setItem("subtitle_panel_position",JSON.stringify({translateX:this.translateX,translateY:this.translateY}));}catch(t){console.error("保存面板位置失败:",t);}}savePanelDimensions(e){try{localStorage.setItem("subtitle_panel_dimensions",JSON.stringify({width:e.offsetWidth,height:e.offsetHeight}));}catch(t){console.error("保存面板尺寸失败:",t);}}loadPanelDimensions(e){try{const t=localStorage.getItem("subtitle_panel_dimensions");if(t){const{width:n,height:o}=JSON.parse(t);e.style.width=`${n}px`,e.style.maxHeight=`${o}px`;}const n=localStorage.getItem("subtitle_panel_position");if(n){const{translateX:t,translateY:o}=JSON.parse(n);this.translateX=t,this.translateY=o,e.style.transform=`translate(${t}px, ${o}px)`;}}catch(t){console.error("加载面板设置失败:",t);}}syncSubtitleHighlight(e){const t=document.querySelector(G);if(!t)return;const n=Array.from(e.querySelectorAll(".subtitle-item"));this.subtitleDataCache=n.map(e=>({element:e,from:parseFloat(e.dataset.from),to:parseFloat(e.dataset.to)})),this.throttledHighlight||(this.throttledHighlight=function(e){let t=null;return function(...n){null===t&&(t=requestAnimationFrame(()=>{e.apply(this,n),t=null;}));}}(e=>{this.updateSubtitleHighlight(e);})),t.addEventListener("timeupdate",()=>{this.throttledHighlight(t.currentTime);});}updateSubtitleHighlight(e){if(!this.subtitleDataCache||0===this.subtitleDataCache.length)return;const t=function(e,t){if(!e||0===e.length)return  -1;if(e.length<50){for(let n=0;n<e.length;n++)if(t>=e[n].from&&t<=e[n].to)return n;return  -1}let n=0,o=e.length-1;for(;n<=o;){const i=Math.floor((n+o)/2),s=e[i];if(t>=s.from&&t<=s.to)return i;t<s.from?o=i-1:n=i+1;}return  -1}(this.subtitleDataCache,e);t!==this.currentHighlightedIndex&&(this.currentHighlightedIndex>=0&&this.currentHighlightedIndex<this.subtitleDataCache.length&&this.subtitleDataCache[this.currentHighlightedIndex].element.classList.remove("current"),t>=0&&this.subtitleDataCache[t].element.classList.add("current"),this.currentHighlightedIndex=t);}scrollToCurrentSubtitle(e){setTimeout(()=>{const t=document.querySelector(G);if(!t)return;const n=t.currentTime,o=e.querySelectorAll(".subtitle-item");for(const e of o){const t=parseFloat(e.dataset.from),o=parseFloat(e.dataset.to);if(n>=t&&n<=o){e.scrollIntoView({behavior:"smooth",block:"center"});break}}},100);}handleSearch(e,t){this.searchTerm=t.trim(),this.clearSearchHighlights(e),this.searchTerm?(this.searchMatches=[],this.highlightSearchInContainer(e),this.updateSearchCounter(this.searchMatches.length>0?1:0,this.searchMatches.length),this.searchMatches.length>0&&(this.currentMatchIndex=0,this.scrollToMatch(this.searchMatches[0]))):this.updateSearchCounter(0,0);}highlightSearchInContainer(e){const t=e.querySelector(".subtitle-content");if(!t)return;const n=t.querySelector(".ai-summary-section");if(n){const e=n.querySelector(".ai-summary-content");e&&this.highlightInElement(e,this.searchTerm);}t.querySelectorAll(".subtitle-item").forEach(e=>{const t=e.querySelector(".subtitle-text");t&&this.highlightInElement(t,this.searchTerm);});}highlightInElement(e,t){const n=e.textContent,o=new RegExp(`(${this.escapeRegex(t)})`,"gi");if(n.match(o)){let t=n.replace(o,e=>`<mark class="search-highlight" data-search-match>${e}</mark>`);e.innerHTML=t;e.querySelectorAll("mark[data-search-match]").forEach(e=>{this.searchMatches.push(e);});}}escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}clearSearchHighlights(e){e.querySelectorAll("mark[data-search-match]").forEach(e=>{const t=e.textContent,n=document.createTextNode(t);e.parentNode.replaceChild(n,e);}),this.searchMatches=[],this.currentMatchIndex=-1;}navigateSearch(e,t){if(0===this.searchMatches.length)return;this.currentMatchIndex>=0&&this.currentMatchIndex<this.searchMatches.length&&(this.searchMatches[this.currentMatchIndex].classList.remove("search-highlight-current"),this.searchMatches[this.currentMatchIndex].classList.add("search-highlight")),this.currentMatchIndex+=t,this.currentMatchIndex>=this.searchMatches.length?this.currentMatchIndex=0:this.currentMatchIndex<0&&(this.currentMatchIndex=this.searchMatches.length-1);const n=this.searchMatches[this.currentMatchIndex];n.classList.remove("search-highlight"),n.classList.add("search-highlight-current"),this.scrollToMatch(n),this.updateSearchCounter(this.currentMatchIndex+1,this.searchMatches.length);}scrollToMatch(e){e.classList.add("search-highlight-current"),e.scrollIntoView({behavior:"smooth",block:"center"});}updateSearchCounter(e,t){const n=document.getElementById("search-counter");n&&(n.textContent=`${e}/${t}`);const o=document.getElementById("search-nav");o&&(o.style.display=t>0?"flex":"none");const i=document.getElementById("search-prev"),s=document.getElementById("search-next");i&&(i.disabled=0===t),s&&(s.disabled=0===t);}showAIConfigModal(){const e=document.getElementById("ai-config-modal");if(!e)return;const t=document.getElementById("ai-config-list");t&&Ne.renderAIConfigList(t),this.clearAIConfigForm();const n=e.querySelector(".ai-config-form");n&&n.classList.add("hidden"),document.getElementById("ai-auto-summary-enabled").checked=ae.getAIAutoSummaryEnabled(),e.classList.add("show");}hideAIConfigModal(){const e=document.getElementById("ai-config-modal");if(!e)return;const t=document.getElementById("ai-auto-summary-enabled").checked;ae.setAIAutoSummaryEnabled(t),e.classList.remove("show"),this.clearAIConfigForm();}clearAIConfigForm(){const e=document.getElementById("ai-config-name"),t=document.getElementById("ai-config-url"),n=document.getElementById("ai-config-apikey"),o=document.getElementById("ai-config-model"),i=document.getElementById("ai-config-prompt"),s=document.getElementById("ai-config-is-openrouter"),r=document.getElementById("ai-save-new-btn"),a=document.getElementById("ai-update-btn"),l=document.getElementById("model-select-wrapper"),c=document.getElementById("api-key-help-link");e&&(e.value=""),t&&(t.value="https://openrouter.ai/api/v1/chat/completions"),n&&(n.value=""),o&&(o.value="alibaba/tongyi-deepresearch-30b-a3b:free"),i&&(i.value="请用中文总结以下视频字幕内容,使用Markdown格式输出。\n\n要求:\n1. 在开头提供TL;DR(不超过50字的核心摘要)\n2. 使用标题、列表等Markdown格式组织内容\n3. 突出关键信息和要点\n\n字幕内容:\n"),s&&(s.checked=true),r&&(r.style.display=""),a&&(a.style.display="none"),l&&(l.style.display="none"),c&&(c.innerHTML="");}showNotionConfigModal(){const e=document.getElementById("notion-config-modal");if(!e)return;const t=ae.getNotionConfig();document.getElementById("notion-api-key").value=t.apiKey,document.getElementById("notion-parent-page-id").value=t.parentPageId,document.getElementById("notion-auto-send-enabled").checked=ae.getNotionAutoSendEnabled();const n=document.getElementById("notion-status-message");n&&(n.innerHTML=""),e.classList.add("show");}hideNotionConfigModal(){const e=document.getElementById("notion-config-modal");e&&e.classList.remove("show");}bindAIConfigModalEvents(e){e.addEventListener("click",t=>{t.target===e&&this.hideAIConfigModal();});const t=document.getElementById("ai-config-list");t&&t.addEventListener("click",n=>{const o=n.target.closest(".ai-config-item"),i=n.target.closest(".ai-edit-btn");if(i){const t=i.dataset.id,n=e.querySelector(".ai-config-form");n&&n.classList.remove("hidden"),this.loadConfigToForm(t);}else if(o&&!i){const n=o.dataset.id;ae.setSelectedAIConfigId(n),Ne.renderAIConfigList(t);const i=ae.getAIConfigs().find(e=>e.id===n);Pe.success(`已选择配置: ${i.name}`);const s=e.querySelector(".ai-config-form");s&&s.classList.remove("hidden"),this.loadConfigToForm(n);}}),document.getElementById("ai-new-config-btn").addEventListener("click",()=>{this.clearAIConfigForm();const t=e.querySelector(".ai-config-form");t&&(t.classList.remove("hidden"),setTimeout(()=>{t.scrollIntoView({behavior:"smooth",block:"nearest"});},100)),Pe.info("请填写新配置信息");}),document.getElementById("ai-save-new-btn").addEventListener("click",()=>{this.saveNewAIConfig();}),document.getElementById("ai-update-btn").addEventListener("click",()=>{this.updateAIConfig();}),document.getElementById("ai-cancel-btn").addEventListener("click",()=>{this.hideAIConfigModal();}),document.getElementById("ai-delete-current-btn").addEventListener("click",()=>{const e=document.getElementById("ai-delete-current-btn"),t=e?.dataset.deleteId;if(t&&Pe.confirm("确定要删除这个配置吗?")){const n=ae.deleteAIConfig(t);if(n.success){Pe.success("配置已删除");const t=document.getElementById("ai-config-list");t&&Ne.renderAIConfigList(t);const n=document.querySelector(".ai-config-form");n&&n.classList.add("hidden"),e.style.display="none";}else Pe.error(n.error);}}),document.getElementById("fetch-models-btn").addEventListener("click",async()=>{await this.fetchModels();});}loadConfigToForm(e){const t=ae.getAIConfigs().find(t=>t.id===e);if(!t)return;const n=document.getElementById("ai-config-name"),o=document.getElementById("ai-config-url"),i=document.getElementById("ai-config-apikey"),s=document.getElementById("ai-config-model"),r=document.getElementById("ai-config-prompt"),a=document.getElementById("ai-config-is-openrouter"),l=document.getElementById("ai-save-new-btn"),c=document.getElementById("ai-update-btn"),d=document.getElementById("model-select-wrapper");n&&(n.value=t.name),o&&(o.value=t.url),i&&(i.value=t.apiKey),s&&(s.value=t.model),r&&(r.value=t.prompt),a&&(a.checked=t.isOpenRouter||false);const p=document.getElementById("api-key-help-link");p&&D[t.id]?p.innerHTML=`<a href="${D[t.id]}" target="_blank" style="color: #60a5fa; text-decoration: none;">📖 如何获取API Key?</a>`:p&&(p.innerHTML=""),l&&(l.style.display="none"),c&&(c.style.display="",c.dataset.editId=e),d&&(d.style.display="none");const u=document.getElementById("ai-delete-current-btn");u&&("openrouter"===e||"openai"===e||"siliconflow"===e||"deepseek"===e||"moonshot"===e||"zhipu"===e||"yi"===e||"dashscope"===e||"gemini"===e?u.style.display="none":(u.style.display="",u.dataset.deleteId=e)),setTimeout(()=>{const e=document.querySelector(".ai-config-form");e&&e.scrollIntoView({behavior:"smooth",block:"nearest"});},100);}editAIConfig(e){this.loadConfigToForm(e);}saveNewAIConfig(){const e={name:document.getElementById("ai-config-name").value.trim(),url:document.getElementById("ai-config-url").value.trim(),apiKey:document.getElementById("ai-config-apikey").value.trim(),model:document.getElementById("ai-config-model").value.trim(),prompt:document.getElementById("ai-config-prompt").value,isOpenRouter:document.getElementById("ai-config-is-openrouter").checked},t=ae.addAIConfig(e);if(t.success){Pe.success(`配置"${e.name}"已添加`);const t=document.getElementById("ai-config-list");t&&Ne.renderAIConfigList(t),this.clearAIConfigForm();}else Pe.error(t.error);}updateAIConfig(){const e=document.getElementById("ai-update-btn").dataset.editId;if(!e)return;const t={name:document.getElementById("ai-config-name").value.trim(),url:document.getElementById("ai-config-url").value.trim(),apiKey:document.getElementById("ai-config-apikey").value.trim(),model:document.getElementById("ai-config-model").value.trim(),prompt:document.getElementById("ai-config-prompt").value,isOpenRouter:document.getElementById("ai-config-is-openrouter").checked},n=ae.updateAIConfig(e,t);if(n.success){Pe.success(`配置"${t.name}"已更新`);const e=document.getElementById("ai-config-list");e&&Ne.renderAIConfigList(e),this.clearAIConfigForm();}else Pe.error(n.error);}async fetchModels(){const e=document.getElementById("ai-config-apikey").value.trim(),t=document.getElementById("ai-config-url").value.trim(),n=document.getElementById("ai-config-is-openrouter").checked;if(!e)return void Pe.error("请先填写 API Key");if(!n)return void Pe.error("仅OpenRouter支持获取模型列表");const o=document.getElementById("fetch-models-btn");o.disabled=true,o.textContent="获取中...";try{const n=await he.fetchOpenRouterModels(e,t),o=document.getElementById("model-select-wrapper"),i=document.getElementById("model-select"),s=document.getElementById("model-search-input");if(!i)return void Pe.error("模型选择器未找到");this.allModels=n,i.innerHTML="",n.forEach(e=>{const t=document.createElement("option");t.value=e.id,t.textContent=`${e.name||e.id} (${e.context_length||"N/A"} tokens)`,t.title=e.id,i.appendChild(t);}),o&&(o.style.display="block"),i.onchange=()=>{document.getElementById("ai-config-model").value=i.value;},i.ondblclick=()=>{document.getElementById("ai-config-model").value=i.value,Pe.success("已选择模型");},s&&(s.value="",s.oninput=e=>{this.filterModels(e.target.value);},s.onkeydown=e=>{"Enter"===e.key&&i.options.length>0&&(i.selectedIndex=0,document.getElementById("ai-config-model").value=i.options[0].value,Pe.success("已选择: "+i.options[0].text));}),Pe.success(`已获取 ${n.length} 个模型`);}catch(i){Pe.error(`获取模型列表失败: ${i.message}`);}finally{o.disabled=false,o.textContent="获取模型";}}filterModels(e){if(!this.allModels)return;const t=document.getElementById("model-select");if(!t)return;const n=e.toLowerCase().trim();if(!n)return t.innerHTML="",void this.allModels.forEach(e=>{const n=document.createElement("option");n.value=e.id,n.textContent=`${e.name||e.id} (${e.context_length||"N/A"} tokens)`,n.title=e.id,t.appendChild(n);});const o=this.allModels.filter(e=>{const t=(e.id||"").toLowerCase(),o=(e.name||"").toLowerCase();return t.includes(n)||o.includes(n)});t.innerHTML="",o.forEach(e=>{const n=document.createElement("option");n.value=e.id,n.textContent=`${e.name||e.id} (${e.context_length||"N/A"} tokens)`,n.title=e.id,t.appendChild(n);});const i=document.getElementById("model-search-input");i&&(i.placeholder=o.length>0?`找到 ${o.length} 个模型`:"未找到匹配的模型");}bindNotionConfigModalEvents(e){e.addEventListener("click",t=>{t.target===e&&this.hideNotionConfigModal();}),document.getElementById("notion-save-btn").addEventListener("click",()=>{const e=document.getElementById("notion-api-key").value.trim(),t=document.getElementById("notion-parent-page-id").value.trim(),n=document.getElementById("notion-auto-send-enabled").checked;if(!e)return void Ne.showNotionStatus("请输入 API Key",true);if(!t)return void Ne.showNotionStatus("请输入目标位置(Page ID 或 Database ID)",true);const o=ae.saveNotionConfig({apiKey:e,parentPageId:t});o.success?(ae.setNotionAutoSendEnabled(n),Ne.showNotionStatus("配置已保存"),setTimeout(()=>{this.hideNotionConfigModal();},1500)):Ne.showNotionStatus(o.error,true);}),document.getElementById("notion-cancel-btn").addEventListener("click",()=>{this.hideNotionConfigModal();});}};const $e=new class{constructor(){this.modal=null,this.updateInterval=null;}createModal(){return this.modal||(this.modal=document.createElement("div"),this.modal.id="speed-control-modal",this.modal.className="config-modal",document.body.appendChild(this.modal)),this.modal}show(){const e=this.createModal();this.renderModal(),e.classList.add("show"),this.startUpdateLoop();}hide(){this.modal&&this.modal.classList.remove("show"),this.stopUpdateLoop();}renderModal(){const e=Ee.getState();this.modal.innerHTML=`\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>播放速度控制</span>\n        </div>\n        <div class="config-modal-body">\n          <div style="margin-bottom: 20px; padding: 15px; background: rgba(254, 235, 234, 0.1); border-radius: 10px; border-left: 4px solid #feebea;">\n            <div style="font-size: 13px; color: #fff; font-weight: 600; margin-bottom: 4px;">快捷键说明</div>\n            <div style="font-size: 12px; color: rgba(255, 255, 255, 0.7); line-height: 1.5;">\n              <strong>,</strong> 减速 | <strong>.</strong> 加速 | <strong>,,</strong> 重置1x | <strong>..</strong> 2倍速<br>\n              <strong>右Option</strong> 临时加速 | <strong>右Option双击</strong> 永久加速<br>\n              <strong>, + .</strong> 同时按切换响度检测\n            </div>\n          </div>\n\n          <div class="speed-control-section-large">\n            <div class="speed-control-header-large">\n              <span class="speed-control-title">当前速度</span>\n              <span class="speed-control-display-large" id="speed-display-modal">${e.finalSpeed.toFixed(2)}x</span>\n            </div>\n            \n            <div class="speed-control-buttons-large">\n              <button class="speed-btn-large" data-action="decrease">\n                <span style="font-size: 24px;">−</span>\n                <span style="font-size: 11px;">减速</span>\n              </button>\n              <button class="speed-btn-large" data-action="reset">\n                <span style="font-size: 18px;">1x</span>\n                <span style="font-size: 11px;">重置</span>\n              </button>\n              <button class="speed-btn-large" data-action="double">\n                <span style="font-size: 18px;">2x</span>\n                <span style="font-size: 11px;">2倍速</span>\n              </button>\n              <button class="speed-btn-large" data-action="increase">\n                <span style="font-size: 24px;">+</span>\n                <span style="font-size: 11px;">加速</span>\n              </button>\n            </div>\n\n            <div class="speed-status-info">\n              ${e.isTempBoosted?'<div class="speed-status-item">临时加速中 (右Option)</div>':""}\n              ${e.isVolumeBoosted?'<div class="speed-status-item">响度加速中</div>':""}\n            </div>\n          </div>\n\n          <div class="config-field" style="margin-top: 20px;">\n            <label style="display: flex; align-items: center; justify-content: space-between;">\n              <span>响度检测自动加速</span>\n              <label class="sponsor-switch">\n                <input type="checkbox" id="volume-detection-toggle" ${e.volumeDetectionEnabled?"checked":""}>\n                <span class="sponsor-switch-slider"></span>\n              </label>\n            </label>\n            <div class="config-help" style="margin-top: 8px;">\n              开启后,当检测到音量低于阈值时自动提速 ${Ee.state.boostMultiplier}x\n            </div>\n          </div>\n\n          ${e.volumeDetectionEnabled?`\n            <div class="config-field">\n              <label>响度阈值 (dB)</label>\n              <div style="display: flex; gap: 8px; align-items: center;">\n                <button class="config-btn config-btn-secondary" style="padding: 8px 16px;" id="threshold-decrease">-</button>\n                <input type="number" \n                       id="volume-threshold-input" \n                       value="${e.currentVolumeThreshold}" \n                       min="-100" \n                       max="0" \n                       step="1"\n                       style="flex: 1; text-align: center;">\n                <button class="config-btn config-btn-secondary" style="padding: 8px 16px;" id="threshold-increase">+</button>\n              </div>\n              <div class="config-help">\n                当前阈值: ${e.currentVolumeThreshold}dB (低于此值触发加速)\n              </div>\n            </div>\n          `:""}\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-secondary" id="speed-close-btn">关闭</button>\n        </div>\n      </div>\n    `,this.bindEvents();}bindEvents(){this.modal.addEventListener("click",e=>{e.target===this.modal&&this.hide();});this.modal.querySelectorAll(".speed-btn-large").forEach(e=>{e.addEventListener("click",()=>{const t=e.getAttribute("data-action");this.handleSpeedAction(t);});});const e=document.getElementById("volume-detection-toggle");e&&e.addEventListener("change",()=>{Ee.toggleVolumeDetection(),this.renderModal();});const t=document.getElementById("threshold-decrease"),n=document.getElementById("threshold-increase"),o=document.getElementById("volume-threshold-input");t&&t.addEventListener("click",()=>{Ee.adjustVolumeThreshold(-1),this.updateThresholdDisplay();}),n&&n.addEventListener("click",()=>{Ee.adjustVolumeThreshold(1),this.updateThresholdDisplay();}),o&&o.addEventListener("change",e=>{const t=parseInt(e.target.value);isNaN(t)||(Ee.state.currentVolumeThreshold=Math.max(-100,Math.min(0,t)),this.updateThresholdDisplay());});const i=document.getElementById("speed-close-btn");i&&i.addEventListener("click",()=>this.hide());}handleSpeedAction(e){switch(e){case "increase":Ee.adjustBaseSpeed(.1);break;case "decrease":Ee.adjustBaseSpeed(-0.1);break;case "reset":Ee.resetToNormalSpeed();break;case "double":Ee.setToDoubleSpeed();}this.updateSpeedDisplay();}updateSpeedDisplay(){const e=document.getElementById("speed-display-modal");if(e){const t=Ee.getCurrentSpeed();e.textContent=`${t.toFixed(2)}x`;}}updateThresholdDisplay(){const e=document.getElementById("volume-threshold-input");e&&(e.value=Ee.state.currentVolumeThreshold);}startUpdateLoop(){this.updateInterval=setInterval(()=>{this.updateSpeedDisplay();},200);}stopUpdateLoop(){this.updateInterval&&(clearInterval(this.updateInterval),this.updateInterval=null);}};const qe=new class{constructor(){this.modal=null;}createModal(){return this.modal||(this.modal=document.createElement("div"),this.modal.id="help-modal",this.modal.className="config-modal",document.body.appendChild(this.modal)),this.modal}show(){const e=this.createModal();this.renderModal(),e.classList.add("show");}hide(){this.modal&&this.modal.classList.remove("show");}renderModal(){this.modal.innerHTML='\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>使用帮助</span>\n        </div>\n        <div class="config-modal-body">\n          <div style="margin-bottom: 20px;">\n            <h3 style="color: #fff; margin-bottom: 10px; font-size: 16px;">功能特性</h3>\n            <ul style="line-height: 1.8; color: #e5e7eb;">\n              <li><strong>字幕提取</strong> - 自动检测并提取B站AI字幕和人工字幕</li>\n              <li><strong>AI智能总结</strong> - 支持OpenAI、OpenRouter等多种AI服务</li>\n              <li><strong>Notion集成</strong> - 一键发送字幕和总结到Notion数据库</li>\n              <li><strong>笔记保存</strong> - 选中任意文字显示粉色钢笔图标保存笔记</li>\n              <li><strong>播放速度控制</strong> - 键盘快捷键控制速度和响度检测自动加速</li>\n            </ul>\n          </div>\n\n          <div style="margin-bottom: 20px;">\n            <h3 style="color: #fff; margin-bottom: 10px; font-size: 16px;">快捷键</h3>\n            <table style="width: 100%; border-collapse: collapse; font-size: 13px;">\n              <thead>\n                <tr style="background: rgba(255, 255, 255, 0.1); border-bottom: 1px solid rgba(254, 235, 234, 0.2);">\n                  <th style="padding: 8px; text-align: left; font-weight: 600; color: #fff;">功能</th>\n                  <th style="padding: 8px; text-align: left; font-weight: 600; color: #fff;">快捷键</th>\n                  <th style="padding: 8px; text-align: left; font-weight: 600; color: #fff;">说明</th>\n                </tr>\n              </thead>\n              <tbody>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">切换字幕面板</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">Cmd/Ctrl + B</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">显示/隐藏字幕面板</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">增加速度</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">.</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">每次增加0.1x</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">减少速度</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">,</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">每次减少0.1x</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">2倍速</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">.. (双击)</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">直接设为2倍速</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">重置速度</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">,, (双击)</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">重置为1倍速</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">临时加速</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">右Option键</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">按住时1.5x加速</td>\n                </tr>\n                <tr style="border-bottom: 1px solid rgba(254, 235, 234, 0.1);">\n                  <td style="padding: 8px; color: #e5e7eb;">响度检测</td>\n                  <td style="padding: 8px;"><code style="background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 3px; color: #feebea;">, + . (同时按)</code></td>\n                  <td style="padding: 8px; color: rgba(255, 255, 255, 0.7);">开启/关闭自动加速</td>\n                </tr>\n              </tbody>\n            </table>\n          </div>\n\n          <div style="margin-bottom: 20px;">\n            <h3 style="color: #fff; margin-bottom: 10px; font-size: 16px;">使用说明</h3>\n            <div style="line-height: 1.8; color: #e5e7eb;">\n              <p style="margin: 8px 0;"><strong>字幕提取:</strong>打开B站视频,等待几秒,字幕面板自动出现在右侧</p>\n              <p style="margin: 8px 0;"><strong>AI总结:</strong>配置AI服务(菜单 → AI配置),点击魔法棒图标 ✨</p>\n              <p style="margin: 8px 0;"><strong>笔记保存:</strong>选中任意文字,点击粉色钢笔图标</p>\n              <p style="margin: 8px 0;"><strong>速度控制:</strong>使用 , 和 . 键调整速度,同时按切换响度检测</p>\n            </div>\n          </div>\n\n          <div style="padding: 15px; background: rgba(254, 235, 234, 0.1); border-radius: 10px; border-left: 4px solid #feebea;">\n            <div style="font-size: 13px; color: #fff; font-weight: 600; margin-bottom: 4px;">提示</div>\n            <div style="font-size: 12px; color: rgba(255, 255, 255, 0.7); line-height: 1.5;">\n              • AI配置支持多个提供商,可自由切换<br>\n              • 笔记保存在本地,按日期自动分组<br>\n              • SponsorBlock支持自动跳过广告和赞助片段\n            </div>\n          </div>\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-primary" id="help-close-btn">知道了</button>\n        </div>\n      </div>\n    ',this.bindEvents();}bindEvents(){this.modal.addEventListener("click",e=>{e.target===this.modal&&this.hide();});const e=document.getElementById("help-close-btn");e&&e.addEventListener("click",()=>this.hide());}};const Ve=new class{constructor(){this.modal=null;}createModal(){return this.modal||(this.modal=document.createElement("div"),this.modal.id="sponsorblock-modal",this.modal.className="config-modal",document.body.appendChild(this.modal)),this.modal}show(){const e=this.createModal();this.renderModal(),e.classList.add("show");}hide(){this.modal&&this.modal.classList.remove("show");}renderModal(){const e=Ae.getAll();this.modal.innerHTML=`\n      <div class="config-modal-content">\n        <div class="config-modal-header">\n          <span>SponsorBlock 设置</span>\n        </div>\n        <div class="config-modal-body">\n          <div style="margin-bottom: 20px; padding: 15px; background: rgba(254, 235, 234, 0.1); border-radius: 10px; border-left: 4px solid #feebea;">\n            <div style="font-size: 13px; color: #fff; font-weight: 600; margin-bottom: 4px;">使用说明</div>\n            <div style="font-size: 12px; color: rgba(255, 255, 255, 0.7); line-height: 1.5;">\n              <strong>勾选的类别</strong> → 自动跳过<br>\n              <strong>未勾选的类别</strong> → 显示手动提示(5秒后自动消失)<br>\n              在进度条上会显示彩色标记,点击可查看详情\n            </div>\n          </div>\n\n          <div class="sponsor-settings-section">\n            <h3>片段类别(勾选=自动跳过,未勾选=手动提示)</h3>\n            <div class="sponsor-checkbox-group">\n              ${Object.entries(W.CATEGORIES).map(([t,n])=>`\n                <div class="sponsor-checkbox-item">\n                  <input type="checkbox" \n                         id="category-${t}" \n                         value="${t}"\n                         ${e.skipCategories.includes(t)?"checked":""}>\n                  <label for="category-${t}">\n                    <span class="category-color-dot" style="background: ${n.color}"></span>\n                    <span>${n.name}</span>\n                  </label>\n                </div>\n              `).join("")}\n            </div>\n          </div>\n\n          <div class="sponsor-settings-section">\n            <h3>显示选项</h3>\n            <div class="sponsor-switch-item">\n              <span>显示片段标签(视频卡片)</span>\n              <label class="sponsor-switch">\n                <input type="checkbox" id="showAdBadge" \n                       ${e.showAdBadge?"checked":""}>\n                <span class="sponsor-switch-slider"></span>\n              </label>\n            </div>\n            <div class="sponsor-switch-item">\n              <span>显示优质视频标签</span>\n              <label class="sponsor-switch">\n                <input type="checkbox" id="showQualityBadge" \n                       ${e.showQualityBadge?"checked":""}>\n                <span class="sponsor-switch-slider"></span>\n              </label>\n            </div>\n            <div class="sponsor-switch-item">\n              <span>进度条显示片段标记</span>\n              <label class="sponsor-switch">\n                <input type="checkbox" id="showProgressMarkers" \n                       ${e.showProgressMarkers?"checked":""}>\n                <span class="sponsor-switch-slider"></span>\n              </label>\n            </div>\n          </div>\n        </div>\n        <div class="config-footer">\n          <button class="config-btn config-btn-secondary" id="sponsorblock-cancel-btn">取消</button>\n          <button class="config-btn config-btn-primary" id="sponsorblock-save-btn">保存</button>\n        </div>\n      </div>\n    `,this.bindEvents();}bindEvents(){this.modal.addEventListener("click",e=>{e.target===this.modal&&this.hide();});const e=document.getElementById("sponsorblock-save-btn");e&&e.addEventListener("click",()=>this.saveSettings());const t=document.getElementById("sponsorblock-cancel-btn");t&&t.addEventListener("click",()=>this.hide());}saveSettings(){const e={skipCategories:Array.from(this.modal.querySelectorAll('.sponsor-checkbox-item input[type="checkbox"]:checked')).map(e=>e.value),showAdBadge:this.modal.querySelector("#showAdBadge").checked,showQualityBadge:this.modal.querySelector("#showQualityBadge").checked,showProgressMarkers:this.modal.querySelector("#showProgressMarkers").checked};Ae.setAll(e),this.hide(),Pe.info("设置已保存!\n\n✅ 勾选的类别 → 自动跳过\n⏸️ 未勾选的类别 → 手动提示(5秒)\n\n页面将刷新以应用新设置。"),setTimeout(()=>{location.reload();},2e3);}},Re="bilibili_shortcuts_config",Oe={toggleSubtitlePanel:{key:"b",ctrl:true,alt:false,shift:false,description:"切换字幕面板"},speedIncrease:{key:"Period",ctrl:false,alt:false,shift:false,description:"增加播放速度"},speedDecrease:{key:"Comma",ctrl:false,alt:false,shift:false,description:"减少播放速度"},speedReset:{key:"Comma",ctrl:false,alt:false,shift:false,doubleClick:true,description:"重置播放速度(双击)"},speedDouble:{key:"Period",ctrl:false,alt:false,shift:false,doubleClick:true,description:"2倍速(双击)"}};const Fe=new class{constructor(){this.shortcuts=this.loadShortcuts(),this.handlers=new Map,this.isListening=false;}loadShortcuts(){try{const e=GM_getValue(Re,null);return e?JSON.parse(e):{...Oe}}catch(e){return console.error("加载快捷键配置失败:",e),{...Oe}}}saveShortcuts(e){try{return this.shortcuts=e,GM_setValue(Re,JSON.stringify(e)),{success:!0,error:null}}catch(t){return console.error("保存快捷键配置失败:",t),{success:false,error:t.message}}}resetToDefaults(){return this.shortcuts={...Oe},this.saveShortcuts(this.shortcuts)}getAllShortcuts(){return {...this.shortcuts}}updateShortcut(e,t){if(!this.shortcuts[e])return {success:false,error:"快捷键不存在"};const n=this.checkConflict(e,t);return n?{success:false,error:`与"${n}"冲突`}:(this.shortcuts[e]={...this.shortcuts[e],...t},this.saveShortcuts(this.shortcuts))}checkConflict(e,t){for(const[n,o]of Object.entries(this.shortcuts))if(n!==e&&o.key===t.key&&o.ctrl===t.ctrl&&o.alt===t.alt&&o.shift===t.shift&&o.doubleClick===t.doubleClick)return o.description;return null}register(e,t){this.handlers.set(e,t);}matches(e,t){const n=e.ctrlKey||e.metaKey;return e.code===t.key&&n===t.ctrl&&e.altKey===t.alt&&e.shiftKey===t.shift}startListening(){this.isListening||(document.addEventListener("keydown",e=>this.handleKeyDown(e),true),this.isListening=true);}handleKeyDown(e){const t="INPUT"===e.target.tagName||"TEXTAREA"===e.target.tagName||e.target.isContentEditable;for(const[n,o]of Object.entries(this.shortcuts)){if(o.doubleClick)continue;const i=o.ctrl||o.alt;if(this.matches(e,o)){if(t&&!i)continue;const o=this.handlers.get(n);o&&(e.preventDefault(),o(e));}}}formatShortcut(e){const t=[];e.ctrl&&t.push(navigator.platform.includes("Mac")?"Cmd":"Ctrl"),e.alt&&t.push("Alt"),e.shift&&t.push("Shift");let n=e.key;return "Period"===n&&(n="."),"Comma"===n&&(n=","),1===n.length&&(n=n.toUpperCase()),t.push(n),e.doubleClick&&t.push("(双击)"),t.join(" + ")}validateConfig(e){return e.key&&"string"==typeof e.key?"boolean"!=typeof e.ctrl||"boolean"!=typeof e.alt||"boolean"!=typeof e.shift?{valid:false,error:"修饰键配置错误"}:{valid:true,error:null}:{valid:false,error:"按键不能为空"}}};const He=new class{constructor(){this.initialized=false,this.ball=null,this.container=null,this.videoQualityService=null;}async init(){var e;this.initialized||(!function(){const e=document.createElement("style");e.textContent=J,document.head.appendChild(e);}(),await this.waitForPageReady(),fe.init(),Ee.init(),await Me.init(),this.videoQualityService=(e=Me.getAPI(),De||(De=new Be(e)),De),this.videoQualityService.start(),this.createUI(),this.bindEvents(),this.setupAutomation(),this.registerMenuCommands(),this.registerShortcuts(),ue.checkSubtitleButton(),this.observeVideoChange(),this.initialized=true);}registerShortcuts(){Fe.register("toggleSubtitlePanel",()=>{re.togglePanel();}),Fe.startListening();}registerMenuCommands(){"undefined"!=typeof GM_registerMenuCommand&&(GM_registerMenuCommand("AI配置",()=>{_e.showAIConfigModal();}),GM_registerMenuCommand("Notion配置",()=>{_e.showNotionConfigModal();}),GM_registerMenuCommand("笔记管理",()=>{ze.togglePanel();}),GM_registerMenuCommand("速度控制",()=>{$e.show();}),GM_registerMenuCommand("SponsorBlock 设置",()=>{Ve.show();}),GM_registerMenuCommand("使用帮助",()=>{qe.show();}),GM_registerMenuCommand("关于",()=>{Pe.info("Bilibili Tools v1.0.0 - by geraldpeng & claude 4.5 sonnet");}));}async waitForPageReady(){return new Promise(t=>{const n=setInterval(()=>{document.querySelector(U)&&(clearInterval(n),t());},e);})}createUI(){this.ball=document.createElement("div"),this.ball.id="subtitle-ball",this.ball.title="字幕提取器";const e=document.querySelector(U);e&&("relative"!==e.style.position&&"absolute"!==e.style.position&&(e.style.position="relative"),e.appendChild(this.ball)),this.createEmbeddedContainer();const t=Ne.createNotionConfigModal();document.body.appendChild(t),_e.bindNotionConfigModalEvents(t);const n=Ne.createAIConfigModal();document.body.appendChild(n),_e.bindAIConfigModalEvents(n);}createEmbeddedContainer(){this.container=document.createElement("div"),this.container.id="subtitle-container";const e=document.querySelector(U);e?("relative"!==e.style.position&&"absolute"!==e.style.position&&(e.style.position="relative"),e.appendChild(this.container)):document.body.appendChild(this.container);}bindEvents(){se.on(y,(e,t)=>{this.renderSubtitles(e);}),se.on(w,()=>{console.log("[App] AI总结开始,小球进入AI总结状态"),this.ball&&(this.ball.classList.remove("loading","active","no-subtitle","error"),this.ball.classList.add("ai-summarizing"),this.ball.title="正在AI总结...");const e=this.container?.querySelector(".ai-icon");e&&e.classList.add("loading");}),se.on(I,e=>{this.container&&Ne.updateAISummary(this.container,e);}),se.on(S,(e,t)=>{console.log("[App] AI总结完成,恢复小球正常状态"),Pe.success("AI总结完成"),this.container&&Ne.updateAISummary(this.container,e),this.ball&&(this.ball.classList.remove("ai-summarizing","loading"),this.ball.classList.add("active"),this.ball.title="字幕提取器 - 点击查看字幕");const n=this.container?.querySelector(".ai-icon");n&&n.classList.remove("loading");}),se.on(C,()=>{Pe.success("字幕已成功发送到 Notion");const e=this.container?.querySelector(".notion-icon");e&&e.classList.remove("loading");}),se.on(x,e=>{Pe.handleError(e,"字幕获取");}),se.on(k,e=>{console.log("[App] AI总结失败,恢复小球正常状态"),Pe.handleError(e,"AI总结"),this.ball&&(this.ball.classList.remove("ai-summarizing","loading"),this.ball.classList.add("active"),this.ball.title="字幕提取器 - 点击查看字幕");const t=this.container?.querySelector(".ai-icon");t&&t.classList.remove("loading");}),se.on(A,e=>{Pe.handleError(e,"Notion发送");}),se.on(L,e=>{this.updateBallStatus(e);}),se.on(T,e=>{this.container&&(e?this.container.classList.add("show"):this.container.classList.remove("show"));}),document.addEventListener("keydown",e=>{(e.metaKey||e.ctrlKey)&&"b"===e.key&&(e.preventDefault(),re.togglePanel());});}renderSubtitles(e){if(!this.container||!e)return;this.container.innerHTML=Ne.renderSubtitlePanel(e);const t=re.getVideoKey(),n=t?re.getAISummary(t):null;if(n)Ne.updateAISummary(this.container,n);else if(re.ai.isSummarizing){const e=this.container.querySelector(".subtitle-content");if(e){const t=Ne.renderAISummarySection(null,true);e.insertBefore(t,e.firstChild);}}_e.bindSubtitlePanelEvents(this.container),console.log("[App] 字幕面板已渲染");}setupAutomation(){se.on(y,async e=>{await pe(a);const t=ae.getAIAutoSummaryEnabled(),n=ae.getSelectedAIConfig(),o=re.getVideoKey(),i=o?re.getAISummary(o):null;if(t&&n&&n.apiKey&&!i)try{await he.summarize(e,!0);}catch(s){console.error("[App] 自动总结失败:",s);}}),se.on(S,async()=>{const e=ae.getNotionAutoSendEnabled(),t=ae.getNotionConfig();if(e&&t.apiKey){const e=re.getSubtitleData();if(e)try{await ge.sendSubtitle(e,!0);}catch(n){console.error("[App] 自动发送失败:",n);}}}),se.on(y,async e=>{await pe(a);const t=ae.getAIAutoSummaryEnabled(),n=ae.getNotionAutoSendEnabled(),o=ae.getNotionConfig();if(!t&&n&&o.apiKey)try{await ge.sendSubtitle(e,!0);}catch(i){console.error("[App] 自动发送失败:",i);}});}updateBallStatus(e){if(this.ball)switch(this.ball.classList.remove("loading","active","no-subtitle","error"),e){case m:this.ball.classList.add("active"),this.ball.style.cursor="pointer",this.ball.onclick=()=>re.togglePanel(),this.ball.title="字幕提取器 - 点击查看字幕";break;case b:this.ball.classList.add("no-subtitle"),this.ball.style.cursor="default",this.ball.onclick=null,this.ball.title="该视频无字幕";break;case f:this.ball.classList.add("error"),this.ball.style.cursor="default",this.ball.onclick=null,this.ball.title="字幕加载失败";break;case g:this.ball.classList.add("loading"),this.ball.style.cursor="default",this.ball.onclick=null,this.ball.title="正在加载字幕...";}}observeVideoChange(){let e=location.href,t=location.href.match(/BV[1-9A-Za-z]{10}/)?.[0],n=null;const o=()=>{try{const e=unsafeWindow.__INITIAL_STATE__;return e?.videoData?.cid||e?.videoData?.pages?.[0]?.cid}catch(e){return null}};n=o();const i=()=>{const i=location.href,s=i.match(/BV[1-9A-Za-z]{10}/)?.[0],a=o();i===e||s===t&&a===n||(console.log("[App] 检测到视频切换:",{from:t,to:s}),e=i,t=s,n=a,re.reset(),ue.reset(),se.emit(M,{bvid:s,cid:a}),setTimeout(()=>{const e=ce();re.setVideoInfo(e),ue.checkSubtitleButton();},r));},s=history.pushState,a=history.replaceState;history.pushState=function(...e){s.apply(this,e),i();},history.replaceState=function(...e){a.apply(this,e),i();},window.addEventListener("popstate",i);const l=setInterval(i,1e3);this.urlChangeCleanup=()=>{history.pushState=s,history.replaceState=a,window.removeEventListener("popstate",i),clearInterval(l);},console.log("[App] 视频切换监听已启动(使用 History API 劫持)");}cleanup(){console.log("[App] 开始清理应用资源"),this.urlChangeCleanup&&this.urlChangeCleanup(),this.videoQualityService&&this.videoQualityService.stop(),Me.playerController&&Me.playerController.destroy(),Ee.destroy(),console.log("[App] 应用资源清理完成");}};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>He.init()):He.init();

})();