视频网页全屏(改)

让所有视频网页全屏,开启画中画功能,支持自定义按钮位置。

// ==UserScript==
// @name                视频网页全屏(改)
// @name:en             Maximize Video(Modify)
// @name:zh-CN          视频网页全屏(改)
// @name:zh-TW          視頻網頁全屏(改)
// @name:ja             ビデオページ全画面(変更)
// @name:ko             비디오 웹페이지 전체화면(수정)
// @namespace           https://gf.qytechs.cn/zh-CN/users/178351-yesilin
// @description         让所有视频网页全屏,开启画中画功能,支持自定义按钮位置。
// @description:en      Maximize all video players.Support Piture-in-picture and custom button position.
// @description:zh-CN   让所有视频网页全屏,开启画中画功能,支持自定义按钮位置。
// @description:zh-TW   讓所有視頻網頁全屏,開啟畫中畫功能,支援自定義按鈕位置。
// @description:ja      すべての動画ページを全画面表示し、ピクチャ・イン・ピクチャ機能を有効にします。ボタン位置のカスタマイズにも対応しています。
// @description:ko      모든 비디오 웹페이지를 전체화면으로 전환하고, PIP(화면 속 화면) 기능과 사용자 지정 버튼 위치를 지원합니다.
// @author              冻猫, RyomaHan, YeSilin
// @include             *
// @exclude             *www.w3school.com.cn*
// @version             12.5.46
// @run-at              document-end
// @license             MIT
// @grant               GM_setValue
// @grant               GM_getValue
// @icon                data:image/svg+xml;base64,PHN2ZyBpZD0i5Zu+5bGCXzEiIGRhdGEtbmFtZT0i5Zu+5bGCIDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDIwMCAxNTIuNiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7fS5jbHMtMntmaWxsOiMyMzE4MTU7fS5jbHMtM3tmaWxsOiMwNDAwMDA7fS5jbHMtNHtmaWxsOiNmNWM5MDA7fTwvc3R5bGU+PC9kZWZzPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTE3MCwxNzEuNDZIMjguMzhhOC42Miw4LjYyLDAsMCwxLTguNi04LjU5Vjc1LjA3QTIxLjU1LDIxLjU1LDAsMCwxLDQxLjI3LDUzLjU4SDE1Ny4xMmEyMS41NSwyMS41NSwwLDAsMSwyMS40OSwyMS40OXY4Ny44YTguNjIsOC42MiwwLDAsMS04LjYsOC41OVoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTIzLjcpIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTczLjEzLDE3Ni4zSDI3YTEyLjI0LDEyLjI0LDAsMCwxLTEyLjI1LTEyLjI1Vjc2LjM2QTI2Ljg3LDI2Ljg3LDAsMCwxLDQxLjU5LDQ5LjQ5SDE1OC40MWEyNi44NywyNi44NywwLDAsMSwyNi44NiwyNi44N3Y4Ny42OWExMi4wOCwxMi4wOCwwLDAsMS0xMi4xNCwxMi4yNVpNNDEuNTksNTYuMjZBMjAuMTQsMjAuMTQsMCwwLDAsMjEuNSw3Ni4zNnY4Ny42OUE1LjUsNS41LDAsMCwwLDI3LDE2OS41M0gxNzMuMTNhNS41LDUuNSwwLDAsMCw1LjQ4LTUuNDhWNzYuMzZhMjAuMTQsMjAuMTQsMCwwLDAtMjAuMS0yMC4xWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtMjMuNykiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik02OC44OSwxMjEuMjhIMy4xMkEzLjEyLDMuMTIsMCwwLDEsMCwxMTguMTYsMywzLDAsMCwxLDMuMTIsMTE1SDY4Ljg5YTMuMTIsMy4xMiwwLDEsMSwwLDYuMjRabTEyOCwwaC02Ni4yYTMuMTIsMy4xMiwwLDEsMSwwLTYuMjRoNjYuMmEzLjEyLDMuMTIsMCwwLDEsMy4xMSwzLjEyQTMsMywwLDAsMSwxOTYuODgsMTIxLjI4Wm0tMTI4LDE1LjM2aC0zNmEzLjEyLDMuMTIsMCwxLDEsMC02LjIzaDM2YTMuMTIsMy4xMiwwLDEsMSwwLDYuMjNabTk4LDBIMTMwLjY4YTMuMTIsMy4xMiwwLDEsMSwwLTYuMjNIMTY2LjlhMy4xMiwzLjEyLDAsMCwxLDMuMTEsMy4xMkEzLDMsMCwwLDEsMTY2LjksMTM2LjY0Wk05MS4xMywxMjQuMDdhMTAsMTAsMCwwLDEtOC43LTUsMS4zMywxLjMzLDAsMSwxLDIuMjYtMS40LDcuNDgsNy40OCwwLDAsMCwxNC0zLjc2LDEuMjksMS4yOSwwLDEsMSwyLjU4LDAsMTAuMTgsMTAuMTgsMCwwLDEtMTAuMTEsMTAuMjFaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0yMy43KSIvPjxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTEwOC42NSwxMjQuMDdBMTAuMDgsMTAuMDgsMCwwLDEsOTguNTUsMTE0YTEuMjksMS4yOSwwLDEsMSwyLjU4LDAsNy41NCw3LjU0LDAsMCwwLDcuNTIsNy41Miw3LjI3LDcuMjcsMCwwLDAsNi40NS0zLjc2LDEuMzMsMS4zMywwLDEsMSwyLjI2LDEuNCwxMC4xNSwxMC4xNSwwLDAsMS04LjcxLDQuOTRaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0yMy43KSIvPjxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTYyLjMzLDk4LjkzYTguNzEsOC43MSwwLDAsMCwxNy40MSwwaDBhOC43MSw4LjcxLDAsMCwwLTE3LjQxLDBaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0yMy43KSIvPjxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTEyMC4zNiw5OC45M2E4LjcxLDguNzEsMCwxLDAsOC43MS04LjcxQTguNzEsOC43MSwwLDAsMCwxMjAuMzYsOTguOTNaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0yMy43KSIvPjxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTEwMC4wNSw3Mi45MmEzLjYyLDMuNjIsMCwwLDEtMy42NS0zLjY1VjYxLjg1YTMuNjYsMy42NiwwLDAsMSw3LjMxLDB2Ny40MkEzLjYyLDMuNjIsMCwwLDEsMTAwLjA1LDcyLjkyWm0tMTQsMGEzLjYyLDMuNjIsMCwwLDEtMy42NS0zLjY1VjYxLjg1YTMuNjYsMy42NiwwLDAsMSw3LjMxLDB2Ny40MkEzLjYyLDMuNjIsMCwwLDEsODYuMDgsNzIuOTJabTI3Ljk0LDBhMy42MiwzLjYyLDAsMCwxLTMuNjUtMy42NVY2MS44NWEzLjY2LDMuNjYsMCwwLDEsNy4zMSwwdjcuNDJBMy42MiwzLjYyLDAsMCwxLDExNCw3Mi45MloiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTIzLjcpIi8+PHBhdGggY2xhc3M9ImNscy00IiBkPSJNMzkuMjMsNTIuODJzNC4wOC0yNS44OSwxOC0yNS44OVM3NS4xMiw1Mi44Miw3NS4xMiw1Mi44MloiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTIzLjcpIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNzUuMTIsNTUuOTRIMzkuMjNhMy4wNywzLjA3LDAsMCwxLTIuMzYtMS4wNywzLjE0LDMuMTQsMCwwLDEtLjc2LTIuNThjLjIyLTEuMTksNC43My0yOC41OSwyMS4wNy0yOC41OVM3OCw1MS4xLDc4LjI0LDUyLjI5YTMuMzUsMy4zNSwwLDAsMS0uNzUsMi41OEEzLjEsMy4xLDAsMCwxLDc1LjEyLDU1Ljk0Wm0tMzItNi4yM0g3MS4yNUM2OS40Myw0Mi4wOCw2NC45MSwzMCw1Ny4xOCwzMCw0OSwzMCw0NC43MSw0My4xNSw0My4xLDQ5LjcxWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtMjMuNykiLz48cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMjQuNjYsNTIuODJzNC4wOS0yNS44OSwxOC0yNS44OSwxNy45NSwyNS44OSwxNy45NSwyNS44OVoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTIzLjcpIi8+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTYwLjQ1LDU1Ljk0SDEyNC41NmEzLjE2LDMuMTYsMCwwLDEtMy4xMi0zLjY1Yy4yMS0xLjE5LDQuNzMtMjguNTksMjEuMDYtMjguNTlzMjAuODUsMjcuNCwyMS4wNiwyOC41OWEzLjMxLDMuMzEsMCwwLDEtLjc1LDIuNThBMy4wNywzLjA3LDAsMCwxLDE2MC40NSw1NS45NFptLTMxLjkyLTYuMjNoMjguMTZDMTU0Ljg2LDQyLjA4LDE1MC4zNSwzMCwxNDIuNjEsMzAsMTM0LjMzLDMwLDEzMC4xNCw0My4xNSwxMjguNTMsNDkuNzFaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0yMy43KSIvPjwvc3ZnPg==
// ==/UserScript==

