S1 Plus - Stage1st 体验增强套件

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

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

(function () {
    'use strict';


    const SCRIPT_VERSION = '4.4.3';
    const SCRIPT_RELEASE_DATE = '2025-08-16';

    // --- 样式注入 ---
    GM_addStyle(`
        /* --- 通用颜色 --- */
        :root {
            --s1p-bg: #ECEDEB;
            --s1p-t: #022C80;
            --s1p-desc-t: #10388a;
            --s1p-pri: #D1D9C1;
            --s1p-sec: #2563eb;
            --s1p-sec-h: #306bebff;
            --s1p-red: #ef4444;
            --s1p-red-h: #dc2626;
            --s1p-input-bg: #d1d5db;
            --s1p-sub: #e9ebe8;
            --s1p-sub-h: #2563eb;
            --s1p-sub-h-t: white;
        }

        /* --- 核心修复:禁用论坛自带的用户信息悬浮窗 --- */
        #p_pop { display: none !important; }

        /* --- [FIX] 核心修复:解决主题帖作者栏布局与点击问题 --- */
        /* 1. 为 .pi 创建BFC,强制其包裹内部浮动的“电梯直达”元素,防止高度塌陷。*/
        .pi { overflow: hidden; }
        /* 2. 为 .pti 赋予新的堆叠上下文(z-index)和不透明背景,确保它显示在任何同级元素之上,
           并彻底修正因布局错位导致的链接垂直点击范围偏移、变小的问题。*/
        .pi > .pti { position: relative; z-index: 1; }

        /* --- [FIX] 修正 .pti 背景遮挡“电梯直达”和“楼主”链接的问题 --- */
        /* 通过提升这两个元素的堆叠顺序,确保它们显示在 .pti 背景之上 */
        .pi > #fj,
        .pi > strong {
            position: relative;
            z-index: 2;
        }


        /* --- 关键字屏蔽样式 --- */

        .s1p-hidden-by-keyword, .s1p-hidden-by-quote { display: none !important; }

        /* --- 按钮通用样式 --- */
        .s1p-btn { display: inline-flex; align-items: center; justify-content: center; padding: 5px 10px 5px 12px; border-radius: 4px; background-color: var(--s1p-sub); color: var(--s1p-t); font-size: 14px; font-weight: bold; cursor: pointer; user-select: none; white-space: nowrap; border: 1px solid var(--s1p-pri); transition: all 0.2s ease-in-out;}
        .s1p-btn:hover { background-color: var(--s1p-sub-h); color: white; border-color: var(--s1p-sub-h); }
        .s1p-red-btn { background-color: var(--s1p-red); color: white; border-color: var(--s1p-red); }
        .s1p-red-btn:hover { background-color: var(--s1p-red-h); border-color: var(--s1p-red-h); }

        /* --- [REPLACED] 帖子屏蔽按钮动画与布局 --- */
        /* 承载所有操作按钮的主容器,它会在悬停时出现 */
        .s1p-action-container {
            position: absolute;
            left: 0;
            top: 50%;
            transform: translateY(-50%);
            z-index: 5;
            height: 26px;
            opacity: 0;
            transition: opacity 0.2s ease-in-out;
            display: flex;
            align-items: center;
            pointer-events: none; /* 隐藏时阻断点击 */
        }

        /* 当鼠标悬停在图标单元格上时,显示主容器 */
        tbody.s1p-hover-reveal .s1p-action-container {
            opacity: 1;
            pointer-events: auto; /* 可见时允许点击 */
        }

        /* 初始的“屏蔽”按钮样式 */
        .thread-block-btn {
            display: inline-flex; align-items: center; justify-content: center;
            width: 26px; padding: 5px 4px;
            border-radius: 0 12px 12px 0;
            background-color: var(--s1p-red); color: white;
            font-size: 12px; border: none;
            box-shadow: 0 1px 3px #00000033;
            cursor: pointer;
            transition: background-color 0.2s ease-in-out;
        }
        .thread-block-btn:hover { background-color: var(--s1p-red-h); }

        /* --- [REPLACED] 帖子行悬停/确认平移效果 --- */
        /* 为所有会移动的单元格准备过渡动画 */
        tbody[id^="normalthread_"] th,
        tbody[id^="stickthread_"] th,
        .icn > a {
            transition: transform 0.2s ease-in-out;
        }

        /* 鼠标悬停在图标单元格上时,平移帖子内容,为“屏蔽”按钮腾出空间 */
        tbody.s1p-hover-reveal .icn > a,
        tbody.s1p-hover-reveal .icn + th {
            transform: translateX(28px);
        }

        /* --- [NEW] 屏蔽确认状态的样式 --- */
        /* 确认/取消按钮的容器,默认隐藏 */
        .s1p-thread-block-confirm-actions {
            display: none;
            align-items: center;
            gap: 4px;
            margin-left: 4px;
        }
        /* 当帖子行处于确认状态时,显示确认/取消按钮 */
        tbody.s1p-blocking-confirm .s1p-thread-block-confirm-actions { display: flex; }

        /* 确认(勾)和取消(叉)按钮本身的样式 */
        .s1p-confirm-action-btn {
            display: flex; align-items: center; justify-content: center;
            width: 28px; height: 28px; /* Make it a square */
            border: none; border-radius: 50%; /* Make it a circle */
            cursor: pointer;
            transition: background-color 0.2s ease, transform 0.1s ease;
            background-repeat: no-repeat;
            background-position: center;
            background-size: 60%;
        }
        .s1p-confirm-action-btn:active { transform: scale(0.95); }
        .s1p-confirm-action-btn.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.confirm:hover {
            background-color: #e0f2e9; /* A light green */
        }
        .s1p-confirm-action-btn.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.cancel:hover {
            background-color: #fee2e2; /* A light red from the theme's error messages */
        }

        /* 当帖子行处于确认状态时,将内容进一步向右平移,为所有按钮腾出空间 */
        tbody.s1p-blocking-confirm .icn > a,
        tbody.s1p-blocking-confirm .icn + th {
            transform: translateX(94px);
        }

        /* [MODIFIED] 阅读进度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; /* Color will be set by JS */
            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: white;
            font-size: 12px;
            font-weight: bold;
            padding: 1px 5px;
            border: 1px solid; /* Color will be set by JS */
            border-left: none;
            border-radius: 0 4px 4px 0;
            line-height: 1.4;
            user-select: none;
        }

        /* --- 文本框基础样式 --- */
        .s1p-textarea {
            background: var(--s1p-input);
            border: 1px solid var(--s1p-pri);
            border-radius: 8px;
            padding: 8px;
            font-size: 14px;
            resize: vertical;
            box-sizing: border-box;
        }

        /* --- [MODIFIED] 用户标记悬浮窗 (Style Revamp per Image 2) --- */
        .s1p-tag-popover {
            position: absolute;
            z-index: 10001;
            width: 300px;
            background-color: var(--s1p-bg);
            border-radius: 12px;
            box-shadow: 0 4px 20px #00000014;
            border: 1px solid var(--s1p-pri);
            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.empty {
            text-align: center;
            color: #888;
        }
        .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 {
            width: 100%;
            height: 90px;
            margin-bottom: 12px;
        }
        .s1p-edit-mode-actions {
            display: flex;
            justify-content: flex-end;
            gap: 8px;
        }

        /* --- [NEW] 用户标记显示悬浮窗 --- */
        .s1p-tag-display-popover {
            position: absolute;
            z-index: 10003;
            max-width: 350px;
            background-color: var(--s1p-bg);
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.12);
            border: 1px solid var(--s1p-pri);
            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-tag-display-popover.visible {
            opacity: 1;
            visibility: visible;
            transform: translateY(0);
        }

        /* --- [NEW] Authi User Tag Display --- */
        .authi {
            display: flex;
            align-items: center;
            flex-wrap: wrap;
        }
        .s1p-authi-actions-wrapper {
            display: inline-flex;
            align-items: center;
            vertical-align: middle;
        }
        .s1p-user-tag-container {
            display: inline-flex;
            align-items: center;
            vertical-align: middle;
            border-radius: 6px;
            overflow: hidden;
        }
        .s1p-user-tag-display {
            background-color: var(--s1p-sub);
            color: var(--s1p-t);
            padding: 2px 8px;
            font-size: 12px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            cursor: default; /* [FIX] Change cursor to default as it's not a link */
        }
        .s1p-user-tag-options {
            display: flex;
            align-items: center;
            justify-content: center;
            background-color: var(--s1p-sub);
            color: var(--s1p-t);
            font-size: 14px; /* 稍微增大字体 */
            font-weight: bold; /* 加粗字体 */
            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);
        }

        /* --- [NEW] 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(0,0,0,0.15);
            border: 1px solid var(--s1p-pri);
            padding: 4px;
            display: flex;
            flex-direction: column;
            gap: 2px;
            min-width: max-content;
        }
        .s1p-tag-options-menu button {
            background: none;
            border: none;
            padding: 6px 12px;
            text-align: left;
            cursor: pointer;
            border-radius: 4px;
            font-size: 14px;
            color: var(--s1p-t);
            white-space: nowrap;
        }
        .s1p-tag-options-menu button:hover {
            background-color: var(--s1p-sub-h);
            color: var(--s1p-sub-h-t);
        }
        .s1p-tag-options-menu button.delete:hover {
            background-color: var(--s1p-red);
            color: white;
        }

        /* --- 设置面板样式 --- */
        .s1p-modal { display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%;  background-color: rgba(0, 0, 0, 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 #0000001a; width: 600px; max-width: 90%; max-height: 80vh; overflow: hidden; display: flex; flex-direction: column; }
        .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 { font-size: 18px; font-weight: bold; }
        .s1p-modal-close {
            width: 12px;
            height: 12px;
            cursor: pointer;
            color: #9ca3af;
            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: 0 16px 16px; overflow-y: auto; flex-grow: 1; }
        .s1p-modal-footer { padding: 12px 16px; border-top: 1px solid var(--s1p-pri); text-align: right; font-size: 12px; }
        .s1p-tabs { display: flex; border-bottom: 1px solid var(--s1p-pri); }
        .s1p-tab-btn { padding: 12px 16px; cursor: pointer; border: none; background-color: transparent; font-size: 14px; border-bottom: 2px solid transparent; transition: all 0.2s; }
        .s1p-tab-btn.active { color: var(--s1p-sec); border-bottom-color: var(--s1p-sec); font-weight: 500; }
        .s1p-tab-content { display: none; padding-top: 16px; }
        .s1p-tab-content.active { display: block; }
        .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-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: #07855b; border-color: #07855b; }
        .s1p-sync-title { font-size: 14px; font-weight: 500; margin-bottom: 8px; }
        .s1p-sync-desc { font-size: 14px; color: var(--s1p-desc-t); margin-bottom: 12px; line-height: 1.5; }
        .s1p-sync-buttons { display: flex; gap: 8px; margin-bottom: 16px; }
        .s1p-sync-textarea { width: 100%; min-height: 80px; margin-bottom: 20px;}
        .s1p-message { font-size: 14px; margin-top: 8px; padding: 8px; border-radius: 4px; display:none; text-align: center; }
        .s1p-message.success { background-color: #d1fae5; color: #065f46; }
        .s1p-message.error { background-color: #fee2e2; color: var(--s1p-red); }

        /* --- 确认弹窗样式 --- */
        @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(0, 0, 0, 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(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); width: 420px; 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 .confirm-title { font-weight: 600; font-size: 18px; margin-bottom: 8px; }
        .s1p-confirm-body .confirm-subtitle { font-size: 14px; color: var(--s1p-desc-t); }
        .s1p-confirm-footer { padding: 12px 16px; display: flex; justify-content: flex-end; gap: 12px; }
        .s1p-confirm-btn { padding: 9px 18px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: 1px solid transparent; transition: all 0.15s ease-in-out; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }
        .s1p-confirm-btn:active { transform: translateY(1px); }
        .s1p-confirm-btn.cancel { background-color: var(--s1p-sub); border-color: var(--s1p-pri); }
        .s1p-confirm-btn.cancel:hover { border-color: var(--s1p-t); }
        .s1p-confirm-btn.confirm { background-color: var(--s1p-red); color: white; border-color: var(--s1p-red); }
        .s1p-confirm-btn.confirm:hover { background-color: var(--s1p-red-h); border-color: var(--s1p-red-h); }

        /* --- Collapsible Section --- */
        .s1p-collapsible-header { display: flex; align-items: center; justify-content: space-between; cursor: pointer; user-select: none; transition: color 0.2s ease; }
        .s1p-settings-group-title.s1p-collapsible-header { margin-bottom: 0; }
        .s1p-collapsible-header:hover { color: var(--s1p-sec); }
        .s1p-collapsible-header:hover .s1p-expander-arrow { color: var(--s1p-sec); }
        .s1p-expander-arrow {
            display: inline-block; width: 12px; height: 12px; color: #6b7280;
            background-image: url("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='currentColor' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E");
            background-repeat: no-repeat; background-position: center; background-size: contain; transition: transform 0.3s ease-in-out, color 0.2s ease;
        }
        .s1p-expander-arrow.expanded { transform: rotate(90deg); }
        .s1p-collapsible-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; }
        .s1p-collapsible-content.expanded { max-height: 500px; transition: max-height 0.4s ease-in; padding-top: 12px; }

        /* --- [NEW] Feature Content Animation --- */
        .s1p-feature-content {
            display: grid;
            grid-template-rows: 0fr;
            transition: grid-template-rows 0.6s ease-in-out, margin-top 0.6s ease-in-out;
            margin-top: 0;
        }
        .s1p-feature-content.expanded {
            grid-template-rows: 1fr;
            margin-top: 16px;
        }
        .s1p-feature-content > div {
            overflow: hidden;
            transition: opacity 0.5s ease-in-out;
        }
        .s1p-feature-content:not(.expanded) > div {
            opacity: 0;
            transition-duration: 0.25s;
        }
        .s1p-feature-content.expanded > div {
            opacity: 1;
            transition-delay: 0.15s;
        }

        /* --- 界面定制设置样式 --- */
        .s1p-settings-group { margin-bottom: 24px; }
        .s1p-settings-group-title { font-size: 16px; font-weight: 500; border-bottom: 1px solid var(--s1p-pri); padding-bottom: 16px; margin-bottom: 12px; }
        .s1p-settings-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; }
        .s1p-settings-item .title-suffix-input { 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-settings-label { font-size: 14px; }
        .s1p-settings-checkbox { /* Handled by .s1p-switch */ }
        .s1p-setting-desc { font-size: 12px; color: var(--s1p-desc-t); margin: -4px 0 12px 0; padding: 0; line-height: 1.5; }
        .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 input[type="text"] { 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; }
        .s1p-editor-btn:hover { background: #e5e7eb; color: #374151; }
        .s1p-editor-btn.keyword-rule-delete,
        .s1p-editor-btn[data-action="delete"] {
            font-size: 0;
            width: 26px;
            height: 26px;
            padding: 4px;
            box-sizing: border-box;
            background-image: url("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");
            background-repeat: no-repeat;
            background-position: center;
            background-size: 18px 18px;
            transition: all 0.2s ease;
        }
        .s1p-editor-btn.keyword-rule-delete:hover,
        .s1p-editor-btn[data-action="delete"]:hover {
            background-color: var(--s1p-red);
            background-image: url("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");
        }
        .s1p-drag-handle { font-size: 18pt; cursor: grab; }
        .s1p-editor-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; }
        .s1p-settings-action-btn { display: inline-block; padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; border: none; }
        .s1p-settings-action-btn.primary { background-color: var(--s1p-sec); color: white; }
        .s1p-settings-action-btn.primary:hover { background-color: var(--s1p-sec-h); }
        .s1p-settings-action-btn.secondary { background-color: #e5e7eb; color: #374151; }
        .s1p-settings-action-btn.secondary:hover { background-color: #d1d5db; }

        /* --- 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: .3s; border-radius: 22px; }
        .s1p-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,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.dragging { opacity: 0.5; }

        /* --- [NEW] 用户标记设置面板专属样式 --- */
        .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: 8px; color: var(--s1p-desc-t); line-height: 1.6; white-space: pre-wrap; word-break: break-all; }
        .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.primary { background-color: #3b82f6; color: white; }
        .s1p-item-actions .s1p-btn.primary:hover { background-color: #2563eb; }
        .s1p-item-actions .s1p-btn.danger { background-color: var(--s1p-red); color: white; }
        .s1p-item-actions .s1p-btn.danger:hover { background-color: var(--s1p-red-h); border-color: var(--s1p-red-h);}

        /* --- [NEW] 引用屏蔽占位符 (Refined Style) --- */
        .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);
        }

        /* --- [NEW] Quote Collapse Animation --- */
        .s1p-quote-wrapper {
            overflow: hidden;
            transition: max-height 0.35s ease-in-out;
        }

        /* --- [NEW] 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;
            padding: 6px 12px;
            border-radius: 6px;
            background-color: var(--s1p-sub);
            color: var(--s1p-t);
            border: 1px solid var(--s1p-pri);
            cursor: pointer;
            font-size: 13px;
            transition: all 0.2s ease;
        }
        .s1p-image-placeholder:hover {
            background-color: var(--s1p-sub-h);
            color: var(--s1p-sub-h-t);
            border-color: var(--s1p-sub-h);
        }
        .s1p-image-container.hidden > .zoom {
            display: none;
        }

        /* --- [MODIFIED] Image Toggle All Button --- */
        .s1p-image-toggle-all-container {
            margin-bottom: 10px;
        }
        .s1p-image-toggle-all-btn {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            padding: 6px 12px;
            border-radius: 6px;
            background-color: var(--s1p-sub);
            color: var(--s1p-t);
            border: 1px solid var(--s1p-pri);
            cursor: pointer;
            font-size: 13px;
            transition: all 0.2s ease;
        }
        .s1p-image-toggle-all-btn:hover {
            background-color: var(--s1p-sub-h);
            color: var(--s1p-sub-h-t);
            border-color: var(--s1p-sub-h);
        }
    `);

    let dynamicallyHiddenThreads = {};

    // --- 数据处理 & 核心功能 ---
    const getBlockedThreads = () => GM_getValue('s1p_blocked_threads', {});
    const saveBlockedThreads = (threads) => GM_setValue('s1p_blocked_threads', threads);
    const getBlockedUsers = () => GM_getValue('s1p_blocked_users', {});
    const saveBlockedUsers = (users) => GM_setValue('s1p_blocked_users', users);
    const saveUserTags = (tags) => GM_setValue('s1p_user_tags', tags);

    // [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);

    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 = (id, name) => { const settings = getSettings(); const b = getBlockedUsers(); b[id] = { name, timestamp: Date.now(), blockThreads: settings.blockThreadsOnUserBlock }; saveBlockedUsers(b); hideUserPosts(id); hideBlockedUserQuotes(); hideBlockedUserRatings(); if (b[id].blockThreads) applyUserThreadBlocklist(); };

    // [MODIFIED] 增加调用评分刷新函数
    const unblockUser = (id) => { const b = getBlockedUsers(); delete b[id]; saveBlockedUsers(b); showUserPosts(id); hideBlockedUserQuotes(); hideBlockedUserRatings(); unblockThreadsByUser(id); };

    // [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' : '';
                }
            }
        });
    };

    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_"]').forEach(row => {
            const titleElement = row.querySelector('th a.s.xst');
            if (!titleElement) return;

            const title = titleElement.textContent.trim();
            const threadId = row.id.replace('normalthread_', '');
            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) => GM_setValue('s1p_read_progress', progress);
    const updateThreadProgress = (threadId, postId, page, lastReadFloor) => {
        if (!postId || !page || !lastReadFloor) return;
        const progress = getReadProgress();
        const existingProgress = progress[threadId];

        // 只有当新进度更“远”时才更新,防止因向上滚动导致进度回滚
        if (!existingProgress || parseInt(page) > parseInt(existingProgress.page) || (parseInt(page) == parseInt(existingProgress.page) && lastReadFloor > existingProgress.lastReadFloor)) {
            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_"]').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_', '');
                    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 exportData = () => JSON.stringify({
        version: 3.2,
        settings: getSettings(),
        threads: getBlockedThreads(),
        users: getBlockedUsers(),
        user_tags: getUserTags(),
        title_filter_rules: getTitleFilterRules(),
        read_progress: getReadProgress()
    }, null, 2);

    const importData = (jsonStr) => {
        try {
            const imported = JSON.parse(jsonStr); if (typeof imported !== 'object' || imported === null) throw new Error("无效数据格式");
            let threadsImported = 0, usersImported = 0, progressImported = 0, rulesImported = 0, tagsImported = 0;

            const upgradeAndMerge = (type, importedData, getter, saver) => {
                if (!importedData || typeof importedData !== 'object') return 0;
                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';
                });
                const merged = { ...getter(), ...importedData };
                saver(merged);
                return Object.keys(importedData).length;
            };

            if(imported.settings) {
                saveSettings({...getSettings(), ...imported.settings});
            }

            threadsImported = upgradeAndMerge('threads', imported.threads, getBlockedThreads, saveBlockedThreads);
            usersImported = upgradeAndMerge('users', imported.users, getBlockedUsers, saveBlockedUsers);

            if (imported.user_tags && typeof imported.user_tags === 'object') {
                const mergedTags = { ...getUserTags(), ...imported.user_tags };
                saveUserTags(mergedTags);
                tagsImported = Object.keys(imported.user_tags).length;
            }

            if (imported.title_filter_rules && Array.isArray(imported.title_filter_rules)) {
                saveTitleFilterRules(imported.title_filter_rules);
                rulesImported = imported.title_filter_rules.length;
            } else if (imported.title_keywords && Array.isArray(imported.title_keywords)) { // 向后兼容导入旧格式
                const newRules = imported.title_keywords.map(k => ({ pattern: k, enabled: true, id: `rule_${Date.now()}_${Math.random()}` }));
                saveTitleFilterRules(newRules);
                rulesImported = newRules.length;
            }


            if (imported.read_progress) {
                const mergedProgress = { ...getReadProgress(), ...imported.read_progress };
                saveReadProgress(mergedProgress);
                progressImported = Object.keys(imported.read_progress).length;
            }

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

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

    // --- 设置管理 ---
    const defaultSettings = {
        enablePostBlocking: true,
        enableUserBlocking: true,
        enableUserTagging: true,
        enableReadProgress: true,
        openProgressInNewTab: true,
        enableNavCustomization: true,
        changeLogoLink: true,
        hideBlacklistTip: true,
        blockThreadsOnUserBlock: true,
        showBlockedByKeywordList: false,
        showManuallyBlockedList: false,
        hideImagesByDefault: false,
        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' }
        ]
    };
    const getSettings = () => {
        const saved = GM_getValue('s1p_settings', {});
        // 如果用户已保存自定义导航,则保留,否则使用默认值
        if (saved.customNavLinks && Array.isArray(saved.customNavLinks)) {
            return { ...defaultSettings, ...saved, customNavLinks: saved.customNavLinks };
        }
        return { ...defaultSettings, ...saved };
    };
    const saveSettings = (settings) => GM_setValue('s1p_settings', settings);

    // --- 界面定制功能 ---
    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;
            }
        }
    };

    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();

        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());
    };

    // --- UI 创建 ---
    const formatDate = (timestamp) => new Date(timestamp).toLocaleString('zh-CN');
    const showMessage = (msgEl, message, isSuccess) => { msgEl.textContent = message; msgEl.className = `s1p-message ${isSuccess ? 'success' : 'error'}`; msgEl.style.display = 'block'; setTimeout(() => { msgEl.style.display = 'none'; }, 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="confirm-title">${title}</div><div class="confirm-subtitle">${subtitle}</div></div><div class="s1p-confirm-footer"><button class="s1p-confirm-btn cancel">取消</button><button class="s1p-confirm-btn 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('.confirm').addEventListener('click', () => { onConfirm(); closeModal(); });
        modal.querySelector('.cancel').addEventListener('click', closeModal);
        modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
        document.body.appendChild(modal);
    };
    
    const removeProgressJumpButtons = () => document.querySelectorAll('.s1p-progress-container').forEach(el => el.remove());
    const removeBlockButtonsFromThreads = () => document.querySelectorAll('.s1p-action-container').forEach(el => el.remove());

    const createManagementModal = () => {
        document.querySelector('.s1p-modal')?.remove();
        const modal = document.createElement('div');
        modal.className = 's1p-modal';
        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">
                    <button class="s1p-tab-btn active" 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="nav-settings">导航栏定制</button>
                    <button class="s1p-tab-btn" data-tab="general-settings">通用设置</button>
                    <button class="s1p-tab-btn" data-tab="sync">设置同步</button>
                </div>
                <div id="s1p-tab-threads" class="s1p-tab-content active"></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-nav-settings" class="s1p-tab-content"></div>
                <div id="s1p-tab-general-settings" class="s1p-tab-content"></div>
                <div id="s1p-tab-sync" class="s1p-tab-content">
                    <div class="s1p-sync-title">全量设置同步</div>
                    <div class="s1p-sync-desc">通过复制/粘贴数据,在不同浏览器或设备间同步你的所有S1 Plus配置,包括屏蔽列表、导航栏、阅读进度和各项开关设置。</div>
                    <div class="s1p-sync-buttons">
                        <button id="s1p-export-btn" class="s1p-btn">导出数据</button>
                        <button id="s1p-import-btn" class="s1p-btn">导入数据</button>
                    </div>
                    <textarea id="s1p-sync-textarea" class="s1p-sync-textarea s1p-textarea" placeholder="在此粘贴导入数据或从此处复制导出数据"></textarea>
                    <div id="s1p-sync-message" class="s1p-message"></div>
                    <div class="s1p-sync-title">危险操作</div>
                    <div class="s1p-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>`;
        document.body.appendChild(modal);

        const tabs = {
            'threads': modal.querySelector('#s1p-tab-threads'),
            'users': modal.querySelector('#s1p-tab-users'),
            'tags': modal.querySelector('#s1p-tab-tags'),
            'nav-settings': modal.querySelector('#s1p-tab-nav-settings'),
            'general-settings': modal.querySelector('#s1p-tab-general-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({}) },
            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('');
        }

        // [REFACTORED] 全新用户标记标签页渲染逻辑
        const renderTagsTab = (options = {}) => {
            const editingUserId = options.editingUserId;
            const settings = getSettings();
            const isEnabled = settings.enableUserTagging;

            const toggleHTML = `
                <div class="s1p-settings-item" style="padding: 0; padding-bottom: 16px; margin-bottom: 10px; border-bottom: 1px solid var(--s1p-pri);">
                    <label class="s1p-settings-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>
            `;

            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-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-sync-textarea s1p-textarea" placeholder="在此粘贴导入数据或从此处复制导出数据..."></textarea>
                    <div id="s1p-tags-sync-message" class="s1p-message"></div>
                </div>

                <div class="s1p-settings-group">
                    <div class="s1p-settings-group-title">已标记用户列表</div>
                    ${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-tag-edit-area">${data.tag}</textarea>
                                    </div>
                                </div>
                                <div class="s1p-item-actions">
                                    <button class="s1p-btn 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 danger" data-action="delete-tag-item" data-user-id="${id}" data-user-name="${data.name}">删除</button>
                                </div>
                            </div>`;
                        }
                    }).join('')}</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 renderUserTab = () => {
            const settings = getSettings();
            const isEnabled = settings.enableUserBlocking;

            const toggleHTML = `
                <div class="s1p-settings-item" style="padding: 0; padding-bottom: 16px; margin-bottom: 10px; border-bottom: 1px solid var(--s1p-pri);">
                    <label class="s1p-settings-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>
            `;

            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>
                </div>
                <p class="s1p-setting-desc" style="margin-top: -4px; margin-bottom: 16px;">
                    <strong>提示</strong>:顶部总开关仅影响<strong>未来新屏蔽用户</strong>的默认设置。每个用户下方的独立开关,才是控制该用户主题帖的<strong>最终开关</strong>,拥有最高优先级。
                </p>
                ${userItemIds.length === 0
                    ? `<div class="s1p-empty">暂无屏蔽的用户</div>`
                    : `<div class="s1p-list">${userItemIds.map(id => {
                        const item = blockedUsers[id];
                        return `<div class="s1p-item" data-user-id="${id}"><div class="s1p-item-info"><div class="s1p-item-title">${item.name || `用户 #${id}`}</div><div class="s1p-item-meta">屏蔽时间: ${formatDate(item.timestamp)}</div><div class="s1p-item-toggle"><label class="s1p-switch"><input type="checkbox" class="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>`
                }
            `;

            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-item" style="padding: 0; padding-bottom: 16px; margin-bottom: 10px; border-bottom: 1px solid var(--s1p-pri);">
                    <label class="s1p-settings-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>
            `;

            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 id="s1p-keywords-message" class="s1p-message"></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' : ''}">
                    ${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>
            `;

            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: #eee; 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; // Exit if content is not rendered
                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 keyword-rule-enable" ${rule.enabled ? 'checked' : ''}><span class="s1p-slider"></span></label>
                        <input type="text" class="keyword-rule-pattern" placeholder="输入关键字或正则表达式" value="${rule.pattern || ''}">
                        <div class="s1p-editor-item-controls">
                            <button class="s1p-editor-btn keyword-rule-delete" title="删除规则"></button>
                        </div>
                    </div>
                `).join('');
                 if (rules.length === 0) {
                    container.innerHTML = `<div class="s1p-empty" style="padding: 12px;">暂无规则</div>`;
                }
            };

            renderRules();
            renderDynamicallyHiddenList();

            const saveAndApplyKeywordRules = () => {
                const newRules = [];
                tabs['threads'].querySelectorAll('#s1p-keyword-rules-list .s1p-editor-item').forEach(item => {
                    const pattern = item.querySelector('.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('.keyword-rule-enable').checked,
                            pattern: pattern
                        });
                    }
                });
                saveTitleFilterRules(newRules);
                hideThreadsByTitleKeyword();
                renderDynamicallyHiddenList();
                renderRules(); // Re-render to show the saved state and assign permanent IDs.
                showMessage(tabs['threads'].querySelector('#s1p-keywords-message'), '规则已保存!', true);
            };

            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 keyword-rule-enable" checked><span class="s1p-slider"></span></label>
                        <input type="text" class="keyword-rule-pattern" placeholder="输入关键字或正则表达式" value="">
                        <div class="s1p-editor-item-controls">
                            <button class="s1p-editor-btn keyword-rule-delete" title="删除规则"></button>
                        </div>
                    `;
                    container.appendChild(newItem);
                    newItem.querySelector('input[type="text"]').focus();
                } else if (target.classList.contains('keyword-rule-delete')) {
                    const item = target.closest('.s1p-editor-item');
                    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>`;
                    }
                } else if (target.id === 's1p-keyword-rules-save-btn') {
                    saveAndApplyKeywordRules();
                }
            });
        };

        const renderGeneralSettingsTab = () => {
            const settings = getSettings();
            tabs['general-settings'].innerHTML = `
                <div class="s1p-settings-group">
                    <div class="s1p-settings-group-title">功能开关</div>
                     <div class="s1p-settings-item">
                        <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-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="openProgressInNewTab" ${settings.openProgressInNewTab ? 'checked' : ''}><span class="s1p-slider"></span></label>
                    </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-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="title-suffix-input" data-setting="customTitleSuffix" value="${settings.customTitleSuffix || ''}" style="width: 200px;">
                    </div>
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-threadBlockHoverDelay">帖子屏蔽按钮悬停延迟</label>
                        <div style="display: flex; align-items: center; gap: 8px;">
                            <input type="number" id="s1p-threadBlockHoverDelay" data-setting="threadBlockHoverDelay" min="0" max="10" step="1" value="${settings.threadBlockHoverDelay}" class="title-suffix-input" style="width: 45px; text-align: center; padding: 6px 4px;">
                            <span style="font-size: 14px;">秒</span>
                        </div>
                    </div>
                </div>`;

            // 总的设置变更事件监听
            tabs['general-settings'].addEventListener('change', e => {
                const target = e.target;
                const settingKey = target.dataset.setting;
                if (settingKey) {
                    const settings = getSettings();
                    if (target.type === 'checkbox') {
                        settings[settingKey] = target.checked;
                    } else if (target.type === 'number') {
                        settings[settingKey] = parseFloat(target.value);
                    } else {
                        settings[settingKey] = target.value;
                    }
                    saveSettings(settings);
                    applyInterfaceCustomizations();

                    if (settingKey === 'hideImagesByDefault') {
                        applyImageHiding();
                        manageImageToggleAllButtons();
                    }
                }
            });
        };

        const renderNavSettingsTab = () => {
            const settings = getSettings();
            tabs['nav-settings'].innerHTML = `
                <div class="s1p-settings-group">
                    <div class="s1p-settings-item">
                        <label class="s1p-settings-label" for="s1p-enableNavCustomization">启用自定义导航栏</label>
                        <label class="s1p-switch"><input type="checkbox" id="s1p-enableNavCustomization" class="s1p-settings-checkbox" ${settings.enableNavCustomization ? 'checked' : ''}><span class="s1p-slider"></span></label>
                    </div>
                    <div class="s1p-nav-editor-list" style="margin-top: 12px; display: flex; flex-direction: column; gap: 8px;"></div>
                </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 id="s1p-settings-message" class="s1p-message"></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="nav-name" placeholder="名称" value="${link.name || ''}">
                        <input type="text" class="nav-href" placeholder="链接" value="${link.href || ''}">
                        <div class="s1p-editor-item-controls"><button class="s1p-editor-btn" 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('dragging');
                    }, 0);
                }
            });

            navListContainer.addEventListener('dragend', e => {
                if (draggedItem) {
                    draggedItem.classList.remove('dragging');
                    draggedItem = null;
                }
            });

            navListContainer.addEventListener('dragover', e => {
                e.preventDefault();
                if (!draggedItem) return;

                const container = e.currentTarget;
                const otherItems = [...container.querySelectorAll('.s1p-editor-item:not(.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="nav-name" placeholder="新链接"><input type="text" class="nav-href" placeholder="forum.php"><div class="s1p-editor-item-controls"><button class="s1p-editor-btn" data-action="delete" title="删除链接"></button></div>`;
                    navListContainer.appendChild(newItem);
                } else if (target.dataset.action === 'delete') {
                    target.closest('.s1p-editor-item').remove();
                } else if (target.id === 's1p-nav-restore-btn') {
                    const currentSettings = getSettings();
                    currentSettings.enableNavCustomization = defaultSettings.enableNavCustomization;
                    currentSettings.customNavLinks = defaultSettings.customNavLinks;
                    saveSettings(currentSettings);
                    renderNavSettingsTab();
                    applyInterfaceCustomizations();
                    initializeNavbar();
                    showMessage(modal.querySelector('#s1p-settings-message'), '导航栏已恢复为默认设置!', 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('.nav-name').value.trim(), href: item.querySelector('.nav-href').value.trim() })).filter(l=>l.name && l.href)
                    };
                    saveSettings(newSettings);
                    applyInterfaceCustomizations();
                    initializeNavbar();
                    showMessage(modal.querySelector('#s1p-settings-message'), '设置已保存!', true);
                }
            });
        };

        // --- 初始化渲染和事件绑定 ---
        renderThreadTab();
        renderUserTab();
        renderTagsTab();
        renderGeneralSettingsTab();
        renderNavSettingsTab();

        modal.addEventListener('change', e => {
            const target = e.target;
            const settings = getSettings();
            const featureKey = target.dataset.feature;

            if (featureKey && target.classList.contains('s1p-feature-toggle')) {
                const isChecked = target.checked;
                settings[featureKey] = isChecked;

                const contentWrapper = target.closest('.s1p-settings-item')?.nextElementSibling;
                if (contentWrapper && contentWrapper.classList.contains('s1p-feature-content')) {
                    contentWrapper.classList.toggle('expanded', isChecked);
                }
                saveSettings(settings);

                switch(featureKey) {
                    case 'enablePostBlocking':
                        isChecked ? addBlockButtonsToThreads() : removeBlockButtonsFromThreads();
                        break;
                    case 'enableUserBlocking':
                        refreshAllAuthiActions();
                        isChecked ? hideBlockedUsersPosts() : Object.keys(getBlockedUsers()).forEach(showUserPosts);
                        hideBlockedUserQuotes();
                        hideBlockedUserRatings();
                        break;
                    case 'enableUserTagging':
                        refreshAllAuthiActions();
                        break;
                    case 'enableReadProgress':
                        isChecked ? addProgressJumpButtons() : removeProgressJumpButtons();
                        break;
                }
                return;
            }
            else if(target.matches('.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);
            }
        });

        modal.addEventListener('click', (e) => {
            const target = e.target;
            if (e.target.matches('.s1p-modal, .s1p-modal-close')) modal.remove();
            if (e.target.matches('.s1p-tab-btn')) {
                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');
            }
            const unblockThreadId = e.target.dataset.unblockThreadId; if (unblockThreadId) { unblockThread(unblockThreadId); renderThreadTab(); }
            const unblockUserId = e.target.dataset.unblockUserId; if (unblockUserId) { unblockUser(unblockUserId); renderUserTab(); renderThreadTab(); }

            // --- 全局同步事件 ---
            const syncTextarea = modal.querySelector('#s1p-sync-textarea');
            const syncMessageEl = modal.querySelector('#s1p-sync-message');
            if(e.target.id === 's1p-export-btn') {
                syncTextarea.value = exportData();
                syncTextarea.select();
                try { document.execCommand('copy'); showMessage(syncMessageEl, '数据已导出并复制到剪贴板', true); }
                catch (err) { showMessage(syncMessageEl, '复制失败,请手动复制', false); }
            }
            if(e.target.id === 's1p-import-btn') {
                const jsonStr = syncTextarea.value.trim();
                if (!jsonStr) return showMessage(syncMessageEl, '请先粘贴要导入的数据', false);
                const result = importData(jsonStr);
                showMessage(syncMessageEl, result.message, result.success);
                if (result.success) {
                    renderThreadTab();
                    renderUserTab();
                    renderGeneralSettingsTab();
                    renderTagsTab();
                }
            }
            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(syncMessageEl, '请至少选择一个要清除的数据项。', false);
                }

                const itemsToClear = selectedKeys.map(key => `“${dataClearanceConfig[key].label}”`).join('、');

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

                        // 全局刷新
                        hideBlockedThreads();
                        hideBlockedUsersPosts();
                        applyUserThreadBlocklist();
                        hideThreadsByTitleKeyword();
                        initializeNavbar();
                        applyInterfaceCustomizations();
                        document.querySelectorAll('.s1p-progress-container').forEach(el => el.remove());

                        // 重新渲染所有标签页
                        renderThreadTab();
                        renderUserTab();
                        renderGeneralSettingsTab();
                        renderTagsTab();

                        showMessage(syncMessageEl, '选中的本地数据已成功清除。', true);
                    },
                    '确认清除'
                );
            }

            // --- [NEW] 用户标记标签页专属事件 ---
            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);
                        renderTagsTab();
                        refreshAllAuthiActions();
                        showMessage(targetTab.querySelector('#s1p-tags-sync-message'), `已删除对 ${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);
                        renderTagsTab();
                        refreshAllAuthiActions();
                        showMessage(targetTab.querySelector('#s1p-tags-sync-message'), `已更新对 ${userName} 的标记。`, true);
                    } else {
                        createConfirmationModal(`标记内容为空`, '您希望删除对该用户的标记吗?', () => {
                             delete tags[userId];
                             saveUserTags(tags);
                             renderTagsTab();
                             refreshAllAuthiActions();
                             showMessage(targetTab.querySelector('#s1p-tags-sync-message'), `已删除对 ${userName} 的标记。`, true);
                        }, '确认删除');
                    }
                }
                else if (target.id === 's1p-export-tags-btn') {
                    const textarea = targetTab.querySelector('#s1p-tags-sync-textarea');
                    const messageEl = targetTab.querySelector('#s1p-tags-sync-message');
                    textarea.value = JSON.stringify(getUserTags(), null, 2);
                    textarea.select();
                    try { document.execCommand('copy'); showMessage(messageEl, '用户标记已导出并复制到剪贴板。', true); }
                    catch (err) { showMessage(messageEl, '复制失败,请手动复制。', false); }
                }
                else if (target.id === 's1p-import-tags-btn') {
                    const textarea = targetTab.querySelector('#s1p-tags-sync-textarea');
                    const messageEl = targetTab.querySelector('#s1p-tags-sync-message');
                    const jsonStr = textarea.value.trim();
                    if (!jsonStr) return showMessage(messageEl, '请先粘贴要导入的数据。', 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(messageEl, `成功导入/更新 ${Object.keys(imported).length} 条用户标记。`, true);
                            textarea.value = '';
                        }, '确认导入');
                    } catch (e) { showMessage(messageEl, `导入失败: ${e.message}`, false); }
                }
            }
        });
    };

    // [MODIFIED] 增加带二次确认的屏蔽按钮
    const addBlockButtonsToThreads = () => {
        const settings = getSettings();
        const hoverDelay = settings.threadBlockHoverDelay * 1000;

        document.querySelectorAll('tbody[id^="normalthread_"], tbody[id^="stickthread_"]').forEach(row => {
            // 防止重复添加按钮
            if (row.querySelector('.s1p-action-container')) return;

            const iconCell = row.querySelector('td.icn');
            const titleElement = row.querySelector('th a.s.xst');

            if (iconCell && titleElement) {
                iconCell.style.position = 'relative';

                let hoverTimeout;
                iconCell.addEventListener('mouseenter', () => {
                    hoverTimeout = setTimeout(() => {
                        row.classList.add('s1p-hover-reveal');
                    }, hoverDelay);
                });
                iconCell.addEventListener('mouseleave', () => {
                    clearTimeout(hoverTimeout);
                    row.classList.remove('s1p-hover-reveal');
                });

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

                // 1. 创建在悬停时出现的主容器
                const actionContainer = document.createElement('div');
                actionContainer.className = 's1p-action-container';

                // 2. 创建初始的“屏蔽”按钮
                const blockBtn = document.createElement('span');
                blockBtn.className = 'thread-block-btn';
                blockBtn.textContent = '屏蔽';
                blockBtn.title = '屏蔽此贴';

                // 3. 创建确认/取消按钮的容器 (默认通过CSS隐藏)
                const confirmActionsContainer = document.createElement('div');
                confirmActionsContainer.className = 's1p-thread-block-confirm-actions';

                // 3a. 创建“取消”按钮 (叉)
                const cancelBtn = document.createElement('button');
                cancelBtn.className = 's1p-confirm-action-btn cancel';
                cancelBtn.innerHTML = ''; // '✗'
                cancelBtn.title = '取消';

                // 3b. 创建“确认”按钮 (勾)
                const confirmBtn = document.createElement('button');
                confirmBtn.className = 's1p-confirm-action-btn confirm';
                confirmBtn.innerHTML = ''; // '✓'
                confirmBtn.title = '确认屏蔽';

                // 组装确认按钮 (顺序:叉,然后是勾)
                confirmActionsContainer.appendChild(cancelBtn);
                confirmActionsContainer.appendChild(confirmBtn);

                // 组装主容器
                actionContainer.appendChild(blockBtn);
                actionContainer.appendChild(confirmActionsContainer);

                // 将完整的UI添加到帖子行中
                iconCell.appendChild(actionContainer);

                // --- 事件监听 ---

                // 点击初始“屏蔽”按钮,为帖子行添加一个class,以触发CSS来显示确认UI
                blockBtn.addEventListener('click', e => {
                    e.preventDefault();
                    e.stopPropagation();
                    row.classList.add('s1p-blocking-confirm');
                });

                // 点击“取消”按钮,移除class,恢复UI
                cancelBtn.addEventListener('click', e => {
                    e.preventDefault();
                    e.stopPropagation();
                    row.classList.remove('s1p-blocking-confirm');
                });

                // 点击“确认”按钮,执行真正的屏蔽操作
                confirmBtn.addEventListener('click', e => {
                    e.preventDefault();
                    e.stopPropagation();
                    blockThread(threadId, threadTitle);
                    // 帖子行被隐藏后,无需再清理class
                });

                // 为整行添加鼠标移出事件,如果确认UI是打开的,则自动关闭它。
                // 这是一个优化体验的细节,避免确认状态被卡住。
                row.addEventListener('mouseleave', () => {
                    if (row.classList.contains('s1p-blocking-confirm')) {
                        row.classList.remove('s1p-blocking-confirm');
                    }
                });
            }
        });
    };

    // [MODIFIED] 根据用户需求,简化了浮窗逻辑
    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);
        }

        let hideTimeout, showTimeout;
        let isComposing = false;
        let currentAnchorElement = null;

        const startHideTimer = () => {
            if (isComposing) return;
            clearTimeout(showTimeout);
            clearTimeout(hideTimeout);
            hideTimeout = setTimeout(() => popover.classList.remove('visible'), 300);
        };

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

        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;

            // Adjust if it goes off-screen
            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-edit-mode-textarea s1p-textarea" placeholder="输入标记内容...">${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, userAvatar, delay = 0, startInEditMode = false) => {
            cancelHideTimer();
            clearTimeout(showTimeout);

            showTimeout = setTimeout(() => {
                currentAnchorElement = anchorElement;
                popover.dataset.userId = userId;
                popover.dataset.userName = userName;
                popover.dataset.userAvatar = userAvatar;

                // Per user request, always go to edit mode.
                const userTags = getUserTags();
                renderEditMode(userName, userId, userTags[userId]?.tag || '');
                
                popover.classList.add('visible');
                repositionPopover(anchorElement);
            }, delay);
        };

        popover.show = show; // Expose the show function

        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);
                    refreshAllAuthiActions();
                    popover.classList.remove('visible');
                    break;
                case 'cancel-edit':
                    // Per user request, cancel closes the popover entirely.
                    popover.classList.remove('visible');
                    break;
            }
        });

        popover.addEventListener('mouseenter', cancelHideTimer);
        popover.addEventListener('mouseleave', startHideTimer);
        popover.addEventListener('compositionstart', () => isComposing = true);
        popover.addEventListener('compositionend', () => isComposing = false);
    };


    // --- [NEW/MODIFIED] 用户标记显示悬浮窗 ---
    const initializeTagDisplayPopover = () => {
        let popover = document.getElementById('s1p-tag-display-popover');
        if (!popover) {
            popover = document.createElement('div');
            popover.id = 's1p-tag-display-popover';
            popover.className = 's1p-tag-display-popover';
            document.body.appendChild(popover);
        }

        let showTimeout, hideTimeout;

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

                // 优先在上方显示
                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);
        };

        // 使用事件委托来处理所有标记的悬停事件
        document.body.addEventListener('mouseover', e => {
            const tagDisplay = e.target.closest('.s1p-user-tag-display');
            // 仅当文本溢出且存在 data-full-tag 属性时才显示悬浮窗
            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 'rgb(192, 51, 34)';
        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 'rgb(107, 114, 128)';
    };

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

        const now = Date.now();

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

            const threadIdMatch = row.id.match(/normalthread_(\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;

                jumpBtn.addEventListener('click', (e) => {
                    e.preventDefault();
                    if (settings.openProgressInNewTab) {
                        window.open(jumpBtn.href, '_blank');
                    } else {
                        window.location.href = jumpBtn.href;
                    }
                });

                jumpBtn.addEventListener('mouseover', () => {
                    jumpBtn.style.backgroundColor = fcolor;
                    jumpBtn.style.color = '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 trackReadProgressInThread = () => {
        const settings = getSettings();
        if (!settings.enableReadProgress || !document.getElementById('postlist')) return;

        const threadIdMatch = window.location.href.match(/thread-(\d+)-/);
        if (!threadIdMatch) return;
        const threadId = threadIdMatch[1];

        const pageMatch = window.location.href.match(/thread-\d+-(\d+)-/);
        const currentPage = pageMatch ? pageMatch[1] : '1';

        let maxFloorOnPage = 0;
        let correspondingPostId = null;
        let saveTimeout;

        const saveCurrentProgress = () => {
            if (correspondingPostId && maxFloorOnPage > 0) {
                updateThreadProgress(threadId, correspondingPostId, currentPage, maxFloorOnPage);
            }
        };

        const debouncedSave = () => {
            clearTimeout(saveTimeout);
            saveTimeout = setTimeout(saveCurrentProgress, 1500); // 停止滚动1.5秒后保存
        };

        const observer = new IntersectionObserver(entries => {
            let hasIntersectingEntry = false;
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    hasIntersectingEntry = true;
                    const postElement = entry.target;
                    const floorElement = postElement.querySelector('.pi em');
                    if (floorElement) {
                        const currentFloor = parseInt(floorElement.textContent) || 0;
                        if (currentFloor > maxFloorOnPage) {
                            maxFloorOnPage = currentFloor;
                            correspondingPostId = postElement.id.replace('pid', '');
                        }
                    }
                }
            });

            if (hasIntersectingEntry) {
                debouncedSave();
            }
        }, { threshold: 0.1 });

        document.querySelectorAll('table[id^="pid"]').forEach(el => observer.observe(el));

        // 当用户离开页面时,作为最后的保险措施立即保存一次
        const finalSave = () => {
            if (document.visibilityState === 'hidden') {
                clearTimeout(saveTimeout); // 取消任何待处理的延迟保存
                saveCurrentProgress(); // 立即保存
            }
        };

        if (window.s1p_saveProgressHandler) {
            document.removeEventListener('visibilitychange', window.s1p_saveProgressHandler);
        }
        window.s1p_saveProgressHandler = finalSave;
        document.addEventListener('visibilitychange', window.s1p_saveProgressHandler);
    };

    const refreshAllAuthiActions = () => {
        document.querySelectorAll('.s1p-authi-actions-wrapper').forEach(el => el.remove());
        addActionsToPostFooter();
    };

    const createOptionsMenu = (anchorElement) => {
        // Remove any existing menu
        document.querySelector('.s1p-tag-options-menu')?.remove();

        const { userId, userName, userAvatar } = 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="delete">删除标记</button>
        `;

        document.body.appendChild(menu);

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

        const closeMenu = () => menu.remove();

        menu.addEventListener('click', (e) => {
            const action = e.target.dataset.action;
            if (action === 'edit') {
                const popover = document.getElementById('s1p-tag-popover-main');
                if (popover && popover.show) {
                    popover.show(anchorElement, userId, userName, userAvatar, 0, true); // Directly enter edit mode
                }
            } else if (action === 'delete') {
                createConfirmationModal(`确认删除对 "${userName}" 的标记吗?`, '此操作不可撤销。', () => {
                    const tags = getUserTags();
                    delete tags[userId];
                    saveUserTags(tags);
                    refreshAllAuthiActions();
                }, '确认删除');
            }
            closeMenu();
        });

        // Close menu when clicking outside
        setTimeout(() => {
            document.addEventListener('click', closeMenu, { once: true });
        }, 0);
    };

    const addActionsToPostFooter = () => {
        const settings = getSettings();
        if (!settings.enableUserBlocking && !settings.enableUserTagging) return;

        document.querySelectorAll('div.authi a[href*="authorid="]').forEach(viewAuthorLink => {
            const authiDiv = viewAuthorLink.closest('.authi');
            if (!authiDiv) return;

            // --- Self-Healing: Clean up old/broken elements before proceeding ---
            authiDiv.querySelector('.s1p-authi-actions-wrapper')?.remove();
            const oldBlockLink = authiDiv.querySelector('a.s1p-block-user-in-authi:not(.s1p-authi-action)');
            if (oldBlockLink) {
                const precedingPipe = oldBlockLink.previousElementSibling;
                if (precedingPipe && precedingPipe.classList.contains('pipe')) precedingPipe.remove();
                oldBlockLink.remove();
            }

            const urlParams = new URLSearchParams(viewAuthorLink.href.split('?')[1]);
            const userId = urlParams.get('authorid');
            if (!userId) return;

            const postContainer = authiDiv.closest('td.plc');
            const plsCell = postContainer ? postContainer.previousElementSibling : null;
            if (!plsCell) return;

            const userLinkInPi = plsCell.querySelector(`.pi .authi a[href*="space-uid-${userId}"]`);
            const userName = userLinkInPi ? userLinkInPi.textContent.trim() : `用户 #${userId}`;
            const userAvatar = plsCell.querySelector('.avatar img')?.src;

            const wrapper = document.createElement('span');
            wrapper.className = 's1p-authi-actions-wrapper';

            // --- Robust Width Calculation ---
            const authiRect = authiDiv.getBoundingClientRect();
            const lastElementRect = viewAuthorLink.getBoundingClientRect();
            let availableWidth = authiRect.right - lastElementRect.right;
            const buffer = 15; // Safety margin
            availableWidth -= buffer;

            if (settings.enableUserBlocking) {
                const pipe = document.createElement('span');
                pipe.className = 'pipe';
                pipe.textContent = '|';
                wrapper.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();
                    const subtitle = getSettings().blockThreadsOnUserBlock ? '该用户的所有帖子和主题帖都将被隐藏。' : '该用户的所有帖子都将被隐藏。';
                    createConfirmationModal(`确定要屏蔽用户 "${userName}" 吗?`, subtitle, () => blockUser(userId, userName), '确定屏蔽');
                });
                wrapper.appendChild(blockLink);
                availableWidth -= 85; // Estimated width for block link + pipe
            }

            if (settings.enableUserTagging) {
                const userTags = getUserTags();
                const userTag = userTags[userId];
                const pipe = document.createElement('span');
                pipe.className = 'pipe';
                pipe.textContent = '|';
                wrapper.appendChild(pipe);
                availableWidth -= 10; // Estimated width for 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.dataset.userAvatar = userAvatar;
                    optionsIcon.addEventListener('click', (e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        createOptionsMenu(e.currentTarget);
                    });

                    tagContainer.appendChild(tagDisplay);
                    tagContainer.appendChild(optionsIcon);

                    if (availableWidth > 50) {
                        tagContainer.style.maxWidth = `${availableWidth}px`;
                    }

                    wrapper.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, 0, true);
                        }
                    });
                    wrapper.appendChild(tagLink);
                }
            }

            // --- [S1P-FIX] Take control of native hover buttons to fix layout shifts and CSS conflicts ---
            const ordertypeLink = authiDiv.querySelector('a[href*="ordertype=1"]');
            const readmodeLink = authiDiv.querySelector('a[onclick*="readmode"]');
            
            // --- [MODIFIED] Find the "只看大图" link by its text content ---
            let viewImagesLink = null;
            for (const link of authiDiv.querySelectorAll('a')) {
                if (link.textContent.trim() === '只看大图') {
                    viewImagesLink = link;
                    break;
                }
            }
            
            const insertionPoint = readmodeLink || viewAuthorLink;
            insertionPoint.after(wrapper);

            if (ordertypeLink && readmodeLink) {
                const nativeElements = [
                    ordertypeLink.previousElementSibling, // pipe
                    ordertypeLink,
                    readmodeLink.previousElementSibling, // pipe
                    readmodeLink
                ].filter(Boolean); // Filter out nulls if elements don't exist

                // 1. Override any stylesheet by hiding elements by default
                nativeElements.forEach(el => el.style.display = 'none');

                // 2. Add precise event listeners to show the buttons
                const showNativeButtons = () => {
                    nativeElements.forEach(el => el.style.display = 'inline');
                };

                viewAuthorLink.addEventListener('mouseenter', showNativeButtons);
                
                // --- [MODIFIED] Add listener to "只看大图" link if found
                if (viewImagesLink) {
                    viewImagesLink.addEventListener('mouseenter', showNativeButtons);
                }

                // 3. Hide buttons when the mouse leaves the entire author info area
                authiDiv.addEventListener('mouseleave', () => {
                    nativeElements.forEach(el => el.style.display = 'none');
                });
            }
        });
    };

    // 自动签到 (适配 study_daily_attendance 插件)
    function autoSign() {
        console.log('S1 Plus: Running autoSign...');
        const checkinLink = document.querySelector('a[href*="study_daily_attendance-daily_attendance.html"]');
        if (!checkinLink) {
            console.log('S1 Plus: Check-in link not found. Exiting autoSign.');
            return;
        }
        console.log('S1 Plus: Check-in link found:', checkinLink.href);

        var now = new Date();
        var date = now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate();
        var signedDate = GM_getValue("signedDate");
        console.log(`S1 Plus: Today is ${date}. Last signed date is ${signedDate}.`);

        // 如果今天已经签到,直接隐藏链接并返回
        if (signedDate == date) {
            console.log('S1 Plus: Already signed in today. Hiding link.');
            checkinLink.style.display = 'none';
            return;
        }

        // 早上6点后才执行签到操作
        if (now.getHours() < 6) {
            console.log('S1 Plus: It is before 6 AM. Skipping sign-in action for now.');
            return;
        }

        console.log('S1 Plus: Proceeding with check-in request...');
        // 使用 GM_xmlhttpRequest 访问签到链接
        GM_xmlhttpRequest({
            method: "GET",
            url: checkinLink.href,
            onload: function (response) {
                // 标记为已签到,防止重复请求
                GM_setValue("signedDate", date);
                // 成功后隐藏链接
                checkinLink.style.display = 'none';
                console.log('S1 Plus: Auto check-in request sent. Status:', response.status);
            },
            onerror: function(response) {
                console.error('S1 Plus: Auto check-in request failed.', response);
            }
        });
    }

    // --- 主流程 ---
    function main() {
        initializeNavbar();
        initializeTagDisplayPopover(); // [NEW] 初始化用户标记显示悬浮窗

        const observerCallback = (mutations, observer) => {
            // 在处理DOM变化前先断开观察,防止无限循环
            observer.disconnect();
            // 执行所有DOM修改
            applyChanges();
            // 完成后再重新连接观察器
            observer.observe(document.getElementById('ct'), { childList: true, subtree: true });
        };

        const observer = new MutationObserver(observerCallback);

        // 首次加载时直接运行一次
        applyChanges();

        // 开始观察 #ct 容器的变化
        observer.observe(document.getElementById('ct'), { childList: true, subtree: true });
    }

    function applyChanges() {
        const settings = getSettings();
        if (settings.enablePostBlocking) {
            hideBlockedThreads();
            hideThreadsByTitleKeyword();
            addBlockButtonsToThreads();
            applyUserThreadBlocklist();
        }
        if (settings.enableUserBlocking) {
            hideBlockedUsersPosts();
            hideBlockedUserQuotes();
            hideBlockedUserRatings();
        }
        if (settings.enableUserBlocking || settings.enableUserTagging) {
            addActionsToPostFooter();
        }
        if (settings.enableUserTagging) {
            initializeTaggingPopover();
        }
        if (settings.enableReadProgress) {
            addProgressJumpButtons();
        }
        applyInterfaceCustomizations();
        applyImageHiding();
        manageImageToggleAllButtons();
        renameAuthorLinks(); // --- [新增] 调用文本替换函数 ---
        trackReadProgressInThread();
        try {
            autoSign();
        } catch (e) {
            console.error('S1 Plus: Error caught while running autoSign():', e);
        }
    }

    main();

})();