Greasy Fork

Greasy Fork is available in English.

S1 Plus - Stage1st 体验增强套件

为Stage1st论坛提供帖子/用户屏蔽、导航栏自定义、自动签到、阅读进度跟踪、回复收藏、远程同步等多种功能,全方位优化你的论坛体验。

// ==UserScript==
// @name         S1 Plus - Stage1st 体验增强套件
// @namespace    http://tampermonkey.net/
// @version      6.2.0
// @description  为Stage1st论坛提供帖子/用户屏蔽、导航栏自定义、自动签到、阅读进度跟踪、回复收藏、远程同步等多种功能,全方位优化你的论坛体验。
// @author       moekyo
// @match        https://stage1st.com/2b/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @grant        GM_openInTab
// @connect      api.github.com
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  const SCRIPT_VERSION = "6.2.0";
  const SCRIPT_RELEASE_DATE = "2025-09-25";

  // --- [新增] SHA-256 哈希计算库 (基于 Web Crypto API) ---
  /**
   * 计算字符串的 SHA-256 哈希值。
   * @param {string} message - 要计算哈希的字符串。
   * @returns {Promise<string>} 64个字符的十六进制哈希字符串。
   */
  const sha256 = async (message) => {
    // 将消息编码为 Uint8Array
    const msgUint8 = new TextEncoder().encode(message);
    // 使用 subtle.digest 进行哈希计算
    const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8);
    // 将 ArrayBuffer 转换为字节数组
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    // 将字节数组转换为十六进制字符串
    return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
  };

  // --- [新增] 确定性JSON序列化与哈希计算辅助函数 ---

  /**
   * 对对象进行深度排序,确保键的顺序一致,以便生成稳定的哈希值。
   * @param {any} obj 要排序的对象。
   * @returns {any} 键已排序的对象。
   */
  const deterministicSort = (obj) => {
    if (typeof obj !== "object" || obj === null) {
      return obj;
    }
    if (Array.isArray(obj)) {
      return obj.map(deterministicSort);
    }
    const sortedKeys = Object.keys(obj).sort();
    const newObj = {};
    for (const key of sortedKeys) {
      newObj[key] = deterministicSort(obj[key]);
    }
    return newObj;
  };

  // --- [新增] 全局状态标志,用于防止启动时的同步竞态条件 ---
  let isInitialSyncInProgress = false;

  /**
   * 计算数据对象的 SHA-256 哈希值。
   * @param {object} dataObject - 要计算哈希的数据对象 (即 `data` 字段的内容)。
   * @returns {Promise<string>} 计算出的哈希值。
   */
  const calculateDataHash = async (dataObject) => {
    // 1. 深度排序对象键,确保序列化结果的确定性
    const sortedData = deterministicSort(dataObject);
    // 2. 序列化为JSON字符串
    const stringifiedData = JSON.stringify(sortedData);
    // 3. 计算SHA-256哈希
    return await sha256(stringifiedData);
  };
  const SVG_ICON_DELETE_DEFAULT = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23374151'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0' /%3E%3C/svg%3E`;
  const SVG_ICON_DELETE_HOVER = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='white'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0' /%3E%3C/svg%3E`;
  const SVG_ICON_ARROW_MASK = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 16'%3E%3Cpath d='M2 2L8 8L2 14' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E`;

  GM_addStyle(`
    /* --- 通用颜色 --- */
    :root {
      /* -- 基础调色板 -- */
      --s1p-bg: #ecedeb;
      --s1p-pri: #d1d9c1;
      --s1p-sub: #e9ebe8;
      --s1p-white: #ffffff;
      --s1p-black-rgb: 0, 0, 0;

      /* -- [新增] 阴影 -- */
      --s1p-shadow-color-rgb: 0, 0, 0;

      /* -- 主题色 -- */
      --s1p-t: #022c80;
      --s1p-desc-t: #10388a;
      --s1p-sec: #2563eb;
      --s1p-sec-h: #306bebff;
      --s1p-sub-h: #2563eb;
      --s1p-sub-h-t: var(--s1p-white);

      /* -- 状态色 -- */
      --s1p-red: #ef4444;
      --s1p-red-h: #dc2626;
      --s1p-green: #22c55e;
      --s1p-green-h: #28b05aff;
      --s1p-success-bg: #d1fae5;
      --s1p-success-text: #065f46;
      --s1p-error-bg: #fee2e2;

      /* -- 组件专属 -- */
      --s1p-text-empty: #888;
      --s1p-icon-color: #a1a1aa;
      --s1p-icon-close: #9ca3af;
      --s1p-icon-arrow: #6b7280;
      --s1p-confirm-hover-bg: #27da80;
      --s1p-cancel-hover-bg: #ff6464;
      --s1p-secondary-bg: #e5e7eb;
      --s1p-secondary-text: #374151;
      --s1p-code-bg: #eee;
      --s1p-readprogress-bg: #b8d56f;

      --s1p-list-item-status-bg: #dddddd;

      /* -- 阅读进度 -- */
      --s1p-progress-hot: rgb(192, 51, 34);
      --s1p-progress-cold: rgb(107, 114, 128);
    }

    @keyframes s1p-tab-fade-in {
      from {
        opacity: 0;
      }
      to {
        opacity: 1;
      }
    }

    /* --- [FIX] 导航栏垂直居中对齐修正 --- */
    #nv > ul {
      display: flex !important;
      align-items: center !important;
    }

    /* --- [MODIFIED] 手动同步导航按钮 (v5) --- */
    #s1p-nav-sync-btn {
      flex-shrink: 0;
      margin-left: 8px;
    }

    #s1p-nav-sync-btn a {
      display: flex;
      align-items: center;
      justify-content: center;
      height: 100%;
      vertical-align: middle;
      padding: 0 10px;
      box-sizing: border-box;
    }
    #s1p-nav-sync-btn svg {
      width: 16px;
      height: 16px;
      flex-shrink: 0;
      color: var(--s1p-t);
      transition: color 0.3s ease, transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      position: relative;
      top: 1.5px;
    }
    /* [新增] 通过为图标的路径同时应用填充和同色描边,来实现视觉上的“加粗”效果 */
    #s1p-nav-sync-btn svg path {
      stroke: currentColor; /* 描边颜色与填充色(currentColor)一致 */
      stroke-width: 0.6px; /* 描边宽度,可调整此值改变加粗程度 */
      stroke-linejoin: round; /* 让描边的边角更平滑 */
    }
    #s1p-nav-sync-btn a:hover svg {
      color: var(--s1p-t);
      transform: scale(1.1);
    }

    /* --- [核心修复] 针对 S1 NUX 窄屏模式的兼容性适配 --- */
    @media (max-width: 909px) {
      #nv ul #s1p-nav-sync-btn {
        margin-left: 0 !important; /* 移除外边距,解决背景断裂问题 */
      }
      #nv ul #s1p-nav-sync-btn a {
        /* [修改] 增加了左侧内边距,使其与左侧按钮的视觉间距更协调 */
        padding: 0 4px 0 8px !important;
      }
    }

    /* --- [MODIFIED] 最终简化版同步动画 (只有旋转) --- */
    @keyframes s1p-sync-simple-rotate {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(360deg);
      }
    }

    #s1p-nav-sync-btn svg.s1p-syncing {
      animation: s1p-sync-simple-rotate 0.8s linear infinite;
      pointer-events: none;
    }
    #s1p-nav-sync-btn svg.s1p-sync-success {
      /* 移除了所有成功状态的视觉效果 */
    }
    #s1p-nav-sync-btn svg.s1p-sync-error {
      /* 移除了所有失败状态的视觉效果 */
    }

    /* --- 手动同步弹窗样式 --- */
    .s1p-sync-choice-info {
      background-color: var(--s1p-sub);
      border-radius: 6px;
      padding: 12px;
      margin-top: 12px;
      font-size: 13px;
      line-height: 1.7;
    }
    .s1p-sync-choice-info-row {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .s1p-sync-choice-info-label {
      font-weight: 500;
      color: var(--s1p-t);
    }
    .s1p-sync-choice-info-time {
      font-family: monospace, sans-serif;
    }
    .s1p-sync-choice-newer {
      color: var(--s1p-success-text);
      font-weight: bold;
    }

    /* --- [MODIFIED V3] 手动同步对比弹窗样式 (美化版) --- */
    .s1p-sync-last-action {
      font-size: 13px;
      color: var(--s1p-desc-t);
      text-align: center;
      margin: 12px 0 4px 0;
      padding: 8px;
      background-color: var(--s1p-sub);
      border-radius: 6px;
    }
    .s1p-sync-comparison-table {
      margin-top: 16px;
      border: 1px solid var(--s1p-pri);
      border-radius: 8px;
      overflow: hidden;
      font-size: 14px;
    }
    .s1p-sync-comparison-row {
      display: grid;
      grid-template-columns: auto 1fr 1fr; /* <-- [核心修改] 使用 auto 关键字 */
      align-items: center;
      border-bottom: 1px solid var(--s1p-pri);
      transition: background-color 0.2s ease;
    }
    .s1p-sync-comparison-row:last-child {
      border-bottom: none;
    }
    /* 斑马条纹效果 */
    .s1p-sync-comparison-row:nth-child(even) {
      background-color: var(--s1p-sub);
    }
    .s1p-sync-comparison-row:hover {
      background-color: var(--s1p-pri);
    }
    /* 表头样式 */
    .s1p-sync-comparison-header {
      font-weight: 600;
      background-color: var(--s1p-pri) !important;
      color: var(--s1p-t);
      border-bottom: 1px solid var(--s1p-pri);
    }
    .s1p-sync-comparison-header > div {
      padding: 10px 14px;
      text-align: center;
    }
    .s1p-sync-comparison-header > div:first-child {
      text-align: left;
    }
    /* 单元格样式 */
    .s1p-sync-comparison-label,
    .s1p-sync-comparison-value {
      padding: 10px 14px;
    }
    .s1p-sync-comparison-label {
      font-weight: 500;
      color: var(--s1p-t);
      white-space: nowrap;
      font-size: 13px;
    }
    .s1p-sync-comparison-value {
      /* [S1P-FIX] 使用 Flexbox 实现完美的垂直居中对齐 */
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 4px; /* 使用 gap 定义元素间距,比 margin 更现代 */
      font-family: monospace, sans-serif;
      font-size: 13px;
    }
    .s1p-newer-badge {
      /* [S1P-FIX V4] 统一字体以解决跨平台对齐根源问题 */
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        sans-serif;
      color: var(--s1p-success-text);
      font-weight: bold;
      font-size: 12px;
      position: relative;
      /* [S1P-FIX V4] 统一为在 Windows 上视觉对齐的偏移值 */
      top: -1px;
    }
    /* --- [新增] 为手动同步弹窗设定更宽的尺寸 --- */
    .s1p-sync-modal .s1p-confirm-content {
      width: 580px;
    }

    .s1p-progress-update-badge {
      font-size: 12px;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        sans-serif;
      color: var(--s1p-sec);
      font-weight: bold;
      /* [S1P-FIX V4] 统一为在 Windows 上视觉对齐的偏移值 */
      position: relative;
      top: -1px;
      /* margin-left 已被父元素的 gap 替代 */
      /* vertical-align 在 Flexbox 布局中无效 */
    }

    /* --- 提示框样式 --- */
    .s1p-notice {
      display: flex;
      align-items: flex-start;
      gap: 12px;
      background-color: var(--s1p-sub);
      border: 1px solid var(--s1p-pri);
      border-radius: 6px;
      padding: 12px;
      margin-top: 12px;
    }
    .s1p-notice-icon {
      flex-shrink: 0;
      width: 20px;
      height: 20px;
      background-color: var(--s1p-t);
      mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM11 7H13V9H11V7ZM11 11H13V17H11V11Z'%3e%3c/path%3e%3c/svg%3e");
      mask-size: contain;
      mask-position: center;
      mask-repeat: no-repeat;
      margin-top: 1px;
    }
    .s1p-notice-content {
      font-size: 13px;
      line-height: 1.6;
      color: var(--s1p-desc-t);
    }
    .s1p-notice-content a {
      color: var(--s1p-t);
      font-weight: 500;
      text-decoration: none;
    }
    .s1p-notice-content a:hover {
      text-decoration: underline;
    }
    .s1p-notice-content p {
      margin: 4px 0 0 0;
      padding: 0;
    }

    /* --- 滑块式分段控件样式 --- */
    .s1p-segmented-control {
      position: relative;
      display: inline-flex;
      background-color: var(--s1p-sub);
      border-radius: 6px;
      padding: 2px;
      user-select: none;
    }
    .s1p-segmented-control-slider {
      position: absolute;
      top: 2px;
      left: 0;
      height: calc(100% - 4px);
      background-color: var(--s1p-sec);
      border-radius: 5px;
      box-shadow: 0 1px 3px rgba(var(--s1p-shadow-color-rgb), 0.1);
      transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1),
        transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
    }
    .s1p-segmented-control:hover .s1p-segmented-control-slider {
      box-shadow: 0 2px 6px rgba(var(--s1p-shadow-color-rgb), 0.15);
    }
    .s1p-segmented-control-option {
      position: relative;
      z-index: 1;
      padding: 4px 12px;
      color: var(--s1p-desc-t);
      cursor: pointer;
      transition: color 0.25s ease-in-out, background-color 0.2s ease-in-out;
      font-size: 13px;
      line-height: 1.5;
      white-space: nowrap;
      border-radius: 4px;
    }
    .s1p-segmented-control-option.active {
      color: var(--s1p-white);
      font-weight: 500;
      cursor: default;
    }
    .s1p-segmented-control-option:not(.active):hover {
      background-color: var(--s1p-pri);
      color: var(--s1p-t);
    }

    /* --- [S1PLUS-MOD] 帖子列表最终布局修正 --- */
    /* [MODIFIED] 此处已移除隐藏 .icn 和 .icn_new 的样式规则 */
    #atarget,
    a.closeprev.y[title="隐藏置顶帖"] {
      display: none !important;
    }

    /* 2. 为脚本注入的“操作列”及“表头占位符”提供统一、明确的样式 */
    #threadlisttableid .th .s1p-header-placeholder {
      width: 32px !important;
      padding: 0 8px !important;
      box-sizing: border-box !important;
    }
    .s1p-options-cell {
      position: relative;
      text-align: center;
      vertical-align: middle;
      width: 20px !important;
      padding: 0px !important;
      box-sizing: border-box !important;
    }

    /* --- 核心修复与通用布局 --- */
    #p_pop {
      display: none !important;
    }

    /* 1. [核心修正] 精确隐藏作为“空白分隔符”的 separatorline,
        通过 .emptb 类来识别,避免隐藏“版块主题”行。*/
    #separatorline.emptb {
      display: none !important;
    }

    /* 3. 统一所有列的对齐方式为左对齐,以匹配原始样式 */
    #threadlisttableid td.by,
    #threadlisttableid td.num,
    #threadlisttableid .th .by,
    #threadlisttableid .th .num {
      text-align: left !important;
    }

    /* --- 关键字屏蔽样式 --- */
    .s1p-hidden-by-keyword,
    .s1p-hidden-by-quote {
      display: none !important;
    }

    /* --- 按钮通用样式 (V4 - 精致阴影与安全区适配) --- */
    .s1p-btn,
    .s1p-confirm-btn {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      padding: 6px 14px;
      border-radius: 6px;
      background-color: var(--s1p-sub);
      color: var(--s1p-t);
      font-size: 14px;
      font-weight: bold;
      cursor: pointer;
      user-select: none;
      white-space: nowrap;
      border: none;
      /* [MODIFIED] 采用更收敛、精致的默认阴影,并微调Y轴位移 */
      box-shadow: 0 1px 2px rgba(var(--s1p-shadow-color-rgb), 0.12);
      transform: translateY(-1px);
      /* [MODIFIED] 优化过渡动画曲线,使其更平滑 */
      transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    }

    .s1p-btn:hover,
    .s1p-confirm-btn:hover {
      background-color: var(--s1p-sub-h);
      color: var(--s1p-sub-h-t);
      /* [MODIFIED] 同样采用更收敛的悬停阴影,并微调Y轴位移 */
      transform: translateY(-3px);
      box-shadow: 0 3px 6px rgba(var(--s1p-shadow-color-rgb), 0.18);
    }

    /* --- [MODIFIED] 危险/红色按钮样式 - 仅在高亮时变色 --- */

    /* * 注意:原有的 .s1p-red-btn 默认红色样式已被移除。
    * 现在,所有带 .s1p-red-btn 或 .s1p-danger 类的按钮,
    * 在默认状态下都将显示上方 .s1p-btn 定义的通用灰色样式。
    */

    /* [MODIFIED] 将所有危险/红色按钮的变色逻辑统一到 hover 状态 */
    .s1p-btn.s1p-red-btn:hover,
    .s1p-btn.s1p-danger:hover,
    .s1p-confirm-btn.s1p-confirm:hover {
      background-color: var(--s1p-red-h); /* 使用更深的红色作为悬停色 */
      border-color: transparent; /* 确保没有边框颜色 */
      color: var(--s1p-white);
    }

    /* --- 帖子操作按钮 (三点图标) --- */
    .s1p-options-btn {
      position: relative; /* 为 ::after 伪元素提供定位上下文 */
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 18px;
      height: 24px;
      border-radius: 4px;
      cursor: pointer;
      color: var(--s1p-icon-color);
      opacity: 0.4;
      transition: background-color 0.2s ease, color 0.2s ease, opacity 0.2s ease;
    }
    /* [S1PLUS-REPLACE-BLOCK] */
    /* [MODIFIED] 创建一个透明的“交互桥梁”,覆盖按钮和菜单之间的物理间隙 */
    .s1p-options-btn::after {
      content: '';
      position: absolute;
      top: 0;
      right: 100%; /* [S1P-MODIFIED] 从 left 改为 right,适配右侧按钮 */
      width: 2px; /* [核心修改] 大幅缩减桥梁宽度,防止其影响旁边的元素 */
      height: 100%;
    }
    .s1p-options-btn:hover {
      background-color: var(--s1p-pri);
      color: var(--s1p-t);
      opacity: 1;
    }
    .s1p-options-menu {
      position: absolute;
      top: 50%;
      right: 100%; /* [S1P-MODIFIED] 从 left 改为 right,让菜单在按钮左侧弹出 */
      /* [S1P-FIX] 移除 margin-left,将间距改为 padding-right,从而消除按钮和菜单间的交互断层 */
      margin-left: 0;
      transform: translateY(-50%);
      z-index: 10;
      background-color: var(--s1p-bg);
      border-radius: 8px;
      box-shadow: 0 4px 12px rgba(var(--s1p-shadow-color-rgb), 0.1);
      /* [S1P-MODIFIED] 将内边距调整到右侧,以在视觉上保持间距 */
      padding: 5px 11px 5px 5px;
      min-width: 110px;
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
      transition: opacity 0.15s ease-out, visibility 0.15s;
    }
    /* [修改] 将菜单的触发条件改为悬停图标按钮,并让菜单自身在悬停时保持显示 */
    .s1p-options-btn:hover + .s1p-options-menu,
    .s1p-options-menu:hover {
      opacity: 1;
      visibility: visible;
      pointer-events: auto;
    }

    /* --- [MODIFIED] 直接确认UI (整体缩小) --- */
    .s1p-direct-confirm {
      display: flex;
      align-items: center;
      gap: 8px; /* 减小间距 */
      font-size: 13px; /* 减小字体 */
      color: var(--s1p-t);
      padding: 4px 6px; /* 减小内边距 */
      white-space: nowrap;
    }
    .s1p-confirm-separator {
      border-left: 1px solid var(--s1p-pri);
      height: 16px; /* 减小高度 */
      margin: 0 2px 0 6px;
    }
    .s1p-confirm-action-btn {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 28px; /* 减小尺寸 */
      height: 28px; /* 减小尺寸 */
      border: none;
      border-radius: 50%;
      cursor: pointer;
      transition: background-color 0.2s ease, transform 0.1s ease,
        background-image 0.2s ease;
      background-repeat: no-repeat;
      background-position: center;
      background-size: 55%; /* 减小图标大小 */
      flex-shrink: 0;
    }
    .s1p-confirm-action-btn:active {
      transform: scale(0.95);
    }
    .s1p-confirm-action-btn.s1p-confirm {
      background-color: transparent;
      background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='2.5' stroke='%2322c55e'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M4.5 12.75l6 6 9-13.5' /%3e%3c/svg%3e");
    }
    .s1p-confirm-action-btn.s1p-confirm:hover {
      background-color: var(--s1p-confirm-hover-bg);
      background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='2.5' stroke='%23ffffff'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M4.5 12.75l6 6 9-13.5' /%3e%3c/svg%3e");
    }
    .s1p-confirm-action-btn.s1p-cancel {
      background-color: transparent;
      background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='2.5' stroke='%23ef4444'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12' /%3e%3c/svg%3e");
    }
    .s1p-confirm-action-btn.s1p-cancel:hover {
      background-color: var(--s1p-cancel-hover-bg);
      background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='2.5' stroke='%23ffffff'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12' /%3e%3c/svg%3e");
    }

    /* --- 行内确认菜单样式 (带动画) --- */
    .s1p-options-menu.s1p-inline-confirm-menu {
      transform: translateY(0) !important;
      z-index: 10004;
      opacity: 0;
      transform: translateX(-8px) scale(0.95) !important;
      transition: opacity 0.15s ease-out, transform 0.15s ease-out;
      pointer-events: none;
      visibility: visible !important;
      /* [MODIFIED] 将 padding 从 4px 减为 2px */
      padding: 2px;
    }
    .s1p-inline-confirm-menu.visible {
      opacity: 1;
      transform: translateX(0) scale(1) !important;
      pointer-events: auto;
    }

    /* --- [NEW] Inline Action Menu --- */
    .s1p-inline-action-menu {
      position: absolute;
      z-index: 10004;
      display: flex;
      align-items: center;
      gap: 4px;
      background-color: var(--s1p-bg);
      border-radius: 8px;
      box-shadow: 0 4px 12px rgba(var(--s1p-shadow-color-rgb), 0.1);
      padding: 5px;
      opacity: 0;
      visibility: hidden;
      transform: translateY(5px) scale(0.95);
      transition: opacity 0.15s ease-out, transform 0.15s ease-out,
        visibility 0.15s;
      pointer-events: none;
    }
    .s1p-inline-action-menu.visible {
      opacity: 1;
      visibility: visible;
      transform: translateY(0) scale(1);
      pointer-events: auto;
    }
    .s1p-inline-action-menu .s1p-action-btn {
      display: flex;
      align-items: center;
      gap: 5px;
      padding: 5px 10px;
      border-radius: 5px;
      font-size: 14px;
      font-weight: 500;
      color: var(--s1p-t);
      background-color: transparent;
      border: none;
      cursor: pointer;
      transition: background-color 0.2s ease, color 0.2s ease;
    }
    .s1p-inline-action-menu .s1p-action-btn:hover {
      background-color: var(--s1p-sub);
    }

    /* --- [MODIFIED] Icon Styling within Action Buttons --- */
    .s1p-inline-action-menu .s1p-action-btn svg {
      width: 20px;
      height: 20px;
      flex-shrink: 0; /* 防止图标被压缩 */
    }

    /* --- 阅读进度UI样式 --- */
    .s1p-progress-container {
      display: inline-flex;
      align-items: center;
      margin: 0 8px;
      vertical-align: middle;
      line-height: 1;
    }
    .s1p-progress-jump-btn {
      display: inline-flex;
      align-items: center;
      gap: 4px;
      font-size: 12px;
      font-weight: bold;
      text-decoration: none;
      border: 1px solid;
      border-radius: 4px;
      padding: 1px 6px 1px 4px;
      transition: all 0.2s ease-in-out;
      line-height: 1.4;
    }
    .s1p-progress-jump-btn::before {
      content: "";
      display: inline-block;
      width: 1.1em;
      height: 1.1em;
      background-color: currentColor;
      mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3cpath d='M19 15l-6 6-1.42-1.42L15.17 16H4V4h2v10h9.17l-3.59-3.58L13 9l6 6z' fill='black'/%3e%3c/svg%3e");
      mask-size: contain;
      mask-repeat: no-repeat;
      mask-position: center;
    }
    .s1p-new-replies-badge {
      display: inline-block;
      color: var(--s1p-white);
      font-size: 12px;
      font-weight: bold;
      padding: 1px 5px;
      border: 1px solid;
      border-left: none;
      border-radius: 0 4px 4px 0;
      line-height: 1.4;
      user-select: none;
    }

    /* --- 通用输入框样式 --- */
    .s1p-input {
      width: 100%;
      background: var(--s1p-bg);
      border: 1px solid var(--s1p-pri);
      border-radius: 6px;
      padding: 8px 12px;
      font-size: 14px;
      box-sizing: border-box;
      transition: border-color 0.2s ease-in-out,
        background-color 0.2s ease-in-out;
      color: var(--s1p-t);
    }
    .s1p-input:focus {
      outline: none;
      border-color: var(--s1p-sec);
      background-color: var(--s1p-white);
    }
    .s1p-textarea {
      resize: vertical;
      min-height: 80px;
    }

    /* --- [新增] 优化 Windows 下密码输入框样式 --- */
    #s1p-remote-pat-input {
      /* 修正部分 Windows 系统下字体渲染问题 */
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
      /* 增加字符间距,让星号 (*) 显示更清晰 */
      letter-spacing: 1.5px;
    }
    #s1p-remote-pat-input::-ms-reveal {
      /* 隐藏 Windows Edge/IE 浏览器自带的显示密码图标 */
      display: none;
    }

    /* --- 用户标记悬浮窗 --- */
    .s1p-tag-popover {
      position: absolute;
      z-index: 10001;
      width: 300px;
      background-color: var(--s1p-bg);
      border-radius: 12px;
      box-shadow: 0 4px 20px rgba(var(--s1p-shadow-color-rgb), 0.08);
      opacity: 0;
      visibility: hidden;
      transform: translateY(5px) scale(0.98);
      transition: opacity 0.2s ease-out, transform 0.2s ease-out,
        visibility 0.2s;
      pointer-events: none;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
    }
    .s1p-tag-popover.visible {
      opacity: 1;
      visibility: visible;
      transform: translateY(0) scale(1);
      pointer-events: auto;
    }
    .s1p-popover-content {
      padding: 16px;
    }
    .s1p-popover-main-content {
      font-size: 14px;
      line-height: 1.6;
      color: var(--s1p-t);
      padding: 4px 4px 20px 4px;
      min-height: 30px;
      word-wrap: break-word;
      white-space: pre-wrap;
    }
    .s1p-popover-main-content.s1p-empty {
      text-align: center;
      color: var(--s1p-text-empty);
    }
    .s1p-popover-hr {
      border: none;
      border-top: 1px solid var(--s1p-pri);
      margin: 0;
    }
    .s1p-popover-footer {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      padding-top: 16px;
    }
    .s1p-popover-user-container {
      display: flex;
      align-items: center;
      gap: 10px;
      min-width: 0;
    }
    .s1p-popover-avatar {
      width: 36px;
      height: 36px;
      border-radius: 50%;
      object-fit: cover;
      flex-shrink: 0;
      background-color: var(--s1p-pri);
    }
    .s1p-popover-user-info {
      flex-grow: 1;
      min-width: 0;
    }
    .s1p-popover-username {
      font-weight: 500;
      font-size: 14px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .s1p-popover-user-id {
      font-size: 12px;
      color: var(--s1p-desc-t);
      white-space: nowrap;
    }
    .s1p-popover-actions {
      display: flex;
      gap: 8px;
      flex-shrink: 0;
    }
    .s1p-edit-mode-header {
      font-weight: 600;
      font-size: 15px;
      margin-bottom: 12px;
    }
    .s1p-edit-mode-textarea {
      height: 90px;
      margin-bottom: 12px;
    }
    .s1p-edit-mode-actions {
      display: flex;
      justify-content: flex-end;
      gap: 8px;
    }

    /* --- [NEW] 通用显示悬浮窗 --- */
    .s1p-generic-display-popover {
      position: absolute;
      z-index: 10003;
      max-width: 350px;
      background-color: var(--s1p-bg);
      border-radius: 8px;
      box-shadow: 0 2px 10px rgba(var(--s1p-shadow-color-rgb), 0.12);
      padding: 10px 14px;
      font-size: 13px;
      line-height: 1.6;
      color: var(--s1p-t);
      opacity: 0;
      visibility: hidden;
      transform: translateY(5px);
      transition: opacity 0.15s ease-out, transform 0.15s ease-out;
      pointer-events: none;
      white-space: pre-wrap;
      word-wrap: break-word;
    }
    .s1p-generic-display-popover.visible {
      opacity: 1;
      visibility: visible;
      transform: translateY(0);
    }
    /* --- [ULTIMATE FIX V2.1] Flexbox Layout Fix (Ultimate Version) --- */
    .pi {
      display: flex !important;
      align-items: center;
      gap: 8px;
      position: relative !important; /* <-- [新增] 为绝对定位的子元素提供定位上下文 */
    }
    .pi > .pti {
      min-width: 0;
      position: static !important;
      z-index: auto !important;
      order: 1;
      overflow: hidden;
    }
    /* [新增] 布局伸缩器,永久固定右侧元素 */
    .s1p-layout-spacer {
      margin-left: auto;
      order: 2;
    }
    .pi > strong {
      flex-shrink: 0;
      order: 4;
      visibility: hidden; /* <-- 核心修改:初始不可见 */
      transition: visibility 0s; /* 确保状态改变是瞬时的 */
    }
    .pi > strong.s1p-layout-ready {
      visibility: visible; /* <-- 脚本添加此类后立即显示 */
    }
    .pi > #fj {
      margin-left: 0;
      order: 5;
      flex-shrink: 0;
      position: static !important;
      z-index: auto !important;
    }
    .authi {
      display: flex !important;
      align-items: center;
      flex-wrap: nowrap;
      overflow: hidden;
      white-space: nowrap !important;
    }

    /* 1. 最外层容器: flex布局,这是基础 */
    .s1p-authi-container {
      display: flex;
      align-items: center;
      min-width: 0;
    }

    /* 2. 原生按钮容器: 绝对不允许被压缩 */
    .s1p-authi-container > .authi {
      flex-shrink: 0;
      white-space: nowrap;
    }

    /* 3. 脚本按钮总容器: 作为被压缩的主要对象,内部强制不换行 */
    .s1p-authi-container > .s1p-authi-actions-wrapper {
      display: flex;
      align-items: center;
      flex-shrink: 1;
      min-width: 0;
      flex-wrap: nowrap;
    }

    /* 4. [已修正冲突] 脚本容器 *内部* 元素的精确规则: */

    /* a) 用户标记容器(.s1p-user-tag-container): 这是唯一允许被压缩的元素 */
    .s1p-authi-actions-wrapper > .s1p-user-tag-container {
      flex-shrink: 1;
      min-width: 30px;
    }

    /* b) 其他所有按钮(<a>)和分隔符(<span>): 绝对不允许被压缩 */
    .s1p-authi-actions-wrapper > a.s1p-authi-action,
    .s1p-authi-actions-wrapper > span.pipe {
      flex-shrink: 0;
    }

    .s1p-authi-actions-wrapper {
      display: inline-flex;
      align-items: center;
      min-width: 0;
      vertical-align: middle;
    }
    .s1p-user-tag-container {
      display: inline-flex;
      align-items: center;
      flex-shrink: 1;
      min-width: 30px;
      vertical-align: middle;
      overflow: hidden;
      border-radius: 6px;
    }
    .s1p-user-tag-display {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      display: block;
      background-color: var(--s1p-sub);
      color: var(--s1p-t);
      padding: 2px 8px;
      font-size: 12px;
      cursor: default;
    }
    .s1p-user-tag-options {
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: var(--s1p-sub);
      color: var(--s1p-t);
      padding: 0 8px;
      flex-shrink: 0;
      align-self: stretch;
      cursor: pointer;
      transition: background-color 0.2s ease-in-out;
    }
    .s1p-user-tag-options:hover {
      background-color: var(--s1p-pri);
    }

    /* --- [MODIFIED] Tag Options Menu (高度比例调整) --- */
    .s1p-tag-options-menu {
      position: absolute;
      z-index: 10002;
      background-color: var(--s1p-bg);
      border-radius: 6px;
      box-shadow: 0 2px 8px rgba(var(--s1p-shadow-color-rgb), 0.15);
      padding: 4px;
      display: flex;
      flex-direction: column;
      gap: 2px;
      min-width: max-content;
    }
    .s1p-tag-options-menu button {
      background: none;
      border: none;
      /* [MODIFIED] 增加垂直内边距,以达到目标高度 */
      padding: 8px 10px;
      text-align: left;
      cursor: pointer;
      border-radius: 4px;
      font-size: 13px;
      color: var(--s1p-t);
      white-space: nowrap;
      line-height: 1.5;
    }
    .s1p-tag-options-menu button:hover {
      background-color: var(--s1p-sub-h);
      color: var(--s1p-sub-h-t);
    }
    .s1p-tag-options-menu button.s1p-delete:hover {
      background-color: var(--s1p-red);
      color: var(--s1p-white);
    }

    /* --- 设置面板样式 --- */
    .s1p-modal {
      display: flex;
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(var(--s1p-black-rgb), 0.5);
      justify-content: center;
      align-items: center;
      z-index: 9999;
    }
    .s1p-modal-content {
      background-color: var(--s1p-bg);
      border-radius: 8px;
      box-shadow: 0 4px 6px rgba(var(--s1p-shadow-color-rgb), 0.1);
      width: 600px;
      max-width: 90%;
      max-height: 80vh;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      position: relative;

      /* [优化] 为设置面板设定统一的字体族和渲染方式 */
      font-family: -apple-system, "BlinkMacSystemFont", "Segoe UI", "Roboto",
        "Helvetica Neue", "Arial", "PingFang SC", "Hiragino Sans GB",
        "Microsoft YaHei", sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
    .s1p-modal-header {
      background: var(--s1p-pri);
      padding: 16px;
      border-bottom: 1px solid var(--s1p-pri);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .s1p-modal-title {
      /* [优化 V2] 微调主标题尺寸 */
      font-size: 20px;
      font-weight: 600;
      letter-spacing: -0.5px;
    }
    .s1p-modal-close {
      width: 12px;
      height: 12px;
      cursor: pointer;
      color: var(--s1p-icon-close);
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 2L14 14M14 2L2 14' stroke='currentColor' stroke-width='2.5' stroke-linecap='round'/%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: center;
      background-size: contain;
      transition: color 0.2s ease-in-out, transform 0.2s ease-in-out;
      transform: rotate(0deg);
    }
    .s1p-modal-close:hover {
      color: var(--s1p-red);
      transform: rotate(90deg);
    }
    .s1p-modal-body {
      padding: 8px 16px 16px;
      overflow-y: auto;
      flex-grow: 1;
      /* --- [调试新增] --- */
      transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1);
      /* 暂时移除 flex-grow 以便手动控制高度 */
      flex-grow: 0;
    }
    .s1p-modal-footer {
      /* [MODIFIED] 增加上下内边距,为按钮阴影提供空间 */
      padding: 16px;
      border-top: 1px solid var(--s1p-pri);
      text-align: right;
      font-size: 12px;
    }

    /* --- [OPTIMIZED] 设置面板Tabs样式 (Pill / 滑块样式) --- */
    .s1p-tabs {
      position: relative;
      display: inline-flex; /* 使容器宽度自适应内容 */
      background-color: var(--s1p-sub);
      border-radius: 6px;
      padding: 4px;
      margin-bottom: 16px;
    }
    .s1p-tab-slider {
      position: absolute;
      top: 4px;
      left: 0;
      height: calc(100% - 8px);
      background-color: var(--s1p-white);
      border-radius: 4px;
      box-shadow: 0 1px 2px rgba(var(--s1p-shadow-color-rgb), 0.1);
    }
    .s1p-tab-content.active {
      display: block;
    }
    .s1p-tab-btn {
      position: relative;
      z-index: 1;
      padding: 6px 16px;
      cursor: pointer;
      border: none;
      background-color: transparent;
      font-size: 14px;
      font-weight: 500;
      color: var(--s1p-desc-t);
      white-space: nowrap;
      border-radius: 4px;
      /* [修改] 增加 background-color 的过渡动画 */
      transition: color 0.25s ease, background-color 0.2s ease;
    }
    .s1p-tab-btn:hover:not(.active) {
      color: var(--s1p-t);
      /* [新增] 添加背景色高亮效果 */
      background-color: var(--s1p-pri);
    }
    .s1p-tab-btn.active {
      color: var(--s1p-t);
      cursor: default;
    }
    .s1p-tab-content {
      display: none;
      padding-top: 0;
    }

    .s1p-tabs-wrapper {
      display: flex;
      justify-content: center;
    }
    .s1p-empty {
      text-align: center;
      padding: 24px;
      color: var(--s1p-desc-t);
    }
    .s1p-list {
      display: flex;
      flex-direction: column;
      gap: 8px;
    }
    .s1p-item {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      padding: 12px;
      border-radius: 6px;
      background-color: var(--s1p-bg);
      border: 1px solid var(--s1p-pri);
    }
    .s1p-item-info {
      flex-grow: 1;
      min-width: 0;
    }
    .s1p-item-title {
      /* [优化] 统一列表项标题样式 */
      font-size: 15px;
      font-weight: 500;
      margin-bottom: 4px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .s1p-item-meta {
      /* [优化] 统一列表项元信息样式 */
      font-size: 12px;
      color: var(--s1p-desc-t);
    }
    .s1p-item-toggle {
      font-size: 12px;
      color: var(--s1p-desc-t);
      display: flex;
      align-items: center;
      gap: 8px;
    }
    .s1p-item-toggle input {
      /* Handled by .s1p-switch */
    }
    .s1p-unblock-btn:hover {
      background-color: var(--s1p-red-h);
    }
    .s1p-sync-title {
      /* [优化] 调整同步标题的样式,与分组标题统一 */
      font-size: 16px;
      font-weight: 600;
      margin-bottom: 8px;
    }
    .s1p-local-sync-desc {
      /* [优化] 调整描述文字样式,提升可读性 */
      font-size: 13px;
      color: var(--s1p-desc-t);
      margin-bottom: 16px;
      line-height: 1.7;
    }
    .s1p-local-sync-buttons {
      display: flex;
      gap: 8px;
      margin-bottom: 20px;
    }
    .s1p-sync-textarea {
      width: 100%;
      min-height: 80px;
      margin-bottom: 8px;
    }
    /* --- [新增] 论坛黑名单同步状态提示 --- */
    .s1p-native-sync-status {
      display: inline-block;
      background-color: var(--s1p-list-item-status-bg);
      color: var(--s1p-t);
      padding: 2px 8px;
      border-radius: 7px;
      font-style: normal;
      font-weight: 500;
      font-size: 11px;
      margin-left: 8px;
      vertical-align: middle;
      position: relative;
      top: -1px;
    }
    /* --- [OPTIMIZED] Sync Settings Panel Disabled State --- */
    #s1p-remote-sync-controls-wrapper {
      transition: opacity 0.3s ease-out;
    }
    #s1p-remote-sync-controls-wrapper.is-disabled {
      opacity: 0.5;
      pointer-events: none;
    }

    /* --- 悬浮提示框 (Toast Notification) --- */
    @keyframes s1p-toast-shake {
      10%,
      90% {
        transform: translate(-51%, 0);
      }
      20%,
      80% {
        transform: translate(-49%, 0);
      }
      30%,
      50%,
      70% {
        transform: translate(-52%, 0);
      }
      40%,
      60% {
        transform: translate(-48%, 0);
      }
    }
    .s1p-toast-notification {
      position: fixed;
      left: 50%;
      bottom: 20px;
      transform: translate(-50%, 50px);
      z-index: 10005;
      padding: 10px 18px;
      border-radius: 6px;
      font-size: 14px;
      font-weight: 500;
      color: var(--s1p-t); /* <-- 修改了此行 */
      background-color: var(--s1p-bg); /* <-- 修改了此行 */
      box-shadow: 0 4px 12px rgba(var(--s1p-shadow-color-rgb), 0.15);
      opacity: 0;
      transition: opacity 0.3s ease-out, transform 0.3s ease-out;
      pointer-events: none;
      white-space: nowrap;
      text-align: center;
    }
    .s1p-modal-content .s1p-toast-notification {
      position: absolute;
      bottom: 15px;
    }
    .s1p-toast-notification.visible {
      opacity: 1;
      transform: translate(-50%, 0);
    }
    .s1p-toast-notification.success {
      background-color: #27da80;
      color: var(--s1p-white); /* 确保成功状态字体为白色 */
      border-color: #27da80; /* 覆盖边框颜色 */
    }
    .s1p-toast-notification.error {
      background-color: var(--s1p-red);
      color: var(--s1p-white); /* 确保失败状态字体为白色 */
      border-color: var(--s1p-red); /* 覆盖边框颜色 */
    }
    .s1p-toast-notification.error.visible {
      animation: s1p-toast-shake 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
    }

    /* --- 确认弹窗样式 --- */
    @keyframes s1p-fade-in {
      from {
        opacity: 0;
      }
      to {
        opacity: 1;
      }
    }
    @keyframes s1p-scale-in {
      from {
        transform: scale(0.95);
        opacity: 0;
      }
      to {
        transform: scale(1);
        opacity: 1;
      }
    }
    @keyframes s1p-fade-out {
      from {
        opacity: 1;
      }
      to {
        opacity: 0;
      }
    }
    @keyframes s1p-scale-out {
      from {
        transform: scale(1);
        opacity: 1;
      }
      to {
        transform: scale(0.97);
        opacity: 0;
      }
    }
    .s1p-confirm-modal {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(var(--s1p-black-rgb), 0.65);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 10000;
      animation: s1p-fade-in 0.2s ease-out;
    }
    .s1p-confirm-content {
      background-color: var(--s1p-bg);
      border-radius: 12px;
      box-shadow: 0 10px 25px -5px rgba(var(--s1p-shadow-color-rgb), 0.1),
        0 10px 10px -5px rgba(var(--s1p-shadow-color-rgb), 0.04);
      width: 480px;
      max-width: 90%;
      text-align: left;
      overflow: hidden;
      animation: s1p-scale-in 0.25s ease-out;
    }
    .s1p-confirm-body {
      padding: 20px 24px;
      font-size: 16px;
      line-height: 1.6;
    }
    .s1p-confirm-body .s1p-confirm-title {
      font-weight: 600;
      font-size: 18px;
      margin-bottom: 8px;
    }
    .s1p-confirm-body .s1p-confirm-subtitle {
      font-size: 14px;
      color: var(--s1p-desc-t);
    }
    .s1p-confirm-footer {
      padding: 12px 24px 20px;
      display: flex;
      justify-content: flex-end;
      gap: 12px;
    }
    .s1p-confirm-footer.s1p-centered {
      justify-content: center;
    }

    /* --- [MODIFIED] Collapsible Section (V7 - Mask Image Fix) --- */
    .s1p-collapsible-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      cursor: pointer;
      user-select: none;
    }
    .s1p-settings-group-title.s1p-collapsible-header {
      margin-bottom: 0;
      transition: color 0.2s ease;
    }
    .s1p-settings-group-title.s1p-collapsible-header:hover {
      color: var(--s1p-sec);
    }
    .s1p-expander-arrow {
      display: inline-block;
      width: 12px;
      height: 12px;
      /* [NEW METHOD] 使用 mask 定义图标形状 */
      -webkit-mask-image: url("${SVG_ICON_ARROW_MASK}");
      mask-image: url("${SVG_ICON_ARROW_MASK}");
      -webkit-mask-size: contain;
      mask-size: contain;
      -webkit-mask-repeat: no-repeat;
      mask-repeat: no-repeat;
      -webkit-mask-position: center;
      mask-position: center;
      /* [NEW METHOD] 使用 background-color 来上色 */
      background-color: var(--s1p-icon-arrow); /* 默认颜色 */
      /* [NEW METHOD] 为 background-color 和 transform 添加过渡效果 */
      transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
        background-color 0.2s ease;
    }
    .s1p-settings-group-title.s1p-collapsible-header:hover .s1p-expander-arrow {
      background-color: var(--s1p-sec); /* 悬停颜色 */
    }
    .s1p-expander-arrow.expanded {
      transform: rotate(90deg);
    }
    .s1p-collapsible-content {
      display: grid;
      grid-template-rows: 0fr;
      padding-top: 0;
      transition: grid-template-rows 0.4s cubic-bezier(0.4, 0, 0.2, 1),
        padding-top 0.4s cubic-bezier(0.4, 0, 0.2, 1);
    }
    .s1p-collapsible-content > div {
      overflow: hidden;
    }
    .s1p-collapsible-content.expanded {
      grid-template-rows: 1fr;
      padding-top: 12px;
    }

    /* --- Feature Content Animation --- */
    .s1p-feature-content {
      display: grid;
      grid-template-rows: 0fr;
      /* [调试] 暂时禁用这里的动画,以便测试我们自己的 height 动画 */
      transition: none;
      margin-top: 0;
    }
    .s1p-feature-content.expanded {
      grid-template-rows: 1fr;
      margin-top: 0px;
    }
    .s1p-feature-content > div {
      overflow: hidden;
      /* [调试] 暂时禁用这里的动画 */
      transition: none;
    }
    .s1p-feature-content:not(.expanded) > div {
      opacity: 0;
      transition-duration: 0s;
    }
    .s1p-feature-content.expanded > div {
      opacity: 1;
      transition-delay: 0s;
    }

    /* --- 界面定制设置样式 --- */
    .s1p-settings-group {
      // margin-bottom: 0;
      padding: 8px;
    }
    .s1p-settings-group-title {
      /* [优化 V2] 调整次级分组标题,拉开层级 */
      font-size: 15px;
      font-weight: 500;
      border-bottom: 1px solid var(--s1p-pri);
      padding-bottom: 12px;
      margin-bottom: 16px;
    }

    /* --- [修改] 统一的设置子选项分组缩进样式 --- */
    .s1p-settings-sub-group {
      padding-left: 20px;
      border-left: none;
      margin-left: 8px;
      margin-top: 8px;
      transition: all 0.3s ease;
    }

    .s1p-settings-item {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 8px 0;
    }
    .s1p-settings-item .s1p-input {
      width: auto;
      min-width: 200px;
    }
    .s1p-settings-label {
      /* [优化] 设定设置项标签的基础样式 */
      font-size: 14px;
      font-weight: 500;
    }
    .s1p-settings-section-title-label {
      /* [优化 V2] 调整主要分组标题,响应用户反馈 */
      font-size: 16px;
      font-weight: 600;
    }
    .s1p-settings-checkbox {
      /* Handled by .s1p-switch */
    }
    .s1p-setting-desc {
      /* [优化] 统一描述文字的样式,提升可读性 */
      font-size: 13px;
      color: var(--s1p-desc-t);
      margin: -4px 0 12px 0;
      padding: 0;
      line-height: 1.7;
    }
    /* --- [新增] 警告文本样式 --- */
    .s1p-warning-text {
      font-weight: bold;
      color: var(--s1p-red);
    }
    .s1p-editor-item {
      display: grid;
      grid-template-columns: auto 1fr auto;
      gap: 8px;
      align-items: center;
      padding: 6px;
      border-radius: 4px;
      background: var(--s1p-bg);
    }
    .s1p-editor-item select {
      background: var(--s1p-bg);
      width: 100%;
      border: 1px solid var(--s1p-pri);
      border-radius: 4px;
      padding: 6px 8px;
      font-size: 14px;
      box-sizing: border-box;
    }
    .s1p-editor-item-controls {
      display: flex;
      align-items: center;
      gap: 4px;
    }
    .s1p-editor-btn {
      padding: 4px;
      font-size: 18px;
      line-height: 1;
      cursor: pointer;
      border-radius: 4px;
      border: none;
      background: transparent;
      color: #9ca3af;
    }
    /* --- [修正] 使用 :not() 伪类来排除删除按钮,防止其悬浮样式被覆盖 --- */
    .s1p-editor-btn:not(.s1p-delete-button):hover {
      background: var(--s1p-secondary-bg);
      color: var(--s1p-secondary-text);
    }
    /* --- [新增] 为删除按钮定义统一的尺寸和边距 (骨架) --- */
    .s1p-editor-btn.s1p-delete-button {
      width: 26px;
      height: 26px;
      padding: 4px;
      box-sizing: border-box;
      font-size: 0;
    }
    /* --- [修改] 固化S1 Plus经典删除按钮样式 --- */
    .s1p-editor-btn.s1p-delete-button {
      font-size: 0 !important;
      width: 26px !important;
      height: 26px !important;
      padding: 4px !important;
      box-sizing: border-box !important;
      background-image: url("${SVG_ICON_DELETE_DEFAULT}") !important;
      background-repeat: no-repeat !important;
      background-position: center !important;
      background-size: 18px 18px !important;
      background-color: transparent !important;
      mask: none !important;
      transition: all 0.2s ease;
    }
    .s1p-editor-btn.s1p-delete-button:hover {
      background-color: var(--s1p-red) !important;
      background-image: url("${SVG_ICON_DELETE_HOVER}") !important;
    }
    .s1p-drag-handle {
      font-size: 18pt;
      cursor: grab;
    }
    .s1p-editor-footer {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 12px;
    }

    /* --- 带图标的搜索框 --- */
    .s1p-search-input-wrapper {
      position: relative;
      display: flex;
      align-items: center;
      width: 100%;
    }
    .s1p-search-input-wrapper .s1p-input {
      padding-left: 34px;
      padding-right: 34px;
    }
    .s1p-search-input-wrapper svg.s1p-search-icon {
      position: absolute;
      left: 10px;
      top: 50%;
      transform: translateY(-50%);
      width: 16px;
      height: 16px;
      color: var(--s1p-icon-color);
      pointer-events: none;
      transition: color 0.2s ease-in-out;
    }
    .s1p-search-input-wrapper .s1p-input:focus + svg.s1p-search-icon {
      color: var(--s1p-sec);
    }

    /* --- 搜索框清空按钮 --- */
    .s1p-search-clear-btn {
      position: absolute;
      right: 10px;
      top: 50%;
      transform: translateY(-50%);
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: transparent;
      border: none;
      border-radius: 50%;
      cursor: pointer;
      opacity: 1;
      transition: background-color 0.2s ease, opacity 0.2s ease,
        transform 0.2s ease;
      padding: 0;
    }
    .s1p-search-clear-btn.hidden {
      opacity: 0;
      pointer-events: none;
      transform: translateY(-50%) scale(0.8);
    }
    .s1p-search-clear-btn:hover {
      background-color: var(--s1p-pri);
    }
    .s1p-search-clear-btn svg {
      width: 12px;
      height: 12px;
      color: var(--s1p-icon-arrow);
    }

    /* --- 搜索关键词高亮 --- */
    mark.s1p-highlight {
      background-color: var(--s1p-pri);
      color: var(--s1p-t);
      font-weight: bold;
      padding: 1px 3px;
      border-radius: 3px;
      text-decoration: none;
    }

    /* --- Modern Toggle Switch --- */
    .s1p-switch {
      position: relative;
      display: inline-block;
      width: 40px;
      height: 22px;
      vertical-align: middle;
      flex-shrink: 0;
    }
    .s1p-switch input {
      opacity: 0;
      width: 0;
      height: 0;
    }
    .s1p-slider {
      position: absolute;
      cursor: pointer;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: var(--s1p-pri);
      transition: 0.3s;
      border-radius: 22px;
    }
    .s1p-slider:before {
      position: absolute;
      content: "";
      height: 16px;
      width: 16px;
      left: 3px;
      bottom: 3px;
      background-color: var(--s1p-white);
      transition: 0.3s;
      border-radius: 50%;
      box-shadow: 0 1px 3px rgba(var(--s1p-shadow-color-rgb), 0.1);
    }
    input:checked + .s1p-slider {
      background-color: var(--s1p-sec);
    }
    input:checked + .s1p-slider:before {
      transform: translateX(18px);
    }

    /* --- Nav Editor Dragging --- */
    .s1p-editor-item.s1p-dragging {
      opacity: 0.5;
    }

    /* --- 用户标记设置面板专属样式 --- */
    .s1p-item-meta-id {
      font-family: monospace;
      background-color: var(--s1p-bg);
      padding: 1px 5px;
      border-radius: 4px;
      font-size: 11px;
      color: var(--s1p-t);
    }
    .s1p-item-content {
      /* [优化] 调整列表项正文内容的样式 */
      margin-top: 10px;
      padding-top: 10px;
      border-top: 1px solid var(--s1p-pri);
      color: var(--s1p-t);
      font-size: 14px;
      font-weight: 400; /* 使用常规字重 */
      line-height: 1.6;
      white-space: pre-wrap;
      word-break: break-all;
    }
    /* --- [微调] 统一调整用户标记和回复收藏列表的内容区样式 --- */
    #s1p-tags-list-container .s1p-item-content,
    #s1p-bookmarks-list-container .s1p-item-content {
      font-size: 14px;
    }
    #s1p-tab-bookmarks .s1p-item-content {
      background-color: transparent;
      border: none;
      border-bottom: 1px solid var(--s1p-pri);
      border-radius: 0;
      padding: 0 0 10px 0;
      margin-top: 0;
      margin-bottom: 10px;
    }
    .s1p-item-editor textarea {
      width: 100%;
      min-height: 60px;
      margin-top: 8px;
    }
    .s1p-item-actions {
      display: flex;
      align-self: flex-start;
      flex-shrink: 0;
      gap: 8px;
      margin-left: 16px;
    }
    .s1p-item-actions .s1p-btn.s1p-primary {
      background-color: #3b82f6;
      color: var(--s1p-white);
    }
    .s1p-item-actions .s1p-btn.s1p-primary:hover {
      background-color: #2563eb;
    }

    /* [MODIFIED] 移除了 .s1p-item-actions .s1p-btn.s1p-danger 的默认红色背景规则 */

    /* [MODIFIED] 将危险按钮的红色样式只应用在 hover 状态 */
    .s1p-item-actions .s1p-btn.s1p-danger:hover {
      background-color: var(--s1p-red-h);
      border-color: transparent; /* 再次确保边框透明 */
      color: var(--s1p-white);
    }

    /* --- 引用屏蔽占位符 --- */
    .s1p-quote-placeholder {
      background-color: var(--s1p-bg);
      border: 1px solid var(--s1p-pri);
      padding: 8px 12px;
      border-radius: 6px;
      margin: 10px 0;
      font-size: 13px;
      color: var(--s1p-desc-t);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    div.s1p-quote-placeholder span.s1p-quote-toggle {
      color: var(--s1p-t);
      font-weight: 500;
      cursor: pointer;
      padding: 4px 8px;
      border-radius: 4px;
      transition: background-color 0.2s ease, color 0.2s ease;
    }
    div.s1p-quote-placeholder span.s1p-quote-toggle:hover {
      background-color: var(--s1p-sub);
      color: var(--s1p-t);
    }
    .s1p-quote-wrapper {
      overflow: hidden;
      transition: max-height 0.35s ease-in-out;
    }

    /* --- [新增] 通知/提醒屏蔽样式 --- */
    .s1p-notification-placeholder {
      background-color: var(--s1p-bg);
      border: 1px solid var(--s1p-pri);
      padding: 8px 12px;
      border-radius: 6px;
      margin-top: 10px;
      font-size: 13px;
      color: var(--s1p-desc-t);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .s1p-notification-placeholder .s1p-notification-toggle {
      color: var(--s1p-t);
      font-weight: 500;
      cursor: pointer;
      padding: 4px 8px;
      border-radius: 4px;
      transition: background-color 0.2s ease, color 0.2s ease;
    }
    .s1p-notification-placeholder .s1p-notification-toggle:hover {
      background-color: var(--s1p-sub);
      color: var(--s1p-t);
    }
    .s1p-notification-wrapper {
      overflow: hidden;
      transition: max-height 0.35s ease-in-out;
      margin-bottom: 10px;
    }

    /* --- Image Hiding --- */
    .s1p-image-container {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      gap: 8px;
      margin: 8px 0;
    }
    .s1p-image-placeholder {
      display: inline-flex;
      align-items: center;
      /* gap: 6px; */ /* [S1P-MOD] 移除gap,因为没有图标 */
      padding: 6px 14px; /* [S1P-MOD] 匹配 s1p-btn 内边距 */
      border-radius: 6px;
      background-color: var(--s1p-pri);
      color: var(--s1p-t);
      border: none; /* [S1P-MOD] 匹配 s1p-btn 边框 */
      cursor: pointer;
      font-size: 14px; /* [S1P-MOD] 匹配 s1p-btn 字体大小 */
      font-weight: bold; /* [S1P-MOD] 匹配 s1p-btn 字体粗细 */
      transition: all 0.2s ease;
      /* [S1P-MOD] 模拟 s1p-btn 的其他属性 */
      justify-content: center;
      user-select: none;
      white-space: nowrap;
    }
    .s1p-image-placeholder:hover {
      background-color: var(--s1p-sub-h);
      color: var(--s1p-sub-h-t);
      /* [S1P-MOD] 移除 border-color,因为 border: none */
    }
    .s1p-image-container.hidden > .zoom {
      display: none;
    }
    .s1p-image-toggle-all-container {
      margin-bottom: 10px;
    }
    .s1p-image-toggle-all-btn {
      display: inline-flex;
      align-items: center;
      /* gap: 6px; */ /* [S1P-MOD] 移除gap,因为没有图标 */
      padding: 6px 14px; /* [S1P-MOD] 匹配 s1p-btn 内边距 */
      border-radius: 6px;
      background-color: var(--s1p-pri);
      color: var(--s1p-t);
      border: none; /* [S1P-MOD] 匹配 s1p-btn 边框 */
      cursor: pointer;
      font-size: 14px; /* [S1P-MOD] 匹配 s1p-btn 字体大小 */
      font-weight: bold; /* [S1P-MOD] 匹配 s1p-btn 字体粗细 */
      transition: all 0.2s ease;
      /* [S1P-MOD] 模拟 s1p-btn 的其他属性 */
      justify-content: center;
      user-select: none;
      white-space: nowrap;
    }
    .s1p-image-toggle-all-btn:hover {
      background-color: var(--s1p-sub-h);
      color: var(--s1p-sub-h-t);
      /* [S1P-MOD] 移除 border-color,因为 border: none */
    }

    /* --- [新增 V9] 可禁用/启用的增强型悬浮控件 --- */
    /* --- 模式 B: 增强控件关闭 (默认状态) --- */
    /* 默认隐藏脚本创建的控件 */
    #s1p-floating-controls-wrapper {
      display: none;
    }

    /* --- 模式 A: 增强控件开启 (当 body 有 s1p-enhanced-controls-active 时) --- */
    /* 1. 让脚本控件显示出来 */
    body.s1p-enhanced-controls-active #s1p-floating-controls-wrapper {
      display: block;
    }
    /* 2. 同时,彻底隐藏原生控件 */
    body.s1p-enhanced-controls-active #scrolltop {
      display: none !important;
    }

    /* 3. 以下是脚本控件自身的样式 (与之前版本类似,但选择器更严谨) */
    #s1p-floating-controls-wrapper {
      position: fixed;
      top: 50%;
      right: 10px;
      transform: translateY(-50%);
      z-index: 9998;
      pointer-events: none; /* <--- 把这一行加在这里 */
    }
    #s1p-controls-handle {
      position: absolute;
      top: 50%;
      right: -10px;
      transform: translateY(-50%);
      width: 20px;
      height: 40px;
      background-color: var(--s1p-bg);
      border-right: none;
      border-radius: 10px 0 0 10px;
      box-shadow: -2px 2px 8px rgba(var(--s1p-shadow-color-rgb), 0.1);
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: opacity 0.3s ease 0.1s;
      pointer-events: auto; /* [新增] 唯独让手柄可以响应鼠标 */
    }
    #s1p-controls-handle::before {
      content: "";
      display: block;
      width: 4px;
      height: 16px;
      background-color: var(--s1p-icon-color);
      -webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 16' fill='currentColor'%3e%3ccircle cx='2' cy='2' r='1.5'/%3e%3ccircle cx='2' cy='8' r='1.5'/%3e%3ccircle cx='2' cy='14' r='1.5'/%3e%3c/svg%3e");
      mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 16' fill='currentColor'%3e%3ccircle cx='2' cy='2' r='1.5'/%3e%3ccircle cx='2' cy='8' r='1.5'/%3e%3ccircle cx='2' cy='14' r='1.5'/%3e%3c/svg%3e");
      -webkit-mask-size: contain;
      mask-size: contain;
      -webkit-mask-repeat: no-repeat;
      mask-repeat: no-repeat;
      -webkit-mask-position: center;
      mask-position: center;
    }
    #s1p-floating-controls {
      transform: translateX(100%);
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
      display: flex;
      flex-direction: column;
      gap: 8px;
      /* [S1PLUS-MODIFIED] 将左侧内边距设为一个 >20px 的值,从而制造按钮和屏幕边缘的间距 */
      padding-left: 35px;
      transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease,
        visibility 0.3s;
    }
    #s1p-floating-controls-wrapper:hover #s1p-controls-handle {
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s ease;
    }
    #s1p-floating-controls-wrapper:hover #s1p-floating-controls {
      transform: translateX(0);
      opacity: 1;
      visibility: visible;
      pointer-events: auto; /* [修改] 之前是auto,现在明确写出以确保逻辑完整 */
    }
    #s1p-floating-controls a {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 40px;
      height: 40px;
      border-radius: 50%;
      background-color: var(--s1p-bg);
      box-shadow: 0 2px 8px rgba(var(--s1p-shadow-color-rgb), 0.15);
      transition: all 0.2s ease-in-out;
      padding: 0;
      box-sizing: border-box;
    }
    #s1p-floating-controls a:hover {
      background-color: var(--s1p-pri);
      transform: scale(1.1);
      fill-opacity: 1;
    }
    #s1p-floating-controls a svg {
      width: 18px;
      height: 18px;
      color: var(--s1p-t);
      fill-opacity: 0.5;
    }
    #s1p-floating-controls a svg:hover {
      fill-opacity: 1;
    }
    #s1p-floating-controls a.s1p-scroll-btn svg {
      width: 22px;
      height: 22px;
      fill-opacity: 0.5;
    }
    #s1p-floating-controls a.s1p-scroll-btn svg:hover {
      fill-opacity: 1;
    }
    /* --- [更新] 回复收藏内容切换 V3 --- */
    .s1p-bookmark-preview,
    .s1p-bookmark-full {
      /* 保留换行和空格,确保纯文本格式正确显示 */
      white-space: pre-wrap;
      word-break: break-word;
    }
    .s1p-bookmark-full {
      display: none;
    }
    .s1p-bookmark-toggle {
      /* 保持为行内元素,自然跟在文字后方 */
      display: inline;
      margin-left: 4px; /* 与文字稍微隔开 */
      font-weight: 500;
      color: var(--s1p-t);
      cursor: pointer;
      text-decoration: none;
      font-size: 13px;
    }
    .s1p-bookmark-toggle:hover {
      color: var(--s1p-sec);
      text-decoration: underline;
    }

    /* --- [修改] 收藏夹内帖子跳转链接样式 --- */
    .s1p-bookmark-meta-line {
      display: flex;
      align-items: center;
      gap: 4px;
      margin-top: 8px;
    }
    .s1p-bookmark-meta-line > span {
      flex-shrink: 0;
      white-space: nowrap;
    }
    .s1p-bookmark-thread-link {
      display: inline-flex;
      align-items: center;
      gap: 5px;
      text-decoration: none;
      font-weight: 500;
      color: var(--s1p-desc-t);
      transition: color 0.2s ease;
      min-width: 0; /* 核心修复:允许此弹性项目收缩至小于其内容宽度 */
    }
    .s1p-bookmark-thread-link:hover {
      color: var(--s1p-sec);
      text-decoration: underline;
    }
    .s1p-bookmark-thread-link svg {
      width: 14px;
      height: 14px;
      flex-shrink: 0; /* 防止图标被压缩 */
    }
    .s1p-bookmark-title-text {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* --- [修改] 阅读进度提示条 - 渐变动画 --- */
    @keyframes s1p-indicator-fade-in {
      from {
        opacity: 0;
        /* 保持垂直居中,并从一个轻微的放大状态恢复,使出现动画更柔和 */
        transform: translateY(-50%) scale(1.05);
      }
      to {
        opacity: 1;
        transform: translateY(-50%) scale(1);
      }
    }
    @keyframes s1p-indicator-fade-out {
      from {
        opacity: 1;
        transform: translateY(-50%) scale(1);
      }
      to {
        opacity: 0;
        /* 消失时轻微缩小,使其更自然 */
        transform: translateY(-50%) scale(0.95);
      }
    }
    .s1p-read-indicator.s1p-anim-appear {
      /* 使用新的 fade-in 动画,时长0.3秒 */
      animation: s1p-indicator-fade-in 0.3s ease-out forwards;
    }
    .s1p-read-indicator.s1p-anim-disappear {
      /* 使用新的 fade-out 动画,时长0.25秒 */
      animation: s1p-indicator-fade-out 0.25s ease-in forwards;
    }

    .s1p-read-indicator {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      display: inline-flex;
      align-items: center;
      gap: 4px;
      background-color: var(--s1p-readprogress-bg);
      color: var(--s1p-white);
      padding: 3px 8px 4px; /* <-- [修改] 使用非对称内边距修正视觉重心 */
      border-radius: 16px;
      font-size: 11px;
      font-weight: bold;
      user-select: none;
      white-space: nowrap;
      box-shadow: 0 0 4px rgba(var(--s1p-shadow-color-rgb), 0.1);
      line-height: 1;
    }
    .s1p-read-indicator-icon {
      width: 14px; /* <-- [核心修改] 减小图标宽度 */
      height: 14px; /* <-- [核心修改] 减小图标高度 */
      background-color: currentColor;
      mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M2 3.9934C2 3.44476 2.45531 3 2.9918 3H21.0082C21.556 3 22 3.44495 22 3.9934V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934ZM11 5H4V19H11V5ZM13 5V19H20V5H13ZM14 7H19V9H14V7ZM14 10H19V12H14V10Z'%3e%3c/path%3e%3c/svg%3e");
      mask-size: contain;
      mask-repeat: no-repeat;
      mask-position: center;
      /* -- [新增] 针对Windows平台的垂直对齐微调 -- */
      position: relative;
      top: 1px;
    }
    /* --- [新增] S1 NUX 推荐弹窗按钮专属样式 --- */
    .s1p-nux-recommend-modal .s1p-confirm-btn.s1p-confirm:hover {
      background-color: var(--s1p-sub-h);
      color: var(--s1p-white);
      border-color: transparent;
    }
    .s1p-nux-recommend-modal .s1p-confirm-btn.s1p-cancel:hover {
      background-color: var(--s1p-red-h);
      color: var(--s1p-white);
      border-color: transparent;
    }

    /* --- [新增] 深色模式样式覆写 --- */
    @media (prefers-color-scheme: dark) {
      :root {
        /* 将阅读进度条背景改为更柔和的橄榄绿 */
        --s1p-readprogress-bg: #99a17a;
        --s1p-list-item-status-bg: #3e3d3d;
      }

      /* 删除按钮:默认状态下使用白色图标 */
      .s1p-editor-btn.s1p-delete-button {
        background-image: url("${SVG_ICON_DELETE_HOVER}") !important;
      }

      /* --- [新增] 深色模式阴影适配 --- */
      .s1p-segmented-control-slider {
        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
      }
      .s1p-segmented-control:hover .s1p-segmented-control-slider {
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
      }
      .s1p-btn,
      .s1p-confirm-btn {
        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3),
          inset 0 1px 0 rgba(255, 255, 255, 0.05);
      }
      .s1p-btn:hover,
      .s1p-confirm-btn:hover {
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4),
          inset 0 1px 0 rgba(255, 255, 255, 0.07);
      }
      .s1p-options-menu,
      .s1p-inline-action-menu {
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
      }
      .s1p-tag-popover {
        box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
      }
      .s1p-generic-display-popover {
        box-shadow: 0 3px 12px rgba(0, 0, 0, 0.35);
      }
      .s1p-tag-options-menu {
        box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
      }
      .s1p-modal-content {
        box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
      }
      .s1p-tab-slider {
        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
      }
      .s1p-toast-notification {
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
      }
      .s1p-confirm-content {
        box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.3);
      }
      .s1p-slider:before {
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
      }
      .s1p-read-indicator {
        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
      }
      #s1p-controls-handle {
        box-shadow: -2px 2px 8px rgba(0, 0, 0, 0.25);
      }
      #s1p-floating-controls a {
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
      }

      .s1p-toast-notification {
        background-color: #303030;
        // color: var(--s1p-bg);
      }
      .s1p-toast-notification.success {
        /* 使用一个更柔和的绿色 */
        background-color: var(--s1p-green-h);
      }
      .s1p-toast-notification.error {
        /* 使用一个更深的红色 */
        background-color: var(--s1p-red-h);
      }

      /* --- [新增] 深色模式下更柔和的警告文本颜色 --- */
      .s1p-warning-text {
        color: #f87171; /* A softer, less jarring red for dark backgrounds */
      }
    }
  `);

  // --- S1 NUX 兼容性检测 ---
  let isS1NuxEnabled = false;
  const detectS1Nux = () => {
    const archiverLink = document.querySelector('a[href*="archiver"]');
    if (archiverLink) {
      const style = window.getComputedStyle(archiverLink, "::before");
      if (style && style.content.includes("NUXISENABLED")) {
        console.log("S1 Plus: S1 NUX is enabled");
        isS1NuxEnabled = true;
      } else {
        console.log("S1 Plus: S1 NUX is not enabled");
      }
    }
  };

  let dynamicallyHiddenThreads = {};

  // [MODIFIED] 为远程推送增加防抖机制,并防止与初始同步冲突
  let remotePushTimeout;
  const debouncedTriggerRemoteSyncPush = () => {
    // [OPTIMIZATION] 如果初始同步正在进行,则跳过由数据变更触发的推送
    if (isInitialSyncInProgress) {
      console.log("S1 Plus: 初始同步进行中,已跳过本次自动推送请求。");
      return;
    }

    const settings = getSettings();
    // [MODIFIED] 增加对自动同步子开关的判断
    if (
      !settings.syncRemoteEnabled ||
      !settings.syncAutoEnabled ||
      !settings.syncRemoteGistId ||
      !settings.syncRemotePat
    ) {
      return;
    }
    clearTimeout(remotePushTimeout);
    // 延迟5秒推送,如果在5秒内有新的数据变动,则重新计时
    remotePushTimeout = setTimeout(() => {
      triggerRemoteSyncPush();
    }, 5000);
  };

  // 只有在数据实际变动时才更新时间戳并触发同步
  const updateLastModifiedTimestamp = () => {
    // 如果初始同步正在进行,则阻止更新时间戳,以防因数据迁移导致错误的覆盖。
    if (isInitialSyncInProgress) {
      console.log(
        "S1 Plus: 同步进行中,已阻止本次 last_modified 时间戳更新,以防数据覆盖。"
      );
      return;
    }
    GM_setValue("s1p_last_modified", Date.now());
    debouncedTriggerRemoteSyncPush();
  };

  // [NEW] 更新上次同步时间的显示
  const updateLastSyncTimeDisplay = () => {
    const container = document.querySelector("#s1p-last-sync-time-container");
    if (!container) return;
    const lastSyncTs = GM_getValue("s1p_last_sync_timestamp", 0);
    if (lastSyncTs > 0) {
      container.textContent = `上次成功同步于: ${new Date(
        lastSyncTs
      ).toLocaleString("zh-CN", { hour12: false })}`;
    } else {
      container.textContent = "尚未进行过远程同步。";
    }
  };

  /**
   * 从当前页面获取 formhash,用于安全验证。
   * @returns {string|null} 成功则返回 formhash 字符串,否则返回 null。
   */
  const getFormhash = () => {
    const formhashInput = document.querySelector('input[name="formhash"]');
    if (formhashInput && formhashInput.value) {
      return formhashInput.value;
    }
    console.error("S1 Plus: 未能获取到 formhash,无法同步论坛黑名单。");
    return null;
  };

  /**
   * 异步将指定用户添加到论坛黑名单。
   * @param {string} username - 要屏蔽的用户名。
   * @param {string} formhash - 安全验证令牌。
   * @returns {Promise<void>}
   */
  const addToNativeBlacklist = (username, formhash) => {
    return new Promise((resolve, reject) => {
      const url = "home.php?mod=spacecp&ac=friend&op=blacklist&start=";
      const postData = `username=${encodeURIComponent(
        username
      )}&formhash=${formhash}&blacklistsubmit_btn=true&blacklistsubmit=true`;
      const refererUrl =
        "https://stage1st.com/2b/home.php?mod=spacecp&ac=friend&op=blacklist";

      GM_xmlhttpRequest({
        method: "POST",
        url: url,
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          Referer: refererUrl,
        },
        data: postData,
        onload: (response) => {
          if (
            response.status === 200 &&
            response.responseText.includes("操作成功")
          ) {
            console.log(`S1 Plus: 已成功将用户 ${username} 同步到论坛黑名单。`);
            resolve();
          } else {
            console.error(`S1 Plus: 同步论坛黑名单失败 (添加)。`, response);
            reject(
              new Error(`HTTP status ${response.status} 或未找到成功标识`)
            );
          }
        },
        onerror: (error) => {
          console.error("S1 Plus: 同步论坛黑名单网络请求失败 (添加)", error);
          reject(error);
        },
      });
    });
  };

  /**
   * 异步将指定用户从论坛黑名单中移除。
   * @param {string} uid - 要取消屏蔽的用户ID。
   * @param {string} formhash - 安全验证令牌。
   * @returns {Promise<void>}
   */
  const removeFromNativeBlacklist = (uid, formhash) => {
    return new Promise((resolve, reject) => {
      // 根据用户提供的网络日志,精确模拟请求
      const url = `home.php?mod=spacecp&ac=friend&op=blacklist&subop=delete&uid=${uid}`;
      const refererUrl = `https://stage1st.com/2b/home.php?mod=space&do=friend&view=blacklist`;

      GM_xmlhttpRequest({
        method: "GET",
        url: url,
        headers: {
          Referer: refererUrl,
        },
        onload: (response) => {
          // [最终修正] 采用用户发现的通用成功标识 "操作成功"
          if (
            response.status === 200 &&
            response.responseText.includes("操作成功")
          ) {
            console.log(`S1 Plus: 已成功将 UID:${uid} 从论坛黑名单同步移除。`);
            resolve();
          } else {
            console.error(`S1 Plus: 同步论坛黑名单失败 (移除)。`, response);
            reject(
              new Error(`HTTP status ${response.status} 或未找到成功标识`)
            );
          }
        },
        onerror: (error) => {
          console.error("S1 Plus: 同步论坛黑名单网络请求失败 (移除)", error);
          reject(error);
        },
      });
    });
  };

  // --- 数据处理 & 核心功能 ---
  const getBlockedThreads = () => GM_getValue("s1p_blocked_threads", {});
  const saveBlockedThreads = (threads) => {
    GM_setValue("s1p_blocked_threads", threads);
    updateLastModifiedTimestamp();
  };
  const getBlockedUsers = () => GM_getValue("s1p_blocked_users", {});
  const saveBlockedUsers = (users) => {
    GM_setValue("s1p_blocked_users", users);
    updateLastModifiedTimestamp();
  };
  const saveUserTags = (tags) => {
    GM_setValue("s1p_user_tags", tags);
    updateLastModifiedTimestamp();
  };
  // [NEW] Bookmarked Replies data functions
  const getBookmarkedReplies = () => GM_getValue("s1p_bookmarked_replies", {});
  const saveBookmarkedReplies = (replies) => {
    GM_setValue("s1p_bookmarked_replies", replies);
    updateLastModifiedTimestamp();
  };

  // [MODIFIED] 升级并获取用户标记,自动迁移旧数据
  const getUserTags = () => {
    const tags = GM_getValue("s1p_user_tags", {});
    let needsMigration = false;
    const migratedTags = { ...tags };

    Object.keys(migratedTags).forEach((id) => {
      if (typeof migratedTags[id] === "string" || !migratedTags[id].timestamp) {
        needsMigration = true;
        const oldTag =
          typeof migratedTags[id] === "string"
            ? migratedTags[id]
            : migratedTags[id].tag;
        const oldName =
          migratedTags[id] && migratedTags[id].name
            ? migratedTags[id].name
            : `用户 #${id}`;
        migratedTags[id] = {
          name: oldName,
          tag: oldTag,
          timestamp:
            (migratedTags[id] && migratedTags[id].timestamp) || Date.now(),
        };
      }
    });

    if (needsMigration) {
      console.log("S1 Plus: 正在将用户标记迁移到新版数据结构...");
      saveUserTags(migratedTags);
      return migratedTags;
    }

    return tags;
  };

  const getTitleFilterRules = () => {
    const rules = GM_getValue("s1p_title_filter_rules", null);
    if (rules !== null) return rules;

    // --- 向下兼容:迁移旧的关键字数据 ---
    const oldKeywords = GM_getValue("s1p_title_keywords", null);
    if (Array.isArray(oldKeywords)) {
      const newRules = oldKeywords.map((k) => ({
        pattern: k,
        enabled: true,
        id: `rule_${Date.now()}_${Math.random()}`,
      }));
      saveTitleFilterRules(newRules);
      GM_setValue("s1p_title_keywords", null); // 清理旧数据
      return newRules;
    }
    return [];
  };
  const saveTitleFilterRules = (rules) => {
    GM_setValue("s1p_title_filter_rules", rules);
    updateLastModifiedTimestamp();
  };

  const blockThread = (id, title, reason = "manual") => {
    const b = getBlockedThreads();
    if (b[id]) return;
    b[id] = { title, timestamp: Date.now(), reason };
    saveBlockedThreads(b);
    hideThread(id);
  };
  const unblockThread = (id) => {
    const b = getBlockedThreads();
    delete b[id];
    saveBlockedThreads(b);
    showThread(id);
  };
  const hideThread = (id) => {
    (
      document.getElementById(`normalthread_${id}`) ||
      document.getElementById(`stickthread_${id}`)
    )?.setAttribute("style", "display: none !important");
  };
  const showThread = (id) => {
    (
      document.getElementById(`normalthread_${id}`) ||
      document.getElementById(`stickthread_${id}`)
    )?.removeAttribute("style");
  };
  const hideBlockedThreads = () =>
    Object.keys(getBlockedThreads()).forEach(hideThread);

  const blockUser = async (id, name) => {
    // [优化] 函数变为异步并返回布尔值
    const settings = getSettings();
    const b = getBlockedUsers();
    b[id] = {
      name,
      timestamp: Date.now(),
      blockThreads: settings.blockThreadsOnUserBlock,
      // [新增] 根据设置决定是否添加同步标记
      addedToNativeBlacklist: settings.syncWithNativeBlacklist,
    };
    saveBlockedUsers(b);

    // [修改] 只有在开关开启时才执行同步操作
    if (settings.syncWithNativeBlacklist) {
      const formhash = getFormhash();
      if (formhash) {
        try {
          await addToNativeBlacklist(name, formhash);
        } catch (e) {
          return false; // 同步失败
        }
      }
    }

    hideUserPosts(id);
    hideBlockedUserQuotes();
    hideBlockedUserRatings();
    if (b[id].blockThreads) applyUserThreadBlocklist();
    return true; // 全部成功
  };

  const unblockUser = async (id) => {
    // [优化] 函数变为异步并返回布尔值
    const b = getBlockedUsers();
    // [修改] 先获取要删除的用户数据
    const userToUnblock = b[id];

    // 如果用户不存在,直接返回成功
    if (!userToUnblock) return true;

    // 从脚本存储中删除用户
    delete b[id];
    saveBlockedUsers(b);

    // [修改] 只有当用户有“已同步”标记时,才执行移除操作
    if (userToUnblock.addedToNativeBlacklist === true) {
      const formhash = getFormhash();
      if (formhash) {
        try {
          await removeFromNativeBlacklist(id, formhash);
        } catch (e) {
          return false; // 同步失败
        }
      }
    }

    showUserPosts(id);
    hideBlockedUserQuotes();
    hideBlockedUserRatings();
    unblockThreadsByUser(id);
    return true; // 全部成功
  };

  // [FIX] 更精确地定位帖子作者,避免错误隐藏被评分的帖子
  const hideUserPosts = (id) => {
    document
      .querySelectorAll(`.authi a[href*="space-uid-${id}.html"]`)
      .forEach((l) =>
        l
          .closest("table.plhin")
          ?.setAttribute("style", "display: none !important")
      );
  };
  const showUserPosts = (id) => {
    document
      .querySelectorAll(`.authi a[href*="space-uid-${id}.html"]`)
      .forEach((l) => l.closest("table.plhin")?.removeAttribute("style"));
  };

  const hideBlockedUsersPosts = () =>
    Object.keys(getBlockedUsers()).forEach(hideUserPosts);

  const hideBlockedUserQuotes = () => {
    const settings = getSettings();
    const blockedUsers = getBlockedUsers();
    const blockedUserNames = Object.values(blockedUsers).map((u) => u.name);

    document.querySelectorAll("div.quote").forEach((quoteElement) => {
      const quoteAuthorElement = quoteElement.querySelector(
        'blockquote font[color="#999999"]'
      );
      if (!quoteAuthorElement) return;

      const text = quoteAuthorElement.textContent.trim();
      const match = text.match(/^(.*)\s发表于\s.*$/);
      if (!match || !match[1]) return;

      const authorName = match[1];
      const isBlocked =
        settings.enableUserBlocking && blockedUserNames.includes(authorName);

      const wrapper = quoteElement.parentElement.classList.contains(
        "s1p-quote-wrapper"
      )
        ? quoteElement.parentElement
        : null;

      if (isBlocked) {
        if (!wrapper) {
          const newWrapper = document.createElement("div");
          newWrapper.className = "s1p-quote-wrapper";
          quoteElement.parentNode.insertBefore(newWrapper, quoteElement);
          newWrapper.appendChild(quoteElement);
          newWrapper.style.maxHeight = "0";

          const newPlaceholder = document.createElement("div");
          newPlaceholder.className = "s1p-quote-placeholder";
          newPlaceholder.innerHTML = `<span>一条来自已屏蔽用户的引用已被隐藏。</span><span class="s1p-quote-toggle s1p-popover-btn">点击展开</span>`;
          newWrapper.parentNode.insertBefore(newPlaceholder, newWrapper);

          newPlaceholder
            .querySelector(".s1p-quote-toggle")
            .addEventListener("click", function () {
              const isCollapsed = newWrapper.style.maxHeight === "0px";
              if (isCollapsed) {
                const style = window.getComputedStyle(quoteElement);
                const marginTop = parseFloat(style.marginTop);
                const marginBottom = parseFloat(style.marginBottom);
                newWrapper.style.maxHeight =
                  quoteElement.offsetHeight + marginTop + marginBottom + "px";
                this.textContent = "点击折叠";
              } else {
                newWrapper.style.maxHeight = "0px";
                this.textContent = "点击展开";
              }
            });
        }
      } else {
        if (wrapper) {
          const placeholder = wrapper.previousElementSibling;
          if (
            placeholder &&
            placeholder.classList.contains("s1p-quote-placeholder")
          ) {
            placeholder.remove();
          }
          wrapper.parentNode.insertBefore(quoteElement, wrapper);
          wrapper.remove();
        }
      }
    });
  };

  // [MODIFIED] 函数现在可以同时处理隐藏和显示,是一个完整的“刷新”功能
  const hideBlockedUserRatings = () => {
    const settings = getSettings();
    const blockedUserIds = Object.keys(getBlockedUsers());
    document.querySelectorAll("tbody.ratl_l tr").forEach((row) => {
      const userLink = row.querySelector('a[href*="space-uid-"]');
      if (userLink) {
        const uidMatch = userLink.href.match(/space-uid-(\d+)/);
        if (uidMatch && uidMatch[1]) {
          const isBlocked =
            settings.enableUserBlocking && blockedUserIds.includes(uidMatch[1]);
          row.style.display = isBlocked ? "none" : "";
        }
      }
    });
  };

  // [修改 V2] 隐藏来自已屏蔽用户的消息提醒 (占位符替换模式)
  const hideBlockedUserNotifications = () => {
    const noticeContainer = document.querySelector(".xld.xlda");
    if (!noticeContainer) return;

    const settings = getSettings();
    if (!settings.enableUserBlocking) return;

    const blockedUserIds = Object.keys(getBlockedUsers());

    // 遍历所有提醒元素或已存在的占位符的下一个元素
    noticeContainer
      .querySelectorAll(
        "dl.cl, .s1p-notification-placeholder + .s1p-notification-wrapper"
      )
      .forEach((element) => {
        let dlElement;
        // 确定我们正在处理的是原始dl还是wrapper内的dl
        if (element.classList.contains("s1p-notification-wrapper")) {
          dlElement = element.querySelector("dl.cl");
        } else {
          dlElement = element;
        }

        if (!dlElement) return;

        const userLink = dlElement.querySelector('a[href*="space-uid-"]');
        if (!userLink) return;

        const uidMatch = userLink.href.match(/space-uid-(\d+)/);
        const authorId = uidMatch ? uidMatch[1] : null;
        const isBlocked = authorId && blockedUserIds.includes(authorId);

        const wrapper = dlElement.parentElement.classList.contains(
          "s1p-notification-wrapper"
        )
          ? dlElement.parentElement
          : null;

        if (isBlocked) {
          if (!wrapper) {
            // 需要屏蔽,但尚未被包装 -> 执行包装和隐藏
            const newWrapper = document.createElement("div");
            newWrapper.className = "s1p-notification-wrapper";
            dlElement.parentNode.insertBefore(newWrapper, dlElement);
            newWrapper.appendChild(dlElement);

            // 关键:先获取高度,再设置为0,以便动画生效
            const initialHeight = dlElement.scrollHeight;
            newWrapper.style.maxHeight = initialHeight + "px"; // 确保初始状态正确

            requestAnimationFrame(() => {
              newWrapper.style.maxHeight = "0px";
            });

            const placeholder = document.createElement("div");
            placeholder.className = "s1p-notification-placeholder";
            placeholder.innerHTML = `<span>一条来自已屏蔽用户的提醒已被隐藏。</span><span class="s1p-notification-toggle">点击展开</span>`;
            newWrapper.parentNode.insertBefore(placeholder, newWrapper);

            placeholder
              .querySelector(".s1p-notification-toggle")
              .addEventListener("click", function () {
                const isCollapsed = newWrapper.style.maxHeight === "0px";
                if (isCollapsed) {
                  newWrapper.style.maxHeight = dlElement.scrollHeight + "px";
                  this.textContent = "点击折叠";
                } else {
                  newWrapper.style.maxHeight = "0px";
                  this.textContent = "点击展开";
                }
              });
          }
        } else {
          if (wrapper) {
            // 不需要屏蔽,但已被包装 -> 解除包装
            const placeholder = wrapper.previousElementSibling;
            if (
              placeholder &&
              placeholder.classList.contains("s1p-notification-placeholder")
            ) {
              placeholder.remove();
            }
            wrapper.parentNode.insertBefore(dlElement, wrapper);
            wrapper.remove();
          }
        }
      });
  };

  const hideThreadsByTitleKeyword = () => {
    const rules = getTitleFilterRules().filter((r) => r.enabled && r.pattern);
    const newHiddenThreads = {};

    const regexes = rules
      .map((r) => {
        try {
          return { regex: new RegExp(r.pattern), pattern: r.pattern };
        } catch (e) {
          console.error(
            `S1 Plus: 屏蔽规则 "${r.pattern}" 不是一个有效的正则表达式,将被忽略。`,
            e
          );
          return null;
        }
      })
      .filter(Boolean);

    document
      .querySelectorAll('tbody[id^="normalthread_"], tbody[id^="stickthread_"]')
      .forEach((row) => {
        const titleElement = row.querySelector("th a.s.xst");
        if (!titleElement) return;

        const title = titleElement.textContent.trim();
        const threadId = row.id.replace(/^(normalthread_|stickthread_)/, "");
        let isHidden = false;

        if (regexes.length > 0) {
          const matchingRule = regexes.find((r) => r.regex.test(title));
          if (matchingRule) {
            newHiddenThreads[threadId] = {
              title,
              pattern: matchingRule.pattern,
            };
            row.classList.add("s1p-hidden-by-keyword");
            isHidden = true;
          }
        }

        if (!isHidden) {
          row.classList.remove("s1p-hidden-by-keyword");
        }
      });
    dynamicallyHiddenThreads = newHiddenThreads;
  };

  const getReadProgress = () => GM_getValue("s1p_read_progress", {});
  const saveReadProgress = (progress, suppressSyncTrigger = false) => {
    GM_setValue("s1p_read_progress", progress);
    if (!suppressSyncTrigger) {
      updateLastModifiedTimestamp();
    }
  };
  const updateThreadProgress = (threadId, postId, page, lastReadFloor) => {
    if (!postId || !page || !lastReadFloor) return;
    const progress = getReadProgress();
    progress[threadId] = {
      postId,
      page,
      timestamp: Date.now(),
      lastReadFloor: lastReadFloor,
    };
    saveReadProgress(progress);
  };

  const applyUserThreadBlocklist = () => {
    const blockedUsers = getBlockedUsers();
    const usersToBlockThreads = Object.keys(blockedUsers).filter(
      (uid) => blockedUsers[uid].blockThreads
    );
    if (usersToBlockThreads.length === 0) return;

    document
      .querySelectorAll('tbody[id^="normalthread_"], tbody[id^="stickthread_"]')
      .forEach((row) => {
        const authorLink = row.querySelector(
          'td.by cite a[href*="space-uid-"]'
        );
        if (authorLink) {
          const uidMatch = authorLink.href.match(/space-uid-(\d+)\.html/);
          const authorId = uidMatch ? uidMatch[1] : null;
          if (authorId && usersToBlockThreads.includes(authorId)) {
            const threadId = row.id.replace(
              /^(normalthread_|stickthread_)/,
              ""
            );
            const titleElement = row.querySelector("th a.s.xst");
            if (threadId && titleElement) {
              blockThread(
                threadId,
                titleElement.textContent.trim(),
                `user_${authorId}`
              );
            }
          }
        }
      });
  };

  const unblockThreadsByUser = (userId) => {
    const allBlockedThreads = getBlockedThreads();
    const reason = `user_${userId}`;
    Object.keys(allBlockedThreads).forEach((threadId) => {
      if (allBlockedThreads[threadId].reason === reason) {
        unblockThread(threadId);
      }
    });
  };

  const updatePostImageButtonState = (postContainer) => {
    const toggleButton = postContainer.querySelector(
      ".s1p-image-toggle-all-btn"
    );
    if (!toggleButton) return;

    const totalImages = postContainer.querySelectorAll(
      ".s1p-image-container"
    ).length;
    if (totalImages <= 1) {
      const container = toggleButton.closest(".s1p-image-toggle-all-container");
      if (container) container.remove();
      return;
    }

    const hiddenImages = postContainer.querySelectorAll(
      ".s1p-image-container.hidden"
    ).length;

    if (hiddenImages > 0) {
      toggleButton.textContent = `显示本楼所有图片 (${hiddenImages}/${totalImages})`;
    } else {
      toggleButton.textContent = `隐藏本楼所有图片 (${totalImages}/${totalImages})`;
    }
  };

  const manageImageToggleAllButtons = () => {
    const settings = getSettings();

    // 如果没有开启“默认隐藏图片”,则移除所有切换按钮并直接返回
    if (!settings.hideImagesByDefault) {
      document
        .querySelectorAll(".s1p-image-toggle-all-container")
        .forEach((el) => el.remove());
      return;
    }

    document.querySelectorAll("table.plhin").forEach((postContainer) => {
      const imageContainers = postContainer.querySelectorAll(
        ".s1p-image-container"
      );
      const postContentArea = postContainer.querySelector("td.t_f");

      if (!postContentArea) return;

      let toggleButtonContainer = postContainer.querySelector(
        ".s1p-image-toggle-all-container"
      );

      if (imageContainers.length <= 1) {
        if (toggleButtonContainer) toggleButtonContainer.remove();
        return;
      }

      if (!toggleButtonContainer) {
        toggleButtonContainer = document.createElement("div");
        toggleButtonContainer.className = "s1p-image-toggle-all-container";

        const toggleButton = document.createElement("button");
        toggleButton.className = "s1p-image-toggle-all-btn";
        toggleButtonContainer.appendChild(toggleButton);

        toggleButton.addEventListener("click", (e) => {
          e.preventDefault();
          const imagesInPost = postContainer.querySelectorAll(
            ".s1p-image-container"
          );
          const shouldShowAll = postContainer.querySelector(
            ".s1p-image-container.hidden"
          );

          if (shouldShowAll) {
            imagesInPost.forEach((container) => {
              container.classList.remove("hidden");
              container.dataset.manualShow = "true";
            });
          } else {
            imagesInPost.forEach((container) => {
              container.classList.add("hidden");
              delete container.dataset.manualShow;
            });
          }
          updatePostImageButtonState(postContainer);
        });

        postContentArea.prepend(toggleButtonContainer);
      }

      updatePostImageButtonState(postContainer);
    });
  };

  // --- [新增] 修改“只看该作者”为“只看该用户”的函数 ---
  const renameAuthorLinks = () => {
    document
      .querySelectorAll('div.authi a[href*="authorid="]')
      .forEach((link) => {
        if (link.textContent.trim() === "只看该作者") {
          link.textContent = "只看该用户";
        }
      });
  };

  // [MODIFIED] 图片隐藏功能的核心逻辑 (支持实时切换)
  const applyImageHiding = () => {
    const settings = getSettings();

    // 如果功能未开启,则移除所有包装和占位符
    if (!settings.hideImagesByDefault) {
      document.querySelectorAll(".s1p-image-container").forEach((container) => {
        const originalElement =
          container.querySelector("img.zoom")?.closest("a") ||
          container.querySelector("img.zoom");
        if (originalElement) {
          container.parentNode.insertBefore(originalElement, container);
        }
        container.remove();
      });
      return;
    }

    // 步骤 1: 遍历所有帖子图片,确保它们都被容器包裹并绑定切换事件
    document.querySelectorAll("div.t_fsz img.zoom").forEach((img) => {
      if (img.closest(".s1p-image-container")) return; // 如果已被包裹,则跳过

      const targetElement = img.closest("a") || img;
      const container = document.createElement("div");
      container.className = "s1p-image-container";

      const placeholder = document.createElement("span");
      placeholder.className = "s1p-image-placeholder";
      // 初始文本不重要,会在步骤2中被正确设置
      placeholder.textContent = "图片处理中...";

      targetElement.parentNode.insertBefore(container, targetElement);
      container.appendChild(placeholder);
      container.appendChild(targetElement);

      placeholder.addEventListener("click", (e) => {
        e.preventDefault();
        const isHidden = container.classList.toggle("hidden");

        if (isHidden) {
          placeholder.textContent = "显示图片";
          delete container.dataset.manualShow;
        } else {
          placeholder.textContent = "隐藏图片";
          container.dataset.manualShow = "true";
        }

        const postContainer = container.closest("table.plhin");
        if (postContainer) {
          updatePostImageButtonState(postContainer);
        }
      });
    });

    // 步骤 2: 根据当前设置和图片状态,同步所有容器的 class 和占位符文本
    document.querySelectorAll(".s1p-image-container").forEach((container) => {
      const placeholder = container.querySelector(".s1p-image-placeholder");
      if (!placeholder) return;

      const shouldBeHidden =
        settings.hideImagesByDefault && container.dataset.manualShow !== "true";

      container.classList.toggle("hidden", shouldBeHidden);

      if (shouldBeHidden) {
        placeholder.textContent = "显示图片";
      } else {
        placeholder.textContent = "隐藏图片";
      }
    });
  };

  const globalLinkClickHandler = (e) => {
    const settings = getSettings();
    const openTabSettings = settings.openInNewTab;

    if (!openTabSettings.master) return;

    const anchor = e.target.closest("a[href]");
    if (!anchor) return;

    const href = anchor.getAttribute("href");
    if (
      href.startsWith("javascript:") ||
      href.includes("mod=logging&action=logout") ||
      anchor.closest(
        ".s1p-modal, .s1p-confirm-modal, .s1p-options-menu, .s1p-tag-popover, .pob, .pgs, .pgbtn, #s1p-nav-link, #s1p-nav-sync-btn"
      )
    ) {
      return;
    }

    // --- [核心重构 V2] 真正与执行顺序无关的精确识别 ---
    const getLinkType = (targetAnchor) => {
      // 步骤 1: 识别链接所具备的所有身份,不提前返回
      const identities = [];
      if (
        targetAnchor.classList.contains("s1p-progress-jump-btn") &&
        targetAnchor.closest("#threadlist")
      ) {
        identities.push("progress_jump");
      }
      if (targetAnchor.closest("#threadlist")) {
        identities.push("thread_list");
      }
      if (targetAnchor.closest(".xld.xlda")) {
        identities.push("notification");
      }
      if (targetAnchor.closest(".wp")) {
        identities.push("header");
      }

      // 如果没有任何身份,则为未处理
      if (identities.length === 0) {
        return "unhandled";
      }

      // 步骤 2: 定义身份的优先级顺序 (从最具体到最宽泛)
      const priorityOrder = [
        "progress_jump",
        "thread_list",
        "notification",
        "header",
      ];

      // 步骤 3: 根据优先级列表,返回链接身份中优先级最高的一个
      for (const priorityType of priorityOrder) {
        if (identities.includes(priorityType)) {
          return priorityType;
        }
      }

      // 理论上不会执行到这里,作为安全保障
      return "unhandled";
    };

    const linkType = getLinkType(anchor);

    let open = false;
    let background = false;
    let settingApplied = false;

    // --- 根据精确识别的类型,应用对应设置 (此部分逻辑不变) ---
    switch (linkType) {
      case "progress_jump":
        if (openTabSettings.progress) {
          open = true;
          background = openTabSettings.progressInBackground;
          settingApplied = true;
        }
        break;
      case "thread_list":
      case "notification":
        if (openTabSettings.threadList) {
          open = true;
          background = openTabSettings.threadListInBackground;
          settingApplied = true;
        }
        break;
      case "header":
        if (openTabSettings.nav) {
          open = true;
          background = openTabSettings.navInBackground;
          settingApplied = true;
        }
        break;
      case "unhandled":
      default:
        // 对于未处理的链接类型,直接返回,保持默认行为
        return;
    }

    if (open && settingApplied) {
      e.preventDefault();
      e.stopPropagation();
      GM_openInTab(anchor.href, { active: !background });
    }
  };

  const applyGlobalLinkBehavior = () => {
    document.body.removeEventListener("click", globalLinkClickHandler);
    const settings = getSettings();
    if (settings.openInNewTab.master) {
      document.body.addEventListener("click", globalLinkClickHandler);
    }
  };

  // [MODIFIED] 导出数据对象,采用新的嵌套结构并包含内容哈希
  const exportLocalDataObject = async () => {
    const lastUpdated = GM_getValue("s1p_last_modified", 0);
    const lastUpdatedFormatted = new Date(lastUpdated).toLocaleString("zh-CN", {
      hour12: false,
    });

    // --- [FIX] 从要同步的设置中排除 Gist ID 和 PAT ---
    const allSettings = getSettings();
    const { syncRemoteGistId, syncRemotePat, ...syncedSettings } = allSettings;
    // -----------------------------------------------------

    const data = {
      settings: syncedSettings, // 使用过滤后的设置对象
      threads: getBlockedThreads(),
      users: getBlockedUsers(),
      user_tags: getUserTags(),
      title_filter_rules: getTitleFilterRules(),
      read_progress: getReadProgress(),
      bookmarked_replies: getBookmarkedReplies(),
    };

    const contentHash = await calculateDataHash(data);

    return {
      version: 4.0, // 版本号升级
      lastUpdated,
      lastUpdatedFormatted,
      contentHash, // 新增内容哈希字段
      data, // 所有用户数据被封装在 data 对象中
    };
  };

  const exportLocalData = async () =>
    JSON.stringify(await exportLocalDataObject(), null, 2);

  // [MODIFIED] 导入数据,兼容新旧两种数据结构,并增加控制选项
  const importLocalData = (jsonStr, options = {}) => {
    const { suppressPostSync = false } = options;
    try {
      // [S1P-FIX-A] 在导入任何数据之前,立刻断开当前页面的阅读进度观察器,修复潜在的竞态条件问题。
      if (pageObserver) {
        pageObserver.disconnect();
        console.log(
          "S1 Plus: 已在导入数据前断开阅读进度观察器,防止数据覆盖。"
        );
      }

      const imported = JSON.parse(jsonStr);
      if (typeof imported !== "object" || imported === null)
        throw new Error("无效数据格式");

      const dataToImport =
        imported.data && imported.version >= 4.0 ? imported.data : imported;

      let threadsImported = 0,
        usersImported = 0,
        progressImported = 0,
        rulesImported = 0,
        tagsImported = 0,
        bookmarksImported = 0;

      const upgradeDataStructure = (type, importedData) => {
        if (!importedData || typeof importedData !== "object") return {};
        Object.keys(importedData).forEach((id) => {
          const item = importedData[id];
          if (type === "users" && typeof item.blockThreads === "undefined")
            item.blockThreads = false;
          if (type === "threads" && typeof item.reason === "undefined")
            item.reason = "manual";
        });
        return importedData;
      };

      if (dataToImport.settings) {
        const importedSettings = { ...dataToImport.settings };
        delete importedSettings.syncRemoteGistId;
        delete importedSettings.syncRemotePat;
        saveSettings({ ...getSettings(), ...importedSettings });
      }

      const threadsToSave = upgradeDataStructure(
        "threads",
        dataToImport.threads || {}
      );
      saveBlockedThreads(threadsToSave);
      threadsImported = Object.keys(threadsToSave).length;

      const usersToSave = upgradeDataStructure(
        "users",
        dataToImport.users || {}
      );
      saveBlockedUsers(usersToSave);
      usersImported = Object.keys(usersToSave).length;

      if (
        dataToImport.user_tags &&
        typeof dataToImport.user_tags === "object"
      ) {
        saveUserTags(dataToImport.user_tags);
        tagsImported = Object.keys(dataToImport.user_tags).length;
      }

      if (
        dataToImport.title_filter_rules &&
        Array.isArray(dataToImport.title_filter_rules)
      ) {
        saveTitleFilterRules(dataToImport.title_filter_rules);
        rulesImported = dataToImport.title_filter_rules.length;
      } else if (
        dataToImport.title_keywords &&
        Array.isArray(dataToImport.title_keywords)
      ) {
        const newRules = dataToImport.title_keywords.map((k) => ({
          pattern: k,
          enabled: true,
          id: `rule_${Date.now()}_${Math.random()}`,
        }));
        saveTitleFilterRules(newRules);
        rulesImported = newRules.length;
      }

      if (dataToImport.read_progress) {
        saveReadProgress(dataToImport.read_progress);
        progressImported = Object.keys(dataToImport.read_progress).length;
      }

      if (dataToImport.bookmarked_replies) {
        saveBookmarkedReplies(dataToImport.bookmarked_replies);
        bookmarksImported = Object.keys(dataToImport.bookmarked_replies).length;
      }

      GM_setValue("s1p_last_modified", imported.lastUpdated || 0);

      hideBlockedThreads();
      hideBlockedUsersPosts();
      applyUserThreadBlocklist();
      hideThreadsByTitleKeyword();
      initializeNavbar();
      applyInterfaceCustomizations();

      // [S1P-FIX-B] 只有在非抑制模式下(例如手动编辑文本框导入)才触发后续同步,修复强制拉取后的冗余操作问题。
      if (!suppressPostSync) {
        triggerRemoteSyncPush();
      }

      return {
        success: true,
        message: `成功导入 ${threadsImported} 条帖子、${usersImported} 条用户、${tagsImported} 条标记、${bookmarksImported} 条收藏、${rulesImported} 条标题规则、${progressImported} 条阅读进度及相关设置。`,
      };
    } catch (e) {
      return { success: false, message: `导入失败: ${e.message}` };
    }
  };

  const fetchRemoteData = () =>
    new Promise((resolve, reject) => {
      const { syncRemoteGistId, syncRemotePat } = getSettings();
      if (!syncRemoteGistId || !syncRemotePat) {
        return reject(new Error("配置不完整"));
      }
      const syncRemoteApiUrl = `https://api.github.com/gists/${syncRemoteGistId}`;

      GM_xmlhttpRequest({
        method: "GET",
        url: syncRemoteApiUrl,
        headers: {
          Authorization: `Bearer ${syncRemotePat}`,
          Accept: "application/vnd.github.v3+json",
          // [新增] 增加此行以禁用API缓存,确保每次都获取最新数据
          "Cache-Control": "no-cache",
        },
        onload: (response) => {
          if (response.status === 200) {
            try {
              const gistData = JSON.parse(response.responseText);
              const fileContent = gistData.files["s1plus_sync.json"]?.content;
              if (fileContent) {
                resolve(JSON.parse(fileContent));
              } else {
                // If file doesn't exist, it's not an error, just means it's the first sync.
                // Return an empty object so it can be populated.
                resolve({});
              }
            } catch (e) {
              reject(new Error(`解析Gist数据失败: ${e.message}`));
            }
          } else {
            reject(new Error(`GitHub API请求失败,状态码: ${response.status}`));
          }
        },
        onerror: () => {
          reject(new Error("网络请求失败。"));
        },
      });
    });

  const pushRemoteData = (dataObject) =>
    new Promise((resolve, reject) => {
      const { syncRemoteGistId, syncRemotePat } = getSettings();
      if (!syncRemoteGistId || !syncRemotePat) {
        return reject(new Error("配置不完整"));
      }
      const syncRemoteApiUrl = `https://api.github.com/gists/${syncRemoteGistId}`;

      const payload = {
        files: {
          "s1plus_sync.json": {
            content: JSON.stringify(dataObject, null, 2),
          },
        },
      };

      GM_xmlhttpRequest({
        method: "PATCH",
        url: syncRemoteApiUrl,
        headers: {
          Authorization: `Bearer ${syncRemotePat}`,
          Accept: "application/vnd.github.v3+json",
          "Content-Type": "application/json",
        },
        data: JSON.stringify(payload),
        onload: (response) => {
          if (response.status === 200) {
            resolve({ success: true, message: "数据已成功推送到Gist。" });
          } else {
            // [S1PLUS-MODIFIED] 优化 Gist API 错误信息处理
            let errorMessage = `Gist 更新失败 (状态码: ${response.status})。`;
            try {
              const apiResponse = JSON.parse(response.responseText);
              // 优先使用 API 返回的 message 字段
              if (apiResponse && apiResponse.message) {
                errorMessage += `原因: ${apiResponse.message}`;
              }
            } catch (e) {
              // 如果响应不是有效的 JSON,则不附加额外信息
              errorMessage += "请检查网络连接或 Gist 配置。";
            }
            reject(new Error(errorMessage));
          }
        },
        onerror: () => {
          reject(new Error("网络请求失败。"));
        },
      });
    });

  // [MODIFIED] 触发式推送函数,现在是异步的
  const triggerRemoteSyncPush = () => {
    // 异步执行,不阻塞主流程
    (async () => {
      const settings = getSettings();
      if (
        !settings.syncRemoteEnabled ||
        !settings.syncAutoEnabled || // [修正] 确保自动同步子开关也开启
        !settings.syncRemoteGistId ||
        !settings.syncRemotePat
      ) {
        return;
      }

      console.log("S1 Plus: 检测到数据变更,触发后台智能同步检查...");

      // [核心修改] 不再是“盲目推送”,而是调用完整的智能同步引擎
      const result = await performAutoSync();

      // [新增] 根据智能同步的结果,决定是否需要用户交互
      switch (result.status) {
        case "conflict":
          // 如果检测到冲突,则调用与启动时同步完全相同的弹窗,让用户解决
          createAdvancedConfirmationModal(
            "检测到后台同步冲突",
            "<p>S1 Plus在后台自动同步时发现,您的本地数据和云端备份可能都已更改,为防止数据丢失,自动同步已暂停。</p><p>请手动选择要保留的版本来解决冲突。</p>",
            [
              {
                text: "稍后处理",
                className: "s1p-cancel",
                action: () => {
                  showMessage("同步已暂停,您可以在设置中手动同步。", null);
                },
              },
              {
                text: "立即解决",
                className: "s1p-confirm",
                action: () => {
                  handleManualSync(); // 调用功能最完善的手动同步流程
                },
              },
            ]
          );
          break;

        case "failure":
          // 如果同步失败,用一个无打扰的toast提示用户
          showMessage(`后台同步失败: ${result.error}`, false);
          break;

        case "success":
          if (result.action === "pulled") {
            // 如果后台自动拉取了数据,提示用户,因为页面内容可能已过期
            showMessage(
              "后台同步完成:云端有更新已被自动拉取。建议刷新页面。",
              true
            );
          }
          // 对于推送成功(pushed)或无需更改(no_change)的情况,控制台日志已足够,无需打扰用户
          break;
      }
    })();
  };

  /**
   * [新增] 迁移并校验远程数据
   * @param {object} remoteGistObject - 从 Gist 拉取的原始对象
   * @returns {Promise<object>} 返回一个包含 data, version, contentHash, lastUpdated 的规范化对象
   */
  const migrateAndValidateRemoteData = async (remoteGistObject) => {
    if (!remoteGistObject || typeof remoteGistObject !== "object") {
      throw new Error("远程数据为空或格式无效");
    }

    let data, version, contentHash, lastUpdated;

    // 场景1: 新版数据结构 (v4.0+)
    if (remoteGistObject.data && remoteGistObject.version >= 4.0) {
      data = remoteGistObject.data;
      version = remoteGistObject.version;
      contentHash = remoteGistObject.contentHash;
      lastUpdated = remoteGistObject.lastUpdated;

      // --- 核心校验逻辑 ---
      const calculatedHash = await calculateDataHash(data);
      if (calculatedHash !== contentHash) {
        throw new Error(
          "云端备份已损坏 (哈希校验失败),同步已暂停以保护您的本地数据。"
        );
      }

      // 场景2: 旧版扁平数据结构 (需要迁移)
    } else {
      console.log("S1 Plus: 检测到旧版云端数据格式,将进行自动迁移。");
      version = remoteGistObject.version || 3.2; // 假设旧版版本
      lastUpdated = remoteGistObject.lastUpdated || 0;
      // 从顶层属性中提取数据
      data = {
        settings: remoteGistObject.settings || defaultSettings,
        threads: remoteGistObject.threads || {},
        users: remoteGistObject.users || {},
        user_tags: remoteGistObject.user_tags || {},
        title_filter_rules: remoteGistObject.title_filter_rules || [],
        read_progress: remoteGistObject.read_progress || {},
        bookmarked_replies: remoteGistObject.bookmarked_replies || {},
      };
      // 旧版数据没有哈希,我们计算一个用于后续比较
      contentHash = await calculateDataHash(data);
    }

    return { data, version, contentHash, lastUpdated, full: remoteGistObject };
  };

  // [MODIFIED] 自动同步控制器 (逻辑优化版)
  const performAutoSync = async (isStartupSync = false) => {
    const settings = getSettings();
    if (
      !settings.syncRemoteEnabled ||
      !settings.syncRemoteGistId ||
      !settings.syncRemotePat
    ) {
      return { status: "skipped", reason: "disabled" };
    }

    isInitialSyncInProgress = true;
    console.log(
      `S1 Plus (Sync): 启动同步检查... (模式: ${
        isStartupSync ? "Startup" : "Normal"
      })`
    );

    try {
      const rawRemoteData = await fetchRemoteData();
      if (Object.keys(rawRemoteData).length === 0) {
        console.log(`S1 Plus (Sync): 远程为空,推送本地数据...`);
        const localData = await exportLocalDataObject();
        await pushRemoteData(localData);
        GM_setValue("s1p_last_sync_timestamp", Date.now());
        return { status: "success", action: "pushed_initial" };
      }

      const remote = await migrateAndValidateRemoteData(rawRemoteData);
      const localDataObject = await exportLocalDataObject();

      // --- 决策阶段 ---
      let syncAction = null;
      if (remote.contentHash === localDataObject.contentHash) {
        syncAction = "no_change";
      } else if (isStartupSync && settings.syncForcePullOnStartup) {
        syncAction = "force_pull";
      } else if (remote.lastUpdated > localDataObject.lastUpdated) {
        syncAction = "pull";
      } else if (localDataObject.lastUpdated > remote.lastUpdated) {
        syncAction = isStartupSync ? "skip_push_on_startup" : "push";
      } else {
        syncAction = "conflict";
      }

      // --- 执行阶段 ---
      switch (syncAction) {
        case "no_change":
          console.log(`S1 Plus (Sync): 本地与远程数据哈希一致,无需同步。`);
          return { status: "success", action: "no_change" };

        case "force_pull":
          console.log(
            `S1 Plus (Sync): 检测到启动时强制拉取已开启,将使用云端数据覆盖本地。`
          );
          importLocalData(JSON.stringify(remote.full), {
            suppressPostSync: true,
          });
          GM_setValue("s1p_last_sync_timestamp", Date.now());
          return { status: "success", action: "force_pulled" };

        case "pull":
          console.log(`S1 Plus (Sync): 远程数据比本地新,正在后台应用...`);
          importLocalData(JSON.stringify(remote.full), {
            suppressPostSync: true,
          });
          GM_setValue("s1p_last_sync_timestamp", Date.now());
          return { status: "success", action: "pulled" };

        case "skip_push_on_startup":
          console.warn(
            `S1 Plus (Sync): 启动同步检测到本地数据较新,已跳过自动推送以确保数据安全。如有需要,请手动同步。`
          );
          return { status: "success", action: "skipped_push_on_startup" };

        case "push":
          console.log(`S1 Plus (Sync): 本地数据比远程新,正在后台推送...`);
          await pushRemoteData(localDataObject);
          GM_setValue("s1p_last_sync_timestamp", Date.now());
          return { status: "success", action: "pushed" };

        case "conflict":
          console.warn(
            `S1 Plus (Sync): 检测到同步冲突 (时间戳相同但内容不同),自动同步已暂停。请手动同步以解决冲突。`
          );
          return {
            status: "conflict",
            reason: "timestamps match but hashes differ",
          };
      }
    } catch (error) {
      console.error("S1 Plus: 自动同步失败:", error);
      return { status: "failure", error: error.message };
    } finally {
      isInitialSyncInProgress = false;
      console.log("S1 Plus (Sync): 同步检查完成。");
    }
  };

  const defaultSettings = {
    enablePostBlocking: true,
    enableGeneralSettings: true,
    enableUserBlocking: true,
    enableUserTagging: true,
    enableReadProgress: true,
    showReadIndicator: true,
    enableBookmarkReplies: true,
    readingProgressCleanupDays: 0,
    openInNewTab: {
      master: false,
      threadList: true,
      threadListInBackground: false,
      progress: true,
      progressInBackground: false,
      nav: true,
      navInBackground: false,
    },
    enableNavCustomization: true,
    changeLogoLink: true,
    hideBlacklistTip: true,
    blockThreadsOnUserBlock: true,
    syncWithNativeBlacklist: true,
    showBlockedByKeywordList: false,
    showManuallyBlockedList: false,
    hideImagesByDefault: false,
    enhanceFloatingControls: true,
    recommendS1Nux: true,
    threadBlockHoverDelay: 1,
    customTitleSuffix: " - STAGE1ₛₜ",
    customNavLinks: [
      { name: "论坛", href: "forum.php" },
      { name: "归墟", href: "forum-157-1.html" },
      { name: "漫区", href: "forum-6-1.html" },
      { name: "游戏", href: "forum-4-1.html" },
      { name: "影视", href: "forum-48-1.html" },
      { name: "PC数码", href: "forum-51-1.html" },
      { name: "黑名单", href: "home.php?mod=space&do=friend&view=blacklist" },
    ],
    syncRemoteEnabled: false,
    syncDailyFirstLoad: true,
    syncAutoEnabled: true,
    syncForcePullOnStartup: false, // <-- [新增] 新增功能开关
    syncDirectChoiceMode: false,
    syncRemoteGistId: "",
    syncRemotePat: "",
  };

  const getSettings = () => {
    const saved = GM_getValue("s1p_settings", {});
    const settings = { ...defaultSettings, ...saved };

    // --- [NEW V2] 数据迁移逻辑 ---
    // 检查是否存在旧的、独立的设置项,或旧的 openInNewTab 结构
    if (
      typeof saved.openThreadsInNewTab !== "undefined" ||
      (saved.openInNewTab && typeof saved.openInNewTab.threads !== "undefined")
    ) {
      console.log(
        "S1 Plus: 检测到旧版“在新标签页打开”设置,正在执行自动迁移..."
      );

      const oldOpenTab = saved.openInNewTab || {};
      const newOpenInNewTab = {
        master: oldOpenTab.threads ?? saved.openThreadsInNewTab ?? false,
        threadList: oldOpenTab.threads ?? saved.openThreadsInNewTab ?? true,
        threadListInBackground:
          oldOpenTab.threadsInBackground ??
          saved.openThreadsInBackground ??
          false,
        progress: oldOpenTab.progress ?? saved.openProgressInNewTab ?? true,
        progressInBackground:
          oldOpenTab.progressInBackground ??
          saved.openProgressInBackground ??
          false,
        nav: oldOpenTab.nav ?? true, // 旧版无此设置,迁移时默认为 true
        navInBackground: oldOpenTab.navInBackground ?? false,
      };

      settings.openInNewTab = newOpenInNewTab;

      // 从设置中删除已迁移的旧键
      delete settings.openThreadsInNewTab;
      delete settings.openThreadsInBackground;
      delete settings.openProgressInNewTab;
      delete settings.openProgressInBackground;
      if (settings.openInNewTab) {
        delete settings.openInNewTab.threads;
        delete settings.openInNewTab.threadsInBackground;
        delete settings.openInNewTab.sidebar;
        delete settings.openInNewTab.sidebarInBackground;
      }

      // 立即保存迁移后的新设置
      saveSettings(settings);
      console.log("S1 Plus: 设置迁移完成。");
    } else {
      // 确保即使在迁移后,openInNewTab 对象也与默认值合并,以添加可能的新子属性
      settings.openInNewTab = {
        ...defaultSettings.openInNewTab,
        ...(saved.openInNewTab || {}),
      };
    }

    if (saved.customNavLinks && Array.isArray(saved.customNavLinks)) {
      settings.customNavLinks = saved.customNavLinks;
    }

    return settings;
  };

  // [S1PLUS-ADD-ABOVE: saveSettings]
  /**
   * [NEW] 设置一个嵌套对象的值。
   * @param {object} obj - 要修改的对象。
   * @param {string} path - 属性路径,用点号分隔,例如 'openInNewTab.threads'。
   * @param {any} value - 要设置的值。
   */
  const setNestedValue = (obj, path, value) => {
    const keys = path.split(".");
    let current = obj;
    for (let i = 0; i < keys.length - 1; i++) {
      if (typeof current[keys[i]] === "undefined") {
        current[keys[i]] = {};
      }
      current = current[keys[i]];
    }
    current[keys[keys.length - 1]] = value;
  };

  // [MODIFIED] 增加 suppressSyncTrigger 参数以阻止在特定情况下触发自动同步
  const saveSettings = (settings, suppressSyncTrigger = false) => {
    GM_setValue("s1p_settings", settings);
    if (!suppressSyncTrigger) {
      updateLastModifiedTimestamp();
    }
  };

  // --- 界面定制功能 ---
  const applyInterfaceCustomizations = () => {
    const settings = getSettings();
    if (settings.changeLogoLink)
      document.querySelector("#hd h2 a")?.setAttribute("href", "./forum.php");
    if (settings.hideBlacklistTip)
      document.getElementById("hiddenpoststip")?.remove();

    // 添加标题后缀修改
    if (settings.customTitleSuffix) {
      const titlePattern =
        /^(.+?)(?:论坛)?(?:\s*-\s*Stage1st)?\s*-\s*stage1\/s1\s+游戏动漫论坛$/;
      if (titlePattern.test(document.title)) {
        document.title =
          document.title.replace(titlePattern, "$1") +
          settings.customTitleSuffix;
      }
    }
  };

  /**
   * [NEW & EXTENDED V6 - SVG & ClassName Support] 创建一个更通用的行内动作菜单
   * @param {HTMLElement} anchorElement - 菜单定位的锚点元素
   * @param {Array<Object>} buttons - 按钮配置数组,例如 [{ label, title, action, className, callback }]
   * @param {Function} [onCloseCallback] - 菜单关闭时执行的回调函数
   */
  const createInlineActionMenu = (anchorElement, buttons, onCloseCallback) => {
    // 确保同一时间只有一个菜单
    document.querySelector(".s1p-inline-action-menu")?.remove();

    const menu = document.createElement("div");
    menu.className = "s1p-inline-action-menu";

    let isClosing = false;
    const closeMenu = () => {
      if (isClosing) return;
      isClosing = true;
      menu.classList.remove("visible");
      setTimeout(() => {
        if (menu.parentNode) menu.remove();
        if (onCloseCallback) onCloseCallback();
      }, 200);
    };

    buttons.forEach((btnConfig) => {
      const button = document.createElement("button");
      button.className = "s1p-action-btn";
      // [新增] 支持自定义 className
      if (btnConfig.className) {
        button.classList.add(...btnConfig.className.split(" "));
      }

      button.dataset.action = btnConfig.action;
      // [核心修正] 使用 innerHTML 替代 textContent 来渲染 SVG
      button.innerHTML = btnConfig.label;
      menu.appendChild(button);

      button.addEventListener("click", (e) => {
        e.stopPropagation();
        if (btnConfig.callback) {
          btnConfig.callback();
        }
        closeMenu();
      });

      const popover = document.getElementById("s1p-generic-display-popover");
      if (popover && popover.s1p_api && btnConfig.title) {
        button.addEventListener("mouseover", (e) =>
          popover.s1p_api.show(e.currentTarget, btnConfig.title)
        );
        button.addEventListener("mouseout", () => popover.s1p_api.hide());
      }
    });

    document.body.appendChild(menu);

    // --- 定位与显示 ---
    const anchorRect = anchorElement.getBoundingClientRect();
    const menuRect = menu.getBoundingClientRect();

    const top =
      anchorRect.top +
      window.scrollY +
      anchorRect.height / 2 -
      menuRect.height / 2;
    let left;

    const spaceOnRight = window.innerWidth - anchorRect.right;
    const requiredSpace = menuRect.width + 16;

    if (spaceOnRight >= requiredSpace) {
      left = anchorRect.right + window.scrollX + 8;
    } else {
      left = anchorRect.left + window.scrollX - menuRect.width - 8;
    }

    if (left < window.scrollX) {
      left = window.scrollX + 8;
    }

    menu.style.top = `${top}px`;
    menu.style.left = `${left}px`;

    // --- 交互逻辑 ---
    let hideTimeout;
    const startHideTimer = () => {
      hideTimeout = setTimeout(closeMenu, 300);
    };
    const cancelHideTimer = () => {
      clearTimeout(hideTimeout);
    };

    anchorElement.addEventListener("mouseleave", startHideTimer);
    menu.addEventListener("mouseenter", cancelHideTimer);
    menu.addEventListener("mouseleave", startHideTimer);

    requestAnimationFrame(() => {
      menu.classList.add("visible");
    });

    return menu;
  };

  /**
   * [NEW] 强制推送处理器,用于手动将本地数据覆盖到云端。
   */
  const handleForcePush = async () => {
    const icon = document.querySelector("#s1p-nav-sync-btn svg");
    if (icon) icon.classList.add("s1p-syncing");
    showMessage("正在向云端推送数据...", null);
    try {
      const localData = await exportLocalDataObject();
      await pushRemoteData(localData);
      GM_setValue("s1p_last_sync_timestamp", Date.now());
      updateLastSyncTimeDisplay();
      showMessage("推送成功!已更新云端备份。", true);
    } catch (e) {
      showMessage(`推送失败: ${e.message}`, false);
    } finally {
      if (icon) {
        icon.classList.remove("s1p-syncing");
        setTimeout(() => (icon.style.transform = ""), 1200); // 重置 transform
      }
    }
  };

  const handleForcePull = async () => {
    const icon = document.querySelector("#s1p-nav-sync-btn svg");
    if (icon) icon.classList.add("s1p-syncing");
    showMessage("正在从云端拉取数据...", null);
    try {
      const remoteData = await fetchRemoteData();
      if (Object.keys(remoteData).length === 0) {
        throw new Error("云端没有数据,无法拉取。");
      }
      const validatedRemote = await migrateAndValidateRemoteData(remoteData);
      // [S1P-FIX] 调用导入时,传入 suppressPostSync 选项来阻止不必要的二次同步
      const result = importLocalData(JSON.stringify(validatedRemote.full), {
        suppressPostSync: true,
      });
      if (result.success) {
        GM_setValue("s1p_last_sync_timestamp", Date.now());
        updateLastSyncTimeDisplay();
        showMessage("拉取成功!页面即将刷新以应用新数据。", true);
        setTimeout(() => location.reload(), 1500);
      } else {
        throw new Error(result.message);
      }
    } catch (e) {
      showMessage(`拉取失败: ${e.message}`, false);
    } finally {
      if (icon) {
        icon.classList.remove("s1p-syncing");
        setTimeout(() => (icon.style.transform = ""), 1200); // 重置 transform
      }
    }
  };

  const updateNavbarSyncButton = () => {
    const settings = getSettings();
    const existingBtnLi = document.getElementById("s1p-nav-sync-btn");
    const managerLink = document.getElementById("s1p-nav-link");

    if (!settings.syncRemoteEnabled) {
      if (existingBtnLi) existingBtnLi.remove();
      return;
    }

    if (existingBtnLi || !managerLink) return;

    const li = document.createElement("li");
    li.id = "s1p-nav-sync-btn";
    const a = document.createElement("a");
    a.href = "javascript:void(0);";
    // --- [核心修正] 恢复为原始的、基于“填充(fill)”的实心图标定义 ---
    a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 4C14.7486 4 17.1749 5.38626 18.6156 7.5H16V9.5H22V3.5H20V5.99936C18.1762 3.57166 15.2724 2 12 2C6.47715 2 2 6.47715 2 12H4C4 7.58172 7.58172 4 12 4ZM20 12C20 16.4183 16.4183 20 12 20C9.25144 20 6.82508 18.6137 5.38443 16.5H8V14.5H2V20.5H4V18.0006C5.82381 20.4283 8.72764 22 12 22C17.5228 22 22 17.5228 22 12H20Z"></path></svg>`;
    li.appendChild(a);

    if (settings.syncDirectChoiceMode) {
      // --- [MODIFIED] 模式2: 高级模式 (点击->智能判断 | 悬停->直接选择) ---
      let activeMenu = null;

      // [核心修改] 为高级模式下的按钮增加与默认模式完全相同的“点击”行为
      a.addEventListener("click", async (e) => {
        e.preventDefault();
        const icon = a.querySelector("svg");
        if (!icon || icon.classList.contains("s1p-syncing")) return;

        icon.classList.remove("s1p-sync-success", "s1p-sync-error");
        icon.classList.add("s1p-syncing");

        try {
          // 调用我们已优化的核心同步函数
          await handleManualSync();
          // 注意:由于handleManualSync现在自己处理所有反馈,这里不再需要处理其返回值来增删图标class
          icon.classList.remove("s1p-syncing");
        } catch (error) {
          icon.classList.remove("s1p-syncing");
          console.error("S1 Plus: Manual sync handler threw an error:", error);
        } finally {
          // 移除动画类,并重置可能存在的transform
          setTimeout(() => {
            icon.classList.remove("s1p-sync-success", "s1p-sync-error");
            icon.style.transform = "";
          }, 1200);
        }
      });

      const pullIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M1 14.5C1 12.1716 2.22429 10.1291 4.06426 8.9812C4.56469 5.044 7.92686 2 12 2C16.0731 2 19.4353 5.044 19.9357 8.9812C21.7757 10.1291 23 12.1716 23 14.5C23 17.9216 20.3562 20.7257 17 20.9811L7 21C3.64378 20.7257 1 17.9216 1 14.5ZM16.8483 18.9868C19.1817 18.8093 21 16.8561 21 14.5C21 12.927 20.1884 11.4962 18.8771 10.6781L18.0714 10.1754L17.9517 9.23338C17.5735 6.25803 15.0288 4 12 4C8.97116 4 6.42647 6.25803 6.0483 9.23338L5.92856 10.1754L5.12288 10.6781C3.81156 11.4962 3 12.927 3 14.5C3 16.8561 4.81833 18.8093 7.1517 18.9868L7.325 19H16.675L16.8483 18.9868ZM13 12H16L12 17L8 12H11V8H13V12Z"></path></svg>`;
      const pushIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M1 14.5C1 12.1716 2.22429 10.1291 4.06426 8.9812C4.56469 5.044 7.92686 2 12 2C16.0731 2 19.4353 5.044 19.9357 8.9812C21.7757 10.1291 23 12.1716 23 14.5C23 17.9216 20.3562 20.7257 17 20.9811L7 21C3.64378 20.7257 1 17.9216 1 14.5ZM16.8483 18.9868C19.1817 18.8093 21 16.8561 21 14.5C21 12.927 20.1884 11.4962 18.8771 10.6781L18.0714 10.1754L17.9517 9.23338C17.5735 6.25803 15.0288 4 12 4C8.97116 4 6.42647 6.25803 6.0483 9.23338L5.92856 10.1754L5.12288 10.6781C3.81156 11.4962 3 12.927 3 14.5C3 16.8561 4.81833 18.8093 7.1517 18.9868L7.325 19H16.675L16.8483 18.9868ZM13 13V17H11V13H8L12 8L16 13H13Z"></path></svg>`;

      li.addEventListener("mouseenter", () => {
        const existingMenu = document.querySelector(".s1p-inline-action-menu");
        if (existingMenu) {
          existingMenu.dispatchEvent(new MouseEvent("mouseenter"));
        } else {
          activeMenu = createInlineActionMenu(
            li,
            [
              {
                label: `${pullIconSVG} <span>拉取</span>`,
                action: "pull",
                title:
                  "用云端备份覆盖您当前的本地数据,本地未同步的修改将丢失!",
                callback: handleForcePull,
              },
              {
                label: `${pushIconSVG} <span>推送</span>`,
                action: "push",
                title: "将您当前的本地数据覆盖到云端备份,此操作不可逆。",
                callback: handleForcePush,
              },
            ],
            () => {
              activeMenu = null;
            }
          );
        }
      });
    } else {
      // --- 模式1: 默认模式 (点击后智能判断) ---
      a.addEventListener("click", async (e) => {
        e.preventDefault();
        const icon = a.querySelector("svg");
        if (!icon || icon.classList.contains("s1p-syncing")) return;

        icon.classList.remove("s1p-sync-success", "s1p-sync-error");
        icon.classList.add("s1p-syncing");

        try {
          // 调用我们已优化的核心同步函数
          await handleManualSync();
          icon.classList.remove("s1p-syncing");
        } catch (error) {
          icon.classList.remove("s1p-syncing");
          console.error("S1 Plus: Manual sync handler threw an error:", error);
        } finally {
          setTimeout(() => {
            icon.classList.remove("s1p-sync-success", "s1p-sync-error");
            icon.style.transform = "";
          }, 1200);
        }
      });

      a.addEventListener("mouseover", (e) => {
        const popover = document.getElementById("s1p-generic-display-popover");
        if (popover && popover.s1p_api) {
          popover.s1p_api.show(e.currentTarget, "手动同步数据 (智能判断)");
        }
      });
      a.addEventListener("mouseout", () => {
        const popover = document.getElementById("s1p-generic-display-popover");
        if (popover && popover.s1p_api) {
          popover.s1p_api.hide();
        }
      });
    }

    managerLink.insertAdjacentElement("afterend", li);
  };

  const initializeNavbar = () => {
    const settings = getSettings();
    const navUl = document.querySelector("#nv > ul");
    if (!navUl) return;

    const createManagerLink = () => {
      const li = document.createElement("li");
      li.id = "s1p-nav-link";
      const a = document.createElement("a");
      a.href = "javascript:void(0);";
      a.textContent = "S1 Plus 设置";
      a.addEventListener("click", createManagementModal);
      li.appendChild(a);
      return li;
    };

    document.getElementById("s1p-nav-link")?.remove();
    document.getElementById("s1p-nav-sync-btn")?.remove();

    if (settings.enableNavCustomization) {
      navUl.innerHTML = "";
      (settings.customNavLinks || []).forEach((link) => {
        if (!link.name || !link.href) return;
        const li = document.createElement("li");
        if (window.location.href.includes(link.href)) li.className = "a";
        const a = document.createElement("a");
        a.href = link.href;
        a.textContent = link.name;
        a.setAttribute("hidefocus", "true");
        li.appendChild(a);
        navUl.appendChild(li);
      });
    }
    navUl.appendChild(createManagerLink());
    updateNavbarSyncButton();
  };

  // --- [NEW] Helper function for search component
  /**
   * Escapes special characters in a string for use in a regular expression.
   * @param {string} str The string to escape.
   * @returns {string} The escaped string.
   */
  function escapeRegExp(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  }

  /**
   * Recursively finds and highlights text in a DOM node without breaking HTML.
   * @param {Node} node The starting DOM node.
   * @param {RegExp} regex The regex to match text with.
   */
  function highlightTextInNode(node, regex) {
    if (node.nodeType === 3) {
      // Text node
      const text = node.textContent;
      const matches = [...text.matchAll(regex)];
      if (matches.length > 0) {
        const fragment = document.createDocumentFragment();
        let lastIndex = 0;
        matches.forEach((match) => {
          if (match.index > lastIndex) {
            fragment.appendChild(
              document.createTextNode(text.substring(lastIndex, match.index))
            );
          }
          const mark = document.createElement("mark");
          mark.className = "s1p-highlight";
          mark.textContent = match[0];
          fragment.appendChild(mark);
          lastIndex = match.index + match[0].length;
        });
        if (lastIndex < text.length) {
          fragment.appendChild(
            document.createTextNode(text.substring(lastIndex))
          );
        }
        node.parentNode.replaceChild(fragment, node);
      }
    } else if (
      node.nodeType === 1 &&
      node.childNodes &&
      !/^(script|style)$/i.test(node.tagName)
    ) {
      // Element node
      const children = Array.from(node.childNodes);
      for (const child of children) {
        highlightTextInNode(child, regex);
      }
    }
  }

  // [REPLACED] Bookmark search component with safe highlighting
  /**
   * Sets up the interactive search functionality for the bookmarks tab.
   * @param {HTMLElement} bookmarksTabElement The container element for the bookmarks tab.
   */
  function setupBookmarkSearchComponent(bookmarksTabElement) {
    const searchInput = bookmarksTabElement.querySelector(
      "#s1p-bookmark-search-input"
    );
    const clearButton = bookmarksTabElement.querySelector(
      "#s1p-bookmark-search-clear-btn"
    );
    const list = bookmarksTabElement.querySelector("#s1p-bookmarks-list");
    const noResultsMessage = bookmarksTabElement.querySelector(
      "#s1p-bookmarks-no-results"
    );
    const emptyMessage = bookmarksTabElement.querySelector(
      "#s1p-bookmarks-empty-message"
    );

    if (!searchInput || !list || !clearButton || !noResultsMessage) return;

    const allItems = Array.from(list.querySelectorAll(".s1p-item"));
    const itemCache = allItems.map((item) => {
      const contentEl = item.querySelector(".s1p-item-content");
      const metaEl = item.querySelector(".s1p-item-meta");

      // Store original HTML if not already stored
      if (!contentEl.dataset.originalHtml) {
        contentEl.dataset.originalHtml = contentEl.innerHTML;
      }
      if (!metaEl.dataset.originalHtml) {
        metaEl.dataset.originalHtml = metaEl.innerHTML;
      }

      return {
        element: item,
        searchableText: (
          contentEl.textContent +
          " " +
          metaEl.textContent
        ).toLowerCase(),
        contentEl: contentEl,
        metaEl: metaEl,
      };
    });

    const performSearch = () => {
      const query = searchInput.value.toLowerCase().trim();
      clearButton.classList.toggle("hidden", query.length === 0);

      const keywords = query.split(/\s+/).filter((k) => k);
      let visibleCount = 0;

      const highlightRegex =
        keywords.length > 0
          ? new RegExp(keywords.map(escapeRegExp).join("|"), "gi")
          : null;

      for (const item of itemCache) {
        const isVisible =
          keywords.length === 0 ||
          keywords.every((keyword) => item.searchableText.includes(keyword));

        // Reset highlights first by restoring original HTML
        item.contentEl.innerHTML = item.contentEl.dataset.originalHtml;
        item.metaEl.innerHTML = item.metaEl.dataset.originalHtml;
        item.element.style.display = isVisible ? "flex" : "none";

        if (isVisible) {
          visibleCount++;
          if (highlightRegex) {
            // Apply new, safe highlighting that only targets text nodes
            highlightTextInNode(item.contentEl, highlightRegex);
            highlightTextInNode(item.metaEl, highlightRegex);
          }
        }
      }

      const hasAnyItems = allItems.length > 0;
      list.style.display = hasAnyItems ? "flex" : "none";
      emptyMessage.style.display = !hasAnyItems ? "block" : "none";
      noResultsMessage.style.display =
        hasAnyItems && visibleCount === 0 && query.length > 0
          ? "block"
          : "none";
    };

    searchInput.addEventListener("input", performSearch);

    clearButton.addEventListener("click", () => {
      searchInput.value = "";
      performSearch();
      searchInput.focus();
    });

    clearButton.classList.toggle("hidden", searchInput.value.length === 0);
  }

  // --- UI 创建 ---
  const formatDate = (timestamp) => new Date(timestamp).toLocaleString("zh-CN");

  let currentToast = null; // 用一个全局变量来管理当前的提示框实例

  /**
   * [MODIFIED] 显示消息,支持 true(成功)/false(失败)/null(中立) 三种状态
   * @param {string} message - 要显示的消息内容。
   * @param {boolean|null} isSuccess - 消息状态。
   */
  const showMessage = (message, isSuccess) => {
    // 如果上一个提示框还存在,立即移除,防止重叠
    if (currentToast) {
      currentToast.remove();
    }

    const toast = document.createElement("div");
    toast.textContent = message;

    // --- [核心修正] ---
    // 使用更完善的逻辑来处理三种状态
    let toastClass = "s1p-toast-notification";
    if (isSuccess === true) {
      toastClass += " success";
    } else if (isSuccess === false) {
      toastClass += " error";
    }
    // 如果 isSuccess 是 null 或 undefined,则不添加额外 class,显示默认的黑灰色样式
    toast.className = toastClass;
    // --- [修正结束] ---

    const modalContent = document.querySelector(".s1p-modal-content");
    if (modalContent) {
      modalContent.appendChild(toast);
    } else {
      document.body.appendChild(toast);
    }
    currentToast = toast;

    // 让动画生效
    setTimeout(() => {
      toast.classList.add("visible");
    }, 50);

    // 3秒后自动消失
    setTimeout(() => {
      toast.classList.remove("visible");
      toast.addEventListener(
        "transitionend",
        () => {
          if (toast.parentNode) {
            toast.remove();
          }
          if (currentToast === toast) {
            currentToast = null;
          }
        },
        { once: true }
      );
    }, 3000);
  };

  const createConfirmationModal = (
    title,
    subtitle,
    onConfirm,
    confirmText = "确定"
  ) => {
    document.querySelector(".s1p-confirm-modal")?.remove();
    const modal = document.createElement("div");
    modal.className = "s1p-confirm-modal";
    modal.innerHTML = `<div class="s1p-confirm-content"><div class="s1p-confirm-body"><div class="s1p-confirm-title">${title}</div><div class="s1p-confirm-subtitle">${subtitle}</div></div><div class="s1p-confirm-footer"><button class="s1p-confirm-btn s1p-cancel">取消</button><button class="s1p-confirm-btn s1p-confirm">${confirmText}</button></div></div>`;
    const closeModal = () => {
      modal.querySelector(".s1p-confirm-content").style.animation =
        "s1p-scale-out 0.25s ease-out forwards";
      modal.style.animation = "s1p-fade-out 0.25s ease-out forwards";
      setTimeout(() => modal.remove(), 250);
    };
    modal.querySelector(".s1p-confirm").addEventListener("click", () => {
      onConfirm();
      closeModal();
    });
    modal.querySelector(".s1p-cancel").addEventListener("click", closeModal);
    modal.addEventListener("click", (e) => {
      if (e.target === modal) closeModal();
    });
    document.body.appendChild(modal);
  };

  /**
   * [MODIFIED] 创建一个行内确认菜单 (V2: 带智能定位和动画)
   * @param {HTMLElement} anchorElement - 菜单定位的锚点元素
   * @param {string} confirmText - 确认提示文本
   * @param {Function} onConfirm - 点击确认后执行的回调函数
   */
  const createInlineConfirmMenu = (anchorElement, confirmText, onConfirm) => {
    document.querySelector(".s1p-inline-confirm-menu")?.remove();

    const menu = document.createElement("div");
    menu.className = "s1p-options-menu s1p-inline-confirm-menu";
    menu.style.width = "max-content";

    menu.innerHTML = `
            <div class="s1p-direct-confirm">
                <span>${confirmText}</span>
                <span class="s1p-confirm-separator"></span>
                <button class="s1p-confirm-action-btn s1p-cancel" title="取消"></button>
                <button class="s1p-confirm-action-btn s1p-confirm" title="确认"></button>
            </div>
        `;

    document.body.appendChild(menu);
    const anchorRect = anchorElement.getBoundingClientRect();
    const menuRect = menu.getBoundingClientRect();

    const top =
      anchorRect.top +
      window.scrollY +
      anchorRect.height / 2 -
      menuRect.height / 2;
    let left;

    const spaceOnRight = window.innerWidth - anchorRect.right;
    const requiredSpace = menuRect.width + 16;

    if (spaceOnRight >= requiredSpace) {
      left = anchorRect.right + window.scrollX + 8;
    } else {
      left = anchorRect.left + window.scrollX - menuRect.width - 8;
    }

    if (left < window.scrollX) {
      left = window.scrollX + 8;
    }

    menu.style.top = `${top}px`;
    menu.style.left = `${left}px`;

    let isClosing = false;
    const closeMenu = () => {
      if (isClosing) return;
      isClosing = true;
      document.removeEventListener("click", closeMenu);
      menu.classList.remove("visible");

      setTimeout(() => {
        if (menu.parentNode) {
          menu.remove();
        }
      }, 200);
    };

    menu.querySelector(".s1p-confirm").addEventListener("click", (e) => {
      e.stopPropagation();
      onConfirm();
      closeMenu();
    });

    menu.querySelector(".s1p-cancel").addEventListener("click", (e) => {
      e.stopPropagation();
      closeMenu();
    });

    requestAnimationFrame(() => {
      menu.classList.add("visible");
    });

    setTimeout(() => {
      document.addEventListener("click", closeMenu, { once: true });
    }, 0);
  };

  const removeProgressJumpButtons = () =>
    document
      .querySelectorAll(".s1p-progress-container")
      .forEach((el) => el.remove());
  const removeBlockButtonsFromThreads = () =>
    document.querySelectorAll(".s1p-options-cell").forEach((el) => el.remove());

  const refreshUserPostsOnPage = (userId) => {
    if (!userId) return;
    document
      .querySelectorAll(`.authi a[href*="space-uid-${userId}"]`)
      .forEach((userLink) => {
        const postTable = userLink.closest('table[id^="pid"]');
        if (postTable) {
          const postId = postTable.id.replace("pid", "");
          refreshSinglePostActions(postId);
        }
      });
  };
  // [最终版] 用于移动Tabs滑块的辅助函数
  const moveTabSlider = (tabContainer) => {
    if (!tabContainer) return;
    const slider = tabContainer.querySelector(".s1p-tab-slider");
    const activeTab = tabContainer.querySelector(".s1p-tab-btn.active");

    if (slider && activeTab) {
      // --- [核心修正] 动画参数现在由JS直接控制 ---
      // 您可以在这里轻松修改动画时长,单位是秒(s)
      const animationDuration = "0.45s";
      const animationEasing = "cubic-bezier(0.4, 0, 0.2, 1)";
      // ---------------------------------------------

      const newWidth = activeTab.offsetWidth;
      const newLeft = activeTab.offsetLeft;

      // 1. 在下一帧,立即为滑块应用过渡动画效果
      requestAnimationFrame(() => {
        slider.style.transition = `width ${animationDuration} ${animationEasing}, transform ${animationDuration} ${animationEasing}`;

        // 2. 紧接着,设置滑块的目标宽度和位置,这将触发动画
        slider.style.width = `${newWidth}px`;
        slider.style.transform = `translateX(${newLeft}px)`;
      });

      // 3. [优化] 监听动画结束事件,结束后移除内联的 transition 样式
      // 这样做的好处是,将控制权交还给CSS,避免潜在的样式冲突,是更规范的做法。
      slider.addEventListener(
        "transitionend",
        () => {
          slider.style.transition = "";
        },
        { once: true }
      ); // { once: true } 确保事件只触发一次后自动移除
    }
  };
  const createManagementModal = () => {
    const calculateModalWidth = () => {
      const measureContainer = document.createElement("div");
      measureContainer.style.cssText =
        "position: absolute; left: -9999px; top: -9999px; visibility: hidden; pointer-events: none;";

      const tabsDiv = document.createElement("div");
      tabsDiv.className = "s1p-tabs";
      tabsDiv.style.display = "inline-flex";
      tabsDiv.innerHTML = `
                <button class="s1p-tab-btn">通用设置</button>
                <button class="s1p-tab-btn">帖子屏蔽</button>
                <button class="s1p-tab-btn">用户屏蔽</button>
                <button class="s1p-tab-btn">用户标记</button>
                <button class="s1p-tab-btn">回复收藏</button>
                <button class="s1p-tab-btn">导航栏定制</button>
                <button class="s1p-tab-btn">设置同步</button>
            `;
      measureContainer.appendChild(tabsDiv);
      document.body.appendChild(measureContainer);

      let totalTabsWidth = 0;
      tabsDiv.querySelectorAll(".s1p-tab-btn").forEach((btn) => {
        const style = window.getComputedStyle(btn);
        totalTabsWidth +=
          btn.offsetWidth +
          parseFloat(style.marginLeft) +
          parseFloat(style.marginRight);
      });
      document.body.removeChild(measureContainer);

      return totalTabsWidth + 32; // 32px for padding
    };
    const requiredWidth = calculateModalWidth();
    document.querySelector(".s1p-modal")?.remove();

    const modal = document.createElement("div");
    modal.className = "s1p-modal";
    modal.style.opacity = "0";
    modal.innerHTML = `<div class="s1p-modal-content">
            <div class="s1p-modal-header"><div class="s1p-modal-title">S1 Plus 设置</div><div class="s1p-modal-close"></div></div>
            <div class="s1p-modal-body">
                <div class="s1p-tabs-wrapper">
                    <div class="s1p-tabs">
                        <div class="s1p-tab-slider"></div>
                        <button class="s1p-tab-btn active" data-tab="general-settings">通用设置</button>
                        <button class="s1p-tab-btn" data-tab="threads">帖子屏蔽</button>
                        <button class="s1p-tab-btn" data-tab="users">用户屏蔽</button>
                        <button class="s1p-tab-btn" data-tab="tags">用户标记</button>
                        <button class="s1p-tab-btn" data-tab="bookmarks">回复收藏</button>
                        <button class="s1p-tab-btn" data-tab="nav-settings">导航栏定制</button>
                        <button class="s1p-tab-btn" data-tab="sync">设置同步</button>
                    </div>
                </div>
                <div id="s1p-tab-general-settings" class="s1p-tab-content active"></div>
                <div id="s1p-tab-threads" class="s1p-tab-content"></div>
                <div id="s1p-tab-users" class="s1p-tab-content"></div>
                <div id="s1p-tab-tags" class="s1p-tab-content"></div>
                <div id="s1p-tab-bookmarks" class="s1p-tab-content"></div>
                <div id="s1p-tab-nav-settings" class="s1p-tab-content"></div>
                <div id="s1p-tab-sync" class="s1p-tab-content">
                    <div class="s1p-settings-group">
                        <div class="s1p-settings-group-title s1p-settings-section-title-label">本地备份与恢复</div>
                        <div class="s1p-local-sync-desc">通过手动复制/粘贴数据,在不同浏览器或设备间迁移或备份你的所有S1 Plus配置,包括屏蔽列表、导航栏、阅读进度和各项开关设置。</div>
                        <div class="s1p-local-sync-buttons">
                            <button id="s1p-local-export-btn" class="s1p-btn">导出数据</button>
                            <button id="s1p-local-import-btn" class="s1p-btn">导入数据</button>
                        </div>
                        <textarea id="s1p-local-sync-textarea" class="s1p-input s1p-textarea s1p-sync-textarea" placeholder="在此粘贴导入数据或从此处复制导出数据" autocomplete="off"></textarea>
                    </div>

                    <div class="s1p-settings-group">
                        <div class="s1p-settings-group-title s1p-settings-section-title-label">远程同步 (通过GitHub Gist)</div>
                        <div id="s1p-last-sync-time-container" class="s1p-setting-desc" style="margin-top: -8px; margin-bottom: 16px;"></div>
                        <div class="s1p-settings-item">
                            <label class="s1p-settings-label" for="s1p-remote-enabled-toggle">启用远程同步</label>
                            <label class="s1p-switch">
                                <input type="checkbox" id="s1p-remote-enabled-toggle" class="s1p-settings-checkbox">
                                <span class="s1p-slider"></span>
                            </label>
                        </div>
                         <p class="s1p-setting-desc">启用后,你可以在导航栏手动同步,或开启下面的自动同步。</p>

                        <div id="s1p-remote-sync-controls-wrapper">
                            <div class="s1p-settings-item">
                                <label class="s1p-settings-label" for="s1p-daily-first-load-sync-enabled-toggle">启用每日首次加载时同步</label>
                                <label class="s1p-switch">
                                    <input type="checkbox" id="s1p-daily-first-load-sync-enabled-toggle" class="s1p-settings-checkbox" data-s1p-sync-control>
                                    <span class="s1p-slider"></span>
                                </label>
                            </div>
                            <p class="s1p-setting-desc">启用后,每天第一次打开论坛时会自动检查并同步数据。此功能独立于下方的“自动后台同步”。</p>
                            
                            <div class="s1p-settings-sub-group">
                                <div class="s1p-settings-item">
                                    <label class="s1p-settings-label" for="s1p-force-pull-on-startup-toggle">启动时强制拉取云端数据</label>
                                    <label class="s1p-switch">
                                        <input type="checkbox" id="s1p-force-pull-on-startup-toggle" class="s1p-settings-checkbox" data-setting="syncForcePullOnStartup" data-s1p-sync-control>
                                        <span class="s1p-slider"></span>
                                    </label>
                                </div>
                                <p class="s1p-setting-desc s1p-warning-text">开启后,每日首次加载时若检测到云端与本地数据不一致,将总是使用云端数据覆盖本地,不再进行提示。请谨慎开启,这可能导致本地未同步的修改丢失。</p>
                            </div>
                            <div class="s1p-settings-item">
                                <label class="s1p-settings-label" for="s1p-auto-sync-enabled-toggle">启用自动后台同步</label>
                                <label class="s1p-switch">
                                    <input type="checkbox" id="s1p-auto-sync-enabled-toggle" class="s1p-settings-checkbox" data-s1p-sync-control>
                                    <span class="s1p-slider"></span>
                                </label>
                            </div>
                            <p class="s1p-setting-desc">启用后,数据将在停止操作5秒后自动同步。关闭后将切换为纯手动同步模式。</p>
                            <div class="s1p-settings-item">
                                <label class="s1p-settings-label" for="s1p-direct-choice-mode-toggle">启用手动同步高级模式 (悬停选择)</label>
                                <label class="s1p-switch">
                                    <input type="checkbox" id="s1p-direct-choice-mode-toggle" class="s1p-settings-checkbox" data-setting="syncDirectChoiceMode">
                                    <span class="s1p-slider"></span>
                                </label>
                            </div>
                            <p class="s1p-setting-desc">关闭时,点击同步按钮将智能判断;开启时,悬停同步按钮可直接选择推送或拉取。</p>
                            <div class="s1p-settings-item" style="flex-direction: column; align-items: flex-start; gap: 4px;">
                                <label class="s1p-settings-label" for="s1p-remote-gist-id-input">Gist ID</label>
                                <input type="text" id="s1p-remote-gist-id-input" class="s1p-input" placeholder="从 Gist 网址中复制的那一长串 ID" style="width: 100%;" autocomplete="off" data-s1p-sync-control>
                            </div>
                            <div class="s1p-settings-item" style="flex-direction: column; align-items: flex-start; gap: 4px; margin-top: 12px;">
                                <label class="s1p-settings-label" for="s1p-remote-pat-input">GitHub Personal Access Token (PAT)</label>
                                <input type="password" id="s1p-remote-pat-input" class="s1p-input" placeholder="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" style="width: 100%;" autocomplete="new-password" data-s1p-sync-control>
                            </div>
                            <div class="s1p-notice">
                                <div class="s1p-notice-icon"></div>
                                <div class="s1p-notice-content">
                                    <a href="https://silver-s1plus.netlify.app/" target="_blank">点击此处查看设置教程</a>
                                    <p>Token只会保存在你的浏览器本地,不会上传到任何地方。</p>
                                </div>
                            </div>
                            <div class="s1p-editor-footer" style="margin-top: 16px; justify-content: flex-end; gap: 8px;">
                                 <button id="s1p-remote-save-btn" class="s1p-btn" data-s1p-sync-control>保存设置</button>
                                 <button id="s1p-remote-manual-sync-btn" class="s1p-btn" data-s1p-sync-control>手动同步</button>
                                 <button id="s1p-open-gist-page-btn" class="s1p-btn" data-s1p-sync-control>打开 Gist 页面</button>
                            </div>
                        </div>
                    </div>

                    <div class="s1p-sync-title">危险操作</div>
                    <div class="s1p-local-sync-desc">以下操作会立即清空脚本在<b>当前浏览器</b>中的所选数据,且无法撤销。请在操作前务必通过“导出数据”功能进行备份。</div>
                    <div id="s1p-clear-data-options" style="margin-top: 12px; display: flex; flex-direction: column; gap: 8px; background-color: var(--s1p-bg); border: 1px solid var(--s1p-pri); border-radius: 6px; padding: 12px;">
                        </div>
                    <div style="margin-top: 12px; display: flex; justify-content: space-between; align-items: center;">
                        <div style="display: flex; align-items: center; gap: 12px;">
                            <label class="s1p-settings-label" for="s1p-clear-select-all">全选</label>
                            <label class="s1p-switch">
                                <input type="checkbox" id="s1p-clear-select-all">
                                <span class="s1p-slider"></span>
                            </label>
                        </div>
                        <button id="s1p-clear-selected-btn" class="s1p-btn s1p-red-btn">清除选中数据</button>
                    </div>
                </div>
            </div>
            <div class="s1p-modal-footer">版本: ${SCRIPT_VERSION} (${SCRIPT_RELEASE_DATE})</div>
        </div>`;

    const modalContent = modal.querySelector(".s1p-modal-content");
    if (requiredWidth > 600) {
      modalContent.style.width = `${requiredWidth}px`;
    }

    document.body.appendChild(modal);
    updateLastSyncTimeDisplay();

    const tabs = {
      "general-settings": modal.querySelector("#s1p-tab-general-settings"),
      threads: modal.querySelector("#s1p-tab-threads"),
      users: modal.querySelector("#s1p-tab-users"),
      tags: modal.querySelector("#s1p-tab-tags"),
      bookmarks: modal.querySelector("#s1p-tab-bookmarks"),
      "nav-settings": modal.querySelector("#s1p-tab-nav-settings"),
      sync: modal.querySelector("#s1p-tab-sync"),
    };
    const dataClearanceConfig = {
      blockedThreads: {
        label: "手动屏蔽的帖子和用户主题帖",
        clear: () => saveBlockedThreads({}),
      },
      blockedUsers: {
        label: "屏蔽的用户列表",
        clear: () => saveBlockedUsers({}),
      },
      userTags: { label: "全部用户标记", clear: () => saveUserTags({}) },
      titleFilterRules: {
        label: "标题关键字屏蔽规则",
        clear: () => {
          saveTitleFilterRules([]);
          GM_setValue("s1p_title_keywords", null);
        },
      },
      readProgress: {
        label: "所有帖子阅读进度",
        clear: () => saveReadProgress({}),
      },
      bookmarkedReplies: {
        label: "收藏的回复",
        clear: () => saveBookmarkedReplies({}),
      },
      settings: {
        label: "界面、导航栏及其他设置",
        clear: () => saveSettings(defaultSettings),
      },
    };
    const clearDataOptionsContainer = modal.querySelector(
      "#s1p-clear-data-options"
    );
    if (clearDataOptionsContainer) {
      clearDataOptionsContainer.innerHTML = Object.keys(dataClearanceConfig)
        .map(
          (key) => `
                <div class="s1p-settings-item" style="padding: 8px 0;">
                    <label class="s1p-settings-label" for="s1p-clear-chk-${key}">${dataClearanceConfig[key].label}</label>
                    <label class="s1p-switch">
                        <input type="checkbox" class="s1p-clear-data-checkbox" id="s1p-clear-chk-${key}" data-clear-key="${key}">
                        <span class="s1p-slider"></span>
                    </label>
                </div>
            `
        )
        .join("");
    }

    const remoteToggle = modal.querySelector("#s1p-remote-enabled-toggle");
    const controlsWrapper = modal.querySelector(
      "#s1p-remote-sync-controls-wrapper"
    );
    const updateRemoteSyncInputsState = () => {
      const isMasterEnabled = remoteToggle.checked;
      controlsWrapper.classList.toggle("is-disabled", !isMasterEnabled);
      controlsWrapper
        .querySelectorAll("[data-s1p-sync-control]")
        .forEach((el) => {
          el.disabled = !isMasterEnabled;
        });
    };

    // [MODIFIED] Start of changes
    const dailySyncToggle = modal.querySelector(
      "#s1p-daily-first-load-sync-enabled-toggle"
    );
    const forcePullWrapper = modal.querySelector(
      "#s1p-tab-sync .s1p-settings-sub-group"
    );
    const forcePullToggle = modal.querySelector(
      "#s1p-force-pull-on-startup-toggle"
    );

    const updateForcePullState = () => {
      const isDailySyncEnabled = dailySyncToggle.checked;
      if (isDailySyncEnabled) {
        forcePullWrapper.style.opacity = "1";
        forcePullWrapper.style.pointerEvents = "auto";
        forcePullToggle.disabled = false;
      } else {
        forcePullWrapper.style.opacity = "0.5";
        forcePullWrapper.style.pointerEvents = "none";
        forcePullToggle.checked = false;
        forcePullToggle.disabled = true;
        // 触发一次change事件以确保设置能被保存
        forcePullToggle.dispatchEvent(new Event("change"));
      }
    };

    dailySyncToggle.addEventListener("change", updateForcePullState);
    // End of changes

    const settings = getSettings();
    remoteToggle.checked = settings.syncRemoteEnabled;
    const directChoiceModeToggle = modal.querySelector(
      "#s1p-direct-choice-mode-toggle"
    );
    if (directChoiceModeToggle) {
      directChoiceModeToggle.checked = settings.syncDirectChoiceMode;
      directChoiceModeToggle.addEventListener("change", (e) => {
        const currentSettings = getSettings();
        currentSettings.syncDirectChoiceMode = e.target.checked;
        saveSettings(currentSettings);
        initializeNavbar();
      });
    }

    modal.querySelector("#s1p-daily-first-load-sync-enabled-toggle").checked =
      settings.syncDailyFirstLoad;
    modal.querySelector("#s1p-auto-sync-enabled-toggle").checked =
      settings.syncAutoEnabled;
    modal.querySelector("#s1p-force-pull-on-startup-toggle").checked =
      settings.syncForcePullOnStartup;
    modal.querySelector("#s1p-remote-gist-id-input").value =
      settings.syncRemoteGistId || "";
    modal.querySelector("#s1p-remote-pat-input").value =
      settings.syncRemotePat || "";

    remoteToggle.addEventListener("change", updateRemoteSyncInputsState);
    updateRemoteSyncInputsState();
    updateForcePullState(); // [MODIFIED] 初始化子选项的状态

    const renderTagsTab = (options = {}) => {
      const editingUserId = options.editingUserId;
      const settings = getSettings();
      const isEnabled = settings.enableUserTagging;

      const toggleHTML = `
                <div class="s1p-settings-group">
                    <div class="s1p-settings-item" style="padding: 0; padding-bottom: 16px; border-bottom: 1px solid var(--s1p-pri);">
                        <label class="s1p-settings-label s1p-settings-section-title-label" for="s1p-enableUserTagging">启用用户标记功能</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-enableUserTagging" data-feature="enableUserTagging" class="s1p-feature-toggle" ${
                          isEnabled ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                </div>
            `;
      const userTags = getUserTags();
      const tagItems = Object.entries(userTags).sort(
        ([, a], [, b]) => (b.timestamp || 0) - (a.timestamp || 0)
      );
      const contentHTML = `
                <div class="s1p-settings-group">
                    <div class="s1p-sync-title">用户标记管理</div>
                    <p class="s1p-setting-desc" style="margin-top: 0; margin-bottom: 16px;">
                        在此集中管理、编辑、导出或导入您为所有用户添加的标记。
                    </p>
                    <div class="s1p-local-sync-buttons">
                        <button id="s1p-export-tags-btn" class="s1p-btn">导出全部标记</button>
                        <button id="s1p-import-tags-btn" class="s1p-btn">导入标记</button>
                    </div>
                    <textarea id="s1p-tags-sync-textarea" class="s1p-input s1p-textarea s1p-sync-textarea" placeholder="在此粘贴导入数据或从此处复制导出数据..." autocomplete="off"></textarea>
                </div>

                <div class="s1p-settings-group">
                    <div id="s1p-tags-list-container">
                        ${
                          tagItems.length === 0
                            ? `<div class="s1p-empty">暂无用户标记</div>`
                            : `<div class="s1p-list">${tagItems
                                .map(([id, data]) => {
                                  if (id === editingUserId) {
                                    return `
                                <div class="s1p-item" data-user-id="${id}">
                                    <div class="s1p-item-info">
                                        <div class="s1p-item-title">${data.name}</div>
                                        <div class="s1p-item-meta">
                                            ID: <span class="s1p-item-meta-id">${id}</span>
                                        </div>
                                        <div class="s1p-item-editor">
                                            <textarea class="s1p-input s1p-textarea s1p-tag-edit-area" autocomplete="off">${data.tag}</textarea>
                                        </div>
                                    </div>
                                    <div class="s1p-item-actions">
                                        <button class="s1p-btn s1p-primary" data-action="save-tag-edit" data-user-id="${id}" data-user-name="${data.name}">保存</button>
                                        <button class="s1p-btn" data-action="cancel-tag-edit">取消</button>
                                    </div>
                                </div>`;
                                  } else {
                                    return `
                                <div class="s1p-item" data-user-id="${id}">
                                    <div class="s1p-item-info">
                                        <div class="s1p-item-title">${
                                          data.name
                                        }</div>
                                        <div class="s1p-item-meta">
                                            ID: <span class="s1p-item-meta-id">${id}</span> &nbsp;
                                            标记于: ${formatDate(
                                              data.timestamp
                                            )}
                                        </div>
                                        <div class="s1p-item-content">用户标记:${
                                          data.tag
                                        }</div>
                                    </div>
                                    <div class="s1p-item-actions">
                                        <button class="s1p-btn" data-action="edit-tag-item" data-user-id="${id}">编辑</button>
                                        <button class="s1p-btn s1p-danger" data-action="delete-tag-item" data-user-id="${id}" data-user-name="${
                                      data.name
                                    }">删除</button>
                                    </div>
                                </div>`;
                                  }
                                })
                                .join("")}</div>`
                        }
                    </div>
                </div>
            `;
      tabs["tags"].innerHTML = `
                ${toggleHTML}
                <div class="s1p-feature-content ${isEnabled ? "expanded" : ""}">
                    <div>${contentHTML}</div>
                </div>
            `;
      if (editingUserId) {
        const textarea = tabs["tags"].querySelector(".s1p-tag-edit-area");
        if (textarea) {
          textarea.focus();
          textarea.selectionStart = textarea.selectionEnd =
            textarea.value.length;
        }
      }
    };
    const renderBookmarksTab = () => {
      const settings = getSettings();
      const isEnabled = settings.enableBookmarkReplies;

      const toggleHTML = `
                <div class="s1p-settings-group">
                    <div class="s1p-settings-item" style="padding: 0; padding-bottom: 16px; border-bottom: 1px solid var(--s1p-pri);">
                        <label class="s1p-settings-label s1p-settings-section-title-label" for="s1p-enableBookmarkReplies">启用回复收藏功能</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-enableBookmarkReplies" data-feature="enableBookmarkReplies" class="s1p-feature-toggle" ${
                          isEnabled ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                </div>
            `;
      const bookmarkedReplies = getBookmarkedReplies();
      const bookmarkItems = Object.values(bookmarkedReplies).sort(
        (a, b) => b.timestamp - a.timestamp
      );

      const hasBookmarks = bookmarkItems.length > 0;
      const contentHTML = `
                ${
                  hasBookmarks
                    ? `
                <div class="s1p-settings-group" style="margin-bottom: 16px;">
                    <div class="s1p-search-input-wrapper">
                        <input type="text" id="s1p-bookmark-search-input" class="s1p-input" placeholder="搜索内容、作者、标题..." autocomplete="off">
                        <svg class="s1p-search-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" /></svg>
                        <button id="s1p-bookmark-search-clear-btn" class="s1p-search-clear-btn hidden" title="清空搜索" aria-label="清空搜索">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" /></svg>
                        </button>
                    </div>
                </div>`
                    : ""
                }
                <div class="s1p-settings-group">
                    <div id="s1p-bookmarks-list-container">
                        ${
                          !hasBookmarks
                            ? `<div id="s1p-bookmarks-empty-message" class="s1p-empty">暂无收藏的回复</div>`
                            : `<div class="s1p-list" id="s1p-bookmarks-list">
                            ${bookmarkItems
                              .map((item) => {
                                const fullText = item.postContent || "";
                                const isLong = fullText.length > 150;
                                let contentBlock;
                                if (isLong) {
                                  const previewText = fullText.substring(
                                    0,
                                    150
                                  );
                                  contentBlock = `<div class="s1p-bookmark-preview"><span>${previewText}... </span><a href="javascript:void(0);" class="s1p-bookmark-toggle" data-action="toggle-bookmark-content">查看完整回复</a></div><div class="s1p-bookmark-full" style="display: none;"><span>${fullText} </span><a href="javascript:void(0);" class="s1p-bookmark-toggle" data-action="toggle-bookmark-content">收起</a></div>`;
                                } else {
                                  contentBlock = `<div class="s1p-bookmark-preview"><span>${fullText}</span></div>`;
                                }
                                return `
                            <div class="s1p-item" data-post-id="${
                              item.postId
                            }" style="position: relative;">
                                <button class="s1p-btn s1p-danger" data-action="remove-bookmark" data-post-id="${
                                  item.postId
                                }" style="position: absolute; top: 12px; right: 12px; padding: 4px 8px;">取消收藏</button>
                                <div class="s1p-item-info" style="width: 100%; padding-right: 100px;">
                                    <div class="s1p-item-content">${contentBlock}</div>
                                    <div class="s1p-item-meta" style="margin-top: 10px;">
                                        <strong>${
                                          item.authorName
                                        }</strong> · 收藏于: ${formatDate(
                                  item.timestamp
                                )}
                                        <div class="s1p-bookmark-meta-line">
                                          <span>来自:</span>
                                          <a class="s1p-bookmark-thread-link" href="forum.php?mod=redirect&goto=findpost&ptid=${
                                            item.threadId
                                          }&pid=${
                                  item.postId
                                }" target="_blank" title="${item.threadTitle}">
                                            <span class="s1p-bookmark-title-text">${
                                              item.threadTitle
                                            }</span>
                                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" /></svg>
                                          </a>
                                        </div>
                                    </div>
                                </div>
                            </div>`;
                              })
                              .join("")}
                        </div>`
                        }
                    </div>
                    <div id="s1p-bookmarks-no-results" class="s1p-empty" style="display: none;">没有找到匹配的收藏</div>
                </div>
            `;
      tabs["bookmarks"].innerHTML = `
                ${toggleHTML}
                <div class="s1p-feature-content ${isEnabled ? "expanded" : ""}">
                    <div>${contentHTML}</div>
                </div>
            `;
      tabs["bookmarks"].addEventListener("click", (e) => {
        const toggleLink = e.target.closest(
          '[data-action="toggle-bookmark-content"]'
        );
        if (toggleLink) {
          e.preventDefault();
          e.stopPropagation();
          const contentItem = toggleLink.closest(".s1p-item-content");
          if (!contentItem) return;
          const preview = contentItem.querySelector(".s1p-bookmark-preview");
          const full = contentItem.querySelector(".s1p-bookmark-full");
          if (!preview || !full) return;

          const isCurrentlyCollapsed =
            window.getComputedStyle(full).display === "none";
          if (isCurrentlyCollapsed) {
            full.style.display = "block";
            preview.style.display = "none";
          } else {
            full.style.display = "none";
            preview.style.display = "block";
          }
        }
      });
      if (hasBookmarks) {
        setupBookmarkSearchComponent(tabs["bookmarks"]);
      }
    };
    const renderUserTab = () => {
      const settings = getSettings();
      const isEnabled = settings.enableUserBlocking;

      const toggleHTML = `
                <div class="s1p-settings-group">
                    <div class="s1p-settings-item" style="padding: 0; padding-bottom: 16px; border-bottom: 1px solid var(--s1p-pri);">
                        <label class="s1p-settings-label s1p-settings-section-title-label" for="s1p-enableUserBlocking">启用用户屏蔽功能</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-enableUserBlocking" data-feature="enableUserBlocking" class="s1p-feature-toggle" ${
                          isEnabled ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                </div>
            `;
      const blockedUsers = getBlockedUsers();
      const userItemIds = Object.keys(blockedUsers).sort(
        (a, b) => blockedUsers[b].timestamp - blockedUsers[a].timestamp
      );
      const contentHTML = `
                <div class="s1p-settings-group" style="margin-bottom: 16px; padding-bottom: 0;">
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-blockThreadsOnUserBlock">屏蔽用户时,默认屏蔽其所有主题帖</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-blockThreadsOnUserBlock" class="s1p-settings-checkbox" ${
                          settings.blockThreadsOnUserBlock ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div> 
                    <p class="s1p-setting-desc" style="margin-top: 8px; margin-bottom: 16px;">
                        <strong>提示</strong>:顶部总开关仅影响<strong>未来新屏蔽用户</strong>的默认设置。每个用户下方的独立开关,才是控制该用户主题帖的<strong>最终开关</strong>,拥有最高优先级。
                    </p>                   
                    <div class="s1p-settings-item" style="margin-top: 8px;">
                        <label class="s1p-settings-label" for="s1p-syncWithNativeBlacklist">同步至论坛黑名单</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-syncWithNativeBlacklist" class="s1p-settings-checkbox" data-setting="syncWithNativeBlacklist" ${
                          settings.syncWithNativeBlacklist ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>

                    <p class="s1p-setting-desc" style="margin-top: 8px; margin-bottom: 16px;">
                        <strong>提示</strong>:开启“同步至论坛黑名单”后,新屏蔽的用户会同时加入论坛黑名单。
                    </p>
                </div>
                <div class="s1p-settings-group">
                    <div id="s1p-blocked-user-list-container">
                        ${
                          userItemIds.length === 0
                            ? `<div class="s1p-empty">暂无屏蔽的用户</div>`
                            : `<div class="s1p-list">${userItemIds
                                .map((id) => {
                                  const item = blockedUsers[id];
                                  // [新增] 根据标记生成状态提示
                                  const syncStatusHtml =
                                    item.addedToNativeBlacklist === true
                                      ? '<span class="s1p-native-sync-status">已同步至论坛黑名单</span>'
                                      : "";
                                  return `<div class="s1p-item" data-user-id="${id}"><div class="s1p-item-info"><div class="s1p-item-title">${
                                    item.name || `用户 #${id}`
                                  }${syncStatusHtml}</div><div class="s1p-item-meta">屏蔽时间: ${formatDate(
                                    item.timestamp
                                  )}</div><div class="s1p-item-toggle"><label class="s1p-switch"><input type="checkbox" class="s1p-user-thread-block-toggle" data-user-id="${id}" ${
                                    item.blockThreads ? "checked" : ""
                                  }><span class="s1p-slider"></span></label><span>屏蔽该用户的主题帖</span></div></div><button class="s1p-unblock-btn s1p-btn" data-unblock-user-id="${id}">取消屏蔽</button></div>`;
                                })
                                .join("")}</div>`
                        }
                    </div>
                </div>
            `;
      tabs["users"].innerHTML = `
                ${toggleHTML}
                <div class="s1p-feature-content ${isEnabled ? "expanded" : ""}">
                    <div>${contentHTML}</div>
                </div>
            `;
    };
    const renderThreadTab = () => {
      const settings = getSettings();
      const isEnabled = settings.enablePostBlocking;

      const toggleHTML = `
                <div class="s1p-settings-group">
                    <div class="s1p-settings-item" style="padding: 0; padding-bottom: 16px; border-bottom: 1px solid var(--s1p-pri);">
                        <label class="s1p-settings-label s1p-settings-section-title-label" for="s1p-enablePostBlocking">启用帖子屏蔽功能</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-enablePostBlocking" data-feature="enablePostBlocking" class="s1p-feature-toggle" ${
                          isEnabled ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                </div>
            `;
      const blockedThreads = getBlockedThreads();
      const manualItemIds = Object.keys(blockedThreads).sort(
        (a, b) => blockedThreads[b].timestamp - blockedThreads[a].timestamp
      );
      const contentHTML = `
                <div class="s1p-settings-group">
                    <div class="s1p-settings-group-title">标题关键字屏蔽规则</div>
                    <p class="s1p-setting-desc">将自动屏蔽标题匹配已启用规则的帖子,支持正则表达式。修改后请点击“保存规则”以生效。</p>
                    <div id="s1p-keyword-rules-list" style="display: flex; flex-direction: column; gap: 8px;"></div>
                    <div class="s1p-editor-footer" style="justify-content: flex-start; gap: 8px;">
                         <button id="s1p-keyword-rule-add-btn" class="s1p-btn">添加新规则</button>
                         <button id="s1p-keyword-rules-save-btn" class="s1p-btn">保存规则</button>
                    </div>
                </div>

                <div class="s1p-settings-group">
                    <div id="s1p-blocked-by-keyword-header" class="s1p-settings-group-title s1p-collapsible-header">
                        <span>关键字/规则的屏蔽帖子列表</span>
                        <span class="s1p-expander-arrow ${
                          settings.showBlockedByKeywordList ? "expanded" : ""
                        }"></span>
                    </div>
                    <div id="s1p-dynamically-hidden-list-container" class="s1p-collapsible-content ${
                      settings.showBlockedByKeywordList ? "expanded" : ""
                    }">
                        <div id="s1p-dynamically-hidden-list"></div>
                    </div>
                </div>

                <div class="s1p-settings-group">
                     <div id="s1p-manually-blocked-header" class="s1p-settings-group-title s1p-collapsible-header">
                        <span>手动屏蔽的帖子列表</span>
                        <span class="s1p-expander-arrow ${
                          settings.showManuallyBlockedList ? "expanded" : ""
                        }"></span>
                    </div>
                    <div id="s1p-manually-blocked-list-container" class="s1p-collapsible-content ${
                      settings.showManuallyBlockedList ? "expanded" : ""
                    }">
                    <div>
                    ${
                      manualItemIds.length === 0
                        ? `<div class="s1p-empty">暂无手动屏蔽的帖子</div>`
                        : `<div class="s1p-list">${manualItemIds
                            .map((id) => {
                              const item = blockedThreads[id];
                              return `<div class="s1p-item" data-thread-id="${id}"><div class="s1p-item-info"><div class="s1p-item-title">${
                                item.title || `帖子 #${id}`
                              }</div><div class="s1p-item-meta">屏蔽时间: ${formatDate(
                                item.timestamp
                              )} ${
                                item.reason && item.reason !== "manual"
                                  ? `(因屏蔽用户${item.reason.replace(
                                      "user_",
                                      ""
                                    )})`
                                  : ""
                              }</div></div><button class="s1p-unblock-btn s1p-btn" data-unblock-thread-id="${id}">取消屏蔽</button></div>`;
                            })
                            .join("")}</div>`
                    }
                    </div>
                    </div>
                </div>
            `;

      tabs["threads"].innerHTML = `
                ${toggleHTML}
                <div class="s1p-feature-content ${isEnabled ? "expanded" : ""}">
                    <div>${contentHTML}</div>
                </div>
            `;

      const renderDynamicallyHiddenList = () => {
        const listContainer = tabs["threads"].querySelector(
          "#s1p-dynamically-hidden-list"
        );
        const hiddenItems = Object.entries(dynamicallyHiddenThreads);
        if (hiddenItems.length === 0) {
          listContainer.innerHTML = `<div class="s1p-empty" style="padding-top: 12px;">当前页面没有被关键字屏蔽的帖子</div>`;
        } else {
          listContainer.innerHTML = `<div class="s1p-list">${hiddenItems
            .map(
              ([id, item]) => `
                        <div class="s1p-item" data-thread-id="${id}">
                            <div class="s1p-item-info">
                                <div class="s1p-item-title" title="${item.title}">${item.title}</div>
                                <div class="s1p-item-meta">匹配规则: <code style="background: var(--s1p-code-bg); padding: 2px 4px; border-radius: 3px;">${item.pattern}</code></div>
                            </div>
                        </div>
                    `
            )
            .join("")}</div>`;
        }
      };
      const renderRules = () => {
        const rules = getTitleFilterRules();
        const container = tabs["threads"].querySelector(
          "#s1p-keyword-rules-list"
        );
        if (!container) return;
        container.innerHTML = rules
          .map(
            (rule) => `
                    <div class="s1p-editor-item" data-rule-id="${rule.id}">
                        <label class="s1p-switch"><input type="checkbox" class="s1p-settings-checkbox s1p-keyword-rule-enable" ${
                          rule.enabled ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                        <input type="text" class="s1p-input s1p-keyword-rule-pattern" placeholder="输入关键字或正则表达式" value="${
                          rule.pattern || ""
                        }" autocomplete="off">
                        <div class="s1p-editor-item-controls">
                            <button class="s1p-editor-btn s1p-delete-button" data-action="delete" title="删除规则"></button>
                        </div>
                    </div>
                `
          )
          .join("");
        if (rules.length === 0) {
          container.innerHTML = `<div class="s1p-empty" style="padding: 12px;">暂无规则</div>`;
        }
      };

      renderRules();
      renderDynamicallyHiddenList();
      const saveKeywordRules = () => {
        const newRules = [];
        tabs["threads"]
          .querySelectorAll("#s1p-keyword-rules-list .s1p-editor-item")
          .forEach((item) => {
            const pattern = item
              .querySelector(".s1p-keyword-rule-pattern")
              .value.trim();
            if (pattern) {
              let id = item.dataset.ruleId;
              if (id.startsWith("new_")) {
                id = `rule_${Date.now()}_${Math.random()}`;
              }
              newRules.push({
                id: id,
                enabled: item.querySelector(".s1p-keyword-rule-enable").checked,
                pattern: pattern,
              });
            }
          });
        saveTitleFilterRules(newRules);
        hideThreadsByTitleKeyword();
        renderDynamicallyHiddenList();
        renderRules();
      };

      tabs["threads"].addEventListener("click", (e) => {
        const target = e.target;
        const header = target.closest(".s1p-collapsible-header");

        if (header) {
          if (header.id === "s1p-blocked-by-keyword-header") {
            const currentSettings = getSettings();
            const isNowExpanded = !currentSettings.showBlockedByKeywordList;
            currentSettings.showBlockedByKeywordList = isNowExpanded;
            saveSettings(currentSettings);

            header
              .querySelector(".s1p-expander-arrow")
              .classList.toggle("expanded", isNowExpanded);
            tabs["threads"]
              .querySelector("#s1p-dynamically-hidden-list-container")
              .classList.toggle("expanded", isNowExpanded);
          } else if (header.id === "s1p-manually-blocked-header") {
            const currentSettings = getSettings();
            const isNowExpanded = !currentSettings.showManuallyBlockedList;
            currentSettings.showManuallyBlockedList = isNowExpanded;
            saveSettings(currentSettings);

            header
              .querySelector(".s1p-expander-arrow")
              .classList.toggle("expanded", isNowExpanded);
            tabs["threads"]
              .querySelector("#s1p-manually-blocked-list-container")
              .classList.toggle("expanded", isNowExpanded);
          }
        } else if (target.id === "s1p-keyword-rule-add-btn") {
          const container = tabs["threads"].querySelector(
            "#s1p-keyword-rules-list"
          );
          const emptyMsg = container.querySelector(".s1p-empty");
          if (emptyMsg) emptyMsg.remove();

          const newItem = document.createElement("div");
          newItem.className = "s1p-editor-item";
          newItem.dataset.ruleId = `new_${Date.now()}`;
          newItem.innerHTML = `
                        <label class="s1p-switch"><input type="checkbox" class="s1p-settings-checkbox s1p-keyword-rule-enable" checked><span class="s1p-slider"></span></label>
                        <input type="text" class="s1p-input s1p-keyword-rule-pattern" placeholder="输入关键字或正则表达式" value="" autocomplete="off">
                        <div class="s1p-editor-item-controls">
                            <button class="s1p-editor-btn s1p-delete-button" data-action="delete" title="删除规则"></button>
                        </div>
                    `;
          container.appendChild(newItem);
          newItem.querySelector('input[type="text"]').focus();
        } else if (target.closest(".s1p-delete-button")) {
          const item = target.closest(".s1p-editor-item");
          if (item) {
            const pattern =
              item.querySelector(".s1p-keyword-rule-pattern").value.trim() ||
              "空规则";
            createConfirmationModal(
              "确认删除该屏蔽规则吗?",
              `规则内容: <code style="background-color: var(--s1p-secondary-bg); padding: 2px 4px; border-radius: 4px;">${pattern}</code><br>此操作将立即生效并从存储中删除该规则。`,
              () => {
                const ruleIdToDelete = item.dataset.ruleId;
                if (!ruleIdToDelete || ruleIdToDelete.startsWith("new_")) {
                  item.remove();
                  const container = tabs["threads"].querySelector(
                    "#s1p-keyword-rules-list"
                  );
                  if (container.children.length === 0) {
                    container.innerHTML = `<div class="s1p-empty" style="padding: 12px;">暂无规则</div>`;
                  }
                  showMessage("未保存的新规则已移除。", null);
                  return;
                }
                const currentRules = getTitleFilterRules();
                const newRules = currentRules.filter(
                  (rule) => rule.id !== ruleIdToDelete
                );
                saveTitleFilterRules(newRules);
                hideThreadsByTitleKeyword();
                renderDynamicallyHiddenList();
                renderRules();
                showMessage("规则已成功删除。", true);
              },
              "确认删除"
            );
          }
        } else if (target.id === "s1p-keyword-rules-save-btn") {
          saveKeywordRules();
          showMessage("规则已保存!", true);
        }
      });
    };
    const renderGeneralSettingsTab = () => {
      const settings = getSettings();
      const openTabSettings = settings.openInNewTab;

      tabs["general-settings"].innerHTML = `
        <div class="s1p-settings-group">
            <div class="s1p-settings-item" style="padding: 0; padding-bottom: 16px; border-bottom: 1px solid var(--s1p-pri);">
                <label class="s1p-settings-label s1p-settings-section-title-label" for="s1p-enableGeneralSettings">启用通用设置</label>
                <label class="s1p-switch">
                    <input type="checkbox" id="s1p-enableGeneralSettings" data-feature="enableGeneralSettings" class="s1p-feature-toggle" ${
                      settings.enableGeneralSettings ? "checked" : ""
                    }>
                    <span class="s1p-slider"></span>
                </label>
            </div>
        </div>

        <div class="s1p-feature-content ${
          settings.enableGeneralSettings ? "expanded" : ""
        }">
            <div>
                <div class="s1p-settings-group">
                    <div class="s1p-settings-group-title">阅读/浏览增强</div>
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-openInNewTab-master">在新标签页打开帖子/版块等链接</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-openInNewTab-master" class="s1p-settings-checkbox" data-setting="openInNewTab.master" ${
                          openTabSettings.master ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                    <p class="s1p-setting-desc" style="margin-top: -4px;">开启后,下方选中的链接类型将在新标签页打开。仅对顶部导航、帖子列表、消息提醒区域内的链接生效。</p>
                    
                    <div class="s1p-settings-sub-group" style="${
                      !openTabSettings.master
                        ? "opacity: 0.5; pointer-events: none;"
                        : ""
                    }">

                        <div class="s1p-settings-item">
                            <label class="s1p-settings-label" for="s1p-openThreadListInNewTab">在新标签页打开帖子/消息链接</label>
                            <label class="s1p-switch"><input type="checkbox" id="s1p-openThreadListInNewTab" class="s1p-settings-checkbox" data-setting="openInNewTab.threadList" ${
                              openTabSettings.threadList ? "checked" : ""
                            }><span class="s1p-slider"></span></label>
                        </div>
                         <p class="s1p-setting-desc" style="margin-top: -4px;">控制帖子列表和消息提醒区域内的常规链接。</p>
                        <div class="s1p-settings-item" id="s1p-openThreadListInBackground-item" style="padding-left: 20px; ${
                          !openTabSettings.threadList ? "display: none;" : ""
                        }">
                            <label class="s1p-settings-label">在后台打开</label>
                            <label class="s1p-switch"><input type="checkbox" id="s1p-openThreadListInBackground" class="s1p-settings-checkbox" data-setting="openInNewTab.threadListInBackground" ${
                              openTabSettings.threadListInBackground
                                ? "checked"
                                : ""
                            }><span class="s1p-slider"></span></label>
                        </div>

                        <div style="margin: 12px 0 8px 0; border-top: 1px solid var(--s1p-pri);"></div>

                        <div class="s1p-settings-item">
                            <label class="s1p-settings-label" for="s1p-openProgressInNewTab">在新标签页打开阅读进度跳转</label>
                            <label class="s1p-switch"><input type="checkbox" id="s1p-openProgressInNewTab" class="s1p-settings-checkbox" data-setting="openInNewTab.progress" ${
                              openTabSettings.progress ? "checked" : ""
                            }><span class="s1p-slider"></span></label>
                        </div>
                        <div class="s1p-settings-item" id="s1p-openProgressInBackground-item" style="padding-left: 20px; ${
                          !openTabSettings.progress ? "display: none;" : ""
                        }">
                            <label class="s1p-settings-label">在后台打开</label>
                            <label class="s1p-switch"><input type="checkbox" id="s1p-openProgressInBackground" class="s1p-settings-checkbox" data-setting="openInNewTab.progressInBackground" ${
                              openTabSettings.progressInBackground
                                ? "checked"
                                : ""
                            }><span class="s1p-slider"></span></label>
                        </div>

                        <div style="margin: 12px 0 8px 0; border-top: 1px solid var(--s1p-pri);"></div>

                        <div class="s1p-settings-item">
                            <label class="s1p-settings-label" for="s1p-openNavInNewTab">在新标签页打开顶部导航/菜单链接</label> 
                            <label class="s1p-switch"><input type="checkbox" id="s1p-openNavInNewTab" class="s1p-settings-checkbox" data-setting="openInNewTab.nav" ${
                              openTabSettings.nav ? "checked" : ""
                            }><span class="s1p-slider"></span></label>
                        </div>
                        <div class="s1p-settings-item" id="s1p-openNavInBackground-item" style="padding-left: 20px; ${
                          !openTabSettings.nav ? "display: none;" : ""
                        }">
                            <label class="s1p-settings-label" >在后台打开</label>
                            <label class="s1p-switch"><input type="checkbox" id="s1p-openNavInBackground" class="s1p-settings-checkbox" data-setting="openInNewTab.navInBackground" ${
                              openTabSettings.navInBackground ? "checked" : ""
                            }><span class="s1p-slider"></span></label>
                        </div>
                    </div>

                     <div class="s1p-settings-item" style="margin-top: 16px;">
                        <label class="s1p-settings-label" for="s1p-enableReadProgress">启用阅读进度跟踪</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-enableReadProgress" data-feature="enableReadProgress" class="s1p-feature-toggle" ${
                          settings.enableReadProgress ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                    <div class="s1p-feature-content ${
                      settings.enableReadProgress ? "expanded" : ""
                    }">
                      <div class="s1p-settings-sub-group">
                        <div class="s1p-settings-item" id="s1p-showReadIndicator-container">
                            <label class="s1p-settings-label" for="s1p-showReadIndicator">显示“当前阅读位置”浮动标识</label>
                            <label class="s1p-switch"><input type="checkbox" id="s1p-showReadIndicator" class="s1p-settings-checkbox" data-setting="showReadIndicator" ${
                              settings.showReadIndicator ? "checked" : ""
                            }><span class="s1p-slider"></span></label>
                        </div>
                        <div class="s1p-settings-item" id="s1p-readingProgressCleanupContainer">
                            <label class="s1p-settings-label">自动清理超过以下时间的阅读记录</label>
                            <div id="s1p-readingProgressCleanupDays-control" class="s1p-segmented-control">
                                <div class="s1p-segmented-control-slider"></div>
                                <div class="s1p-segmented-control-option ${
                                  settings.readingProgressCleanupDays == 30
                                    ? "active"
                                    : ""
                                }" data-value="30">1个月</div>
                                <div class="s1p-segmented-control-option ${
                                  settings.readingProgressCleanupDays == 90
                                    ? "active"
                                    : ""
                                }" data-value="90">3个月</div>
                                <div class="s1p-segmented-control-option ${
                                  settings.readingProgressCleanupDays == 180
                                    ? "active"
                                    : ""
                                }" data-value="180">6个月</div>
                                <div class="s1p-segmented-control-option ${
                                  settings.readingProgressCleanupDays == 0
                                    ? "active"
                                    : ""
                                }" data-value="0">永不</div>
                            </div>
                        </div>
                      </div>
                    </div>
                     <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-hideImagesByDefault">默认隐藏帖子图片</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-hideImagesByDefault" class="s1p-settings-checkbox" data-setting="hideImagesByDefault" ${
                          settings.hideImagesByDefault ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                </div>
                <div class="s1p-settings-group">
                    <div class="s1p-settings-group-title">界面与个性化</div>
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-recommendS1Nux">推荐 S1 NUX 安装</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-recommendS1Nux" class="s1p-settings-checkbox" data-setting="recommendS1Nux" ${
                          settings.recommendS1Nux ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                    <p class="s1p-setting-desc">S1 Plus 与 S1 NUX 论坛美化美化扩展搭配使用效果更佳。开启后,若检测到您未安装 S1 NUX,脚本会适时弹出对话框进行推荐。</p>
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-enhanceFloatingControls">使用 S1 Plus 增强型悬浮控件</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-enhanceFloatingControls" class="s1p-settings-checkbox" data-setting="enhanceFloatingControls" ${
                          settings.enhanceFloatingControls ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                    <p class="s1p-setting-desc">开启后,将使用脚本提供的全新悬停展开式控件;关闭则恢复使用论坛原生的滚动控件。</p>
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-changeLogoLink">修改论坛Logo链接 (指向论坛首页)</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-changeLogoLink" class="s1p-settings-checkbox" data-setting="changeLogoLink" ${
                          settings.changeLogoLink ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-hideBlacklistTip">隐藏已屏蔽用户发言的黄条提示</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-hideBlacklistTip" class="s1p-settings-checkbox" data-setting="hideBlacklistTip" ${
                          settings.hideBlacklistTip ? "checked" : ""
                        }><span class="s1p-slider"></span></label>
                    </div>
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-customTitleSuffix">自定义标题后缀</label>
                        <input type="text" id="s1p-customTitleSuffix" class="s1p-input" data-setting="customTitleSuffix" value="${
                          settings.customTitleSuffix || ""
                        }" autocomplete="off">
                    </div>
                </div>
            </div>
        </div>`;

      const tabContent = tabs["general-settings"];
      const masterSwitch = tabContent.querySelector("#s1p-openInNewTab-master");
      const subOptionsContainer = tabContent.querySelector(
        ".s1p-settings-sub-group"
      );

      const setupSubSwitch = (mainSwitchId, subItemContainerId) => {
        const mainSwitch = tabContent.querySelector(`#${mainSwitchId}`);
        const subItemContainer = tabContent.querySelector(
          `#${subItemContainerId}`
        );
        if (mainSwitch && subItemContainer) {
          mainSwitch.addEventListener("change", (e) => {
            subItemContainer.style.display = e.target.checked ? "flex" : "none";
          });
        }
      };

      masterSwitch.addEventListener("change", (e) => {
        if (e.target.checked) {
          subOptionsContainer.style.opacity = "1";
          subOptionsContainer.style.pointerEvents = "auto";
        } else {
          subOptionsContainer.style.opacity = "0.5";
          subOptionsContainer.style.pointerEvents = "none";
        }
      });

      setupSubSwitch(
        "s1p-openThreadListInNewTab",
        "s1p-openThreadListInBackground-item"
      );
      setupSubSwitch(
        "s1p-openProgressInNewTab",
        "s1p-openProgressInBackground-item"
      );
      setupSubSwitch("s1p-openNavInNewTab", "s1p-openNavInBackground-item");

      const moveSlider = (control) => {
        if (!control) return;
        requestAnimationFrame(() => {
          const slider = control.querySelector(".s1p-segmented-control-slider");
          const activeOption = control.querySelector(
            ".s1p-segmented-control-option.active"
          );
          if (
            slider &&
            activeOption &&
            activeOption.offsetWidth > 0 &&
            activeOption.offsetParent !== null
          ) {
            slider.style.width = `${activeOption.offsetWidth}px`;
            slider.style.transform = `translateX(${activeOption.offsetLeft}px)`;
          } else if (slider && activeOption) {
            setTimeout(() => moveSlider(control), 100);
          }
        });
      };

      const cleanupControl = tabs["general-settings"].querySelector(
        "#s1p-readingProgressCleanupDays-control"
      );
      if (cleanupControl) {
        moveSlider(cleanupControl);
        cleanupControl.addEventListener("click", (e) => {
          const target = e.target.closest(".s1p-segmented-control-option");
          if (!target || target.classList.contains("active")) return;
          const newValue = parseInt(target.dataset.value, 10);
          const currentSettings = getSettings();
          currentSettings.readingProgressCleanupDays = newValue;
          saveSettings(currentSettings);
          cleanupControl
            .querySelectorAll(".s1p-segmented-control-option")
            .forEach((opt) => opt.classList.remove("active"));
          target.classList.add("active");
          moveSlider(cleanupControl);
        });
      }
    };
    const renderNavSettingsTab = () => {
      const settings = getSettings();
      tabs["nav-settings"].innerHTML = `
        <div class="s1p-settings-group">
            <div class="s1p-settings-item" style="padding: 0; padding-bottom: 16px; border-bottom: 1px solid var(--s1p-pri);">
                <label class="s1p-settings-label s1p-settings-section-title-label" for="s1p-enableNavCustomization">启用自定义导航栏</label>
                <label class="s1p-switch">
                    <input type="checkbox" id="s1p-enableNavCustomization" data-feature="enableNavCustomization" class="s1p-feature-toggle" ${
                      settings.enableNavCustomization ? "checked" : ""
                    }>
                    <span class="s1p-slider"></span>
                </label>
            </div>
        </div>

        <div class="s1p-feature-content ${
          settings.enableNavCustomization ? "expanded" : ""
        }">
            <div>
                <div class="s1p-settings-group">
                    <div class="s1p-settings-group-title">导航链接编辑器</div>
                    <div class="s1p-list s1p-nav-editor-list"></div>
                    <div class="s1p-editor-footer">
                        <div style="display: flex; gap: 8px;">
                            <button id="s1p-nav-add-btn" class="s1p-btn">添加新链接</button>
                            <button id="s1p-settings-save-btn" class="s1p-btn">保存设置</button>
                        </div>
                        <button id="s1p-nav-restore-btn" class="s1p-btn s1p-red-btn">恢复默认导航</button>
                    </div>
                </div>
            </div>
        </div>`;

      const navListContainer = tabs["nav-settings"].querySelector(
        ".s1p-nav-editor-list"
      );
      const renderNavList = (links) => {
        navListContainer.innerHTML = (links || [])
          .map(
            (link, index) => `
            <div class="s1p-editor-item" draggable="true" data-index="${index}" style="grid-template-columns: auto 1fr 1fr auto; user-select: none;">
                <div class="s1p-drag-handle">::</div>
                <input type="text" class="s1p-input s1p-nav-name" placeholder="名称" value="${
                  link.name || ""
                }" autocomplete="off">
                <input type="text" class="s1p-input s1p-nav-href" placeholder="链接" value="${
                  link.href || ""
                }" autocomplete="off">
                <div class="s1p-editor-item-controls"><button class="s1p-editor-btn s1p-delete-button" data-action="delete" title="删除链接"></button></div>
            </div>`
          )
          .join("");
      };

      renderNavList(settings.customNavLinks);

      let draggedItem = null;
      navListContainer.addEventListener("dragstart", (e) => {
        if (e.target.classList.contains("s1p-editor-item")) {
          draggedItem = e.target;
          setTimeout(() => {
            e.target.classList.add("s1p-dragging");
          }, 0);
        }
      });
      navListContainer.addEventListener("dragend", (e) => {
        if (draggedItem) {
          draggedItem.classList.remove("s1p-dragging");
          draggedItem = null;
        }
      });
      navListContainer.addEventListener("dragover", (e) => {
        e.preventDefault();
        if (!draggedItem) return;

        const container = e.currentTarget;
        const otherItems = [
          ...container.querySelectorAll(".s1p-editor-item:not(.s1p-dragging)"),
        ];
        const nextSibling = otherItems.find((item) => {
          const rect = item.getBoundingClientRect();
          return e.clientY < rect.top + rect.height / 2;
        });

        if (nextSibling) {
          container.insertBefore(draggedItem, nextSibling);
        } else {
          container.appendChild(draggedItem);
        }
      });
      tabs["nav-settings"].addEventListener("click", (e) => {
        const target = e.target;
        if (target.id === "s1p-nav-add-btn") {
          const newItem = document.createElement("div");
          newItem.className = "s1p-editor-item";
          newItem.draggable = true;
          newItem.style.gridTemplateColumns = "auto 1fr 1fr auto";
          newItem.innerHTML = `<div class="s1p-drag-handle">::</div><input type="text" class="s1p-input s1p-nav-name" placeholder="新链接" autocomplete="off"><input type="text" class="s1p-input s1p-nav-href" placeholder="forum.php" autocomplete="off"><div class="s1p-editor-item-controls"><button class="s1p-editor-btn s1p-delete-button" data-action="delete" title="删除链接"></button></div>`;
          navListContainer.appendChild(newItem);
        } else if (target.closest(".s1p-delete-button")) {
          const item = target.closest(".s1p-editor-item");
          if (item) {
            const name =
              item.querySelector(".s1p-nav-name").value.trim() || "未命名链接";
            createConfirmationModal(
              "确认删除该导航链接吗?",
              `链接名称: ${name}<br>此操作仅在UI上移除,需要点击下方的“保存设置”按钮才会真正生效。`,
              () => {
                item.remove();
                showMessage("链接已从列表移除。", true);
              },
              "确认删除"
            );
          }
        } else if (target.id === "s1p-nav-restore-btn") {
          createConfirmationModal(
            "确认要恢复默认导航栏吗?",
            "您当前的自定义导航链接将被重置为脚本的默认设置。",
            () => {
              const currentSettings = getSettings();
              currentSettings.enableNavCustomization =
                defaultSettings.enableNavCustomization;
              currentSettings.customNavLinks = defaultSettings.customNavLinks;
              saveSettings(currentSettings);
              renderNavSettingsTab();
              initializeNavbar();
              showMessage("导航栏已恢复为默认设置!", true);
            },
            "确认恢复"
          );
        } else if (target.id === "s1p-settings-save-btn") {
          const newSettings = {
            ...getSettings(),
            enableNavCustomization: tabs["nav-settings"].querySelector(
              "#s1p-enableNavCustomization"
            ).checked,
            customNavLinks: Array.from(
              navListContainer.querySelectorAll(".s1p-editor-item")
            )
              .map((item) => ({
                name: item.querySelector(".s1p-nav-name").value.trim(),
                href: item.querySelector(".s1p-nav-href").value.trim(),
              }))
              .filter((l) => l.name && l.href),
          };
          saveSettings(newSettings);
          initializeNavbar();
          showMessage("设置已保存!", true);
        }
      });
    };

    renderGeneralSettingsTab();
    renderThreadTab();
    renderUserTab();
    renderTagsTab();
    renderBookmarksTab();
    renderNavSettingsTab();

    const tabContainer = modal.querySelector(".s1p-tabs");
    setTimeout(() => moveTabSlider(tabContainer), 50);

    modal.style.transition = "opacity 0.2s ease-out";
    requestAnimationFrame(() => {
      modal.style.opacity = "1";
    });
    // [REPLACE ENTIRE EVENT LISTENER BLOCK]
    modal.addEventListener("change", (e) => {
      const target = e.target;
      const settings = getSettings();
      const featureKey = target.dataset.feature;
      const settingKey = target.dataset.setting;

      if (settingKey) {
        const value =
          target.type === "checkbox"
            ? target.checked
            : target.type === "number" || target.tagName === "SELECT"
            ? parseInt(target.value, 10)
            : target.value;

        // [MODIFIED] 使用新的辅助函数来处理嵌套和非嵌套设置
        setNestedValue(settings, settingKey, value);
        saveSettings(settings);

        if (settingKey === "enhanceFloatingControls") {
          applyChanges();
          return;
        }

        applyInterfaceCustomizations();
        if (settingKey === "showReadIndicator" && !target.checked) {
          updateReadIndicatorUI(null);
        }
        if (settingKey === "hideImagesByDefault") {
          applyImageHiding();
          manageImageToggleAllButtons();
        }
        // [MODIFIED] 当任何一个新标签页设置改变时,都重新应用全局行为
        if (settingKey.startsWith("openInNewTab.")) {
          applyGlobalLinkBehavior();
          // 如果是阅读进度相关的设置改变了,则刷新按钮
          if (settingKey.includes("progress")) {
            removeProgressJumpButtons();
            addProgressJumpButtons();
          }
        }
      }

      if (featureKey && target.classList.contains("s1p-feature-toggle")) {
        let contentWrapper =
          target.closest(".s1p-settings-item")?.nextElementSibling;
        if (
          !contentWrapper ||
          !contentWrapper.classList.contains("s1p-feature-content")
        ) {
          contentWrapper = target.closest(
            ".s1p-settings-group"
          )?.nextElementSibling;
        }

        const modalBody = modal.querySelector(".s1p-modal-body");

        if (
          !modalBody ||
          !contentWrapper ||
          !contentWrapper.classList.contains("s1p-feature-content")
        ) {
          console.warn(
            "S1 Plus Debug: Animation structure not found for this toggle, but will proceed with saving."
          );
        }

        const isChecked = target.checked;
        settings[featureKey] = isChecked;
        saveSettings(settings);

        if (
          contentWrapper &&
          contentWrapper.classList.contains("s1p-feature-content")
        ) {
          const oldHeight = modalBody.offsetHeight;
          modalBody.style.height = `${oldHeight}px`;
          contentWrapper.classList.toggle("expanded", isChecked);
          requestAnimationFrame(() => {
            modalBody.style.height = "auto";
            const newHeight = modalBody.offsetHeight;
            modalBody.style.height = `${oldHeight}px`;
            requestAnimationFrame(() => {
              modalBody.style.height = `${newHeight}px`;
            });
          });
          modalBody.addEventListener(
            "transitionend",
            function onEnd() {
              modalBody.removeEventListener("transitionend", onEnd);
              modalBody.style.height = "auto";
            },
            { once: true }
          );
        }

        switch (featureKey) {
          case "enableGeneralSettings":
            applyInterfaceCustomizations();
            applyGlobalLinkBehavior();
            applyImageHiding();
            manageImageToggleAllButtons();
            if (isChecked) {
              addProgressJumpButtons();
            } else {
              removeProgressJumpButtons();
              updateReadIndicatorUI(null);
            }
            renderGeneralSettingsTab();
            break;
          case "enablePostBlocking":
            isChecked
              ? addBlockButtonsToThreads()
              : removeBlockButtonsFromThreads();
            renderThreadTab();
            break;
          case "enableUserBlocking":
            refreshAllAuthiActions();
            if (isChecked) {
              hideBlockedUsersPosts();
              hideBlockedUserQuotes();
              hideBlockedUserRatings();
            } else {
              Object.keys(getBlockedUsers()).forEach(showUserPosts);
            }
            renderUserTab();
            break;
          case "enableUserTagging":
            refreshAllAuthiActions();
            renderTagsTab();
            break;
          case "enableReadProgress":
            renderGeneralSettingsTab();
            isChecked ? addProgressJumpButtons() : removeProgressJumpButtons();
            if (!isChecked) {
              updateReadIndicatorUI(null);
            }
            break;
          case "enableBookmarkReplies":
            refreshAllAuthiActions();
            renderBookmarksTab();
            break;
          case "enableNavCustomization":
            initializeNavbar();
            renderNavSettingsTab(); // 重新渲染以更新其内部的设置状态
            break;
        }
        return;
      } else if (target.matches(".s1p-user-thread-block-toggle")) {
        const userId = target.dataset.userId;
        const blockThreads = target.checked;
        const users = getBlockedUsers();
        if (users[userId]) {
          users[userId].blockThreads = blockThreads;
          saveBlockedUsers(users);
          if (blockThreads) applyUserThreadBlocklist();
          else unblockThreadsByUser(userId);
          renderThreadTab();
        }
      } else if (target.matches("#s1p-blockThreadsOnUserBlock")) {
        const currentSettings = getSettings();
        currentSettings.blockThreadsOnUserBlock = target.checked;
        saveSettings(currentSettings);
      }
    });

    function removeListItem(triggerElement, emptyHTML, onEmptyCallback) {
      const item = triggerElement.closest(".s1p-item");
      if (!item) return;

      const list = item.parentElement;
      item.remove();

      if (list && list.children.length === 0) {
        const container = list.parentElement;
        if (container) {
          container.innerHTML = emptyHTML;
        }
        if (onEmptyCallback) {
          onEmptyCallback(container);
        }
      }
    }

    modal.addEventListener("click", async (e) => {
      const target = e.target;
      if (e.target.matches(".s1p-modal, .s1p-modal-close")) modal.remove();
      if (e.target.matches(".s1p-tab-btn")) {
        const tabContainer = e.target.closest(".s1p-tabs");
        modal
          .querySelectorAll(".s1p-tab-btn, .s1p-tab-content")
          .forEach((el) => el.classList.remove("active"));
        e.target.classList.add("active");
        const activeTab = tabs[e.target.dataset.tab];
        if (activeTab) activeTab.classList.add("active");
        moveTabSlider(tabContainer);
      }

      const unblockThreadId = e.target.dataset.unblockThreadId;
      if (unblockThreadId) {
        const item = target.closest(".s1p-item");
        const title = item
          ? item.querySelector(".s1p-item-title").textContent.trim()
          : `帖子 #${unblockThreadId}`;
        createConfirmationModal(
          "确认取消屏蔽该帖子吗?",
          `帖子标题: ${title}`,
          () => {
            unblockThread(unblockThreadId);
            removeListItem(
              target,
              '<div class="s1p-empty">暂无手动屏蔽的帖子</div>'
            );
            showMessage("帖子已取消屏蔽。", true);
          },
          "确认取消"
        );
      }

      const unblockUserId = e.target.dataset.unblockUserId;
      if (unblockUserId) {
        const item = target.closest(".s1p-item");

        // 通过克隆、移除状态元素的方式,来安全地获取纯净的用户名。
        let userName;
        if (item) {
          const titleEl = item.querySelector(".s1p-item-title");
          if (titleEl) {
            const titleClone = titleEl.cloneNode(true);
            const statusSpan = titleClone.querySelector(
              ".s1p-native-sync-status"
            );
            if (statusSpan) {
              statusSpan.remove();
            }
            userName = titleClone.textContent.trim();
          } else {
            userName = `用户 #${unblockUserId}`;
          }
        } else {
          userName = `用户 #${unblockUserId}`;
        }

        createConfirmationModal(
          `确认取消屏蔽 “${userName}” 吗?`,
          "该用户及其主题帖(如果已关联屏蔽)将被取消屏蔽。",
          async () => {
            const allBlockedThreads = getBlockedThreads();
            const threadsToUnblock = Object.keys(allBlockedThreads).filter(
              (threadId) =>
                allBlockedThreads[threadId].reason === `user_${unblockUserId}`
            );

            const blockedUsers = getBlockedUsers();
            const userToUnblockData = blockedUsers[unblockUserId];
            const wasSynced =
              userToUnblockData?.addedToNativeBlacklist === true;

            const success = await unblockUser(unblockUserId);

            if (success) {
              removeListItem(
                target,
                '<div class="s1p-empty">暂无屏蔽的用户</div>'
              );

              const threadList = document.querySelector(
                "#s1p-manually-blocked-list-container .s1p-list"
              );
              if (threadList) {
                threadsToUnblock.forEach((threadId) => {
                  const threadItemToRemove = threadList.querySelector(
                    `.s1p-item[data-thread-id="${threadId}"]`
                  );
                  threadItemToRemove?.remove();
                });
                if (threadList.children.length === 0) {
                  const container = threadList.closest(
                    "#s1p-manually-blocked-list-container"
                  );
                  if (container) {
                    container.innerHTML =
                      '<div class="s1p-empty">暂无手动屏蔽的帖子</div>';
                  }
                }
              }
              const message = wasSynced
                ? `已取消对 ${userName} 的屏蔽并从论坛同步移除。`
                : `已取消对 ${userName} 的屏蔽。`;
              showMessage(message, true);
            } else {
              showMessage(
                `脚本内取消屏蔽成功,但同步移除论坛黑名单失败。`,
                false
              );
            }
          },
          "确认取消"
        );
      }

      const removeBookmarkId = target.closest('[data-action="remove-bookmark"]')
        ?.dataset.postId;
      if (removeBookmarkId) {
        createConfirmationModal(
          "确认取消收藏该回复吗?",
          "此操作将从您的收藏列表中永久移除该条目。",
          () => {
            const bookmarks = getBookmarkedReplies();
            delete bookmarks[removeBookmarkId];
            saveBookmarkedReplies(bookmarks);
            refreshSinglePostActions(removeBookmarkId);
            removeListItem(
              target,
              '<div class="s1p-empty">暂无收藏的回复</div>',
              () => {
                document
                  .querySelector("#s1p-bookmark-search-input")
                  ?.closest(".s1p-settings-group")
                  ?.remove();
              }
            );
            showMessage("已取消收藏。", true);
          },
          "确认取消"
        );
      }

      const syncTextarea = modal.querySelector("#s1p-local-sync-textarea");
      if (e.target.id === "s1p-local-export-btn") {
        const dataToExport = await exportLocalData();
        syncTextarea.value = dataToExport;
        syncTextarea.select();
        navigator.clipboard
          .writeText(dataToExport)
          .then(() => {
            showMessage("数据已导出并复制到剪贴板", true);
          })
          .catch(() => {
            showMessage("自动复制失败,请手动复制", false);
          });
      }
      if (e.target.id === "s1p-local-import-btn") {
        const jsonStr = syncTextarea.value.trim();
        if (!jsonStr) return showMessage("请先粘贴要导入的数据", false);
        const result = importLocalData(jsonStr);
        showMessage(result.message, result.success);
        if (result.success) {
          renderThreadTab();
          renderUserTab();
          renderGeneralSettingsTab();
          renderTagsTab();
          renderBookmarksTab();
        }
      }
      if (e.target.id === "s1p-clear-select-all") {
        const isChecked = e.target.checked;
        modal
          .querySelectorAll(".s1p-clear-data-checkbox")
          .forEach((chk) => (chk.checked = isChecked));
      }

      if (e.target.id === "s1p-clear-selected-btn") {
        const selectedKeys = Array.from(
          modal.querySelectorAll(".s1p-clear-data-checkbox:checked")
        ).map((chk) => chk.dataset.clearKey);
        if (selectedKeys.length === 0) {
          return showMessage("请至少选择一个要清除的数据项。", false);
        }

        const itemsToClear = selectedKeys
          .map((key) => `“${dataClearanceConfig[key].label}”`)
          .join("、");
        createConfirmationModal(
          "确认要清除所选数据吗?",
          `即将删除 ${itemsToClear} 的所有数据,此操作不可逆!`,
          () => {
            selectedKeys.forEach((key) => {
              if (dataClearanceConfig[key]) {
                dataClearanceConfig[key].clear();
              }
            });

            if (selectedKeys.includes("settings")) {
              modal.querySelector("#s1p-remote-enabled-toggle").checked = false;
              modal.querySelector(
                "#s1p-daily-first-load-sync-enabled-toggle"
              ).checked = true;
              modal.querySelector(
                "#s1p-auto-sync-enabled-toggle"
              ).checked = true;
              modal.querySelector("#s1p-remote-gist-id-input").value = "";
              modal.querySelector("#s1p-remote-pat-input").value = "";
              updateRemoteSyncInputsState();
            }

            hideBlockedThreads();
            hideBlockedUsersPosts();
            applyUserThreadBlocklist();
            hideThreadsByTitleKeyword();
            initializeNavbar();
            applyInterfaceCustomizations();
            document
              .querySelectorAll(".s1p-progress-container")
              .forEach((el) => el.remove());

            renderThreadTab();
            renderUserTab();
            renderGeneralSettingsTab();
            renderTagsTab();
            renderBookmarksTab();
            showMessage("选中的本地数据已成功清除。", true);
          },
          "确认清除"
        );
      }

      if (e.target.id === "s1p-remote-save-btn") {
        const button = e.target;
        button.disabled = true;
        button.textContent = "正在保存...";

        const currentSettings = getSettings();
        currentSettings.syncRemoteEnabled = modal.querySelector(
          "#s1p-remote-enabled-toggle"
        ).checked;
        currentSettings.syncDailyFirstLoad = modal.querySelector(
          "#s1p-daily-first-load-sync-enabled-toggle"
        ).checked;
        currentSettings.syncAutoEnabled = modal.querySelector(
          "#s1p-auto-sync-enabled-toggle"
        ).checked;
        currentSettings.syncRemoteGistId = modal
          .querySelector("#s1p-remote-gist-id-input")
          .value.trim();
        currentSettings.syncRemotePat = modal
          .querySelector("#s1p-remote-pat-input")
          .value.trim();

        saveSettings(currentSettings, true);
        updateNavbarSyncButton();

        if (currentSettings.syncRemoteGistId && currentSettings.syncRemotePat) {
          showMessage("设置已保存,正在启动首次同步检查...", null);
          await handleManualSync();
        } else {
          showMessage("远程同步设置已保存。", true);
        }
        button.disabled = false;
        button.textContent = "保存设置";
      }

      if (e.target.id === "s1p-remote-manual-sync-btn") {
        handleManualSync();
      }

      if (e.target.id === "s1p-open-gist-page-btn") {
        const gistId = modal
          .querySelector("#s1p-remote-gist-id-input")
          .value.trim();
        if (gistId) {
          GM_openInTab(`https://gist.github.com/${gistId}`, true);
        } else {
          showMessage("请先填写 Gist ID。", false);
        }
      }

      const targetTab = target.closest("#s1p-tab-tags");
      if (targetTab) {
        const action = target.dataset.action;
        const userId = target.dataset.userId;

        if (action === "edit-tag-item")
          renderTagsTab({ editingUserId: userId });
        if (action === "cancel-tag-edit") renderTagsTab();
        if (action === "delete-tag-item") {
          const userName = target.dataset.userName;
          createConfirmationModal(
            `确认删除对 "${userName}" 的标记吗?`,
            "此操作不可撤销。",
            () => {
              const tags = getUserTags();
              delete tags[userId];
              saveUserTags(tags);
              refreshUserPostsOnPage(userId);
              removeListItem(
                target,
                `<div class="s1p-empty">暂无用户标记</div>`
              );
              showMessage(`已删除对 ${userName} 的标记。`, true);
            },
            "确认删除"
          );
        } else if (action === "save-tag-edit") {
          const userName = target.dataset.userName;
          const newTag = targetTab
            .querySelector(
              `.s1p-item[data-user-id="${userId}"] .s1p-tag-edit-area`
            )
            .value.trim();
          const tags = getUserTags();
          if (newTag) {
            tags[userId] = {
              ...tags[userId],
              tag: newTag,
              timestamp: Date.now(),
              name: userName,
            };
            saveUserTags(tags);
            refreshUserPostsOnPage(userId);
            renderTagsTab();
            showMessage(`已更新对 ${userName} 的标记。`, true);
          } else {
            createConfirmationModal(
              `标记内容为空`,
              "您希望删除对该用户的标记吗?",
              () => {
                delete tags[userId];
                saveUserTags(tags);
                refreshUserPostsOnPage(userId);
                renderTagsTab();
                showMessage(`已删除对 ${userName} 的标记。`, true);
              },
              "确认删除"
            );
          }
        } else if (target.id === "s1p-export-tags-btn") {
          const textarea = targetTab.querySelector("#s1p-tags-sync-textarea");
          const dataToExport = JSON.stringify(getUserTags(), null, 2);
          textarea.value = dataToExport;
          textarea.select();
          navigator.clipboard
            .writeText(dataToExport)
            .then(() => {
              showMessage("用户标记已导出并复制到剪贴板。", true);
            })
            .catch(() => {
              showMessage("复制失败,请手动复制。", false);
            });
        } else if (target.id === "s1p-import-tags-btn") {
          const textarea = targetTab.querySelector("#s1p-tags-sync-textarea");
          const jsonStr = textarea.value.trim();
          if (!jsonStr) return showMessage("请先粘贴要导入的数据。", false);

          try {
            const imported = JSON.parse(jsonStr);
            if (
              typeof imported !== "object" ||
              imported === null ||
              Array.isArray(imported)
            )
              throw new Error("无效数据格式,应为一个对象。");
            for (const key in imported) {
              const item = imported[key];
              if (
                typeof item !== "object" ||
                item === null ||
                typeof item.tag === "undefined" ||
                typeof item.name === "undefined"
              )
                throw new Error(`用户 #${key} 的数据格式不正确。`);
            }
            createConfirmationModal(
              "确认导入用户标记吗?",
              "导入的数据将覆盖现有相同用户的标记。",
              () => {
                const currentTags = getUserTags();
                const mergedTags = { ...currentTags, ...imported };
                saveUserTags(mergedTags);
                renderTagsTab();
                showMessage(
                  `成功导入/更新 ${Object.keys(imported).length} 条用户标记。`,
                  true
                );
                textarea.value = "";
                refreshAllAuthiActions();
              },
              "确认导入"
            );
          } catch (e) {
            showMessage(`导入失败: ${e.message}`, false);
          }
        }
      }
    });
  };

  /**
   * [MODIFIED V4] 创建手动同步时的对比详情HTML (增加阅读进度智能更新提示)
   * @param {object} localDataObj - 本地数据对象
   * @param {object} remoteDataObj - 远程数据对象
   * @param {boolean} isConflict - 是否为冲突状态
   * @returns {string} - 用于弹窗的HTML字符串
   */
  const createSyncComparisonHtml = (
    localDataObj,
    remoteDataObj,
    isConflict
  ) => {
    const lastSyncInfo = GM_getValue("s1p_last_manual_sync_info", null);
    let lastActionHtml = "";
    if (lastSyncInfo && lastSyncInfo.timestamp && lastSyncInfo.action) {
      const actionText = lastSyncInfo.action === "push" ? "推送" : "拉取";
      const formattedTime = new Date(lastSyncInfo.timestamp).toLocaleString(
        "zh-CN",
        { hour12: false }
      );
      lastActionHtml = `<div class="s1p-sync-last-action">这台电脑上次手动操作: 于 ${formattedTime} <strong>${actionText}</strong>了数据</div>`;
    }

    const localNewer = localDataObj.lastUpdated > remoteDataObj.lastUpdated;
    let title = "";
    if (isConflict) {
      title = `<h2 style="color: var(--s1p-red);">检测到同步冲突!</h2><p>时间戳相同但内容不同,请仔细选择要保留的版本。</p>`;
    } else if (localNewer) {
      title = `<h2>本地数据较新</h2><p>建议选择“推送”以更新云端备份。</p>`;
    } else {
      title = `<h2>云端备份较新</h2><p>建议选择“拉取”以更新本地数据。</p>`;
    }

    const formatTime = (ts) =>
      new Date(ts || 0).toLocaleString("zh-CN", { hour12: false });
    const localTime = formatTime(localDataObj.lastUpdated);
    const remoteTime = formatTime(remoteDataObj.lastUpdated);
    const newerBadge = '<span class="s1p-newer-badge">(较新)</span>';

    const categories = [
      { label: "屏蔽用户", key: "users" },
      { label: "屏蔽帖子", key: "threads" },
      { label: "用户标记", key: "user_tags" },
      { label: "回复收藏", key: "bookmarked_replies" },
      { label: "标题规则", key: "title_filter_rules" },
      { label: "阅读进度", key: "read_progress" },
    ];

    const sortAndStringify = (obj) => {
      // 一个简化的确定性序列化,用于对比
      if (!obj || typeof obj !== "object") return JSON.stringify(obj);
      return JSON.stringify(
        Object.keys(obj)
          .sort()
          .reduce((result, key) => {
            result[key] = obj[key];
            return result;
          }, {})
      );
    };

    const tableRows = categories
      .map((cat) => {
        const localData = localDataObj.data[cat.key] || {};
        const remoteData = remoteDataObj.data[cat.key] || {};
        const localCount = Object.keys(localData).length;
        const remoteCount = Object.keys(remoteData).length;

        let localBadge = "";
        let remoteBadge = "";

        // [核心升级] 仅对“阅读进度”进行智能更新检查
        if (
          cat.key === "read_progress" &&
          localCount === remoteCount &&
          localCount > 0
        ) {
          const localContentStr = sortAndStringify(localData);
          const remoteContentStr = sortAndStringify(remoteData);

          if (localContentStr !== remoteContentStr) {
            // 内容不一致,根据整体时间戳判断哪边是更新的
            const updateBadge =
              '<span class="s1p-progress-update-badge">(内容更新)</span>';
            if (localNewer) {
              localBadge = updateBadge;
            } else {
              remoteBadge = updateBadge;
            }
          }
        }

        return `
            <div class="s1p-sync-comparison-row">
                <div class="s1p-sync-comparison-label">${cat.label}</div>
                <div class="s1p-sync-comparison-value">${localCount} ${localBadge}</div>
                <div class="s1p-sync-comparison-value">${remoteCount} ${remoteBadge}</div>
            </div>
        `;
      })
      .join("");

    return `
        ${title}
        ${lastActionHtml}
        <div class="s1p-sync-comparison-table">
            <div class="s1p-sync-comparison-row s1p-sync-comparison-header">
                <div>数据项</div>
                <div>本地数量</div>
                <div>云端数量</div>
            </div>
            ${tableRows}
            <div class="s1p-sync-comparison-row">
                <div class="s1p-sync-comparison-label">最后更新</div>
                <div class="s1p-sync-comparison-value" style="font-size: 13px;">${localTime} ${
      localNewer && !isConflict ? newerBadge : ""
    }</div>
                <div class="s1p-sync-comparison-value" style="font-size: 13px;">${remoteTime} ${
      !localNewer && !isConflict ? newerBadge : ""
    }</div>
            </div>
        </div>
    `;
  };

  /**
   * [NEW] 获取当前页面的帖子ID
   * @returns {string|null} 帖子ID或null
   */
  const getCurrentThreadId = () => {
    const threadIdMatch = window.location.href.match(/thread-(\d+)-/);
    if (threadIdMatch && threadIdMatch[1]) {
      return threadIdMatch[1];
    }
    const params = new URLSearchParams(window.location.search);
    const tid = params.get("tid") || params.get("ptid");
    if (tid) {
      return tid;
    }
    const tidInput = document.querySelector('input[name="tid"]#tid');
    if (tidInput && tidInput.value) {
      return tidInput.value;
    }
    return null;
  };

  // [MODIFIED V6 FINAL] 修正帖子详情页智能同步逻辑,实现真正的双向判断
  const handleManualSync = (suppressInitialMessage = false) => {
    return new Promise(async (resolve) => {
      const settings = getSettings();
      if (
        !settings.syncRemoteEnabled ||
        !settings.syncRemoteGistId ||
        !settings.syncRemotePat
      ) {
        showMessage("远程同步未启用或配置不完整。", false);
        return resolve(false);
      }

      if (!suppressInitialMessage) {
        showMessage("正在检查云端数据...", null);
      }

      try {
        const rawRemoteData = await fetchRemoteData();

        if (Object.keys(rawRemoteData).length === 0) {
          const pushAction = {
            text: "推送本地数据到云端",
            className: "s1p-confirm",
            action: async () => {
              showMessage("正在向云端推送数据...", null);
              try {
                const localData = await exportLocalDataObject();
                await pushRemoteData(localData);
                GM_setValue("s1p_last_sync_timestamp", Date.now());
                GM_setValue("s1p_last_manual_sync_info", {
                  action: "push",
                  timestamp: Date.now(),
                });
                updateLastSyncTimeDisplay();
                showMessage("推送成功!已初始化云端备份。", true);
                resolve(true);
              } catch (e) {
                showMessage(`推送失败: ${e.message}`, false);
                resolve(false);
              }
            },
          };
          const cancelAction = {
            text: "取消",
            className: "s1p-cancel",
            action: () => {
              showMessage("操作已取消。", null);
              resolve(null);
            },
          };
          createAdvancedConfirmationModal(
            "初始化云端同步",
            "<p>检测到云端备份为空,是否将当前本地数据作为初始版本推送到云端?</p>",
            [pushAction, cancelAction],
            { modalClassName: "s1p-sync-modal" }
          );
          return;
        }

        const remote = await migrateAndValidateRemoteData(rawRemoteData);
        const localDataObject = await exportLocalDataObject();

        if (remote.contentHash === localDataObject.contentHash) {
          showMessage("数据已是最新,无需同步。", true);
          GM_setValue("s1p_last_sync_timestamp", Date.now());
          updateLastSyncTimeDisplay();
          return resolve(true);
        }

        // [S1PLUS-SMART-SYNC V2 - LOGIC FIXED]
        const currentThreadId = getCurrentThreadId();
        if (currentThreadId && !isInitialSyncInProgress) {
          const sanitizedLocalData = JSON.parse(
            JSON.stringify(localDataObject.data)
          );
          const sanitizedRemoteData = JSON.parse(JSON.stringify(remote.data));
          const currentThreadLocalProgress = sanitizedLocalData.read_progress
            ? sanitizedLocalData.read_progress[currentThreadId]
            : undefined;

          if (sanitizedLocalData.read_progress)
            delete sanitizedLocalData.read_progress[currentThreadId];
          if (sanitizedRemoteData.read_progress)
            delete sanitizedRemoteData.read_progress[currentThreadId];

          const sanitizedLocalHash = await calculateDataHash(
            sanitizedLocalData
          );
          const sanitizedRemoteHash = await calculateDataHash(
            sanitizedRemoteData
          );

          if (sanitizedLocalHash === sanitizedRemoteHash) {
            // [逻辑修正] 在确认唯一区别是当前帖子进度后,必须再根据时间戳判断同步方向
            if (localDataObject.lastUpdated > remote.lastUpdated) {
              // 场景A: 本地较新 (用户主动阅读导致),执行无感推送
              showMessage(
                "智能同步:本地进度已更新,正在自动推送到云端...",
                null
              );
              try {
                await pushRemoteData(localDataObject);
                GM_setValue("s1p_last_sync_timestamp", Date.now());
                updateLastSyncTimeDisplay();
                showMessage("智能同步成功!已将本地最新进度推送到云端。", true);
                return resolve(true);
              } catch (e) {
                showMessage(`智能推送失败: ${e.message}`, false);
                return resolve(false);
              }
            } else {
              // 场景B: 云端较新 (被动打开帖子),执行无感拉取与合并
              showMessage("智能同步:正在合并云端数据与当前阅读进度...", null);
              importLocalData(JSON.stringify(remote.full), {
                suppressPostSync: true,
              });
              if (currentThreadLocalProgress) {
                const progress = getReadProgress();
                progress[currentThreadId] = currentThreadLocalProgress;
                saveReadProgress(progress);
              }
              GM_setValue("s1p_last_sync_timestamp", Date.now());
              updateLastSyncTimeDisplay();
              showMessage("智能同步成功!已保留当前帖子的最新阅读进度。", true);
              return resolve(true);
            }
          }
        }

        const isConflict = localDataObject.lastUpdated === remote.lastUpdated;
        const bodyHtml = createSyncComparisonHtml(
          localDataObject,
          remote,
          isConflict
        );

        const pullAction = {
          text: "从云端拉取",
          className: "s1p-confirm",
          action: () => {
            const result = importLocalData(JSON.stringify(remote.full), {
              suppressPostSync: true,
            });
            if (result.success) {
              GM_setValue("s1p_last_sync_timestamp", Date.now());
              GM_setValue("s1p_last_manual_sync_info", {
                action: "pull",
                timestamp: Date.now(),
              });
              updateLastSyncTimeDisplay();
              showMessage(`拉取成功!页面即将刷新。`, true);
              setTimeout(() => location.reload(), 1200);
              resolve(true);
            } else {
              showMessage(`导入失败: ${result.message}`, false);
              resolve(false);
            }
          },
        };
        const pushAction = {
          text: "向云端推送",
          className: "s1p-confirm",
          action: async () => {
            try {
              await pushRemoteData(localDataObject);
              GM_setValue("s1p_last_sync_timestamp", Date.now());
              GM_setValue("s1p_last_manual_sync_info", {
                action: "push",
                timestamp: Date.now(),
              });
              updateLastSyncTimeDisplay();
              showMessage("推送成功!已更新云端备份。", true);
              resolve(true);
            } catch (e) {
              showMessage(`推送失败: ${e.message}`, false);
              resolve(false);
            }
          },
        };
        const cancelAction = {
          text: "取消",
          className: "s1p-cancel",
          action: () => {
            showMessage("操作已取消。", null);
            resolve(null);
          },
        };
        createAdvancedConfirmationModal(
          "手动同步选择",
          bodyHtml,
          [pullAction, pushAction, cancelAction],
          { modalClassName: "s1p-sync-modal" }
        );
      } catch (error) {
        const corruptionErrorMessage = "云端备份已损坏";
        if (error?.message.includes(corruptionErrorMessage)) {
          const forcePushAction = {
            text: "强制推送,覆盖云端",
            className: "s1p-confirm",
            action: async () => {
              try {
                const localDataObjectForPush = await exportLocalDataObject();
                await pushRemoteData(localDataObjectForPush);
                GM_setValue("s1p_last_sync_timestamp", Date.now());
                GM_setValue("s1p_last_manual_sync_info", {
                  action: "push",
                  timestamp: Date.now(),
                });
                updateLastSyncTimeDisplay();
                showMessage("推送成功!已使用本地数据修复云端备份。", true);
                resolve(true);
              } catch (e) {
                showMessage(`强制推送失败: ${e.message}`, false);
                resolve(false);
              }
            },
          };
          const cancelAction = {
            text: "暂不处理",
            className: "s1p-cancel",
            action: () => {
              showMessage("操作已取消。云端备份仍处于损坏状态。", null);
              resolve(null);
            },
          };
          createAdvancedConfirmationModal(
            "检测到云端备份损坏",
            `<p style="color: var(--s1p-red);">云端备份文件校验失败,为保护数据已暂停同步。</p><p>是否用当前健康的本地数据强制覆盖云端损坏的备份?</p>`,
            [forcePushAction, cancelAction],
            { modalClassName: "s1p-sync-modal" }
          );
        } else {
          showMessage(`操作失败: ${error.message}`, false);
          resolve(false);
        }
      }
    });
  };

  const createAdvancedConfirmationModal = (
    title,
    bodyHtml,
    buttons,
    options = {}
  ) => {
    // [修改] 增加 options 参数
    document.querySelector(".s1p-confirm-modal")?.remove();
    const modal = document.createElement("div");
    modal.className = "s1p-confirm-modal";

    // [新增] 如果传入了自定义类名,则添加到 modal 元素上
    if (options.modalClassName) {
      modal.classList.add(options.modalClassName);
    }

    const footerButtons = buttons
      .map(
        (btn, index) =>
          `<button class="s1p-confirm-btn ${
            btn.className || ""
          }" data-btn-index="${index}">${btn.text}</button>`
      )
      .join("");

    modal.innerHTML = `
            <div class="s1p-confirm-content">
                <div class="s1p-confirm-body">
                    <div class="s1p-confirm-title">${title}</div>
                    <div class="s1p-confirm-subtitle">${bodyHtml}</div>
                </div>
                <div class="s1p-confirm-footer s1p-centered">
                    ${footerButtons}
                </div>
            </div>`;

    const closeModal = () => {
      modal.querySelector(".s1p-confirm-content").style.animation =
        "s1p-scale-out 0.25s ease-out forwards";
      modal.style.animation = "s1p-fade-out 0.25s ease-out forwards";
      setTimeout(() => modal.remove(), 250);
    };

    modal.addEventListener("click", (e) => {
      if (e.target === modal) {
        const cancelButton = modal.querySelector(".s1p-confirm-btn.s1p-cancel");
        if (cancelButton) {
          cancelButton.click();
        } else {
          closeModal();
        }
      }
    });

    buttons.forEach((btn, index) => {
      const buttonEl = modal.querySelector(`[data-btn-index="${index}"]`);
      if (buttonEl) {
        buttonEl.addEventListener("click", () => {
          if (btn.action) btn.action();
          closeModal();
        });
      }
    });

    document.body.appendChild(modal);
  };

  const addBlockButtonsToThreads = () => {
    // 核心修复:注入一个空的表头单元格,以匹配内容行的列数。
    // S1Plus 原脚本会给每个内容行动态添加一个“操作列”单元格,但没有给表头行添加,导致列数不匹配。
    // 此代码通过给表头也添加一个对应的占位单元格,使得结构恢复一致,从而让浏览器能够正确对齐所有列。
    const headerTr = document.querySelector("#threadlisttableid .th tr");
    if (headerTr && !headerTr.querySelector(".s1p-header-placeholder")) {
      const placeholderCell = document.createElement("td");
      placeholderCell.className = "s1p-header-placeholder";
      headerTr.appendChild(placeholderCell);
    }

    document
      .querySelectorAll('tbody[id^="normalthread_"], tbody[id^="stickthread_"]')
      .forEach((row) => {
        const tr = row.querySelector("tr");
        if (
          !tr ||
          row.querySelector(".s1p-options-cell") ||
          tr.classList.contains("ts") ||
          tr.classList.contains("th")
        )
          return;

        const titleElement = row.querySelector("th a.s.xst");
        if (!titleElement) return;

        const threadId = row.id.replace(/^(normalthread_|stickthread_)/, "");
        const threadTitle = titleElement.textContent.trim();

        const optionsCell = document.createElement("td");
        optionsCell.className = "s1p-options-cell";

        const optionsBtn = document.createElement("div");
        optionsBtn.className = "s1p-options-btn";
        optionsBtn.title = "屏蔽此贴";
        optionsBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>`;

        const optionsMenu = document.createElement("div");
        optionsMenu.className = "s1p-options-menu";
        // [S1P-MODIFIED] 调整了确认框内部所有元素的顺序
        optionsMenu.innerHTML = `
                <div class="s1p-direct-confirm">
                    <button class="s1p-confirm-action-btn s1p-confirm" title="确认屏蔽"></button>
                    <button class="s1p-confirm-action-btn s1p-cancel" title="取消"></button>
                    <span class="s1p-confirm-separator"></span>
                    <span>屏蔽该帖子吗?</span>
                </div>
            `;

        const cancelBtn = optionsMenu.querySelector(".s1p-cancel");
        const confirmBtn = optionsMenu.querySelector(".s1p-confirm");

        cancelBtn.addEventListener("click", (e) => {
          e.preventDefault();
          e.stopPropagation();

          const parentCell = e.currentTarget.closest(".s1p-options-cell");
          if (parentCell) {
            optionsMenu.style.visibility = "hidden";
            optionsMenu.style.opacity = "0";
            parentCell.style.pointerEvents = "none";
            setTimeout(() => {
              optionsMenu.style.removeProperty("visibility");
              optionsMenu.style.removeProperty("opacity");
              parentCell.style.removeProperty("pointer-events");
            }, 200);
          }
        });

        confirmBtn.addEventListener("click", (e) => {
          e.preventDefault();
          e.stopPropagation();
          blockThread(threadId, threadTitle);
        });

        optionsCell.appendChild(optionsBtn);
        optionsCell.appendChild(optionsMenu);
        tr.appendChild(optionsCell);

        const separatorRow = document.querySelector("#separatorline > tr.ts");
        if (separatorRow && separatorRow.childElementCount < 6) {
          const emptyTd = document.createElement("td");
          separatorRow.appendChild(emptyTd);
        }
      });
  };

  const initializeTaggingPopover = () => {
    let popover = document.getElementById("s1p-tag-popover-main");
    if (!popover) {
      popover = document.createElement("div");
      popover.id = "s1p-tag-popover-main";
      popover.className = "s1p-tag-popover";
      document.body.appendChild(popover);
    }

    // --- [S1P-FIX START] 优化交互逻辑为“点击外部关闭” ---
    let currentAnchor = null; // 保存触发弹窗的锚点元素

    const closePopover = () => {
      popover.classList.remove("visible");
      // 移除全局点击监听器,避免内存泄漏
      document.removeEventListener("click", handleOutsideClick);
      currentAnchor = null;
    };

    const handleOutsideClick = (e) => {
      // 如果点击事件发生在弹窗内部,或者发生在触发弹窗的原始锚点上,则不关闭
      if (
        popover.contains(e.target) ||
        (currentAnchor && currentAnchor.contains(e.target))
      ) {
        return;
      }
      closePopover();
    };
    // --- [S1P-FIX END] ---

    const repositionPopover = (anchorElement) => {
      if (!anchorElement) return;
      const rect = anchorElement.getBoundingClientRect();
      const popoverRect = popover.getBoundingClientRect();

      let top = rect.bottom + window.scrollY + 5;
      let left = rect.left + window.scrollX;

      if (left + popoverRect.width > window.innerWidth - 10) {
        left = window.innerWidth - popoverRect.width - 10;
      }
      if (left < 10) {
        left = 10;
      }

      popover.style.top = `${top}px`;
      popover.style.left = `${left}px`;
    };

    const renderEditMode = (userName, userId, currentTag = "") => {
      popover.innerHTML = `
                 <div class="s1p-popover-content">
                    <div class="s1p-edit-mode-header">为 ${userName} ${
        currentTag ? "编辑" : "添加"
      }标记</div>
                    <textarea class="s1p-input s1p-textarea s1p-edit-mode-textarea" placeholder="输入标记内容..." autocomplete="off">${currentTag}</textarea>
                    <div class="s1p-edit-mode-actions">
                        <button class="s1p-btn" data-action="cancel-edit">取消</button>
                        <button class="s1p-btn" data-action="save">保存</button>
                    </div>
                </div>`;
      popover.querySelector("textarea").focus();
    };

    const show = (anchorElement, userId, userName) => {
      // --- [S1P-FIX START] 更新显示逻辑 ---
      // 如果弹窗已显示且由同一个锚点触发,则不执行任何操作(防止重复打开)
      if (
        popover.classList.contains("visible") &&
        currentAnchor === anchorElement
      ) {
        return;
      }

      currentAnchor = anchorElement; // 保存当前触发的锚点

      popover.dataset.userId = userId;
      popover.dataset.userName = userName;

      const userTags = getUserTags();
      renderEditMode(userName, userId, userTags[userId]?.tag || "");

      popover.classList.add("visible");
      repositionPopover(anchorElement);

      // 延迟添加全局监听器,以防止触发弹窗的同一次点击事件立即将其关闭
      setTimeout(() => {
        document.addEventListener("click", handleOutsideClick);
      }, 0);
      // --- [S1P-FIX END] ---
    };

    popover.show = show;

    popover.addEventListener("click", (e) => {
      const target = e.target.closest("button[data-action]");
      if (!target) return;

      const { userId, userName } = popover.dataset;
      const userTags = getUserTags();

      switch (target.dataset.action) {
        case "save":
          const newTag = popover.querySelector("textarea").value.trim();
          if (newTag) {
            userTags[userId] = {
              name: userName,
              tag: newTag,
              timestamp: Date.now(),
            };
          } else {
            delete userTags[userId];
          }
          saveUserTags(userTags);
          refreshUserPostsOnPage(userId);
          closePopover(); // [S1P-FIX] 改为调用新的关闭函数
          break;
        case "cancel-edit":
          closePopover(); // [S1P-FIX] 改为调用新的关闭函数
          break;
      }
    });
  };

  const initializeGenericDisplayPopover = () => {
    let popover = document.getElementById("s1p-generic-display-popover");
    if (!popover) {
      popover = document.createElement("div");
      popover.id = "s1p-generic-display-popover";
      popover.className = "s1p-generic-display-popover";
      document.body.appendChild(popover);
    }

    let showTimeout, hideTimeout;

    const show = (anchor, text) => {
      clearTimeout(hideTimeout);
      showTimeout = setTimeout(() => {
        popover.textContent = text;
        const rect = anchor.getBoundingClientRect();

        popover.style.display = "block";
        let top = rect.top + window.scrollY - popover.offsetHeight - 6;
        let left =
          rect.left + window.scrollX + rect.width / 2 - popover.offsetWidth / 2;
        if (top < window.scrollY) {
          top = rect.bottom + window.scrollY + 6;
        }

        if (left < 10) left = 10;
        if (left + popover.offsetWidth > window.innerWidth) {
          left = window.innerWidth - popover.offsetWidth - 10;
        }

        popover.style.top = `${top}px`;
        popover.style.left = `${left}px`;
        popover.classList.add("visible");
      }, 50);
    };

    const hide = () => {
      clearTimeout(showTimeout);
      hideTimeout = setTimeout(() => {
        popover.classList.remove("visible");
      }, 100);
    };

    // [MODIFIED] Attach API to the element for external use
    if (!popover.s1p_api) {
      popover.s1p_api = { show, hide };
    }

    // Keep existing listeners for user tags
    document.body.addEventListener("mouseover", (e) => {
      const tagDisplay = e.target.closest(".s1p-user-tag-display");
      if (
        tagDisplay &&
        tagDisplay.dataset.fullTag &&
        tagDisplay.scrollWidth > tagDisplay.clientWidth
      ) {
        show(tagDisplay, tagDisplay.dataset.fullTag);
      }
    });

    document.body.addEventListener("mouseout", (e) => {
      const tagDisplay = e.target.closest(".s1p-user-tag-display");
      if (tagDisplay) {
        hide();
      }
    });
  };

  const getTimeBasedColor = (hours) => {
    if (hours <= 1) return "var(--s1p-progress-hot)";
    if (hours <= 24)
      return `rgb(${Math.round(192 - hours * 4)}, ${Math.round(
        51 + hours * 2
      )}, ${Math.round(34 + hours * 2)})`;
    if (hours <= 168)
      return `rgb(${Math.round(100 - (hours - 24) / 3)}, ${Math.round(
        100 + (hours - 24) / 4
      )}, ${Math.round(80 + (hours - 24) / 4)})`;
    return "var(--s1p-progress-cold)";
  };

  const addProgressJumpButtons = () => {
    const settings = getSettings();
    const progressData = getReadProgress();
    if (Object.keys(progressData).length === 0) return;

    const now = Date.now();

    document
      .querySelectorAll('tbody[id^="normalthread_"], tbody[id^="stickthread_"]')
      .forEach((row) => {
        const container = row.querySelector("th");
        if (!container || container.querySelector(".s1p-progress-container"))
          return;

        const threadIdMatch = row.id.match(
          /(?:normalthread_|stickthread_)(\d+)/
        );
        if (!threadIdMatch) return;
        const threadId = threadIdMatch[1];

        const progress = progressData[threadId];
        if (progress && progress.page) {
          const {
            postId,
            page,
            timestamp,
            lastReadFloor: savedFloor,
          } = progress;
          const hoursDiff = (now - (timestamp || 0)) / 3600000;
          const fcolor = getTimeBasedColor(hoursDiff);

          const replyEl = row.querySelector("td.num a.xi2");
          const currentReplies = replyEl
            ? parseInt(replyEl.textContent.replace(/,/g, "")) || 0
            : 0;
          const latestFloor = currentReplies + 1;
          const newReplies =
            savedFloor !== undefined && latestFloor > savedFloor
              ? latestFloor - savedFloor
              : 0;

          const progressContainer = document.createElement("span");
          progressContainer.className = "s1p-progress-container";

          const jumpBtn = document.createElement("a");
          jumpBtn.className = "s1p-progress-jump-btn";
          if (savedFloor) {
            jumpBtn.textContent = `P${page}-#${savedFloor}`;
            jumpBtn.title = `跳转至上次离开的第 ${page} 页,第 ${savedFloor} 楼`;
          } else {
            jumpBtn.textContent = `P${page}`;
            jumpBtn.title = `跳转至上次离开的第 ${page} 页`;
          }

          jumpBtn.href = `forum.php?mod=redirect&goto=findpost&ptid=${threadId}&pid=${postId}`;
          jumpBtn.style.color = fcolor;
          jumpBtn.style.borderColor = fcolor;

          // [MODIFIED] 移除了此处的 click 事件监听器

          jumpBtn.addEventListener("mouseover", () => {
            jumpBtn.style.backgroundColor = fcolor;
            jumpBtn.style.color = "var(--s1p-white)";
          });
          jumpBtn.addEventListener("mouseout", () => {
            jumpBtn.style.backgroundColor = "transparent";
            jumpBtn.style.color = fcolor;
          });

          progressContainer.appendChild(jumpBtn);
          if (newReplies > 0) {
            const newRepliesBadge = document.createElement("span");
            newRepliesBadge.className = "s1p-new-replies-badge";
            newRepliesBadge.textContent = `+${newReplies}`;
            newRepliesBadge.title = `有 ${newReplies} 条新回复`;
            newRepliesBadge.style.backgroundColor = fcolor;
            newRepliesBadge.style.borderColor = fcolor;
            progressContainer.appendChild(newRepliesBadge);
            jumpBtn.style.borderTopRightRadius = "0";
            jumpBtn.style.borderBottomRightRadius = "0";
          }

          container.appendChild(progressContainer);
        }
      });
  };

  const updateReadIndicatorUI = (targetPostId) => {
    // [核心修复] 在函数入口处直接检查设置状态
    // 如果开关关闭,则强制将目标ID设为null,这将触发后续的隐藏逻辑
    if (!getSettings().showReadIndicator) {
      targetPostId = null;
    }

    if (!readIndicatorElement) {
      readIndicatorElement = document.createElement("div");
      readIndicatorElement.className = "s1p-read-indicator";
      readIndicatorElement.innerHTML = `
                <span class="s1p-read-indicator-icon"></span>
                <span>当前阅读位置</span>
            `;
    }
    const indicator = readIndicatorElement;
    const currentPostId = currentIndicatorParent
      ?.closest('table[id^="pid"]')
      ?.id.replace("pid", "");

    if (targetPostId === currentPostId) {
      return;
    }

    const oldParentPi = currentIndicatorParent;
    const isVisible = !!currentIndicatorParent;

    if (isVisible) {
      indicator.classList.remove("s1p-anim-appear");
      indicator.classList.add("s1p-anim-disappear");
    }

    setTimeout(
      () => {
        let indicatorWidth = 0;
        if (oldParentPi) {
          const oldPti = oldParentPi.querySelector(".pti");
          if (oldPti) {
            oldPti.style.paddingRight = "";
          }
        }
        if (indicator.parentElement) {
          indicatorWidth = indicator.offsetWidth;
          indicator.parentElement.removeChild(indicator);
        }
        currentIndicatorParent = null;

        if (targetPostId) {
          const postTable = document.getElementById(`pid${targetPostId}`);
          const newParentPi = postTable?.querySelector("td.plc .pi");
          if (newParentPi) {
            if (indicatorWidth === 0) {
              newParentPi.appendChild(indicator);
              indicatorWidth = indicator.offsetWidth;
            }
            const newPti = newParentPi.querySelector(".pti");
            const gap = 8;
            if (newPti && indicatorWidth > 0) {
              newPti.style.paddingRight = `${indicatorWidth + gap}px`;
            }
            if (!indicator.parentElement) {
              newParentPi.appendChild(indicator);
            }
            const floorEl = newParentPi.querySelector("strong");
            const actionsEl = newParentPi.querySelector("#fj");
            const rightOffset =
              (floorEl ? floorEl.offsetWidth : 0) +
              (actionsEl ? actionsEl.offsetWidth : 0) +
              gap;
            indicator.style.right = `${rightOffset}px`;
            currentIndicatorParent = newParentPi;

            indicator.classList.remove("s1p-anim-disappear");
            requestAnimationFrame(() => {
              indicator.classList.add("s1p-anim-appear");
            });
          }
        }
      },
      isVisible ? 300 : 0
    );
  };

  let readIndicatorElement = null;
  let currentIndicatorParent = null;
  let pageObserver = null;
  let currentLoggedInUid = null; // [新增] 缓存当前登录用户的UID
  /**
   * [新增 & 修正] 获取当前登录用户的UID
   * (根据用户提供的HTML片段修正了选择器和正则表达式)
   * @returns {string|null} 当前登录用户的UID,如果未找到则返回null
   */
  const getCurrentLoggedInUid = () => {
    if (currentLoggedInUid) {
      return currentLoggedInUid;
    }

    // 方案 1: 查找 #um strong.vwmy a (来自用户截图)
    // <strong class="vwmy"><a href="space-uid-425635.html" ...>moekyo</a></strong>
    let userSpaceLink = document.querySelector(
      '#um strong.vwmy a[href*="space-uid-"]'
    );
    if (userSpaceLink) {
      const match = userSpaceLink.href.match(/space-uid-(\d+)/);
      if (match && match[1]) {
        currentLoggedInUid = match[1];
        // console.log("S1 Plus: 当前登录用户 UID (vwmy) ->", currentLoggedInUid);
        return currentLoggedInUid;
      }
    }

    console.warn(
      "S1 Plus: 无法确定当前登录用户的 UID。屏蔽/标记按钮可能也会显示在自己的帖子上。"
    );
    return null;
  };

  const trackReadProgressInThread = () => {
    const settings = getSettings();
    if (!settings.enableReadProgress || !document.getElementById("postlist"))
      return;

    let threadId = null;
    const threadIdMatch = window.location.href.match(/thread-(\d+)-/);
    if (threadIdMatch) {
      threadId = threadIdMatch[1];
    } else {
      const params = new URLSearchParams(window.location.search);
      threadId = params.get("tid") || params.get("ptid");
    }
    if (!threadId) {
      const tidInput = document.querySelector('input[name="tid"]#tid');
      if (tidInput) {
        threadId = tidInput.value;
      }
    }
    if (!threadId) return;

    let currentPage = "1";
    const threadPageMatch = window.location.href.match(/thread-\d+-(\d+)-/);
    const params = new URLSearchParams(window.location.search);
    if (threadPageMatch) {
      currentPage = threadPageMatch[1];
    } else if (params.has("page")) {
      currentPage = params.get("page");
    } else {
      const currentPageElement = document.querySelector("div.pg strong");
      if (currentPageElement && !isNaN(currentPageElement.textContent.trim())) {
        currentPage = currentPageElement.textContent.trim();
      }
    }

    // --- [核心修改] 确保 pageObserver 只初始化一次,并能监控后续新增的元素 ---

    // 仅当 pageObserver 未被创建时才创建,确保全局唯一
    if (!pageObserver) {
      let visiblePosts = new Map();
      let saveTimeout;

      const getFloorFromElement = (el) => {
        const floorElement = el.querySelector(".pi em");
        return floorElement ? parseInt(floorElement.textContent) || 0 : 0;
      };

      const saveCurrentProgress = () => {
        if (visiblePosts.size === 0) return;
        let maxFloor = 0;
        let finalPostId = null;

        visiblePosts.forEach((floor, postId) => {
          if (floor > maxFloor) {
            maxFloor = floor;
            finalPostId = postId;
          }
        });

        if (finalPostId && maxFloor > 0) {
          if (getSettings().showReadIndicator) {
            updateReadIndicatorUI(finalPostId);
          }
          updateThreadProgress(threadId, finalPostId, currentPage, maxFloor);
        }
      };

      const debouncedSave = () => {
        clearTimeout(saveTimeout);
        saveTimeout = setTimeout(saveCurrentProgress, 1500);
      };

      pageObserver = new IntersectionObserver(
        (entries) => {
          entries.forEach((entry) => {
            const postId = entry.target.id.replace("pid", "");
            if (entry.isIntersecting) {
              const floor = getFloorFromElement(entry.target);
              if (floor > 0) {
                visiblePosts.set(postId, floor);
              }
            } else {
              visiblePosts.delete(postId);
            }
          });
          debouncedSave();
        },
        { threshold: 0.3 }
      );

      const finalSave = () => {
        clearTimeout(saveTimeout);
        saveCurrentProgress();
      };

      document.addEventListener("visibilitychange", () => {
        if (document.visibilityState === "hidden") {
          finalSave();
        }
      });
      window.addEventListener("beforeunload", finalSave);
    }

    // [新增] 每次函数运行时(包括页面动态变化后),都检查并添加未被监控的帖子
    document.querySelectorAll('table[id^="pid"]').forEach((el) => {
      // 使用一个自定义属性来避免重复添加监控
      if (!el.dataset.s1pObserved) {
        pageObserver.observe(el);
        el.dataset.s1pObserved = "true";
      }
    });
  };

  const refreshAllAuthiActions = () => {
    // 遍历每个由脚本创建的、用于包裹原生按钮和脚本按钮的总容器
    document.querySelectorAll(".s1p-authi-container").forEach((container) => {
      // 在容器中找到原生的 .authi 元素
      const authiDiv = container.querySelector(".authi");
      if (authiDiv) {
        // 将原生 .authi 元素移回其原始位置(即总容器的前面)
        container.parentElement.insertBefore(authiDiv, container);
      }
      // 彻底移除脚本创建的总容器,这样里面的脚本按钮(.s1p-authi-actions-wrapper)也会一并被移除
      container.remove();
    });

    // 在完成彻底的清理后,重新调用主函数,根据当前最新的设置来添加按钮
    addActionsToPostFooter();
  };

  const refreshSinglePostActions = (postId) => {
    const postTable = document.querySelector(`table#pid${postId}`);
    if (!postTable) return;

    const container = postTable.querySelector(".s1p-authi-container");
    if (container) {
      const authiDiv = container.querySelector(".authi");
      if (authiDiv) {
        container.parentElement.insertBefore(authiDiv, container);
      }
      container.remove();
    }
    addActionsToSinglePost(postTable);
  };

  const createTagDeleteConfirmMenu = (anchorElement, tagOptionsAnchor) => {
    // 清理任何已存在的确认菜单
    document
      .querySelector(".s1p-inline-confirm-menu[data-s1p-confirm-for-tag]")
      ?.remove();

    const { userId, userName } = tagOptionsAnchor.dataset;

    const menu = document.createElement("div");
    menu.className = "s1p-options-menu s1p-inline-confirm-menu";
    menu.dataset.s1pConfirmForTag = "true"; // 添加唯一标识
    menu.style.width = "max-content";
    menu.innerHTML = `
        <div class="s1p-direct-confirm">
            <span>确认删除?</span>
            <span class="s1p-confirm-separator"></span>
            <button class="s1p-confirm-action-btn s1p-cancel" title="取消"></button>
            <button class="s1p-confirm-action-btn s1p-confirm" title="确认"></button>
        </div>
    `;
    document.body.appendChild(menu);

    // --- [修正] 智能定位 (修正垂直对齐逻辑) ---
    const anchorRect = anchorElement.getBoundingClientRect();
    const menuRect = menu.getBoundingClientRect();

    // 直接将确认菜单的顶部与触发它的“删除标记”按钮的顶部对齐
    const top = anchorRect.top + window.scrollY - 2;

    let left;
    const spaceOnRight = window.innerWidth - anchorRect.right;
    const requiredSpace = menuRect.width + 10;

    if (spaceOnRight >= requiredSpace) {
      // 右侧定位: 在按钮右侧留出 10px 间距
      left = anchorRect.right + window.scrollX + 8;
    } else {
      // [最终修正 V2] 左侧定位:
      // 在理论值 17px 的基础上,增加 7px 的安全边距,总偏移量为 24px。
      // 这足以覆盖浏览器渲染时产生的未知布局偏差。
      left = anchorRect.left + window.scrollX - menuRect.width - 28;
    }

    if (left < window.scrollX) {
      left = window.scrollX + 8;
    }

    menu.style.top = `${top}px`;
    menu.style.left = `${left}px`;

    // --- 交互逻辑 ---
    let isClosing = false;
    const optionsMenu = anchorElement.closest(".s1p-tag-options-menu");

    const closeAllMenus = () => {
      if (isClosing) return;
      isClosing = true;
      document.removeEventListener("click", closeAllMenusOnClick);

      menu.classList.remove("visible");
      if (optionsMenu) optionsMenu.remove();
      setTimeout(() => menu.remove(), 200);
    };

    const closeAllMenusOnClick = (e) => {
      if (
        !e.target.closest(
          ".s1p-tag-options-menu, .s1p-inline-confirm-menu[data-s1p-confirm-for-tag]"
        )
      ) {
        closeAllMenus();
      }
    };

    menu.querySelector(".s1p-confirm").addEventListener("click", (e) => {
      e.stopPropagation();
      const tags = getUserTags();
      delete tags[userId];
      saveUserTags(tags);
      refreshUserPostsOnPage(userId);
      closeAllMenus();
    });

    menu.querySelector(".s1p-cancel").addEventListener("click", (e) => {
      e.stopPropagation();
      closeAllMenus();
    });

    // 让确认菜单也参与到主菜单的悬停逻辑中
    if (optionsMenu && optionsMenu.s1p_api) {
      menu.addEventListener("mouseenter", optionsMenu.s1p_api.cancelHideTimer);
      menu.addEventListener("mouseleave", optionsMenu.s1p_api.startHideTimer);
    }

    // 添加全局点击监听器,用于在点击空白处时关闭所有菜单
    setTimeout(
      () => document.addEventListener("click", closeAllMenusOnClick),
      0
    );

    // 动画入场
    requestAnimationFrame(() => menu.classList.add("visible"));
  };

  const createOptionsMenu = (anchorElement) => {
    // 如果菜单已存在,则取消其隐藏计时器,防止因快速移入移出导致闪烁
    const existingMenu = document.querySelector(".s1p-tag-options-menu");
    if (existingMenu) {
      if (existingMenu.s1p_api) existingMenu.s1p_api.cancelHideTimer();
      return;
    }

    // 在创建新菜单前,清理所有可能残留的菜单
    document
      .querySelector(".s1p-inline-confirm-menu[data-s1p-confirm-for-tag]")
      ?.remove();

    const { userId, userName } = anchorElement.dataset;
    const menu = document.createElement("div");
    menu.className = "s1p-tag-options-menu";
    menu.innerHTML = `
            <button data-action="edit">编辑标记</button>
            <button data-action="delete" class="s1p-delete">删除标记</button>
        `;
    document.body.appendChild(menu);

    // --- 悬停与计时器逻辑 ---
    let hideTimeout;
    const closeAllMenus = () => {
      menu.remove();
      document
        .querySelector(".s1p-inline-confirm-menu[data-s1p-confirm-for-tag]")
        ?.remove();
    };

    const startHideTimer = () => {
      clearTimeout(hideTimeout);
      hideTimeout = setTimeout(() => {
        const confirmMenu = document.querySelector(
          ".s1p-inline-confirm-menu[data-s1p-confirm-for-tag]"
        );
        // 如果鼠标不在触发点、选项菜单或确认菜单上,则关闭所有
        if (
          !anchorElement.matches(":hover") &&
          !menu.matches(":hover") &&
          (!confirmMenu || !confirmMenu.matches(":hover"))
        ) {
          closeAllMenus();
        }
      }, 300);
    };

    const cancelHideTimer = () => clearTimeout(hideTimeout);

    // 将API附加到菜单元素上,以便其他部分(如确认菜单)可以调用
    menu.s1p_api = { startHideTimer, cancelHideTimer };

    anchorElement.addEventListener("mouseleave", startHideTimer);
    menu.addEventListener("mouseenter", cancelHideTimer);
    menu.addEventListener("mouseleave", startHideTimer);

    // --- 定位 ---
    const rect = anchorElement.getBoundingClientRect();
    menu.style.top = `${rect.bottom + window.scrollY + 2}px`;
    menu.style.left = `${rect.right + window.scrollX - menu.offsetWidth}px`;

    // --- 按钮事件 ---
    menu.addEventListener("click", (e) => {
      e.stopPropagation();
      const targetButton = e.target.closest("button");
      if (!targetButton) return;
      const action = targetButton.dataset.action;

      if (action === "edit") {
        const popover = document.getElementById("s1p-tag-popover-main");
        if (popover && popover.show) {
          popover.show(anchorElement, userId, userName);
        }
        closeAllMenus();
      } else if (action === "delete") {
        // 点击删除时,取消隐藏计时器并弹出确认菜单
        cancelHideTimer();
        createTagDeleteConfirmMenu(targetButton, anchorElement);
      }
    });

    // 初始调用,防止鼠标快速划过时意外触发关闭
    cancelHideTimer();
  };

  /**
   * [修改] 帖子楼层内操作按钮注入 (V2 - 增加当前用户判断)
   * * @param {HTMLElement} postTable - 帖子的 <table> DOM 元素
   */
  const addActionsToSinglePost = (postTable) => {
    const settings = getSettings();
    const authiDiv = postTable.querySelector(".plc .authi");
    if (!authiDiv) return;

    // --- [核心修改] 将 pi 容器作为操作目标 ---
    const piContainer = authiDiv.closest(".pi");
    if (!piContainer) return;

    // --- [这里是新增的“伸缩器”逻辑] ---
    // 检查并添加永久的布局伸缩器,确保布局稳定
    if (!piContainer.querySelector(".s1p-layout-spacer")) {
      const spacer = document.createElement("div");
      spacer.className = "s1p-layout-spacer";
      piContainer.appendChild(spacer);
    }
    // --- [新增逻辑结束] ---

    if (authiDiv.parentElement.classList.contains("s1p-authi-container")) {
      // --- [新增] 即使容器已存在,也要确保楼层号可见 ---
      const floorNumberEl = piContainer.querySelector("strong");
      if (floorNumberEl) {
        floorNumberEl.classList.add("s1p-layout-ready");
      }
      return;
    }

    const plsCell = postTable.querySelector("td.pls");
    if (!plsCell) return;
    const userProfileLink = plsCell.querySelector('a[href*="space-uid-"]');
    if (!userProfileLink) return;
    const uidMatch = userProfileLink.href.match(/space-uid-(\d+)\.html/);
    const userId = uidMatch ? uidMatch[1] : null; // 这是帖子作者的 UID
    if (!userId) return;

    // --- [新增] 获取当前登录用户的UID,并进行比较 ---
    const loggedInUid = getCurrentLoggedInUid();
    const isCurrentUserPost = loggedInUid && loggedInUid === userId;
    // ------------------------------------------

    const postId = postTable.id.replace("pid", "");
    const floorElement = postTable.querySelector(`#postnum${postId} em`);
    const floor = floorElement ? parseInt(floorElement.textContent, 10) : 0;
    const userName = userProfileLink.textContent.trim();
    const userAvatar = plsCell.querySelector(".avatar img")?.src;

    const newContainer = document.createElement("div");
    newContainer.className = "s1p-authi-container";
    const scriptActionsWrapper = document.createElement("span");
    scriptActionsWrapper.className = "s1p-authi-actions-wrapper";

    if (settings.enableBookmarkReplies) {
      const bookmarkedReplies = getBookmarkedReplies();
      const isBookmarked = !!bookmarkedReplies[postId];
      const pipe = document.createElement("span");
      pipe.className = "pipe";
      pipe.textContent = "|";
      scriptActionsWrapper.appendChild(pipe);
      const bookmarkLink = document.createElement("a");
      bookmarkLink.href = "javascript:void(0);";
      bookmarkLink.className = "s1p-authi-action s1p-bookmark-reply";
      bookmarkLink.textContent = isBookmarked ? "该回复已收藏" : "收藏该回复";
      bookmarkLink.addEventListener("click", (e) => {
        e.preventDefault();
        const currentBookmarks = getBookmarkedReplies();
        const wasBookmarked = !!currentBookmarks[postId];
        if (wasBookmarked) {
          delete currentBookmarks[postId];
          saveBookmarkedReplies(currentBookmarks);
          bookmarkLink.textContent = "收藏该回复";
          showMessage("已取消收藏该回复。", true);
        } else {
          const threadTitleEl = document.querySelector("#thread_subject");
          const threadTitle = threadTitleEl
            ? threadTitleEl.textContent.trim()
            : "未知标题";
          const threadIdMatch = window.location.href.match(/thread-(\d+)-/);
          const params = new URLSearchParams(window.location.search);
          const threadId = threadIdMatch
            ? threadIdMatch[1]
            : params.get("tid") || params.get("ptid");
          const contentEl = postTable.querySelector("td.t_f");

          let postContent = "无法获取内容";
          if (contentEl) {
            const contentClone = contentEl.cloneNode(true);
            contentClone
              .querySelectorAll(
                ".pstatus, .quote, .s1p-image-toggle-all-container, .s1p-quote-placeholder"
              )
              .forEach((el) => el.remove());
            postContent = contentClone.innerText
              .trim()
              .replace(/\n{3,}/g, "\n\n");
          }

          if (!threadId) {
            showMessage("无法获取帖子ID,收藏失败。", false);
            return;
          }
          currentBookmarks[postId] = {
            postId,
            threadId,
            threadTitle,
            floor,
            authorId: userId,
            authorName: userName,
            postContent: postContent,
            timestamp: Date.now(),
          };
          saveBookmarkedReplies(currentBookmarks);
          bookmarkLink.textContent = "该回复已收藏";
          showMessage("已收藏该回复。", true);
        }
      });
      scriptActionsWrapper.appendChild(bookmarkLink);
    }

    if (!isCurrentUserPost) {
      if (settings.enableUserBlocking) {
        const pipe = document.createElement("span");
        pipe.className = "pipe";
        pipe.textContent = "|";
        scriptActionsWrapper.appendChild(pipe);
        const blockLink = document.createElement("a");
        blockLink.href = "javascript:void(0);";
        blockLink.textContent = "屏蔽该用户";
        blockLink.className = "s1p-authi-action s1p-block-user-in-authi";
        blockLink.addEventListener("click", (e) => {
          e.preventDefault();
          e.stopPropagation();

          const currentSettings = getSettings();
          let confirmText = currentSettings.blockThreadsOnUserBlock
            ? `屏蔽用户并隐藏其主题帖?`
            : `确认屏蔽该用户?`;
          if (currentSettings.syncWithNativeBlacklist) {
            confirmText += " (将同步至论坛黑名单)";
          }

          // 将 e.currentTarget (即被点击的 a 标签)作为第一个参数传入
          createInlineConfirmMenu(e.currentTarget, confirmText, async () => {
            const success = await blockUser(userId, userName);

            const currentSettings = getSettings();
            if (success) {
              const message = currentSettings.syncWithNativeBlacklist
                ? `已屏蔽用户 ${userName} 并同步至论坛黑名单。`
                : `已屏蔽用户 ${userName}。`;
              showMessage(message, true);
            } else {
              showMessage(`脚本内屏蔽成功,但同步论坛黑名单失败。`, false);
            }
          });
        });
        scriptActionsWrapper.appendChild(blockLink);
      }

      if (settings.enableUserTagging) {
        const userTags = getUserTags();
        const userTag = userTags[userId];
        const pipe = document.createElement("span");
        pipe.className = "pipe";
        pipe.textContent = "|";
        scriptActionsWrapper.appendChild(pipe);
        if (userTag && userTag.tag) {
          const tagContainer = document.createElement("span");
          tagContainer.className = "s1p-authi-action s1p-user-tag-container";
          const fullTagText = userTag.tag;
          const tagDisplay = document.createElement("span");
          tagDisplay.className = "s1p-user-tag-display";
          tagDisplay.textContent = `用户标记:${fullTagText}`;
          tagDisplay.dataset.fullTag = fullTagText;
          tagDisplay.removeAttribute("title");
          const optionsIcon = document.createElement("span");
          optionsIcon.className = "s1p-user-tag-options";
          optionsIcon.innerHTML = "&#8942;";
          optionsIcon.dataset.userId = userId;
          optionsIcon.dataset.userName = userName;
          optionsIcon.addEventListener("mouseenter", (e) => {
            e.preventDefault();
            e.stopPropagation();
            createOptionsMenu(e.currentTarget);
          });
          tagContainer.appendChild(tagDisplay);
          tagContainer.appendChild(optionsIcon);
          scriptActionsWrapper.appendChild(tagContainer);
        } else {
          const tagLink = document.createElement("a");
          tagLink.href = "javascript:void(0);";
          tagLink.textContent = "标记该用户";
          tagLink.className = "s1p-authi-action s1p-tag-user-in-authi";
          tagLink.addEventListener("click", (e) => {
            e.preventDefault();
            const popover = document.getElementById("s1p-tag-popover-main");
            if (popover && popover.show) {
              popover.show(e.currentTarget, userId, userName, userAvatar);
            }
          });
          scriptActionsWrapper.appendChild(tagLink);
        }
      }
    }

    if (scriptActionsWrapper.hasChildNodes()) {
      authiDiv.parentElement.insertBefore(newContainer, authiDiv);
      newContainer.appendChild(authiDiv);
      newContainer.appendChild(scriptActionsWrapper);
    }

    const floorNumberEl = piContainer.querySelector("strong");
    if (floorNumberEl) {
      floorNumberEl.classList.add("s1p-layout-ready");
    }
  };

  const addActionsToPostFooter = () => {
    const settings = getSettings();
    if (
      !settings.enableUserBlocking &&
      !settings.enableUserTagging &&
      !settings.enableBookmarkReplies
    )
      return;
    document
      .querySelectorAll('table[id^="pid"]')
      .forEach(addActionsToSinglePost);
  };

  // [新增] S1 NUX 安装推荐函数
  const handleNuxRecommendation = () => {
    // 1. 如果已启用 NUX,则不执行任何操作
    if (isS1NuxEnabled) return;

    // 2. 检查用户是否在设置中关闭了推荐
    const settings = getSettings();
    if (!settings.recommendS1Nux) return;

    // 3. 频率控制:每7天最多推荐一次
    const LAST_REC_KEY = "s1p_last_nux_recommendation_timestamp";
    const lastRecommendationTimestamp = GM_getValue(LAST_REC_KEY, 0);
    const now = Date.now();
    const threeDaysInMillis = 7 * 24 * 60 * 60 * 1000;

    if (now - lastRecommendationTimestamp < threeDaysInMillis) {
      console.log(
        "S1 Plus: S1 NUX recommendation throttled (less than 7 days ago)."
      );
      return;
    }

    // 4. 创建并显示推荐弹窗
    const bodyHtml = `
        <p>检测到您尚未安装 <strong>S1 NUX</strong> 论坛美化扩展。</p>
        <p>S1 Plus 与 S1 NUX 搭配使用可获得最佳论坛浏览体验,强烈推荐安装!</p>
        <div class="s1p-notice" style="margin-top: 16px; gap: 8px;">
             <div class="s1p-notice-icon"></div>
             <div class="s1p-notice-content">
                <a href="https://stage1st.com/2b/thread-1826103-1-2.html" target="_blank">点击此处,了解 S1 NUX 详情</a>
                <p>一个由 S1 用户创作的、旨在优化论坛视觉和交互的 CSS 样式扩展。</p>
             </div>
        </div>
    `;

    const installButton = {
      text: "前往安装",
      className: "s1p-confirm",
      action: () => {
        GM_openInTab("https://userstyles.world/style/539", true);
        const currentSettings = getSettings();
        if (currentSettings.recommendS1Nux) {
          currentSettings.recommendS1Nux = false;
          saveSettings(currentSettings);
        }
      },
    };

    const disableButton = {
      text: "不再提示",
      className: "s1p-cancel",
      action: () => {
        const currentSettings = getSettings();
        currentSettings.recommendS1Nux = false;
        saveSettings(currentSettings);
        showMessage("好的,将不再为您推荐 S1 NUX。", true);
      },
    };

    // [核心修改] 调用时传入 modalClassName
    createAdvancedConfirmationModal(
      "S1 Plus 体验升级推荐",
      bodyHtml,
      [disableButton, installButton],
      { modalClassName: "s1p-nux-recommend-modal" }
    );

    // 5. 更新推荐时间戳
    GM_setValue(LAST_REC_KEY, now);
  };

  /**
   * [修改] 自动签到 (V2 - 支持多账号)
   * 修正了多账号切换时,签到记录互相干扰的问题。
   */
  function autoSign() {
    const checkinLink = document.querySelector(
      'a[href*="study_daily_attendance-daily_attendance.html"]'
    );
    if (!checkinLink) return;

    // --- [新增] 获取当前用户UID ---
    const uid = getCurrentLoggedInUid();
    if (!uid) {
      // 如果没有UID(未登录),则不执行签到
      return;
    }
    const signedDateKey = `signedDate_${uid}`; // <-- [修改] Key 变为 per-user
    // ----------------------------

    var now = new Date();
    var date =
      now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate();
    var signedDate = GM_getValue(signedDateKey); // <-- [修改]

    if (signedDate == date) {
      checkinLink.style.display = "none";
      return;
    }

    if (now.getHours() < 6) return;

    GM_xmlhttpRequest({
      method: "GET",
      url: checkinLink.href,
      onload: function (response) {
        GM_setValue(signedDateKey, date); // <-- [修改]
        checkinLink.style.display = "none";
        console.log(
          `S1 Plus: Auto check-in for UID ${uid} sent. Status:`,
          response.status
        );
      },
      onerror: function (response) {
        console.error(
          `S1 Plus: Auto check-in for UID ${uid} failed.`,
          response
        );
      },
    });
  }
  // [修改] 将设置项重命名为 enhanceFloatingControls
  const createCustomFloatingControls = () => {
    // 1. 如果自定义控件已存在,则直接退出
    if (document.getElementById("s1p-floating-controls-wrapper")) {
      return;
    }

    // 2. 从原生 #scrolltop 控件中搜集信息
    const originalContainer = document.getElementById("scrolltop");
    let replyAction = null;
    let returnAction = null;
    if (originalContainer) {
      const replyLink = originalContainer.querySelector("a.replyfast");
      if (replyLink) {
        replyAction = {
          href: replyLink.href,
          onclick: replyLink.onclick,
          title: replyLink.title,
        };
      }
      const returnLink = originalContainer.querySelector(
        "a.returnboard, a.returnlist"
      );
      if (returnLink) {
        returnAction = { href: returnLink.href, title: returnLink.title };
      }
    }

    // 3. 定义SVG图标
    const svgs = {
      scrollTop: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 13.9142L16.7929 18.7071L18.2071 17.2929L12 11.0858L5.79289 17.2929L7.20711 18.7071L12 13.9142ZM6 7L18 7V9L6 9L6 7Z"></path></svg>`,
      scrollBottom: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 10.0858L7.20711 5.29291L5.79289 6.70712L12 12.9142L18.2071 6.70712L16.7929 5.29291L12 10.0858ZM18 17L6 17L6 15L18 15V17Z"></path></svg>`,
      reply: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M14 22.5L11.2 19H6C5.44772 19 5 18.5523 5 18V7.10256C5 6.55028 5.44772 6.10256 6 6.10256H22C22.5523 6.10256 23 6.55028 23 7.10256V18C23 18.5523 22.5523 19 22 19H16.8L14 22.5ZM15.8387 17H21V8.10256H7V17H11.2H12.1613L14 19.2984L15.8387 17ZM2 2H19V4H3V15H1V3C1 2.44772 1.44772 2 2 2Z"></path></svg>`,
      board: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2 4C2 3.44772 2.44772 3 3 3H21C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4ZM4 5V19H20V5H4ZM6 7H8V9H6V7ZM8 11H6V13H8V11ZM6 15H8V17H6V15ZM18 7H10V9H18V7ZM10 15H18V17H10V15ZM18 11H10V13H18V11Z"></path></svg>`,
    };

    // 4. 创建DOM结构
    const wrapper = document.createElement("div");
    wrapper.id = "s1p-floating-controls-wrapper";
    // [核心修改] 读取新命名的设置并添加对应的class
    const settings = getSettings();
    if (settings.enhanceFloatingControls) {
      wrapper.classList.add("s1p-hover-interaction-enabled");
    }

    const handle = document.createElement("div");
    handle.id = "s1p-controls-handle";

    const panel = document.createElement("div");
    panel.id = "s1p-floating-controls";

    // 5. 创建按钮并添加到 panel 中
    const createButton = (title, className, svg, href, onclick) => {
      const link = document.createElement("a");
      link.title = title;
      link.className = className;
      link.innerHTML = svg;
      if (href) link.href = href;
      if (onclick) link.onclick = onclick;
      return link;
    };

    if (replyAction) {
      panel.appendChild(
        createButton(
          replyAction.title,
          "",
          svgs.reply,
          replyAction.href,
          replyAction.onclick
        )
      );
    }
    const scrollTopBtn = createButton(
      "返回顶部",
      "s1p-scroll-btn",
      svgs.scrollTop,
      "javascript:void(0);",
      (e) => {
        e.preventDefault();
        window.scrollTo({ top: 0, behavior: "smooth" });
      }
    );
    const scrollBottomBtn = createButton(
      "返回底部",
      "s1p-scroll-btn",
      svgs.scrollBottom,
      "javascript:void(0);",
      (e) => {
        e.preventDefault();
        window.scrollTo({
          top: document.body.scrollHeight,
          behavior: "smooth",
        });
      }
    );
    panel.appendChild(scrollTopBtn);
    panel.appendChild(scrollBottomBtn);
    if (returnAction) {
      panel.appendChild(
        createButton(
          returnAction.title,
          "",
          svgs.board,
          returnAction.href,
          null
        )
      );
    }

    // 6. 组装并添加到页面
    wrapper.appendChild(panel);
    wrapper.appendChild(handle);
    document.body.appendChild(wrapper);
  };

  // --- [新增] 悬浮控件管理器 ---
  const manageFloatingControls = () => {
    const settings = getSettings();
    // 根据设置,切换body上的class,从而激活不同的CSS规则
    document.body.classList.toggle(
      "s1p-enhanced-controls-active",
      settings.enhanceFloatingControls
    );

    if (settings.enhanceFloatingControls) {
      // 如果设置为开启,则调用创建函数(它内部有防重复机制)
      createCustomFloatingControls();
    } else {
      // 如果设置为关闭,则确保移除脚本创建的控件
      document.getElementById("s1p-floating-controls-wrapper")?.remove();
    }
  };

  const cleanupOldReadProgress = () => {
    const settings = getSettings();
    if (
      !settings.readingProgressCleanupDays ||
      settings.readingProgressCleanupDays <= 0
    )
      return;
    const progress = getReadProgress();
    const originalCount = Object.keys(progress).length;
    if (originalCount === 0) return;

    const now = Date.now();
    const maxAge = settings.readingProgressCleanupDays * 24 * 60 * 60 * 1000;
    const cleanedProgress = {};
    let cleanedCount = 0;

    for (const threadId in progress) {
      if (Object.prototype.hasOwnProperty.call(progress, threadId)) {
        const record = progress[threadId];
        if (record.timestamp && now - record.timestamp < maxAge) {
          cleanedProgress[threadId] = record;
        } else {
          cleanedCount++;
        }
      }
    }

    if (cleanedCount > 0) {
      console.log(
        `S1 Plus: Cleaned up ${cleanedCount} old reading progress records (older than ${settings.readingProgressCleanupDays} days).`
      );
      // [S1P-FIX] 调用保存时,传入 true 来阻止更新 last_modified 时间戳
      saveReadProgress(cleanedProgress, true);
    }
  };
  const handlePerLoadSyncCheck = async () => {
    const settings = getSettings();

    if (!settings.syncDailyFirstLoad && settings.syncAutoEnabled) {
      console.log("S1 Plus: 执行常规启动时同步检查(因每日首次同步已关闭)...");
      // [S1P-FIX] 调用时传入 true,启用启动安全模式
      const result = await performAutoSync(true);
      if (result.status === "success" && result.action === "pulled") {
        showMessage("检测到云端有更新,正在刷新页面...", true);
        setTimeout(() => location.reload(), 1500);
        return true;
      }
    }
    return false;
  };
  const handleStartupSync = async () => {
    const settings = getSettings();
    if (!settings.syncRemoteEnabled || !settings.syncDailyFirstLoad) {
      return false;
    }

    const today = new Date().toLocaleDateString("sv");
    const lastSyncDate = GM_getValue("s1p_last_daily_sync_date", null);

    if (today === lastSyncDate) {
      return false;
    }

    const SYNC_LOCK_KEY = "s1p_sync_lock";
    const SYNC_LOCK_TIMEOUT_MS = 60 * 1000;

    const lockTimestamp = GM_getValue(SYNC_LOCK_KEY, 0);
    if (Date.now() - lockTimestamp < SYNC_LOCK_TIMEOUT_MS) {
      console.log(
        "S1 Plus: 检测到另一个标签页可能正在同步,本次启动同步已跳过。"
      );
      return false;
    }

    GM_setValue(SYNC_LOCK_KEY, Date.now());

    try {
      const currentDateAfterLock = GM_getValue(
        "s1p_last_daily_sync_date",
        null
      );
      if (currentDateAfterLock === today) {
        console.log("S1 Plus: 在锁定期间检测到同步已完成,已取消重复操作。");
        return false;
      }

      console.log("S1 Plus: 正在执行每日首次加载同步...");
      GM_setValue("s1p_last_daily_sync_date", today);

      const result = await performAutoSync(true);

      switch (result.status) {
        case "success":
          // [修改] 增加对 "force_pulled" 状态的处理
          if (result.action === "pulled" || result.action === "force_pulled") {
            const message =
              result.action === "force_pulled"
                ? "启动时强制同步完成:已使用云端数据覆盖本地。正在刷新..."
                : "每日同步完成:云端有更新已被自动拉取。正在刷新...";
            showMessage(message, true);
            setTimeout(() => location.reload(), 1500);
            return true;
          } else if (result.action === "skipped_push_on_startup") {
            createAdvancedConfirmationModal(
              "检测到本地有未同步的更改",
              "<p>S1 Plus 在启动时发现,您的本地数据比云端备份要新。这可能意味着您在其他设备的工作未推送,或有离线修改未同步。</p><p>为防止数据丢失,自动同步已暂停。请选择如何处理:</p>",
              [
                {
                  text: "稍后处理",
                  className: "s1p-cancel",
                  action: () => {
                    showMessage("同步已暂停,您可稍后从导航栏手动同步。", null);
                  },
                },
                {
                  text: "立即解决",
                  className: "s1p-confirm",
                  action: () => {
                    // [S1P-UX-FIX] 调用时传入 true,进入静默模式,避免弹出多余的提示
                    handleManualSync(true);
                  },
                },
              ]
            );
          } else {
            if (result.action !== "pushed_initial") {
              showMessage("每日首次同步完成,数据已是最新。", true);
            }
          }
          break;
        case "failure":
          showMessage(`每日首次同步失败: ${result.error}`, false);
          break;

        case "conflict":
          createAdvancedConfirmationModal(
            "检测到同步冲突",
            "<p>S1 Plus在自动同步时发现,您的本地数据和云端备份可能都已更改。</p><p>为防止数据丢失,自动同步已暂停。请手动选择要保留的版本来解决冲突。</p>",
            [
              {
                text: "稍后处理",
                className: "s1p-cancel",
                action: () => {
                  showMessage("同步已暂停,您可以在设置中手动同步。", null);
                },
              },
              {
                text: "立即解决",
                className: "s1p-confirm",
                action: () => {
                  // [S1P-UX-FIX] 此处也应进入静默模式
                  handleManualSync(true);
                },
              },
            ]
          );
          break;
      }
    } finally {
      GM_deleteValue(SYNC_LOCK_KEY);
      console.log("S1 Plus: 同步锁已释放。");
    }

    return false;
  };

  /**
   * [MODIFIED] 首次加载此版本时,显示更新亮点弹窗,并返回是否显示了弹窗。
   * @returns {boolean} 如果显示了弹窗则返回 true,否则返回 false。
   */
  const showFirstTimeWelcomeIfNeeded = () => {
    // [OPTIMIZED] 动态生成欢迎标记的键,确保每个版本只显示一次
    const WELCOME_FLAG_KEY = `s1p_v${SCRIPT_VERSION.replace(
      /\./g,
      "_"
    )}_welcomed`;
    const hasSeenWelcome = GM_getValue(WELCOME_FLAG_KEY, false);

    if (hasSeenWelcome) {
      return false; // 已看过,不显示弹窗,返回 false
    }

    // [OPTIMIZED] 优化HTML结构以改善文本布局和换行
    const bodyHtml = `
    <p>已更新至 v${SCRIPT_VERSION}!本次更新包含一项重要的界面布局优化:</p>
    <p style="margin-top: 8px;">我们将帖子列表的操作按钮(原悬停屏蔽按钮)从行首移动到了行末(右侧),以改善界面布局和操作流程。</p>
    <p style="margin-top: 16px;">此外,此版本还包含了大量其他的功能优化与 Bug 修复。</p>
  `;

    const buttons = [
      {
        text: "我明白了",
        className: "s1p-confirm",
        action: () => {
          GM_setValue(WELCOME_FLAG_KEY, true);
        },
      },
    ];

    // [OPTIMIZED] 动态获取版本号
    createAdvancedConfirmationModal(
      `S1 Plus v${SCRIPT_VERSION} 更新亮点`,
      bodyHtml,
      buttons,
      {
        modalClassName: "s1p-welcome-modal",
      }
    );

    return true; // 确认显示了弹窗,返回 true
  };

  async function main() {
    // [修改] 调用欢迎弹窗并接收其状态
    const welcomePopupWasShown = showFirstTimeWelcomeIfNeeded();

    // --- [核心修改] 按照您的建议,分离启动同步的调用 ---
    // 步骤1: 尝试执行每日首次同步
    const isReloadingAfterDailySync = await handleStartupSync();
    if (isReloadingAfterDailySync) {
      return; // 如果页面即将刷新,则中断后续所有脚本初始化操作
    }
    // 步骤2: 尝试执行常规的“每次加载”同步检查
    const isReloadingAfterPerLoadSync = await handlePerLoadSyncCheck();
    if (isReloadingAfterPerLoadSync) {
      return; // 如果页面即将刷新,则中断后续所有脚本初始化操作
    }

    cleanupOldReadProgress();
    detectS1Nux();

    // [核心修正] 只有在未显示欢迎弹窗时,才检查NUX推荐,避免冲突
    if (!welcomePopupWasShown) {
      handleNuxRecommendation();
    }

    initializeNavbar();
    initializeGenericDisplayPopover();

    const observerCallback = (mutations, observer) => {
      const navNeedsReinit = !document.getElementById("s1p-nav-link");
      observer.disconnect();
      if (navNeedsReinit) {
        console.log("S1 Plus: 检测到导航栏被重置,正在重新应用自定义设置。");
        initializeNavbar();
      }
      applyChanges();
      const watchTarget = document.getElementById("wp") || document.body;
      observer.observe(watchTarget, { childList: true, subtree: true });
    };

    const observer = new MutationObserver(observerCallback);
    applyChanges();
    const watchTarget = document.getElementById("wp") || document.body;
    observer.observe(watchTarget, { childList: true, subtree: true });
  }

  function applyChanges() {
    const settings = getSettings();

    manageFloatingControls(); // <-- [核心修改]

    if (settings.enablePostBlocking) {
      hideBlockedThreads();
      hideThreadsByTitleKeyword();
      addBlockButtonsToThreads();
      applyUserThreadBlocklist();
    }
    if (settings.enableUserBlocking) {
      hideBlockedUsersPosts();
      hideBlockedUserQuotes();
      hideBlockedUserRatings();
      hideBlockedUserNotifications(); // [新增] 调用提醒屏蔽函数
    }
    if (
      settings.enableUserBlocking ||
      settings.enableUserTagging ||
      settings.enableBookmarkReplies
    ) {
      addActionsToPostFooter();
    }
    if (settings.enableUserTagging) {
      initializeTaggingPopover();
    }
    if (settings.enableReadProgress) {
      addProgressJumpButtons();
    }

    applyInterfaceCustomizations();
    applyImageHiding();
    manageImageToggleAllButtons();
    renameAuthorLinks();
    applyGlobalLinkBehavior(); // <--- MODIFIED
    trackReadProgressInThread();
    try {
      autoSign();
    } catch (e) {
      console.error("S1 Plus: Error caught while running autoSign():", e);
    }
  }

  main();
})();