(() => {
  // 全局变量存储对象 (global variables)
  const gv = {
    // 状态标记类
    isFull: false, // 是否处于全屏状态
    isIframe: false, // 当前页面是否在 iframe 中
    useCssFullscreen: false, // 是否使用了 CSS 注入的网页全屏方式
    ytbStageChange: false, // YouTube 舞台模式切换标记
    autoCheckCount: 0, // 自动检测计数器

    // 播放器相关
    player: null, // 当前激活的视频播放器元素
    playerChilds: [], // 播放器子元素列表
    playerParents: [], // 播放器父元素列表
    backControls: null, // 全屏前视频控件状态

    // 页面结构备份
    backHtmlId: "", // 全屏前 html 元素的 ID
    backBodyId: "", // 全屏前 body 元素的 ID

    // 滚动与交互状态
    scrollTop: 0, // 保存进入全屏前的垂直滚动位置
    scrollLeft: 0, // 保存进入全屏前的水平滚动位置
    scrollFixTimer: null, // 滚动修正定时器
    mouseoverEl: null, // 鼠标悬停元素

    // 按钮配置与元素
    btnText: {}, // 按钮文本(多语言支持)
    btnPosition: GM_getValue("buttonPosition", "top-right"), // 按钮位置,默认右上角
    picinpicBtn: null, // 画中画按钮
    controlBtn: null, // 网页全屏按钮
    leftBtn: null, // 左侧边缘退出网页全屏按钮
    rightBtn: null, // 右侧边缘退出网页全屏按钮

    // 界面元素
    contextMenu: null, // 右键菜单元素
  };

  // Html5播放器规则[播放器最外层],适用于无法自动识别的自适应大小HTML5播放器
  // 键为域名,值为该域名下播放器元素的选择器对象
  const html5Rules = {
    "www.bilibili.com": {
      player: ["#bilibiliPlayer"],
      fullscreen: [".bpx-player-ctrl-web"],
      pip: [".bpx-player-ctrl-pip"],
    },
    "v.qq.com": {
      player: ["#player-container"],
      fullscreen: [".txp_btn_fake"],
      pip: [".txp_btn_pip"],
    },
    "www.youtube.com": {
      player: ["#ytd-player"],
    },
    "www.twitch.tv": {
      player: [".player"],
    },
    "www.huya.com": {
      player: ["#videoContainer"],
    },
    "www.douyu.com": {
      player: ["#js-player-video-case"],
    },
    "www.acfun.cn": {
      player: [".player-container .player"],
    },
    "www.miguvideo.com": {
      player: ["#mod-player"],
    },
    "www.yy.com": {
      player: ["#player"],
    },
    "v.huya.com": {
      player: ["#video_embed_flash>div"],
    },
    "*weibo.com": {
      player: ['[aria-label="Video Player"]', ".html5-video-live .html5-video", ".FeedPlayer_feedVideo_39PLs"],
      fullscreen: ["#videoFull"],
    },
  };

  // 通用html5播放器选择器,用于匹配常见的视频播放器类名
  const generalPlayerRules = [".dplayer", ".video-js", ".jwplayer", "[data-player]", ".art-video-player"];

  // 判断当前页面是否在iframe中
  if (window.top !== window.self) {
    gv.isIframe = true;
  }

  // 根据浏览器语言设置按钮文本
  if (navigator.language.toLocaleLowerCase() == "zh-cn") {
    gv.btnText = {
      max: "网页全屏",
      maxTooltip: "切换网页全屏(ESC),右键选择按钮位置", // 悬浮提示
      pip: "画中画",
      pipTooltip: "切换画中画(F2),右键选择按钮位置", // 悬浮提示
      tip: "Iframe内视频,请用鼠标点击视频后重试",
      menuTitle: "选择按钮位置",
      topLeft: "左上角",
      topRight: "右上角",
    };
  } else {
    gv.btnText = {
      max: "Maximize",
      maxTooltip: "Toggle fullscreen (ESC). Right-click to choose button position", // 悬浮提示
      pip: "PicInPic",
      pipTooltip: "Toggle Picture-in-Picture (F2). Right-click to choose button position", // 悬浮提示
      tip: "Iframe video. Please click on the video and try again",
      menuTitle: "Choose Button Position",
      topLeft: "Top Left",
      topRight: "Top Right",
    };
  }

  // 工具函数集合
  const tool = {
    /**
     * 带时间戳的日志打印
     * @param {string} log - 日志内容
     */
    print(log) {
      const now = new Date();
      const year = now.getFullYear();
      const month = (now.getMonth() + 1 < 10 ? "0" : "") + (now.getMonth() + 1);
      const day = (now.getDate() < 10 ? "0" : "") + now.getDate();
      const hour = (now.getHours() < 10 ? "0" : "") + now.getHours();
      const minute = (now.getMinutes() < 10 ? "0" : "") + now.getMinutes();
      const second = (now.getSeconds() < 10 ? "0" : "") + now.getSeconds();
      const timenow = "[" + year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second + "]";
      console.log(timenow + "[Maximize Video] > " + log);
    },

    /**
     * 获取元素的位置信息
     * @param {HTMLElement} element - 目标元素
     * @returns {Object} 包含页面坐标和屏幕坐标的对象
     */
    getRect(element) {
      const rect = element.getBoundingClientRect();
      const scroll = tool.getScroll();
      return {
        pageX: rect.left + scroll.left, // 元素左上角在页面中的X坐标
        pageY: rect.top + scroll.top, // 元素左上角在页面中的Y坐标
        screenX: rect.left, // 元素左上角在视口中的X坐标
        screenY: rect.top, // 元素左上角在视口中的Y坐标
        width: rect.width,
        height: rect.height,
      };
    },

    /**
     * 判断元素是否半全屏显示(宽或高接近视口)
     * @param {HTMLElement} element - 目标元素
     * @returns {boolean} 是否半全屏
     */
    isHalfFullClient(element) {
      const client = tool.getClient();
      const rect = tool.getRect(element);
      // 宽或高接近视口,且元素居中显示
      if (
        (Math.abs(client.width - element.offsetWidth) < 21 && rect.screenX < 20) ||
        (Math.abs(client.height - element.offsetHeight) < 21 && rect.screenY < 10)
      ) {
        if (
          Math.abs(element.offsetWidth / 2 + rect.screenX - client.width / 2) < 21 &&
          Math.abs(element.offsetHeight / 2 + rect.screenY - client.height / 2) < 21
        ) {
          return true;
        } else {
          return false;
        }
      } else {
        return false;
      }
    },

    /**
     * 判断元素是否完全全屏显示(宽和高都接近视口)
     * @param {HTMLElement} element - 目标元素
     * @returns {boolean} 是否完全全屏
     */
    isAllFullClient(element) {
      const client = tool.getClient();
      const rect = tool.getRect(element);
      // 宽和高都接近视口,且位置在左上角附近
      if (
        Math.abs(client.width - element.offsetWidth) < 21 &&
        rect.screenX < 20 &&
        Math.abs(client.height - element.offsetHeight) < 21 &&
        rect.screenY < 10
      ) {
        return true;
      } else {
        return false;
      }
    },

    /**
     * 获取页面滚动距离
     * @returns {Object} 包含左右和上下滚动距离的对象
     */
    getScroll() {
      return {
        left: document.documentElement.scrollLeft || document.body.scrollLeft,
        top: document.documentElement.scrollTop || document.body.scrollTop,
      };
    },

    /**
     * 获取视口尺寸
     * @returns {Object} 包含视口宽高的对象
     */
    getClient() {
      return {
        width: document.compatMode == "CSS1Compat" ? document.documentElement.clientWidth : document.body.clientWidth,
        height:
          document.compatMode == "CSS1Compat" ? document.documentElement.clientHeight : document.body.clientHeight,
      };
    },

    /**
     * 向页面添加CSS样式
     * @param {string} css - CSS样式字符串
     * @returns {HTMLElement} 创建的 style 元素
     */
    addStyle(css) {
      const style = document.createElement("style");
      style.className = "maximize-video-style";
      style.appendChild(document.createTextNode(css));
      document.head.appendChild(style);
      return style;
    },

    /**
     * 匹配字符串与规则(支持通配符*)
     * @param {string} str - 要匹配的字符串
     * @param {string} rule - 包含*的规则字符串
     * @returns {boolean} 是否匹配
     */
    matchRule(str, rule) {
      return new RegExp("^" + rule.split("*").join(".*") + "$").test(str);
    },

    /**
     * 创建按钮元素
     * @param {string} id - 按钮id
     * @param {string} title - 按钮提示文本
     * @param {Function} [clickHandler] - 可选的按钮点击事件处理函数
     * @returns {HTMLElement} 创建的按钮元素
     */
    createButton(id, title, clickHandler) {
      const btn = document.createElement("tbdiv");
      btn.id = id;
      btn.title = title; // 设置提示文本

      // 如果提供了点击处理函数,则绑定
      if (typeof clickHandler === "function") {
        btn.onclick = clickHandler;
      }
      document.body.appendChild(btn);
      return btn;
    },

    /**
     * 显示提示信息
     * @param {string} str - 提示文本
     * @returns {Promise} 提示显示完成的Promise
     */
    async addTip(str) {
      if (!document.getElementById("catTip")) {
        const tip = document.createElement("tbdiv");
        tip.id = "catTip";
        tip.innerHTML = str;
        tip.style.cssText =
          'transition: all 0.8s ease-out;background: none repeat scroll 0 0 #27a9d8;color: #FFFFFF;font: 1.1em "微软雅黑";margin-left: -250px;overflow: hidden;padding: 10px;position: fixed;text-align: center;bottom: 100px;z-index: 300;';
        document.body.appendChild(tip);
        tip.style.right = -tip.offsetWidth - 5 + "px";

        // 显示提示动画
        await new Promise((resolve) => {
          tip.style.display = "block";
          setTimeout(() => {
            tip.style.right = "25px";
            resolve("OK");
          }, 300);
        });

        // 停留一段时间
        await new Promise((resolve) => {
          setTimeout(() => {
            tip.style.right = -tip.offsetWidth - 5 + "px";
            resolve("OK");
          }, 3500);
        });

        // 移除提示元素
        await new Promise((resolve) => {
          setTimeout(() => {
            document.body.removeChild(tip);
            resolve("OK");
          }, 1000);
        });
      }
    },

    /**
     * 查找并触发网站原生按钮(全屏/画中画)
     * @param {string} type - 按钮类型,支持 'fullscreen' 或 'pip'
     * @returns {boolean} 是否找到并触发了原生按钮
     */
    triggerNativeButton(type, prompt) {
      const hostname = document.location.hostname;
      // 遍历规则匹配当前域名
      for (let domain in html5Rules) {
        const ruleSet = html5Rules[domain];
        if (
          tool.matchRule(hostname, domain) &&
          ruleSet.hasOwnProperty(type) &&
          Array.isArray(ruleSet[type]) &&
          ruleSet[type].length > 0
        ) {
          for (let selector of ruleSet[type]) {
            const nativeBtn = document.querySelector(selector);
            if (nativeBtn) {
              nativeBtn.click();
              tool.print(`优先使用 ${domain} 的原生${prompt}按钮`);
              return true;
            }
          }
          break;
        }
      }
      return false; // 未找到原生按钮
    },

    /**
     * 节流函数:控制函数在指定时间内最多执行一次
     * @param {Function} fn - 需要节流的函数
     * @param {number} delay - 延迟时间(毫秒),默认100ms
     * @returns {Function} 节流后的函数
     */
    throttle(fn, delay = 100) {
      let lastTime = 0;
      return function (...args) {
        const now = Date.now();
        if (now - lastTime > delay) {
          fn.apply(this, args); // 保持原函数的this和参数
          lastTime = now;
        }
      };
    },

    /**
     * 防抖函数:在事件停止触发一段时间后执行一次
     * @param {Function} fn - 要执行的函数
     * @param {number} delay - 延迟时间(毫秒),默认 300ms
     * @returns {Function} 防抖后的函数
     */
    debounce(fn, delay = 300) {
      let timer = null;
      return function (...args) {
        clearTimeout(timer); // 清除前一次等待
        timer = setTimeout(() => {
          fn.apply(this, args); // 执行最后一次触发
        }, delay);
      };
    },
  };

  // 按钮设置相关方法
  const setButton = {
    /**
     * 初始化按钮
     */
    init() {
      if (!document.getElementById("playerControlBtn")) {
        init();
      }
      // 如果在iframe中且播放器半全屏,向父窗口发送消息
      if (gv.isIframe && tool.isHalfFullClient(gv.player)) {
        window.parent.postMessage("iframeVideo", "*");
        return;
      }
      this.show();
    },

    /**
     * 显示按钮并设置事件监听
     */
    show() {
      // 移除并重新添加鼠标离开事件监听,避免重复绑定
      gv.player.removeEventListener("mouseleave", handle.leavePlayer, false);
      gv.player.addEventListener("mouseleave", handle.leavePlayer, false);

      // 非全屏状态下添加滚动监听,用于修正按钮位置
      if (!gv.isFull) {
        document.removeEventListener("scroll", handle.scrollFix, false);
        document.addEventListener("scroll", handle.scrollFix, false);
      }
      gv.controlBtn.style.display = "block";
      gv.controlBtn.style.visibility = "visible";

      // 支持画中画功能且播放器不是OBJECT/EMBED时显示画中画按钮
      if (document.pictureInPictureEnabled && gv.player.nodeName != "OBJECT" && gv.player.nodeName != "EMBED") {
        gv.picinpicBtn.style.display = "block";
        gv.picinpicBtn.style.visibility = "visible";
      }
      this.locate();
    },

    /**
     * 定位按钮位置(基于播放器位置和用户设置)
     */
    locate() {
      let escapeHTMLPolicy;
      // 处理可信类型(Trusted Types)安全策略
      const hasTrustedTypes = Boolean(window.trustedTypes && window.trustedTypes.createPolicy);
      if (hasTrustedTypes) {
        escapeHTMLPolicy = window.trustedTypes.createPolicy("myEscapePolicy", {
          createHTML: (string, sink) => string,
        });
      }

      const playerRect = tool.getRect(gv.player);
      const client = tool.getClient();

      // 设置按钮样式和位置(根据用户配置)
      gv.controlBtn.style.opacity = "1.0";
      gv.controlBtn.innerHTML = hasTrustedTypes ? escapeHTMLPolicy.createHTML(gv.btnText.max) : gv.btnText.max;

      gv.picinpicBtn.style.opacity = "1.0";
      gv.picinpicBtn.innerHTML = hasTrustedTypes ? escapeHTMLPolicy.createHTML(gv.btnText.pip) : gv.btnText.pip;

      // 根据用户设置的位置放置按钮
      if (gv.btnPosition === "top-left") {
        // 左上角位置
        gv.controlBtn.style.top = playerRect.screenY - gv.controlBtn.offsetHeight + "px";
        gv.controlBtn.style.left = playerRect.screenX + "px";

        gv.picinpicBtn.style.top = gv.controlBtn.style.top;
        gv.picinpicBtn.style.left = playerRect.screenX + gv.controlBtn.offsetWidth + 1 + "px";
      } else {
        // 右上角位置(默认)
        gv.controlBtn.style.top = playerRect.screenY - gv.controlBtn.offsetHeight + "px";
        gv.controlBtn.style.left = playerRect.screenX + gv.player.offsetWidth - gv.controlBtn.offsetWidth + "px";

        gv.picinpicBtn.style.top = gv.controlBtn.style.top;
        gv.picinpicBtn.style.left = parseFloat(gv.controlBtn.style.left) - gv.picinpicBtn.offsetWidth - 1 + "px";
      }
    },
  };

  // 事件处理相关方法
  const handle = {
    /**
     * 获取鼠标悬停的播放器元素(优化版:仅从鼠标路径中筛选)
     * @param {MouseEvent} e - 鼠标事件对象
     */
    getPlayer(e) {
      if (gv.isFull) return;

      gv.mouseoverEl = e.target;
      const hostname = document.location.hostname;
      const elements = document.elementsFromPoint(e.clientX, e.clientY);

      // 1. 优先使用站点特定规则匹配播放器
      for (let i in html5Rules) {
        if (tool.matchRule(hostname, i)) {
          for (let playerSelector of html5Rules[i].player) {
            for (let el of elements) {
              if (el.matches(playerSelector)) {
                gv.player = el;
                setButton.init();
                return;
              }
            }
          }
          break; // 匹配到站点规则后不再继续
        }
      }

      // 2. 使用通用规则匹配
      for (let generalPlayerRule of generalPlayerRules) {
        for (let el of elements) {
          if (el.matches(generalPlayerRule)) {
            gv.player = el;
            setButton.init();
            return;
          }
        }
      }

      // 3. 匹配 video 元素(尺寸足够大)
      for (let el of elements) {
        if (el.nodeName === "VIDEO" && el.offsetWidth > 399 && el.offsetHeight > 220) {
          gv.player = handle.autoCheck(el);
          gv.autoCheckCount = 1;
          setButton.init();
          return;
        }
      }

      // 4. 兜底处理 VIDEO/OBJECT/EMBED(直接目标元素)
      switch (e.target.nodeName) {
        case "VIDEO":
        case "OBJECT":
        case "EMBED":
          if (e.target.offsetWidth > 399 && e.target.offsetHeight > 220) {
            gv.player = e.target;
            setButton.init();
            return;
          }
          break;
      }

      // 5. 未匹配到播放器,清除状态
      handle.leavePlayer();
    },

    /**
     * 自动检测播放器的父容器(寻找与视频尺寸相近的容器)
     * @param {HTMLElement} v - 视频元素
     * @returns {HTMLElement} 最合适的播放器容器
     */
    autoCheck(v) {
      let tempPlayer,
        el = v;
      gv.playerChilds = [];
      gv.playerChilds.push(v);
      // 向上遍历父节点,寻找与视频尺寸相近的容器
      while ((el = el.parentNode)) {
        if (Math.abs(v.offsetWidth - el.offsetWidth) < 15 && Math.abs(v.offsetHeight - el.offsetHeight) < 15) {
          tempPlayer = el;
          gv.playerChilds.push(el);
        } else {
          break;
        }
      }
      return tempPlayer;
    },

    /**
     * 处理鼠标离开播放器的事件(隐藏按钮)
     */
    leavePlayer() {
      if (gv.controlBtn.style.visibility == "visible") {
        gv.controlBtn.style.opacity = "";
        gv.controlBtn.style.visibility = "";
        gv.picinpicBtn.style.opacity = "";
        gv.picinpicBtn.style.visibility = "";
        gv.player.removeEventListener("mouseleave", handle.leavePlayer, false);
        document.removeEventListener("scroll", handle.scrollFix, false);
      }
    },

    /**
     * 处理滚动事件(延迟修正按钮位置,避免频繁触发)
     */
    scrollFix(e) {
      clearTimeout(gv.scrollFixTimer);
      gv.scrollFixTimer = setTimeout(() => {
        setButton.locate();
      }, 20);
    },

    /**
     * 处理键盘快捷键
     * @param {KeyboardEvent} e - 键盘事件对象
     */
    hotKey(e) {
      // ESC键:切换全屏状态(默认)
      if (e.key === "Escape") {
        // 阻止事件传播和默认行为,避免网站监听到ESC键
        e.preventDefault(); // 阻止默认行为(如浏览器默认的ESC行为)
        e.stopPropagation(); // 阻止事件冒泡到父元素
        e.stopImmediatePropagation(); // 阻止当前元素上的其他监听器执行
        maximize.playerControl();
      }
      // F2键:切换画中画(默认)
      if (e.code === "F2") {
        handle.pictureInPicture();
      }
    },

    /**
     * 处理鼠标中键点击事件
     * @param {MouseEvent} e - 鼠标事件对象
     */
    mouseMiddleClick(e) {
      // 中键点击(button=1)时触发
      if (e.button === 1) {
        // const clickedInsidePlayer = gv.player && gv.player.contains(e.target);
        // 改为鼠标穿透
        const elements = document.elementsFromPoint(e.clientX, e.clientY);
        const clickedInsidePlayer = gv.player && elements.includes(gv.player);
        const isFullscreenActive = gv.isFull === true;
        // 如果点击在播放器内,或当前处于网页全屏状态,则触发控制逻辑
        if (clickedInsidePlayer || isFullscreenActive) {
          e.preventDefault(); // 阻止默认行为(如打开链接)
          e.stopPropagation(); // 阻止事件继续传递给视频元素
          maximize.playerControl(); // 切换网页全屏
        }
      }
    },

    /**
     * 处理跨窗口消息
     * @param {MessageEvent} e - 消息事件对象
     */
    async receiveMessage(e) {
      switch (e.data) {
        case "iframePicInPic":
          tool.print("messege:iframePicInPic");
          // 处理iframe中的画中画请求
          if (!document.pictureInPictureElement) {
            await document
              .querySelector("video")
              .requestPictureInPicture()
              .catch((error) => {
                tool.addTip(gv.btnText.tip);
              });
          } else {
            await document.exitPictureInPicture();
          }
          break;
        case "iframeVideo":
          tool.print("messege:iframeVideo");
          // 处理iframe中的视频全屏请求
          if (!gv.isFull) {
            gv.player = gv.mouseoverEl;
            setButton.init();
          }
          break;
        case "parentFull":
          tool.print("messege:parentFull");
          // 处理父窗口的全屏请求
          gv.player = gv.mouseoverEl;
          if (gv.isIframe) {
            window.parent.postMessage("parentFull", "*");
          }
          maximize.checkParent();
          maximize.fullWin();
          // 修正特定播放器的位置
          if (getComputedStyle(gv.player).left != "0px") {
            tool.addStyle(
              "#htmlToothbrush #bodyToothbrush .playerToothbrush {left:0px !important;width:100vw !important;}"
            );
          }
          gv.isFull = true;
          break;
        case "parentSmall":
          tool.print("messege:parentSmall");
          // 处理父窗口的退出全屏请求
          if (gv.isIframe) {
            window.parent.postMessage("parentSmall", "*");
          }
          maximize.smallWin();
          break;
        case "innerFull":
          tool.print("messege:innerFull");
          // 处理iframe内部的全屏请求
          if (gv.player.nodeName == "IFRAME") {
            gv.player.contentWindow.postMessage("innerFull", "*");
          }
          maximize.checkParent();
          maximize.fullWin();
          break;
        case "innerSmall":
          tool.print("messege:innerSmall");
          // 处理iframe内部的退出全屏请求
          if (gv.player.nodeName == "IFRAME") {
            gv.player.contentWindow.postMessage("innerSmall", "*");
          }
          maximize.smallWin();
          break;
      }
    },

    /**
     * 处理画中画功能切换
     */
    pictureInPicture() {
      // 优先使用网站原生画中画按钮(调用通用函数)
      if (tool.triggerNativeButton("pip", "画中画")) {
        return; // 找到并触发后终止
      }

      // 原有画中画逻辑
      if (!document.pictureInPictureElement) {
        if (gv.player) {
          if (gv.player.nodeName == "IFRAME") {
            // 向iframe发送画中画请求
            gv.player.contentWindow.postMessage("iframePicInPic", "*");
          } else {
            // 直接请求画中画
            gv.player.parentNode.querySelector("video").requestPictureInPicture();
          }
        } else {
          // 没有指定播放器时使用第一个video元素
          document.querySelector("video").requestPictureInPicture();
        }
      } else {
        // 退出画中画
        document.exitPictureInPicture();
      }
    },

    /**
     * 显示右键菜单
     * @param {MouseEvent} e - 鼠标事件对象
     */
    showContextMenu(e) {
      e.preventDefault();
      // 先移除已存在的菜单
      handle.hideContextMenu();

      // 创建菜单元素
      gv.contextMenu = document.createElement("div");
      gv.contextMenu.id = "btnContextMenu";

      // 添加菜单项
      const menuTitle = document.createElement("div");
      menuTitle.textContent = gv.btnText.menuTitle;
      gv.contextMenu.appendChild(menuTitle);

      // 左上角选项
      const topLeftItem = document.createElement("div");
      topLeftItem.textContent = gv.btnText.topLeft;
      topLeftItem.addEventListener("click", () => {
        handle.setButtonPosition("top-left");
        handle.hideContextMenu();
      });
      gv.contextMenu.appendChild(topLeftItem);

      // 右上角选项
      const topRightItem = document.createElement("div");
      topRightItem.textContent = gv.btnText.topRight;
      topRightItem.addEventListener("click", () => {
        handle.setButtonPosition("top-right");
        handle.hideContextMenu();
      });
      gv.contextMenu.appendChild(topRightItem);

      document.body.appendChild(gv.contextMenu);

      // 计算菜单位置,确保在视口内
      const client = tool.getClient();
      const menuRect = gv.contextMenu.getBoundingClientRect();

      // 初始位置在鼠标下方
      let left = e.clientX;
      let top = e.clientY;

      // 边界检测 - 水平方向
      if (left + menuRect.width > client.width) {
        left = client.width - menuRect.width;
      }

      // 边界检测 - 垂直方向
      if (top + menuRect.height > client.height) {
        top = client.height - menuRect.height;
      }

      // 应用位置
      gv.contextMenu.style.left = left + "px";
      gv.contextMenu.style.top = top + "px";

      // 点击页面其他地方关闭菜单
      document.addEventListener("click", handle.hideContextMenuOnce);
    },

    /**
     * 隐藏右键菜单
     */
    hideContextMenu() {
      if (gv.contextMenu && gv.contextMenu.parentNode) {
        document.body.removeChild(gv.contextMenu);
        gv.contextMenu = null;
      }
    },

    /**
     * 一次性隐藏菜单的事件处理
     */
    hideContextMenuOnce() {
      handle.hideContextMenu();
      document.removeEventListener("click", handle.hideContextMenuOnce);
    },

    /**
     * 设置按钮位置并保存
     * @param {string} position - 位置值 ('top-left' 或 'top-right')
     */
    setButtonPosition(position) {
      gv.btnPosition = position;
      GM_setValue("buttonPosition", position);
      setButton.locate(); // 重新定位按钮
    },
  };

  // 网页全屏相关方法
  const maximize = {
    /**
     * 播放器控制(切换全屏/退出全屏)
     */
    playerControl() {
      if (!gv.player) {
        return;
      }

      // 优先使用网站原生全屏按钮(调用通用函数)
      if (!gv.useCssFullscreen && tool.triggerNativeButton("fullscreen", "网页全屏")) {
        gv.isFull = !gv.isFull; // 更新状态
        return;
      }

      // 原有全屏逻辑(只有未使用网站按钮时才会执行)
      this.checkParent();
      if (!gv.isFull) {
        // 进入全屏
        if (gv.isIframe) {
          window.parent.postMessage("parentFull", "*");
        }
        if (gv.player.nodeName == "IFRAME") {
          gv.player.contentWindow.postMessage("innerFull", "*");
        }
        this.fullWin();

        // 自动调整播放器容器(最多尝试10次)
        if (gv.autoCheckCount > 0 && !tool.isHalfFullClient(gv.playerChilds[0])) {
          if (gv.autoCheckCount > 10) {
            for (let v of gv.playerChilds) {
              v.classList.add("videoToothbrush");
            }
            return;
          }
          const tempPlayer = handle.autoCheck(gv.playerChilds[0]);
          gv.autoCheckCount++;
          maximize.playerControl();
          gv.player = tempPlayer;
          maximize.playerControl();
        } else {
          gv.autoCheckCount = 0;
        }
      } else {
        // 退出全屏
        if (gv.isIframe) {
          window.parent.postMessage("parentSmall", "*");
        }
        if (gv.player.nodeName == "IFRAME") {
          gv.player.contentWindow.postMessage("innerSmall", "*");
        }
        this.smallWin();
      }
    },

    /**
     * 记录播放器的父元素链(用于退出全屏时恢复)
     */
    checkParent() {
      if (gv.isFull) {
        return;
      }
      gv.playerParents = [];
      let full = gv.player;
      // 遍历父节点直到body
      while ((full = full.parentNode)) {
        if (full.nodeName == "BODY") {
          break;
        }
        if (full.getAttribute) {
          gv.playerParents.push(full);
        }
      }
    },

    /**
     * 进入全屏状态
     */
    fullWin() {
      if (!gv.isFull) {
        // 移除鼠标悬停监听(全屏状态不需要)
        document.removeEventListener("mouseover", handle.getPlayer, false);
        // 保存原始id(用于恢复)
        gv.backHtmlId = document.body.parentNode.id;
        gv.backBodyId = document.body.id;
        // 保存当前滚动位置
        gv.scrollTop = tool.getScroll().top;
        gv.scrollLeft = tool.getScroll().left;
        // 显示辅助按钮
        gv.leftBtn.style.display = "block";
        gv.rightBtn.style.display = "block";
        gv.picinpicBtn.style.display = "";
        gv.controlBtn.style.display = "";
        // 添加全屏相关样式类
        this.addClass();
        const hostname = document.location.hostname;

        // YouTube特殊处理:切换剧院模式
        if (hostname.includes("www.youtube.com")) {
          const flexy = document.querySelector("#page-manager > ytd-watch-flexy");
          // 是否处于剧院模式
          const isTheaterMode =
            flexy && getComputedStyle(flexy).getPropertyValue("--ytd-watch-flexy-chat-max-height").trim() === "460px";
          // 不是剧院模式就自动进入宽屏模式
          if (!isTheaterMode) {
            document.querySelector("#movie_player .ytp-size-button").click();
            gv.ytbStageChange = true;
          }
        }
      }
      gv.isFull = true;
      gv.useCssFullscreen = true;
    },

    /**
     * 为全屏状态添加样式类
     */
    addClass() {
      // 修改根元素id(用于CSS选择器定位)
      document.body.parentNode.id = "htmlToothbrush";
      document.body.id = "bodyToothbrush";
      // 为父元素添加样式类
      for (let v of gv.playerParents) {
        v.classList.add("parentToothbrush");
        // 处理fixed定位的父元素(避免层级问题)
        if (getComputedStyle(v).position == "fixed") {
          v.classList.add("absoluteToothbrush");
        }
      }
      // 为播放器添加样式类
      gv.player.classList.add("playerToothbrush");
      // 确保video元素显示控件
      if (gv.player.nodeName == "VIDEO") {
        gv.backControls = gv.player.controls;
        gv.player.controls = true;
      }
      // 触发resize事件(刷新播放器尺寸)
      window.dispatchEvent(new Event("resize"));
    },

    /**
     * 退出全屏状态(恢复原始状态)
     */
    smallWin() {
      // 恢复原始id
      document.body.parentNode.id = gv.backHtmlId;
      document.body.id = gv.backBodyId;
      // 恢复滚动位置
      window.scrollTo(gv.scrollLeft, gv.scrollTop);
      // 移除父元素的样式类
      for (let v of gv.playerParents) {
        v.classList.remove("parentToothbrush");
        v.classList.remove("absoluteToothbrush");
      }
      // 移除播放器的样式类
      gv.player.classList.remove("playerToothbrush");

      // YouTube特殊处理:恢复剧院模式
      if (document.location.hostname == "www.youtube.com" && gv.ytbStageChange) {
        document.querySelector("#movie_player .ytp-size-button").click();
        gv.ytbStageChange = false;
      }

      // 恢复video控件状态
      if (gv.player.nodeName == "VIDEO") {
        gv.player.controls = gv.backControls;
      }
      // 隐藏辅助按钮
      gv.leftBtn.style.display = "";
      gv.rightBtn.style.display = "";
      gv.controlBtn.style.display = "";
      // 恢复鼠标悬停监听
      document.addEventListener("mouseover", handle.getPlayer, false);
      // 触发resize事件
      window.dispatchEvent(new Event("resize"));
      gv.isFull = false;
      gv.useCssFullscreen = false;
    },
  };

  /**
   * 初始化脚本
   */
  const init = () => {
    // 创建画中画按钮
    gv.picinpicBtn = tool.createButton("picinpicBtn", gv.btnText.pipTooltip, () => {
      handle.pictureInPicture();
    });
    gv.picinpicBtn.addEventListener("contextmenu", handle.showContextMenu); // 添加右键菜单事件

    // 创建网页全屏按钮
    gv.controlBtn = tool.createButton("playerControlBtn", gv.btnText.maxTooltip, () => {
      maximize.playerControl();
    });
    gv.controlBtn.addEventListener("contextmenu", handle.showContextMenu); // 添加右键菜单事件

    // 创建左侧边缘退出按钮
    gv.leftBtn = tool.createButton("leftFullStackButton", "", () => {
      maximize.playerControl();
    });
    // 创建右侧边缘退出按钮
    gv.rightBtn = tool.createButton("rightFullStackButton", "", () => {
      maximize.playerControl();
    });

    // 确保全局样式只添加一次
    if (getComputedStyle(gv.controlBtn).position != "fixed") {
      tool.addStyle(`
/* 针对B站播放器的特殊样式 */
#htmlToothbrush #bodyToothbrush .parentToothbrush .bilibili-player-video {
  margin: 0 !important;
}

/* 全屏状态下禁止页面滚动 */
#htmlToothbrush,
#bodyToothbrush {
  overflow: hidden !important;
  zoom: 100% !important;
  opacity: 1 !important;
}

/* 父元素清障样式 */
#htmlToothbrush #bodyToothbrush .parentToothbrush {
  /* 清除 transform,避免触发包含块影响 fixed 定位 */
  transform: none !important;
  /* 清除滤镜效果,同样会触发包含块 */
  filter: none !important;
  /* 清除 3D 透视属性,避免影响定位上下文 */
  perspective: none !important;
  /* 清除背景滤镜,避免触发新的渲染图层 */
  backdrop-filter: none !important;
  /* 禁止提前优化 transform,防止浏览器创建隔离层 */
  will-change: auto !important;
  /* 禁用布局隔离,避免阻断 fixed 元素继承视口定位 */
  contain: none !important;
  /* 关闭图层隔离,避免 stacking context 干扰 */
  isolation: auto !important;
  /* 禁用 3D transform 的子元素继承,恢复为 2D 渲染 */
  -webkit-transform-style: flat !important;
  /* 清除裁剪路径,避免 fixed 元素被遮挡或裁剪 */
  clip-path: none !important;
  /* 清除遮罩图层,避免 fixed 元素被遮挡 */
  mask: none !important;
  /* 禁用混合模式,防止 fixed 元素与背景发生视觉混合 */
  mix-blend-mode: normal !important;
  /* 恢复默认背面可见性,防止 3D 翻转影响渲染 */
  backface-visibility: visible !important;
  /* 恢复默认层级,避免 stacking context 干扰 fixed 元素 */
  z-index: auto !important;
  /* 恢复默认滚动行为,避免 overscroll 影响 fixed 元素交互 */
  overscroll-behavior: auto !important;
  /* 清除父级透明度,确保子元素视觉不被包裹影响 */
  opacity: 1 !important;
}

/* 修正fixed定位的父元素 */
#htmlToothbrush #bodyToothbrush .absoluteToothbrush {
  position: absolute !important;
}

/* 播放器元素全屏样式 */
#htmlToothbrush #bodyToothbrush .playerToothbrush {
  /* 固定定位,确保相对于视口而不是父元素定位 */
  position: fixed !important;
  /* 铺满整个视口宽高 */
  top: 0 !important;
  left: 0 !important;
  width: 100vw !important;
  height: 100vh !important;
  /* 清除尺寸限制,确保播放器能真正铺满视口 */
  max-width: none !important;
  max-height: none !important;
  min-width: 0 !important;
  min-height: 0 !important;
  /* 清除边距和内边距,避免布局偏移 */
  margin: 0 !important;
  padding: 0 !important;
  /* 设置盒模型为 border-box,避免尺寸计算异常 */
  box-sizing: border-box;
  /* 清除边框,避免视觉干扰 */
  border: none !important;
  /* 设置背景色,防止底层内容透出(可根据主题调整) */
  background-color: black;
  /* 保证在所有元素之上显示(接近最大 z-index) */
  z-index: 2147483646 !important;
  /* 清除自身 transform,避免定位错乱 */
  transform: none !important;
  /* 清除滤镜效果,避免视觉异常 */
  filter: none !important;
  /* 清除 3D 透视影响 */
  perspective: none !important;
  /* 禁止提前优化 transform,避免触发包含块 */
  will-change: auto !important;
  /* 防止播放器被裁剪或遮罩 */
  clip-path: none !important;
  mask: none !important;
  /* 禁用混合模式,确保视觉纯净 */
  mix-blend-mode: normal !important;
  /* 限制布局影响范围,提升渲染性能 */
  contain: strict;
  /* 创建独立图层,避免混合模式污染 */
  isolation: isolate;
  /* 启用硬件加速,提升渲染性能 */
  backface-visibility: hidden;
  /* 禁用动画过渡,避免进入/退出全屏时出现闪烁或延迟 */
  transition: none !important;
  /* 清除圆角,避免触发图层重排或抗锯齿变化 */
  border-radius: 0 !important;
}

/* 视频内容适配 */
#htmlToothbrush #bodyToothbrush .parentToothbrush video {
  object-fit: contain !important;
}

/* 视频元素全屏样式 */
#htmlToothbrush #bodyToothbrush .parentToothbrush .videoToothbrush {
  width: 100vw !important;
  height: 100vh !important;
}

/* 网页全屏按钮和画中画按钮样式 */
#playerControlBtn,
#picinpicBtn {
  /* 布局定位 */
  position: fixed;
  z-index: 2147483647;
  /* 盒模型 */
  display: none;
  width: auto;
  height: 20px;
  margin: 0;
  padding: 0 10px;
  line-height: 20px;
  border-radius: 2px;
  /* 排版样式 */
  text-align: center;
  font-size: 12px;
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
  /* 视觉样式 */
  background-color: rgba(39, 169, 216, 0.7);
  color: rgba(255, 255, 255, 0.95);
  text-shadow: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  backdrop-filter: blur(3px); /* 毛玻璃模糊 */
  -webkit-backdrop-filter: blur(3px); /* Safari 支持 */
  /* 动效与交互 */
  cursor: pointer;
  user-select: none;
  visibility: hidden;
  opacity: 0;
  transition: all 0.5s ease;
}
#playerControlBtn:hover,
#picinpicBtn:hover {
  visibility: visible;
  opacity: 1;
  background-color: rgba(39, 116, 216, 0.7);
}

/* 左右退出全屏按钮样式 */
#leftFullStackButton,
#rightFullStackButton {
  display: none;
  position: fixed;
  width: 1px;
  height: 100vh;
  top: 0;
  z-index: 2147483647;
  background: #000;
}
#leftFullStackButton {
  left: 0;
}
#rightFullStackButton {
  right: 0;
}

/* 右键菜单样式,默认浅色主题 */
#btnContextMenu {
  position: fixed;
  background: rgba(250, 250, 250, 0.72);
  backdrop-filter: blur(24px) saturate(180%);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 14px;
  padding: 6px 0;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.08);
  z-index: 2147483647;
  width: 220px;
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
  font-size: 13px;
}
#btnContextMenu > div:first-child {
  padding: 8px 20px;
  color: rgba(60, 60, 67, 0.6);
  font-weight: 500;
  font-size: 13px;
  border-bottom: 1px solid rgba(60, 60, 67, 0.15);
  cursor: default;
}
#btnContextMenu > div:not(:first-child) {
  padding: 5px 10px;
  cursor: pointer;
  color: #1c1c1e;
  border-radius: 8px;
  transition: background-color 0.2s ease;
}
#btnContextMenu > div:not(:first-child):hover {
  background-color: rgba(0, 0, 0, 0.06);
}
/* 右键菜单深色主题 */
@media (prefers-color-scheme: dark) {
  #btnContextMenu {
    background: rgba(28, 28, 30, 0.72);
    backdrop-filter: blur(24px) saturate(180%);
    -webkit-backdrop-filter: blur(24px) saturate(180%);
    border: 1px solid rgba(255, 255, 255, 0.1);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.32);
    color: rgba(255, 255, 255, 0.85);
  }
  #btnContextMenu > div:first-child {
    color: rgba(235, 235, 245, 0.6);
    border-bottom: 1px solid rgba(84, 84, 88, 0.65);
  }
  #btnContextMenu > div:not(:first-child) {
    color: rgba(255, 255, 255, 0.85);
  }
  #btnContextMenu > div:not(:first-child):hover {
    /* background-color: rgba(255, 255, 255, 0.10); */
    /* 修改为蓝色,为了兼容 Dark Reader */
    background-color: rgba(27, 134, 187, 0.2);
  }
}
`);
    }

    // 添加事件监听
    document.addEventListener("mouseover", tool.debounce(handle.getPlayer), false);
    document.addEventListener("keydown", handle.hotKey, false);
    window.addEventListener("message", handle.receiveMessage, false);
    // 添加鼠标中键点击事件监听
    document.addEventListener("pointerdown", handle.mouseMiddleClick, true);
    // 添加全局右键点击事件,用于关闭菜单
    document.addEventListener("contextmenu", (e) => {
      if (
        gv.contextMenu &&
        !gv.contextMenu.contains(e.target) &&
        e.target.id !== "playerControlBtn" &&
        e.target.id !== "picinpicBtn"
      ) {
        handle.hideContextMenu();
      }
    });

    tool.print("Ready");
  };

  // 初始化脚本
  init();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址