South Plus +++

South Plus +++ 是一款集界面与阅读优化、帖子筛选屏蔽、快捷导航回复及自动购买等功能于一体的 South Plus 系列论坛增强脚本。

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         South Plus +++
// @namespace    https://south-plus.org/
// @version      0.6.0
// @description  South Plus +++ 是一款集界面与阅读优化、帖子筛选屏蔽、快捷导航回复及自动购买等功能于一体的 South Plus 系列论坛增强脚本。
// @author       local
// @match        *://*.south-plus.net/*
// @match        *://south-plus.net/*
// @match        *://*.south-plus.org/*
// @match        *://south-plus.org/*
// @match        *://*.white-plus.net/*
// @match        *://white-plus.net/*
// @match        *://*.north-plus.net/*
// @match        *://north-plus.net/*
// @match        *://*.level-plus.net/*
// @match        *://level-plus.net/*
// @match        *://*.soul-plus.net/*
// @match        *://soul-plus.net/*
// @match        *://*.snow-plus.net/*
// @match        *://snow-plus.net/*
// @match        *://*.spring-plus.net/*
// @match        *://spring-plus.net/*
// @match        *://*.summer-plus.net/*
// @match        *://summer-plus.net/*
// @match        *://*.blue-plus.net/*
// @match        *://blue-plus.net/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

/*
 * 使用说明:
 * 1. 将本脚本安装到 Tampermonkey / Violentmonkey 等用户脚本管理器后启用。
 * 2. 打开匹配的 South Plus 站点页面,脚本会自动生效,无需手动执行。
 * 3. 默认提供紧凑布局、阅读优化、快速导航、已读标记、关注主题和本地屏蔽规则等增强功能。
 * 4. 所有设置与已读/关注状态仅保存在当前浏览器本地,不会上传到服务器。
 */

(function factoryWrapper(root, factory) {
  if (typeof module === 'object' && module.exports) {
    module.exports = factory(true);
    return;
  }

  var api = factory(false);
  api.init();
})(typeof globalThis !== 'undefined' ? globalThis : this, function createSouthPlusEnhancer(testMode) {
  'use strict';

  var APP = 'spEnhancer';
  var STORE_KEY = APP + ':settings:v1';
  var READ_KEY = APP + ':readThreads:v1';
  var WATCH_KEY = APP + ':watchThreads:v1';
  var PROGRESS_KEY = APP + ':readProgress:v1';
  var RESTORE_PROGRESS_KEY = APP + ':restoreProgressTid:v1';
  var AUTO_BUY_KEY = APP + ':autoBuyAttempts:v1';
  var TASK_CLAIM_KEY = APP + ':taskClaims:v1';
  var TASK_AUTO_CLAIM_STATE_KEY = APP + ':taskAutoClaim:v1';
  var PROFILE_INFOBOX_CACHE_KEY = APP + ':profileInfobox:v1';
  var RESOURCE_KEY = APP + ':resources:v1';
  var READ_RESOURCE_RAIL_COLLAPSED_KEY = APP + ':readResourceRailCollapsed:v1';
  var NAVIGATION_KEY = APP + ':navigation:v1';
  var NAVIGATION_COLLAPSE_KEY = APP + ':navigationCollapse:v1';
  var NAVIGATION_COLLAPSE_DEFAULT_KEY = APP + ':navigationCollapseDefault:v1';
  var NAVIGATION_PIN_KEY = APP + ':navigationPins:v1';
  var NAVIGATION_USAGE_KEY = APP + ':navigationUsage:v1';
  var NAVIGATION_REFRESH_KEY = APP + ':navigationRefresh:v1';
  var FAVORITE_NAV_COUNT_CACHE_KEY = APP + ':favoriteNavCountCache:v1';
  var FAVORITE_NAV_SEEN_KEY = APP + ':favoriteNavSeen:v1';
  var THREAD_UPDATE_KEY = APP + ':threadUpdates:v1';
  var AUTO_BUY_CHECK_TTL = 10 * 60 * 1000;
  var AUTO_BUY_ATTEMPT_LIMIT = 100;
  var TASK_CLAIM_RECORD_LIMIT = 100;
  var TASK_AUTO_CLAIM_DAILY_COOLDOWN = 18 * 60 * 60 * 1000;
  var TASK_AUTO_CLAIM_WEEKLY_COOLDOWN = 7 * 24 * 60 * 60 * 1000;
  var TASK_AUTO_CLAIM_PROBE_RETRY_TTL = 6 * 60 * 60 * 1000;
  var TASK_AUTO_CLAIM_ERROR_RETRY_TTL = 60 * 60 * 1000;
  var PROFILE_INFOBOX_CACHE_TTL = 6 * 60 * 60 * 1000;
  var RESOURCE_LIMIT = 500;
  var NAVIGATION_POOL_LIMIT = 160;
  var NAVIGATION_USAGE_LIMIT = 160;
  var NAVIGATION_REFRESH_TTL = 6 * 60 * 60 * 1000;
  var FAVORITE_NAV_COUNT_CACHE_TTL = 2 * 60 * 1000;
  var FAVORITE_NAV_COUNT_RETRY_TTL = 60 * 1000;
  var READ_PROGRESS_LIMIT = 200;
  var STALE_PROGRESS_MAX_AGE = 180 * 24 * 60 * 60 * 1000;
  var LOCAL_STORAGE_WARNING_BYTES = 4 * 1024 * 1024;
  var PREVIEW_GALLERY_BATCH_SIZE = 36;
  var PREVIEW_DOWNLOAD_MAX_RETRIES = 6;
  var PREVIEW_DOWNLOAD_CONCURRENCY = 3;
  var FAVORITE_NAV_BATCH_SIZE = 50;
  var FAVORITE_NAV_MAX_FETCHED_ITEMS = 120;
  var READ_REPLIES_PER_PAGE = 30;
  var THREAD_UPDATE_LIMIT = 260;
  var THREAD_UPDATE_CHECK_BATCH_SIZE = 10;
  var THREAD_UPDATE_HOT_TTL = 15 * 60 * 1000;
  var THREAD_UPDATE_WARM_TTL = 6 * 60 * 60 * 1000;
  var THREAD_UPDATE_COLD_TTL = 24 * 60 * 60 * 1000;
  var THREAD_UPDATE_RATE_LIMIT_COOLDOWN = 60 * 60 * 1000;
  var THREAD_PREVIEW_CACHE_TTL = 20 * 60 * 1000;
  var THREAD_PREVIEW_FAILURE_TTL = 3 * 60 * 1000;
  var THREAD_PREVIEW_CACHE_LIMIT = 50;
  var THREAD_PREVIEW_IMAGE_LIMIT = 6;
  var THREAD_PREVIEW_HOVER_DELAY = 520;
  var THREAD_PREVIEW_FAST_HOVER_DELAY = 260;
  var THREAD_PREVIEW_IMAGE_LOAD_DELAY = 180;
  var THREAD_PREVIEW_IMAGE_BATCH_SIZE = 2;
  var SP_BALANCE_CACHE_TTL = 90 * 1000;
  var TOOLBOX_BUTTON_SELECTOR = '[data-spx-toolbox-button="1"]';
  var SETTINGS_BUTTON_SELECTOR = '[data-spx-settings-button="1"]';
  var COMMAND_PALETTE_BUTTON_SELECTOR = '[data-spx-command-palette-button="1"]';
  var THREAD_ROW_HIDDEN_CLASSES = ['spx-filter-hidden', 'spx-resource-filter-hidden', 'spx-hidden-rule', 'spx-unread-hidden', 'spx-thread-row-hidden'];
  var pendingModuleNavigationConfigs = [];
  var scriptRequestState = {
    queue: [],
    running: false,
    timer: null,
    lastStartedAt: 0,
    cooldownUntil: 0,
    sequence: 0,
  };
  var threadPreviewCache = {};
  var threadPreviewCacheOrder = [];
  var spBalanceCache = { value: null, expiresAt: 0 };
  var readResourceRailContext = { posts: null, state: null, filter: 'all' };
  var readSummaryContext = { settings: null, state: null, posts: null, tid: '', originalAuthor: '' };
  var pendingFavoriteNavStatusNotes = [];
  var enhanceCycle = 0;
  var RESOURCE_STATUSES = {
    saved: '已保存',
    todo: '待下载',
    done: '已处理',
    invalid: '已失效',
  };
  var RESOURCE_CATEGORIES = {
    magnet: '磁力',
    ed2k: '电驴',
    torrent: '种子',
    archive: '压缩包',
    cloud: '网盘',
    image: '图片',
    external: '外链',
  };
  var RESOURCE_BADGE_ORDER = ['baidu', 'quark', 'pikpak', 'magnet', 'torrent', 'archive', 'ed2k', 'external', 'cloud'];
  var RESOURCE_BADGE_DEFINITIONS = {
    baidu: { label: '百度', pattern: /百度|baidu|pan\.baidu|yun\.baidu/i },
    quark: { label: '夸克', pattern: /夸克|quark|pan\.quark/i },
    pikpak: { label: 'PikPak', pattern: /pikpak/i },
    magnet: { label: '磁力', pattern: /磁力|magnet/i },
    torrent: { label: '种子', pattern: /种子|torrent/i },
    archive: { label: '压缩包', pattern: /压缩包|解压|zip|rar|7z|iso/i },
    ed2k: { label: '电驴', pattern: /电驴|ed2k/i },
    external: { label: '外链', pattern: /外链|直链|镜像/i },
    cloud: { label: '网盘', pattern: /网盘|云盘/i },
  };
  var CLOUD_RESOURCE_HOST_PATTERN = [
    'pan\\.baidu\\.com',
    'yun\\.baidu\\.com',
    'aliyundrive\\.com',
    'alipan\\.com',
    'quark\\.cn',
    'drive\\.uc\\.cn',
    '115\\.com',
    'mega\\.nz',
    'terabox\\.com',
    'lanzou[a-z]?\\.com',
    'ilanzou\\.com',
    'weiyun\\.com',
    'cloud\\.189\\.cn',
    '123pan\\.com',
    'mypikpak\\.com',
    'pikpak\\.com',
    'mediafire\\.com',
    '4shared\\.com',
    'onedrive\\.live\\.com',
    'pan\\.xunlei\\.com',
    'cowtransfer\\.com',
    'feijipan\\.com',
  ].join('|');
  var CLOUD_RESOURCE_HOST_RE = new RegExp('(?:^|\\.)(?:' + CLOUD_RESOURCE_HOST_PATTERN + ')$', 'i');
  var CLOUD_RESOURCE_PATH_RE = new RegExp('^(?:(?:www|share|pan)\\.)?(?:' + CLOUD_RESOURCE_HOST_PATTERN + ')\\/', 'i');
  var TORRENT_RESOURCE_EXT_RE = /\.torrent(?:[?#&]|$)/i;
  var ARCHIVE_RESOURCE_EXT_RE = /\.(?:zip|rar|7z|tar|tgz|gz|bz2|xz|iso|001)(?:[?#&]|$)/i;
  var DEFAULT_SETTINGS = {
    cleanMode: true,
    readerMode: true,
    immersiveRead: true,
    nightMode: false,
    immersiveFontSize: 16,
    unifiedPreviewGallery: true,
    homeDashboard: true,
    forumDashboard: true,
    moduleNavDensity: 'comfortable',
    adBlock: true,
    compactRead: true,
    unreadOnly: false,
    onlyOriginalAuthor: false,
    foldQuotes: true,
    hideUserProfile: false,
    networkFriendly: true,
    autoTaskClaim: true,
    autoBuyPost: false,
    autoBuyMaxSp: 5,
    smartModuleNavSort: true,
    titleKeywords: [],
    authorKeywords: [],
    quickReplies: [
      '感谢分享',
      '支持一下',
      '楼主辛苦了',
      '感谢楼主分享',
      '收藏备用',
      '马克一下',
      '先顶后看',
      '感谢整理',
      '资源很棒',
      '正好需要',
      '学习一下',
      '感谢补档',
    ],
  };

  var QUICK_REPLY_EMOTES = [
    '001.jpg', '002.jpg', '003.jpg', '005.jpg', '006.jpg', '007.jpg', '008.jpg', '009.jpg', '010.jpg', '012.jpg',
    '013.gif', '014.jpg', '015.jpg', '016.jpg', '017.jpg', '018.jpg', '019.jpg', '020.jpg', '021.gif', '022.gif',
    '023.jpg', '024.jpg', '025.jpg', '026.jpg', '027.jpg', '028.jpg', '029.jpg', '030.jpg', '031.gif', '032.jpg',
    '033.jpg', '034.jpg', '035.jpg', '036.jpg', '037.gif', '038.gif', '039.jpg', '040.jpg',
  ].map(function mapQuickReplyEmote(fileName) {
    var code = fileName.replace(/\D/g, '').replace(/^0+/, '') || '1';
    return {
      code: '[s:' + code + ']',
      fileName: fileName,
      src: 'https://south-plus.org/images/post/smile/smallface/face' + fileName,
    };
  });

  function parseThreadId(value) {
    var text = String(value || '');
    var match =
      text.match(/^(\d+)$/) ||
      text.match(/(?:^|[_-])(?:td|ajax)_(\d+)(?:\D|$)/) ||
      text.match(/(?:td|a_ajax)_(\d+)/) ||
      text.match(/[?&]tid=(\d+)/) ||
      text.match(/[?&]tid-(\d+)/) ||
      text.match(/read\.php\?tid[=-](\d+)/);
    return match ? match[1] : '';
  }

  function parseLineList(value) {
    var seen = {};
    return String(value || '')
      .split(/\r?\n/)
      .map(function trimLine(line) {
        return line.trim();
      })
      .filter(function keepUnique(line) {
        if (!line || seen[line]) return false;
        seen[line] = true;
        return true;
      });
  }

  function parseQuickReplyList(value) {
    return parseLineList(value).slice(0, 30);
  }

  function normalizeModuleNavDensity(value) {
    var text = String(value || '').trim();
    if (text === 'compact' || text === 'standard' || text === 'comfortable') return text;
    return 'comfortable';
  }

  function getModuleNavigationDensityConfig(value) {
    var density = normalizeModuleNavDensity(value);
    var configs = {
      compact: {
        width: 220,
        maxOffset: 76,
        sectionHeight: 28,
        parentHeight: 30,
        itemHeight: 34,
        childHeight: 30,
        itemPadding: 10,
        sectionPadding: 9,
        radius: 9,
      },
      standard: {
        width: 240,
        maxOffset: 64,
        sectionHeight: 30,
        parentHeight: 36,
        itemHeight: 40,
        childHeight: 32,
        itemPadding: 12,
        sectionPadding: 11,
        radius: 10,
      },
      comfortable: {
        width: 260,
        maxOffset: 52,
        sectionHeight: 34,
        parentHeight: 40,
        itemHeight: 44,
        childHeight: 36,
        itemPadding: 14,
        sectionPadding: 12,
        radius: 12,
      },
    };
    return Object.assign({ density: density }, configs[density]);
  }

  function normalizeListValue(value, parser) {
    var source = value || [];
    return parser(source.join ? source.join('\n') : source);
  }

  function parseTagList(value) {
    var seen = {};
    return String(value || '')
      .replace(/\\n/g, '\n')
      .split(/[\n,,、#]+/)
      .map(function trimTag(tag) {
        return tag.trim().replace(/\s+/g, ' ');
      })
      .filter(function keepTag(tag) {
        var key = tag.toLowerCase();
        if (!tag || seen[key]) return false;
        seen[key] = true;
        return true;
      })
      .slice(0, 12);
  }

  function mergeTagLists() {
    var seen = {};
    var result = [];
    Array.prototype.slice.call(arguments).forEach(function readTags(tags) {
      normalizeListValue(tags, parseTagList).forEach(function addTag(tag) {
        var key = tag.toLowerCase();
        if (seen[key]) return;
        seen[key] = true;
        result.push(tag);
      });
    });
    return result;
  }

  function formatTags(tags) {
    var values = normalizeListValue(tags, parseTagList);
    return values.length ? values.join(' / ') : '';
  }

  function clampRatio(value) {
    var ratio = Number(value);
    if (!isFinite(ratio)) return 0;
    return Math.min(1, Math.max(0, ratio));
  }

  function getReadProgressPercent(record) {
    return Math.round(clampRatio(record && record.progress) * 100);
  }

  function isCompletedReadProgress(record) {
    return !!(record && record.updatedAt && getReadProgressPercent(record) >= 100);
  }

  function isReadProgressBehind(previous, next) {
    if (!previous || !next) return false;
    var previousPage = Math.max(1, Number(previous.page) || 1);
    var nextPage = Math.max(1, Number(next.page) || 1);
    if (nextPage !== previousPage) return nextPage < previousPage;
    return clampRatio(next.progress) < clampRatio(previous.progress);
  }

  function mergeReadProgressRecord(previous, next) {
    if (!next) return previous || null;
    if (isReadProgressBehind(previous, next)) return previous;
    if (previous && previous.tags && !next.tags) {
      next.tags = normalizeListValue(previous.tags, parseTagList);
    }
    return next;
  }

  function formatReadProgress(record) {
    if (!record || !record.updatedAt) return '';
    var percent = getReadProgressPercent(record);
    var page = Number(record.page) || 1;
    return (page > 1 ? '第 ' + page + ' 页 · ' : '') + percent + '%';
  }

  function getReadProgressFloorLabels(record) {
    return {
      last: String((record && record.floorLabel) || ''),
      next: String((record && (record.nextFloorLabel || record.floorLabel)) || ''),
    };
  }

  function getReadProgressRestoreTarget(record, mode) {
    var currentMode = mode === 'last' ? 'last' : 'next';
    var lastTop = Math.max(0, Number(record && (record.floorTop !== undefined ? record.floorTop : record.scrollY)) || 0);
    var nextTop = Math.max(
      0,
      Number(record && (record.nextFloorTop !== undefined ? record.nextFloorTop : record.floorTop !== undefined ? record.floorTop : record.scrollY)) || 0
    );
    if (currentMode === 'last') {
      return {
        mode: 'last',
        hash: String((record && record.floorHash) || ''),
        top: lastTop,
        label: String((record && record.floorLabel) || ''),
      };
    }
    return {
      mode: 'next',
      hash: String((record && (record.nextFloorHash || record.floorHash)) || ''),
      top: nextTop,
      label: String((record && (record.nextFloorLabel || record.floorLabel)) || ''),
    };
  }

  function normalizeThreadUpdateReplyCount(value) {
    if (value === null || value === undefined || value === '') return null;
    var count = Number(value);
    if (!isFinite(count) || count < 0) return null;
    return Math.floor(count);
  }

  function parseThreadReplyCount(text) {
    var value = compactText(text);
    if (!value) return null;
    var labeled = value.match(/(?:^|[\s·|/::])(?:回复|回帖|Replies?)\s*[::]?\s*(\d+)/i);
    return labeled ? normalizeThreadUpdateReplyCount(labeled[1]) : null;
  }

  function normalizeThreadUpdateRecord(record, key) {
    if (!record || typeof record !== 'object') return null;
    var source = record;
    var id = parseThreadId(key || source.id || source.url || source.progressUrl || '');
    if (!id) return null;
    var knownReplies = normalizeThreadUpdateReplyCount(source.knownReplies);
    if (knownReplies === null) knownReplies = normalizeThreadUpdateReplyCount(source.replies);
    if (knownReplies === null) knownReplies = 0;
    var readReplies = normalizeThreadUpdateReplyCount(source.readReplies);
    if (readReplies === null) readReplies = knownReplies;
    readReplies = Math.min(readReplies, knownReplies);
    var unreadReplies = Math.max(0, knownReplies - readReplies);
    return {
      id: id,
      title: compactText(source.title),
      url: String(source.url || source.progressUrl || ''),
      knownReplies: knownReplies,
      readReplies: readReplies,
      unreadReplies: unreadReplies,
      hasNewReplies: unreadReplies > 0,
      lastCheckedAt: Math.max(0, Number(source.lastCheckedAt) || 0),
      lastReadAt: Math.max(0, Number(source.lastReadAt) || 0),
      updatedAt: Math.max(0, Number(source.updatedAt || source.lastCheckedAt || source.lastReadAt) || 0),
      source: String(source.source || ''),
    };
  }

  function pruneThreadUpdates(updates, limit) {
    var source = updates || {};
    var normalized = {};
    Object.keys(source).forEach(function normalizeUpdateRecord(key) {
      var record = normalizeThreadUpdateRecord(source[key], key);
      if (record) normalized[record.id] = record;
    });
    var keys = Object.keys(normalized)
      .sort(function sortThreadUpdates(left, right) {
        var leftAt = Math.max(normalized[left].updatedAt, normalized[left].lastCheckedAt, normalized[left].lastReadAt);
        var rightAt = Math.max(normalized[right].updatedAt, normalized[right].lastCheckedAt, normalized[right].lastReadAt);
        return rightAt - leftAt || Number(right) - Number(left);
      })
      .slice(0, Math.max(1, Number(limit) || THREAD_UPDATE_LIMIT));
    var result = {};
    keys.forEach(function keepThreadUpdate(key) {
      result[key] = normalized[key];
    });
    return result;
  }

  function getThreadUpdateActivityAt(entry, record) {
    return Math.max(
      Number(entry && entry.progressAt) || 0,
      Number(entry && entry.savedAt) || 0,
      Number(record && record.lastReadAt) || 0,
      Number(record && record.updatedAt) || 0
    );
  }

  function getThreadUpdateCheckInterval(entry, record, now) {
    var currentTime = Number(now) || Date.now();
    var activityAt = getThreadUpdateActivityAt(entry, record);
    if (record && record.hasNewReplies) return THREAD_UPDATE_HOT_TTL;
    if (!entry || !entry.read || activityAt >= currentTime - 7 * 24 * 60 * 60 * 1000) return THREAD_UPDATE_HOT_TTL;
    if (activityAt >= currentTime - 30 * 24 * 60 * 60 * 1000) return THREAD_UPDATE_WARM_TTL;
    return THREAD_UPDATE_COLD_TTL;
  }

  function shouldCheckThreadUpdate(entry, record, now, manual) {
    if (!entry || !entry.id) return false;
    if (manual) return true;
    var normalizedRecord = normalizeThreadUpdateRecord(record, entry.id);
    if (!normalizedRecord || !normalizedRecord.lastCheckedAt) return true;
    return normalizedRecord.lastCheckedAt + getThreadUpdateCheckInterval(entry, normalizedRecord, now) <= (Number(now) || Date.now());
  }

  function updateThreadReplyState(updateMap, entry, options, now) {
    var map = updateMap || {};
    var source = entry || {};
    var id = parseThreadId(source.id || source.url || source.progressUrl || '');
    if (!id) return { map: map, record: null, changed: false };
    var opts = options || {};
    var currentTime = now === undefined ? Date.now() : Number(now);
    if (!isFinite(currentTime) || currentTime <= 0) currentTime = Date.now();
    var previous = normalizeThreadUpdateRecord(map[id], id);
    var replyCount = normalizeThreadUpdateReplyCount(source.replies);
    if (replyCount === null) replyCount = parseThreadReplyCount(source.meta || source.rowText || '');
    if (replyCount === null && !opts.markRead && !previous) return { map: map, record: null, changed: false };

    var knownReplies = replyCount === null ? (previous ? previous.knownReplies : 0) : replyCount;
    if (previous && replyCount !== null) knownReplies = Math.max(previous.knownReplies, replyCount);
    var readReplies;
    if (opts.markRead) {
      readReplies = knownReplies;
    } else if (previous) {
      readReplies = Math.min(previous.readReplies, knownReplies);
    } else {
      readReplies = knownReplies;
    }

    var record = Object.assign({}, previous || {}, {
      id: id,
      title: compactText(source.title) || (previous && previous.title) || '',
      url: String(source.url || source.progressUrl || (previous && previous.url) || ''),
      knownReplies: knownReplies,
      readReplies: readReplies,
      unreadReplies: Math.max(0, knownReplies - readReplies),
      lastCheckedAt: opts.checked === false ? (previous && previous.lastCheckedAt || 0) : currentTime,
      updatedAt: currentTime,
      source: String(opts.source || source.source || (previous && previous.source) || ''),
    });
    record.hasNewReplies = record.unreadReplies > 0;
    if (opts.markRead) record.lastReadAt = currentTime;

    var before = previous ? JSON.stringify(previous) : '';
    var after = JSON.stringify(normalizeThreadUpdateRecord(record, id));
    map[id] = record;
    return { map: map, record: record, changed: before !== after };
  }

  function applyThreadUpdateEntries(updateMap, entries, options, now) {
    var map = updateMap || {};
    var changed = false;
    (entries || []).forEach(function applyThreadUpdateEntry(entry) {
      var result = updateThreadReplyState(map, entry, options, now);
      map = result.map;
      if (result.changed) changed = true;
    });
    var pruned = pruneThreadUpdates(map);
    if (Object.keys(pruned).length !== Object.keys(map).length) changed = true;
    return { map: pruned, changed: changed };
  }

  function getThreadUpdateStatusForEntry(entry, updates) {
    var id = parseThreadId(entry && (entry.id || entry.url || entry.progressUrl || ''));
    if (!id) return null;
    var record = normalizeThreadUpdateRecord(updates && updates[id], id);
    if (!record) return null;
    return record;
  }

  function buildThreadLatestReadUrl(threadId, url) {
    var id = parseThreadId(threadId || url || '');
    if (!id) return String(url || '');
    var baseUrl = String(url || (typeof location !== 'undefined' ? location.href : 'https://south-plus.org/'));
    try {
      var parsed = new URL(baseUrl, typeof location !== 'undefined' ? location.href : 'https://south-plus.org/');
      return parsed.origin + '/read.php?tid=' + encodeURIComponent(id) + '&page=e#a';
    } catch (error) {
      return '/read.php?tid=' + encodeURIComponent(id) + '&page=e#a';
    }
  }

  function getReadReplyPage(replyNumber) {
    var targetReply = normalizeThreadUpdateReplyCount(replyNumber);
    if (targetReply === null || targetReply <= 0) return 1;
    return Math.floor((targetReply - 1) / READ_REPLIES_PER_PAGE) + 1;
  }

  function buildThreadReplyPageUrl(threadId, url, replyNumber) {
    var id = parseThreadId(threadId || url || '');
    if (!id) return String(url || '');
    var targetPage = getReadReplyPage(replyNumber);
    var baseUrl = String(url || (typeof location !== 'undefined' ? location.href : 'https://south-plus.org/'));
    try {
      var parsed = new URL(baseUrl, typeof location !== 'undefined' ? location.href : 'https://south-plus.org/');
      return parsed.origin + '/read.php?tid-' + encodeURIComponent(id) + '-page-' + targetPage + '.html';
    } catch (error) {
      return '/read.php?tid-' + encodeURIComponent(id) + '-page-' + targetPage + '.html';
    }
  }

  function getFavoriteNavFirstUnreadReply(entry) {
    var readReplies = normalizeThreadUpdateReplyCount(entry && entry.readReplies);
    var knownReplies = normalizeThreadUpdateReplyCount(entry && entry.knownReplies);
    if (readReplies !== null && knownReplies !== null && knownReplies > readReplies) return readReplies + 1;
    return null;
  }

  function decorateFavoriteNavEntryWithUpdate(entry, updates) {
    var source = Object.assign({}, entry || {});
    var record = getThreadUpdateStatusForEntry(source, updates);
    if (!record) return source;
    source.knownReplies = record.knownReplies;
    source.readReplies = record.readReplies;
    source.unreadReplies = record.unreadReplies;
    source.hasNewReplies = record.hasNewReplies;
    source.updateCheckedAt = record.lastCheckedAt;
    source.updateText = record.hasNewReplies ? ('新 +' + record.unreadReplies) : '';
    source.latestUrl = buildThreadLatestReadUrl(source.id, source.url || record.url);
    return source;
  }

  function decorateFavoriteNavEntriesWithUpdates(entries, updates) {
    return (entries || []).map(function decorateFavoriteEntry(entry) {
      return decorateFavoriteNavEntryWithUpdate(entry, updates);
    });
  }

  function decorateFavoriteNavEntryWithProgress(entry, progressMap) {
    var source = Object.assign({}, entry || {});
    var id = parseThreadId(source.id || source.url || '');
    var record = id && progressMap ? progressMap[id] : null;
    if (!record || !record.updatedAt) return source;
    var floorLabels = getReadProgressFloorLabels(record);
    source.progressAt = Number(record.updatedAt) || source.progressAt || 0;
    source.progressUrl = String(record.url || source.url || '');
    source.progressText = formatReadProgress(record);
    source.floorLabel = floorLabels.last;
    source.nextFloorLabel = floorLabels.next;
    source.read = source.read || isCompletedReadProgress(record);
    source.tags = mergeTagLists(source.tags, record.tags);
    source.tagText = formatTags(source.tags);
    return source;
  }

  function decorateFavoriteNavEntriesWithProgress(entries, progressMap) {
    return (entries || []).map(function decorateFavoriteProgress(entry) {
      return decorateFavoriteNavEntryWithProgress(entry, progressMap);
    });
  }

  function getFavoriteNavUpdateSummary(entries, updates) {
    var seen = {};
    var count = 0;
    var unreadReplies = 0;
    var latestAt = 0;
    (entries || []).forEach(function countUpdatedFavoriteEntry(entry) {
      var record = getThreadUpdateStatusForEntry(entry, updates);
      if (!record || !record.hasNewReplies || seen[record.id]) return;
      seen[record.id] = true;
      count += 1;
      unreadReplies += Number(record.unreadReplies) || 0;
      latestAt = Math.max(latestAt, record.lastCheckedAt, record.updatedAt);
    });
    return { count: count, unreadReplies: unreadReplies, latestAt: latestAt };
  }

  function getFavoriteNavUnreadUrl(entry) {
    if (!entry) return '';
    var firstUnreadReply = getFavoriteNavFirstUnreadReply(entry);
    if (firstUnreadReply) return buildThreadReplyPageUrl(entry.id, entry.url || entry.progressUrl || entry.latestUrl || '', firstUnreadReply);
    if (entry.latestUrl) return entry.latestUrl;
    return buildThreadLatestReadUrl(entry.id, entry.url || entry.progressUrl || '');
  }

  function parseReadPageNumberFromNode(node) {
    if (!node) return 0;
    var href = node.getAttribute ? (node.getAttribute('href') || '') : '';
    var hrefMatch = href.match(/-page-(\d+)/) || href.match(/[?&]page=(\d+)/);
    if (hrefMatch) return Number(hrefMatch[1]) || 0;
    var text = compactText(node.textContent || '');
    return /^\d+$/.test(text) ? Number(text) || 0 : 0;
  }

  function getReadPageNumbers(root) {
    var scope = root || document;
    var numbers = [];
    qsa('.pages a,.pages b,.pages strong,.pages span', scope).forEach(function collectPageNumber(node) {
      var page = parseReadPageNumberFromNode(node);
      if (page) numbers.push(page);
    });
    return numbers;
  }

  function parseThreadReadReplyCountFromDocument(root, pageUrl) {
    var scope = root || document;
    var posts = qsa('table.js-post', scope).length;
    if (!posts) return null;
    var numbers = getReadPageNumbers(scope);
    var currentPage = currentPageNumber(pageUrl || (typeof location !== 'undefined' ? location.href : '')) || 1;
    qsa('.pages b,.pages strong,.pages .current', scope).forEach(function collectCurrentPage(node) {
      currentPage = Math.max(currentPage, parseReadPageNumberFromNode(node));
    });
    var maxPage = numbers.reduce(function maxPageNumber(max, page) { return Math.max(max, page); }, currentPage);
    var url = String(pageUrl || (typeof location !== 'undefined' ? location.href : ''));
    var lastPageUrl = /[?&]page=e(?:[&#]|$)/i.test(url);
    if (maxPage > 1 && currentPage < maxPage && !lastPageUrl) return null;
    var effectivePage = lastPageUrl ? maxPage : currentPage;
    var firstPageTopicPost = effectivePage <= 1 ? 1 : 0;
    return Math.max(0, (Math.max(1, effectivePage) - 1) * READ_REPLIES_PER_PAGE + posts - firstPageTopicPost);
  }

  function pruneReadProgress(progress, limit) {
    var source = progress || {};
    var max = Math.max(1, Number(limit) || READ_PROGRESS_LIMIT);
    var keys = Object.keys(source)
      .filter(function hasProgress(key) {
        return source[key] && source[key].updatedAt;
      })
      .sort(function sortByUpdatedAt(left, right) {
        return (Number(source[right].updatedAt) || 0) - (Number(source[left].updatedAt) || 0);
      })
      .slice(0, max);
    var result = {};
    keys.forEach(function keepProgress(key) {
      result[key] = source[key];
    });
    return result;
  }

  function getWatchCenterEntries(watch, progress) {
    var progressMap = progress || {};
    return Object.keys(watch || {})
      .filter(function hasWatchItem(key) {
        var item = watch[key];
        return item && (item.title || item.url);
      })
      .map(function toWatchEntry(key) {
        var item = watch[key] || {};
        var record = progressMap[key] || {};
        var floorLabels = getReadProgressFloorLabels(record);
        var tags = mergeTagLists(item.tags, record.tags);
        return {
          id: key,
          title: String(record.title || item.title || '未命名帖子'),
          url: String(item.url || record.url || ''),
          savedAt: Number(item.savedAt) || 0,
          progressAt: Number(record.updatedAt) || 0,
          progressUrl: String(record.url || item.url || ''),
          progressText: formatReadProgress(record),
          progressPercent: getReadProgressPercent(record),
          isCompleted: isCompletedReadProgress(record),
          floorLabel: floorLabels.last,
          nextFloorLabel: floorLabels.next,
          floorHash: String(record.floorHash || ''),
          nextFloorHash: String(record.nextFloorHash || record.floorHash || ''),
          tags: tags,
          tagText: formatTags(tags),
        };
      })
      .sort(function sortBySavedAt(left, right) {
        return (right.savedAt || 0) - (left.savedAt || 0);
      });
  }

  function getHistoryCenterEntries(progress) {
    return Object.keys(progress || {})
      .filter(function hasHistoryItem(key) {
        var item = progress[key];
        return item && (item.title || item.url) && item.updatedAt;
      })
      .map(function toHistoryEntry(key) {
        var item = progress[key] || {};
        var floorLabels = getReadProgressFloorLabels(item);
        var tags = parseTagList(item.tags && item.tags.join ? item.tags.join('\n') : item.tags);
        return {
          id: key,
          title: String(item.title || '未命名帖子'),
          url: String(item.url || ''),
          page: Number(item.page) || 1,
          scrollY: Math.max(0, Number(item.scrollY) || 0),
          progressAt: Number(item.updatedAt) || 0,
          progressText: formatReadProgress(item),
          progressPercent: getReadProgressPercent(item),
          isCompleted: isCompletedReadProgress(item),
          floorLabel: floorLabels.last,
          nextFloorLabel: floorLabels.next,
          floorHash: String(item.floorHash || ''),
          nextFloorHash: String(item.nextFloorHash || item.floorHash || ''),
          tags: tags,
          tagText: formatTags(tags),
        };
      })
      .sort(function sortByProgressAt(left, right) {
        return (right.progressAt || 0) - (left.progressAt || 0);
      });
  }

  function getAutoBuyStatusLabel(status) {
    var labels = {
      checking: '检查中',
      skipped: '已跳过',
      buying: '购买中',
      done: '已完成',
      failed: '失败',
    };
    return labels[status] || '未知';
  }

  function getAutoBuyCenterEntries(attempts) {
    return Object.keys(attempts || {})
      .filter(function hasAttempt(key) {
        return attempts[key] && attempts[key].status;
      })
      .map(function toAutoBuyEntry(key) {
        var item = attempts[key] || {};
        return {
          key: key,
          status: String(item.status || ''),
          statusLabel: getAutoBuyStatusLabel(item.status),
          message: String(item.message || ''),
          url: String(item.url || ''),
          price: item.price === undefined ? null : Number(item.price),
          balance: item.balance === undefined ? null : Number(item.balance),
          updatedAt: Number(item.updatedAt) || 0,
        };
      })
      .sort(function sortByUpdatedAt(left, right) {
        return (right.updatedAt || 0) - (left.updatedAt || 0);
      });
  }

  function getTaskClaimTaskKey(taskName) {
    var text = String(taskName || '').replace(/\s+/g, '').toLowerCase();
    if (/日常|daily/.test(text)) return 'daily';
    if (/周常|weekly/.test(text)) return 'weekly';
    return text || 'task';
  }

  function getTaskClaimTaskLabel(taskKey, taskName) {
    var key = String(taskKey || getTaskClaimTaskKey(taskName));
    if (key === 'daily') return '日常';
    if (key === 'weekly') return '周常';
    return String(taskName || key || '任务');
  }

  function parseTaskClaimTaskName(text) {
    var value = String(text || '').replace(/\s+/g, ' ').trim();
    if (/日常/.test(value)) return '日常';
    if (/周常/.test(value)) return '周常';
    var match = value.match(/^([^\s((]+)\s*(?:[((]|任务时效|奖励)/);
    return match ? match[1] : '';
  }

  function parseTaskClaimRewardSp(text) {
    var match = String(text || '').match(/奖励\s*[::]\s*SP币\s*([0-9]+(?:\.[0-9]+)?)/i);
    return match ? Number(match[1]) : null;
  }

  function parseTaskClaimCompletedAt(text) {
    var match = String(text || '').match(/完成时间\s*(\d{4})-(\d{1,2})-(\d{1,2})\s*(?:(AM|PM)\s*[::]?)?\s*(\d{1,2}):(\d{2})(?::(\d{2}))?/i);
    if (!match) return 0;
    var year = Number(match[1]);
    var month = Number(match[2]);
    var day = Number(match[3]);
    var meridiem = String(match[4] || '').toUpperCase();
    var hour = Number(match[5]);
    var minute = Number(match[6]);
    var second = Number(match[7] || 0);
    if (meridiem === 'PM' && hour < 12) hour += 12;
    if (meridiem === 'AM' && hour === 12) hour = 0;
    var date = new Date(year, month - 1, day, hour, minute, second);
    return isNaN(date.getTime()) ? 0 : date.getTime();
  }

  function createTaskClaimRecordKey(record) {
    var item = record || {};
    var key = getTaskClaimTaskKey(item.taskKey || item.taskName);
    var completedAt = Math.max(0, Number(item.completedAt) || 0);
    return key + '|' + completedAt;
  }

  function normalizeTaskClaimRecord(record, fallbackKey) {
    var source = record || {};
    var taskName = getTaskClaimTaskLabel(source.taskKey, source.taskName);
    var taskKey = getTaskClaimTaskKey(source.taskKey || taskName);
    var completedAt = Math.max(0, Number(source.completedAt) || 0);
    if (!completedAt) return null;
    var rewardSp = source.rewardSp === null || source.rewardSp === undefined ? null : Number(source.rewardSp);
    if (rewardSp !== null && !isFinite(rewardSp)) rewardSp = null;
    var normalized = {
      key: String(fallbackKey || source.key || ''),
      taskKey: taskKey,
      taskName: getTaskClaimTaskLabel(taskKey, taskName),
      rewardSp: rewardSp,
      completedAt: completedAt,
      recordedAt: Math.max(0, Number(source.recordedAt) || completedAt || Date.now()),
      source: String(source.source || 'local'),
      sourceUrl: String(source.sourceUrl || ''),
    };
    normalized.key = normalized.key || createTaskClaimRecordKey(normalized);
    return normalized;
  }

  function pruneTaskClaimRecords(records) {
    var source = records || {};
    var entries = Object.keys(source).map(function mapTaskClaimRecord(key) {
      return normalizeTaskClaimRecord(source[key], key);
    }).filter(Boolean).sort(function sortTaskClaims(left, right) {
      return (right.completedAt || 0) - (left.completedAt || 0) || (right.recordedAt || 0) - (left.recordedAt || 0);
    }).slice(0, TASK_CLAIM_RECORD_LIMIT);
    var result = {};
    entries.forEach(function keepTaskClaim(record) {
      result[record.key] = record;
    });
    return result;
  }

  function getTaskClaimCenterEntries(records) {
    return Object.keys(records || {}).map(function toTaskClaimEntry(key) {
      return normalizeTaskClaimRecord(records[key], key);
    }).filter(Boolean).sort(function sortTaskClaimEntries(left, right) {
      return (right.completedAt || 0) - (left.completedAt || 0) || (right.recordedAt || 0) - (left.recordedAt || 0);
    });
  }

  function filterTaskClaimCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = String((options && options.filter) || 'all');
    return (entries || []).filter(function matchTaskClaimEntry(entry) {
      if (!matchesCenterSearch(query, [entry.taskName, entry.taskKey, entry.rewardSp, entry.sourceUrl])) return false;
      if (filter === 'all') return true;
      return entry.taskKey === filter;
    });
  }

  function getTaskAutoClaimCooldownMs(taskKey) {
    var key = getTaskClaimTaskKey(taskKey);
    if (key === 'daily') return TASK_AUTO_CLAIM_DAILY_COOLDOWN;
    if (key === 'weekly') return TASK_AUTO_CLAIM_WEEKLY_COOLDOWN;
    return TASK_AUTO_CLAIM_PROBE_RETRY_TTL;
  }

  function getLatestTaskClaimCompletedAt(records, taskKey) {
    var key = getTaskClaimTaskKey(taskKey);
    return getTaskClaimCenterEntries(records).reduce(function latestTaskClaimCompletedAt(latest, record) {
      if (!record || record.taskKey !== key) return latest;
      return Math.max(latest, Number(record.completedAt) || 0);
    }, 0);
  }

  function isTaskClaimCompletedToday(records, taskKey, now) {
    return isTimestampToday(getLatestTaskClaimCompletedAt(records, taskKey), now);
  }

  function normalizeTaskAutoClaimState(state) {
    var source = isPlainObject(state) ? state : {};
    var tasks = isPlainObject(source.tasks) ? source.tasks : {};
    var normalizedTasks = {};
    ['daily', 'weekly'].forEach(function normalizeTaskAutoClaimTask(key) {
      var item = isPlainObject(tasks[key]) ? tasks[key] : {};
      normalizedTasks[key] = {
        nextCheckAt: Math.max(0, Number(item.nextCheckAt) || 0),
        updatedAt: Math.max(0, Number(item.updatedAt) || 0),
        reason: String(item.reason || ''),
      };
    });
    return {
      nextCheckAt: Math.max(0, Number(source.nextCheckAt) || 0),
      checkedAt: Math.max(0, Number(source.checkedAt) || 0),
      updatedAt: Math.max(0, Number(source.updatedAt) || 0),
      reason: String(source.reason || ''),
      tasks: normalizedTasks,
    };
  }

  function loadTaskAutoClaimState() {
    return normalizeTaskAutoClaimState(loadMap(TASK_AUTO_CLAIM_STATE_KEY));
  }

  function saveTaskAutoClaimState(state) {
    saveMap(TASK_AUTO_CLAIM_STATE_KEY, normalizeTaskAutoClaimState(state));
  }

  function getTaskAutoClaimNextCheckAtFromRecords(records, now) {
    var currentTime = now === undefined ? Date.now() : Number(now);
    var candidates = [];
    var hasUnknownTask = false;
    ['daily', 'weekly'].forEach(function collectTaskAutoClaimNextCheck(taskKey) {
      var completedAt = getLatestTaskClaimCompletedAt(records, taskKey);
      if (taskKey === 'daily' && isTaskClaimCompletedToday(records, 'daily', currentTime)) {
        candidates.push(getNextDayStart(currentTime));
      } else if (completedAt) {
        candidates.push(completedAt + getTaskAutoClaimCooldownMs(taskKey));
      } else {
        hasUnknownTask = true;
      }
    });
    if (hasUnknownTask) candidates.push(currentTime + TASK_AUTO_CLAIM_PROBE_RETRY_TTL);
    if (!candidates.length) return currentTime + TASK_AUTO_CLAIM_PROBE_RETRY_TTL;
    return Math.max(currentTime, Math.min.apply(Math, candidates));
  }

  function getTaskAutoClaimGate(records, state, now) {
    var currentTime = now === undefined ? Date.now() : Number(now);
    var normalizedState = normalizeTaskAutoClaimState(state);
    var globalNextCheckAt = Math.max(0, Number(normalizedState.nextCheckAt) || 0);
    var dailyCompletedToday = isTaskClaimCompletedToday(records, 'daily', currentTime);
    if (globalNextCheckAt > currentTime && (normalizedState.reason !== 'success' || dailyCompletedToday)) {
      return {
        canRun: false,
        nextCheckAt: globalNextCheckAt,
        dueTaskKeys: [],
        reason: normalizedState.reason || 'global-cooldown',
      };
    }

    var dueTaskKeys = [];
    var futureChecks = [];
    ['daily', 'weekly'].forEach(function collectTaskAutoClaimGate(taskKey) {
      var completedAt = getLatestTaskClaimCompletedAt(records, taskKey);
      var taskState = normalizedState.tasks[taskKey] || {};
      var stateNextCheckAt = Number(taskState.nextCheckAt) || 0;
      if (taskKey === 'daily') {
        if (dailyCompletedToday) {
          futureChecks.push(getNextDayStart(currentTime));
        } else if (stateNextCheckAt > currentTime) {
          futureChecks.push(stateNextCheckAt);
        } else {
          dueTaskKeys.push(taskKey);
        }
        return;
      }
      var recordNextCheckAt = completedAt ? completedAt + getTaskAutoClaimCooldownMs(taskKey) : 0;
      var taskNextCheckAt = Math.max(recordNextCheckAt, stateNextCheckAt);
      if (!taskNextCheckAt || taskNextCheckAt <= currentTime) {
        dueTaskKeys.push(taskKey);
      } else {
        futureChecks.push(taskNextCheckAt);
      }
    });

    if (dueTaskKeys.length) {
      return { canRun: true, nextCheckAt: 0, dueTaskKeys: dueTaskKeys, reason: 'due' };
    }
    return {
      canRun: false,
      nextCheckAt: futureChecks.length ? Math.min.apply(Math, futureChecks) : currentTime + TASK_AUTO_CLAIM_PROBE_RETRY_TTL,
      dueTaskKeys: [],
      reason: 'task-cooldown',
    };
  }

  function rememberTaskAutoClaimCheck(reason, nextCheckAt, now) {
    var currentTime = now === undefined ? Date.now() : Number(now);
    var state = loadTaskAutoClaimState();
    state.checkedAt = currentTime;
    state.updatedAt = currentTime;
    state.reason = String(reason || 'checked');
    state.nextCheckAt = Math.max(currentTime, Number(nextCheckAt) || (currentTime + TASK_AUTO_CLAIM_PROBE_RETRY_TTL));
    saveTaskAutoClaimState(state);
    return state;
  }

  function rememberTaskAutoClaimTaskCooldown(taskKey, reason, nextCheckAt, now) {
    var currentTime = now === undefined ? Date.now() : Number(now);
    var key = getTaskClaimTaskKey(taskKey);
    if (key !== 'daily' && key !== 'weekly') return loadTaskAutoClaimState();
    var state = loadTaskAutoClaimState();
    state.checkedAt = currentTime;
    state.updatedAt = currentTime;
    state.reason = String(reason || 'task-cooldown');
    state.tasks[key] = {
      nextCheckAt: Math.max(currentTime, Number(nextCheckAt) || (currentTime + getTaskAutoClaimCooldownMs(key))),
      updatedAt: currentTime,
      reason: state.reason,
    };
    saveTaskAutoClaimState(state);
    return state;
  }

  function getTaskAutoClaimBlockedRetryAt(results, now) {
    var currentTime = now === undefined ? Date.now() : Number(now);
    var candidates = [];
    (results || []).filter(isTaskAutoClaimResultBlocked).forEach(function collectBlockedTaskAutoClaimRetry(result) {
      var taskKey = result && result.target && result.target.taskKey;
      if (taskKey === 'daily' || taskKey === 'weekly') {
        candidates.push(currentTime + getTaskAutoClaimCooldownMs(taskKey));
      }
    });
    return candidates.length ? Math.min.apply(Math, candidates) : currentTime + TASK_AUTO_CLAIM_ERROR_RETRY_TTL;
  }

  function formatTaskAutoClaimNextCheck(nextCheckAt, now) {
    var currentTime = now === undefined ? Date.now() : Number(now);
    var remainingMs = Math.max(0, Number(nextCheckAt) - currentTime);
    if (!remainingMs) return '现在';
    var hours = Math.ceil(remainingMs / (60 * 60 * 1000));
    if (hours >= 24) return Math.ceil(hours / 24) + ' 天后';
    return hours + ' 小时后';
  }

  function isTaskInProgressPageUrl(url) {
    return /(?:actions[-=]newtasks|actions=newtasks|newtasks\.html)/i.test(String(url || ''));
  }

  function hasTaskAutoClaimStartLabel(text) {
    return /申请|开始|接受|领取任务/.test(String(text || '').replace(/\s+/g, ' '));
  }

  function hasTaskAutoClaimFinishLabel(text) {
    return /领取|领奖|奖励|完成/.test(String(text || '').replace(/\s+/g, ' '));
  }

  function hasTaskAutoClaimFinishContext(text) {
    var value = String(text || '').replace(/\s+/g, ' ');
    return /领取(?:此)?奖励|领奖|已完成\s*100\s*[%%]|完成\s*100\s*[%%]/.test(value);
  }

  function getTaskAutoClaimStartJobAction(actionType, pageUrl, contextText) {
    var type = String(actionType || '');
    if (type === 'finish') return 'job2';
    if (type === 'start') return 'job';
    if (isTaskInProgressPageUrl(pageUrl) || hasTaskAutoClaimFinishContext(contextText)) return 'job2';
    return 'job';
  }

  function extractTaskAutoClaimUrl(value, pageUrl, actionType, contextText) {
    var text = String(value || '').replace(/&amp;/g, '&').trim();
    if (!text) return '';
    var startJobMatch = text.match(/\bstartjob\s*\(\s*['"]?(\d+)['"]?\s*\)/i);
    if (startJobMatch) {
      var jobAction = getTaskAutoClaimStartJobAction(actionType, pageUrl, contextText);
      try {
        return new URL('plugin.php?H_name=tasks&action=ajax&actions=' + jobAction + '&cid=' + encodeURIComponent(startJobMatch[1]), pageUrl || (typeof location !== 'undefined' ? location.href : 'https://south-plus.org/')).href;
      } catch (error) {
        return '';
      }
    }
    var quoted = text.match(/['"]([^'"]*(?:plugin|hack|job)\.php\?[^'"]*(?:H_name[-=]?tasks|H_name-tasks|tasks)[^'"]*)['"]/i);
    var direct = quoted ? quoted[1] : '';
    if (!direct) {
      var directMatch = text.match(/((?:https?:\/\/[^\s'"<>]+|\/[^\s'"<>]+|(?:plugin|hack|job)\.php\?[^\s'"<>]+)(?:H_name[-=]?tasks|H_name-tasks|tasks)[^\s'"<>]*)/i);
      direct = directMatch ? directMatch[1] : '';
    }
    if (!direct) return '';
    try {
      return new URL(direct, pageUrl || (typeof location !== 'undefined' ? location.href : 'https://south-plus.org/')).href;
    } catch (error) {
      return '';
    }
  }

  function isTaskAutoClaimNavigationText(text) {
    return /^(?:社区论坛任务|新任务选择|进行中任务|已完成任务|已失败任务)$/i.test(String(text || '').replace(/\s+/g, ' ').trim());
  }

  function getTaskAutoClaimActionType(text, href, contextText) {
    var label = String(text || '').replace(/\s+/g, ' ');
    if (hasTaskAutoClaimStartLabel(label)) return 'start';
    if (hasTaskAutoClaimFinishLabel(label)) return 'finish';
    var value = [label, href, contextText].map(function normalizeTaskAutoClaimPart(part) {
      return String(part || '').replace(/\s+/g, ' ');
    }).join(' ');
    if (/[?&]actions=job2(?:[&#]|$)|(?:^|[-_=])job2(?:\b|[-_])/i.test(value)) return 'finish';
    if (/(?:job|action)[-_=]?(?:receive|reward|draw|finish|done|get)/i.test(value)) return 'finish';
    if (/(?:job|action)[-_=]?(?:apply|accept|start|add)/i.test(value)) return 'start';
    if (/[?&]actions=job(?:[&#]|$)/i.test(value)) return 'start';
    if (hasTaskAutoClaimFinishContext(value)) return 'finish';
    if (hasTaskAutoClaimStartLabel(value)) return 'start';
    return '';
  }

  function isTaskAutoClaimCandidate(text, href, contextText) {
    var label = String(text || '').replace(/\s+/g, ' ').trim();
    var scopeText = [label, contextText].join(' ');
    if (isTaskAutoClaimNavigationText(label)) return false;
    if (!/(日常|周常|daily|weekly)/i.test(scopeText)) return false;
    return !!getTaskAutoClaimActionType(label, href, contextText);
  }

  function createTaskClaimRecord(taskName, rewardSp, completedAt, sourceUrl, source, recordedAt) {
    var record = normalizeTaskClaimRecord({
      taskName: taskName,
      rewardSp: rewardSp,
      completedAt: completedAt,
      recordedAt: recordedAt || Date.now(),
      source: source || 'site-completed',
      sourceUrl: sourceUrl || '',
    });
    if (!record) return null;
    record.key = createTaskClaimRecordKey(record);
    return record;
  }

  function parseTaskClaimRecordsFromRows(rowTexts, options) {
    var opts = options || {};
    var current = null;
    var records = [];
    (rowTexts || []).forEach(function parseTaskClaimRow(rowText) {
      var text = String(rowText || '').replace(/\s+/g, ' ').trim();
      if (!text) return;
      var rowTaskName = parseTaskClaimTaskName(text);
      var rowRewardSp = parseTaskClaimRewardSp(text);
      if ((/任务时效|奖励\s*[::]/.test(text)) && rowTaskName) {
        current = { taskName: rowTaskName, rewardSp: rowRewardSp };
      }
      var completedAt = parseTaskClaimCompletedAt(text);
      if (!completedAt) return;
      var taskName = rowTaskName || (current && current.taskName) || '';
      if (!taskName) return;
      var rewardSp = rowRewardSp !== null ? rowRewardSp : (current && current.rewardSp !== undefined ? current.rewardSp : null);
      var record = createTaskClaimRecord(taskName, rewardSp, completedAt, opts.sourceUrl, opts.source, opts.recordedAt);
      if (record) records.push(record);
    });
    return getTaskClaimCenterEntries(pruneTaskClaimRecords(records.reduce(function collectTaskClaim(result, record) {
      result[record.key] = record;
      return result;
    }, {})));
  }

  function parseTaskClaimRecordsFromText(text, options) {
    var sourceText = String(text || '').replace(/\s+/g, ' ').trim();
    if (!sourceText) return [];
    var records = [];
    var pattern = /(周常|日常)[\s\S]*?奖励\s*[::]\s*SP币\s*([0-9]+(?:\.[0-9]+)?)[\s\S]*?完成时间\s*(\d{4}-\d{1,2}-\d{1,2}\s*(?:AM|PM)?\s*[::]?\s*\d{1,2}:\d{2}(?::\d{2})?)/gi;
    var match;
    var opts = options || {};
    while ((match = pattern.exec(sourceText))) {
      var completedAt = parseTaskClaimCompletedAt('完成时间 ' + match[3]);
      var record = createTaskClaimRecord(match[1], Number(match[2]), completedAt, opts.sourceUrl, opts.source, opts.recordedAt);
      if (record) records.push(record);
    }
    return getTaskClaimCenterEntries(pruneTaskClaimRecords(records.reduce(function collectTaskClaim(result, record) {
      result[record.key] = record;
      return result;
    }, {})));
  }

  function getDayStart(timestamp) {
    var date = new Date(Number(timestamp) || Date.now());
    return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
  }

  function getNextDayStart(timestamp) {
    return getDayStart(timestamp) + 24 * 60 * 60 * 1000;
  }

  function isTimestampToday(timestamp, now) {
    var value = Number(timestamp) || 0;
    var currentTime = now || Date.now();
    return value >= getDayStart(currentTime) && value < getNextDayStart(currentTime);
  }

  function getForumDashboardTopicKey(item) {
    var source = item || {};
    var id = parseThreadId(source.id || source.url || source.progressUrl || source.sourceUrl || '');
    if (id) return 'tid:' + id;
    var baseUrl = typeof location !== 'undefined' ? location.href : 'https://south-plus.org/';
    var url = normalizeNavigationHref(source.url || source.progressUrl || source.sourceUrl || '', baseUrl);
    if (url) return 'url:' + url.replace(/#.*$/, '').toLowerCase();
    var title = compactText(source.title || source.sourceTitle || '');
    return title ? 'title:' + title.toLowerCase() : '';
  }

  function getForumDashboardTopicTags(item) {
    var tags = mergeTagLists(item && item.tags, item && item.resourceTags);
    var text = [item && item.title, item && item.meta, item && item.sourceText].filter(Boolean).join(' ');
    inferFavoriteNavTags(text, '').forEach(function appendInferredTag(tag) {
      if (tags.indexOf(tag) === -1) tags.push(tag);
    });
    return tags;
  }

  function mergeForumDashboardTopic(target, source) {
    var next = source || {};
    if (!target.title && next.title) target.title = next.title;
    if (!target.url && next.url) target.url = next.url;
    if (!target.author && next.author) target.author = next.author;
    if (!target.meta && next.meta) target.meta = next.meta;
    target.savedAt = Math.max(Number(target.savedAt) || 0, Number(next.savedAt) || 0);
    target.progressAt = Math.max(Number(target.progressAt) || 0, Number(next.progressAt) || 0);
    target.favoriteAt = Math.max(Number(target.favoriteAt) || 0, Number(next.favoriteAt) || 0);
    target.resourceAt = Math.max(Number(target.resourceAt) || 0, Number(next.resourceAt) || 0);
    target.progressPercent = Math.max(Number(target.progressPercent) || 0, Number(next.progressPercent) || 0);
    target.isCompleted = !!(target.isCompleted || next.isCompleted);
    target.hasWatch = !!(target.hasWatch || next.hasWatch);
    target.hasHistory = !!(target.hasHistory || next.hasHistory);
    target.hasSiteFavorite = !!(target.hasSiteFavorite || next.hasSiteFavorite);
    target.resourceCount = (Number(target.resourceCount) || 0) + (Number(next.resourceCount) || 0);
    target.tags = mergeTagLists(target.tags, getForumDashboardTopicTags(next));
    target.lastAt = Math.max(target.savedAt || 0, target.progressAt || 0, target.favoriteAt || 0, target.resourceAt || 0);
    return target;
  }

  function addForumDashboardTopic(map, item) {
    var key = getForumDashboardTopicKey(item);
    if (!key) return null;
    if (!map[key]) {
      map[key] = {
        key: key,
        id: parseThreadId(item && (item.id || item.url || item.progressUrl || item.sourceUrl)),
        title: '',
        url: '',
        author: '',
        meta: '',
        savedAt: 0,
        progressAt: 0,
        favoriteAt: 0,
        resourceAt: 0,
        progressPercent: 0,
        isCompleted: false,
        hasWatch: false,
        hasHistory: false,
        hasSiteFavorite: false,
        resourceCount: 0,
        tags: [],
        lastAt: 0,
      };
    }
    return mergeForumDashboardTopic(map[key], item);
  }

  function getForumDashboardTopicScore(item, now) {
    var topic = item || {};
    var currentTime = Number(now) || Date.now();
    var score = 0;
    if (topic.hasSiteFavorite && !topic.isCompleted) score += 30;
    if (topic.hasWatch && !topic.isCompleted) score += 25;
    if (topic.progressAt >= currentTime - 7 * 24 * 60 * 60 * 1000) score += 18;
    if (topic.progressPercent > 0 && topic.progressPercent < 100) score += 16;
    if (topic.resourceCount > 0) score += 15 + Math.min(10, topic.resourceCount * 2);
    if (isTimestampToday(topic.savedAt, currentTime) || isTimestampToday(topic.favoriteAt, currentTime)) score += 12;
    if (topic.tags && topic.tags.length) score += Math.min(12, topic.tags.length * 3);
    if (topic.isCompleted) score -= 18;
    if (topic.lastAt && topic.lastAt < currentTime - 30 * 24 * 60 * 60 * 1000) score -= 10;
    return score;
  }

  function formatForumDashboardRelativeTime(timestamp, now) {
    var value = Number(timestamp) || 0;
    if (!value) return '';
    var diff = Math.max(0, (Number(now) || Date.now()) - value);
    if (diff < 60 * 1000) return '刚刚';
    if (diff < 60 * 60 * 1000) return Math.max(1, Math.round(diff / 60 / 1000)) + ' 分钟前';
    if (diff < 24 * 60 * 60 * 1000) return Math.max(1, Math.round(diff / 60 / 60 / 1000)) + ' 小时前';
    if (diff < 30 * 24 * 60 * 60 * 1000) return Math.max(1, Math.round(diff / 24 / 60 / 60 / 1000)) + ' 天前';
    return formatShortTime(value);
  }

  function getForumDashboardRequestSummary(requestState, now) {
    var source = requestState || scriptRequestState || {};
    var queueCount = Array.isArray(source.queue) ? source.queue.length : Math.max(0, Number(source.queueCount) || 0);
    var cooldownRemainingMs = Math.max(0, Number(source.cooldownUntil || 0) - (Number(now) || Date.now()));
    return {
      queueCount: queueCount,
      cooldownRemainingMs: cooldownRemainingMs,
      status: cooldownRemainingMs > 0 ? '冷却中' : (queueCount > 0 || source.running ? '排队中' : '正常'),
      detail: '队列 ' + queueCount + ' / 冷却 ' + Math.ceil(cooldownRemainingMs / 1000) + ' 秒',
    };
  }

  function getForumDashboardActivityItems(report, now) {
    var items = [];
    (report.worthReviewing || []).slice(0, 2).forEach(function appendWorthActivity(topic) {
      items.push({
        type: topic.resourceCount ? 'resource' : 'progress',
        title: (topic.progressPercent ? '继续阅读 ' : '回看 ') + topic.title,
        meta: topic.meta || topic.reason || '',
        time: formatForumDashboardRelativeTime(topic.lastAt, now),
      });
    });
    (report.resources || []).slice(0, 2).forEach(function appendResourceActivity(entry) {
      items.push({
        type: 'resource',
        title: '资源待处理:' + (entry.sourceTitle || entry.label || '未命名资源'),
        meta: [entry.label, entry.provider, entry.statusLabel].filter(Boolean).join(' · '),
        time: formatForumDashboardRelativeTime(entry.updatedAt || entry.savedAt, now),
      });
    });
    if (report.request.status !== '正常') {
      items.push({ type: 'request', title: '后台请求' + report.request.status, meta: report.request.detail, time: '现在' });
    }
    return items.slice(0, 4);
  }

  function getForumDashboardTagStats(topics, resources) {
    var counts = {};
    (topics || []).forEach(function collectTopicTags(topic) {
      (topic.tags || []).forEach(function countTag(tag) {
        counts[tag] = (counts[tag] || 0) + 1;
      });
    });
    (resources || []).forEach(function collectResourceTag(entry) {
      var label = entry.type === 'cloud' ? '网盘' : entry.label;
      if (label) counts[label] = (counts[label] || 0) + 1;
    });
    return Object.keys(counts).map(function mapTagStat(tag) {
      return { label: tag, count: counts[tag] };
    }).sort(function sortTagStats(left, right) {
      return right.count - left.count || left.label.localeCompare(right.label, 'zh-Hans-CN');
    }).slice(0, 4);
  }

  function collectForumDashboardReport(data, now) {
    var source = data || {};
    var currentTime = Number(now) || Date.now();
    var origin = typeof location !== 'undefined' && location.origin ? location.origin : 'https://south-plus.org';
    var todayStart = getDayStart(currentTime);
    var readMap = source.read || {};
    var progressMap = source.progress || {};
    var watchMap = source.watch || {};
    var favoriteSeen = normalizeFavoriteNavSeenMap(source.favoriteSeen || {});
    var resources = getResourceCenterEntries(source.resources || {});
    var topics = {};

    getWatchCenterEntries(watchMap, progressMap).forEach(function collectWatchTopic(entry) {
      addForumDashboardTopic(topics, Object.assign({}, entry, {
        hasWatch: true,
        url: entry.progressUrl || entry.url,
        meta: [entry.progressText, entry.nextFloorLabel ? '续读 ' + entry.nextFloorLabel : '', entry.tagText].filter(Boolean).join(' · '),
      }));
    });

    getHistoryCenterEntries(progressMap).forEach(function collectHistoryTopic(entry) {
      addForumDashboardTopic(topics, Object.assign({}, entry, {
        hasHistory: true,
        url: entry.url,
        meta: [entry.progressText, entry.nextFloorLabel ? '续读 ' + entry.nextFloorLabel : '', entry.tagText].filter(Boolean).join(' · '),
      }));
    });

    resources.forEach(function collectResourceTopic(entry) {
      addForumDashboardTopic(topics, {
        id: parseThreadId(entry.sourceUrl),
        title: entry.sourceTitle || entry.sourceUrl || entry.label,
        url: entry.sourceUrl || entry.url,
        author: entry.author,
        sourceText: entry.sourceText,
        resourceTags: entry.tags,
        resourceCount: 1,
        resourceAt: entry.updatedAt || entry.savedAt,
        meta: [entry.label, entry.provider, entry.statusLabel].filter(Boolean).join(' · '),
      });
    });

    Object.keys(favoriteSeen).forEach(function markFavoriteTopic(id) {
      addForumDashboardTopic(topics, {
        id: id,
        hasSiteFavorite: true,
        favoriteAt: favoriteSeen[id],
      });
    });

    var topicList = Object.keys(topics).map(function finalizeTopic(key) {
      var topic = topics[key];
      topic.title = topic.title || (topic.id ? '收藏主题 #' + topic.id : '未命名主题');
      topic.url = topic.url || (topic.id ? origin + '/read.php?tid-' + topic.id + '.html' : '');
      topic.score = getForumDashboardTopicScore(topic, currentTime);
      var reasons = [];
      if (topic.hasSiteFavorite) reasons.push('站内收藏');
      if (topic.hasWatch) reasons.push('稍后看');
      if (topic.resourceCount) reasons.push('资源 ' + topic.resourceCount);
      if (topic.progressPercent) reasons.push('进度 ' + topic.progressPercent + '%');
      if (topic.tags && topic.tags.length) reasons.push('标签 ' + formatTags(topic.tags.slice(0, 3)));
      topic.reason = reasons.join(' · ') || '本地记录';
      return topic;
    }).filter(function keepScoredTopic(topic) {
      return topic.score > 0 && topic.title;
    }).sort(function sortDashboardTopics(left, right) {
      return right.score - left.score || right.lastAt - left.lastAt || String(left.title).localeCompare(String(right.title), 'zh-Hans-CN');
    });

    var todayViewed = {};
    Object.keys(readMap).forEach(function collectReadToday(id) {
      if (Number(readMap[id]) >= todayStart) todayViewed[id] = true;
    });
    Object.keys(progressMap).forEach(function collectProgressToday(id) {
      if (Number(progressMap[id] && progressMap[id].updatedAt) >= todayStart) todayViewed[id] = true;
    });

    var watchEntries = getWatchCenterEntries(watchMap, progressMap);
    var siteFavoriteToday = Object.keys(favoriteSeen).filter(function countFavoriteToday(id) {
      return Number(favoriteSeen[id]) >= todayStart;
    }).length;
    var watchToday = watchEntries.filter(function countWatchToday(entry) {
      return Number(entry.savedAt) >= todayStart;
    }).length;
    var unreadFavorites = topicList.filter(function countUnreadFavorite(topic) {
      return (topic.hasSiteFavorite || topic.hasWatch) && !topic.isCompleted;
    }).length;
    var watchBacklog = watchEntries.filter(function countWatchBacklog(entry) {
      return !entry.isCompleted;
    }).length;
    var resourceToday = resources.filter(function countResourceToday(entry) {
      return Math.max(Number(entry.updatedAt) || 0, Number(entry.savedAt) || 0) >= todayStart;
    }).length;
    var request = getForumDashboardRequestSummary(source.requestState, currentTime);
    var tagStats = getForumDashboardTagStats(topicList, resources);
    var report = {
      stats: {
        todayViewed: Object.keys(todayViewed).length,
        favoriteAdded: siteFavoriteToday + watchToday,
        unreadFavorites: unreadFavorites,
        resourceAdded: resourceToday,
        watchBacklog: watchBacklog,
        requestStatus: request.status,
      },
      request: request,
      worthReviewing: topicList.slice(0, 5),
      resources: resources.slice(0, 4),
      tagStats: tagStats,
      backlogStats: [
        { label: '稍后看', count: watchBacklog },
        { label: '未读收藏', count: unreadFavorites },
        { label: '待处理资源', count: resources.filter(function countTodoResource(entry) { return entry.status === 'todo' || entry.status === 'saved'; }).length },
        { label: '失败请求', count: getAutoBuyCenterEntries(source.autoBuyAttempts || {}).filter(function countFailedAttempt(entry) { return entry.status === 'failed'; }).length },
      ],
    };
    report.activities = getForumDashboardActivityItems(report, currentTime);
    return report;
  }

  function normalizeCenterSearchQuery(value) {
    return String(value || '')
      .replace(/\s+/g, ' ')
      .trim()
      .toLowerCase();
  }

  function matchesCenterSearch(query, values) {
    if (!query) return true;
    return (values || []).some(function hasSearchValue(value) {
      return String(value || '').toLowerCase().indexOf(query) !== -1;
    });
  }

  function filterWatchCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = String((options && options.filter) || 'all');
    var tag = String((options && options.tag) || 'all').toLowerCase();
    return (entries || []).filter(function matchWatchEntry(entry) {
      if (!matchesCenterSearch(query, [entry.title, entry.progressText, entry.floorLabel, entry.nextFloorLabel, entry.tagText])) return false;
      if (tag !== 'all' && parseTagList(entry.tags).map(function lower(item) { return item.toLowerCase(); }).indexOf(tag) === -1) return false;
      if (filter === 'todo') return !entry.isCompleted;
      if (filter === 'progress') return !!entry.progressAt;
      if (filter === 'done') return !!entry.isCompleted;
      return true;
    });
  }

  function filterHistoryCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = String((options && options.filter) || 'all');
    var tag = String((options && options.tag) || 'all').toLowerCase();
    return (entries || []).filter(function matchHistoryEntry(entry) {
      if (!matchesCenterSearch(query, [entry.title, entry.progressText, entry.floorLabel, entry.nextFloorLabel, entry.tagText])) return false;
      if (tag !== 'all' && parseTagList(entry.tags).map(function lower(item) { return item.toLowerCase(); }).indexOf(tag) === -1) return false;
      if (filter === 'todo') return !entry.isCompleted;
      if (filter === 'done') return !!entry.isCompleted;
      return true;
    });
  }

  function getCenterTagOptions(entries) {
    var tags = [];
    (entries || []).forEach(function collectEntryTags(entry) {
      tags = mergeTagLists(tags, entry && entry.tags);
    });
    return tags
      .sort(function sortTags(left, right) {
        return left.localeCompare(right, 'zh-Hans-CN');
      })
      .map(function toOption(tag) {
        return { value: tag, label: tag };
      });
  }

  function filterAutoBuyCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = String((options && options.filter) || 'all');
    return (entries || []).filter(function matchAutoBuyEntry(entry) {
      if (!matchesCenterSearch(query, [entry.key, entry.statusLabel, entry.message, entry.url, entry.price, entry.balance])) return false;
      if (filter === 'all') return true;
      return entry.status === filter;
    });
  }

  function normalizeCommandPaletteFilter(value) {
    var text = String(value || 'all');
    return /^(all|navigate|center|resource|setting|action)$/.test(text) ? text : 'all';
  }

  function getCommandPaletteCategoryLabel(value) {
    var labels = {
      navigate: '导航',
      center: '我的中心',
      resource: '资源',
      setting: '设置',
      action: '页面动作',
    };
    return labels[normalizeCommandPaletteFilter(value)] || '全部';
  }

  function getCommandPaletteSourceLabel(entry) {
    var item = entry || {};
    return item.source || getCommandPaletteCategoryLabel(item.category);
  }

  function createCommandPaletteEntry(config) {
    var source = config || {};
    var title = compactText(source.title || source.label || '未命名命令');
    if (!title) return null;
    var category = normalizeCommandPaletteFilter(source.category);
    var id = String(source.id || [category, title, source.href || source.key || source.panelId || source.recordId || ''].join('|'));
    return {
      id: id,
      category: category,
      icon: compactText(source.icon || title.slice(0, 1)).slice(0, 2),
      title: title,
      description: compactText(source.description || source.meta || getCommandPaletteCategoryLabel(category)),
      meta: compactText(source.meta || ''),
      source: compactText(source.source || getCommandPaletteCategoryLabel(category)),
      target: compactText(source.target || ''),
      risk: compactText(source.risk || '低'),
      href: String(source.href || ''),
      action: String(source.action || ''),
      key: String(source.key || ''),
      panelId: String(source.panelId || ''),
      recordId: String(source.recordId || ''),
      resourceKey: String(source.resourceKey || ''),
      keywords: compactText(source.keywords || ''),
      order: Number(source.order) || 0,
      payload: source.payload || null,
    };
  }

  function getCommandPaletteEntrySearchText(entry) {
    var item = entry || {};
    return compactText([
      item.title,
      item.description,
      item.meta,
      item.source,
      item.target,
      item.href,
      item.keywords,
      getCommandPaletteCategoryLabel(item.category),
    ].join(' ')).toLowerCase();
  }

  function getCommandPaletteConfigCategory(config) {
    var item = config || {};
    if (item.panelId) return 'center';
    if (item.key || item.kind === 'settings') return 'setting';
    if (item.group === '页面导航' || item.href) return 'navigate';
    return 'action';
  }

  function getCommandPaletteConfigAction(config) {
    var item = config || {};
    if (item.href) return 'open-url';
    if (item.panelId) return 'open-panel';
    if (item.kind === 'settings') return 'open-settings';
    if (item.key) return 'toggle-setting';
    if (typeof item.onClick === 'function') return 'run-action';
    return '';
  }

  function createCommandPaletteEntryFromToolboxConfig(config, index) {
    if (!config || config.show === false) return null;
    var category = getCommandPaletteConfigCategory(config);
    return createCommandPaletteEntry({
      id: 'toolbox|' + (config.key || config.panelId || config.kind || config.href || config.label || index),
      category: category,
      icon: config.text || config.label,
      title: config.label || config.title || config.text,
      description: config.description || config.title || '',
      source: config.group || getCommandPaletteCategoryLabel(category),
      target: config.href || config.panelId || config.key || '',
      risk: /清空|删除|购买/.test(config.label || config.title || '') ? '中' : '低',
      href: config.href,
      action: getCommandPaletteConfigAction(config),
      key: config.key,
      panelId: config.panelId,
      keywords: [config.group, config.title, config.description].join(' '),
      order: 100 + index,
      payload: config,
    });
  }

  function createCommandPaletteNavigationEntries(configs) {
    var seen = {};
    return (configs || []).map(function mapNavigationCommand(config, index) {
      if (!config || !config.href || !config.label) return null;
      var key = normalizeNavigationHref(config.href, typeof location !== 'undefined' ? location.href : 'https://south-plus.org/').replace(/#.*$/, '') + '|' + normalizeNavigationLabel(config.label);
      if (seen[key]) return null;
      seen[key] = true;
      return createCommandPaletteEntry({
        id: 'nav|' + key,
        category: 'navigate',
        icon: '导',
        title: config.label,
        description: [config.section || '导航中心', config.parentLabel, config.title && config.title !== config.label ? config.title : ''].filter(Boolean).join(' · '),
        source: config.section || '导航中心',
        target: config.href,
        href: config.href,
        action: 'open-url',
        keywords: [config.parentLabel, config.title].join(' '),
        order: 300 + index,
      });
    }).filter(Boolean);
  }

  function createCommandPaletteWatchEntries(watch, progress) {
    return getWatchCenterEntries(watch, progress).slice(0, 40).map(function mapWatchCommand(entry, index) {
      return createCommandPaletteEntry({
        id: 'watch|' + entry.id,
        category: 'center',
        icon: '存',
        title: entry.title,
        description: ['稍后看', entry.progressText, entry.nextFloorLabel, entry.tagText].filter(Boolean).join(' · '),
        source: '稍后看',
        target: entry.progressUrl || entry.url,
        href: entry.progressUrl || entry.url,
        action: 'open-watch-entry',
        recordId: entry.id,
        keywords: [entry.floorLabel, entry.nextFloorLabel, entry.tagText].join(' '),
        order: 500 + index,
      });
    });
  }

  function createCommandPaletteHistoryEntries(progress) {
    return getHistoryCenterEntries(progress).slice(0, 40).map(function mapHistoryCommand(entry, index) {
      return createCommandPaletteEntry({
        id: 'history|' + entry.id,
        category: 'center',
        icon: '历',
        title: entry.title,
        description: ['最近浏览', entry.progressText, entry.nextFloorLabel, entry.tagText].filter(Boolean).join(' · '),
        source: '最近浏览',
        target: entry.url,
        href: entry.url,
        action: 'open-history-entry',
        recordId: entry.id,
        keywords: [entry.floorLabel, entry.nextFloorLabel, entry.tagText].join(' '),
        order: 560 + index,
      });
    });
  }

  function createCommandPaletteResourceEntries(resources) {
    return getResourceCenterEntries(resources).slice(0, 60).map(function mapResourceCommand(entry, index) {
      return createCommandPaletteEntry({
        id: 'resource|' + entry.key,
        category: 'resource',
        icon: '源',
        title: entry.sourceTitle || entry.label || entry.url,
        description: [entry.label, entry.provider, entry.statusLabel, entry.accessCode ? ('提取码 ' + entry.accessCode) : '', entry.tagText].filter(Boolean).join(' · '),
        source: '资源工作台',
        target: entry.url,
        href: entry.url,
        action: 'open-resource-url',
        resourceKey: entry.key,
        keywords: [entry.url, entry.sourceUrl, entry.floorLabel, entry.author, entry.note, entry.text, entry.tagText].join(' '),
        order: 620 + index,
      });
    });
  }

  function createCommandPalettePageEntries(items) {
    var seen = {};
    return (items || []).map(function mapPageCommand(item, index) {
      if (!item || !item.title || !item.href) return null;
      var href = String(item.href || '');
      var key = href.replace(/#.*$/, '') + '|' + item.title;
      if (seen[key]) return null;
      seen[key] = true;
      return createCommandPaletteEntry({
        id: 'page|' + key,
        category: item.category || 'navigate',
        icon: item.icon || '页',
        title: item.title,
        description: item.description || '当前页面结果',
        source: item.source || '当前页面',
        target: href,
        href: href,
        action: 'open-url',
        keywords: item.keywords || '',
        order: 700 + index,
      });
    }).filter(Boolean);
  }

  function collectCommandPaletteEntries(data) {
    var source = data || {};
    var entries = [];
    (source.toolboxConfigs || []).forEach(function appendToolboxCommand(config, index) {
      var entry = createCommandPaletteEntryFromToolboxConfig(config, index);
      if (entry) entries.push(entry);
    });
    (source.centerConfigs || []).forEach(function appendCenterCommand(config, index) {
      var entry = createCommandPaletteEntryFromToolboxConfig(config, 200 + index);
      if (entry) entries.push(entry);
    });
    (source.settingsConfigs || []).forEach(function appendSettingsCommand(config, index) {
      var entry = createCommandPaletteEntryFromToolboxConfig(config, 300 + index);
      if (entry) entries.push(entry);
    });
    entries = entries
      .concat(createCommandPaletteNavigationEntries(source.navigationConfigs || []))
      .concat(createCommandPaletteWatchEntries(source.watch || {}, source.progress || {}))
      .concat(createCommandPaletteHistoryEntries(source.progress || {}))
      .concat(createCommandPaletteResourceEntries(source.resources || {}))
      .concat(createCommandPalettePageEntries(source.pageItems || []));
    return entries.filter(Boolean).sort(function sortCommandEntries(left, right) {
      return (left.order || 0) - (right.order || 0) || left.title.localeCompare(right.title, 'zh-Hans-CN');
    });
  }

  function getCommandPaletteQueryScore(entry, query) {
    var term = normalizeCenterSearchQuery(query);
    if (!term) return 0;
    var title = String(entry && entry.title || '').toLowerCase();
    if (title === term) return -30;
    if (title.indexOf(term) === 0) return -20;
    if (title.indexOf(term) >= 0) return -10;
    return 0;
  }

  function filterCommandPaletteEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = normalizeCommandPaletteFilter(options && options.filter);
    return (entries || []).filter(function matchCommandEntry(entry) {
      if (filter !== 'all' && entry.category !== filter) return false;
      return matchesCenterSearch(query, [getCommandPaletteEntrySearchText(entry)]);
    }).sort(function sortFilteredCommandEntries(left, right) {
      return getCommandPaletteQueryScore(left, query) - getCommandPaletteQueryScore(right, query) ||
        (left.order || 0) - (right.order || 0) ||
        left.title.localeCompare(right.title, 'zh-Hans-CN');
    });
  }

  function formatCommandPaletteResultSummary(visibleEntries, allEntries, options) {
    var visibleCount = (visibleEntries || []).length;
    var allCount = (allEntries || []).length;
    var filter = normalizeCommandPaletteFilter(options && options.filter);
    var prefix = filter === 'all' ? '全部命令' : getCommandPaletteCategoryLabel(filter);
    return prefix + ' · ' + visibleCount + ' / ' + allCount + ' 项';
  }

  function containsAny(value, needles) {
    var haystack = String(value || '').toLowerCase();
    return (needles || []).some(function hasNeedle(needle) {
      return haystack.indexOf(String(needle || '').toLowerCase()) !== -1;
    });
  }

  function matchesBlockRules(item, rules) {
    var data = item || {};
    var config = rules || {};
    return (
      containsAny(data.title, config.titleKeywords) ||
      containsAny(data.author, config.authorKeywords)
    );
  }

  function parseForumFilterQuery(query) {
    var result = {
      includes: [],
      excludes: [],
      author: '',
    };
    String(query || '')
      .split(/\s+/)
      .map(function trimToken(token) {
        return token.trim();
      })
      .filter(Boolean)
      .forEach(function parseToken(token) {
        if (/^(?:作者|author):/i.test(token)) {
          result.author = token.replace(/^(?:作者|author):/i, '').toLowerCase();
          return;
        }
        if (token.charAt(0) === '!') {
          var excluded = token.slice(1);
          if (excluded) result.excludes.push(excluded.toLowerCase());
          return;
        }
        result.includes.push(token.toLowerCase());
      });
    return result;
  }

  function matchesForumFilter(item, query) {
    var parsed = typeof query === 'string' ? parseForumFilterQuery(query) : (query || {});
    var data = item || {};
    var title = String(data.title || '').toLowerCase();
    var author = String(data.author || '').toLowerCase();
    var resourceText = (data.resourceBadges || []).map(function mapFilterResourceBadge(badge) {
      return [badge.type, badge.label].filter(Boolean).join(' ');
    }).join(' ').toLowerCase();
    var combined = title + ' ' + author + ' ' + resourceText;

    if (parsed.author && author.indexOf(parsed.author) === -1) return false;
    if ((parsed.excludes || []).some(function hasExcluded(token) {
      return token && combined.indexOf(token) !== -1;
    })) {
      return false;
    }
    return (parsed.includes || []).every(function hasIncluded(token) {
      return !token || combined.indexOf(token) !== -1;
    });
  }

  function extractPreviewImageUrls(images, pageUrl) {
    var seen = {};
    return (images || [])
      .filter(isPreviewImageCandidate)
      .map(function toAbsoluteUrl(image) {
        try {
          return new URL(String(image.src || ''), pageUrl || location.href).href;
        } catch (error) {
          return '';
        }
      })
      .filter(function keepUnique(url) {
        if (!url || seen[url]) return false;
        seen[url] = true;
        return true;
      })
      .slice(0, 6);
  }

  function getPreviewImageSource(image) {
    var data = image || {};
    var node = data.node || {};
    return String(data.src || data.currentSrc || node.currentSrc || node.src || '').trim();
  }

  function getPreviewImageSize(image) {
    var data = image || {};
    var node = data.node || {};
    return {
      width: Number(data.naturalWidth || node.naturalWidth || data.width || node.width || 0),
      height: Number(data.naturalHeight || node.naturalHeight || data.height || node.height || 0),
    };
  }

  function markPreviewImageLoaded(image) {
    if (!image || !image.dataset) return;
    image.dataset.spxPreviewReady = '1';
    image.dataset.spxPreviewLoaded = '1';
  }

  function preparePreviewImageReveal(image) {
    if (!image || !image.dataset) return image;
    image.dataset.spxPreviewReady = '0';

    var markLoaded = function markLoadedPreviewImage() {
      markPreviewImageLoaded(image);
      image.removeEventListener('load', markLoaded);
      image.removeEventListener('error', markLoaded);
    };

    image.addEventListener('load', markLoaded);
    image.addEventListener('error', markLoaded);
    if (image.src && image.complete && image.naturalWidth) markLoaded();
    return image;
  }

  function isLargePreviewImage(image) {
    var size = getPreviewImageSize(image);
    if (!size.width && !size.height) return true;
    return Math.max(size.width, size.height) >= 600 || (size.width >= 480 && size.height >= 360);
  }

  function formatPreviewImageLinks(images) {
    var seen = {};
    return (images || [])
      .map(getPreviewImageSource)
      .filter(function keepUniquePreviewLink(url) {
        if (!url || seen[url]) return false;
        seen[url] = true;
        return true;
      })
      .join('\n');
  }

  function getPreviewImageMetaText(image, index) {
    var item = image || {};
    return ['图 ' + ((Number(index) || 0) + 1), item.floorLabel, item.author].filter(Boolean).join(' · ');
  }

  function formatPreviewImageMarkdownLinks(images) {
    var seen = {};
    return (images || [])
      .map(function formatPreviewMarkdownItem(item, index) {
        var url = getPreviewImageSource(item);
        if (!url || seen[url]) return '';
        seen[url] = true;
        return '- ![' + getPreviewImageMetaText(item, index) + '](' + url + ')';
      })
      .filter(Boolean)
      .join('\n');
  }

  function formatPreviewImageLinksByFloor(images) {
    var groups = {};
    var order = [];
    var seen = {};
    (images || []).forEach(function collectPreviewFloor(item, index) {
      var url = getPreviewImageSource(item);
      if (!url || seen[url]) return;
      seen[url] = true;
      var label = item && item.floorLabel ? item.floorLabel : '未知楼层';
      var author = item && item.author ? item.author : '';
      var key = label + '\n' + author;
      if (!groups[key]) {
        groups[key] = {
          label: label,
          author: author,
          urls: [],
        };
        order.push(key);
      }
      groups[key].urls.push('#' + (index + 1) + ' ' + url);
    });
    return order
      .map(function formatPreviewFloorGroup(key) {
        var group = groups[key];
        return '[' + [group.label, group.author].filter(Boolean).join(' · ') + ']\n' + group.urls.join('\n');
      })
      .join('\n\n');
  }

  function getPreviewGalleryRenderState(total, limit, batchSize) {
    var count = Math.max(0, Number(total) || 0);
    var step = Math.max(1, Number(batchSize) || PREVIEW_GALLERY_BATCH_SIZE);
    var nextLimit = Math.max(0, Number(limit) || step);
    var rendered = Math.min(count, nextLimit);
    return {
      total: count,
      rendered: rendered,
      hasMore: rendered < count,
      nextLimit: Math.min(count, rendered + step),
    };
  }

  function formatPreviewGallerySummary(total, visibleCount, renderedCount, largeOnly) {
    var pageTotal = Math.max(0, Number(total) || 0);
    var visible = Math.max(0, Number(visibleCount) || 0);
    var renderedValue = renderedCount == null ? visible : Number(renderedCount);
    var rendered = Math.min(visible, Math.max(0, renderedValue || 0));
    if (largeOnly) {
      return rendered < visible
        ? '大图已显示 ' + rendered + ' / ' + visible + '(当前页 ' + pageTotal + ' 张),点击进入灯箱'
        : '大图 ' + visible + ' / 当前页 ' + pageTotal + ' 张,点击进入灯箱';
    }
    return rendered < visible
      ? '已显示 ' + rendered + ' / 当前页 ' + pageTotal + ' 张,点击进入灯箱'
      : '当前页 ' + pageTotal + ' 张,点击进入灯箱';
  }

  function padPreviewDownloadPart(value, size) {
    return String(Math.max(0, Number(value) || 0)).padStart(size || 2, '0');
  }

  function formatPreviewImageArchiveFileName(timestamp) {
    var rawTime = Number(timestamp);
    var date = new Date(isFinite(rawTime) ? rawTime : Date.now());
    return [
      'southplus-images-',
      date.getFullYear(),
      padPreviewDownloadPart(date.getMonth() + 1),
      padPreviewDownloadPart(date.getDate()),
      '-',
      padPreviewDownloadPart(date.getHours()),
      padPreviewDownloadPart(date.getMinutes()),
      '.zip',
    ].join('');
  }

  function sanitizePreviewDownloadName(value) {
    return String(value || '')
      .replace(/[\\/:*?"<>|]+/g, '-')
      .replace(/\s+/g, '-')
      .replace(/-+/g, '-')
      .replace(/^-|-$/g, '')
      .slice(0, 72) || 'image';
  }

  function getPreviewImageDownloadExtension(url, contentType) {
    var type = String(contentType || '').toLowerCase();
    if (/image\/png/.test(type)) return '.png';
    if (/image\/webp/.test(type)) return '.webp';
    if (/image\/gif/.test(type)) return '.gif';
    if (/image\/(?:jpeg|jpg|pjpeg)/.test(type)) return '.jpg';
    if (/image\/bmp/.test(type)) return '.bmp';
    try {
      var pathname = new URL(String(url || ''), 'https://south-plus.org/').pathname;
      var match = pathname.match(/\.([a-z0-9]{2,5})$/i);
      if (match && /^(?:jpg|jpeg|png|webp|gif|bmp|avif)$/.test(match[1].toLowerCase())) {
        return match[1].toLowerCase() === 'jpeg' ? '.jpg' : '.' + match[1].toLowerCase();
      }
    } catch (error) {}
    return '.jpg';
  }

  function formatPreviewImageDownloadFileName(item, index, contentType) {
    var image = item || {};
    var prefix = padPreviewDownloadPart((Number(index) || 0) + 1, 3);
    var meta = sanitizePreviewDownloadName([image.floorLabel, image.author].filter(Boolean).join('-'));
    return prefix + '-' + meta + getPreviewImageDownloadExtension(getPreviewImageSource(image), contentType);
  }

  function getPreviewDownloadStatusSummary(counts) {
    var data = counts || {};
    var total = Math.max(0, Number(data.total) || 0);
    var done = Math.max(0, Number(data.done) || 0);
    var failed = Math.max(0, Number(data.failed) || 0);
    var active = Math.max(0, Number(data.active) || 0);
    var queued = Math.max(0, Number(data.queued) || 0);
    if (!total) return '没有可下载图片';
    if (data.packing) return '正在打包 ' + done + ' 张图片';
    if (active || queued) return '下载中 ' + done + ' / ' + total + (failed ? ' · ' + failed + ' 张失败' : '');
    if (failed) return '已完成 ' + done + ' / ' + total + ' · ' + failed + ' 张失败可跳过';
    return '已完成 ' + done + ' / ' + total + ' · 准备 ZIP';
  }

  function formatPreviewDownloadReport(entries) {
    var rows = ['SouthPlus Lite 预览图下载报告', '生成时间:' + new Date().toLocaleString(), ''];
    var failed = (entries || []).filter(function keepFailedPreviewDownload(entry) {
      return entry && entry.status === 'failed';
    });
    rows.push('失败图片:' + failed.length + ' 张');
    failed.forEach(function appendFailedPreviewDownload(entry) {
      rows.push('');
      rows.push('#' + (Number(entry.index) + 1 || 1));
      rows.push('地址:' + (entry.src || getPreviewImageSource(entry.item)));
      rows.push('楼层:' + ((entry.item && entry.item.floorLabel) || '未知楼层'));
      rows.push('作者:' + ((entry.item && entry.item.author) || '未知作者'));
      rows.push('重试:' + (entry.attempts || 0) + ' / ' + PREVIEW_DOWNLOAD_MAX_RETRIES);
      rows.push('原因:' + (entry.error || '下载失败'));
    });
    return rows.join('\n');
  }

  var zipCrcTable = null;

  function getZipCrcTable() {
    if (zipCrcTable) return zipCrcTable;
    zipCrcTable = [];
    for (var i = 0; i < 256; i += 1) {
      var crc = i;
      for (var bit = 0; bit < 8; bit += 1) {
        crc = (crc & 1) ? (0xedb88320 ^ (crc >>> 1)) : (crc >>> 1);
      }
      zipCrcTable[i] = crc >>> 0;
    }
    return zipCrcTable;
  }

  function getZipCrc32(bytes) {
    var table = getZipCrcTable();
    var crc = 0xffffffff;
    var data = bytes || [];
    for (var i = 0; i < data.length; i += 1) {
      crc = table[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
    }
    return (crc ^ 0xffffffff) >>> 0;
  }

  function encodeZipText(text) {
    if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(String(text || ''));
    var encoded = unescape(encodeURIComponent(String(text || '')));
    var bytes = new Uint8Array(encoded.length);
    for (var i = 0; i < encoded.length; i += 1) bytes[i] = encoded.charCodeAt(i);
    return bytes;
  }

  function getZipDosDateTime(timestamp) {
    var date = new Date(timestamp || Date.now());
    var year = Math.max(1980, date.getFullYear());
    return {
      time: (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2),
      date: ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(),
    };
  }

  function setZipUint16(view, offset, value) {
    view.setUint16(offset, value, true);
  }

  function setZipUint32(view, offset, value) {
    view.setUint32(offset, value >>> 0, true);
  }

  function getBlobBytes(blob) {
    if (blob instanceof Uint8Array) return Promise.resolve(blob);
    if (blob && typeof blob.arrayBuffer === 'function') {
      return blob.arrayBuffer().then(function convertBlobArrayBuffer(buffer) {
        return new Uint8Array(buffer);
      });
    }
    return new Promise(function readBlobWithFileReader(resolve, reject) {
      if (typeof FileReader === 'undefined' || !blob) {
        resolve(new Uint8Array(0));
        return;
      }
      var reader = new FileReader();
      reader.onload = function handleBlobRead() { resolve(new Uint8Array(reader.result || [])); };
      reader.onerror = function handleBlobReadError() { reject(reader.error || new Error('读取文件失败')); };
      reader.readAsArrayBuffer(blob);
    });
  }

  function createPreviewZipBlob(files) {
    if (typeof Blob === 'undefined' || typeof Uint8Array === 'undefined' || typeof DataView === 'undefined') {
      return Promise.reject(new Error('当前浏览器不支持 ZIP 打包'));
    }
    var sourceFiles = (files || []).filter(function keepZipFile(file) {
      return file && file.name && file.blob;
    });
    return Promise.all(sourceFiles.map(function normalizeZipFile(file) {
      return getBlobBytes(file.blob).then(function attachZipBytes(bytes) {
        return {
          name: String(file.name || 'image'),
          bytes: bytes,
          timestamp: file.timestamp || Date.now(),
        };
      });
    })).then(function buildZipFromBytes(normalizedFiles) {
      var chunks = [];
      var central = [];
      var offset = 0;
      normalizedFiles.forEach(function appendZipFile(file) {
        var nameBytes = encodeZipText(file.name);
        var data = file.bytes || new Uint8Array(0);
        var crc = getZipCrc32(data);
        var timeParts = getZipDosDateTime(file.timestamp);
        var header = new Uint8Array(30 + nameBytes.length);
        var view = new DataView(header.buffer);
        setZipUint32(view, 0, 0x04034b50);
        setZipUint16(view, 4, 20);
        setZipUint16(view, 6, 0x0800);
        setZipUint16(view, 8, 0);
        setZipUint16(view, 10, timeParts.time);
        setZipUint16(view, 12, timeParts.date);
        setZipUint32(view, 14, crc);
        setZipUint32(view, 18, data.length);
        setZipUint32(view, 22, data.length);
        setZipUint16(view, 26, nameBytes.length);
        setZipUint16(view, 28, 0);
        header.set(nameBytes, 30);
        chunks.push(header, data);
        central.push({ nameBytes: nameBytes, crc: crc, size: data.length, offset: offset, time: timeParts.time, date: timeParts.date });
        offset += header.length + data.length;
      });

      var centralOffset = offset;
      central.forEach(function appendCentralZipRecord(record) {
        var header = new Uint8Array(46 + record.nameBytes.length);
        var view = new DataView(header.buffer);
        setZipUint32(view, 0, 0x02014b50);
        setZipUint16(view, 4, 20);
        setZipUint16(view, 6, 20);
        setZipUint16(view, 8, 0x0800);
        setZipUint16(view, 10, 0);
        setZipUint16(view, 12, record.time);
        setZipUint16(view, 14, record.date);
        setZipUint32(view, 16, record.crc);
        setZipUint32(view, 20, record.size);
        setZipUint32(view, 24, record.size);
        setZipUint16(view, 28, record.nameBytes.length);
        setZipUint16(view, 30, 0);
        setZipUint16(view, 32, 0);
        setZipUint16(view, 34, 0);
        setZipUint16(view, 36, 0);
        setZipUint32(view, 38, 0);
        setZipUint32(view, 42, record.offset);
        header.set(record.nameBytes, 46);
        chunks.push(header);
        offset += header.length;
      });
      var centralSize = offset - centralOffset;
      var footer = new Uint8Array(22);
      var footerView = new DataView(footer.buffer);
      setZipUint32(footerView, 0, 0x06054b50);
      setZipUint16(footerView, 4, 0);
      setZipUint16(footerView, 6, 0);
      setZipUint16(footerView, 8, central.length);
      setZipUint16(footerView, 10, central.length);
      setZipUint32(footerView, 12, centralSize);
      setZipUint32(footerView, 16, centralOffset);
      setZipUint16(footerView, 20, 0);
      chunks.push(footer);
      return new Blob(chunks, { type: 'application/zip' });
    });
  }

  function hasThreadRowHiddenClass(row) {
    if (!row || !row.classList) return false;
    return THREAD_ROW_HIDDEN_CLASSES.some(function hasHiddenClass(className) {
      return row.classList.contains(className);
    });
  }

  function syncThreadRowDisplay(row) {
    if (!row || !row.style) return;
    if (hasThreadRowHiddenClass(row)) {
      setImportantStyle(row, 'display', 'none');
      return;
    }
    if (row.dataset && row.dataset.spxThreadListRow === '1') {
      setImportantStyle(row, 'display', 'grid');
      return;
    }
    if (typeof row.style.removeProperty === 'function') {
      row.style.removeProperty('display');
    } else {
      row.style.display = '';
    }
  }

  function setThreadRowHiddenClass(row, className, hidden) {
    if (!row || !row.classList || !className) return;
    row.classList.toggle(className, !!hidden);
    syncThreadRowDisplay(row);
  }

  function isVisibleThreadRow(row) {
    if (!row) return false;
    if (hasThreadRowHiddenClass(row)) return false;
    if (row.offsetParent === null) return false;
    return true;
  }

  function markThreadsRead(items, state, timestamp) {
    var target = state || {};
    target.read = target.read || {};
    (items || []).forEach(function markItemRead(item) {
      if (!item || !item.id || !isVisibleThreadRow(item.row)) return;
      target.read[item.id] = timestamp || Date.now();
    });
    return target.read;
  }

  function findThreadIdsByAuthor(items, author) {
    var needle = String(author || '').toLowerCase();
    if (!needle) return [];
    return (items || [])
      .filter(function sameAuthor(item) {
        return String((item && item.author) || '').toLowerCase() === needle;
      })
      .map(function getThreadId(item) {
        return item.id;
      })
      .filter(Boolean);
  }

  function isPreviewImageCandidate(image) {
    var data = image || {};
    var src = String(data.src || '');
    var width = Number(data.naturalWidth || data.width || 0);
    var height = Number(data.naturalHeight || data.height || 0);
    var className = String(data.className || '');
    var alt = String(data.alt || '');

    if (!src) return false;
    if (/\/(?:face|avatar|avatars|uc_server)\/|\/images\/face\//i.test(src)) return false;
    if (/\/images\/post\/smile\//i.test(src)) return false;
    if (/\/images\/.*(?:face|smile|emotion|icon|common)/i.test(src)) return false;
    if (/(?:smile|emotion|face|avatar|head|icon)/i.test(className + ' ' + alt)) return false;
    if (width === 0 && height === 0) return true;
    if (width <= 80 && height <= 80) return false;
    return width >= 120 || height >= 120;
  }

  function trimResourceUrlToken(value) {
    return String(value || '')
      .replace(/&amp;/g, '&')
      .trim()
      .replace(/^[<"'“”‘’]+|[<>"'“”‘’]+$/g, '')
      .replace(/[,。;、\s]+$/g, '')
      .replace(/[)\])】]+$/g, '');
  }

  function decodeResourceUrlToken(value) {
    var text = trimResourceUrlToken(value);
    for (var index = 0; index < 2; index += 1) {
      try {
        var decoded = decodeURIComponent(text);
        if (decoded === text) break;
        text = decoded;
      } catch (error) {
        break;
      }
    }
    return text;
  }

  function isResourceLikeToken(value) {
    var text = trimResourceUrlToken(value);
    return /^magnet:\?/i.test(text) ||
      /^ed2k:\/\//i.test(text) ||
      /^https?:\/\//i.test(text) ||
      /^\/\//.test(text) ||
      CLOUD_RESOURCE_PATH_RE.test(text);
  }

  function unwrapResourceRedirectUrl(value, pageUrl) {
    var text = trimResourceUrlToken(value);
    if (!text) return '';

    var decoded = decodeResourceUrlToken(text);
    var parsed;
    try {
      parsed = new URL(decoded, pageUrl || 'https://south-plus.org/');
    } catch (error) {
      return decoded;
    }

    var paramNames = [
      'url',
      'u',
      'target',
      'to',
      'link',
      'href',
      'go',
      'jump',
      'redirect',
      'redirect_url',
      'redirect_uri',
      'r',
      'site',
      'src',
      'source',
      'file',
      'down',
      'download',
    ];
    for (var index = 0; index < paramNames.length; index += 1) {
      var raw = parsed.searchParams.get(paramNames[index]);
      var candidate = decodeResourceUrlToken(raw);
      if (candidate && isResourceLikeToken(candidate)) return candidate;
    }

    var queryText = [
      decodeResourceUrlToken(parsed.search.slice(1)),
      decodeResourceUrlToken(parsed.hash.replace(/^#/, '')),
      decoded,
    ].join(' ');
    var resourceMatch = queryText.match(new RegExp(
      "(magnet:\\?xt=urn:[^\\s<>\"',。;、))\\]]+|ed2k:\\/\\/\\|file\\|[^\\s<>\"',。;、))\\]]+|https?:\\/\\/[^\\s<>\"',。;、))\\]]+|(?:(?:www|share|pan)\\.)?(?:" +
        CLOUD_RESOURCE_HOST_PATTERN +
        ")\\/[^\\s<>\"',。;、))\\]]+)",
      'i'
    ));
    return resourceMatch ? resourceMatch[1] : decoded;
  }

  function normalizeResourceUrl(value, pageUrl) {
    var text = unwrapResourceRedirectUrl(value, pageUrl);
    if (!text) return '';
    if (/^(?:javascript|mailto|tel|data):/i.test(text) || text.charAt(0) === '#') return '';
    if (/^magnet:\?/i.test(text)) return text;
    if (/^ed2k:\/\//i.test(text)) return text;
    if (CLOUD_RESOURCE_PATH_RE.test(text)) {
      text = 'https://' + text;
    }
    if (/^\/\//.test(text)) text = 'https:' + text;
    try {
      var fallbackBase = 'https://south-plus.org/';
      var base = pageUrl || (typeof location !== 'undefined' ? location.href : fallbackBase);
      if (base && !/^[a-z][a-z0-9+.-]*:/i.test(String(base))) {
        base = new URL(String(base), fallbackBase).href;
      }
      return new URL(text, base || fallbackBase).href;
    } catch (error) {
      return '';
    }
  }

  function classifyResourceLink(url) {
    var text = String(url || '');
    var lower = text.toLowerCase();
    if (/^magnet:\?/i.test(text)) return 'magnet';
    if (/^ed2k:\/\//i.test(text)) return 'ed2k';
    if (TORRENT_RESOURCE_EXT_RE.test(lower)) return 'torrent';
    if (ARCHIVE_RESOURCE_EXT_RE.test(lower)) return 'archive';
    try {
      var parsedUrl = new URL(text);
      var host = parsedUrl.hostname.toLowerCase();
      var path = decodeURIComponent(parsedUrl.pathname || '').toLowerCase();
      if (TORRENT_RESOURCE_EXT_RE.test(path)) return 'torrent';
      if (ARCHIVE_RESOURCE_EXT_RE.test(path)) return 'archive';
      if (CLOUD_RESOURCE_HOST_RE.test(host)) return 'cloud';
    } catch (error) {
      if (CLOUD_RESOURCE_PATH_RE.test(lower)) return 'cloud';
    }
    if (/\.(?:jpe?g|png|gif|webp|bmp|avif)(?:[?#]|$)/i.test(lower)) return 'image';
    if (/^https?:\/\//i.test(text)) return 'external';
    return '';
  }

  function normalizeResourceStorageType(type, url) {
    var value = String(type || '').toLowerCase();
    if (RESOURCE_CATEGORIES[value]) return value;
    if (/^(?:baidu|quark|pikpak)$/.test(value)) return 'cloud';
    return classifyResourceLink(url);
  }

  function getCloudProviderLabel(url) {
    var host = '';
    try {
      host = new URL(normalizeResourceUrl(url)).hostname.toLowerCase();
    } catch (error) {
      host = String(url || '').toLowerCase();
    }

    if (/baidu\.com$/.test(host)) return '百度网盘';
    if (/(?:aliyundrive|alipan)\.com$/.test(host)) return '阿里云盘';
    if (/quark\.cn$/.test(host)) return '夸克网盘';
    if (/drive\.uc\.cn$/.test(host)) return 'UC网盘';
    if (/115\.com$/.test(host)) return '115网盘';
    if (/mega\.nz$/.test(host)) return 'MEGA';
    if (/terabox\.com$/.test(host)) return 'Terabox';
    if (/lanzou[a-z]?\.com$|ilanzou\.com$/.test(host)) return '蓝奏云';
    if (/weiyun\.com$/.test(host)) return '腾讯微云';
    if (/cloud\.189\.cn$/.test(host)) return '天翼云盘';
    if (/123pan\.com$/.test(host)) return '123云盘';
    if (/(?:mypikpak|pikpak)\.com$/.test(host)) return 'PikPak';
    if (/mediafire\.com$/.test(host)) return 'MediaFire';
    if (/4shared\.com$/.test(host)) return '4shared';
    if (/onedrive\.live\.com$/.test(host)) return 'OneDrive';
    if (/pan\.xunlei\.com$/.test(host)) return '迅雷云盘';
    if (/cowtransfer\.com$/.test(host)) return '奶牛快传';
    if (/feijipan\.com$/.test(host)) return '小飞机网盘';
    return '其他网盘';
  }

  function getResourceDisplayLabel(item) {
    var data = typeof item === 'string' ? { url: item } : (item || {});
    var type = data.type || classifyResourceLink(data.url);
    if (type === 'cloud') return getCloudProviderLabel(data.url);
    return RESOURCE_CATEGORIES[type] || data.label || '链接';
  }

  function extractResourceAccessCode(value) {
    var text = String(value || '').replace(/\s+/g, ' ');
    var compactText = text.replace(/\s+/g, '');
    var match = text.match(/(?:提取码|提取碼|提取密码|访问码|访问密码|分享码|分享密码|取件码|取件密码|解压码|解压密码|密码|pass(?:word)?|pwd|code|access\s*code)\s*(?:是|为)?\s*[::=]?\s*([A-Za-z0-9]{3,12})/i) ||
      compactText.match(/(?:提取码|提取碼|提取密码|访问码|访问密码|分享码|分享密码|取件码|取件密码|解压码|解压密码|密码)(?:是|为)?[::=]?([A-Za-z0-9]{3,12})/i);
    return match ? match[1] : '';
  }

  function createResourceLink(url, meta) {
    var normalized = normalizeResourceUrl(url, meta && meta.pageUrl);
    var type = (meta && meta.type) || classifyResourceLink(normalized);
    if (!normalized || !type) return null;
    var accessCode = type === 'cloud'
      ? extractResourceAccessCode(normalized + ' ' + ((meta && meta.text) || '') + ' ' + ((meta && meta.context) || ''))
      : '';
    return {
      url: normalized,
      type: type,
      label: getResourceDisplayLabel({ url: normalized, type: type }),
      accessCode: accessCode,
      text: String((meta && meta.text) || '').replace(/\s+/g, ' ').trim().slice(0, 80),
      floorLabel: String((meta && meta.floorLabel) || ''),
      author: String((meta && meta.author) || ''),
      postIndex: Number(meta && meta.postIndex) || 0,
      sourceIndex: Number(meta && meta.sourceIndex) || 0,
    };
  }

  function dedupeResourceLinks(links) {
    var seen = {};
    var result = [];
    (links || []).forEach(function keepUniqueResourceLink(item) {
      if (!item || !item.url) return;
      var type = item.type || classifyResourceLink(item.url);
      if (!type) return;
      var key = type + '|' + item.url.toLowerCase();
      if (seen[key]) return;
      seen[key] = true;
      result.push(Object.assign({}, item, {
        type: type,
        label: getResourceDisplayLabel(Object.assign({}, item, { type: type })),
      }));
    });
    return result;
  }

  function extractResourceLinksFromText(text, pageUrl, meta) {
    var source = String(text || '');
    var links = [];
    var patterns = [
      /magnet:\?xt=urn:[^\s<>"',。;、))\]]+/ig,
      /ed2k:\/\/\|file\|[^\s<>"',。;、))\]]+/ig,
      /https?:\/\/[^\s<>"',。;、))\]]+/ig,
      new RegExp(
        "(?:(?:www|share|pan)\\.)?(?:" + CLOUD_RESOURCE_HOST_PATTERN + ")\\/[^\\s<>\"',。;、))\\]]+",
        'ig'
      ),
    ];
    patterns.forEach(function scanPattern(pattern) {
      var match;
      while ((match = pattern.exec(source))) {
        var item = createResourceLink(match[0], Object.assign({}, meta || {}, {
          pageUrl: pageUrl,
          sourceIndex: match.index,
          context: source.slice(match.index, match.index + match[0].length + 96),
        }));
        if (item) links.push(item);
      }
    });
    return dedupeResourceLinks(links.sort(function sortBySourceIndex(left, right) {
      return (Number(left.sourceIndex) || 0) - (Number(right.sourceIndex) || 0);
    }));
  }

  function extractResourceLinksFromNode(node, pageUrl, meta) {
    if (!node) return [];
    var links = [];
    qsa('a[href],area[href]', node).forEach(function collectAnchor(link) {
      var item = createResourceLink(link.getAttribute('href') || link.href, Object.assign({}, meta || {}, {
        pageUrl: pageUrl,
        text: link.textContent,
      }));
      if (item) links.push(item);
    });
    qsa('img[src],source[src]', node).forEach(function collectMedia(media) {
      var mediaMeta = Object.assign({}, meta || {}, {
        pageUrl: pageUrl,
        type: 'image',
        text: media.getAttribute('alt') || media.getAttribute('title') || '',
      });
      if (media.tagName === 'IMG' && !isPreviewImageCandidate({
        src: media.currentSrc || media.src,
        naturalWidth: media.naturalWidth,
        naturalHeight: media.naturalHeight,
        width: media.width,
        height: media.height,
        className: media.className,
        alt: media.alt,
      })) return;
      var item = createResourceLink(media.getAttribute('src') || media.currentSrc || media.src, mediaMeta);
      if (item) links.push(item);
    });
    return dedupeResourceLinks(links.concat(extractResourceLinksFromText(node.textContent, pageUrl, meta)));
  }

  function extractReadPageResourceLinks(posts, pageUrl) {
    var list = posts || [];
    var links = [];
    list.forEach(function collectPostResources(post, index) {
      var content = qs('.tpc_content', post) || post;
      var meta = {
        pageUrl: pageUrl,
        floorLabel: getPostFloorLabel(index),
        author: getPostAuthor(post),
        postIndex: index,
      };
      links = links.concat(extractResourceLinksFromNode(content, pageUrl, meta));
    });
    return dedupeResourceLinks(links);
  }

  function filterResourceLinks(links, options) {
    var config = options || {};
    var scope = config.scope || 'all';
    var category = config.category || 'all';
    return (links || []).filter(function keepResourceLink(item) {
      if (!item || !item.url) return false;
      if (category !== 'all' && item.type !== category) return false;
      if (scope === 'floor') return Number(item.postIndex) === Number(config.postIndex);
      if (scope === 'author') return !!config.author && item.author === config.author;
      return true;
    });
  }

  function formatResourceLinks(links) {
    return dedupeResourceLinks(links)
      .map(function formatResourceLine(item) {
        var parts = ['[' + (item.label || getResourceDisplayLabel(item)) + ']'];
        if (item.floorLabel) parts.push(item.floorLabel);
        if (item.author) parts.push(item.author);
        parts.push(item.url);
        if (item.accessCode) parts.push('提取码 ' + item.accessCode);
        return parts.join(' ');
      })
      .join('\n');
  }

  function normalizeResourceTags(value) {
    return normalizeListValue(value, parseTagList);
  }

  function formatResourceTags(tags) {
    return formatTags(normalizeResourceTags(tags));
  }

  function getJumpResourceLinks(links) {
    var normalizedLinks = dedupeResourceLinks(links).map(function normalizeJumpResource(item) {
      var type = normalizeResourceStorageType(item && item.type, item && item.url);
      if (!type || type === item.type) return item;
      return Object.assign({}, item, {
        type: type,
        label: getResourceDisplayLabel({ url: item.url, type: type }),
      });
    });
    return dedupeResourceLinks(normalizedLinks).filter(function keepJumpResource(item) {
      return item && /^(?:magnet|ed2k|torrent|archive|cloud|external)$/.test(item.type || '');
    });
  }

  function getResourceDownloadQueueEntries(entries) {
    return getJumpResourceLinks(entries).filter(function keepTodoResource(item) {
      return normalizeResourceStatus(item.status) === 'todo';
    });
  }

  function formatResourceDownloadList(entries) {
    return getJumpResourceLinks(entries)
      .map(function formatResourceDownloadItem(item, index) {
        var lines = [
          '#' + (index + 1) + ' [' + (item.label || getResourceDisplayLabel(item)) + '] ' + item.url,
        ];
        var source = [item.sourceTitle, item.floorLabel, item.author].filter(Boolean).join(' · ');
        if (item.accessCode) lines.push('提取码:' + item.accessCode);
        if (source) lines.push('来源:' + source);
        if (item.sourceUrl) lines.push('来源链接:' + item.sourceUrl);
        if (item.note) lines.push('备注:' + item.note);
        if (formatResourceTags(item.tags)) lines.push('标签:' + formatResourceTags(item.tags));
        if (item.status) lines.push('状态:' + getResourceStatusLabel(item.status));
        return lines.join('\n');
      })
      .join('\n\n');
  }

  function formatResourceMarkdownList(entries) {
    return getJumpResourceLinks(entries)
      .map(function formatResourceMarkdownItem(item, index) {
        var lines = [
          (index + 1) + '. **' + (item.label || getResourceDisplayLabel(item)) + '**:' + item.url,
        ];
        var source = [item.sourceTitle, item.floorLabel, item.author].filter(Boolean).join(' · ');
        if (item.accessCode) lines.push('   - 提取码:' + item.accessCode);
        if (source) lines.push('   - 来源:' + source);
        if (item.sourceUrl) lines.push('   - 来源链接:' + item.sourceUrl);
        if (item.note) lines.push('   - 备注:' + item.note);
        if (formatResourceTags(item.tags)) lines.push('   - 标签:' + formatResourceTags(item.tags));
        if (item.status) lines.push('   - 状态:' + getResourceStatusLabel(item.status));
        return lines.join('\n');
      })
      .join('\n\n');
  }

  function formatResourceJumpSummary(links) {
    var counts = {};
    var order = [];
    getJumpResourceLinks(links).forEach(function countResourceLabel(item) {
      var label = item.label || getResourceDisplayLabel(item);
      if (!counts[label]) {
        counts[label] = 0;
        order.push(label);
      }
      counts[label] += 1;
    });
    return order.map(function formatSummary(label) {
      return label + ' ' + counts[label];
    }).join(' / ');
  }

  function normalizeResourceStatus(status) {
    var value = String(status || 'saved');
    return RESOURCE_STATUSES[value] ? value : 'saved';
  }

  function getResourceStatusLabel(status) {
    return RESOURCE_STATUSES[normalizeResourceStatus(status)] || RESOURCE_STATUSES.saved;
  }

  function getResourceLibraryKey(item) {
    var data = item || {};
    var url = normalizeResourceUrl(data.url, data.pageUrl || data.sourceUrl);
    var type = normalizeResourceStorageType(data.type, url);
    if (!url || !type) return '';
    return type + '|' + url.toLowerCase();
  }

  function normalizeResourceRecord(record, key) {
    var source = record || {};
    var url = normalizeResourceUrl(source.url, source.pageUrl || source.sourceUrl);
    var type = normalizeResourceStorageType(source.type, url);
    if (!url || !type) return null;
    var label = source.label || getResourceDisplayLabel({ url: url, type: type });
    var provider = type === 'cloud' ? getCloudProviderLabel(url) : label;
    var savedAt = Number(source.savedAt || source.updatedAt || Date.now()) || Date.now();
    var updatedAt = Number(source.updatedAt || savedAt) || savedAt;
    var canonicalKey = getResourceLibraryKey({ url: url, type: type });
    return {
      key: canonicalKey || key || '',
      url: url,
      type: type,
      label: label,
      provider: provider,
      accessCode: String(source.accessCode || ''),
      sourceTitle: String(source.sourceTitle || source.title || ''),
      sourceUrl: String(source.sourceUrl || source.pageUrl || ''),
      floorLabel: String(source.floorLabel || ''),
      author: String(source.author || ''),
      text: String(source.text || '').replace(/\s+/g, ' ').trim().slice(0, 120),
      note: String(source.note || source.memo || '').replace(/\s+/g, ' ').trim().slice(0, 160),
      tags: normalizeResourceTags(source.tags),
      status: normalizeResourceStatus(source.status),
      savedAt: savedAt,
      updatedAt: updatedAt,
    };
  }

  function createResourceRecord(item, meta, timestamp) {
    var source = item || {};
    var context = meta || {};
    var now = Number(timestamp) || Date.now();
    return normalizeResourceRecord({
      url: source.url,
      type: source.type,
      label: source.label,
      accessCode: source.accessCode,
      sourceTitle: context.sourceTitle || source.sourceTitle,
      sourceUrl: context.sourceUrl || source.sourceUrl || source.pageUrl,
      floorLabel: source.floorLabel || context.floorLabel,
      author: source.author || context.author,
      text: source.text,
      note: source.note || context.note,
      tags: mergeTagLists(source.tags, context.tags),
      status: source.status || context.status || 'saved',
      savedAt: now,
      updatedAt: now,
    });
  }

  function mergeResourceRecord(previous, next) {
    if (!next) return previous || null;
    if (!previous) return next;
    return Object.assign({}, previous, next, {
      status: previous.status || next.status,
      savedAt: Number(previous.savedAt || next.savedAt) || next.savedAt,
      accessCode: next.accessCode || previous.accessCode || '',
      sourceTitle: next.sourceTitle || previous.sourceTitle || '',
      sourceUrl: next.sourceUrl || previous.sourceUrl || '',
      floorLabel: next.floorLabel || previous.floorLabel || '',
      author: next.author || previous.author || '',
      text: next.text || previous.text || '',
      note: next.note || previous.note || '',
      tags: mergeTagLists(previous.tags, next.tags),
      updatedAt: Math.max(Number(previous.updatedAt) || 0, Number(next.updatedAt) || 0),
    });
  }

  function pruneResourceLibrary(resources, limit) {
    var source = resources || {};
    var max = Math.max(1, Number(limit) || RESOURCE_LIMIT);
    var normalized = {};
    Object.keys(source).forEach(function normalizeResourceKey(key) {
      var record = normalizeResourceRecord(source[key], key);
      if (!record || !record.key) return;
      normalized[record.key] = mergeResourceRecord(normalized[record.key], record);
    });
    var keys = Object.keys(normalized)
      .sort(function sortByResourceFreshness(left, right) {
        return (Number(normalized[right].updatedAt || normalized[right].savedAt) || 0) -
          (Number(normalized[left].updatedAt || normalized[left].savedAt) || 0);
      })
      .slice(0, max);
    var result = {};
    keys.forEach(function keepResource(key) {
      result[key] = normalized[key];
    });
    return result;
  }

  function saveResourceLinksToLibrary(links, resources, meta, timestamp) {
    var target = pruneResourceLibrary(resources || {});
    var saved = 0;
    getJumpResourceLinks(links).forEach(function saveResourceLink(item) {
      var record = createResourceRecord(item, meta, timestamp);
      if (!record || !record.key) return;
      target[record.key] = mergeResourceRecord(target[record.key], record);
      saved += 1;
    });
    return {
      resources: pruneResourceLibrary(target),
      saved: saved,
    };
  }

  function getResourceCenterEntries(resources) {
    var library = pruneResourceLibrary(resources || {});
    return Object.keys(library)
      .map(function toResourceEntry(key) {
        var item = normalizeResourceRecord(library[key], key);
        if (!item) return null;
        return Object.assign({}, item, {
          statusLabel: getResourceStatusLabel(item.status),
          sourceText: [item.sourceTitle, item.floorLabel, item.author].filter(Boolean).join(' · '),
          tagText: formatResourceTags(item.tags),
          providerKey: (item.type === 'cloud' ? item.provider : item.label).toLowerCase(),
        });
      })
      .filter(Boolean)
      .sort(function sortResourceEntries(left, right) {
        return (right.updatedAt || right.savedAt || 0) - (left.updatedAt || left.savedAt || 0);
      });
  }

  function getResourceBadgeDefinition(type) {
    return RESOURCE_BADGE_DEFINITIONS[type] || null;
  }

  function getResourceBadgeOrderIndex(type) {
    var index = RESOURCE_BADGE_ORDER.indexOf(type);
    return index === -1 ? RESOURCE_BADGE_ORDER.length : index;
  }

  function getCloudResourceBadgeType(value) {
    var text = String(value || '');
    if (/百度|baidu/i.test(text)) return 'baidu';
    if (/夸克|quark/i.test(text)) return 'quark';
    if (/pikpak/i.test(text)) return 'pikpak';
    return 'cloud';
  }

  function getResourceBadgeFromResourceItem(item, source) {
    var data = item || {};
    var type = data.type || classifyResourceLink(data.url);
    var badgeType = type === 'cloud'
      ? getCloudResourceBadgeType([data.provider, data.label, data.url].filter(Boolean).join(' '))
      : type;
    var definition = getResourceBadgeDefinition(badgeType);
    if (!definition) return null;
    return {
      type: badgeType,
      label: definition.label,
      source: source || 'resource',
      guessed: false,
    };
  }

  function mergeResourceBadges() {
    var map = {};
    Array.prototype.slice.call(arguments).forEach(function mergeBadgeList(list) {
      (list || []).forEach(function mergeBadge(badge) {
        if (!badge || !badge.type || !getResourceBadgeDefinition(badge.type)) return;
        var previous = map[badge.type];
        if (!previous || (previous.guessed && !badge.guessed)) {
          map[badge.type] = Object.assign({}, badge, {
            label: badge.label || getResourceBadgeDefinition(badge.type).label,
            guessed: !!badge.guessed,
          });
        }
      });
    });
    return Object.keys(map)
      .sort(function sortResourceBadges(left, right) {
        return getResourceBadgeOrderIndex(left) - getResourceBadgeOrderIndex(right);
      })
      .map(function mapResourceBadge(type) {
        return map[type];
      });
  }

  function getResourceBadgesFromItems(items, source) {
    return mergeResourceBadges((items || []).map(function mapResourceBadgeItem(item) {
      return getResourceBadgeFromResourceItem(item, source);
    }));
  }

  function inferResourceBadgesFromText(text) {
    var source = String(text || '');
    var badges = RESOURCE_BADGE_ORDER.map(function inferResourceBadge(type) {
      var definition = getResourceBadgeDefinition(type);
      if (!definition || !definition.pattern.test(source)) return null;
      return {
        type: type,
        label: definition.label,
        source: 'title',
        guessed: true,
      };
    });
    var merged = mergeResourceBadges(badges);
    if (merged.some(function hasSpecificCloudBadge(badge) {
      return badge.type === 'baidu' || badge.type === 'quark' || badge.type === 'pikpak';
    })) {
      return merged.filter(function dropGenericCloudBadge(badge) {
        return badge.type !== 'cloud';
      });
    }
    return merged;
  }

  function getThreadResourceBadgeIndex(resources) {
    var index = {};
    getResourceCenterEntries(resources || {}).forEach(function indexResourceEntry(entry) {
      var tid = parseThreadId(entry.sourceUrl || entry.sourceTitle || '');
      var badge = getResourceBadgeFromResourceItem(entry, 'resource');
      if (!tid || !badge) return;
      index[tid] = mergeResourceBadges(index[tid], [badge]);
    });
    return index;
  }

  function getThreadResourceBadges(item, badgeIndex, previewPayload) {
    var data = item || {};
    var index = badgeIndex || {};
    var titleBadges = inferResourceBadgesFromText(data.title || '');
    var previewBadges = previewPayload && previewPayload.resourceBadges
      ? (previewPayload.resourceBadges || []).map(function markPreviewBadge(badge) {
        return Object.assign({}, badge, { source: badge.source || 'preview', guessed: !!badge.guessed });
      })
      : [];
    return mergeResourceBadges(index[data.id], previewBadges, titleBadges);
  }

  function getResourceBadgeTypes(badges) {
    return (badges || []).map(function mapBadgeType(badge) {
      return badge.type;
    }).filter(Boolean);
  }

  function getResourceRailTypeKey(item) {
    var data = item || {};
    var badge = getResourceBadgeFromResourceItem(data, 'resource');
    if (badge && badge.type) return badge.type;
    var type = data.type || classifyResourceLink(data.url);
    if (type === 'cloud') {
      return getCloudResourceBadgeType([data.provider, data.label, data.url].filter(Boolean).join(' '));
    }
    return getResourceBadgeDefinition(type) ? type : 'external';
  }

  function getResourceRailTypeLabel(type) {
    var definition = getResourceBadgeDefinition(type);
    if (definition && definition.label) return definition.label;
    return RESOURCE_CATEGORIES[type] || '资源';
  }

  function getResourceRailEntries(links, resources) {
    var library = pruneResourceLibrary(resources || {});
    return getJumpResourceLinks(links).map(function mapResourceRailEntry(item, index) {
      var key = getResourceLibraryKey(item);
      var savedRecord = key && library[key] ? normalizeResourceRecord(library[key], key) : null;
      var source = savedRecord || item || {};
      var type = getResourceRailTypeKey(source);
      var status = savedRecord ? normalizeResourceStatus(savedRecord.status) : 'todo';
      var accessCode = String(source.accessCode || item.accessCode || '');
      var floorLabel = String(item.floorLabel || source.floorLabel || '');
      var author = String(item.author || source.author || '');
      return {
        key: key || ('resource-rail-' + index),
        url: item.url || source.url || '',
        type: type,
        typeLabel: getResourceRailTypeLabel(type),
        label: item.label || source.label || getResourceDisplayLabel(source),
        accessCode: accessCode,
        floorLabel: floorLabel,
        author: author,
        postIndex: Number(item.postIndex) || 0,
        status: status,
        statusLabel: savedRecord ? getResourceStatusLabel(status) : '待保存',
        saved: !!savedRecord,
        sourceTitle: String(source.sourceTitle || ''),
        sourceUrl: String(source.sourceUrl || item.sourceUrl || item.pageUrl || ''),
        sourceText: [source.sourceTitle, floorLabel, author].filter(Boolean).join(' · '),
        note: String((savedRecord && savedRecord.note) || ''),
        tags: normalizeResourceTags(savedRecord && savedRecord.tags),
      };
    });
  }

  function filterResourceRailEntries(entries, filter) {
    var type = String(filter || 'all');
    if (type === 'all') return (entries || []).slice();
    return (entries || []).filter(function keepResourceRailEntry(entry) {
      return entry && entry.type === type;
    });
  }

  function formatResourceRailSummary(entries) {
    var floors = {};
    var codes = 0;
    var todo = 0;
    var saved = 0;
    (entries || []).forEach(function countResourceRailEntry(entry) {
      if (!entry) return;
      if (entry.floorLabel) floors[entry.floorLabel] = true;
      if (entry.accessCode) codes += 1;
      if (entry.saved) saved += 1;
      if (!entry.saved || normalizeResourceStatus(entry.status) === 'todo') todo += 1;
    });
    return {
      total: (entries || []).length,
      floors: Object.keys(floors).length,
      codes: codes,
      todo: todo,
      saved: saved,
    };
  }

  function formatResourceRailSummaryText(entries) {
    var summary = formatResourceRailSummary(entries);
    return summary.total + ' 条 · ' + summary.floors + ' 个楼层 · ' + summary.codes + ' 个口令 · 待处理 ' + summary.todo;
  }

  function formatResourceRailCodes(entries) {
    return (entries || []).filter(function hasResourceRailCode(entry) {
      return entry && entry.accessCode;
    }).map(function formatResourceRailCode(entry) {
      return [
        '[' + (entry.typeLabel || entry.label || '资源') + ']',
        entry.floorLabel,
        entry.author,
        entry.url,
        '提取码 ' + entry.accessCode,
      ].filter(Boolean).join(' ');
    }).join('\n');
  }

  function getAvailableResourceRailFilterTypes(entries) {
    var seen = {};
    (entries || []).forEach(function collectResourceRailType(entry) {
      if (entry && entry.type && getResourceBadgeDefinition(entry.type)) seen[entry.type] = true;
    });
    return RESOURCE_BADGE_ORDER.filter(function keepResourceRailType(type) {
      return !!seen[type];
    });
  }

  function getResourceProviderOptions(entries) {
    var seen = {};
    return (entries || [])
      .map(function toProviderOption(entry) {
        var label = entry.type === 'cloud' ? entry.provider : entry.label;
        var value = String(label || '').toLowerCase();
        return { value: value, label: label || '未知来源' };
      })
      .filter(function keepUniqueProvider(option) {
        if (!option.value || seen[option.value]) return false;
        seen[option.value] = true;
        return true;
      })
      .sort(function sortProvider(left, right) {
        return left.label.localeCompare(right.label, 'zh-Hans-CN');
      });
  }

  function getResourceSourceGroupKey(entry) {
    var item = entry || {};
    return String(item.sourceUrl || item.sourceTitle || 'unknown');
  }

  function getResourceSourceGroupLabel(entry) {
    var item = entry || {};
    return String(item.sourceTitle || item.sourceUrl || '未知来源');
  }

  function groupResourceCenterEntries(entries) {
    var groups = {};
    var order = [];
    (entries || []).forEach(function collectResourceGroup(entry) {
      if (!entry) return;
      var key = getResourceSourceGroupKey(entry);
      if (!groups[key]) {
        groups[key] = {
          key: key,
          label: getResourceSourceGroupLabel(entry),
          sourceUrl: entry.sourceUrl || '',
          entries: [],
          updatedAt: 0,
        };
        order.push(key);
      }
      groups[key].entries.push(entry);
      groups[key].updatedAt = Math.max(groups[key].updatedAt, Number(entry.updatedAt || entry.savedAt) || 0);
    });
    return order.map(function mapResourceGroup(key) {
      return groups[key];
    }).sort(function sortResourceGroups(left, right) {
      return right.updatedAt - left.updatedAt;
    });
  }

  function filterResourceCenterEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var status = String((options && options.filter) || 'all');
    var provider = String((options && options.provider) || 'all').toLowerCase();
    var tag = String((options && options.tag) || 'all').toLowerCase();
    return (entries || []).filter(function matchResourceEntry(entry) {
      if (!matchesCenterSearch(query, [
        entry.url,
        entry.label,
        entry.provider,
        entry.statusLabel,
        entry.sourceTitle,
        entry.floorLabel,
        entry.author,
        entry.accessCode,
        entry.text,
        entry.note,
        entry.tagText,
      ])) return false;
      if (status !== 'all' && entry.status !== status) return false;
      if (provider !== 'all' && entry.providerKey !== provider) return false;
      if (tag !== 'all' && normalizeResourceTags(entry.tags).map(function lowerResourceTag(item) {
        return item.toLowerCase();
      }).indexOf(tag) === -1) return false;
      return true;
    });
  }

  function ensureResourceSelection(panelState) {
    if (!panelState.selectedResources || typeof panelState.selectedResources !== 'object') {
      panelState.selectedResources = {};
    }
    return panelState.selectedResources;
  }

  function getSelectedResourceKeys(entries, panelState) {
    var selection = ensureResourceSelection(panelState || {});
    var valid = {};
    (entries || []).forEach(function collectValidResourceKey(entry) {
      if (entry && entry.key) valid[entry.key] = true;
    });
    return Object.keys(selection).filter(function keepSelectedResourceKey(key) {
      return !!selection[key] && !!valid[key];
    });
  }

  function setResourceSelection(entries, panelState, selected) {
    var selection = ensureResourceSelection(panelState || {});
    (entries || []).forEach(function setResourceSelected(entry) {
      if (!entry || !entry.key) return;
      if (selected) selection[entry.key] = true;
      else delete selection[entry.key];
    });
    return selection;
  }

  function getResourceEntriesByKeys(entries, keys) {
    var selected = {};
    (keys || []).forEach(function keepKey(key) {
      selected[key] = true;
    });
    return (entries || []).filter(function isSelectedResourceEntry(entry) {
      return entry && selected[entry.key];
    });
  }

  function clampPreviewZoom(value) {
    var zoom = Number(value);
    if (!isFinite(zoom)) return 1;
    return Math.min(4, Math.max(0.5, Math.round(zoom * 100) / 100));
  }

  function getPreviewLightboxKeyAction(event) {
    if (!event || event.altKey || event.ctrlKey || event.metaKey) return '';
    var key = event.key || event.code || '';
    if (key === 'Escape' || key === 'Esc') return 'close';
    if (key === 'ArrowLeft' || key === 'Left') return 'previous';
    if (key === 'ArrowRight' || key === 'Right') return 'next';
    if (key === '+' || key === '=' || key === 'Add') return 'zoomIn';
    if (key === '-' || key === '_' || key === 'Subtract') return 'zoomOut';
    if (key === '0' || key === 'Digit0' || key === 'Numpad0') return 'zoomReset';
    return '';
  }

  function parsePostPrice(value) {
    var text = String(value || '').replace(/\s+/g, ' ');
    var patterns = [
      /(?:此帖|本帖|帖子)?\s*(?:售价|价格)\s*[::]?\s*([0-9]+(?:\.[0-9]+)?)\s*SP(?:币)?/i,
      /(?:购买(?:此帖)?(?:需要)?|购买需要|需要|需支付|花费)\s*[::]?\s*([0-9]+(?:\.[0-9]+)?)\s*SP(?:币)?/i,
    ];
    for (var index = 0; index < patterns.length; index += 1) {
      var match = text.match(patterns[index]);
      if (match) return Number(match[1]);
    }
    return null;
  }

  function parseUserSpBalance(value) {
    var text = String(value || '').replace(/\s+/g, ' ');
    var patterns = [
      /SP(?:币)?\s*(?:余额|结余|可用)?\s*[::]\s*([0-9]+(?:\.[0-9]+)?)/i,
      /(?:当前(?:拥有|余额)?|账户余额|帐户余额|可用余额|余额)\s*[::]?\s*([0-9]+(?:\.[0-9]+)?)\s*SP(?:币)?/i,
    ];
    for (var index = 0; index < patterns.length; index += 1) {
      var match = text.match(patterns[index]);
      if (match) return Number(match[1]);
    }
    return null;
  }

  function shouldAutoBuyPost(settings, price, balance) {
    var config = settings || {};
    var maxPrice = Number(config.autoBuyMaxSp);
    var postPrice = Number(price);
    var currentBalance = Number(balance);
    return !!config.autoBuyPost &&
      maxPrice > 0 &&
      postPrice >= 0 &&
      postPrice < maxPrice &&
      currentBalance >= postPrice;
  }

  function extractBuyTopicUrl(value, pageUrl) {
    var text = String(value || '').replace(/&amp;/g, '&');
    var quoted = text.match(/['"]([^'"]*job\.php\?[^'"]*action=buytopic[^'"]*)['"]/i);
    var direct = text.match(/(?:https?:\/\/[^'"\s]*job\.php\?[^'"\s]*action=buytopic[^'"\s]*|\/?job\.php\?[^'"\s]*action=buytopic[^'"\s]*)/i);
    var target = quoted ? quoted[1] : (direct ? direct[0] : '');
    if (!target) return '';
    try {
      return new URL(target, pageUrl || 'https://south-plus.org/').href;
    } catch (error) {
      return '';
    }
  }

  function getAutoBuyAttemptKey(targetUrl, pageUrl) {
    var tid = '';
    var pid = '';
    try {
      var url = new URL(String(targetUrl || ''), pageUrl || 'https://south-plus.org/');
      tid = url.searchParams.get('tid') || parseThreadId(pageUrl) || parseThreadId(targetUrl);
      pid = url.searchParams.get('pid') || 'tpc';
    } catch (error) {
      tid = parseThreadId(targetUrl) || parseThreadId(pageUrl);
      pid = 'tpc';
    }
    if (!tid) return '';
    return tid + ':' + (pid || 'tpc');
  }

  function isAutoBuyAttemptBlocked(record, now) {
    if (!record || !record.status) return false;
    if (record.status === 'skipped') return false;
    if (record.status === 'checking') {
      var checkedAt = Number(record.updatedAt) || 0;
      var currentTime = now === undefined ? Date.now() : Number(now);
      return !checkedAt || (currentTime - checkedAt) <= AUTO_BUY_CHECK_TTL;
    }
    return record.status === 'buying' || record.status === 'done' || record.status === 'failed';
  }

  function formatAutoBuyAttemptMessage(record) {
    var data = record || {};
    var labels = {
      checking: '上次检查尚未完成',
      buying: '已发起过购买请求',
      done: '已记录购买完成',
      failed: '上次自动购买失败',
    };
    var label = labels[data.status] || '已有自动购买记录';
    var detail = data.message ? ',记录:' + data.message : '';
    return '自动购买未重复执行:' + label + detail + '。如需重试,请清空自动购买记录后再操作。';
  }

  function getAutoBuyDoneAttemptForThread(attempts, threadId) {
    var tid = parseThreadId(threadId);
    if (!tid) return null;
    return Object.keys(attempts || {}).reduce(function findLatestAutoBuyDoneAttempt(latest, key) {
      var record = attempts[key] || {};
      var keyTid = String(key || '').split(':')[0];
      if (keyTid !== tid || record.status !== 'done') return latest;
      var current = Object.assign({ key: key }, record);
      if (!latest || (Number(current.updatedAt) || 0) > (Number(latest.updatedAt) || 0)) return current;
      return latest;
    }, null);
  }

  function formatAutoBuyNavSuccessDetail(record) {
    var data = record || {};
    var parts = ['自动购买已完成'];
    if (data.price !== undefined && isFinite(Number(data.price))) parts.push('价格 ' + Number(data.price) + ' SP');
    if (data.resourceSummary) parts.push('资源:' + data.resourceSummary);
    if (data.message) parts.push(data.message);
    return parts.join(' · ');
  }

  function parseTodayCount(text) {
    var match = String(text || '').match(/\((\d+)\)\s*$/);
    return match ? Number(match[1]) : 0;
  }

  function isAdUrl(url) {
    var text = String(url || '').toLowerCase();
    return /(?:taobao|tmall|alimama|doubleclick|googlesyndication|adservice)/.test(text);
  }

  function detectPageType(url) {
    var text = String(url || '');
    if (/\/simple\//.test(text)) return 'simple';
    if (/\/read\.php\?tid[=-]\d+/.test(text)) return 'read';
    if (/\/post\.php(?:[?#].*)?$/.test(text) || /\/post\.php\?/.test(text)) return 'post';
    if (/\/thread(?:_new)?\.php\?fid[=-]\d+/.test(text)) return 'forum';
    if (/\/search2?\.php(?:[?#].*)?$/.test(text)) return 'search';
    if (/\/(?:u|profile|userpay|message)\.php(?:[?#].*)?$/.test(text)) return 'profile';
    if (/\/(?:hack|plugin)\.php/i.test(text) && /(?:H_name|h_name)[=-]?tasks|tasks/i.test(text)) return 'task';
    if (/\/(?:index\.php)?(?:[?#].*)?$/.test(text)) return 'home';
    return 'other';
  }

  function shouldUseImmersiveRead(settings, url) {
    return !!(settings && settings.immersiveRead) && detectPageType(url) === 'read';
  }

  function shouldUseReaderMode(settings, url) {
    var type = detectPageType(url);
    return !!(settings && settings.readerMode) && (type === 'read' || type === 'forum');
  }

  function shouldUseSiteShell(url) {
    return true;
  }

  function hasForumThreadList(root) {
    if (!root || typeof root.querySelectorAll !== 'function') return true;
    var hasListRows = qsa('td[id^="td_"]', root).some(function hasThreadTitle(cell) {
      return parseThreadId(cell.id) && !!qs('a[id^="a_ajax_"]', cell);
    });
    if (hasListRows) return true;
    return qsa('#wall .stream li a[href*="read.php?tid"]', root).some(function hasGalleryThread(link) {
      return !!parseThreadId(link.getAttribute('href') || link.href);
    });
  }

  function shouldUseForumDashboard(url, root) {
    return detectPageType(url) === 'forum' && hasForumThreadList(root);
  }

  function shouldUseForumKeyboardPaging(url) {
    return detectPageType(url) === 'forum';
  }

  function shouldUseSearchPage(url) {
    return detectPageType(url) === 'search';
  }

  function shouldUseProfilePage(url) {
    return detectPageType(url) === 'profile';
  }

  function shouldUseTaskPage(url) {
    return detectPageType(url) === 'task';
  }

  function shouldUseHomeDashboard(settings, url) {
    return !!(settings && settings.homeDashboard) && detectPageType(url) === 'home';
  }

  function shouldUseModuleNavigation(settings, url, root) {
    var type = detectPageType(url);
    if (type === 'home') return shouldUseHomeDashboard(settings, url);
    if (type === 'forum') return shouldUseForumDashboard(url, root);
    if (type === 'read') return shouldUseReaderMode(settings, url) || shouldUseImmersiveRead(settings, url);
    if (type === 'search') return true;
    if (type === 'task') return true;
    if (type === 'profile') return true;
    return false;
  }

  function shouldShowToolbarFeature(feature) {
    return feature !== 'latest' && feature !== 'clean';
  }

  function hasPreviewGalleryImages(root) {
    var scope = root || document;
    var firstPost = qsa('table.js-post', scope)[0];
    var content = firstPost ? qs('.tpc_content', firstPost) : null;
    if (!content) return false;

    return qsa('img', content)
      .map(function mapPreviewCandidate(img) {
        return {
          src: img.currentSrc || img.src,
          naturalWidth: img.naturalWidth,
          naturalHeight: img.naturalHeight,
          postIndex: 0,
        };
      })
      .some(isPreviewImageCandidate);
  }

  function shouldShowToolbarAction(action, url, root) {
    var type = detectPageType(url);
    var page = currentPageNumber(url);

    if (action === 'prevPage') return (type === 'forum' || type === 'read') && page > 1;
    if (action === 'nextPage') return type === 'forum' || type === 'read';
    if (action === 'home') return type !== 'home';
    if (action === 'reader') return type === 'forum' || type === 'read';
    if (action === 'adBlock') return type !== 'simple';
    if (action === 'homeDashboard') return type === 'home';
    if (action === 'immersiveRead') return type === 'read';
    if (action === 'previewGallery') return type === 'read' && hasPreviewGalleryImages(root);
    if (action === 'unreadOnly') return type === 'forum';
    if (action === 'onlyOriginalAuthor') return type === 'read';
    return true;
  }

  function getSettingsPanelKeys(url, root) {
    var keys = [];
    var allKeys = [
      'cleanMode',
      'readerMode',
      'immersiveRead',
      'nightMode',
      'unifiedPreviewGallery',
      'forumDashboard',
      'compactRead',
      'foldQuotes',
      'hideUserProfile',
      'networkFriendly',
      'autoTaskClaim',
      'autoBuyPost',
      'unreadOnly',
      'onlyOriginalAuthor',
    ];

    allKeys.forEach(function keepUsefulSetting(key) {
      if (key === 'cleanMode' && shouldShowToolbarFeature('clean')) keys.push(key);
      if (key === 'readerMode' && shouldShowToolbarAction('reader', url, root)) keys.push(key);
      if (key === 'immersiveRead' && shouldShowToolbarAction('immersiveRead', url, root)) keys.push(key);
      if (key === 'nightMode') keys.push(key);
      if (key === 'unifiedPreviewGallery' && shouldShowToolbarAction('previewGallery', url, root)) keys.push(key);
      if (key === 'forumDashboard' && shouldShowToolbarAction('homeDashboard', url, root)) keys.push(key);
      if (key === 'compactRead' && detectPageType(url) === 'read') keys.push(key);
      if (key === 'foldQuotes' && detectPageType(url) === 'read') keys.push(key);
      if (key === 'hideUserProfile' && detectPageType(url) === 'read') keys.push(key);
      if (key === 'networkFriendly') keys.push(key);
      if (key === 'autoTaskClaim') keys.push(key);
      if (key === 'autoBuyPost') keys.push(key);
      if (key === 'unreadOnly' && shouldShowToolbarAction('unreadOnly', url, root)) keys.push(key);
      if (key === 'onlyOriginalAuthor' && shouldShowToolbarAction('onlyOriginalAuthor', url, root)) keys.push(key);
    });

    return keys;
  }

  function buildPageUrl(url, page) {
    var targetPage = Math.max(1, Number(page) || 1);
    var parsed = new URL(String(url || ''), 'https://south-plus.org/');
    var href = parsed.href;
    var thread = href.match(/read\.php\?tid[=-](\d+)/);
    var galleryForum = href.match(/thread_new\.php\?fid[=-](\d+)/);
    var forum = href.match(/thread\.php\?fid[=-](\d+)/);

    if (thread) {
      return parsed.origin + '/read.php?tid-' + thread[1] + '-page-' + targetPage + '.html';
    }

    if (galleryForum) {
      return buildForumModePageUrl(href, targetPage, true);
    }

    if (forum) {
      return buildForumModePageUrl(href, targetPage, false);
    }

    return href;
  }

  function buildForumModePageUrl(url, page, galleryMode) {
    var parsed = new URL(String(url || ''), 'https://south-plus.org/');
    var fid = getCurrentForumId(parsed.href);
    var targetPage = Math.max(1, Number(page) || currentPageNumber(parsed.href) || 1);
    if (!fid) return parsed.href;
    if (galleryMode) {
      return parsed.origin + '/thread_new.php?fid-' + fid + '-page-' + targetPage + '.html';
    }
    if (targetPage === 1) return parsed.origin + '/thread.php?fid-' + fid + '.html';
    return parsed.origin + '/thread.php?fid-' + fid + '-page-' + targetPage + '.html';
  }

  function getForumListModeUrl(url, page) {
    return buildForumModePageUrl(url, page || currentPageNumber(url), false);
  }

  function getForumGalleryModeUrl(url, page) {
    return buildForumModePageUrl(url, page || currentPageNumber(url), true);
  }

  function isNetworkFriendlyMode(settings) {
    return !settings || settings.networkFriendly !== false;
  }

  function getScriptRequestPolicyConfig(policy) {
    var source = policy || {};
    var mode = source.mode || source.type || 'background';
    var friendly = source.networkFriendly !== false;
    var minDelay = Number(source.minDelay);
    var priority = Number(source.priority);
    var cooldownMs = Number(source.cooldownMs);

    if (!(minDelay >= 0)) {
      if (mode === 'action' || mode === 'interactive') minDelay = friendly ? 850 : 300;
      else if (mode === 'preview') minDelay = friendly ? 1200 : 500;
      else minDelay = friendly ? 1500 : 650;
    }

    if (!isFinite(priority)) {
      if (mode === 'action' || mode === 'interactive') priority = 30;
      else if (mode === 'preview') priority = 20;
      else priority = 5;
    }

    if (!(cooldownMs > 0)) cooldownMs = friendly ? 20 * 1000 : 10 * 1000;

    return {
      mode: mode,
      label: source.label || '脚本请求',
      priority: priority,
      minDelay: minDelay,
      cooldownMs: cooldownMs,
      networkFriendly: friendly,
      allowDuringCooldown: !!source.allowDuringCooldown,
    };
  }

  function isScriptRateLimitStatus(status) {
    var code = Number(status);
    return code === 429 || code === 503 || code === 504 || code === 520;
  }

  function isScriptRateLimitHtml(html) {
    var text = String(html || '').replace(/\s+/g, ' ').slice(0, 4000);
    return /(?:1\s*秒内操作频繁|操作频繁|访问过于频繁|请求过于频繁|系统繁忙|504\s*Gateway|Gateway\s*Timeout|HTTP\s*504|Error\s*code\s*520|HTTP\s*520|Web\s*server\s*is\s*returning\s*an\s*unknown\s*error)/i.test(text);
  }

  function createScriptRateLimitError(message) {
    var error = new Error(message || '请求过于频繁,脚本已暂停后台请求,请稍后再试。');
    error.spxRateLimited = true;
    return error;
  }

  function getScriptRequestDelay(policy, state, now) {
    var config = getScriptRequestPolicyConfig(policy);
    var source = state || {};
    var currentTime = now === undefined ? Date.now() : Number(now);
    var cooldownDelay = config.allowDuringCooldown ? 0 : Math.max(0, Number(source.cooldownUntil || 0) - currentTime);
    var intervalDelay = Math.max(0, Number(source.lastStartedAt || 0) + config.minDelay - currentTime);
    return Math.max(cooldownDelay, intervalDelay);
  }

  function isScriptRequestCoolingDown(now) {
    return Number(scriptRequestState.cooldownUntil || 0) > (now === undefined ? Date.now() : Number(now));
  }

  function getRateLimitErrorMessage(policy, reason) {
    var config = getScriptRequestPolicyConfig(policy);
    var label = config.label || '脚本请求';
    var detail = reason ? ':' + reason : '';
    return label + '触发站点限流' + detail + ',已暂停后台请求,请稍后再试。';
  }

  function markScriptRequestCooldown(policy, reason) {
    var config = getScriptRequestPolicyConfig(policy);
    scriptRequestState.cooldownUntil = Math.max(
      Number(scriptRequestState.cooldownUntil || 0),
      Date.now() + config.cooldownMs
    );
    return createScriptRateLimitError(getRateLimitErrorMessage(config, reason));
  }

  function getSchedulerTimerHost() {
    if (typeof window !== 'undefined' && typeof window.setTimeout === 'function') return window;
    return typeof globalThis !== 'undefined' ? globalThis : null;
  }

  function pumpScriptRequestQueue() {
    var timerHost = getSchedulerTimerHost();
    if (scriptRequestState.running || !scriptRequestState.queue.length) return;
    var next = scriptRequestState.queue[0];
    var delay = getScriptRequestDelay(next.policy, scriptRequestState, Date.now());

    if (delay > 0) {
      if (!timerHost || typeof timerHost.setTimeout !== 'function' || scriptRequestState.timer) return;
      scriptRequestState.timer = timerHost.setTimeout(function resumeScriptRequestQueue() {
        scriptRequestState.timer = null;
        pumpScriptRequestQueue();
      }, delay);
      return;
    }

    scriptRequestState.queue.shift();
    scriptRequestState.running = true;
    scriptRequestState.lastStartedAt = Date.now();
    Promise.resolve()
      .then(next.task)
      .then(function resolveScriptRequest(value) {
        scriptRequestState.running = false;
        next.resolve(value);
        pumpScriptRequestQueue();
      }, function rejectScriptRequest(error) {
        scriptRequestState.running = false;
        next.reject(error);
        pumpScriptRequestQueue();
      });
  }

  function scheduleScriptRequest(task, policy) {
    if (typeof Promise !== 'function') return task();
    var config = getScriptRequestPolicyConfig(policy);
    return new Promise(function enqueueScriptRequest(resolve, reject) {
      var timerHost = getSchedulerTimerHost();
      scriptRequestState.sequence += 1;
      scriptRequestState.queue.push({
        task: task,
        policy: config,
        resolve: resolve,
        reject: reject,
        sequence: scriptRequestState.sequence,
      });
      scriptRequestState.queue.sort(function sortScriptRequestQueue(left, right) {
        if (right.policy.priority !== left.policy.priority) return right.policy.priority - left.policy.priority;
        return left.sequence - right.sequence;
      });
      if (scriptRequestState.timer && timerHost && typeof timerHost.clearTimeout === 'function') {
        timerHost.clearTimeout(scriptRequestState.timer);
        scriptRequestState.timer = null;
      }
      pumpScriptRequestQueue();
    });
  }

  function requestWithPolicy(url, options, policy) {
    var fetchImpl = policy && policy.fetch;
    if (!fetchImpl && typeof window !== 'undefined' && typeof window.fetch === 'function') {
      fetchImpl = window.fetch.bind(window);
    }
    if (typeof fetchImpl !== 'function') return Promise.reject(new Error('当前浏览器不支持 fetch'));
    var config = getScriptRequestPolicyConfig(policy);
    return scheduleScriptRequest(function executeScriptFetch() {
      return fetchImpl(url, options).then(function inspectScriptResponse(response) {
        if (response && isScriptRateLimitStatus(response.status)) {
          throw markScriptRequestCooldown(config, 'HTTP ' + response.status);
        }
        return response;
      });
    }, config);
  }

  function readScriptResponseText(response, policy) {
    if (!response || typeof response.text !== 'function') return Promise.resolve('');
    return response.text().then(function inspectScriptResponseText(html) {
      if (isScriptRateLimitHtml(html)) throw markScriptRequestCooldown(policy, '页面提示频繁或 504');
      return html;
    });
  }

  function getCachedThreadPreview(url, now) {
    var key = String(url || '');
    var entry = threadPreviewCache[key];
    if (!entry) return null;
    if (Number(entry.expiresAt || 0) <= (now === undefined ? Date.now() : Number(now))) {
      delete threadPreviewCache[key];
      threadPreviewCacheOrder = threadPreviewCacheOrder.filter(function keepThreadPreviewKey(item) {
        return item !== key;
      });
      return null;
    }
    return cloneJson(entry.payload, entry.payload);
  }

  function rememberThreadPreview(url, payload, now, ttl) {
    var key = String(url || '');
    if (!key || !payload) return;
    if (!threadPreviewCache[key]) threadPreviewCacheOrder.push(key);
    threadPreviewCache[key] = {
      expiresAt: (now === undefined ? Date.now() : Number(now)) + (Number(ttl) > 0 ? Number(ttl) : THREAD_PREVIEW_CACHE_TTL),
      payload: cloneJson(payload, payload),
    };
    while (threadPreviewCacheOrder.length > THREAD_PREVIEW_CACHE_LIMIT) {
      delete threadPreviewCache[threadPreviewCacheOrder.shift()];
    }
  }

  function getCachedUserSpBalance(now) {
    if (Number(spBalanceCache.expiresAt || 0) <= (now === undefined ? Date.now() : Number(now))) return null;
    return spBalanceCache.value === null ? null : Number(spBalanceCache.value);
  }

  function rememberUserSpBalance(balance, now) {
    var value = Number(balance);
    if (!isFinite(value)) return;
    spBalanceCache.value = value;
    spBalanceCache.expiresAt = (now === undefined ? Date.now() : Number(now)) + SP_BALANCE_CACHE_TTL;
  }

  function clearUserSpBalanceCache() {
    spBalanceCache = { value: null, expiresAt: 0 };
  }

  function getNavigationRefreshKey(urls) {
    return (urls || []).map(function normalizeRefreshUrl(url) {
      return String(url || '').replace(/#.*$/, '');
    }).join('|');
  }

  function shouldRefreshNavigationPool(urls, now) {
    var key = getNavigationRefreshKey(urls);
    var refreshedAt = Number(loadMap(NAVIGATION_REFRESH_KEY)[key] || 0);
    return !key || !refreshedAt || ((now === undefined ? Date.now() : Number(now)) - refreshedAt) >= NAVIGATION_REFRESH_TTL;
  }

  function rememberNavigationPoolRefresh(urls, now) {
    var key = getNavigationRefreshKey(urls);
    if (!key) return;
    var data = loadMap(NAVIGATION_REFRESH_KEY);
    data[key] = now === undefined ? Date.now() : Number(now);
    var keys = Object.keys(data).sort(function sortNavigationRefresh(left, right) {
      return Number(data[right] || 0) - Number(data[left] || 0);
    });
    keys.slice(20).forEach(function pruneNavigationRefresh(item) {
      delete data[item];
    });
    saveMap(NAVIGATION_REFRESH_KEY, data);
  }

  function safeJsonParse(value, fallback) {
    try {
      return JSON.parse(value);
    } catch (error) {
      return fallback;
    }
  }

  function isPlainObject(value) {
    return !!value && Object.prototype.toString.call(value) === '[object Object]';
  }

  function cloneJson(value, fallback) {
    if (value === undefined) return fallback;
    return safeJsonParse(JSON.stringify(value), fallback);
  }

  function copyStorageMap(value) {
    if (!isPlainObject(value)) return {};
    var result = {};
    Object.keys(value).forEach(function copyMapEntry(key) {
      var item = value[key];
      if (item === undefined || typeof item === 'function') return;
      result[key] = cloneJson(item, item);
    });
    return result;
  }

  function normalizeSettings(value) {
    var settings = Object.assign(copySettings(DEFAULT_SETTINGS), isPlainObject(value) ? value : {});
    Object.keys(DEFAULT_SETTINGS).forEach(function normalizeSetting(key) {
      var defaultValue = DEFAULT_SETTINGS[key];
      if (typeof defaultValue === 'boolean') {
        settings[key] = !!settings[key];
      }
    });
    settings.moduleNavDensity = normalizeModuleNavDensity(settings.moduleNavDensity);
    settings.immersiveFontSize = 16;
    settings.autoBuyMaxSp = Math.max(0, Number(settings.autoBuyMaxSp) || 0);
    settings.titleKeywords = normalizeListValue(settings.titleKeywords, parseLineList);
    settings.authorKeywords = normalizeListValue(settings.authorKeywords, parseLineList);
    settings.quickReplies = normalizeListValue(settings.quickReplies, parseQuickReplyList);
    return settings;
  }

  function getSettingsPresetDefinitions() {
    return {
      light: {
        label: '轻量',
        description: '减少后台和视觉增强,保留基础清爽阅读。',
        values: {
          cleanMode: true,
          readerMode: true,
          immersiveRead: false,
          unifiedPreviewGallery: false,
          forumDashboard: false,
          compactRead: true,
          networkFriendly: true,
        },
      },
      resource: {
        label: '资源',
        description: '强化图集、资源识别、仪表盘和工作台入口。',
        values: {
          cleanMode: true,
          readerMode: true,
          immersiveRead: true,
          unifiedPreviewGallery: true,
          forumDashboard: true,
          compactRead: true,
          networkFriendly: true,
        },
      },
      reading: {
        label: '阅读',
        description: '聚焦帖子正文、进度续读和长引用折叠。',
        values: {
          cleanMode: true,
          readerMode: true,
          immersiveRead: true,
          unifiedPreviewGallery: true,
          forumDashboard: false,
          compactRead: false,
          foldQuotes: true,
          hideUserProfile: false,
          networkFriendly: true,
        },
      },
    };
  }

  function applySettingsPreset(settings, presetKey) {
    var presets = getSettingsPresetDefinitions();
    var preset = presets[String(presetKey || '')];
    if (!preset) return normalizeSettings(settings);
    var nextSettings = Object.assign({}, settings || {}, preset.values || {});
    return normalizeSettings(nextSettings);
  }

  function createBackupPayload(data, timestamp) {
    var source = data || {};
    return {
      app: APP,
      version: 1,
      exportedAt: Number(timestamp) || Date.now(),
      data: {
        settings: normalizeSettings(source.settings),
        read: copyStorageMap(source.read),
        watch: copyStorageMap(source.watch),
        progress: pruneReadProgress(copyStorageMap(source.progress)),
        threadUpdates: pruneThreadUpdates(copyStorageMap(source.threadUpdates)),
        autoBuyAttempts: pruneAutoBuyAttempts(copyStorageMap(source.autoBuyAttempts)),
        taskClaims: pruneTaskClaimRecords(copyStorageMap(source.taskClaims)),
        resources: pruneResourceLibrary(copyStorageMap(source.resources)),
        navigation: normalizeNavigationPool(source.navigation),
        navigationPins: normalizeNavigationPinMap(source.navigationPins),
        navigationUsage: normalizeNavigationUsageMap(source.navigationUsage),
      },
    };
  }

  function normalizeBackupPayload(value) {
    var payload = typeof value === 'string' ? safeJsonParse(value, null) : value;
    if (!isPlainObject(payload)) return null;
    var source = isPlainObject(payload.data) ? payload.data : payload;
    return createBackupPayload({
      settings: source.settings,
      read: source.read,
      watch: source.watch,
      progress: source.progress,
      threadUpdates: source.threadUpdates,
      autoBuyAttempts: source.autoBuyAttempts || source.autoBuy,
      taskClaims: source.taskClaims,
      resources: source.resources,
      navigation: source.navigation,
      navigationPins: source.navigationPins,
      navigationUsage: source.navigationUsage,
    }, payload.exportedAt);
  }

  function collectBackupPayload(settings, state) {
    return createBackupPayload({
      settings: settings,
      read: state && state.read,
      watch: state && state.watch,
      progress: state && state.progress,
      threadUpdates: state && state.threadUpdates ? state.threadUpdates : loadThreadUpdates(),
      autoBuyAttempts: loadAutoBuyAttempts(),
      taskClaims: loadTaskClaimRecords(),
      resources: state && state.resources,
      navigation: loadNavigationPool(),
      navigationPins: loadNavigationPins(),
      navigationUsage: loadNavigationUsage(),
    });
  }

  function countMapItems(value) {
    return Object.keys(value || {}).length;
  }

  function estimateTextBytes(value) {
    var text = String(value == null ? '' : value);
    if (typeof TextEncoder !== 'undefined') {
      try {
        return new TextEncoder().encode(text).length;
      } catch (error) {
        // Fall back to a small UTF-8 byte counter below.
      }
    }
    var bytes = 0;
    for (var index = 0; index < text.length; index += 1) {
      var code = text.charCodeAt(index);
      if (code <= 0x7f) bytes += 1;
      else if (code <= 0x7ff) bytes += 2;
      else if (code >= 0xd800 && code <= 0xdbff) {
        bytes += 4;
        index += 1;
      } else {
        bytes += 3;
      }
    }
    return bytes;
  }

  function stringifyStorageValue(value) {
    try {
      return JSON.stringify(value == null ? {} : value);
    } catch (error) {
      return '';
    }
  }

  function formatStorageBytes(bytes) {
    var size = Math.max(0, Number(bytes) || 0);
    if (size < 1024) return size + ' B';
    if (size < 1024 * 1024) return (size / 1024).toFixed(size >= 10 * 1024 ? 0 : 1) + ' KB';
    return (size / 1024 / 1024).toFixed(1) + ' MB';
  }

  function createStorageUsageEntry(key, label, value, count, limit) {
    var text = stringifyStorageValue(value);
    return {
      key: key,
      label: label,
      bytes: estimateTextBytes(text),
      size: formatStorageBytes(estimateTextBytes(text)),
      count: Math.max(0, Number(count) || 0),
      limit: Math.max(0, Number(limit) || 0),
    };
  }

  function collectStorageUsageReport(data) {
    var backup = createBackupPayload(data || {});
    var source = backup.data || {};
    var entries = [
      createStorageUsageEntry(STORE_KEY, '设置', source.settings, 1, 0),
      createStorageUsageEntry(READ_KEY, '已读记录', source.read, countMapItems(source.read), 0),
      createStorageUsageEntry(WATCH_KEY, '稍后看', source.watch, countMapItems(source.watch), 0),
      createStorageUsageEntry(PROGRESS_KEY, '阅读进度', source.progress, countMapItems(source.progress), READ_PROGRESS_LIMIT),
      createStorageUsageEntry(THREAD_UPDATE_KEY, '新回复追踪', source.threadUpdates, countMapItems(source.threadUpdates), THREAD_UPDATE_LIMIT),
      createStorageUsageEntry(AUTO_BUY_KEY, '自动购买记录', source.autoBuyAttempts, countMapItems(source.autoBuyAttempts), AUTO_BUY_ATTEMPT_LIMIT),
      createStorageUsageEntry(TASK_CLAIM_KEY, '任务领取记录', source.taskClaims, countMapItems(source.taskClaims), TASK_CLAIM_RECORD_LIMIT),
      createStorageUsageEntry(RESOURCE_KEY, '资源库', source.resources, countMapItems(source.resources), RESOURCE_LIMIT),
      createStorageUsageEntry(NAVIGATION_KEY, '导航池', source.navigation, countMapItems(source.navigation), NAVIGATION_POOL_LIMIT),
      createStorageUsageEntry(NAVIGATION_PIN_KEY, '导航置顶', source.navigationPins, countMapItems(source.navigationPins), 0),
      createStorageUsageEntry(NAVIGATION_USAGE_KEY, '导航排序', source.navigationUsage, countMapItems(source.navigationUsage), NAVIGATION_USAGE_LIMIT),
    ];
    var totalBytes = entries.reduce(function sumBytes(total, entry) {
      return total + entry.bytes;
    }, 0);
    var largest = entries.slice().sort(function sortByBytes(left, right) {
      return right.bytes - left.bytes;
    })[0] || null;
    var suggestions = [];

    if (totalBytes >= LOCAL_STORAGE_WARNING_BYTES) {
      suggestions.push('本地存储接近浏览器常见上限,建议先导出备份再清理旧记录');
    }
    entries.forEach(function addLimitSuggestion(entry) {
      if (!entry.limit) return;
      if (entry.count >= Math.floor(entry.limit * 0.8)) {
        suggestions.push(entry.label + '接近 ' + entry.limit + ' 条上限,建议清理或导出归档');
      }
    });
    if (largest && largest.bytes >= 512 * 1024) {
      suggestions.push(largest.label + '占用超过 512 KB,优先检查这一项');
    }

    return {
      entries: entries,
      totalBytes: totalBytes,
      totalSize: formatStorageBytes(totalBytes),
      largest: largest,
      suggestions: suggestions,
    };
  }

  function formatStorageUsageSummary(report) {
    var data = report || collectStorageUsageReport({});
    var largest = data.largest;
    return '本地存储约 ' + data.totalSize + ' · ' + (data.entries || []).length + ' 项' +
      (largest ? ' · 最大:' + largest.label + ' ' + largest.size : '');
  }

  function formatStorageUsageWarnings(report) {
    var data = report || {};
    return data.suggestions && data.suggestions.length
      ? data.suggestions.join(' · ')
      : '当前体积正常,暂无额外清理建议';
  }

  function formatStorageUsageLimit(entry) {
    if (!entry) return '0 条';
    if (!entry.limit) return entry.count + ' 条';
    var ratio = Math.round(clampRatio(entry.count / entry.limit) * 100);
    return entry.count + ' / ' + entry.limit + ' 条(' + ratio + '%)';
  }

  function getStorageUsageLevel(entry) {
    if (!entry) return 'ok';
    if (entry.limit && entry.count >= Math.floor(entry.limit * 0.9)) return 'danger';
    if (entry.limit && entry.count >= Math.floor(entry.limit * 0.8)) return 'warning';
    if (entry.bytes >= 512 * 1024) return 'warning';
    return 'ok';
  }

  function formatStorageUsageEntry(entry) {
    if (!entry) return '';
    return entry.label + ':' + entry.size + ' / ' + entry.count + ' 条' +
      (entry.limit ? ' / 上限 ' + entry.limit : '');
  }

  function getDataRecordTimestamp(record) {
    if (typeof record === 'number') return Number(record) || 0;
    return Number(record && (record.updatedAt || record.savedAt || record.createdAt)) || 0;
  }

  function getDataRecordFingerprint(record) {
    if (!record || typeof record === 'number') return '';
    var rawUrl = String(record.url || record.progressUrl || '');
    if (rawUrl) {
      try {
        var parsed = new URL(rawUrl, 'https://south-plus.org/');
        parsed.hash = '';
        return 'url:' + parsed.href.toLowerCase();
      } catch (error) {
        return 'url:' + rawUrl.split('#')[0].toLowerCase();
      }
    }
    var title = compactText(record.title || record.subject || '');
    return title ? 'title:' + title.toLowerCase() : '';
  }

  function getDuplicateRecordKeys(map) {
    var source = map || {};
    var seen = {};
    var duplicates = [];
    Object.keys(source)
      .sort(function sortByFreshness(left, right) {
        return getDataRecordTimestamp(source[right]) - getDataRecordTimestamp(source[left]);
      })
      .forEach(function detectDuplicateRecord(key) {
        var fingerprint = getDataRecordFingerprint(source[key]);
        if (!fingerprint) return;
        if (seen[fingerprint]) {
          duplicates.push(key);
          return;
        }
        seen[fingerprint] = key;
      });
    return duplicates;
  }

  function getInvalidProgressKeys(progress) {
    var source = progress || {};
    return Object.keys(source).filter(function isInvalidProgress(key) {
      return !(source[key] && source[key].updatedAt);
    });
  }

  function getInvalidAutoBuyKeys(attempts) {
    var source = attempts || {};
    return Object.keys(source).filter(function isInvalidAutoBuy(key) {
      return !(source[key] && source[key].status);
    });
  }

  function getInvalidTaskClaimKeys(records) {
    var source = records || {};
    return Object.keys(source).filter(function isInvalidTaskClaim(key) {
      return !normalizeTaskClaimRecord(source[key], key);
    });
  }

  function getInvalidResourceKeys(resources) {
    var source = resources || {};
    return Object.keys(source).filter(function isInvalidResource(key) {
      return !normalizeResourceRecord(source[key], key);
    });
  }

  function getInvalidThreadUpdateKeys(updates) {
    var source = updates || {};
    return Object.keys(source).filter(function isInvalidThreadUpdate(key) {
      return !normalizeThreadUpdateRecord(source[key], key);
    });
  }

  function getStaleProgressKeys(progress, now, maxAge) {
    var source = progress || {};
    var currentTime = Number(now) || Date.now();
    var threshold = Math.max(1, Number(maxAge) || STALE_PROGRESS_MAX_AGE);
    return Object.keys(source).filter(function isStaleProgress(key) {
      var updatedAt = Number(source[key] && source[key].updatedAt) || 0;
      return !!updatedAt && currentTime - updatedAt > threshold;
    });
  }

  function getOrphanProgressKeys(progress, read, watch) {
    var readMap = read || {};
    var watchMap = watch || {};
    return Object.keys(progress || {}).filter(function isOrphanProgress(key) {
      return !(readMap[key] || watchMap[key]);
    });
  }

  function collectDataHealthReport(data, now) {
    var source = data || {};
    var settings = normalizeSettings(source.settings);
    var read = copyStorageMap(source.read);
    var watch = copyStorageMap(source.watch);
    var progress = copyStorageMap(source.progress);
    var threadUpdates = copyStorageMap(source.threadUpdates);
    var autoBuyAttempts = copyStorageMap(source.autoBuyAttempts || source.autoBuy);
    var taskClaims = copyStorageMap(source.taskClaims);
    var resources = copyStorageMap(source.resources);
    var autoBuyStatusCounts = {};

    Object.keys(autoBuyAttempts).forEach(function countAutoBuyStatus(key) {
      var status = String(autoBuyAttempts[key] && autoBuyAttempts[key].status || '');
      if (!status) return;
      autoBuyStatusCounts[status] = (autoBuyStatusCounts[status] || 0) + 1;
    });

    var duplicateWatchKeys = getDuplicateRecordKeys(watch);
    var duplicateProgressKeys = getDuplicateRecordKeys(progress);
    var staleProgressKeys = getStaleProgressKeys(progress, now);
    var invalidProgressKeys = getInvalidProgressKeys(progress);
    var invalidThreadUpdateKeys = getInvalidThreadUpdateKeys(threadUpdates);
    var invalidAutoBuyKeys = getInvalidAutoBuyKeys(autoBuyAttempts);
    var invalidTaskClaimKeys = getInvalidTaskClaimKeys(taskClaims);
    var invalidResourceKeys = getInvalidResourceKeys(resources);

    return {
      counts: {
        titleKeywords: settings.titleKeywords.length,
        authorKeywords: settings.authorKeywords.length,
        quickReplies: settings.quickReplies.length,
        read: countMapItems(read),
        watch: countMapItems(watch),
        progress: countMapItems(progress),
        threadUpdates: countMapItems(pruneThreadUpdates(threadUpdates)),
        autoBuyAttempts: countMapItems(autoBuyAttempts),
        taskClaims: countMapItems(pruneTaskClaimRecords(taskClaims)),
        resources: countMapItems(pruneResourceLibrary(resources)),
      },
      autoBuyStatusCounts: autoBuyStatusCounts,
      duplicateWatchKeys: duplicateWatchKeys,
      duplicateProgressKeys: duplicateProgressKeys,
      staleProgressKeys: staleProgressKeys,
      orphanProgressKeys: getOrphanProgressKeys(progress, read, watch),
      invalidProgressKeys: invalidProgressKeys,
      invalidThreadUpdateKeys: invalidThreadUpdateKeys,
      invalidAutoBuyKeys: invalidAutoBuyKeys,
      invalidTaskClaimKeys: invalidTaskClaimKeys,
      invalidResourceKeys: invalidResourceKeys,
      cleanupCount:
        duplicateWatchKeys.length +
        duplicateProgressKeys.length +
        staleProgressKeys.length +
        invalidProgressKeys.length +
        invalidThreadUpdateKeys.length +
        invalidAutoBuyKeys.length +
        invalidTaskClaimKeys.length +
        invalidResourceKeys.length,
    };
  }

  function cleanupDataHealthPayload(data, now) {
    var source = data || {};
    var settings = normalizeSettings(source.settings);
    var read = copyStorageMap(source.read);
    var watch = copyStorageMap(source.watch);
    var progress = copyStorageMap(source.progress);
    var threadUpdates = copyStorageMap(source.threadUpdates);
    var autoBuyAttempts = copyStorageMap(source.autoBuyAttempts || source.autoBuy);
    var taskClaims = copyStorageMap(source.taskClaims);
    var resources = copyStorageMap(source.resources);
    var before = collectDataHealthReport({
      settings: settings,
      read: read,
      watch: watch,
      progress: progress,
      threadUpdates: threadUpdates,
      autoBuyAttempts: autoBuyAttempts,
      taskClaims: taskClaims,
      resources: resources,
    }, now);

    before.duplicateWatchKeys.forEach(function removeDuplicateWatch(key) {
      delete watch[key];
    });
    before.duplicateProgressKeys.concat(before.staleProgressKeys, before.invalidProgressKeys).forEach(function removeBadProgress(key) {
      delete progress[key];
    });
    before.invalidThreadUpdateKeys.forEach(function removeBadThreadUpdate(key) {
      delete threadUpdates[key];
    });
    before.invalidAutoBuyKeys.forEach(function removeBadAutoBuy(key) {
      delete autoBuyAttempts[key];
    });
    before.invalidTaskClaimKeys.forEach(function removeBadTaskClaim(key) {
      delete taskClaims[key];
    });
    before.invalidResourceKeys.forEach(function removeBadResource(key) {
      delete resources[key];
    });

    var payload = createBackupPayload({
      settings: settings,
      read: read,
      watch: watch,
      progress: progress,
      threadUpdates: threadUpdates,
      autoBuyAttempts: autoBuyAttempts,
      taskClaims: taskClaims,
      resources: resources,
      navigation: source.navigation,
      navigationPins: source.navigationPins,
      navigationUsage: source.navigationUsage,
    }, now);

    return {
      payload: payload,
      before: before,
      after: collectDataHealthReport(payload.data, now),
    };
  }

  function formatDataHealthSummary(report) {
    var data = report || collectDataHealthReport({});
    var counts = data.counts || {};
    return [
      '已读 ' + (counts.read || 0),
      '稍后看 ' + (counts.watch || 0),
      '阅读进度 ' + (counts.progress || 0),
      '自动购买记录 ' + (counts.autoBuyAttempts || 0),
      '任务领取记录 ' + (counts.taskClaims || 0),
      '资源 ' + (counts.resources || 0),
      '屏蔽词 ' + ((counts.titleKeywords || 0) + (counts.authorKeywords || 0)),
      '快捷回复 ' + (counts.quickReplies || 0),
    ].join(' · ');
  }

  function formatDataHealthWarnings(report) {
    var data = report || {};
    var warnings = [];
    if (data.duplicateWatchKeys && data.duplicateWatchKeys.length) warnings.push('重复稍后看 ' + data.duplicateWatchKeys.length);
    if (data.duplicateProgressKeys && data.duplicateProgressKeys.length) warnings.push('重复进度 ' + data.duplicateProgressKeys.length);
    if (data.staleProgressKeys && data.staleProgressKeys.length) warnings.push('过期进度 ' + data.staleProgressKeys.length);
    if (data.orphanProgressKeys && data.orphanProgressKeys.length) warnings.push('孤立进度 ' + data.orphanProgressKeys.length);
    if (data.invalidProgressKeys && data.invalidProgressKeys.length) warnings.push('异常进度 ' + data.invalidProgressKeys.length);
    if (data.invalidThreadUpdateKeys && data.invalidThreadUpdateKeys.length) warnings.push('异常新回复追踪 ' + data.invalidThreadUpdateKeys.length);
    if (data.invalidAutoBuyKeys && data.invalidAutoBuyKeys.length) warnings.push('异常购买记录 ' + data.invalidAutoBuyKeys.length);
    if (data.invalidTaskClaimKeys && data.invalidTaskClaimKeys.length) warnings.push('异常任务领取记录 ' + data.invalidTaskClaimKeys.length);
    if (data.invalidResourceKeys && data.invalidResourceKeys.length) warnings.push('异常资源 ' + data.invalidResourceKeys.length);
    return warnings.length ? warnings.join(' · ') : '未发现需要清理的重复或过期数据';
  }

  function formatBackupImportPreview(payload) {
    var backup = normalizeBackupPayload(payload);
    if (!backup) return '';
    var report = collectDataHealthReport(backup.data, backup.exportedAt);
    return [
      '即将导入 South Plus +++ 本地备份。',
      formatDataHealthSummary(report),
      formatDataHealthWarnings(report),
      '导入会覆盖当前浏览器内同类本地数据,确认继续?',
    ].join('\n');
  }

  function getStorage() {
    if (typeof window === 'undefined' || !window.localStorage) return null;
    return window.localStorage;
  }

  function loadSettings() {
    var storage = getStorage();
    if (!storage) return copySettings(DEFAULT_SETTINGS);
    var stored = safeJsonParse(storage.getItem(STORE_KEY), {});
    return normalizeSettings(stored);
  }

  function saveSettings(settings) {
    var storage = getStorage();
    if (!storage) return;
    storage.setItem(STORE_KEY, JSON.stringify(settings));
  }

  function copySettings(settings) {
    return JSON.parse(JSON.stringify(settings));
  }

  function loadMap(key) {
    var storage = getStorage();
    if (!storage) return {};
    return safeJsonParse(storage.getItem(key), {}) || {};
  }

  function saveMap(key, map) {
    var storage = getStorage();
    if (!storage) return;
    storage.setItem(key, JSON.stringify(map || {}));
  }

  function normalizeNavigationHref(href, baseUrl) {
    var value = String(href || '').trim();
    if (!value || /^javascript:/i.test(value)) return '';
    try {
      return new URL(value, baseUrl || (typeof location !== 'undefined' ? location.href : 'https://south-plus.org/')).href;
    } catch (error) {
      return value;
    }
  }

  function getNavigationItemKey(section, label, href) {
    var cleanHref = String(href || '').replace(/#.*$/, '').toLowerCase();
    return [String(section || '导航'), String(label || '').toLowerCase(), cleanHref].join('|');
  }

  function isForumGalleryModeUrl(url) {
    return /(?:^|\/)thread_new\.php\?fid[=-]?\d+/i.test(String(url || ''));
  }

  function shouldKeepNavigationLabel(label, options) {
    var normalized = normalizeNavigationLabel(label);
    if (!normalized) return false;
    if (/图墙模式/.test(normalized) && !(options && options.allowForumViewSwitch)) return false;
    return true;
  }

  function isPersistentNavigationSection(section) {
    return ['子栏目', '版块导航'].indexOf(String(section || '子栏目')) !== -1;
  }

  function normalizePersistentNavigationConfig(config, baseUrl, fallbackOrder) {
    if (!config) return null;
    var label = normalizeNavigationLabel(config.label || config.title);
    var href = normalizeNavigationHref(config.href, baseUrl);
    if (!shouldKeepNavigationLabel(label) || !href || config.transient) return null;
    var canonicalLabel = getCanonicalForumNavigationLabel(label, href);
    if (canonicalLabel) label = canonicalLabel;
    if (label.length > 32) label = label.slice(0, 31) + '…';
    var section = config.persistSection || config.section || '子栏目';
    if (!isPersistentNavigationSection(section)) return null;
    return {
      section: section,
      parentLabel: normalizeNavigationLabel(config.parentLabel || ''),
      label: label,
      href: href,
      title: canonicalLabel || normalizeNavigationLabel(config.title || label),
      className: config.className || '',
      order: Math.max(0, Number(config.order !== undefined ? config.order : fallbackOrder) || 0),
      updatedAt: Math.max(0, Number(config.updatedAt) || Date.now()),
    };
  }

  function normalizeNavigationPool(value) {
    var source = value || {};
    var result = {};
    var entries = Array.isArray(source)
      ? source
      : Object.keys(source).map(function mapNavigationEntry(key) {
        return source[key];
      });
    entries.forEach(function normalizeEntry(item, index) {
      var normalized = normalizePersistentNavigationConfig(item, item && item.baseUrl, index + 1);
      if (!normalized) return;
      var key = getNavigationItemKey(normalized.section, normalized.label, normalized.href);
      result[key] = normalized;
    });
    Object.keys(result)
      .sort(function sortNavigationPool(left, right) {
        var leftItem = result[left];
        var rightItem = result[right];
        return (rightItem.updatedAt || 0) - (leftItem.updatedAt || 0);
      })
      .slice(NAVIGATION_POOL_LIMIT)
      .forEach(function removeExtraNavigation(key) {
        delete result[key];
      });
    return result;
  }

  function loadNavigationPool() {
    return normalizeNavigationPool(loadMap(NAVIGATION_KEY));
  }

  function saveNavigationPool(pool) {
    saveMap(NAVIGATION_KEY, normalizeNavigationPool(pool));
  }

  function normalizeNavigationPinMap(value) {
    var source = isPlainObject(value) ? value : {};
    var result = {};
    Object.keys(source).forEach(function normalizePinKey(key) {
      var text = String(key || '').trim();
      if (text && source[key]) result[text] = true;
    });
    return result;
  }

  function normalizeNavigationUsageMap(value) {
    var source = isPlainObject(value) ? value : {};
    var result = {};
    Object.keys(source).forEach(function normalizeUsageEntry(key) {
      var text = String(key || '').trim();
      var item = isPlainObject(source[key]) ? source[key] : {};
      if (!text) return;
      result[text] = {
        usedAt: Math.max(0, Number(item.usedAt) || 0),
        clickedAt: Math.max(0, Number(item.clickedAt) || 0),
        hitCount: Math.max(0, Math.floor(Number(item.hitCount) || 0)),
      };
    });
    Object.keys(result)
      .sort(function sortNavigationUsage(left, right) {
        var leftItem = result[left] || {};
        var rightItem = result[right] || {};
        var leftTime = Math.max(Number(leftItem.clickedAt) || 0, Number(leftItem.usedAt) || 0);
        var rightTime = Math.max(Number(rightItem.clickedAt) || 0, Number(rightItem.usedAt) || 0);
        return rightTime - leftTime;
      })
      .slice(NAVIGATION_USAGE_LIMIT)
      .forEach(function removeExtraNavigationUsage(key) {
        delete result[key];
      });
    return result;
  }

  function loadNavigationPins() {
    return normalizeNavigationPinMap(loadMap(NAVIGATION_PIN_KEY));
  }

  function saveNavigationPins(pins) {
    saveMap(NAVIGATION_PIN_KEY, normalizeNavigationPinMap(pins));
  }

  function loadNavigationUsage() {
    return normalizeNavigationUsageMap(loadMap(NAVIGATION_USAGE_KEY));
  }

  function saveNavigationUsage(usage) {
    saveMap(NAVIGATION_USAGE_KEY, normalizeNavigationUsageMap(usage));
  }

  function getModuleNavigationGroupKey(label) {
    return normalizeNavigationLabel(label).toLowerCase();
  }

  function normalizeNavigationCollapseState(value) {
    var source = isPlainObject(value) ? value : {};
    var result = {};
    Object.keys(source).forEach(function normalizeGroupState(key) {
      var groupKey = getModuleNavigationGroupKey(key);
      if (groupKey && source[key]) result[groupKey] = true;
    });
    return result;
  }

  function loadNavigationCollapseState() {
    return normalizeNavigationCollapseState(loadMap(NAVIGATION_COLLAPSE_KEY));
  }

  function saveNavigationCollapseState(state) {
    saveMap(NAVIGATION_COLLAPSE_KEY, normalizeNavigationCollapseState(state));
  }

  function isModuleNavigationGroupCollapsed(state, label) {
    return !!(state && state[getModuleNavigationGroupKey(label)]);
  }

  function hasActiveModuleNavigationNode(node) {
    if (!node) return false;
    if (node.config && node.config.active) return true;
    return (node.children || []).some(hasActiveModuleNavigationNode);
  }

  function getDefaultModuleNavigationCollapseState(groups) {
    var result = {};
    (groups || []).forEach(function defaultCollapseGroup(group) {
      var label = normalizeNavigationLabel(group && group.label);
      var hasActive = (group.nodes || []).some(hasActiveModuleNavigationNode);
      if (!label || hasActive) return;
      if (/版块导航|历史导航|已发现/.test(label) || (group.nodes || []).length > 18) {
        result[getModuleNavigationGroupKey(label)] = true;
      }
    });
    return result;
  }

  function applyInitialModuleNavigationCollapseState(groups, collapseState) {
    var current = normalizeNavigationCollapseState(collapseState);
    var storage = getStorage();
    if (!storage || storage.getItem(NAVIGATION_COLLAPSE_DEFAULT_KEY) === '1') return current;
    storage.setItem(NAVIGATION_COLLAPSE_DEFAULT_KEY, '1');
    if (Object.keys(current).length) return current;
    var defaults = getDefaultModuleNavigationCollapseState(groups);
    if (!Object.keys(defaults).length) return current;
    saveNavigationCollapseState(defaults);
    return defaults;
  }

  function loadReadProgress() {
    return pruneReadProgress(loadMap(PROGRESS_KEY));
  }

  function saveReadProgress(progress) {
    saveMap(PROGRESS_KEY, pruneReadProgress(progress));
  }

  function loadThreadUpdates() {
    return pruneThreadUpdates(loadMap(THREAD_UPDATE_KEY));
  }

  function saveThreadUpdates(updates) {
    saveMap(THREAD_UPDATE_KEY, pruneThreadUpdates(updates));
  }

  function pruneAutoBuyAttempts(attempts) {
    var source = attempts || {};
    var keys = Object.keys(source)
      .filter(function hasRecord(key) {
        return source[key] && source[key].status;
      })
      .sort(function sortByUpdatedAt(left, right) {
        return (Number(source[right].updatedAt) || 0) - (Number(source[left].updatedAt) || 0);
      })
      .slice(0, AUTO_BUY_ATTEMPT_LIMIT);
    var result = {};
    keys.forEach(function keepAttempt(key) {
      result[key] = source[key];
    });
    return result;
  }

  function loadAutoBuyAttempts() {
    return pruneAutoBuyAttempts(loadMap(AUTO_BUY_KEY));
  }

  function saveAutoBuyAttempts(attempts) {
    saveMap(AUTO_BUY_KEY, pruneAutoBuyAttempts(attempts));
  }

  function loadTaskClaimRecords() {
    return pruneTaskClaimRecords(loadMap(TASK_CLAIM_KEY));
  }

  function saveTaskClaimRecords(records) {
    saveMap(TASK_CLAIM_KEY, pruneTaskClaimRecords(records));
  }

  function mergeTaskClaimRecords(existing, incoming) {
    var result = pruneTaskClaimRecords(existing || {});
    (incoming || []).forEach(function mergeTaskClaimRecord(record) {
      var normalized = normalizeTaskClaimRecord(record);
      if (!normalized) return;
      var key = normalized.key || createTaskClaimRecordKey(normalized);
      var previous = result[key] || {};
      result[key] = Object.assign({}, previous, normalized, {
        key: key,
        recordedAt: Math.max(Number(previous.recordedAt) || 0, Number(normalized.recordedAt) || 0),
      });
    });
    return pruneTaskClaimRecords(result);
  }

  function loadResourceLibrary() {
    return pruneResourceLibrary(loadMap(RESOURCE_KEY));
  }

  function saveResourceLibrary(resources) {
    saveMap(RESOURCE_KEY, pruneResourceLibrary(resources));
  }

  function applyBackupPayload(payload, settings, state) {
    var backup = normalizeBackupPayload(payload);
    if (!backup) return false;
    var data = backup.data || {};
    var nextSettings = normalizeSettings(data.settings);
    Object.keys(settings || {}).forEach(function removeOldSetting(key) {
      delete settings[key];
    });
    Object.assign(settings, nextSettings);
    state.read = copyStorageMap(data.read);
    state.watch = copyStorageMap(data.watch);
    state.progress = pruneReadProgress(copyStorageMap(data.progress));
    state.threadUpdates = pruneThreadUpdates(copyStorageMap(data.threadUpdates));
    state.resources = pruneResourceLibrary(copyStorageMap(data.resources));
    var navigation = normalizeNavigationPool(data.navigation);
    var navigationPins = normalizeNavigationPinMap(data.navigationPins);
    var navigationUsage = normalizeNavigationUsageMap(data.navigationUsage);
    saveSettings(settings);
    saveMap(READ_KEY, state.read);
    saveMap(WATCH_KEY, state.watch);
    saveReadProgress(state.progress);
    saveThreadUpdates(state.threadUpdates);
    saveAutoBuyAttempts(data.autoBuyAttempts);
    saveTaskClaimRecords(data.taskClaims);
    saveResourceLibrary(state.resources);
    saveNavigationPool(navigation);
    saveNavigationPins(navigationPins);
    saveNavigationUsage(navigationUsage);
    clearReadProgressRestoreRequest(parseThreadId(location.href));
    refreshWatchCenter();
    refreshHistoryCenter();
    refreshFavoriteNavPanels();
    refreshAutoBuyCenter();
    refreshTaskClaimInlineSection();
    refreshResourceCenter();
    refreshReadResourceRail();
    enhanceAll(settings, state);
    return true;
  }

  function formatBackupFileName(timestamp) {
    var date = new Date(Number(timestamp) || Date.now());
    function pad(number) {
      return String(number).padStart(2, '0');
    }
    return 'southplus-plus-backup-' +
      date.getFullYear() +
      pad(date.getMonth() + 1) +
      pad(date.getDate()) +
      '-' +
      pad(date.getHours()) +
      pad(date.getMinutes()) +
      '.json';
  }

  function downloadBackupPayload(payload) {
    if (
      typeof Blob === 'undefined' ||
      typeof URL === 'undefined' ||
      typeof URL.createObjectURL !== 'function' ||
      !document ||
      !document.body
    ) return false;
    var backup = normalizeBackupPayload(payload);
    if (!backup) return false;
    var blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' });
    var url = URL.createObjectURL(blob);
    var link = createEl('a');
    link.href = url;
    link.download = formatBackupFileName(backup.exportedAt);
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.setTimeout(function revokeBackupUrl() {
      if (typeof URL.revokeObjectURL === 'function') URL.revokeObjectURL(url);
    }, 1000);
    return true;
  }

  function importBackupFile(file, settings, state, callback) {
    if (!file || typeof FileReader === 'undefined') {
      callback(false, '无法读取备份文件');
      return;
    }
    var reader = new FileReader();
    reader.onload = function handleBackupLoaded() {
      var backup = normalizeBackupPayload(reader.result);
      if (!backup) {
        callback(false, '备份文件格式无效');
        return;
      }
      if (typeof window.confirm === 'function' && !window.confirm(formatBackupImportPreview(backup))) {
        callback(false, '已取消导入');
        return;
      }
      var ok = applyBackupPayload(backup, settings, state);
      callback(ok, ok ? '已导入本地备份' : '备份文件格式无效');
    };
    reader.onerror = function handleBackupError() {
      callback(false, '备份文件读取失败');
    };
    reader.readAsText(file);
  }

  function recordAutoBuyAttempt(key, status, message, details) {
    if (!key) return null;
    var record = {
      status: status,
      message: String(message || ''),
      updatedAt: Date.now(),
    };
    var extra = details || {};
    if (extra.url) record.url = String(extra.url);
    if (extra.price !== undefined) record.price = Number(extra.price);
    if (extra.balance !== undefined) record.balance = Number(extra.balance);
    if (extra.resourceSummary) record.resourceSummary = String(extra.resourceSummary);

    var attempts = loadAutoBuyAttempts();
    attempts[key] = record;
    saveAutoBuyAttempts(attempts);
    refreshAutoBuyCenter();
    return record;
  }

  function currentPageNumber(url) {
    var text = String(url || '');
    var match = text.match(/-page-(\d+)/) || text.match(/[?&]page=(\d+)/);
    return match ? Number(match[1]) : 1;
  }

  function qs(selector, root) {
    return (root || document).querySelector(selector);
  }

  function qsa(selector, root) {
    return Array.prototype.slice.call((root || document).querySelectorAll(selector));
  }

  function createEl(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (typeof text === 'string') node.textContent = text;
    return node;
  }

  var SPX_FONT = '-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif';
  var CSS_BOX = 'box-sizing:border-box!important;';
  var CSS_HIDE = 'display:none!important;';
  var CSS_PAGE_W = 'width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)))!important;';
  var CSS_ELLIPSIS = 'min-width:0!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;';

  function getInjectedStyleText() {
    return [
      ':root,.spx-theme-clean{--spx-bg:#f7f9fc;--spx-page-bg:#eef2f6;--spx-panel:#fff;--spx-panel-muted:#f7f9fc;--spx-line:#d7dee9;--spx-line-soft:#e5eaf2;--spx-text:#1f2937;--spx-strong:#182230;--spx-sub:#667085;--spx-muted:#98a2b3;--spx-link:#1d4ed8;--spx-accent:#2563eb;--spx-accent-soft:#dbeafe;--spx-accent-wash:#eff6ff;--spx-danger:#b91c1c;--spx-danger-soft:#fef2f2;--spx-warn:#b45309;--spx-nav-bg:#283241;--spx-nav-hover:#334155;--spx-nav-text:#dfe7f0;--spx-nav-current-bg:#fff;--spx-nav-current-text:#182230;--spx-input-bg:#fff;--spx-row-bg:#fff;--spx-row-hover:#f8fbff;--spx-module-width:260px;--spx-module-max-offset:52px;--spx-module-section-height:34px;--spx-module-parent-height:40px;--spx-module-item-height:44px;--spx-module-child-height:36px;--spx-module-item-padding:14px;--spx-module-section-padding:12px;--spx-module-item-radius:12px;--spx-radius-sm:6px;--spx-radius:8px;--spx-radius-lg:12px;--spx-radius-xl:16px;--spx-shadow-card:0 6px 18px rgba(15,23,42,.06);--spx-shadow-popover:0 18px 48px rgba(15,23,42,.24);--spx-shadow-strong:0 24px 80px rgba(15,23,42,.3);--spx-page-max:1680px;--spx-page-space:clamp(28px,5.2vw,72px);--spx-reader-line:clamp(760px,62vw,960px);--spx-panel-width:min(620px,calc(100vw - 96px));--spx-panel-max-height:min(78vh,760px);--spx-control-height:32px;}',
      ':root.spx-theme-night,.spx-theme-night{--spx-bg:#202720;--spx-page-bg:#111412;--spx-panel:#181d1a;--spx-panel-muted:#202720;--spx-line:#303a32;--spx-line-soft:#263029;--spx-text:#edf4ec;--spx-strong:#f8fff5;--spx-sub:#aebbae;--spx-muted:#7c887d;--spx-link:#86efac;--spx-accent:#4ade80;--spx-accent-soft:#173a25;--spx-accent-wash:#102318;--spx-danger:#f87171;--spx-danger-soft:#3a171c;--spx-warn:#fbbf24;--spx-nav-bg:#070908;--spx-nav-hover:#202720;--spx-nav-text:#aebbae;--spx-nav-current-bg:#202720;--spx-nav-current-text:#f8fff5;--spx-input-bg:#151a17;--spx-row-bg:#181d1a;--spx-row-hover:#202720;--spx-shadow-card:0 10px 30px rgba(0,0,0,.28);--spx-shadow-popover:0 18px 48px rgba(0,0,0,.46);--spx-shadow-strong:0 24px 80px rgba(0,0,0,.55);}',
      '@media(max-width:900px){:root,.spx-theme-clean,.spx-theme-night{--spx-page-max:100vw;--spx-page-space:16px;--spx-reader-line:calc(100vw - 28px);--spx-panel-width:calc(100vw - 16px);--spx-panel-max-height:calc(100vh - 80px);}}',
      '.spx-adblock .spx-ad-hidden{'+CSS_HIDE+'}',
      '.spx-adblock #banner a[href*="taobao"],.spx-adblock #banner a[href*="tmall"],.spx-adblock #banner a[href*="equity"]{'+CSS_HIDE+'}',
      '.spx-adblock a[href*="taobao"],.spx-adblock a[href*="tmall"],.spx-adblock a[href*="alimama"]{'+CSS_HIDE+'}',
      '.spx-adblock img[src*="taobao"],.spx-adblock img[src*="tmall"],.spx-adblock img[src*="alimama"]{'+CSS_HIDE+'}',
      '.spx-adblock #banner{min-height:0!important;}',
      '.spx-site-shell,.spx-site-shell body{width:100%!important;min-width:0!important;margin:0!important;background:var(--spx-page-bg)!important;color:var(--spx-text)!important;font:14px/1.65 '+SPX_FONT+'!important;}',
      '.spx-site-shell a{color:var(--spx-link)!important;text-decoration:none!important;}',
      '.spx-site-shell a:hover{text-decoration:underline!important;}',
      '.spx-site-shell #wrapA,.spx-site-shell #main{'+CSS_BOX+'width:100%!important;max-width:none!important;background:var(--spx-page-bg)!important;}',
      '.spx-site-shell #header,.spx-site-shell #mainNav,.spx-site-shell #infobox,.spx-site-shell #notice,.spx-site-shell #content,.spx-site-shell #main{'+CSS_BOX+''+CSS_PAGE_W+'margin-left:auto!important;margin-right:auto!important;}',
      '.spx-site-shell #mainNav{display:block!important;height:30px!important;border-radius:8px!important;background:var(--spx-nav-bg)!important;box-shadow:0 3px 10px rgba(15,23,42,.12)!important;overflow:visible!important;}',
      '.spx-site-shell #mainNav a{color:var(--spx-nav-text)!important;text-decoration:none!important;text-shadow:none!important;font-size:14px!important;line-height:30px!important;}',
      '.spx-site-shell #mainNav a:hover{color:#fff!important;text-decoration:none!important;}',
      '.spx-site-shell #mainNav>div[style*="padding-left"]{'+CSS_BOX+'width:100%!important;max-width:100%!important;height:30px!important;padding-left:250px!important;background:transparent!important;overflow:visible!important;}',
      '.spx-site-shell #mainNav>div[style*="padding-left"] table,.spx-site-shell #mainNav>div[style*="padding-left"] tbody,.spx-site-shell #mainNav>div[style*="padding-left"] tr,.spx-site-shell #mainNav>div[style*="padding-left"] td{display:block!important;'+CSS_BOX+'width:auto!important;height:30px!important;margin:0!important;padding:0!important;border:0!important;background:transparent!important;}',
      '.spx-site-shell #guide{display:flex!important;float:none!important;align-items:center!important;justify-content:flex-start!important;flex-wrap:nowrap!important;width:max-content!important;max-width:100%!important;margin:0!important;padding:0!important;overflow:visible!important;white-space:nowrap!important;}',
      '.spx-site-shell #guide>li{display:block!important;float:none!important;width:auto!important;min-width:max-content!important;margin:0!important;white-space:nowrap!important;}',
      '.spx-site-shell #guide>#h_push,.spx-site-shell #guide>#h_hack{'+CSS_HIDE+'}',
      '.spx-site-shell #guide>li>a{'+CSS_BOX+'display:flex!important;float:none!important;align-items:center!important;height:30px!important;line-height:30px!important;padding:0 12px!important;white-space:nowrap!important;color:var(--spx-nav-text)!important;font-size:14px!important;font-weight:800!important;letter-spacing:.1px!important;text-shadow:none!important;}',
      '.spx-site-shell #guide>li>a:hover{background:var(--spx-nav-hover)!important;color:#fff!important;text-decoration:none!important;}',
      '.spx-site-shell #guide>li.current>a,.spx-site-shell #guide>li.spx-nav-current>a,.spx-site-shell #guide>li>a.spx-nav-current{background:var(--spx-nav-current-bg)!important;color:var(--spx-nav-current-text)!important;text-decoration:none!important;box-shadow:inset 0 -3px 0 var(--spx-accent)!important;}',
      '.spx-site-shell #guide>li.spx-nav-active>a,.spx-site-shell #guide>li>a.spx-menu-open{background:var(--spx-nav-hover)!important;color:#fff!important;text-decoration:none!important;box-shadow:none!important;}',
      '.spx-site-shell #spx-nav-brand{display:inline-flex!important;flex:none!important;align-items:center!important;height:28px!important;padding:0 12px!important;color:#fff!important;font-size:13px!important;font-weight:900!important;line-height:28px!important;letter-spacing:.02em!important;white-space:nowrap!important;}',
      '.spx-favorite-nav{position:relative!important;display:flex!important;flex:none!important;align-items:center!important;margin-left:auto!important;align-self:center!important;height:38px!important;overflow:visible!important;z-index:10002!important;}',
      '.spx-favorite-nav-trigger{'+CSS_BOX+'display:inline-flex!important;align-items:center!important;justify-content:center!important;gap:6px!important;height:28px!important;min-width:max-content!important;margin:0!important;padding:0 10px!important;border:1px solid rgba(217,119,6,.28)!important;border-radius:8px!important;background:#ffedd5!important;color:#7c2d12!important;font:900 12px/28px '+SPX_FONT+'!important;white-space:nowrap!important;cursor:pointer!important;box-shadow:none!important;}',
      '.spx-favorite-nav-trigger:hover,.spx-favorite-nav-trigger[aria-expanded="true"]{background:#fff7ed!important;color:#7c2d12!important;text-decoration:none!important;box-shadow:inset 0 -2px 0 var(--spx-warn)!important;}',
      '.spx-favorite-nav-star{display:inline-flex!important;align-items:center!important;justify-content:center!important;width:18px!important;height:18px!important;border-radius:6px!important;background:var(--spx-warn)!important;color:#fff!important;font-size:12px!important;line-height:18px!important;}',
      '.spx-favorite-nav-count{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:20px!important;height:18px!important;padding:0 6px!important;border-radius:999px!important;background:#fff!important;color:var(--spx-warn)!important;font-size:11px!important;line-height:18px!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-favorite-nav-update-count{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:28px!important;height:18px!important;padding:0 7px!important;border-radius:999px!important;background:#dc2626!important;color:#fff!important;font-size:11px!important;line-height:18px!important;font-variant-numeric:tabular-nums!important;}.spx-favorite-nav-update-count[hidden]{'+CSS_HIDE+'}',
      '.spx-favorite-nav-note,.spx-task-auto-claim-nav-note{'+CSS_BOX+'display:inline-flex!important;align-items:center!important;justify-content:center!important;height:24px!important;max-width:128px!important;margin-left:8px!important;padding:0 10px!important;border:1px solid #bbf7d0!important;border-radius:999px!important;background:#f0fdf4!important;color:#166534!important;font-size:12px!important;font-weight:900!important;line-height:24px!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;box-shadow:0 4px 10px rgba(22,101,52,.12)!important;}',
      '.spx-favorite-nav-update-note{'+CSS_BOX+'display:inline-flex!important;align-items:center!important;justify-content:center!important;height:24px!important;max-width:132px!important;margin-left:8px!important;padding:0 10px!important;border:1px solid #fecaca!important;border-radius:999px!important;background:#fee2e2!important;color:#991b1b!important;font-size:12px!important;font-weight:900!important;line-height:24px!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;box-shadow:0 4px 10px rgba(153,27,27,.1)!important;}.spx-favorite-nav-update-note[hidden]{'+CSS_HIDE+'}',
      '.spx-auto-buy-nav-note{border-color:#fed7aa!important;background:#fff7ed!important;color:#9a3412!important;}',
      '.spx-favorite-nav-panel{'+CSS_BOX+'position:absolute!important;top:46px!important;right:0!important;left:auto!important;z-index:10020!important;width:min(720px,calc(100vw - 72px))!important;max-height:min(78vh,720px)!important;overflow:hidden!important;border:1px solid #f3c27a!important;border-radius:12px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:0 22px 52px rgba(15,23,42,.24)!important;font:14px/1.55 '+SPX_FONT+'!important;}',
      '.spx-favorite-nav-panel[hidden]{'+CSS_HIDE+'}',
      '.spx-favorite-head{display:flex!important;align-items:flex-start!important;justify-content:space-between!important;gap:12px!important;padding:14px 14px 10px!important;border-bottom:1px solid #fde4bd!important;background:linear-gradient(180deg,#fff7ed 0%,var(--spx-panel) 100%)!important;}',
      '.spx-favorite-title h3{margin:0!important;color:var(--spx-strong)!important;font-size:15px!important;line-height:1.3!important;font-weight:900!important;}.spx-favorite-summary{margin-top:4px!important;color:#7c2d12!important;font-size:12px!important;font-weight:700!important;}',
      '.spx-favorite-open{display:inline-flex!important;align-items:center!important;min-height:30px!important;padding:0 10px!important;border-radius:8px!important;background:var(--spx-warn)!important;color:#fff!important;text-decoration:none!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;}.spx-favorite-head-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:8px!important;flex-wrap:wrap!important;}.spx-favorite-selected-count{color:#7c2d12!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;}',
      '.spx-favorite-stats{display:grid!important;grid-template-columns:repeat(4,minmax(0,1fr))!important;gap:8px!important;padding:12px 14px 0!important;}.spx-favorite-stat{min-width:0!important;padding:9px 10px!important;border:1px solid #fde4bd!important;border-radius:10px!important;background:#fffaf3!important;}.spx-favorite-stat strong{display:block!important;color:#7c2d12!important;font-size:16px!important;line-height:1.2!important;font-variant-numeric:tabular-nums!important;}.spx-favorite-stat span{display:block!important;margin-top:3px!important;color:var(--spx-sub)!important;font-size:11px!important;font-weight:800!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-favorite-search{'+CSS_BOX+'width:calc(100% - 28px)!important;min-height:34px!important;margin:12px 14px 8px!important;padding:0 11px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;font-size:13px!important;outline:none!important;}.spx-favorite-search:focus{border-color:var(--spx-warn)!important;box-shadow:0 0 0 3px rgba(217,119,6,.14)!important;}',
      '.spx-favorite-tabs,.spx-favorite-tools{display:flex!important;align-items:center!important;gap:8px!important;padding:0 14px 10px!important;}.spx-favorite-tools{justify-content:space-between!important;}.spx-favorite-chips{display:flex!important;gap:6px!important;min-width:0!important;overflow-x:auto!important;scrollbar-width:thin!important;}.spx-favorite-tab,.spx-favorite-chip{flex:none!important;min-height:28px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:999px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;}.spx-favorite-tab.spx-active,.spx-favorite-chip.spx-active{border-color:var(--spx-warn)!important;background:#ffedd5!important;color:#7c2d12!important;}.spx-favorite-chip.spx-favorite-chip-update{border-color:#fecaca!important;background:#fee2e2!important;color:#991b1b!important;}.spx-favorite-sort{width:128px!important;height:30px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-favorite-list{display:grid!important;gap:8px!important;max-height:360px!important;overflow:auto!important;padding:0 14px 14px!important;}.spx-favorite-group{position:sticky!important;top:0!important;z-index:1!important;display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;min-height:30px!important;padding:4px 2px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;}.spx-favorite-group-main{display:flex!important;align-items:center!important;gap:8px!important;min-width:0!important;}.spx-favorite-group-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:6px!important;flex-wrap:wrap!important;}.spx-favorite-group-actions button{min-height:26px!important;padding:0 8px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;}.spx-favorite-group-actions .spx-danger{border-color:#fecaca!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}.spx-favorite-update-badge,.spx-thread-update-badge{display:inline-flex!important;align-items:center!important;justify-content:center!important;height:21px!important;min-width:max-content!important;padding:0 7px!important;border-radius:999px!important;background:#fee2e2!important;color:#b91c1c!important;font-size:11px!important;font-weight:900!important;line-height:21px!important;white-space:nowrap!important;}.spx-thread-update-badge{margin-right:6px!important;vertical-align:middle!important;}.spx-favorite-item{display:grid!important;grid-template-columns:minmax(0,1fr) auto!important;gap:12px!important;min-height:64px!important;padding:11px 12px!important;border:1px solid var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel)!important;}.spx-favorite-item.spx-favorite-has-update{border-color:#fecaca!important;background:#fffafa!important;}.spx-favorite-body-head{display:flex!important;align-items:flex-start!important;gap:8px!important;min-width:0!important;}.spx-favorite-select{flex:none!important;width:16px!important;height:16px!important;margin:2px 0 0!important;accent-color:var(--spx-warn)!important;}.spx-favorite-item-title{display:-webkit-box!important;min-width:0!important;color:var(--spx-strong)!important;font-size:14px!important;font-weight:900!important;line-height:1.38!important;white-space:normal!important;overflow:hidden!important;text-overflow:ellipsis!important;-webkit-line-clamp:2!important;-webkit-box-orient:vertical!important;}.spx-favorite-meta{margin-top:5px!important;color:var(--spx-text)!important;font-size:13px!important;line-height:1.42!important;white-space:normal!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-favorite-actions{display:flex!important;gap:6px!important;align-items:center!important;}.spx-favorite-actions a,.spx-favorite-actions button,.spx-favorite-load-more{min-width:52px!important;height:28px!important;padding:0 8px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;text-decoration:none!important;cursor:pointer!important;}.spx-favorite-actions .spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;}.spx-favorite-actions .spx-danger{border-color:#fecaca!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}.spx-favorite-load{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;min-height:42px!important;padding:8px 10px!important;border:1px dashed #f3c27a!important;border-radius:10px!important;background:#fffaf3!important;color:#7c2d12!important;font-size:12px!important;font-weight:900!important;}.spx-favorite-empty{padding:18px 14px 20px!important;color:var(--spx-sub)!important;font-size:13px!important;font-weight:700!important;text-align:center!important;}',
      '.spx-favorite-nav-panel a.spx-favorite-item-title,.spx-favorite-nav-panel a.spx-favorite-item-title:link,.spx-favorite-nav-panel a.spx-favorite-item-title:visited{color:var(--spx-strong)!important;text-decoration:none!important;text-shadow:none!important;opacity:1!important;filter:none!important;}.spx-favorite-nav-panel .spx-favorite-meta{color:var(--spx-text)!important;text-shadow:none!important;opacity:1!important;filter:none!important;}',
      '.spx-site-shell #mainNav .spx-favorite-nav-panel a.spx-favorite-item-title,.spx-site-shell #mainNav .spx-favorite-nav-panel a.spx-favorite-item-title:link,.spx-site-shell #mainNav .spx-favorite-nav-panel a.spx-favorite-item-title:visited{display:-webkit-box!important;color:#182230!important;text-decoration:none!important;text-shadow:none!important;font-size:14px!important;font-weight:900!important;line-height:1.38!important;opacity:1!important;filter:none!important;}.spx-site-shell #mainNav .spx-favorite-nav-panel .spx-favorite-meta{color:#1f2937!important;font-size:13px!important;line-height:1.42!important;opacity:1!important;filter:none!important;}.spx-site-shell #mainNav .spx-favorite-nav-panel .spx-favorite-open,.spx-site-shell #mainNav .spx-favorite-nav-panel .spx-favorite-actions a.spx-primary{color:#fff!important;line-height:28px!important;}.spx-site-shell #mainNav .spx-favorite-nav-panel .spx-favorite-actions a:not(.spx-primary){color:#1f2937!important;line-height:28px!important;}',
      '.spx-site-shell #guide .spx-peacemaker-nav{position:relative!important;width:auto!important;min-width:max-content!important;overflow:visible!important;}',
      '.spx-site-shell #peacemakerconfig{position:relative!important;overflow:visible!important;color:var(--spx-nav-text)!important;}',
      '.spx-site-shell #peacemakerconfig.spx-menu-open{color:#fff!important;}',
      '.spx-site-shell #peacemakerconfig>div[hidden]{'+CSS_HIDE+'}',
      '.spx-site-shell #peacemakerconfig>div:not([hidden]){'+CSS_BOX+'display:block!important;position:absolute!important;top:30px!important;right:0!important;left:auto!important;z-index:10000!important;width:148px!important;min-width:148px!important;margin:0!important;padding:5px!important;border:1px solid #334155!important;border-radius:8px!important;background:#f8fafc!important;color:#0f172a!important;box-shadow:0 12px 28px rgba(15,23,42,.22)!important;font:700 13px/1.35 '+SPX_FONT+'!important;}',
      '.spx-site-shell #peacemakerconfig>div:not([hidden])>div{'+CSS_BOX+'display:block!important;min-height:28px!important;margin:0!important;padding:5px 8px!important;background:#f8fafc!important;color:#0f172a!important;text-align:left!important;font:inherit!important;line-height:18px!important;border-radius:5px!important;}',
      '.spx-site-shell #peacemakerconfig>div:not([hidden])>div:hover{background:#e0f2fe!important;}',
      '.spx-forum-dashboard-panel{'+CSS_BOX+'grid-column:1/-1!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;padding:14px!important;border:1px solid var(--spx-line)!important;border-radius:12px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-card)!important;overflow:hidden!important;}',
      '.spx-forum-dashboard-head{display:flex!important;align-items:flex-end!important;justify-content:space-between!important;gap:14px!important;margin:0 0 12px!important;}.spx-forum-dashboard-head h2{margin:0!important;color:var(--spx-strong)!important;font-size:20px!important;line-height:1.25!important;font-weight:900!important;}.spx-forum-dashboard-head p{margin:4px 0 0!important;color:var(--spx-sub)!important;font-size:13px!important;line-height:1.45!important;}',
      '.spx-forum-dashboard-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:8px!important;flex-wrap:wrap!important;}.spx-forum-dashboard-actions button{min-height:32px!important;padding:0 11px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;}.spx-forum-dashboard-actions button.spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;}',
      '.spx-forum-dashboard-stats{display:grid!important;grid-template-columns:repeat(6,minmax(0,1fr))!important;gap:10px!important;margin:0 0 12px!important;}.spx-forum-dashboard-stat{'+CSS_BOX+'min-width:0!important;padding:11px!important;border:1px solid var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel-muted)!important;}.spx-forum-dashboard-stat-label{display:block!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-forum-dashboard-stat strong{display:block!important;margin:7px 0 0!important;color:var(--spx-strong)!important;font-size:22px!important;line-height:1.1!important;font-weight:900!important;font-variant-numeric:tabular-nums!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-forum-dashboard-stat small{display:block!important;margin:5px 0 0!important;color:var(--spx-muted)!important;font-size:12px!important;font-weight:800!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-forum-dashboard-stat.spx-blue{border-color:#bfdbfe!important;background:#eff6ff!important;}.spx-forum-dashboard-stat.spx-amber{border-color:#fed7aa!important;background:#fff7ed!important;}.spx-forum-dashboard-stat.spx-rose{border-color:#fecdd3!important;background:#fff1f2!important;}.spx-forum-dashboard-stat.spx-green{border-color:#bbf7d0!important;background:#f0fdf4!important;}.spx-forum-dashboard-stat.spx-violet{border-color:#ddd6fe!important;background:#f5f3ff!important;}',
      '.spx-forum-dashboard-grid{display:grid!important;grid-template-columns:minmax(0,1.08fr) minmax(340px,.92fr)!important;gap:12px!important;align-items:start!important;}.spx-forum-dashboard-stack{display:grid!important;gap:12px!important;min-width:0!important;}.spx-forum-dashboard-split{display:grid!important;grid-template-columns:1fr 1fr!important;gap:12px!important;}.spx-forum-dashboard-card{'+CSS_BOX+'min-width:0!important;padding:12px!important;border:1px solid var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel)!important;box-shadow:none!important;overflow:hidden!important;}.spx-forum-dashboard-card h3{margin:0 0 10px!important;color:var(--spx-strong)!important;font-size:14px!important;font-weight:900!important;line-height:1.3!important;}',
      '.spx-forum-dashboard-topic-list,.spx-forum-dashboard-resource-list,.spx-forum-dashboard-activity-list{display:grid!important;gap:8px!important;}.spx-forum-dashboard-topic,.spx-forum-dashboard-resource,.spx-forum-dashboard-activity{'+CSS_BOX+'display:grid!important;grid-template-columns:minmax(0,1fr) 54px auto!important;gap:10px!important;align-items:center!important;min-height:54px!important;padding:9px 10px!important;border:1px solid var(--spx-line-soft)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;}.spx-forum-dashboard-resource{grid-template-columns:minmax(0,1fr) auto!important;}.spx-forum-dashboard-activity{grid-template-columns:auto minmax(0,1fr) auto!important;}',
      '.spx-forum-dashboard-topic-title,.spx-forum-dashboard-resource-title{display:block!important;min-width:0!important;color:var(--spx-strong)!important;font-size:13px!important;font-weight:900!important;line-height:1.35!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;text-decoration:none!important;}.spx-forum-dashboard-topic-meta{margin-top:3px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:700!important;line-height:1.35!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-forum-dashboard-score{color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;text-align:right!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-forum-dashboard-chips{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:5px!important;flex-wrap:wrap!important;}.spx-forum-dashboard-chip{display:inline-flex!important;align-items:center!important;justify-content:center!important;height:22px!important;padding:0 7px!important;border-radius:999px!important;background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;font-size:11px!important;font-weight:900!important;white-space:nowrap!important;}.spx-forum-dashboard-chip.spx-green{background:#dcfce7!important;color:#047857!important;}.spx-forum-dashboard-chip.spx-amber{background:#ffedd5!important;color:#92400e!important;}.spx-forum-dashboard-chip.spx-violet{background:#ede9fe!important;color:#6d28d9!important;}',
      '.spx-forum-dashboard-ranks{display:grid!important;gap:7px!important;}.spx-forum-dashboard-rank{display:grid!important;grid-template-columns:70px minmax(0,1fr) 34px!important;gap:8px!important;align-items:center!important;min-height:28px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;}.spx-forum-dashboard-rank>b{display:block!important;height:8px!important;border-radius:999px!important;background:var(--spx-line-soft)!important;overflow:hidden!important;}.spx-forum-dashboard-rank>b>i{display:block!important;height:100%!important;border-radius:inherit!important;background:var(--spx-accent)!important;}.spx-forum-dashboard-rank>strong{text-align:right!important;color:var(--spx-strong)!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-forum-dashboard-activity-mark{width:9px!important;height:36px!important;border-radius:999px!important;background:var(--spx-accent)!important;}.spx-forum-dashboard-activity.spx-resource .spx-forum-dashboard-activity-mark{background:#059669!important;}.spx-forum-dashboard-activity.spx-request .spx-forum-dashboard-activity-mark{background:#d97706!important;}.spx-forum-dashboard-activity time{color:var(--spx-muted)!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;}.spx-forum-dashboard-empty{padding:12px!important;color:var(--spx-sub)!important;font-size:13px!important;font-weight:700!important;text-align:center!important;}',
      '@media(max-width:1180px){.spx-forum-dashboard-stats{grid-template-columns:repeat(3,minmax(0,1fr))!important}.spx-forum-dashboard-grid{grid-template-columns:minmax(0,1fr)!important}}',
      '@media(max-width:900px){.spx-forum-dashboard-panel{padding:10px!important}.spx-forum-dashboard-head{align-items:flex-start!important;flex-direction:column!important}.spx-forum-dashboard-actions{justify-content:flex-start!important}.spx-forum-dashboard-stats{grid-template-columns:repeat(2,minmax(0,1fr))!important}.spx-forum-dashboard-split{grid-template-columns:minmax(0,1fr)!important}.spx-forum-dashboard-topic,.spx-forum-dashboard-resource,.spx-forum-dashboard-activity{grid-template-columns:minmax(0,1fr)!important}.spx-forum-dashboard-score{text-align:left!important}.spx-forum-dashboard-chips{justify-content:flex-start!important}}',
      '.spx-search-page #wrapA,.spx-search-page #main{'+CSS_BOX+'max-width:none!important;background:var(--spx-page-bg)!important;}',
      '.spx-search-page #main{'+CSS_BOX+''+CSS_PAGE_W+'margin:16px auto 42px!important;padding:0!important;display:block!important;}',
      '.spx-search-page .t{'+CSS_BOX+'width:100%!important;margin:0 0 14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;overflow:hidden!important;}',
      '.spx-search-page .tr1,.spx-search-page .tr2,.spx-search-page .tr3{font-size:14px!important;line-height:1.6!important;}',
      '.spx-search-page .tr3 td,.spx-search-page .tr1 td{padding:8px 10px!important;}',
      '.spx-search-page .spx-module-body input[type="text"],.spx-search-page .spx-module-body input[type="search"],.spx-search-page .spx-module-body input[name="keyword"],.spx-search-page .spx-module-body input[name="username"]{'+CSS_BOX+'width:min(520px,100%)!important;min-width:0!important;max-width:100%!important;height:34px!important;padding:5px 10px!important;border:1px solid #cbd5e1!important;border-radius:5px!important;background:#fff!important;color:#172033!important;font-size:16px!important;line-height:22px!important;}',
      '.spx-search-page .spx-module-body input[type="text"]:focus,.spx-search-page .spx-module-body input[type="search"]:focus,.spx-search-page .spx-module-body input[name="keyword"]:focus,.spx-search-page .spx-module-body input[name="username"]:focus{border-color:#0f766e!important;box-shadow:0 0 0 2px rgba(15,118,110,.14)!important;outline:none!important;}',
      '.spx-profile-page #wrapA,.spx-profile-page #main{'+CSS_BOX+'max-width:none!important;background:var(--spx-page-bg)!important;}',
      '.spx-profile-page #main,.spx-profile-page #content{'+CSS_BOX+''+CSS_PAGE_W+'margin:16px auto 42px!important;padding:0!important;display:block!important;}',
      '.spx-profile-page #main>*{'+CSS_BOX+'width:100%!important;max-width:none!important;}',
      '.spx-profile-page #main>.bdbA,.spx-profile-page #main>.t{margin-left:0!important;margin-right:0!important;}',
      '#infobox.spx-profile-infobox-restored{'+CSS_BOX+'display:flex!important;align-items:center!important;justify-content:space-between!important;gap:20px!important;min-height:88px!important;margin:0 auto 14px!important;padding:14px 22px!important;background:var(--spx-panel)!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;box-shadow:var(--spx-shadow-card)!important;color:var(--spx-text)!important;overflow:hidden!important;}',
      '.spx-profile-infobox-main{display:flex!important;align-items:center!important;gap:14px!important;min-width:0!important;flex:1 1 auto!important;}',
      '.spx-profile-infobox-avatar{flex:0 0 56px!important;width:56px!important;height:56px!important;overflow:hidden!important;border-radius:6px!important;background:var(--spx-panel-muted)!important;}.spx-profile-infobox-avatar img{display:block!important;width:56px!important;height:56px!important;object-fit:cover!important;}',
      '.spx-profile-infobox-body{display:grid!important;gap:6px!important;min-width:0!important;flex:1 1 auto!important;}.spx-profile-infobox-meta{color:var(--spx-text)!important;font-size:14px!important;font-weight:800!important;line-height:1.45!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-profile-infobox-meta a{font-size:14px!important;font-weight:900!important;}.spx-profile-infobox-honor{color:var(--spx-text)!important;font-size:14px!important;line-height:1.45!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-profile-infobox-hot{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:8px!important;flex:0 1 44%!important;min-width:300px!important;color:var(--spx-sub)!important;font-size:13px!important;font-weight:800!important;line-height:1.45!important;white-space:normal!important;overflow:visible!important;flex-wrap:wrap!important;}.spx-profile-infobox-hot-label{flex:none!important;color:var(--spx-sub)!important;font-weight:900!important;}.spx-profile-infobox-hot a{flex:none!important;font-size:13px!important;font-weight:900!important;}',
      '@media(max-width:760px){#infobox.spx-profile-infobox-restored{align-items:flex-start!important;flex-direction:column!important;padding:12px 14px!important}.spx-profile-infobox-hot{justify-content:flex-start!important;min-width:0!important;flex:1 1 auto!important}.spx-profile-infobox-meta,.spx-profile-infobox-honor{white-space:normal!important}}',
      '.spx-account-tabs{'+CSS_BOX+'display:flex!important;flex-wrap:wrap!important;align-items:center!important;justify-content:flex-start!important;gap:8px!important;width:100%!important;margin:0 0 14px!important;padding:12px 14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;}',
      '.spx-account-tabs a{display:flex!important;align-items:center!important;justify-content:center!important;min-height:34px!important;padding:0 16px!important;border:1px solid #cbd5e1!important;border-radius:8px!important;background:#f8fafc!important;color:#075985!important;font-size:15px!important;font-weight:800!important;line-height:1.2!important;text-decoration:none!important;}',
      '.spx-account-tabs a.spx-account-tab-active,.spx-account-tabs a:hover{background:#e0f2fe!important;border-color:#7dd3fc!important;color:#0369a1!important;text-decoration:none!important;}',
      '.spx-profile-page #u-wrap,.spx-profile-page #u-wrap2{'+CSS_BOX+'width:100%!important;max-width:none!important;background:#fff!important;overflow:visible!important;}',
      '.spx-profile-page #u-wrap2{display:grid!important;grid-template-columns:minmax(220px,300px) minmax(0,1fr)!important;gap:24px!important;padding:18px!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;}',
      '.spx-profile-page #u-sidebar{'+CSS_BOX+'float:none!important;width:auto!important;min-width:0!important;}',
      '.spx-profile-page #u-sidebar .bdbA,.spx-profile-page #u-sidebar .bgA{'+CSS_BOX+'width:100%!important;max-width:100%!important;margin:0 0 12px!important;padding:8px 12px!important;background:#fff!important;border:0!important;border-bottom:1px solid #e2e8f0!important;border-radius:0!important;box-shadow:none!important;overflow:visible!important;}',
      '.spx-profile-page #u-sidebar .bdbA:last-child,.spx-profile-page #u-sidebar .bgA:last-child{margin-bottom:0!important;}',
      '.spx-profile-page #u-content{'+CSS_BOX+'float:none!important;width:auto!important;min-width:0!important;display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr))!important;gap:18px 24px!important;}',
      '.spx-profile-page #u-contentmain{'+CSS_BOX+'grid-column:1/-1!important;float:none!important;width:100%!important;min-width:0!important;max-width:none!important;}',
      '.spx-profile-page #u-contentside{'+CSS_HIDE+'}',
      '.spx-profile-page #u-top{'+CSS_BOX+'grid-column:1/-1!important;width:100%!important;height:auto!important;min-height:0!important;padding:0!important;position:static!important;}',
      '.spx-profile-page #u-top-nav{position:static!important;display:block!important;'+CSS_BOX+'width:100%!important;height:auto!important;margin:12px 0 0!important;padding:0!important;overflow:visible!important;}',
      '.spx-profile-page #u-top-nav .b{display:flex!important;float:none!important;align-items:center!important;justify-content:flex-start!important;flex-wrap:wrap!important;gap:8px!important;width:100%!important;height:auto!important;margin:0!important;padding:0!important;list-style:none!important;overflow:visible!important;}',
      '.spx-profile-page #u-top-nav .b>li{display:block!important;float:none!important;width:auto!important;height:auto!important;margin:0!important;padding:0!important;list-style:none!important;}',
      '.spx-profile-page #u-top-nav .b>li>a{display:flex!important;align-items:center!important;justify-content:center!important;min-height:34px!important;padding:0 16px!important;margin:0!important;border:1px solid #cbd5e1!important;border-radius:8px!important;background:#f8fafc!important;color:#075985!important;font-size:15px!important;font-weight:800!important;line-height:1.2!important;text-decoration:none!important;}',
      '.spx-profile-page #u-top-nav .b>li.current>a,.spx-profile-page #u-top-nav .b>li>a:hover{background:#e0f2fe!important;border-color:#7dd3fc!important;color:#0369a1!important;text-decoration:none!important;}',
      '.spx-profile-page.spx-account-tabs-ready #u-top-nav{'+CSS_HIDE+'}',
      '.spx-profile-page .u-content-66,.spx-profile-page .u-content-33{'+CSS_BOX+'float:none!important;width:auto!important;min-width:0!important;}',
      '.spx-profile-page .u-profile,.spx-profile-page #u-content>.c{grid-column:1/-1!important;}',
      '.spx-site-shell:not(.spx-reader) .bdbA,.spx-site-shell:not(.spx-reader) .t,.spx-site-shell:not(.spx-reader) .t3,.spx-site-shell:not(.spx-reader) .t5{'+CSS_BOX+''+CSS_PAGE_W+'margin:16px auto 14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:var(--spx-radius)!important;box-shadow:var(--spx-shadow-card)!important;overflow:hidden!important;}',
      '.spx-site-shell:not(.spx-reader) .t,.spx-site-shell:not(.spx-reader) .t3,.spx-site-shell:not(.spx-reader) .t5{display:block!important;}',
      '.spx-site-shell:not(.spx-reader) .tr1,.spx-site-shell:not(.spx-reader) .tr2,.spx-site-shell:not(.spx-reader) .tr3{font-size:14px!important;line-height:1.6!important;}',
      '.spx-site-shell:not(.spx-reader) .tr3 td,.spx-site-shell:not(.spx-reader) .tr1 td,.spx-site-shell:not(.spx-reader) th,.spx-site-shell:not(.spx-reader) td{padding:8px 10px!important;}',
      '.spx-task-page #wrapA,.spx-task-page #main{'+CSS_BOX+'max-width:none!important;background:var(--spx-page-bg)!important;}',
      '.spx-task-page #main{display:block!important;width:calc(100vw - 40px)!important;max-width:1480px!important;margin:16px auto 42px!important;padding:0!important;overflow:hidden!important;}',
      '.spx-task-page #main>.bdbA,.spx-task-page #main>.spx-task-breadcrumb-block{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;}',
      '.spx-task-page #main>.t,.spx-task-page #main>.t3,.spx-task-page #main>.t5{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;}',
      '.spx-task-page #main>.t table,.spx-task-page #main>.t3 table,.spx-task-page #main>.t5 table{max-width:100%!important;}',
      '@media(max-width:900px){.spx-task-page #main{width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}}',
      '.spx-site-shell #set-wrap{display:grid!important;grid-template-columns:minmax(190px,240px) minmax(0,1fr)!important;gap:16px!important;'+CSS_BOX+'width:100%!important;max-width:none!important;margin:16px auto 42px!important;padding:0!important;background:transparent!important;line-height:1.6!important;}',
      '.spx-site-shell #set-side,.spx-site-shell #set-content{'+CSS_BOX+'float:none!important;width:auto!important;min-width:0!important;margin:0!important;padding:0!important;background:transparent!important;}',
      '.spx-site-shell #set-side-wrap,.spx-site-shell #set-content-wrap{'+CSS_BOX+'width:100%!important;min-width:0!important;margin:0!important;padding:14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;overflow:visible!important;}',
      '.spx-site-shell #set-side .set-h2{'+CSS_BOX+'margin:0 0 10px!important;padding:0 2px 10px!important;border-bottom:1px solid #e2e8f0!important;color:#0f172a!important;font-size:16px!important;line-height:1.35!important;font-weight:800!important;}',
      '.spx-site-shell #set-menu{'+CSS_BOX+'display:flex!important;flex-direction:column!important;gap:6px!important;width:100%!important;margin:0!important;padding:0!important;list-style:none!important;background:transparent!important;}',
      '.spx-site-shell #set-menu>li{display:block!important;width:100%!important;margin:0!important;padding:0!important;list-style:none!important;background:transparent!important;}',
      '.spx-site-shell #set-menu>li>a{display:flex!important;align-items:center!important;min-height:34px!important;'+CSS_BOX+'width:100%!important;padding:0 12px!important;margin:0!important;border:1px solid #e2e8f0!important;border-radius:8px!important;background:#f8fafc!important;color:#075985!important;font-size:14px!important;font-weight:700!important;line-height:1.25!important;text-decoration:none!important;}',
      '.spx-site-shell #set-menu>li.current>a,.spx-site-shell #set-menu>li>a:hover{background:#e0f2fe!important;border-color:#7dd3fc!important;color:#0369a1!important;text-decoration:none!important;}',
      '.spx-profile-page.spx-account-tabs-ready #set-side{'+CSS_HIDE+'}',
      '.spx-profile-page.spx-account-tabs-ready #set-wrap{grid-template-columns:minmax(0,1fr)!important;}',
      '.spx-site-shell #set-content{'+CSS_BOX+'display:block!important;overflow:visible!important;}',
      '.spx-site-shell #set-content table{'+CSS_BOX+'width:100%!important;max-width:100%!important;}',
      '.spx-site-shell #set-content .set-tab-table{'+CSS_BOX+'width:100%!important;margin:0 0 12px!important;overflow:auto!important;}',
      '.spx-site-shell #set-content .set-tab-box{'+CSS_BOX+'width:100%!important;overflow:visible!important;}',
    ].concat(
      getInjectedContentLayoutStyleRules(),
      getInjectedWidgetStyleRules(),
      getInjectedThemeOverrideStyleRules(),
      getInjectedPreviewParityStyleRules()
    ).join('\n');
  }

  function getInjectedContentLayoutStyleRules() {
    return [
      '.spx-reader,.spx-reader body{background:#f4f6f8!important;background-image:none!important;color:#263238!important;font:15px/1.7 '+SPX_FONT+'!important;}',
      '.spx-reader #wrapA,.spx-reader #main,.spx-reader #content{background:#f4f6f8!important;background-image:none!important;}',
      '.spx-reader #main>br,.spx-reader #content>br{'+CSS_HIDE+'}',
      '.spx-reader a{color:#075985!important;text-decoration:none!important;}',
      '.spx-reader a:hover{text-decoration:underline!important;}',
      '.spx-reader #wrapA{'+CSS_BOX+''+CSS_PAGE_W+'max-width:none!important;margin:0 auto!important;}',
      '.spx-reader #main,.spx-reader #content{font-size:15px!important;line-height:1.65!important;}',
      '.spx-reader .t,.spx-reader .t3,.spx-reader .t5,.spx-reader .tr1,.spx-reader .tr2,.spx-reader .tr3{font-size:15px!important;}',
      '.spx-reader .tr3 td,.spx-reader .tr1 td{padding-top:8px!important;padding-bottom:8px!important;}',
      '.spx-reader td[id^="td_"]{font-size:15px!important;line-height:1.65!important;}',
      '.spx-reader td[id^="td_"] a[id^="a_ajax_"]{font-size:16px!important;line-height:1.65!important;font-weight:600!important;}',
      '.spx-reader td[id^="td_"] .s8{display:inline-block;margin-right:6px;padding:1px 6px;border-radius:4px;background:#e8f3ff;color:#075985!important;font-size:13px!important;}',
      '.spx-reader table.js-post{'+CSS_BOX+''+CSS_PAGE_W+'max-width:none!important;margin:14px auto!important;background:#fff!important;border:1px solid #d9e2ec!important;border-radius:var(--spx-radius)!important;box-shadow:0 3px 12px rgba(15,23,42,.06)!important;overflow:hidden!important;table-layout:fixed!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_two{'+CSS_BOX+'width:128px!important;max-width:128px!important;min-width:128px!important;padding:18px 8px!important;overflow:hidden!important;vertical-align:top!important;text-align:center!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_two .user-pic{display:block!important;width:96px!important;height:96px!important;max-height:96px!important;margin:0 auto 10px!important;padding:0!important;text-align:center!important;overflow:hidden!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_two .user-pic img,.spx-reader table.js-post>tbody>tr>th.r_two>img{display:block!important;width:96px!important;max-width:96px!important;height:auto!important;margin:0 auto!important;}',
      '.spx-reader table.js-post>tbody>tr>th.r_two>a[href*="u.php"],.spx-reader table.js-post>tbody>tr>th.r_two .readprofile>a[href],.spx-reader table.js-post>tbody>tr>th.r_two .user-info>a[href]{display:block!important;margin:0 auto!important;padding:0!important;color:var(--spx-link)!important;font-size:16px!important;font-weight:800!important;line-height:1.35!important;text-align:center!important;word-break:break-word!important;}',
      '.spx-reader #content>table:not(.js-post):empty,.spx-reader #main>table:not(.js-post):empty,.spx-reader #content>table:not(.js-post) td:empty,.spx-reader #main>table:not(.js-post) td:empty{'+CSS_HIDE+'}',
      '.spx-reader #content>table:not(.js-post),.spx-reader #main>table:not(.js-post){border:0!important;box-shadow:none!important;background:transparent!important;}',
      '.spx-reader [data-spx-read-separator-hidden="1"]{'+CSS_HIDE+'}',
      '.spx-reader table.js-post>tbody>tr>th.r_one{'+CSS_BOX+'width:auto!important;min-width:0!important;}',
      '.spx-reader.spx-compact-read table.js-post>tbody>tr>th.r_two,.spx-reader.spx-hide-profile table.js-post>tbody>tr>th.r_two{width:112px!important;max-width:112px!important;min-width:112px!important;}',
      '.spx-reader table.js-post td{font-size:15px!important;line-height:1.75!important;}',
      '.spx-reader .h1,.spx-reader [id^="subject_"]{font-size:18px!important;line-height:1.55!important;font-weight:700!important;color:#111827!important;}',
      '.spx-reader .tpc_content{'+CSS_BOX+'max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:14px 18px 20px!important;font-size:16px!important;line-height:1.78!important;letter-spacing:0!important;color:#1f2937!important;word-break:break-word!important;}',
      '.spx-reader .tpc_content #read_tpc,.spx-reader .tpc_content>.f14{font-size:16px!important;line-height:1.78!important;}',
      '.spx-reader .tpc_content br{line-height:1.75!important;}',
      '.spx-reader .tpc_content img{max-width:100%!important;height:auto!important;border-radius:4px!important;}',
      '.spx-reader .tiptop,.spx-reader .readbot{max-width:var(--spx-reader-line)!important;margin-left:auto!important;margin-right:auto!important;color:#64748b!important;}',
      '.spx-reader .signature,.spx-reader .sigline{max-width:var(--spx-reader-line)!important;margin-left:auto!important;margin-right:auto!important;color:#64748b!important;font-size:13px!important;}',
      '.spx-home-dashboard,.spx-home-dashboard body{width:100%!important;min-width:0!important;overflow-x:hidden!important;background:var(--spx-page-bg)!important;color:#172033!important;}',
      '.spx-home-dashboard #wrapA,.spx-home-dashboard #main{'+CSS_BOX+'width:100vw!important;max-width:none!important;margin:0!important;padding:0!important;background:var(--spx-page-bg)!important;border:0!important;}',
      '.spx-home-dashboard #content{'+CSS_BOX+''+CSS_PAGE_W+'margin:16px auto 42px!important;display:block!important;background:transparent!important;}',
      '.spx-home-dashboard #spx-home-grid{display:grid!important;grid-template-columns:repeat(12,minmax(0,1fr))!important;gap:14px!important;min-width:0!important;}',
      '.spx-home-dashboard #toptool,.spx-home-dashboard #footer,.spx-home-dashboard .footer,.spx-home-dashboard #cate_info{'+CSS_HIDE+'}',
      '.spx-home-dashboard #header,.spx-home-dashboard #mainNav,.spx-home-dashboard #infobox,.spx-home-dashboard #notice{'+CSS_BOX+''+CSS_PAGE_W+'margin-left:auto!important;margin-right:auto!important;}',
      '.spx-home-dashboard #header{margin-top:10px!important;}',
      '.spx-home-dashboard #mainNav{position:sticky!important;top:0!important;z-index:9990!important;border-radius:8px!important;box-shadow:0 4px 16px rgba(15,23,42,.08)!important;overflow:visible!important;}',
      '.spx-home-dashboard #notice{display:block!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;padding:10px 14px!important;box-shadow:0 4px 14px rgba(15,23,42,.05)!important;}',
      '.spx-home-dashboard #notice table,.spx-home-dashboard #notice tbody,.spx-home-dashboard #notice tr{display:block!important;width:100%!important;}',
      '.spx-home-dashboard #notice td{display:block!important;width:auto!important;padding:4px 0!important;}',
      '.spx-home-dashboard .spx-home-quick{'+CSS_HIDE+'}',
      '.spx-home-dashboard .spx-home-quick a{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;min-height:42px!important;padding:0 12px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;color:#0f172a!important;text-decoration:none!important;font-weight:700!important;box-shadow:0 3px 12px rgba(15,23,42,.05)!important;}',
      '.spx-home-dashboard .spx-home-quick a span{font-size:12px!important;color:#64748b!important;font-weight:500!important;}',
      '.spx-home-dashboard .spx-home-module{grid-column:span 6!important;'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;overflow:hidden!important;}',
      '.spx-home-dashboard .spx-home-module[data-spx-large="1"]{grid-column:span 12!important;}',
      '.spx-home-dashboard .spx-home-module>h2,.spx-home-dashboard .spx-home-module .h{display:flex!important;align-items:center!important;justify-content:space-between!important;min-height:38px!important;padding:0 14px!important;margin:0!important;background:#f8fafc!important;border-bottom:1px solid #e2e8f0!important;color:#0f172a!important;font-size:15px!important;font-weight:800!important;}',
      '.spx-home-dashboard .spx-home-module table,.spx-home-dashboard .spx-home-module tbody{display:block!important;width:100%!important;border:0!important;background:transparent!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2{'+CSS_HIDE+'}',
      '.spx-home-dashboard .spx-home-module tr.tr3{display:grid!important;grid-template-columns:minmax(220px,1.15fr) 120px minmax(260px,1fr)!important;gap:10px!important;align-items:center!important;margin:0!important;padding:10px 14px!important;border-bottom:1px solid #edf2f7!important;background:#fff!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3:hover{background:#f8fbff!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3:last-child{border-bottom:0!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3>td{display:block!important;width:auto!important;padding:0!important;border:0!important;background:transparent!important;font-size:13px!important;line-height:1.45!important;color:#475569!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3>td:first-child{'+CSS_HIDE+'}',
      '.spx-home-dashboard .spx-home-module [id^="fn_"] a,.spx-home-dashboard .spx-home-module [id^="fn_"]{font-size:15px!important;font-weight:800!important;color:#0f172a!important;line-height:1.4!important;}',
      '.spx-home-dashboard .spx-home-module [id^="desc_"]{margin-top:4px!important;color:#64748b!important;font-size:12px!important;}',
      '.spx-home-dashboard .spx-home-hot [id^="fn_"] a,.spx-home-dashboard .spx-home-hot [id^="fn_"]{color:#075985!important;}',
      '.spx-home-dashboard .spx-home-badge{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:26px!important;height:20px!important;margin-left:6px!important;padding:0 7px!important;border-radius:999px!important;background:#e0f2fe!important;color:#0369a1!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-home-dashboard .spx-home-collapse{border:0!important;background:transparent!important;color:#64748b!important;font-size:12px!important;cursor:pointer!important;}',
      '.spx-forum-dashboard,.spx-forum-dashboard body{width:100%!important;min-width:0!important;overflow-x:hidden!important;background:var(--spx-page-bg)!important;color:#172033!important;}',
      '.spx-forum-dashboard #wrapA,.spx-forum-dashboard #main{'+CSS_BOX+'width:100vw!important;max-width:none!important;margin:0!important;padding:0!important;background:var(--spx-page-bg)!important;border:0!important;}',
      '.spx-forum-dashboard #content{'+CSS_BOX+''+CSS_PAGE_W+'margin:16px auto 42px!important;display:block!important;background:transparent!important;}',
      '.spx-forum-dashboard #content .t{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;overflow:hidden!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table table,.spx-forum-dashboard #content .t.spx-thread-list-table tbody{display:block!important;width:100%!important;border:0!important;background:transparent!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2{'+CSS_HIDE+'}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3{display:grid!important;grid-template-columns:minmax(0,1fr) 96px 96px minmax(220px,.8fr)!important;gap:10px!important;align-items:center!important;margin:0!important;padding:10px 14px!important;border-bottom:1px solid #edf2f7!important;background:#fff!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3:hover{background:#f8fbff!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3:last-child{border-bottom:0!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td{display:block!important;width:auto!important;padding:0!important;border:0!important;background:transparent!important;font-size:13px!important;line-height:1.45!important;color:#475569!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td:first-child:not([id^="td_"]){'+CSS_HIDE+'}',
      '.spx-forum-dashboard td[id^="td_"]{font-size:14px!important;line-height:1.6!important;color:#475569!important;}',
      '.spx-forum-dashboard td[id^="td_"] a[id^="a_ajax_"],.spx-forum-dashboard [id^="td_"] a[href*="read.php"]{font-size:17px!important;font-weight:800!important;color:#075985!important;line-height:1.45!important;}',
      '.spx-forum-dashboard .gonggao{'+CSS_HIDE+'}',
      '.spx-forum-dashboard .spx-thread-tools{margin-left:8px!important;}',
      '.spx-immersive-read,.spx-immersive-read body{width:100%!important;min-width:0!important;overflow-x:hidden!important;background:var(--spx-page-bg)!important;}',
      '.spx-immersive-read #toptool,.spx-immersive-read #banner,.spx-immersive-read #footer,.spx-immersive-read .footer,.spx-immersive-read #bottom,.spx-immersive-read #music,.spx-immersive-read #readlog,.spx-immersive-read #threadlog{'+CSS_HIDE+'}',
      '.spx-immersive-read #header,.spx-immersive-read #mainNav,.spx-immersive-read #breadcrumbs,.spx-immersive-read .crumbs-item{'+CSS_BOX+''+CSS_PAGE_W+'margin-left:auto!important;margin-right:auto!important;}',
      '.spx-immersive-read #header{margin-top:10px!important;}',
      '.spx-immersive-read #mainNav{position:sticky!important;top:0!important;z-index:9990!important;border-radius:8px!important;box-shadow:0 4px 16px rgba(15,23,42,.08)!important;overflow:visible!important;}',
      '.spx-immersive-read .bdbA{display:block!important;'+CSS_BOX+''+CSS_PAGE_W+'margin:10px auto 0!important;padding:0!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:var(--spx-radius)!important;box-shadow:0 4px 14px rgba(15,23,42,.05)!important;overflow:visible!important;}',
      '.spx-immersive-read #breadcrumbs{display:flex!important;align-items:center!important;flex-wrap:wrap!important;gap:6px!important;width:100%!important;min-height:36px!important;margin:0!important;padding:7px 12px!important;overflow:visible!important;color:#64748b!important;font-size:13px!important;line-height:1.45!important;background:transparent!important;}',
      '.spx-immersive-read #breadcrumbs .crumbs-item{display:inline-flex!important;align-items:center!important;width:auto!important;max-width:100%!important;margin:0!important;overflow:visible!important;white-space:nowrap!important;color:#64748b!important;}',
      '.spx-immersive-read #breadcrumbs .crumbs-item.current{min-width:0!important;white-space:normal!important;color:#0f172a!important;font-weight:700!important;}',
      '.spx-immersive-read #wrapA,.spx-immersive-read #main,.spx-immersive-read #content{'+CSS_BOX+'width:100vw!important;max-width:none!important;margin:0!important;padding:0!important;background:var(--spx-page-bg)!important;border:0!important;}',
      '.spx-immersive-read #content>table:not(.js-post),.spx-immersive-read #main>table:not(.js-post){'+CSS_PAGE_W+'margin:12px auto!important;}',
      '.spx-immersive-read table.js-post{'+CSS_BOX+''+CSS_PAGE_W+'max-width:none!important;margin:18px auto!important;border:1px solid #d6dee8!important;border-radius:var(--spx-radius)!important;background:#fff!important;box-shadow:0 8px 26px rgba(15,23,42,.08)!important;}',
      '.spx-immersive-read table.js-post>tbody>tr>td:first-child{'+CSS_HIDE+'}',
      '.spx-immersive-read table.js-post>tbody>tr>td{display:block!important;'+CSS_BOX+'width:100%!important;padding:0!important;border:0!important;background:#fff!important;}',
      '.spx-immersive-read .spx-post-tools{'+CSS_BOX+'max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:8px 18px 0!important;color:#94a3b8!important;font-size:12px!important;opacity:.62!important;}',
      '.spx-immersive-read .spx-post-tools:hover{opacity:1!important;}',
      '.spx-immersive-read .spx-post-tools span{font-size:12px!important;font-weight:400!important;color:#94a3b8!important;}',
      '.spx-immersive-read .spx-post-tools button{font-size:12px!important;color:#94a3b8!important;border-color:#e2e8f0!important;background:#f8fafc!important;padding:1px 7px!important;}',
      '.spx-immersive-read .spx-preview-panel{'+CSS_BOX+'max-width:100%!important;margin:0 auto 14px!important;padding:12px 18px 16px!important;background:#fff!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:0 6px 18px rgba(15,23,42,.06)!important;}',
      '.spx-immersive-read .spx-preview-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 10px;font-size:13px!important;color:#64748b!important;}',
      '.spx-immersive-read .spx-preview-header strong{font-size:14px!important;color:#0f172a!important;}',
      '.spx-immersive-read .spx-preview-summary{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
      '.spx-immersive-read .spx-preview-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:6px;}',
      '.spx-immersive-read .spx-preview-actions button{height:26px;margin:0;padding:0 8px;border:1px solid #cbd5e1;border-radius:6px;background:#f8fafc;color:#334155;font-size:12px;line-height:1;cursor:pointer;}',
      '.spx-immersive-read .spx-preview-actions button:hover,.spx-immersive-read .spx-preview-actions button:focus-visible{border-color:#38bdf8;background:#e0f2fe;color:#075985;outline:none;}',
      '.spx-immersive-read .spx-preview-actions button[aria-pressed="true"]{border-color:#0284c7;background:#0284c7;color:#fff;}',
      '.spx-immersive-read .spx-preview-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;}',
      '.spx-immersive-read .spx-preview-item{display:block;overflow:hidden;border:1px solid #e2e8f0;border-radius:8px;background:#f8fafc;text-decoration:none;}',
      '.spx-immersive-read .spx-preview-item img{display:block;width:100%;height:180px;object-fit:cover;background:#fff;}',
      '.spx-immersive-read .spx-preview-item span{display:block;padding:6px 8px;font-size:12px;line-height:1.35;color:#475569;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.spx-immersive-read .spx-preview-load-more{display:block;width:100%;height:32px;margin:10px 0 0;border:1px solid #cbd5e1;border-radius:7px;background:#fff;color:#334155;font-size:12px;cursor:pointer;}',
      '.spx-immersive-read .spx-preview-load-more:hover,.spx-immersive-read .spx-preview-load-more:focus-visible{border-color:#38bdf8;background:#e0f2fe;color:#075985;outline:none;}',
      '.spx-immersive-read .spx-preview-load-more[hidden]{'+CSS_HIDE+'}',
      '.spx-immersive-read .spx-preview-empty{padding:10px 2px;color:#94a3b8;font-size:13px;}',
      '.spx-immersive-read .spx-preview-source{'+CSS_HIDE+'}',
      '.spx-immersive-read .h1,.spx-immersive-read [id^="subject_"]{display:block!important;'+CSS_BOX+'max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:22px 18px 8px!important;font-size:21px!important;line-height:1.45!important;color:#111827!important;}',
      '.spx-immersive-read .tpc_content{'+CSS_BOX+'max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:18px 34px 34px!important;font-size:16px!important;line-height:1.78!important;color:#172033!important;background:#fff!important;}',
      '.spx-immersive-read .tpc_content #read_tpc,.spx-immersive-read .tpc_content>.f14{font-size:16px!important;line-height:1.78!important;}',
      '.spx-immersive-read .tiptop,.spx-immersive-read .readbot,.spx-immersive-read .signature,.spx-immersive-read .sigline{max-width:var(--spx-reader-line)!important;margin-left:auto!important;margin-right:auto!important;padding-left:18px!important;padding-right:18px!important;color:#94a3b8!important;font-size:12px!important;opacity:.58!important;}',
      '.spx-immersive-read textarea,.spx-immersive-read input[type="text"]{font-size:16px!important;}',
      '.spx-reader .spx-post-body-split,.spx-immersive-read .spx-post-body-split{'+CSS_BOX+'display:grid!important;grid-template-columns:minmax(320px,clamp(360px,34vw,560px)) minmax(0,1fr)!important;gap:clamp(14px,2vw,24px)!important;align-items:start!important;max-width:100%!important;margin:0 auto!important;padding:18px clamp(18px,3vw,36px) 36px!important;background:#fff!important;}',
      '.spx-reader .spx-post-body-split .tpc_content,.spx-immersive-read .spx-post-body-split .tpc_content{'+CSS_BOX+'width:100%!important;max-width:var(--spx-reader-line)!important;margin:0 auto!important;padding:0!important;background:transparent!important;}',
      '.spx-reader .spx-preview-panel,.spx-immersive-read .spx-preview-panel{'+CSS_BOX+'max-width:none!important;margin:0!important;padding:10px!important;background:#f8fafc!important;border:1px solid #d7e1eb!important;border-radius:8px!important;box-shadow:none!important;max-height:min(72vh,680px)!important;overflow:auto!important;}',
      '.spx-reader .spx-preview-header,.spx-immersive-read .spx-preview-header{position:sticky!important;top:0!important;z-index:3!important;display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;margin:0 0 10px!important;background:inherit!important;font-size:13px!important;color:#64748b!important;}',
      '.spx-reader .spx-preview-header strong,.spx-immersive-read .spx-preview-header strong{font-size:14px!important;color:#0f172a!important;}',
      '.spx-reader .spx-preview-summary,.spx-immersive-read .spx-preview-summary{'+CSS_ELLIPSIS+'}',
      '.spx-reader .spx-preview-actions,.spx-immersive-read .spx-preview-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;flex-wrap:wrap!important;gap:6px!important;}',
      '.spx-reader .spx-preview-actions button,.spx-immersive-read .spx-preview-actions button{'+CSS_BOX+'height:26px!important;margin:0!important;padding:0 8px!important;border:1px solid #cbd5e1!important;border-radius:6px!important;background:#f8fafc!important;color:#334155!important;font-size:12px!important;line-height:1!important;cursor:pointer!important;}',
      '.spx-reader .spx-preview-actions button:hover,.spx-reader .spx-preview-actions button:focus-visible,.spx-immersive-read .spx-preview-actions button:hover,.spx-immersive-read .spx-preview-actions button:focus-visible{border-color:#38bdf8!important;background:#e0f2fe!important;color:#075985!important;outline:none!important;}',
      '.spx-reader .spx-preview-actions button[aria-pressed="true"],.spx-immersive-read .spx-preview-actions button[aria-pressed="true"]{border-color:#0284c7!important;background:#0284c7!important;color:#fff!important;}',
      '.spx-reader .spx-preview-grid,.spx-immersive-read .spx-preview-grid{display:grid!important;grid-template-columns:repeat(2,minmax(0,1fr))!important;gap:8px!important;}',
      '.spx-reader .spx-preview-item,.spx-immersive-read .spx-preview-item{display:block!important;overflow:hidden!important;border:1px solid #e2e8f0!important;border-radius:8px!important;background:#fff!important;text-decoration:none!important;}',
      '.spx-reader .spx-preview-item img,.spx-immersive-read .spx-preview-item img{display:block!important;width:100%!important;height:190px!important;object-fit:cover!important;background:#fff!important;}',
      '.spx-reader .spx-preview-item img[data-spx-preview-ready="0"],.spx-immersive-read .spx-preview-item img[data-spx-preview-ready="0"],.spx-preview-images img[data-spx-preview-ready="0"],.spx-preview-lightbox-image[data-spx-preview-ready="0"],.spx-preview-lightbox-thumb img[data-spx-preview-ready="0"]{opacity:0!important;}',
      '.spx-reader .spx-preview-item img[data-spx-preview-ready="1"],.spx-immersive-read .spx-preview-item img[data-spx-preview-ready="1"],.spx-preview-images img[data-spx-preview-ready="1"],.spx-preview-lightbox-image[data-spx-preview-ready="1"],.spx-preview-lightbox-thumb img[data-spx-preview-ready="1"]{opacity:1!important;transition:opacity .16s ease!important;}',
      '.spx-reader .spx-preview-item .spx-preview-hover-image,.spx-immersive-read .spx-preview-item .spx-preview-hover-image{'+CSS_HIDE+'position:fixed!important;left:50%!important;top:50%!important;transform:translate(-50%,-50%)!important;z-index:100002!important;width:auto!important;height:auto!important;max-width:min(64vw,900px)!important;max-height:min(78vh,760px)!important;object-fit:contain!important;padding:6px!important;background:#fff!important;border:1px solid #cbd5e1!important;border-radius:10px!important;box-shadow:0 18px 52px rgba(15,23,42,.28)!important;pointer-events:none!important;transition:opacity .18s ease!important;}',
      '.spx-reader .spx-preview-item:hover .spx-preview-hover-image,.spx-immersive-read .spx-preview-item:hover .spx-preview-hover-image{display:block!important;opacity:.92!important;}',
      '.spx-reader .spx-preview-item:hover .spx-preview-hover-image[data-spx-preview-ready="0"],.spx-immersive-read .spx-preview-item:hover .spx-preview-hover-image[data-spx-preview-ready="0"]{opacity:0!important;}',
      '.spx-reader .spx-preview-item span,.spx-immersive-read .spx-preview-item span{display:block!important;padding:5px 7px!important;font-size:12px!important;line-height:1.35!important;color:#475569!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-reader .spx-preview-load-more,.spx-immersive-read .spx-preview-load-more{'+CSS_BOX+'display:block!important;width:100%!important;height:32px!important;margin:10px 0 0!important;border:1px solid #cbd5e1!important;border-radius:7px!important;background:#fff!important;color:#334155!important;font-size:12px!important;cursor:pointer!important;}',
      '.spx-reader .spx-preview-load-more:hover,.spx-reader .spx-preview-load-more:focus-visible,.spx-immersive-read .spx-preview-load-more:hover,.spx-immersive-read .spx-preview-load-more:focus-visible{border-color:#38bdf8!important;background:#e0f2fe!important;color:#075985!important;outline:none!important;}',
      '.spx-reader .spx-preview-load-more[hidden],.spx-immersive-read .spx-preview-load-more[hidden]{'+CSS_HIDE+'}',
      '.spx-preview-download{'+CSS_BOX+'display:grid!important;gap:7px!important;margin:0 0 10px!important;padding:8px 9px!important;border:1px solid var(--spx-line-soft)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;}',
      '.spx-preview-download[hidden]{'+CSS_HIDE+'}.spx-preview-download-top{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;color:var(--spx-strong)!important;font-size:12px!important;font-weight:900!important;}.spx-preview-download-percent{flex:none!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:850!important;}',
      '.spx-preview-download-track{height:8px!important;overflow:hidden!important;border-radius:999px!important;background:var(--spx-line-soft)!important;}.spx-preview-download-progress{display:block!important;width:0;height:100%!important;border-radius:inherit!important;background:linear-gradient(90deg,var(--spx-accent),var(--spx-link))!important;transition:width .16s ease!important;}',
      '.spx-preview-download-compact{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;}.spx-preview-download-badges{display:flex!important;flex-wrap:wrap!important;gap:5px!important;min-width:0!important;}.spx-preview-download-badge{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:22px!important;padding:0 8px!important;border-radius:999px!important;background:#e0f2fe!important;color:#075985!important;font-size:11px!important;font-weight:900!important;white-space:nowrap!important;}.spx-preview-download-badge.spx-ok{background:var(--spx-ok-soft,#dcfce7)!important;color:var(--spx-ok,#15803d)!important;}.spx-preview-download-badge.spx-fail{background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}.spx-preview-download-badge.spx-zip{background:#ffedd5!important;color:var(--spx-warn)!important;}',
      '.spx-preview-download-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;flex-wrap:wrap!important;gap:6px!important;}.spx-preview-download-actions button{height:24px!important;margin:0!important;padding:0 8px!important;border:1px solid var(--spx-line)!important;border-radius:7px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:11px!important;font-weight:850!important;line-height:1!important;cursor:pointer!important;}.spx-preview-download-actions button:hover,.spx-preview-download-actions button:focus-visible{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;outline:none!important;}.spx-preview-download-actions .spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;}.spx-preview-download-actions button[disabled]{opacity:.58!important;cursor:not-allowed!important;}',
      '.spx-preview-download-detail{margin:0!important;border:1px solid var(--spx-line-soft)!important;border-radius:8px!important;background:var(--spx-panel)!important;}.spx-preview-download-detail summary{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;min-height:32px!important;padding:0 9px!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;list-style:none!important;}.spx-preview-download-detail summary::-webkit-details-marker{'+CSS_HIDE+'}.spx-preview-download-detail summary:after{content:"展开";flex:none!important;color:var(--spx-link)!important;font-size:11px!important;font-weight:900!important;}.spx-preview-download-detail[open] summary{border-bottom:1px solid var(--spx-line-soft)!important;}.spx-preview-download-detail[open] summary:after{content:"收起";}.spx-preview-download-detail-text{'+CSS_ELLIPSIS+'}',
      '.spx-preview-download-queue{display:grid!important;gap:7px!important;padding:8px!important;}.spx-preview-download-row{display:grid!important;grid-template-columns:minmax(0,1fr) auto!important;gap:8px!important;align-items:center!important;padding:7px!important;border:1px solid var(--spx-line-soft)!important;border-radius:8px!important;background:var(--spx-panel-muted)!important;}.spx-preview-download-name{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;margin:0 0 5px!important;color:var(--spx-strong)!important;font-size:12px!important;font-weight:900!important;}.spx-preview-download-name b{'+CSS_ELLIPSIS+'}.spx-preview-download-name em{flex:none!important;color:var(--spx-muted)!important;font-style:normal!important;font-size:11px!important;font-weight:850!important;}.spx-preview-download-meta{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;margin-top:5px!important;color:var(--spx-muted)!important;font-size:11px!important;font-weight:800!important;}.spx-preview-download-row button{height:24px!important;padding:0 8px!important;border:1px solid #fecaca!important;border-radius:7px!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;font-size:11px!important;font-weight:850!important;cursor:pointer!important;}.spx-preview-download-row button[disabled]{color:var(--spx-muted)!important;background:var(--spx-panel-muted)!important;border-color:var(--spx-line-soft)!important;cursor:not-allowed!important;}',
      '.spx-preview-download-report{display:grid!important;gap:7px!important;padding:9px!important;border:1px solid #fed7aa!important;border-radius:8px!important;background:#fff7ed!important;color:var(--spx-warn)!important;font-size:12px!important;font-weight:850!important;}.spx-preview-download-report strong{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;color:#7c2d12!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-reader .spx-preview-source,.spx-immersive-read .spx-preview-source{'+CSS_HIDE+'}',
      '.spx-preview-panel.spx-preview-drawer{position:fixed!important;right:14px!important;top:86px!important;bottom:18px!important;z-index:99970!important;'+CSS_BOX+'width:min(clamp(360px,28vw,500px),calc(100vw - 28px))!important;max-width:none!important;max-height:none!important;margin:0!important;padding:10px!important;overflow:auto!important;background:var(--spx-panel-muted)!important;border:1px solid #cbd5e1!important;border-radius:var(--spx-radius-lg)!important;box-shadow:var(--spx-shadow-popover)!important;}',
      '.spx-preview-panel.spx-preview-drawer .spx-preview-grid,.spx-preview-panel.spx-preview-masonry .spx-preview-grid{display:block!important;column-width:150px!important;column-gap:8px!important;}',
      '.spx-preview-panel.spx-preview-drawer .spx-preview-item,.spx-preview-panel.spx-preview-masonry .spx-preview-item{display:block!important;break-inside:avoid!important;margin:0 0 8px!important;background:#fff!important;}',
      '.spx-preview-panel.spx-preview-drawer .spx-preview-item img:not(.spx-preview-hover-image),.spx-preview-panel.spx-preview-masonry .spx-preview-item img:not(.spx-preview-hover-image){width:100%!important;height:auto!important;max-height:none!important;object-fit:contain!important;}',
      '.spx-preview-panel.spx-preview-drawer .spx-preview-drawer-tab{'+CSS_HIDE+'}',
      '.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed{top:120px!important;bottom:auto!important;width:48px!important;height:132px!important;padding:0!important;overflow:hidden!important;border-radius:10px 0 0 10px!important;}',
      '.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-header,.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-download,.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-grid,.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-load-more{'+CSS_HIDE+'}',
      '.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed .spx-preview-drawer-tab{display:flex!important;align-items:center!important;justify-content:center!important;width:100%!important;height:100%!important;border:0!important;border-radius:0!important;background:var(--spx-accent)!important;color:#fff!important;font-weight:900!important;writing-mode:vertical-rl!important;letter-spacing:.08em!important;cursor:pointer!important;}',
      '.spx-preview-lightbox{position:fixed!important;inset:0!important;z-index:100010!important;'+CSS_BOX+'display:flex!important;padding:18px!important;background:rgba(2,6,23,.9)!important;backdrop-filter:blur(5px)!important;color:#e2e8f0!important;font:13px/1.45 '+SPX_FONT+'!important;}',
      '.spx-preview-lightbox-shell{'+CSS_BOX+'display:flex!important;flex:1!important;min-width:0!important;min-height:0!important;flex-direction:column!important;overflow:hidden!important;border:1px solid rgba(148,163,184,.34)!important;border-radius:12px!important;background:#020617!important;box-shadow:0 24px 80px rgba(0,0,0,.5)!important;}',
      '.spx-preview-lightbox-toolbar{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;min-height:48px!important;padding:8px 10px 8px 14px!important;border-bottom:1px solid rgba(148,163,184,.22)!important;background:#0f172a!important;}',
      '.spx-preview-lightbox-counter{font-weight:700!important;color:#f8fafc!important;white-space:nowrap!important;}',
      '.spx-preview-lightbox-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;flex-wrap:wrap!important;gap:6px!important;}',
      '.spx-preview-lightbox button{'+CSS_BOX+'min-width:34px!important;height:32px!important;margin:0!important;padding:0 10px!important;border:1px solid #334155!important;border-radius:7px!important;background:#1e293b!important;color:#e2e8f0!important;cursor:pointer!important;font:600 13px/1 '+SPX_FONT+'!important;}',
      '.spx-preview-lightbox button:hover,.spx-preview-lightbox button:focus-visible{border-color:#38bdf8!important;background:#0c4a6e!important;color:#fff!important;outline:none!important;}',
      '.spx-preview-lightbox-zoom{display:inline-flex!important;min-width:58px!important;justify-content:center!important;color:#cbd5e1!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-preview-lightbox-stage{position:relative!important;flex:1!important;min-height:0!important;overflow:hidden!important;background:#020617!important;}',
      '.spx-preview-lightbox-viewport{position:absolute!important;inset:0!important;overflow:auto!important;overscroll-behavior:contain!important;}',
      '.spx-preview-lightbox-canvas{'+CSS_BOX+'display:flex!important;align-items:center!important;justify-content:center!important;min-width:100%!important;min-height:100%!important;padding:34px 78px!important;}',
      '.spx-preview-lightbox-image{display:block!important;flex:none!important;max-width:none!important;max-height:none!important;object-fit:contain!important;background:#fff!important;box-shadow:0 16px 50px rgba(0,0,0,.45)!important;}',
      '.spx-preview-lightbox-nav{position:absolute!important;top:50%!important;z-index:2!important;width:46px!important;height:64px!important;padding:0!important;transform:translateY(-50%)!important;border-color:rgba(148,163,184,.35)!important;background:rgba(15,23,42,.78)!important;font-size:30px!important;}',
      '.spx-preview-lightbox-prev{left:14px!important;}',
      '.spx-preview-lightbox-next{right:14px!important;}',
      '.spx-preview-lightbox-caption{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;min-height:38px!important;padding:7px 14px!important;border-top:1px solid rgba(148,163,184,.22)!important;background:#0f172a!important;color:#94a3b8!important;}',
      '.spx-preview-lightbox-url{'+CSS_ELLIPSIS+'}',
      '.spx-preview-lightbox-help{flex:none!important;white-space:nowrap!important;font-size:12px!important;color:#64748b!important;}',
      '.spx-preview-lightbox-strip{display:flex!important;gap:7px!important;overflow-x:auto!important;padding:8px 10px!important;border-top:1px solid rgba(148,163,184,.18)!important;background:#020617!important;scrollbar-width:thin!important;}',
      '.spx-preview-lightbox-thumb{'+CSS_BOX+'flex:none!important;width:58px!important;height:46px!important;padding:0!important;border:2px solid transparent!important;border-radius:7px!important;overflow:hidden!important;background:#111827!important;opacity:.62!important;cursor:pointer!important;}',
      '.spx-preview-lightbox-thumb.spx-active{border-color:#38bdf8!important;opacity:1!important;}',
      '.spx-preview-lightbox-thumb img{display:block!important;width:100%!important;height:100%!important;object-fit:cover!important;}',
      '.spx-clean #notice,.spx-clean #footer,.spx-clean .footer{'+CSS_HIDE+'}',
      '.spx-clean:not(.spx-site-shell) #wrapA{max-width:1180px!important;margin:0 auto!important;}',
      '.spx-clean #main{margin-top:8px!important;}',
      '.spx-clean table{border-collapse:collapse;}',
    ];
  }

  function getInjectedWidgetStyleRules() {
    return [
      '.spx-toolbar{position:fixed;right:14px;bottom:18px;z-index:99999;display:flex;flex-direction:column;gap:7px;box-sizing:border-box;padding:6px;border:1px solid rgba(203,213,225,.8);border-radius:16px;background:rgba(255,255,255,.92);box-shadow:0 18px 46px rgba(15,23,42,.18);font:12px/1.2 Arial,Helvetica,sans-serif;backdrop-filter:blur(10px);}',
      '.spx-toolbar button{width:52px;height:36px;border:1px solid transparent;border-radius:12px;background:#fff;color:var(--spx-text);box-shadow:0 4px 12px rgba(15,23,42,.08);cursor:pointer;text-align:center;display:flex;align-items:center;justify-content:center;padding:0;font-size:13px;font-weight:800;transition:background .16s ease,border-color .16s ease,color .16s ease,transform .16s ease,box-shadow .16s ease;}',
      '.spx-toolbar button:hover,.spx-toolbar button:focus-visible{border-color:var(--spx-accent);color:var(--spx-accent);box-shadow:0 7px 18px rgba(15,118,110,.16);outline:none;transform:translateY(-1px);}',
      '.spx-toolbar .spx-active{background:var(--spx-accent-soft);border-color:var(--spx-accent);color:var(--spx-accent);font-weight:bold;box-shadow:inset 0 0 0 1px rgba(15,118,110,.12),0 6px 16px rgba(15,118,110,.14);}',
      '.spx-command-overlay{position:fixed!important;inset:0!important;z-index:100080!important;'+CSS_BOX+'display:flex!important;align-items:flex-start!important;justify-content:center!important;padding:72px 24px 28px!important;background:rgba(15,23,42,.28)!important;backdrop-filter:blur(3px)!important;color:var(--spx-text)!important;font:13px/1.45 '+SPX_FONT+'!important;}',
      '.spx-command-overlay[hidden]{'+CSS_HIDE+'}.spx-command-wrap{'+CSS_BOX+'display:grid!important;grid-template-columns:minmax(0,720px) minmax(220px,300px)!important;gap:12px!important;width:min(1032px,calc(100vw - 72px))!important;align-items:start!important;}.spx-command-palette,.spx-command-detail{'+CSS_BOX+'overflow:hidden!important;border:1px solid var(--spx-line)!important;border-radius:14px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-popover)!important;}.spx-command-palette{display:flex!important;flex-direction:column!important;max-height:calc(100vh - 112px)!important;}',
      '.spx-command-head{display:grid!important;gap:10px!important;padding:14px!important;border-bottom:1px solid var(--spx-line-soft)!important;background:linear-gradient(180deg,#fff 0%,#f8fafc 100%)!important;}.spx-command-title{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;}.spx-command-title strong{display:block!important;color:var(--spx-strong)!important;font-size:15px!important;line-height:1.25!important;font-weight:900!important;}.spx-command-title span{color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;white-space:nowrap!important;}.spx-command-close{flex:none!important;width:30px!important;height:30px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;font-size:16px!important;font-weight:900!important;cursor:pointer!important;}',
      '.spx-command-search-wrap{position:relative!important;}.spx-command-search{'+CSS_BOX+'width:100%!important;height:42px!important;padding:0 42px 0 14px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-input-bg)!important;color:var(--spx-text)!important;font-size:15px!important;font-weight:850!important;outline:none!important;}.spx-command-search:focus{border-color:var(--spx-accent)!important;box-shadow:0 0 0 3px rgba(37,99,235,.12)!important;}.spx-command-search-mark{position:absolute!important;right:12px!important;top:50%!important;transform:translateY(-50%)!important;color:var(--spx-muted)!important;font-size:13px!important;font-weight:900!important;}',
      '.spx-command-tabs{display:flex!important;gap:7px!important;min-width:0!important;overflow-x:auto!important;scrollbar-width:thin!important;}.spx-command-tab{flex:none!important;min-height:28px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:999px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;cursor:pointer!important;}.spx-command-tab.spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;}',
      '.spx-command-list{display:grid!important;flex:1 1 auto!important;min-height:0!important;max-height:min(56vh,474px)!important;overflow:auto!important;padding:10px!important;scrollbar-width:thin!important;}.spx-command-item{'+CSS_BOX+'display:grid!important;grid-template-columns:38px minmax(0,1fr) auto!important;gap:10px!important;align-items:center!important;min-height:64px!important;width:100%!important;padding:9px 10px!important;border:1px solid transparent!important;border-radius:12px!important;background:transparent!important;color:var(--spx-text)!important;text-align:left!important;cursor:pointer!important;}.spx-command-item:hover,.spx-command-item.spx-active{border-color:var(--spx-line)!important;background:var(--spx-panel-muted)!important;}.spx-command-item.spx-active{box-shadow:inset 3px 0 0 var(--spx-accent)!important;}',
      '.spx-command-icon{display:inline-flex!important;width:38px!important;height:38px!important;align-items:center!important;justify-content:center!important;border:1px solid var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel)!important;color:var(--spx-accent)!important;font-size:13px!important;font-weight:900!important;}.spx-command-main{min-width:0!important;}.spx-command-main strong{display:block!important;color:var(--spx-strong)!important;font-size:14px!important;line-height:1.3!important;font-weight:900!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}.spx-command-main span{display:block!important;margin-top:3px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:750!important;white-space:nowrap!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-command-tail{display:flex!important;align-items:center!important;gap:7px!important;color:var(--spx-muted)!important;font-size:11px!important;font-weight:900!important;white-space:nowrap!important;}.spx-command-pill,.spx-command-kbd{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:22px!important;padding:0 8px!important;border-radius:999px!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;font-size:11px!important;font-weight:900!important;}.spx-command-kbd{min-width:34px!important;height:20px!important;border:1px solid var(--spx-line)!important;border-radius:6px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;box-shadow:inset 0 -1px 0 rgba(15,23,42,.08)!important;}.spx-command-pill.spx-warn{background:#ffedd5!important;color:#7c2d12!important;}.spx-command-pill.spx-ok{background:#dcfce7!important;color:#15803d!important;}',
      '.spx-command-empty{padding:22px 10px!important;color:var(--spx-sub)!important;text-align:center!important;font-size:13px!important;font-weight:800!important;}.spx-command-foot{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;padding:9px 14px!important;border-top:1px solid var(--spx-line-soft)!important;background:var(--spx-panel-muted)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:850!important;}.spx-command-keys{display:flex!important;flex-wrap:wrap!important;gap:6px!important;justify-content:flex-end!important;}',
      '.spx-command-detail{padding:14px!important;}.spx-command-detail h3{margin:0!important;color:var(--spx-strong)!important;font-size:15px!important;line-height:1.3!important;font-weight:900!important;}.spx-command-detail p{margin:5px 0 12px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:750!important;}.spx-command-detail-card{display:grid!important;gap:9px!important;padding:11px!important;border:1px solid var(--spx-line-soft)!important;border-radius:12px!important;background:var(--spx-panel-muted)!important;}.spx-command-detail-row{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;min-height:26px!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:850!important;}.spx-command-detail-row span:first-child{flex:none!important;color:var(--spx-sub)!important;}.spx-command-detail-row strong,.spx-command-detail-row a{'+CSS_ELLIPSIS+'text-align:right!important;color:inherit!important;font-weight:900!important;text-decoration:none!important;}.spx-command-detail-row a:hover{text-decoration:underline!important;}.spx-command-detail-target{align-items:flex-start!important;}.spx-command-detail-target strong,.spx-command-detail-target a{white-space:normal!important;overflow:visible!important;overflow-wrap:anywhere!important;word-break:break-word!important;line-height:1.35!important;text-align:left!important;color:var(--spx-link)!important;}',
      '@media(max-width:980px){.spx-command-wrap{grid-template-columns:1fr!important;width:min(720px,calc(100vw - 52px))!important}.spx-command-detail{display:none!important}}',
      '@media(max-width:760px){.spx-command-overlay{padding:58px 8px 20px!important}.spx-command-wrap{width:calc(100vw - 16px)!important}.spx-command-palette{max-height:calc(100vh - 78px)!important}.spx-command-list{max-height:none!important}.spx-command-item{grid-template-columns:34px minmax(0,1fr)!important;min-height:62px!important}.spx-command-icon{width:34px!important;height:34px!important}.spx-command-tail{grid-column:2!important;justify-content:flex-start!important}.spx-command-foot{align-items:flex-start!important;flex-direction:column!important}}',
      '.spx-toolbox{position:fixed;right:82px;bottom:18px;width:min(440px,calc(100vw - 24px));max-height:min(72vh,680px);overflow:hidden;z-index:100000;box-sizing:border-box;background:rgba(255,255,255,.98);border:1px solid rgba(148,163,184,.48);box-shadow:var(--spx-shadow-strong);border-radius:var(--spx-radius-xl);padding:0;color:var(--spx-text);font:13px/1.45 Arial,Helvetica,sans-serif;backdrop-filter:blur(12px);}',
      '.spx-toolbox[hidden]{'+CSS_HIDE+'}',
      '.spx-toolbox-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--spx-line-soft);background:linear-gradient(135deg,#f8fafc 0%,#ecfeff 100%);}',
      '.spx-toolbox-eyebrow{margin:0 0 3px;color:#0f766e;font-size:11px;font-weight:900;letter-spacing:.08em;}',
      '.spx-toolbox-header h3{margin:0;color:var(--spx-strong);font-size:17px;line-height:1.25;}',
      '.spx-toolbox-header p{display:none;}',
      '.spx-toolbox-close{flex:none;width:32px;height:32px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:#334155;cursor:pointer;font-size:18px;font-weight:800;line-height:1;}',
      '.spx-toolbox-close:hover,.spx-toolbox-close:focus-visible{border-color:#0f766e;color:#0f766e;outline:none;}',
      '.spx-toolbox-body{box-sizing:border-box;max-height:calc(min(72vh,680px) - 58px);overflow:auto;padding:12px 13px 13px;scrollbar-width:thin;}',
      '.spx-toolbox-section{margin-top:12px;}',
      '.spx-toolbox-section:first-of-type{margin-top:0;}',
      '.spx-toolbox-section-title{display:flex;align-items:center;gap:8px;margin:0 0 8px;color:#475569;font-size:12px;font-weight:900;letter-spacing:.02em;}',
      '.spx-toolbox-section-title:after{content:"";height:1px;flex:1;background:#e2e8f0;}',
      '.spx-toolbox-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:#e0f2fe;color:#075985;font-size:11px;font-weight:900;letter-spacing:0;}',
      '.spx-toolbox-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;}',
      '.spx-toolbox-action{box-sizing:border-box;min-width:0;min-height:56px;display:flex!important;align-items:center!important;justify-content:flex-start!important;gap:9px;position:relative;border:1px solid #dbe4ee!important;border-radius:var(--spx-radius-lg)!important;background:#fff!important;color:#1f2937!important;padding:9px 10px!important;text-align:left!important;text-decoration:none!important;cursor:pointer!important;box-shadow:0 5px 16px rgba(15,23,42,.06)!important;transition:background .16s ease,border-color .16s ease,color .16s ease,transform .16s ease,box-shadow .16s ease!important;}',
      '.spx-toolbox-action:hover,.spx-toolbox-action:focus-visible{border-color:var(--spx-accent)!important;background:#f0fdfa!important;color:#0f766e!important;text-decoration:none!important;outline:none!important;box-shadow:0 10px 24px rgba(15,118,110,.15)!important;transform:translateY(-1px)!important;}',
      '.spx-toolbox-action.spx-active{border-color:#0f766e!important;background:#ccfbf1!important;color:#0f766e!important;box-shadow:0 8px 22px rgba(15,118,110,.18)!important;}',
      '.spx-toolbox-action.spx-active:before{content:"";position:absolute;left:0;top:12px;bottom:12px;width:3px;border-radius:0 999px 999px 0;background:#0f766e;}',
      '.spx-toolbox-key{flex:none;width:32px;height:32px;border-radius:10px;display:flex;align-items:center;justify-content:center;background:#f8fafc;border:1px solid #cbd5e1;color:#075985;font-size:15px;font-weight:900;}',
      '.spx-toolbox-action.spx-active .spx-toolbox-key{border-color:#0f766e;background:#f0fdfa;color:#0f766e;}',
      '.spx-toolbox-copy{min-width:0;display:block;}',
      '.spx-toolbox-name{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:inherit;font-size:13px;font-weight:800;line-height:1.25;}',
      '.spx-toolbox-desc{display:-webkit-box;overflow:hidden;-webkit-line-clamp:1;-webkit-box-orient:vertical;margin-top:4px;color:#64748b;font-size:11px;line-height:1.3;}',
      '.spx-settings{position:fixed;right:66px;bottom:18px;width:min(520px,calc(100vw - 24px));max-height:var(--spx-panel-max-height);overflow:hidden;z-index:100000;display:flex;flex-direction:column;background:var(--spx-panel);border:1px solid var(--spx-line);box-shadow:var(--spx-shadow-popover);border-radius:var(--spx-radius-lg);padding:0;color:var(--spx-text);font:13px/1.45 Arial,Helvetica,sans-serif;}',
      '.spx-settings[hidden]{'+CSS_HIDE+'}',
      '.spx-settings-header{flex:none;padding:13px 16px;border-bottom:1px solid var(--spx-line-soft);background:linear-gradient(135deg,#fff 0%,#f8fafc 100%);}',
      '.spx-settings h3{margin:0;font-size:16px;color:var(--spx-strong);line-height:1.3;}',
      '.spx-settings-subtitle{margin:3px 0 0;color:var(--spx-sub);font-size:12px;}',
      '.spx-settings-body{box-sizing:border-box;overflow:auto;padding:12px;display:grid;gap:11px;scrollbar-width:thin;}',
      '.spx-settings-section{box-sizing:border-box;padding:11px 12px;border:1px solid var(--spx-line-soft);border-radius:var(--spx-radius-lg);background:var(--spx-panel-muted);}',
      '.spx-settings-section h4{margin:0 0 8px;color:var(--spx-strong);font-size:13px;font-weight:900;line-height:1.3;}',
      '.spx-settings-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px 12px;}',
      '.spx-settings label{display:flex;gap:8px;align-items:center;min-height:26px;margin:0;color:var(--spx-text);}',
      '.spx-settings label input[type="checkbox"]{flex:none;accent-color:var(--spx-accent);}',
      '.spx-settings .spx-number-setting{justify-content:space-between;gap:10px;}',
      '.spx-settings .spx-number-setting input{box-sizing:border-box;width:92px;height:var(--spx-control-height);border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);padding:0 8px;text-align:right;}',
      '.spx-settings .spx-choice-setting{justify-content:space-between;gap:10px;}',
      '.spx-settings .spx-choice-setting select{box-sizing:border-box;min-width:132px;height:var(--spx-control-height);border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);padding:0 8px;background:#fff;color:var(--spx-text);}',
      '.spx-settings textarea{box-sizing:border-box;width:100%;min-height:72px;border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);padding:7px;background:#fff;font:12px/1.4 monospace;}',
      '.spx-settings .spx-help{margin:4px 0 8px;color:var(--spx-sub);font-size:12px;}',
      '.spx-settings .spx-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;}',
      '.spx-settings-footer{flex:none;display:flex;justify-content:flex-end;gap:8px;padding:10px 12px;border-top:1px solid var(--spx-line-soft);background:#fff;}',
      '.spx-settings button{border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);background:#fff;padding:6px 10px;cursor:pointer;color:var(--spx-text);}',
      '.spx-settings .spx-primary{background:var(--spx-accent);border-color:var(--spx-accent);color:#fff;}',
      '.spx-settings .spx-danger{border-color:#fecaca;background:var(--spx-danger-soft);color:var(--spx-danger);}',
      '.spx-settings-presets .spx-preset-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;}.spx-settings-presets .spx-preset-tabs button{min-height:34px;font-weight:900;background:#fff;border-color:var(--spx-line);}.spx-settings-presets .spx-preset-tabs button:hover{border-color:var(--spx-accent);color:var(--spx-accent);}',
      '.spx-batch-confirm-overlay{position:fixed;inset:0;z-index:100090;display:flex;align-items:center;justify-content:center;box-sizing:border-box;padding:18px;background:rgba(15,23,42,.36);backdrop-filter:blur(4px);font:13px/1.45 '+SPX_FONT+';color:var(--spx-text);}',
      '.spx-batch-confirm{box-sizing:border-box;width:min(520px,calc(100vw - 32px));max-height:min(78vh,680px);overflow:auto;padding:14px;border:1px solid var(--spx-line);border-radius:16px;background:var(--spx-panel);box-shadow:var(--spx-shadow-popover);}.spx-batch-confirm-head{display:grid;gap:5px;margin-bottom:11px;}.spx-batch-confirm-head strong{color:var(--spx-strong);font-size:16px;font-weight:900;line-height:1.3;}.spx-batch-confirm-head span{color:var(--spx-sub);font-size:12px;}',
      '.spx-batch-impact-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:11px;}.spx-batch-impact{box-sizing:border-box;display:grid;gap:2px;padding:10px;border:1px solid var(--spx-line-soft);border-radius:11px;background:var(--spx-panel-muted);}.spx-batch-impact b{color:var(--spx-strong);font-size:20px;line-height:1;font-weight:900;}.spx-batch-impact span{color:var(--spx-text);font-size:12px;font-weight:900;}.spx-batch-impact em{color:var(--spx-sub);font-size:11px;font-style:normal;}',
      '.spx-batch-list{display:grid;gap:6px;max-height:240px;overflow:auto;margin:0 0 12px;padding:8px;border:1px solid var(--spx-line-soft);border-radius:12px;background:#fff;}.spx-batch-item{display:grid;gap:2px;min-width:0;padding:6px 7px;border-radius:8px;background:var(--spx-panel-muted);}.spx-batch-item span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--spx-strong);font-weight:900;}.spx-batch-item em,.spx-batch-more{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--spx-sub);font-size:11px;font-style:normal;}',
      '.spx-batch-confirm-foot{display:flex;justify-content:flex-end;gap:8px;}.spx-batch-confirm-foot button{min-height:34px;padding:0 12px;border:1px solid var(--spx-line);border-radius:9px;background:#fff;color:var(--spx-text);font-weight:900;cursor:pointer;}.spx-batch-confirm-foot .spx-primary{border-color:var(--spx-danger);background:var(--spx-danger);color:#fff;}.spx-batch-confirm:not(.spx-batch-danger) .spx-batch-confirm-foot .spx-primary{border-color:var(--spx-accent);background:var(--spx-accent);}',
      '.spx-data-health{box-sizing:border-box;margin-top:10px;padding:10px;border:1px solid #e2e8f0;border-radius:var(--spx-radius);background:#fff;}',
      '.spx-data-health[hidden]{'+CSS_HIDE+'}',
      '.spx-storage-usage{display:grid;gap:6px;margin:7px 0 9px;font-size:12px;color:#334155;}',
      '.spx-storage-usage-row{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:7px 8px;border-radius:7px;background:#fff;border:1px solid #e5e7eb;}',
      '.spx-storage-usage-row.spx-storage-warning{border-color:#fbbf24;background:#fffbeb;}',
      '.spx-storage-usage-row.spx-storage-danger{border-color:#f87171;background:#fef2f2;}',
      '.spx-storage-usage-main{min-width:0;display:block;}',
      '.spx-storage-usage-main b{display:block;color:#0f172a;font-size:12px;line-height:1.3;}',
      '.spx-storage-usage-main em{display:block;margin-top:2px;color:#94a3b8;font-style:normal;font-size:11px;line-height:1.25;word-break:break-all;}',
      '.spx-storage-usage-meta{flex:none;text-align:right;color:#334155;font-weight:800;line-height:1.45;white-space:nowrap;}',
      '.spx-storage-suggestions{margin:6px 0 8px;padding:7px 8px;border-radius:7px;background:#fff;border:1px solid #e5e7eb;color:#475569;font-size:12px;line-height:1.45;}',
      '.spx-watch-center{position:fixed;right:66px;bottom:18px;width:var(--spx-panel-width);max-height:var(--spx-panel-max-height);overflow:auto;z-index:100000;background:var(--spx-panel);border:1px solid var(--spx-line);box-shadow:var(--spx-shadow-popover);border-radius:var(--spx-radius-lg);padding:14px;color:var(--spx-text);font:13px/1.45 Arial,Helvetica,sans-serif;scrollbar-width:thin;}',
      '.spx-resource-panel,#spx-resource-center{width:min(720px,calc(100vw - 96px));}',
      '#spx-content-center{width:min(680px,calc(100vw - 96px));}.spx-content-center-header p{margin:3px 0 0;color:var(--spx-sub);font-size:12px;}.spx-content-stats{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:8px;margin:12px 0;}.spx-content-stat{box-sizing:border-box;display:grid;gap:2px;min-width:0;padding:10px;border:1px solid var(--spx-line-soft);border-radius:12px;background:var(--spx-panel-muted);}.spx-content-stat b{color:var(--spx-strong);font-size:19px;line-height:1;font-weight:900;}.spx-content-stat span{color:var(--spx-text);font-size:12px;font-weight:900;}.spx-content-stat em{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--spx-sub);font-size:11px;font-style:normal;}.spx-content-stat.spx-ok{border-color:#bbf7d0;background:#f0fdf4;}.spx-content-stat.spx-warn{border-color:#fde68a;background:#fffbeb;}',
      '.spx-workbench-open .spx-read-resource-rail,.spx-workbench-open .spx-read-resource-launcher,.spx-workbench-open .spx-preview-panel.spx-preview-drawer,.spx-workbench-open .spx-preview-popover{'+CSS_HIDE+'}.spx-module-body.spx-workbench-mode>*:not(.spx-workbench){'+CSS_HIDE+'}.spx-workbench{box-sizing:border-box;width:100%;max-width:100%;min-width:0;margin:0 0 12px;overflow:hidden;border:1px solid var(--spx-line);border-radius:14px;background:var(--spx-panel);box-shadow:var(--spx-shadow-card);color:var(--spx-text);font:13px/1.45 Arial,Helvetica,sans-serif;}.spx-workbench[hidden]{'+CSS_HIDE+'}.spx-workbench-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;min-width:0;padding:13px 14px;border-bottom:1px solid var(--spx-line-soft);background:linear-gradient(135deg,#fff 0%,#f8fafc 100%);}.spx-workbench-title{min-width:0;}.spx-workbench-title strong{display:block;color:var(--spx-strong);font-size:16px;font-weight:900;line-height:1.25;}.spx-workbench-title span{display:block;margin-top:3px;color:var(--spx-sub);font-size:12px;font-weight:800;}.spx-workbench-close{flex:none;min-height:30px;padding:0 10px;border:1px solid var(--spx-line);border-radius:9px;background:#fff;color:var(--spx-text);font-size:12px;font-weight:900;cursor:pointer;}.spx-workbench-tabs{display:flex;gap:7px;max-width:100%;overflow:auto;padding:10px 12px;border-bottom:1px solid var(--spx-line-soft);background:var(--spx-panel-muted);scrollbar-width:thin;}.spx-workbench-tab{flex:none;min-height:30px;padding:0 10px;border:1px solid var(--spx-line);border-radius:999px;background:var(--spx-panel);color:var(--spx-sub);font-size:12px;font-weight:900;cursor:pointer;}.spx-workbench-tab.spx-active{border-color:var(--spx-accent);background:var(--spx-accent-wash);color:var(--spx-accent);}.spx-workbench-stage{min-width:0;max-width:100%;overflow:hidden;}.spx-workbench-inline-panel.spx-watch-center{'+CSS_BOX+'position:static!important;right:auto!important;bottom:auto!important;display:block!important;width:100%!important;max-width:100%!important;min-width:0!important;max-height:none!important;overflow:hidden!important;z-index:auto!important;border:0!important;border-radius:0!important;box-shadow:none!important;background:transparent!important;padding:14px!important;}.spx-workbench-inline-panel.spx-watch-center[hidden]{'+CSS_HIDE+'}.spx-workbench-inline-panel .spx-watch-center-header{'+CSS_BOX+'position:static!important;top:auto!important;display:flex!important;align-items:flex-start!important;justify-content:space-between!important;flex-wrap:wrap!important;width:auto!important;max-width:100%!important;min-width:0!important;background:rgba(255,255,255,.92);}.spx-workbench-inline-panel .spx-watch-center-header>div:first-child{min-width:0!important;}.spx-workbench-inline-panel .spx-watch-center-header .spx-watch-actions{flex:1 1 260px!important;justify-content:flex-end!important;max-width:100%!important;min-width:0!important;}.spx-workbench-inline-panel#spx-content-center,.spx-workbench-inline-panel#spx-resource-center{width:100%!important;max-width:100%!important;}.spx-workbench-inline-panel .spx-watch-controls{'+CSS_BOX+'max-width:100%;min-width:0;}.spx-workbench-inline-panel .spx-watch-controls input{flex:1 1 260px!important;min-width:0!important;max-width:100%;}.spx-workbench-inline-panel .spx-watch-controls select{flex:1 1 150px!important;min-width:0!important;max-width:100%;}.spx-workbench-inline-panel .spx-watch-actions button,.spx-workbench-inline-panel .spx-watch-actions a{white-space:nowrap!important;}',
      '.spx-content-shortcuts{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;}.spx-content-shortcuts button{min-height:32px;padding:0 10px;border:1px solid var(--spx-line);border-radius:9px;background:#fff;color:var(--spx-text);font-weight:900;cursor:pointer;}.spx-content-shortcuts .spx-primary{border-color:var(--spx-accent);background:var(--spx-accent);color:#fff;}.spx-content-recent-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;}.spx-content-recent-section{box-sizing:border-box;padding:10px;border:1px solid var(--spx-line-soft);border-radius:12px;background:var(--spx-panel-muted);}.spx-content-recent-section h4{margin:0 0 8px;color:var(--spx-strong);font-size:13px;font-weight:900;}.spx-content-recent-list{display:grid;gap:7px;}.spx-content-recent{display:grid;gap:3px;min-width:0;padding:7px;border-radius:9px;background:#fff;border:1px solid var(--spx-line-soft);}.spx-content-recent strong,.spx-content-recent span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.spx-content-recent strong{color:var(--spx-strong);font-size:12px;}.spx-content-recent span,.spx-content-empty{color:var(--spx-sub);font-size:11px;}',
      '.spx-read-summary-card{box-sizing:border-box;width:100%;max-width:100%;min-width:0;overflow:hidden;margin:10px 0 12px;padding:14px;border:1px solid var(--spx-line);border-radius:16px;background:var(--spx-panel);box-shadow:var(--spx-shadow-card);color:var(--spx-text);font:13px/1.45 '+SPX_FONT+';}.spx-read-summary-head{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:start;margin-bottom:11px;}.spx-read-summary-copy{min-width:0;}.spx-read-summary-eyebrow{margin-bottom:3px;color:var(--spx-accent);font-size:11px;font-weight:900;letter-spacing:.04em;}.spx-read-summary-copy h3{margin:0;color:var(--spx-strong);font-size:17px;line-height:1.35;font-weight:900;word-break:break-word;}.spx-read-summary-copy p{margin:4px 0 0;color:var(--spx-sub);font-size:12px;}.spx-read-summary-chips{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;}.spx-chip{display:inline-flex;align-items:center;min-height:24px;padding:0 8px;border:1px solid var(--spx-line);border-radius:999px;background:var(--spx-panel-muted);color:var(--spx-sub);font-size:11px;font-weight:900;}.spx-chip.spx-ok{border-color:#bbf7d0;background:#f0fdf4;color:#15803d;}.spx-chip.spx-warn{border-color:#fde68a;background:#fffbeb;color:#92400e;}.spx-chip.spx-danger{border-color:#fecaca;background:#fef2f2;color:#b91c1c;}',
      '.spx-read-summary-progress{display:grid;grid-template-columns:auto minmax(140px,1fr);gap:10px;align-items:center;margin-bottom:11px;color:var(--spx-sub);font-size:12px;font-weight:900;}.spx-read-summary-track{height:8px;border-radius:999px;background:#e2e8f0;overflow:hidden;}.spx-read-summary-track i{display:block;height:100%;border-radius:999px;background:linear-gradient(90deg,var(--spx-accent),#22c55e);}.spx-read-summary-metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:8px;margin-bottom:12px;}.spx-read-summary-metric{box-sizing:border-box;display:grid;gap:2px;min-width:0;padding:9px;border:1px solid var(--spx-line-soft);border-radius:12px;background:var(--spx-panel-muted);}.spx-read-summary-metric b{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--spx-strong);font-size:16px;font-weight:900;}.spx-read-summary-metric span{color:var(--spx-text);font-size:12px;font-weight:900;}.spx-read-summary-metric em{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--spx-sub);font-size:11px;font-style:normal;}.spx-read-summary-metric.spx-ok{border-color:#bbf7d0;background:#f0fdf4;}.spx-read-summary-metric.spx-warn{border-color:#fde68a;background:#fffbeb;}.spx-read-summary-metric.spx-danger{border-color:#fecaca;background:#fef2f2;}.spx-read-summary-actions{display:flex;flex-wrap:wrap;gap:7px;}.spx-read-summary-actions button{min-height:32px;padding:0 10px;border:1px solid var(--spx-line);border-radius:9px;background:#fff;color:var(--spx-text);font-weight:900;cursor:pointer;}.spx-read-summary-actions button:disabled{cursor:default;opacity:.56;}.spx-read-summary-actions .spx-primary{border-color:var(--spx-accent);background:var(--spx-accent);color:#fff;}.spx-read-summary-actions button:hover:not(:disabled){border-color:var(--spx-accent);color:var(--spx-accent);background:var(--spx-accent-wash);}',
      '.spx-read-resource-rail{position:fixed!important;right:76px!important;top:72px!important;z-index:100015!important;'+CSS_BOX+'display:flex!important;flex-direction:column!important;width:320px!important;max-height:calc(100vh - 112px)!important;padding:12px!important;border:1px solid var(--spx-line)!important;border-radius:16px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-popover)!important;font:13px/1.45 '+SPX_FONT+'!important;}',
      '.spx-read-resource-rail[hidden]{'+CSS_HIDE+'}',
      '.spx-read-resource-rail-head{display:flex!important;align-items:flex-start!important;justify-content:space-between!important;gap:10px!important;margin:-12px -12px 10px!important;padding:12px!important;border-bottom:1px solid var(--spx-line-soft)!important;border-radius:16px 16px 0 0!important;background:linear-gradient(180deg,#fff 0%,#f8fafc 100%)!important;}',
      '.spx-read-resource-rail-head strong{display:block!important;color:var(--spx-strong)!important;font-size:15px!important;font-weight:900!important;line-height:1.25!important;}',
      '.spx-read-resource-summary{display:block!important;margin-top:3px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-read-resource-rail button,.spx-read-resource-launcher{'+CSS_BOX+'border:1px solid var(--spx-line)!important;border-radius:8px!important;background:#fff!important;color:var(--spx-text)!important;cursor:pointer!important;font:800 12px/1.2 '+SPX_FONT+'!important;}',
      '.spx-read-resource-rail button{min-height:28px!important;padding:0 9px!important;}',
      '.spx-read-resource-rail button:hover,.spx-read-resource-launcher:hover{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;}',
      '.spx-read-resource-rail button:disabled{cursor:not-allowed!important;opacity:.5!important;}',
      '.spx-read-resource-actions,.spx-read-resource-filters,.spx-read-resource-card-actions{display:flex!important;flex-wrap:wrap!important;gap:6px!important;align-items:center!important;}',
      '.spx-read-resource-actions{margin-bottom:8px!important;}',
      '.spx-read-resource-filters{margin-bottom:10px!important;padding:8px!important;border:1px solid var(--spx-line-soft)!important;border-radius:12px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-read-resource-filters button.spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;}',
      '.spx-read-resource-list{display:flex!important;flex-direction:column!important;gap:8px!important;min-height:0!important;overflow:auto!important;padding-right:2px!important;scrollbar-width:thin!important;}',
      '.spx-read-resource-card{'+CSS_BOX+'display:grid!important;gap:7px!important;padding:10px!important;border:1px solid var(--spx-line-soft)!important;border-radius:12px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;}',
      '.spx-read-resource-card.spx-active{border-color:var(--spx-accent)!important;box-shadow:0 0 0 3px rgba(37,99,235,.12)!important;}',
      '.spx-read-resource-card-top{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;}',
      '.spx-read-resource-type{display:inline-flex!important;align-items:center!important;min-height:20px!important;padding:0 7px!important;border-radius:999px!important;background:#e0f2fe!important;color:#075985!important;font-size:11px!important;font-weight:900!important;}',
      '.spx-read-resource-url{display:block!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;color:var(--spx-link)!important;font-size:12px!important;font-weight:850!important;text-decoration:none!important;}',
      '.spx-read-resource-url:hover{text-decoration:underline!important;}',
      '.spx-read-resource-meta{color:var(--spx-sub)!important;font-size:12px!important;font-weight:750!important;line-height:1.38!important;word-break:break-word!important;}',
      '.spx-read-resource-card-actions button{min-height:26px!important;padding:0 8px!important;font-size:11px!important;}',
      '.spx-read-resource-card-actions .spx-action-secondary{color:var(--spx-sub)!important;background:var(--spx-panel)!important;}',
      '.spx-read-resource-launcher{position:fixed!important;right:16px!important;bottom:132px!important;z-index:100016!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:82px!important;height:38px!important;padding:0 12px!important;border-color:rgba(37,99,235,.34)!important;border-radius:14px!important;background:var(--spx-accent)!important;color:#fff!important;box-shadow:0 16px 42px rgba(37,99,235,.28)!important;}',
      '.spx-read-resource-launcher[hidden]{'+CSS_HIDE+'}',
      'table.js-post.spx-read-resource-floor-active{outline:3px solid rgba(37,99,235,.22)!important;outline-offset:3px!important;}',
      '.spx-watch-center[hidden]{'+CSS_HIDE+'}',
      '.spx-watch-center-header{position:sticky;top:-14px;z-index:1;display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin:-14px -14px 12px;padding:13px 14px 11px;border-bottom:1px solid var(--spx-line-soft);background:rgba(255,255,255,.96);backdrop-filter:blur(10px);}',
      '.spx-watch-center h3{margin:0;color:var(--spx-strong);font-size:16px;line-height:1.3;}',
      '.spx-watch-center .spx-watch-summary{color:var(--spx-sub);font-size:12px;}',
      '.spx-watch-controls{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:0 0 12px;padding:9px;border:1px solid var(--spx-line-soft);border-radius:var(--spx-radius-lg);background:var(--spx-panel-muted);}',
      '.spx-watch-controls input,.spx-watch-controls select{box-sizing:border-box;height:var(--spx-control-height);border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);background:#fff;color:var(--spx-text);padding:0 10px;font-size:12px;}',
      '.spx-watch-controls input{flex:1 1 220px;min-width:184px;}',
      '.spx-watch-controls select{flex:1 0 132px;min-width:116px;max-width:190px;}',
      '.spx-watch-list{display:flex;flex-direction:column;gap:9px;}',
      '.spx-watch-item{box-sizing:border-box;padding:11px 12px;border:1px solid var(--spx-line-soft);border-radius:var(--spx-radius-lg);background:var(--spx-panel-muted);}',
      '.spx-watch-title{display:block;margin-bottom:5px;color:var(--spx-link)!important;font-size:14px;font-weight:800;line-height:1.38;text-decoration:none;word-break:break-word;}',
      '.spx-resource-title-row{display:flex;align-items:flex-start;gap:8px;margin-bottom:4px;}',
      '.spx-resource-title-row .spx-watch-title{min-width:0;margin-bottom:0;word-break:break-all;}',
      '.spx-resource-select{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--spx-accent);}',
      '.spx-resource-url{display:block;overflow:hidden;text-overflow:ellipsis;white-space:normal;word-break:break-all;color:var(--spx-link)!important;font-size:12px;line-height:1.35;text-decoration:none;}',
      '.spx-auto-resource-jump{box-sizing:border-box;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)));margin:10px auto;padding:10px 12px;border:1px solid #99f6e4;border-radius:var(--spx-radius);background:#f0fdfa;color:#0f766e;font:13px/1.45 Arial,Helvetica,sans-serif;}',
      '.spx-auto-resource-jump strong{display:block;margin-bottom:6px;color:#0f766e;font-size:14px;}',
      '.spx-auto-resource-actions{display:flex;flex-wrap:wrap;gap:6px;align-items:center;}',
      '.spx-auto-resource-actions a,.spx-auto-resource-actions button{box-sizing:border-box;max-width:220px;height:28px;padding:0 9px;border:1px solid #5eead4;border-radius:999px;background:#fff;color:#0f766e!important;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-decoration:none;font-size:12px;line-height:26px;cursor:pointer;}',
      '.spx-auto-resource-actions button:hover,.spx-auto-resource-actions a:hover{border-color:#0f766e;background:#ccfbf1;text-decoration:none!important;}',
      '.spx-watch-meta{margin-bottom:8px;color:var(--spx-sub);font-size:12px;}',
      '.spx-watch-actions{display:flex;flex-wrap:wrap;gap:6px;}',
      '.spx-watch-actions button,.spx-watch-actions a,.spx-watch-center-header button{border:1px solid var(--spx-line);border-radius:var(--spx-radius-sm);background:#fff;color:var(--spx-text);padding:4px 8px;cursor:pointer;text-decoration:none;font-size:12px;line-height:1.25;}',
      '.spx-watch-actions .spx-action-primary{border-color:#99f6e4;background:var(--spx-accent-wash);color:var(--spx-accent)!important;font-weight:700;}',
      '.spx-watch-actions .spx-action-secondary{color:var(--spx-sub)!important;}',
      '.spx-watch-actions .spx-danger{border-color:#fecaca!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}',
      '.spx-watch-actions button[data-action*="clear"],.spx-watch-actions button[data-action*="remove"],.spx-watch-actions button[data-action*="delete"],.spx-watch-actions button[data-action*="invalid"]{border-color:#fecaca;background:var(--spx-danger-soft);color:var(--spx-danger);}',
      '.spx-watch-actions button:hover,.spx-watch-actions a:hover,.spx-watch-center-header button:hover{border-color:var(--spx-accent);color:var(--spx-accent);}',
      '.spx-watch-empty{padding:14px 2px;color:var(--spx-sub);font-size:13px;}',
      '@media(max-width:900px){.spx-read-summary-card{width:100%;max-width:100%;margin:8px 0 10px;padding:12px;border-radius:14px;}.spx-read-summary-head{grid-template-columns:1fr;}.spx-read-summary-chips{justify-content:flex-start;}.spx-read-summary-progress{grid-template-columns:1fr;gap:6px;}.spx-read-summary-metrics,.spx-content-stats{grid-template-columns:repeat(2,minmax(0,1fr));}.spx-content-recent-grid{grid-template-columns:1fr;}#spx-content-center{width:auto;}.spx-batch-impact-grid{grid-template-columns:1fr;}}',
      '.spx-status-badge{display:inline-block;margin-right:6px;padding:1px 6px;border-radius:999px;background:#e0f2fe;color:#075985;font-weight:800;}',
      '.spx-status-badge.spx-status-saved{background:#e0f2fe;color:#075985;}',
      '.spx-status-badge.spx-status-todo{background:#fef3c7;color:#92400e;}',
      '.spx-status-badge.spx-status-failed{background:#fee2e2;color:#b91c1c;}',
      '.spx-status-badge.spx-status-done{background:#dcfce7;color:#15803d;}',
      '.spx-status-badge.spx-status-invalid{background:#fee2e2;color:#b91c1c;}',
      '.spx-quick-reply{position:fixed!important;right:82px!important;bottom:78px!important;z-index:100020!important;'+CSS_BOX+'width:min(560px,calc(100vw - 108px))!important;max-height:min(78vh,720px)!important;overflow:auto!important;border:1px solid var(--spx-line)!important;border-radius:14px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-popover)!important;font:13px/1.45 '+SPX_FONT+'!important;}',
      '.spx-quick-reply.spx-quick-reply-collapsed{'+CSS_HIDE+'}',
      '.spx-quick-reply-launcher{position:fixed!important;right:16px!important;bottom:78px!important;z-index:100021!important;'+CSS_HIDE+'align-items:center!important;justify-content:center!important;min-width:118px!important;height:42px!important;padding:0 14px!important;border:1px solid rgba(37,99,235,.34)!important;border-radius:14px!important;background:var(--spx-accent)!important;color:#fff!important;font-size:13px!important;font-weight:900!important;box-shadow:0 18px 46px rgba(37,99,235,.3)!important;cursor:pointer!important;}',
      '.spx-quick-reply-launcher.spx-visible{display:inline-flex!important;}',
      '.spx-quick-reply-header{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;margin:0!important;padding:12px 14px!important;border-bottom:1px solid var(--spx-line-soft)!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;}',
      '.spx-quick-reply-header strong{display:block!important;color:var(--spx-text)!important;font-size:14px!important;font-weight:900!important;line-height:1.25!important;}',
      '.spx-quick-reply-status{display:block!important;margin-top:3px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-quick-reply-status.spx-error{color:#b91c1c!important;font-weight:800!important;}',
      '.spx-quick-reply-actions{display:flex!important;gap:6px!important;flex:none!important;}',
      '.spx-quick-reply button{'+CSS_BOX+'border:1px solid var(--spx-line)!important;background:#fff!important;color:var(--spx-text)!important;cursor:pointer!important;}',
      '.spx-quick-reply button:hover{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;}',
      '.spx-quick-reply button:disabled{cursor:wait!important;opacity:.55!important;}',
      '.spx-quick-reply-icon{min-width:58px!important;height:30px!important;padding:0 10px!important;border-radius:8px!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-quick-reply-context{'+CSS_HIDE+'align-items:center!important;justify-content:space-between!important;gap:10px!important;min-height:36px!important;padding:8px 12px!important;border-bottom:1px solid #fde4bd!important;background:#fffbeb!important;color:#7c2d12!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-quick-reply-context.spx-visible{display:flex!important;}',
      '.spx-quick-reply-context button{flex:none!important;width:24px!important;height:24px!important;padding:0!important;border:0!important;border-radius:7px!important;background:rgba(255,255,255,.8)!important;color:#7c2d12!important;font-size:14px!important;font-weight:900!important;}',
      '.spx-quick-reply-body{padding:12px!important;background:var(--spx-panel)!important;}',
      '.spx-quick-reply-list{display:flex!important;gap:7px!important;margin:0 0 9px!important;overflow-x:auto!important;scrollbar-width:thin!important;}',
      '.spx-quick-reply-chip{flex:none!important;min-height:28px!important;padding:0 9px!important;border-radius:999px!important;background:var(--spx-panel-muted)!important;font-size:13px!important;font-weight:800!important;line-height:1.2!important;}',
      '.spx-quick-reply-chip.spx-hot{border-color:#16a34a!important;background:#ecfdf5!important;color:#047857!important;}',
      '.spx-quick-reply-emotes{margin:-1px 0 10px!important;padding:8px!important;border:1px solid var(--spx-line-soft)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-quick-reply-emote-head{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;margin-bottom:7px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-quick-reply-emote-grid{display:grid!important;grid-template-columns:repeat(auto-fill,34px)!important;gap:5px!important;align-items:center!important;}',
      '.spx-quick-reply-emote{width:34px!important;height:34px!important;padding:0!important;border-radius:7px!important;background:#fff!important;line-height:1!important;}',
      '.spx-quick-reply-emote img{display:block!important;max-width:28px!important;max-height:28px!important;margin:auto!important;object-fit:contain!important;}',
      '.spx-quick-reply-tools{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;margin-bottom:8px!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:850!important;}',
      '.spx-quick-reply-note{'+CSS_ELLIPSIS+'}',
      '.spx-quick-reply-counter{flex:none!important;font-weight:900!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-quick-reply-editor{'+CSS_BOX+'display:block!important;width:100%!important;min-height:128px!important;max-height:260px!important;resize:vertical!important;padding:11px 12px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;font-size:14px!important;line-height:1.6!important;outline:none!important;}',
      '.spx-quick-reply-editor:focus{border-color:var(--spx-accent)!important;box-shadow:0 0 0 3px rgba(37,99,235,.12)!important;}',
      '.spx-quick-reply-attach{display:grid!important;grid-template-columns:minmax(0,1fr) auto!important;gap:8px!important;align-items:center!important;margin-top:9px!important;padding:8px!important;border:1px solid var(--spx-line-soft)!important;border-radius:9px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-quick-reply-desc{'+CSS_BOX+'min-width:0!important;height:32px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:#fff!important;color:var(--spx-text)!important;font-size:13px!important;outline:none!important;}',
      '.spx-quick-reply-picker{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:32px!important;padding:0 11px!important;border:1px solid var(--spx-accent)!important;border-radius:8px!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;font-size:13px!important;font-weight:900!important;cursor:pointer!important;white-space:nowrap!important;}',
      '.spx-quick-reply-help{grid-column:1/-1!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-quick-reply-attachments{'+CSS_HIDE+'grid-column:1/-1!important;grid-template-columns:repeat(auto-fit,minmax(140px,1fr))!important;gap:7px!important;margin-top:1px!important;}',
      '.spx-quick-reply-attachments.spx-visible{display:grid!important;}',
      '.spx-quick-reply-attachment{display:grid!important;grid-template-columns:38px minmax(0,1fr) auto!important;gap:8px!important;align-items:center!important;min-width:0!important;padding:6px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:#fff!important;}',
      '.spx-quick-reply-thumb{width:38px!important;height:38px!important;border-radius:7px!important;background:#e5e7eb!important;object-fit:cover!important;}',
      '.spx-quick-reply-attachment-name{overflow:hidden!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-quick-reply-attachment-size{margin-top:2px!important;color:var(--spx-sub)!important;font-size:11px!important;font-weight:800!important;}',
      '.spx-quick-reply-remove{width:26px!important;height:26px!important;padding:0!important;border-radius:7px!important;color:#b91c1c!important;font-weight:900!important;}',
      '.spx-quick-reply-footer{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:10px!important;margin-top:10px!important;}',
      '.spx-quick-reply-hint{min-width:0!important;overflow:hidden!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-quick-reply-submit-actions{display:flex!important;gap:7px!important;flex:none!important;}',
      '.spx-quick-reply-action{min-height:32px!important;padding:0 12px!important;border-radius:8px!important;font-weight:900!important;}',
      '.spx-quick-reply-action.spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;}',
      '.spx-quick-reply-selection{position:fixed!important;left:52%!important;top:50%!important;z-index:100022!important;'+CSS_HIDE+'gap:6px!important;padding:6px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-panel)!important;box-shadow:var(--spx-shadow-popover)!important;}',
      '.spx-quick-reply-selection.spx-visible{display:flex!important;}',
      '.spx-quick-reply-selection button{min-height:28px!important;padding:0 10px!important;border-radius:8px!important;font-size:12px!important;font-weight:900!important;}',
      '@media(max-width:900px){.spx-quick-reply{right:8px!important;bottom:calc(78px + env(safe-area-inset-bottom,0px))!important;width:calc(100vw - 16px)!important;border-radius:12px!important}.spx-quick-reply-launcher{right:8px!important;bottom:calc(78px + env(safe-area-inset-bottom,0px))!important}.spx-quick-reply-footer{align-items:stretch!important;flex-direction:column!important}.spx-quick-reply-attach{grid-template-columns:minmax(0,1fr)!important}.spx-quick-reply-picker{width:100%!important}.spx-quick-reply-submit-actions{justify-content:flex-end!important}}',
      '.spx-author-hover-source{cursor:help!important;}',
      '.spx-author-popover{position:fixed!important;z-index:100003!important;'+CSS_BOX+'width:min(320px,calc(100vw - 32px))!important;max-height:min(460px,calc(100vh - 32px))!important;overflow:auto!important;padding:12px!important;background:#fff!important;border:1px solid #cbd5e1!important;border-radius:10px!important;box-shadow:0 18px 46px rgba(15,23,42,.24)!important;color:#172033!important;font:13px/1.5 '+SPX_FONT+'!important;}',
      '.spx-author-popover-header{display:grid!important;grid-template-columns:48px minmax(0,1fr)!important;gap:10px!important;align-items:center!important;margin:0 0 10px!important;padding-bottom:10px!important;border-bottom:1px solid #e2e8f0!important;}',
      '.spx-author-popover-avatar{width:48px!important;height:48px!important;object-fit:cover!important;border-radius:8px!important;background:#f1f5f9!important;}',
      '.spx-author-popover-name{font-size:16px!important;font-weight:800!important;color:#075985!important;line-height:1.25!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-author-popover-link{display:inline-block!important;margin-top:3px!important;color:#64748b!important;font-size:12px!important;text-decoration:none!important;}',
      '.spx-author-popover-lines{display:flex!important;flex-direction:column!important;gap:5px!important;color:#475569!important;}',
      '.spx-author-popover-line{display:block!important;word-break:break-word!important;}',
      '.spx-fold-box{border:1px dashed var(--spx-line);background:var(--spx-bg);padding:8px;margin:8px 0;border-radius:6px;color:var(--spx-sub);}',
      '.spx-fold-box button{margin-left:8px;border:1px solid var(--spx-line);background:#fff;border-radius:5px;padding:2px 8px;cursor:pointer;}',
      '.spx-read-thread{opacity:.48;}',
      '.spx-hidden-rule{'+CSS_HIDE+'}',
      '.spx-unread-hidden{'+CSS_HIDE+'}',
      '.spx-thread-tools{display:inline-flex;gap:4px;margin-left:8px;vertical-align:middle;}',
      '.spx-thread-tools button{border:1px solid var(--spx-line);background:#fff;border-radius:4px;color:var(--spx-sub);font-size:12px;line-height:16px;padding:0 5px;cursor:pointer;}',
      '.spx-thread-tools button:hover{color:var(--spx-accent);border-color:var(--spx-accent);}',
      '.spx-watch-badge{display:inline-block;margin-left:5px;color:var(--spx-warn);font-weight:bold;}',
      '.spx-forum-tools{box-sizing:border-box;display:flex;align-items:center;gap:8px;width:min(var(--spx-page-max),calc(100vw - var(--spx-page-space)));margin:0 auto 10px;padding:10px 12px;background:#fff;border:1px solid #d7e1eb;border-radius:var(--spx-radius);box-shadow:0 4px 14px rgba(15,23,42,.05);}',
      '.spx-forum-tools input{box-sizing:border-box;flex:1;min-width:180px;height:30px;border:1px solid var(--spx-line);border-radius:6px;padding:0 9px;font-size:13px;}',
      '.spx-forum-tools button{height:30px;border:1px solid var(--spx-line);border-radius:6px;background:#fff;color:var(--spx-text);padding:0 9px;cursor:pointer;font-size:12px;}',
      '.spx-forum-tools button:hover{border-color:var(--spx-accent);color:var(--spx-accent);}',
      '.spx-forum-resource-filters{display:flex;align-items:center;gap:5px;max-width:46%;overflow-x:auto;scrollbar-width:thin;}',
      '.spx-forum-tools .spx-resource-filter-active{border-color:#93c5fd;background:#dbeafe;color:#1d4ed8;font-weight:900;}',
      '.spx-resource-badges{display:inline-flex;align-items:center;flex-wrap:wrap;gap:4px;margin-left:6px;vertical-align:middle;}',
      '.spx-resource-badge{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:21px;padding:0 7px;border:1px solid #bfdbfe;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:11px;font-weight:900;line-height:1;white-space:nowrap;cursor:pointer;}',
      '.spx-resource-badge:hover{filter:brightness(.97);transform:translateY(-1px);}',
      '.spx-resource-badge-guess{border-style:dashed;background:#fff;color:#475467;}',
      '.spx-resource-badge-baidu{border-color:#bfdbfe;background:#eff6ff;color:#1d4ed8;}.spx-resource-badge-quark{border-color:#99f6e4;background:#f0fdfa;color:#0f766e;}.spx-resource-badge-pikpak{border-color:#ddd6fe;background:#faf5ff;color:#6d28d9;}.spx-resource-badge-magnet{border-color:#fed7aa;background:#fff7ed;color:#c2410c;}.spx-resource-badge-torrent{border-color:#bae6fd;background:#f0f9ff;color:#0369a1;}.spx-resource-badge-archive{border-color:#fbcfe8;background:#fdf2f8;color:#be185d;}.spx-resource-badge-ed2k{border-color:#cbd5e1;background:#f1f5f9;color:#334155;}.spx-resource-badge-external{border-color:#fde68a;background:#fffbeb;color:#92400e;}.spx-resource-badge-cloud{border-color:#cbd5e1;background:#f8fafc;color:#475569;}',
      '.spx-module-nav-ready.spx-home-dashboard #content,.spx-module-nav-ready.spx-forum-dashboard #content{display:grid!important;grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;align-items:start!important;}',
      '.spx-module-body{grid-column:2!important;min-width:0!important;display:block!important;}',
      '.spx-module-nav-ready.spx-home-dashboard #content>*:not(.spx-module-nav):not(.spx-module-body),.spx-module-nav-ready.spx-forum-dashboard #content>*:not(.spx-module-nav):not(.spx-module-body){grid-column:2!important;min-width:0!important;}',
      '.spx-module-nav-ready .spx-forum-tools{width:100%!important;margin:0 0 10px!important;}',
      '.spx-module-nav{grid-column:1!important;position:sticky!important;top:46px!important;z-index:20!important;'+CSS_BOX+'display:flex!important;flex-direction:column!important;align-self:start!important;gap:8px!important;max-height:calc(100vh - var(--spx-module-max-offset))!important;padding:12px!important;overflow:auto!important;border:1px solid var(--spx-line)!important;border-radius:var(--spx-radius-lg)!important;background:var(--spx-panel)!important;box-shadow:var(--spx-shadow-card)!important;color:var(--spx-text)!important;scrollbar-width:thin!important;}',
      '.spx-module-nav-title{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;padding:0 2px 8px!important;border-bottom:1px solid var(--spx-line-soft)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;line-height:1.25!important;white-space:nowrap!important;}',
      '.spx-module-nav-title strong{color:var(--spx-strong)!important;font-size:12px!important;font-weight:900!important;line-height:1.2!important;}.spx-module-nav-title span{flex:none!important;color:var(--spx-muted)!important;font-size:11px!important;font-weight:900!important;}',
      '.spx-module-nav-controls{display:flex!important;align-items:center!important;width:100%!important;min-width:0!important;}',
      'input.spx-module-nav-search{'+CSS_BOX+'flex:1 1 auto!important;width:100%!important;min-width:0!important;max-width:100%!important;height:30px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:9px!important;background:var(--spx-input-bg)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:800!important;line-height:30px!important;outline:none!important;}',
      'input.spx-module-nav-search:focus{border-color:var(--spx-accent)!important;box-shadow:0 0 0 2px rgba(37,99,235,.12)!important;}',
      '.spx-module-nav-group{display:flex!important;flex-direction:column!important;gap:5px!important;}',
      '.spx-module-nav-section{appearance:none!important;'+CSS_BOX+'display:flex!important;align-items:center!important;gap:7px!important;width:100%!important;min-height:var(--spx-module-section-height)!important;margin:5px 0 1px!important;padding:0 var(--spx-module-section-padding)!important;border:0!important;border-radius:calc(var(--spx-module-item-radius) - 2px)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;font-size:11px!important;font-weight:900!important;line-height:1.2!important;text-align:left!important;white-space:nowrap!important;cursor:pointer!important;}',
      '.spx-module-nav-section:before{content:"▸";font-size:10px!important;color:var(--spx-accent)!important;}',
      '.spx-module-nav-section[aria-expanded="true"]:before{content:"▾";}',
      '.spx-module-nav-section[aria-expanded="false"]:before{content:"▸";}',
      '.spx-module-nav-section:after{content:"";height:1px!important;flex:1!important;background:rgba(37,99,235,.18)!important;}',
      '.spx-module-nav-group.spx-module-nav-collapsed>.spx-module-nav-node{'+CSS_HIDE+'}',
      '.spx-module-nav[data-spx-module-nav-searching="1"] .spx-module-nav-group.spx-module-nav-collapsed>.spx-module-nav-node{display:flex!important;}',
      '.spx-module-nav-node{display:flex!important;flex-direction:column!important;gap:4px!important;}',
      '.spx-module-nav-children{display:flex!important;flex-direction:column!important;gap:4px!important;margin:2px 0 3px 11px!important;padding-left:9px!important;border-left:2px solid var(--spx-line-soft)!important;}',
      '.spx-module-nav-parent-title{'+CSS_BOX+'display:flex!important;align-items:center!important;min-height:var(--spx-module-parent-height)!important;padding:0 var(--spx-module-section-padding)!important;border-radius:var(--spx-module-item-radius)!important;background:var(--spx-panel-muted)!important;color:var(--spx-strong)!important;font-size:12px!important;font-weight:900!important;line-height:1.25!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;}',
      '.spx-module-nav-item{'+CSS_BOX+'display:flex!important;align-items:center!important;justify-content:space-between!important;gap:8px!important;min-height:var(--spx-module-item-height)!important;width:100%!important;padding:0 var(--spx-module-item-padding)!important;border:1px solid transparent!important;border-radius:var(--spx-module-item-radius)!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;text-decoration:none!important;font-size:12px!important;font-weight:900!important;line-height:1.2!important;white-space:nowrap!important;cursor:pointer!important;}',
      '.spx-module-nav-item.spx-module-nav-level-3{min-height:var(--spx-module-child-height)!important;padding:0 var(--spx-module-section-padding)!important;border-radius:calc(var(--spx-module-item-radius) - 2px)!important;background:transparent!important;color:var(--spx-link)!important;font-size:11px!important;font-weight:800!important;}',
      '.spx-module-nav-label{'+CSS_ELLIPSIS+'}',
      '.spx-module-nav-pin{flex:none;display:inline-flex!important;align-items:center!important;justify-content:center!important;width:20px!important;height:20px!important;border-radius:999px!important;color:var(--spx-muted)!important;font-size:13px!important;line-height:1!important;opacity:.72!important;cursor:pointer!important;}',
      '.spx-module-nav-pin:hover,.spx-module-nav-pin:focus-visible{background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;opacity:1!important;outline:none!important;}',
      '.spx-module-nav-item.spx-module-nav-pinned .spx-module-nav-pin{color:var(--spx-accent)!important;opacity:1!important;}',
      '.spx-module-nav-search-hidden{'+CSS_HIDE+'}',
      '.spx-module-nav-item:hover,.spx-module-nav-item:focus-visible{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;text-decoration:none!important;outline:none!important;}',
      '.spx-module-nav-item.spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent)!important;color:#fff!important;box-shadow:0 7px 16px rgba(37,99,235,.2)!important;}',
      '.spx-module-nav-count{display:inline-flex!important;align-items:center!important;justify-content:center!important;min-width:22px!important;height:20px!important;padding:0 6px!important;border-radius:999px!important;background:var(--spx-panel)!important;color:var(--spx-sub)!important;font-size:11px!important;font-weight:900!important;font-variant-numeric:tabular-nums!important;}',
      '.spx-module-nav-item.spx-active .spx-module-nav-count{background:rgba(255,255,255,.22)!important;color:#fff!important;}',
      '.spx-filter-hidden{'+CSS_HIDE+'}',
      '.spx-resource-filter-hidden{'+CSS_HIDE+'}',
      '.spx-module-filter-hidden{'+CSS_HIDE+'}',
      '.spx-forum-prelude-hidden{'+CSS_HIDE+'}',
      '.spx-thread-row-hidden{'+CSS_HIDE+'}',
      '.spx-forum-section-title{'+CSS_BOX+'display:flex!important;align-items:center!important;justify-content:space-between!important;gap:12px!important;min-height:38px!important;margin:0 0 10px!important;color:var(--spx-strong)!important;}',
      '.spx-forum-section-main{display:flex!important;align-items:center!important;gap:10px!important;min-width:0!important;}',
      '.spx-forum-section-title strong{display:block!important;'+CSS_ELLIPSIS+'font-size:16px!important;font-weight:900!important;}',
      '.spx-forum-section-title span{flex:none!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-forum-section-actions{display:flex!important;align-items:center!important;justify-content:flex-end!important;gap:8px!important;flex:none!important;flex-wrap:wrap!important;margin-left:auto!important;}',
      '.spx-forum-post-link img{display:block!important;max-width:100%!important;height:auto!important;}',
      '.spx-forum-gallery-link{flex:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:30px!important;padding:0 11px!important;border:1px solid #fecaca!important;border-radius:8px!important;background:#fef2f2!important;color:#b91c1c!important;font-size:12px!important;font-weight:900!important;text-decoration:none!important;}',
      '.spx-forum-gallery-link:hover,.spx-forum-gallery-link:focus-visible{border-color:#f87171!important;background:#fee2e2!important;color:#991b1b!important;text-decoration:none!important;outline:none!important;}',
      '.spx-forum-gallery-link.spx-forum-list-link{border-color:#bfdbfe!important;background:#eff6ff!important;color:#1d4ed8!important;}',
      '.spx-forum-gallery-link.spx-forum-list-link:hover,.spx-forum-gallery-link.spx-forum-list-link:focus-visible{border-color:#60a5fa!important;background:#dbeafe!important;color:#1e40af!important;}',
      '.spx-forum-gallery-page #wall{'+CSS_BOX+'width:100%!important;max-width:100%!important;margin:0!important;padding:0!important;overflow:visible!important;background:transparent!important;}',
      '.spx-forum-gallery-page #wall .stream{position:static!important;display:grid!important;grid-template-columns:repeat(auto-fill,minmax(224px,1fr))!important;gap:14px!important;width:100%!important;height:auto!important;margin:0!important;padding:0!important;list-style:none!important;}',
      '.spx-forum-gallery-page #wall .stream>li{position:static!important;left:auto!important;top:auto!important;right:auto!important;bottom:auto!important;'+CSS_BOX+'display:block!important;width:auto!important;height:auto!important;margin:0!important;padding:0!important;list-style:none!important;}',
      '.spx-forum-gallery-page #wall .stream>li.spx-filter-hidden,.spx-forum-gallery-page #wall .stream>li.spx-resource-filter-hidden,.spx-forum-gallery-page #wall .stream>li.spx-hidden-rule,.spx-forum-gallery-page #wall .stream>li.spx-unread-hidden,.spx-forum-gallery-page #wall .stream>li.spx-thread-row-hidden{'+CSS_HIDE+'}',
      '.spx-forum-gallery-page #wall .inner,.spx-forum-gallery-page #wall .spx-gallery-card-inner{'+CSS_BOX+'display:flex!important;flex-direction:column!important;gap:0!important;min-height:100%!important;margin:0!important;padding:0!important;border:1px solid var(--spx-line)!important;border-radius:12px!important;background:var(--spx-panel)!important;box-shadow:0 8px 20px rgba(15,23,42,.07)!important;overflow:hidden!important;color:var(--spx-text)!important;}',
      '.spx-forum-gallery-page #wall .inner:hover{border-color:#bfdbfe!important;box-shadow:0 14px 30px rgba(15,23,42,.11)!important;}',
      '.spx-forum-gallery-page #wall .section-title{'+CSS_BOX+'display:block!important;width:100%!important;max-width:100%!important;height:auto!important;min-height:0!important;margin:0!important;padding:12px 12px 7px!important;border:0!important;background:transparent!important;font-size:14px!important;line-height:1.45!important;overflow:visible!important;}',
      '.spx-forum-gallery-page #wall .section-title a{display:block!important;color:var(--spx-strong)!important;font-size:14px!important;font-weight:900!important;line-height:1.45!important;text-decoration:none!important;word-break:break-word!important;}',
      '.spx-forum-gallery-page #wall .section-title a:hover{color:var(--spx-accent)!important;text-decoration:none!important;}',
      '.spx-forum-gallery-page #wall .section-text{'+CSS_BOX+'display:block!important;width:100%!important;max-width:100%!important;margin:0!important;padding:0 12px 12px!important;clear:both!important;color:var(--spx-sub)!important;font-size:12px!important;line-height:1.55!important;overflow:visible!important;}',
      '.spx-forum-gallery-page #wall .section-text>span{float:none!important;display:block!important;width:100%!important;max-width:100%!important;margin:0 0 8px!important;padding:0!important;clear:both!important;color:var(--spx-sub)!important;font-size:12px!important;line-height:1.45!important;}',
      '.spx-forum-gallery-page #wall .section-text>span:first-child{'+CSS_HIDE+'}',
      '.spx-forum-gallery-page #wall .section-text>div{display:block!important;width:100%!important;max-width:100%!important;margin:0!important;clear:both!important;}',
      '.spx-forum-gallery-page #wall .section-text img{display:block!important;width:100%!important;max-width:100%!important;height:176px!important;object-fit:cover!important;border:1px solid var(--spx-line-soft)!important;border-radius:10px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-forum-gallery-page #wall .section-text a#favor{'+CSS_HIDE+'}',
      '.spx-forum-gallery-page #wall .section-intro{'+CSS_BOX+'position:static!important;left:auto!important;right:auto!important;top:auto!important;bottom:auto!important;z-index:auto!important;display:block!important;width:100%!important;max-width:100%!important;flex:none!important;margin:auto 0 0!important;padding:9px 12px!important;border-top:1px solid var(--spx-line-soft)!important;background:var(--spx-panel-muted)!important;color:var(--spx-sub)!important;font-size:12px!important;line-height:1.45!important;overflow:hidden!important;}',
      '.spx-forum-gallery-page #wall .section-intro table,.spx-forum-gallery-page #wall .section-intro tbody,.spx-forum-gallery-page #wall .section-intro tr,.spx-forum-gallery-page #wall .section-intro td{display:block!important;width:auto!important;margin:0!important;padding:0!important;border:0!important;background:transparent!important;}',
      '.spx-forum-gallery-page #wall .section-intro,.spx-forum-gallery-page #wall .section-intro *:not(a){color:var(--spx-sub)!important;text-shadow:none!important;opacity:1!important;filter:none!important;}',
      '.spx-forum-gallery-page #wall .section-intro a,.spx-forum-gallery-page #wall .section-intro a *{color:var(--spx-link)!important;font-weight:800!important;text-decoration:none!important;text-shadow:none!important;opacity:1!important;filter:none!important;}',
      '.spx-forum-gallery-page #wall .clear{'+CSS_HIDE+'}',
      '.spx-gallery-card-tools{'+CSS_BOX+'display:flex!important;align-items:center!important;gap:6px!important;width:100%!important;max-width:100%!important;flex:none!important;margin:0!important;padding:9px 12px 12px!important;clear:both!important;border-top:1px solid var(--spx-line-soft)!important;background:var(--spx-panel)!important;}',
      '.spx-gallery-card-tools button{'+CSS_BOX+'min-height:28px!important;padding:0 10px!important;border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;font-size:12px!important;font-weight:900!important;line-height:26px!important;cursor:pointer!important;}',
      '.spx-gallery-card-tools button:hover,.spx-gallery-card-tools button:focus-visible{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;outline:none!important;}',
      '.spx-gallery-card-tools button:disabled{cursor:default!important;opacity:.62!important;}',
      '@media(max-width:900px){.spx-forum-gallery-page #wall .stream{grid-template-columns:repeat(auto-fill,minmax(156px,1fr))!important;gap:10px!important}.spx-forum-gallery-page #wall .section-title{padding:10px 10px 6px!important}.spx-forum-gallery-page #wall .section-text{padding:0 10px 10px!important}.spx-forum-gallery-page #wall .section-text img{height:138px!important}.spx-forum-gallery-page #wall .section-intro{padding:8px 10px!important}.spx-gallery-card-tools{gap:5px!important;padding:8px 10px 10px!important}.spx-gallery-card-tools button{flex:1 1 0!important;min-width:0!important;padding:0 6px!important}}',
      '.spx-post-tools{display:flex;gap:6px;justify-content:flex-end;margin:4px 0;}',
      '.spx-post-tools button{border:1px solid var(--spx-line);background:#fff;border-radius:5px;padding:2px 8px;cursor:pointer;color:var(--spx-sub);}',
      '.spx-auto-buy-status{box-sizing:border-box;margin:8px 0;padding:8px 10px;border:1px solid #99f6e4;border-radius:6px;background:#f0fdfa;color:#0f766e;font-size:13px;line-height:1.45;}',
      '.spx-auto-buy-status.spx-error{border-color:#fecaca;background:#fef2f2;color:#b91c1c;}',
      '.spx-post-hidden{'+CSS_HIDE+'}',
      '.spx-site-shell .spx-post-shell-hidden,.spx-site-shell:not(.spx-reader) .spx-post-shell-hidden,.spx-reader .spx-post-shell-hidden,.spx-immersive-read .spx-post-shell-hidden{'+CSS_HIDE+'border:0!important;margin:0!important;padding:0!important;height:0!important;min-height:0!important;overflow:hidden!important;}',
      '.spx-preview-popover{position:fixed;z-index:100001;width:min(520px,calc(100vw - 28px));max-height:min(74vh,620px);overflow:auto;padding:12px;background:#fff;border:1px solid #cbd5e1;border-radius:10px;box-shadow:0 18px 48px rgba(15,23,42,.28);color:#172033;font:13px/1.55 '+SPX_FONT+';}',
      '.spx-preview-popover h4{display:block;margin:0 0 6px;font-size:15px;line-height:1.35;color:#0f172a;}',
      '.spx-preview-meta{margin-bottom:8px;color:#64748b;font-size:12px;}',
      '.spx-preview-chip-row{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 9px;}.spx-preview-chip{box-sizing:border-box;display:inline-flex;align-items:center;min-height:24px;padding:0 8px;border:1px solid #dbe4ee;border-radius:999px;background:#f8fafc;color:#475569;font-size:11px;font-weight:900;line-height:1.2;}.spx-preview-chip.spx-ok{border-color:#bbf7d0;background:#f0fdf4;color:#15803d;}.spx-preview-chip.spx-resource{border-color:#bae6fd;background:#f0f9ff;color:#0369a1;}.spx-preview-chip.spx-guess{border-style:dashed;color:#64748b;}.spx-preview-chip-row button{cursor:pointer;}.spx-preview-chip-row button:hover{border-color:#2563eb;color:#2563eb;background:#eff6ff;}',
      '.spx-preview-popover-actions{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px;}',
      '.spx-preview-popover-actions a,.spx-preview-popover-actions button{box-sizing:border-box;min-height:28px;border:1px solid #cbd5e1;border-radius:7px;background:#fff;color:#1f2937;padding:4px 9px;font-size:12px;line-height:18px;text-decoration:none;cursor:pointer;}',
      '.spx-preview-popover-actions a:hover,.spx-preview-popover-actions button:hover{border-color:#2563eb;color:#2563eb;}',
      '.spx-preview-popover-actions button:disabled{cursor:default;opacity:.62;}',
      '.spx-preview-text{max-height:120px;overflow:hidden;margin-bottom:10px;color:#334155;}',
      '.spx-preview-images{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px;}',
      '.spx-preview-images a{display:block;overflow:hidden;border:1px solid #e2e8f0;border-radius:7px;background:#f8fafc;}',
      '.spx-preview-images img{display:block;width:100%;height:118px;object-fit:cover;}',
      '.spx-preview-images img[data-spx-preview-lazy-src]{background:linear-gradient(135deg,#f1f5f9,#e2e8f0);opacity:.68;}',
      '.spx-preview-images img[data-spx-preview-loaded="1"]{opacity:1;transition:opacity .16s ease;}',
      '.spx-preview-status{color:#94a3b8;font-size:12px;}',
      '.spx-compact-read .user-info,.spx-compact-read .readprofile,.spx-hide-profile .user-pic,.spx-hide-profile .user-info,.spx-hide-profile .readprofile{'+CSS_HIDE+'}',
      '.spx-compact-read:not(.spx-reader) .tpc_content{font-size:15px;line-height:1.7;max-width:var(--spx-reader-line);}',
      '.spx-folded-quote{max-height:110px;overflow:hidden;position:relative;border-bottom:1px dashed var(--spx-line);}',
      '.spx-folded-quote:after{content:"";position:absolute;left:0;right:0;bottom:0;height:30px;background:linear-gradient(transparent,var(--spx-panel));}',
      '@media(max-width:900px){.spx-home-dashboard #content,.spx-forum-dashboard #content{width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}.spx-module-nav-ready.spx-home-dashboard #content,.spx-module-nav-ready.spx-forum-dashboard #content{display:grid!important;grid-template-columns:minmax(0,1fr)!important;gap:10px!important}.spx-module-nav-ready.spx-home-dashboard #content>*:not(.spx-module-nav),.spx-module-nav-ready.spx-forum-dashboard #content>*:not(.spx-module-nav),.spx-module-nav{grid-column:1!important}.spx-module-nav{position:static!important;display:flex!important;flex-direction:row!important;align-items:center!important;max-height:none!important;overflow-x:auto!important;overflow-y:hidden!important;padding:7px!important}.spx-module-nav-title{flex:none!important;padding:0 8px!important;border-bottom:0!important}.spx-module-nav-controls{flex:none!important;width:180px!important}.spx-module-nav-group,.spx-module-nav-node,.spx-module-nav-children{flex:none!important;display:flex!important;flex-direction:row!important;align-items:center!important;gap:6px!important;margin:0!important;padding:0!important;border-left:0!important}.spx-module-nav-section,.spx-module-nav-parent-title{flex:none!important}.spx-module-nav-parent-title{min-height:30px!important;border-radius:999px!important}.spx-module-nav-item{flex:none!important;width:auto!important;min-height:30px!important;border-radius:999px!important}.spx-home-dashboard #spx-home-grid{grid-template-columns:1fr!important}.spx-home-dashboard .spx-home-module,.spx-home-dashboard .spx-home-module[data-spx-large="1"]{grid-column:1!important}.spx-home-dashboard #header,.spx-home-dashboard #mainNav,.spx-home-dashboard #infobox,.spx-home-dashboard #notice,.spx-home-dashboard .spx-home-quick,.spx-forum-dashboard #header,.spx-forum-dashboard #mainNav,.spx-forum-dashboard #infobox,.spx-forum-dashboard #notice{width:calc(100vw - 16px)!important}.spx-home-dashboard .spx-home-module tr.tr3,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3{grid-template-columns:1fr!important;gap:4px!important}.spx-home-dashboard .spx-home-module tr.tr3>td:first-child,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td:first-child:not([id^="td_"]){display:none!important}.spx-forum-tools{width:100%!important;flex-wrap:wrap}.spx-forum-tools input{flex-basis:100%!important}}',
      '@media(max-width:760px){.spx-read-resource-rail{left:8px!important;right:8px!important;top:auto!important;bottom:calc(104px + env(safe-area-inset-bottom,0px))!important;width:auto!important;max-height:46vh!important;border-radius:18px!important}.spx-read-resource-rail-head{border-radius:18px 18px 0 0!important}.spx-read-resource-list{max-height:calc(46vh - 156px)!important}.spx-read-resource-launcher{right:8px!important;bottom:calc(104px + env(safe-area-inset-bottom,0px))!important;min-width:88px!important;height:40px!important}.spx-read-resource-actions button,.spx-read-resource-filters button{flex:1 1 auto!important}.spx-read-resource-card-actions{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr))!important}.spx-read-resource-card-actions button{min-width:0!important;padding:0 5px!important}}',
      '@media(max-width:760px){.spx-preview-lightbox{padding:0!important}.spx-preview-lightbox-shell{border:0!important;border-radius:0!important}.spx-preview-lightbox-toolbar{align-items:flex-start!important;min-height:0!important;padding:8px!important}.spx-preview-lightbox-actions{gap:4px!important}.spx-preview-lightbox button{height:30px!important;padding:0 8px!important}.spx-preview-lightbox-canvas{padding:22px 50px!important}.spx-preview-lightbox-nav{width:38px!important;height:54px!important;font-size:26px!important}.spx-preview-lightbox-prev{left:6px!important}.spx-preview-lightbox-next{right:6px!important}.spx-preview-lightbox-caption{padding:6px 9px!important}.spx-preview-lightbox-help{display:none!important}.spx-reader body{font-size:16px!important}.spx-reader #wrapA{width:calc(100vw - 14px)!important;margin:0 7px!important}.spx-reader .tpc_content{font-size:16px!important;line-height:1.76!important;padding:12px!important}.spx-reader .tpc_content #read_tpc,.spx-reader .tpc_content>.f14{font-size:16px!important;line-height:1.76!important}.spx-reader .spx-post-body-split,.spx-immersive-read .spx-post-body-split{display:flex!important;flex-direction:column!important;gap:12px!important;padding:14px!important}.spx-reader .spx-post-body-split .tpc_content,.spx-immersive-read .spx-post-body-split .tpc_content{padding:0!important}.spx-reader .spx-preview-panel,.spx-immersive-read .spx-preview-panel{width:auto!important;max-height:360px!important;margin:0!important;padding:10px!important}.spx-reader .spx-preview-grid,.spx-immersive-read .spx-preview-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important;gap:8px!important}.spx-reader .spx-preview-item img,.spx-immersive-read .spx-preview-item img{height:132px!important}.spx-preview-panel.spx-preview-drawer{left:8px!important;right:8px!important;top:auto!important;bottom:calc(62px + env(safe-area-inset-bottom,0px))!important;width:auto!important;max-height:58vh!important;border-radius:16px!important}.spx-preview-panel.spx-preview-drawer.spx-preview-collapsed{left:auto!important;right:8px!important;bottom:calc(62px + env(safe-area-inset-bottom,0px))!important;top:auto!important;width:46px!important;height:126px!important}.spx-immersive-read #wrapA,.spx-immersive-read #main,.spx-immersive-read #content{width:100vw!important;margin:0!important}.spx-immersive-read table.js-post{width:calc(100vw - 14px)!important;margin:10px 7px!important}.spx-immersive-read .h1,.spx-immersive-read [id^="subject_"]{font-size:19px!important;padding:16px 14px 6px!important}.spx-immersive-read .tpc_content{font-size:16px!important;line-height:1.76!important;padding:14px!important}.spx-immersive-read .tpc_content #read_tpc,.spx-immersive-read .tpc_content>.f14{font-size:16px!important;line-height:1.76!important}.spx-toolbar{left:8px!important;right:8px!important;bottom:calc(8px + env(safe-area-inset-bottom,0px))!important;width:auto!important;flex-direction:row!important;justify-content:space-between!important;padding:6px!important;border-radius:16px!important}.spx-toolbar button{flex:1 1 0!important;width:auto!important;height:34px!important;font-size:12px}.spx-settings,.spx-watch-center,.spx-toolbox{left:8px!important;right:8px!important;bottom:calc(58px + env(safe-area-inset-bottom,0px))!important;width:auto!important;max-height:calc(100vh - 92px - env(safe-area-inset-bottom,0px))!important;border-radius:18px!important}.spx-settings-grid,.spx-toolbox-grid{grid-template-columns:1fr!important}.spx-toolbox-body{max-height:calc(100vh - 150px - env(safe-area-inset-bottom,0px));padding:11px}.spx-toolbox-action{min-height:54px!important}.spx-watch-controls input,.spx-watch-controls select{flex:1 1 100%!important;max-width:none!important}.spx-resource-panel,#spx-resource-center{width:auto!important}.spx-watch-center-header{top:-14px}.spx-toolbox-desc{-webkit-line-clamp:1}}',
    ];
  }

  function getInjectedThemeOverrideStyleRules() {
    return [
      '.spx-theme-night,.spx-theme-night body{background:var(--spx-page-bg)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night #wrapA,.spx-theme-night #main,.spx-theme-night #content,.spx-theme-night.spx-reader #wrapA,.spx-theme-night.spx-reader #main,.spx-theme-night.spx-reader #content,.spx-theme-night.spx-immersive-read #wrapA,.spx-theme-night.spx-immersive-read #main,.spx-theme-night.spx-immersive-read #content{background:var(--spx-page-bg)!important;background-image:none!important;color:var(--spx-text)!important;}',
      '.spx-theme-night a{color:var(--spx-link)!important;}',
      '.spx-theme-night input,.spx-theme-night textarea,.spx-theme-night select{background:var(--spx-input-bg)!important;color:var(--spx-text)!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night .t,.spx-theme-night .t3,.spx-theme-night .t5,.spx-theme-night .bdbA,.spx-theme-night table.js-post,.spx-theme-night .spx-home-module,.spx-theme-night .spx-watch-center,.spx-theme-night .spx-workbench,.spx-theme-night .spx-settings,.spx-theme-night .spx-toolbox,.spx-theme-night .spx-command-palette,.spx-theme-night .spx-command-detail,.spx-theme-night .spx-preview-panel,.spx-theme-night .spx-preview-popover,.spx-theme-night .spx-author-popover,.spx-theme-night .spx-quick-reply,.spx-theme-night .spx-forum-tools,.spx-theme-night .spx-read-resource-rail,.spx-theme-night .spx-read-resource-card,.spx-theme-night .spx-read-resource-filters,.spx-theme-night .spx-read-summary-card,.spx-theme-night .spx-batch-confirm,.spx-theme-night .spx-data-health,.spx-theme-night .spx-storage-usage-row,.spx-theme-night .spx-storage-suggestions{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-card)!important;}',
      '.spx-theme-night .tr1,.spx-theme-night .tr2,.spx-theme-night .tr3,.spx-theme-night td,.spx-theme-night th,.spx-theme-night .spx-home-module tr.tr3,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3{background:var(--spx-row-bg)!important;border-color:var(--spx-line-soft)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-home-module tr.tr3:hover,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3:hover{background:var(--spx-row-hover)!important;}',
      '.spx-theme-night .spx-home-module>h2,.spx-theme-night .spx-home-module .h,.spx-theme-night .spx-settings-header,.spx-theme-night .spx-settings-footer,.spx-theme-night .spx-toolbox-header,.spx-theme-night .spx-watch-center-header,.spx-theme-night .spx-workbench-head,.spx-theme-night .spx-workbench-tabs,.spx-theme-night .spx-read-resource-rail-head,.spx-theme-night .spx-preview-lightbox-toolbar,.spx-theme-night .spx-preview-lightbox-caption{background:var(--spx-panel-muted)!important;border-color:var(--spx-line-soft)!important;color:var(--spx-strong)!important;}',
      '.spx-theme-night .spx-read-resource-rail button{background:var(--spx-panel)!important;color:var(--spx-text)!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night .spx-toolbox-action,.spx-theme-night .spx-watch-item,.spx-theme-night .spx-watch-controls,.spx-theme-night .spx-settings-section,.spx-theme-night .spx-preview-item,.spx-theme-night .spx-preview-images a,.spx-theme-night .spx-read-summary-metric,.spx-theme-night .spx-content-stat,.spx-theme-night .spx-content-recent-section,.spx-theme-night .spx-content-recent,.spx-theme-night .spx-batch-list,.spx-theme-night .spx-batch-item,.spx-theme-night .spx-batch-impact,.spx-theme-night .spx-fold-box,.spx-theme-night .spx-auto-resource-actions a,.spx-theme-night .spx-auto-resource-actions button{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night button,.spx-theme-night .spx-toolbar button,.spx-theme-night .spx-thread-tools button,.spx-theme-night .spx-post-tools button,.spx-theme-night .spx-watch-actions button,.spx-theme-night .spx-watch-actions a{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-toolbox-action:hover,.spx-theme-night .spx-toolbox-action:focus-visible,.spx-theme-night button:hover,.spx-theme-night .spx-toolbar button:hover{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-toolbox-action.spx-active,.spx-theme-night .spx-toolbar .spx-active,.spx-theme-night .spx-settings .spx-primary{border-color:var(--spx-accent)!important;background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-toolbox-key,.spx-theme-night .spx-status-badge,.spx-theme-night .spx-home-badge,.spx-theme-night .spx-resource-badge,.spx-theme-night .spx-preview-chip,.spx-theme-night .spx-preview-download-badge,.spx-theme-night .spx-command-pill,.spx-theme-night .spx-forum-dashboard-chip,.spx-theme-night .spx-task-claim-reward,.spx-theme-night .spx-forum-tools .spx-resource-filter-active,.spx-theme-night td[id^="td_"] .s8{background:var(--spx-accent-soft)!important;border-color:var(--spx-line)!important;color:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-status-badge.spx-status-todo,.spx-theme-night .spx-preview-download-badge.spx-zip,.spx-theme-night .spx-command-pill.spx-warn,.spx-theme-night .spx-resource-badge-magnet,.spx-theme-night .spx-resource-badge-external,.spx-theme-night .spx-forum-dashboard-chip.spx-amber{background:rgba(251,191,36,.16)!important;border-color:rgba(251,191,36,.42)!important;color:#facc15!important;}',
      '.spx-theme-night .spx-status-badge.spx-status-done,.spx-theme-night .spx-preview-chip.spx-ok,.spx-theme-night .spx-command-pill.spx-ok,.spx-theme-night .spx-resource-badge-quark,.spx-theme-night .spx-forum-dashboard-chip.spx-green{background:rgba(74,222,128,.16)!important;border-color:rgba(74,222,128,.42)!important;color:#86efac!important;}',
      '.spx-theme-night .spx-status-badge.spx-status-failed,.spx-theme-night .spx-status-badge.spx-status-invalid,.spx-theme-night .spx-preview-download-badge.spx-fail,.spx-theme-night .spx-resource-badge-archive{background:var(--spx-danger-soft)!important;border-color:rgba(248,113,113,.42)!important;color:var(--spx-danger)!important;}',
      '.spx-theme-night .spx-resource-badge-guess,.spx-theme-night .spx-resource-badge-ed2k,.spx-theme-night .spx-resource-badge-cloud{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-sub)!important;}',
      '.spx-theme-night #infobox.spx-profile-infobox-restored,.spx-theme-night .spx-profile-infobox-restored{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-card)!important;}',
      '.spx-theme-night .spx-profile-infobox-meta,.spx-theme-night .spx-profile-infobox-honor{color:var(--spx-text)!important;}.spx-theme-night .spx-profile-infobox-hot,.spx-theme-night .spx-profile-infobox-hot-label{color:var(--spx-sub)!important;}',
      '.spx-theme-night .spx-preview-lightbox{background:rgba(0,0,0,.92)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-preview-lightbox-shell,.spx-theme-night .spx-preview-lightbox-stage,.spx-theme-night .spx-preview-lightbox-strip{background:#070908!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night .spx-preview-lightbox-image{background:#0b0f0c!important;}',
      '.spx-theme-night .spx-danger,.spx-theme-night .spx-watch-actions .spx-danger,.spx-theme-night .spx-settings .spx-danger{border-color:var(--spx-danger)!important;background:var(--spx-danger-soft)!important;color:var(--spx-danger)!important;}',
      '.spx-theme-night #peacemakerconfig>div:not([hidden]),.spx-theme-night #peacemakerconfig>div:not([hidden])>div,.spx-theme-night.spx-home-dashboard #notice,.spx-theme-night.spx-home-dashboard .spx-home-quick a,.spx-theme-night.spx-search-page .t,.spx-theme-night.spx-profile-page .spx-account-tabs,.spx-theme-night.spx-profile-page #u-wrap,.spx-theme-night.spx-profile-page #u-wrap2,.spx-theme-night.spx-profile-page #set-side-wrap,.spx-theme-night.spx-profile-page #set-content-wrap{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:var(--spx-shadow-card)!important;}',
      '.spx-theme-night.spx-home-dashboard #notice td,.spx-theme-night.spx-home-dashboard .spx-home-quick a span,.spx-theme-night.spx-home-dashboard .spx-home-module tr.tr3>td,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td,.spx-theme-night.spx-forum-dashboard td[id^="td_"],.spx-theme-night.spx-search-page .tr1,.spx-theme-night.spx-search-page .tr2,.spx-theme-night.spx-search-page .tr3,.spx-theme-night.spx-profile-page #set-side .set-h2{background:transparent!important;border-color:var(--spx-line-soft)!important;color:var(--spx-sub)!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module [id^="fn_"],.spx-theme-night.spx-home-dashboard .spx-home-module [id^="fn_"] a,.spx-theme-night.spx-forum-dashboard td[id^="td_"] a[id^="a_ajax_"],.spx-theme-night.spx-forum-dashboard [id^="td_"] a[href*="read.php"],.spx-theme-night.spx-profile-page #set-menu>li>a,.spx-theme-night.spx-profile-page #u-top-nav .b>li>a,.spx-theme-night.spx-profile-page .spx-account-tabs a{color:var(--spx-link)!important;}',
      '.spx-theme-night.spx-profile-page #set-menu>li>a,.spx-theme-night.spx-profile-page #u-top-nav .b>li>a,.spx-theme-night.spx-profile-page .spx-account-tabs a,.spx-theme-night.spx-search-page .spx-module-body input[type="text"],.spx-theme-night.spx-search-page .spx-module-body input[type="search"],.spx-theme-night.spx-search-page .spx-module-body input[name="keyword"],.spx-theme-night.spx-search-page .spx-module-body input[name="username"]{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-preview-popover h4{color:var(--spx-strong)!important;}',
      '.spx-theme-night .spx-preview-popover .spx-preview-meta,.spx-theme-night .spx-preview-popover .spx-preview-status{color:var(--spx-muted)!important;}',
      '.spx-theme-night .spx-preview-popover-actions a,.spx-theme-night .spx-preview-popover-actions button{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-preview-popover-actions a:hover,.spx-theme-night .spx-preview-popover-actions button:hover{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-preview-popover .spx-preview-text{color:var(--spx-text)!important;}',
      '.spx-theme-night .spx-preview-popover .spx-preview-images a{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night .spx-read-summary-track{background:var(--spx-line)!important;}.spx-theme-night .spx-preview-chip,.spx-theme-night .spx-chip{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-sub)!important;}',
    ];
  }

  function getInjectedPreviewParityStyleRules() {
    return [
      ':root,.spx-theme-clean{--spx-page-max:1480px;--spx-page-space:40px;--spx-radius:10px;--spx-radius-lg:12px;--spx-shadow-card:0 8px 18px rgba(15,23,42,.07);}',
      ':root.spx-theme-night,.spx-theme-night{--spx-page-max:1480px;}',
      '.spx-site-shell #mainNav,.spx-home-dashboard #mainNav,.spx-forum-dashboard #mainNav,.spx-immersive-read #mainNav{height:38px!important;padding:0 8px!important;border-radius:10px!important;box-shadow:0 6px 18px rgba(15,23,42,.12)!important;}',
      '.spx-site-shell #mainNav>div:not([style*="padding-left"]){'+CSS_HIDE+'}',
      '.spx-site-shell #mainNav>div[style*="padding-left"]{display:flex!important;align-items:center!important;gap:6px!important;height:38px!important;padding-left:8px!important;}',
      '.spx-site-shell #mainNav>div[style*="padding-left"] table,.spx-site-shell #mainNav>div[style*="padding-left"] tbody,.spx-site-shell #mainNav>div[style*="padding-left"] tr,.spx-site-shell #mainNav>div[style*="padding-left"] td{flex:none!important;height:38px!important;}',
      '.spx-site-shell #guide{height:38px!important;align-items:center!important;gap:6px!important;}',
      '.spx-site-shell #guide>li>a{height:28px!important;line-height:28px!important;padding:0 10px!important;border-radius:8px!important;font-size:12px!important;font-weight:800!important;}',
      '.spx-site-shell #guide>li.current>a,.spx-site-shell #guide>li.spx-nav-current>a,.spx-site-shell #guide>li>a.spx-nav-current{box-shadow:inset 0 -2px 0 var(--spx-accent)!important;}',
      '.spx-site-shell #peacemakerconfig>div:not([hidden]){top:38px!important;border-radius:10px!important;}',
      '.spx-site-shell #header,.spx-site-shell #mainNav,.spx-site-shell #infobox,.spx-site-shell #notice,.spx-site-shell #content,.spx-home-dashboard #header,.spx-home-dashboard #mainNav,.spx-home-dashboard #infobox,.spx-home-dashboard #notice,.spx-home-dashboard #content,.spx-forum-dashboard #header,.spx-forum-dashboard #mainNav,.spx-forum-dashboard #infobox,.spx-forum-dashboard #notice,.spx-forum-dashboard #content,.spx-immersive-read #header,.spx-immersive-read #mainNav,.spx-immersive-read #breadcrumbs{width:calc(100vw - 40px)!important;max-width:1480px!important;margin-left:auto!important;margin-right:auto!important;}',
      '.spx-home-dashboard #header,.spx-forum-dashboard #header,.spx-immersive-read #header{margin-top:18px!important;}',
      '.spx-home-dashboard #content,.spx-forum-dashboard #content{margin:14px auto 56px!important;}',
      '.spx-module-nav-ready.spx-home-dashboard #content,.spx-module-nav-ready.spx-forum-dashboard #content{grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;}',
      '.spx-module-nav-ready .spx-module-nav-host{display:grid!important;grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;align-items:start!important;width:calc(100vw - 40px)!important;max-width:1480px!important;margin:14px auto 56px!important;}',
      '.spx-module-nav-ready .spx-module-body{grid-column:2!important;min-width:0!important;display:block!important;}',
      '.spx-module-nav-ready .spx-module-nav-host>*:not(.spx-module-nav):not(.spx-module-body){grid-column:2!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host{display:grid!important;grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;align-items:start!important;width:calc(100vw - 40px)!important;max-width:1480px!important;margin:14px auto 56px!important;}',
      '.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host>.spx-module-nav{grid-column:1!important;width:auto!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host>.spx-module-body{grid-column:2!important;width:auto!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body>.bdbA,.spx-module-nav-ready.spx-search-page .spx-module-body>.t,.spx-module-nav-ready.spx-search-page .spx-module-body>.t3,.spx-module-nav-ready.spx-search-page .spx-module-body>.t5,.spx-module-nav-ready.spx-profile-page .spx-module-body>.bdbA,.spx-module-nav-ready.spx-profile-page .spx-module-body>.t,.spx-module-nav-ready.spx-profile-page .spx-module-body>.t3,.spx-module-nav-ready.spx-profile-page .spx-module-body>.t5{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body form{display:block!important;'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body form>.t,.spx-module-nav-ready.spx-search-page .spx-module-body form .t{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin-left:0!important;margin-right:0!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body table,.spx-module-nav-ready.spx-profile-page .spx-module-body table{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;table-layout:fixed!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body form table{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;table-layout:fixed!important;}',
      '.spx-module-nav-ready.spx-search-page .spx-module-body td,.spx-module-nav-ready.spx-search-page .spx-module-body th{'+CSS_BOX+'max-width:100%!important;word-break:break-word!important;}',
      '.spx-module-nav-ready.spx-profile-page .spx-module-body>#spx-account-tabs,.spx-module-nav-ready.spx-profile-page .spx-module-body>#u-wrap,.spx-module-nav-ready.spx-profile-page .spx-module-body>#u-wrap2,.spx-module-nav-ready.spx-profile-page .spx-module-body>#set-wrap,.spx-module-nav-ready.spx-profile-page .spx-module-body>#set-side-wrap,.spx-module-nav-ready.spx-profile-page .spx-module-body>#set-content-wrap{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-reader #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-reader #content.spx-module-nav-host>.spx-module-body{grid-column:2!important;width:auto!important;max-width:100%!important;min-width:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body>.bdbA{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 10px!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body #breadcrumbs{'+CSS_BOX+'display:flex!important;flex-wrap:wrap!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body #breadcrumbs .crumbs-item{min-width:0!important;max-width:100%!important;white-space:normal!important;word-break:break-word!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body table.js-post{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:14px 0!important;table-layout:fixed!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body table.js-post>tbody,.spx-module-nav-ready.spx-reader .spx-module-body table.js-post>tbody>tr{max-width:100%!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body table.js-post>tbody>tr>th.r_one{'+CSS_BOX+'width:auto!important;max-width:100%!important;min-width:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body table.js-post>tbody>tr>th.r_two{'+CSS_BOX+'max-width:128px!important;min-width:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-reader .spx-module-body .spx-post-tools,.spx-module-nav-ready.spx-reader .spx-module-body .tiptop,.spx-module-nav-ready.spx-reader .spx-module-body .tipad,.spx-module-nav-ready.spx-reader .spx-module-body .readbot,.spx-module-nav-ready.spx-reader .spx-module-body .spx-post-body-split,.spx-module-nav-ready.spx-reader .spx-module-body .tpc_content,.spx-module-nav-ready.spx-reader .spx-module-body .signature,.spx-module-nav-ready.spx-reader .spx-module-body .sigline{'+CSS_BOX+'max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host{display:grid!important;grid-template-columns:minmax(196px,var(--spx-module-width)) minmax(0,1fr)!important;gap:14px!important;align-items:start!important;width:calc(100vw - 40px)!important;max-width:1480px!important;margin:14px auto 56px!important;}',
      '.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-nav{grid-column:1!important;width:auto!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-body{grid-column:2!important;display:block!important;width:auto!important;max-width:100%!important;min-width:0!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body>.bdbA,.spx-module-nav-ready.spx-task-page .spx-module-body>.spx-task-breadcrumb-block{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-breadcrumb-block #breadcrumbs,.spx-module-nav-ready.spx-task-page .spx-module-body>#breadcrumbs{'+CSS_BOX+'display:flex!important;flex-wrap:wrap!important;align-items:center!important;width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-breadcrumb-block #breadcrumbs .crumbs-item,.spx-module-nav-ready.spx-task-page .spx-module-body>#breadcrumbs .crumbs-item{min-width:0!important;max-width:100%!important;white-space:normal!important;word-break:break-word!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body>.t,.spx-module-nav-ready.spx-task-page .spx-module-body>.t3,.spx-module-nav-ready.spx-task-page .spx-module-body>.t5{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0 0 14px!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body table{'+CSS_BOX+'width:100%!important;max-width:100%!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body td,.spx-module-nav-ready.spx-task-page .spx-module-body th{max-width:100%!important;word-break:break-word!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-module-body img{max-width:100%!important;height:auto!important;}',
      '.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-body.spx-task-layout-body,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-body.spx-task-layout-body{display:grid!important;grid-template-columns:minmax(240px,320px) minmax(0,1fr)!important;gap:18px!important;align-items:start!important;overflow:visible!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-layout-body>.bdbA,.spx-module-nav-ready.spx-task-page .spx-task-layout-body>.spx-task-breadcrumb-block{grid-column:1/-1!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-side-stack{grid-column:1!important;display:flex!important;flex-direction:column!important;gap:14px!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-side-stack:empty{'+CSS_HIDE+'}',
      '.spx-module-nav-ready.spx-task-page .spx-task-main-stack{grid-column:2!important;min-width:0!important;max-width:100%!important;overflow:hidden!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-side-stack>.t,.spx-module-nav-ready.spx-task-page .spx-task-side-stack>.t3,.spx-module-nav-ready.spx-task-page .spx-task-side-stack>.t5,.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.t,.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.t3,.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.t5{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-side-stack>.spx-task-side-block,.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.spx-task-main-block{float:none!important;clear:none!important;'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:0!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-main-stack table,.spx-module-nav-ready.spx-task-page .spx-task-side-stack table{'+CSS_BOX+'width:100%!important;max-width:100%!important;}',
      '.spx-module-nav-ready.spx-task-page .spx-task-main-stack>.spx-task-claim-inline{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;margin:14px 0 0!important;padding:14px!important;background:var(--spx-panel)!important;border:1px solid var(--spx-line)!important;border-radius:12px!important;box-shadow:var(--spx-shadow-card)!important;color:var(--spx-text)!important;}',
      '.spx-task-claim-head{display:flex!important;align-items:flex-start!important;justify-content:space-between!important;gap:12px!important;margin:0 0 10px!important;padding:0 0 10px!important;border-bottom:1px solid var(--spx-line-soft)!important;}',
      '.spx-task-claim-head h3{margin:0!important;color:var(--spx-strong)!important;font-size:16px!important;line-height:1.3!important;font-weight:900!important;}',
      '.spx-task-claim-summary{margin:3px 0 0!important;color:var(--spx-sub)!important;font-size:12px!important;line-height:1.35!important;}',
      '.spx-task-claim-actions{display:flex!important;flex-wrap:wrap!important;justify-content:flex-end!important;gap:6px!important;}',
      '.spx-task-claim-actions button,.spx-task-claim-remove{border:1px solid var(--spx-line)!important;border-radius:8px!important;background:var(--spx-panel-muted)!important;color:var(--spx-text)!important;padding:5px 10px!important;cursor:pointer!important;font-size:12px!important;line-height:1.25!important;}',
      '.spx-task-claim-actions button:hover,.spx-task-claim-remove:hover{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;}',
      '.spx-task-claim-inline .spx-watch-controls{margin:0 0 10px!important;}',
      '.spx-task-claim-status{margin:0 0 10px!important;padding:8px 10px!important;border:1px solid #bbf7d0!important;border-radius:8px!important;background:#f0fdf4!important;color:#166534!important;font-size:12px!important;line-height:1.45!important;}',
      '.spx-task-claim-status.spx-error{border-color:#fecaca!important;background:#fef2f2!important;color:#991b1b!important;}',
      '.spx-task-auto-claim-status{'+CSS_BOX+'margin:0 0 14px!important;padding:9px 11px!important;border:1px solid #bbf7d0!important;border-radius:10px!important;background:#f0fdf4!important;color:#166534!important;font-size:12px!important;line-height:1.45!important;}',
      '.spx-task-auto-claim-status.spx-error{border-color:#fecaca!important;background:#fef2f2!important;color:#991b1b!important;}',
      '.spx-task-claim-empty{'+CSS_BOX+'padding:14px!important;border:1px dashed var(--spx-line)!important;border-radius:10px!important;background:var(--spx-panel-muted)!important;color:var(--spx-sub)!important;font-size:13px!important;line-height:1.5!important;}',
      '.spx-task-claim-list{display:grid!important;gap:8px!important;max-height:420px!important;overflow:auto!important;padding-right:2px!important;scrollbar-width:thin!important;}',
      '.spx-task-claim-row{'+CSS_BOX+'display:grid!important;grid-template-columns:minmax(0,1fr) auto!important;gap:10px!important;align-items:center!important;padding:10px 11px!important;border:1px solid var(--spx-line-soft)!important;border-radius:10px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-task-claim-body{min-width:0!important;display:grid!important;gap:4px!important;}',
      '.spx-task-claim-title{display:flex!important;align-items:center!important;gap:8px!important;min-width:0!important;color:var(--spx-strong)!important;font-size:13px!important;font-weight:900!important;line-height:1.35!important;}',
      '.spx-task-claim-title a,.spx-task-claim-title strong{'+CSS_ELLIPSIS+'color:var(--spx-strong)!important;text-decoration:none!important;}',
      '.spx-task-claim-reward{flex:0 0 auto!important;padding:2px 7px!important;border-radius:999px!important;background:#dcfce7!important;color:#166534!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-task-claim-meta{'+CSS_ELLIPSIS+'color:var(--spx-sub)!important;font-size:12px!important;line-height:1.35!important;}',
      '.spx-module-nav-ready.spx-home-dashboard .spx-module-body #spx-home-grid{grid-template-columns:minmax(0,1fr)!important;width:100%!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav-ready.spx-home-dashboard .spx-module-body .spx-home-module,.spx-module-nav-ready.spx-home-dashboard .spx-module-body .spx-home-module[data-spx-large="1"]{grid-column:1!important;width:100%!important;max-width:100%!important;min-width:0!important;}',
      '.spx-module-nav{top:52px!important;gap:9px!important;padding:12px!important;border-radius:12px!important;box-shadow:0 5px 14px rgba(15,23,42,.05)!important;}',
      '.spx-module-nav-title{padding:0 2px 9px!important;border-bottom:1px solid var(--spx-line)!important;}',
      '.spx-module-nav-section{margin:6px 0 1px!important;border:0!important;background:var(--spx-accent-wash)!important;cursor:pointer!important;}',
      '.spx-module-nav-group.spx-module-nav-collapsed>.spx-module-nav-node{'+CSS_HIDE+'}',
      '.spx-module-nav-item{min-height:var(--spx-module-item-height)!important;padding:0 var(--spx-module-item-padding)!important;border-radius:var(--spx-module-item-radius)!important;background:var(--spx-panel-muted)!important;}',
      '.spx-module-nav-count{min-width:22px!important;height:20px!important;}',
      '.spx-home-dashboard .spx-home-quick{'+CSS_HIDE+'}',
      '.spx-home-dashboard #notice,.spx-home-dashboard .spx-home-module,.spx-forum-dashboard #content .t,.spx-forum-dashboard .spx-module-nav-host .t{'+CSS_BOX+'width:100%!important;max-width:100%!important;min-width:0!important;border-radius:12px!important;background:var(--spx-panel)!important;border-color:var(--spx-line)!important;box-shadow:0 8px 18px rgba(15,23,42,.06)!important;}',
      '.spx-home-dashboard .spx-home-module>h2,.spx-home-dashboard .spx-home-module .h{min-height:38px!important;padding:0 12px!important;background:var(--spx-panel)!important;border:0!important;border-bottom:1px solid var(--spx-line)!important;color:var(--spx-strong)!important;font-size:13px!important;font-weight:900!important;}',
      '.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table table,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tbody{display:block!important;width:100%!important;border:0!important;background:transparent!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2{display:grid!important;gap:8px!important;padding:10px 12px!important;border-bottom:1px solid var(--spx-line)!important;background:var(--spx-panel-muted)!important;color:var(--spx-sub)!important;font-size:12px!important;font-weight:900!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2{grid-template-columns:minmax(360px,620px) 96px minmax(180px,260px)!important;justify-content:start!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2{grid-template-columns:minmax(0,1fr) 92px 82px minmax(160px,.7fr)!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2>td,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2>td,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2>td{display:block!important;width:auto!important;padding:0!important;border:0!important;background:transparent!important;color:var(--spx-sub)!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr2>td:last-child,.spx-home-dashboard .spx-home-module tr.tr3>td:last-child{'+CSS_HIDE+'}',
      '.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2>td:first-child{'+CSS_HIDE+'}',
      '.spx-home-dashboard .spx-home-module tr.tr3,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3{min-height:44px!important;padding:9px 12px!important;border-bottom:1px solid var(--spx-line)!important;background:var(--spx-panel)!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3{grid-template-columns:minmax(360px,620px) 96px minmax(180px,260px)!important;justify-content:start!important;}',
      '.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3{grid-template-columns:minmax(0,1fr) 92px 82px minmax(160px,.7fr)!important;}',
      '.spx-home-dashboard .spx-home-module tr.tr3>td,.spx-home-dashboard .spx-home-module tr.tr3>th,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3>td,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3>td,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3>th{display:block!important;width:auto!important;padding:0!important;border:0!important;background:transparent!important;font-size:12px!important;line-height:1.45!important;color:var(--spx-sub)!important;overflow:hidden!important;text-overflow:ellipsis!important;}',
      '.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3>td:first-child:not([id^="td_"]){'+CSS_HIDE+'}',
      '.spx-home-dashboard .spx-home-module [id^="fn_"] a,.spx-home-dashboard .spx-home-module [id^="fn_"],.spx-forum-dashboard td[id^="td_"] a[id^="a_ajax_"],.spx-forum-dashboard [id^="td_"] a[href*="read.php"]{color:var(--spx-strong)!important;font-size:14px!important;font-weight:800!important;}',
      '.spx-home-dashboard .spx-home-hot [id^="fn_"] a,.spx-home-dashboard .spx-home-hot [id^="fn_"]{color:var(--spx-accent)!important;}',
      '.spx-home-dashboard .spx-home-badge,.spx-toolbox-count,.spx-status-badge,.spx-toolbox-key{background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;}',
      '.spx-forum-tools{min-height:42px!important;padding:8px 10px!important;border-radius:12px!important;background:var(--spx-panel)!important;border-color:var(--spx-line)!important;box-shadow:0 8px 18px rgba(15,23,42,.06)!important;}',
      '.spx-forum-tools input{height:30px!important;border-radius:8px!important;background:var(--spx-panel-muted)!important;}',
      '.spx-forum-tools button,.spx-thread-tools button,.spx-post-tools button{border-radius:8px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;}',
      '.spx-forum-dashboard .spx-thread-list-table tr:not(.tr2):not(.tr3){'+CSS_HIDE+'}',
      '.spx-forum-dashboard .spx-thread-list-table tr.spx-filter-hidden,.spx-forum-dashboard .spx-thread-list-table tr.spx-hidden-rule,.spx-forum-dashboard .spx-thread-list-table tr.spx-unread-hidden,.spx-forum-dashboard .spx-thread-list-table tr.spx-thread-row-hidden{'+CSS_HIDE+'}',
      '.spx-forum-dashboard .spx-thread-list-table tr.tr3:not(:hover) .spx-thread-tools{'+CSS_HIDE+'}',
      '.spx-theme-night.spx-forum-gallery-page #wall .inner,.spx-theme-night.spx-forum-gallery-page #wall .spx-gallery-card-inner{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:0 10px 30px rgba(0,0,0,.28)!important;}',
      '.spx-theme-night.spx-forum-gallery-page #wall .section-intro{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;color:var(--spx-sub)!important;}',
      '.spx-theme-night.spx-forum-gallery-page #wall .section-text img{border-color:var(--spx-line)!important;background:var(--spx-panel-muted)!important;}',
      '.spx-toolbar{right:16px!important;bottom:18px!important;display:flex!important;flex-direction:row!important;gap:7px!important;padding:6px!important;border-radius:16px!important;background:rgba(255,255,255,.92)!important;box-shadow:0 18px 46px rgba(15,23,42,.18)!important;}',
      '.spx-toolbar button{width:52px!important;height:36px!important;border-radius:12px!important;background:var(--spx-panel)!important;color:var(--spx-text)!important;}',
      '.spx-toolbar button:hover,.spx-toolbar button:focus-visible{border-color:var(--spx-accent)!important;color:var(--spx-accent)!important;box-shadow:0 7px 18px rgba(37,99,235,.16)!important;}',
      '.spx-toolbar .spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;box-shadow:inset 0 0 0 1px rgba(37,99,235,.12),0 6px 16px rgba(37,99,235,.14)!important;}',
      '.spx-toolbox,.spx-settings,.spx-watch-center{right:16px!important;bottom:72px!important;border-radius:12px!important;}',
      '.spx-toolbox{width:min(460px,calc(100vw - 32px))!important;}',
      '.spx-toolbox-header,.spx-settings-header,.spx-watch-center-header{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;}',
      '.spx-toolbox-eyebrow{color:var(--spx-accent)!important;}',
      '.spx-toolbox-action{min-height:42px!important;border-radius:10px!important;background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;box-shadow:none!important;}',
      '.spx-toolbox-action:hover,.spx-toolbox-action:focus-visible{border-color:var(--spx-accent)!important;background:var(--spx-accent-wash)!important;color:var(--spx-accent)!important;box-shadow:0 10px 24px rgba(37,99,235,.15)!important;}',
      '.spx-toolbox-action.spx-active{border-color:var(--spx-accent)!important;background:var(--spx-accent-soft)!important;color:var(--spx-accent)!important;box-shadow:0 8px 22px rgba(37,99,235,.18)!important;}',
      '.spx-toolbox-action.spx-active:before{background:var(--spx-accent)!important;}',
      '.spx-theme-night .spx-toolbar{background:rgba(24,29,26,.92)!important;box-shadow:0 18px 46px rgba(0,0,0,.42)!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module,.spx-theme-night.spx-home-dashboard #notice,.spx-theme-night.spx-home-dashboard .spx-home-quick a,.spx-theme-night.spx-forum-dashboard #content .t,.spx-theme-night.spx-forum-dashboard .spx-module-nav-host .t{background:#181d1a!important;border-color:#303a32!important;color:#edf4ec!important;box-shadow:0 10px 30px rgba(0,0,0,.28)!important;}',
      '.spx-theme-night .spx-forum-dashboard-panel,.spx-theme-night .spx-forum-dashboard-card{background:var(--spx-panel)!important;border-color:var(--spx-line)!important;color:var(--spx-text)!important;box-shadow:0 10px 30px rgba(0,0,0,.28)!important;}.spx-theme-night .spx-forum-dashboard-stat,.spx-theme-night .spx-forum-dashboard-topic,.spx-theme-night .spx-forum-dashboard-resource,.spx-theme-night .spx-forum-dashboard-activity{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;}.spx-theme-night .spx-forum-dashboard-stat.spx-blue,.spx-theme-night .spx-forum-dashboard-stat.spx-amber,.spx-theme-night .spx-forum-dashboard-stat.spx-rose,.spx-theme-night .spx-forum-dashboard-stat.spx-green,.spx-theme-night .spx-forum-dashboard-stat.spx-violet{background:var(--spx-panel-muted)!important;border-color:var(--spx-line)!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module tr.tr2,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2,.spx-theme-night.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2{background:#202720!important;border-color:#303a32!important;color:#b7c8b7!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module tr.tr3,.spx-theme-night.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3,.spx-theme-night.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3{background:#181d1a!important;border-color:#303a32!important;}',
      '.spx-theme-night.spx-home-dashboard .spx-home-module>h2,.spx-theme-night.spx-home-dashboard .spx-home-module .h,.spx-theme-night .spx-toolbox-header,.spx-theme-night .spx-settings-header,.spx-theme-night .spx-watch-center-header{background:var(--spx-panel)!important;}',
      '@media(max-width:900px){:root,.spx-theme-clean,.spx-theme-night{--spx-page-space:16px}.spx-site-shell #mainNav,.spx-home-dashboard #mainNav,.spx-forum-dashboard #mainNav,.spx-immersive-read #mainNav{height:34px!important}.spx-site-shell #guide{height:34px!important;overflow-x:auto!important}.spx-site-shell #guide>li>a{height:26px!important}.spx-site-shell #spx-nav-brand{height:26px!important;line-height:26px!important}.spx-module-nav-ready.spx-home-dashboard #content,.spx-module-nav-ready.spx-forum-dashboard #content,.spx-module-nav-ready .spx-module-nav-host{grid-template-columns:minmax(0,1fr)!important;gap:10px!important;width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}.spx-module-nav-ready .spx-module-nav-host>*:not(.spx-module-nav),.spx-module-nav-ready .spx-module-body,.spx-module-nav{grid-column:1!important;width:auto!important;max-width:100%!important}.spx-module-nav{top:auto!important;position:static!important}.spx-module-nav-section{flex:none!important;margin:0 2px!important}.spx-home-dashboard .spx-home-module tr.tr2,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr2,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr2{display:none!important}.spx-home-dashboard .spx-home-module tr.tr3,.spx-forum-dashboard #content .t.spx-thread-list-table tr.tr3,.spx-forum-dashboard .spx-module-nav-host .t.spx-thread-list-table tr.tr3{grid-template-columns:1fr!important}}',
      '@media(max-width:900px){.spx-favorite-nav{margin-left:0!important;height:34px!important}.spx-favorite-nav-trigger{height:26px!important;line-height:26px!important}.spx-favorite-nav-panel{top:38px!important;right:auto!important;left:0!important;width:min(720px,calc(100vw - 24px))!important;max-height:calc(100vh - 84px)!important}.spx-favorite-stats{grid-template-columns:repeat(3,minmax(96px,1fr))!important;overflow-x:auto!important}.spx-favorite-item{grid-template-columns:minmax(0,1fr)!important}.spx-favorite-actions{justify-content:flex-start!important}.spx-favorite-tools{align-items:flex-start!important;flex-direction:column!important}.spx-favorite-sort{width:100%!important}}',
      '@media(max-width:900px){.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host{grid-template-columns:minmax(0,1fr)!important;width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-search-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-search-page #content.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-profile-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-profile-page #content.spx-module-nav-host>.spx-module-body{grid-column:1!important;width:auto!important;max-width:100%!important}}',
      '@media(max-width:900px){.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host{grid-template-columns:minmax(0,1fr)!important;width:calc(100vw - 16px)!important;margin:10px 8px 34px!important}.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-nav,.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-body,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-body{grid-column:1!important;width:auto!important;max-width:100%!important}.spx-module-nav-ready.spx-task-page #main.spx-module-nav-host>.spx-module-body.spx-task-layout-body,.spx-module-nav-ready.spx-task-page #content.spx-module-nav-host>.spx-module-body.spx-task-layout-body{grid-template-columns:minmax(0,1fr)!important}.spx-module-nav-ready.spx-task-page .spx-task-side-stack,.spx-module-nav-ready.spx-task-page .spx-task-main-stack{grid-column:1!important}}',
    ];
  }

  function injectStyles() {
    if (qs('#sp-enhancer-style')) return;
    var style = createEl('style');
    style.id = 'sp-enhancer-style';
    style.textContent = getInjectedStyleText();
    document.head.appendChild(style);
  }

  function setBodyClasses(settings) {
    var navDensity = getModuleNavigationDensityConfig(settings && settings.moduleNavDensity);
    document.documentElement.classList.toggle('spx-theme-clean', !settings.nightMode);
    document.documentElement.classList.toggle('spx-theme-night', !!settings.nightMode);
    document.documentElement.classList.toggle('spx-site-shell', shouldUseSiteShell(location.href));
    document.documentElement.classList.toggle('spx-adblock', !!settings.adBlock);
    document.documentElement.classList.toggle('spx-clean', !!settings.cleanMode);
    document.documentElement.classList.toggle('spx-reader', shouldUseReaderMode(settings, location.href));
    document.documentElement.classList.toggle('spx-immersive-read', shouldUseImmersiveRead(settings, location.href));
    document.documentElement.classList.toggle('spx-home-dashboard', shouldUseHomeDashboard(settings, location.href));
    document.documentElement.classList.toggle('spx-forum-dashboard', shouldUseForumDashboard(location.href, document));
    document.documentElement.classList.toggle('spx-forum-gallery-page', isForumGalleryModeUrl(location.href));
    document.documentElement.classList.toggle('spx-search-page', shouldUseSearchPage(location.href));
    document.documentElement.classList.toggle('spx-profile-page', shouldUseProfilePage(location.href));
    document.documentElement.classList.toggle('spx-task-page', shouldUseTaskPage(location.href));
    document.documentElement.classList.toggle('spx-compact-read', !!settings.compactRead);
    document.documentElement.classList.toggle('spx-hide-profile', !!settings.hideUserProfile);
    document.documentElement.style.setProperty('--spx-immersive-font-size', '16px');
    document.documentElement.style.setProperty('--spx-module-width', navDensity.width + 'px');
    document.documentElement.style.setProperty('--spx-module-max-offset', navDensity.maxOffset + 'px');
    document.documentElement.style.setProperty('--spx-module-section-height', navDensity.sectionHeight + 'px');
    document.documentElement.style.setProperty('--spx-module-parent-height', navDensity.parentHeight + 'px');
    document.documentElement.style.setProperty('--spx-module-item-height', navDensity.itemHeight + 'px');
    document.documentElement.style.setProperty('--spx-module-child-height', navDensity.childHeight + 'px');
    document.documentElement.style.setProperty('--spx-module-item-padding', navDensity.itemPadding + 'px');
    document.documentElement.style.setProperty('--spx-module-section-padding', navDensity.sectionPadding + 'px');
    document.documentElement.style.setProperty('--spx-module-item-radius', navDensity.radius + 'px');
  }

  function extractAccountUserId(root, url) {
    var scope = root || document;
    var text = String(url || location.href || '');
    var urlMatch = text.match(/[?&]uid[=-](\d+)/) || text.match(/uid-(\d+)/);
    if (urlMatch) return urlMatch[1];

    var globalDocument = typeof document !== 'undefined' ? document : null;
    if (scope && scope !== globalDocument) {
      var sourceUidLink = qsa('a[href*="uid"]', scope).map(function mapSourceUidLink(link) {
        return link.getAttribute('href') || link.href || '';
      }).filter(Boolean)[0];
      var sourceUidMatch = String(sourceUidLink || '').match(/uid[=-](\d+)/) || String(sourceUidLink || '').match(/uid-(\d+)/);
      if (sourceUidMatch) return sourceUidMatch[1];
    }

    var ownProfileLink = qsa('a[href*="u.php?action-show-uid"]', scope).filter(function likelyOwnProfile(link) {
      return /查看个人资料|个人资料|资料/.test((link.textContent || '').trim());
    })[0] || qs('#user-login a[href*="u.php"]', scope);

    var href = ownProfileLink ? ownProfileLink.getAttribute('href') || ownProfileLink.href : '';
    var linkMatch = String(href || '').match(/uid[=-](\d+)/) || String(href || '').match(/uid-(\d+)/);
    return linkMatch ? linkMatch[1] : '';
  }

  function getAccountTabItems(userId, origin) {
    var uid = String(userId || '');
    var base = String(origin || location.origin || '');
    return [
      { key: 'home', label: '个人首页', href: base + '/u.php?uid-' + uid + '.html' },
      { key: 'profile', label: '资料', href: base + '/u.php?action-show-uid-' + uid + '.html' },
      { key: 'topic', label: '主题', href: base + '/u.php?action-topic-uid-' + uid + '.html' },
      { key: 'post', label: '回复', href: base + '/u.php?action-post-uid-' + uid + '.html' },
      { key: 'favor', label: '收藏', href: base + '/u.php?action-favor-uid-' + uid + '.html' },
      { key: 'friend', label: '好友', href: base + '/u.php?action-friend-uid-' + uid + '.html' },
      { key: 'trade', label: '商品', href: base + '/u.php?action-trade-uid-' + uid + '.html' },
      { key: 'message', label: '消息', href: base + '/message.php' },
      { key: 'settings', label: '设置', href: base + '/profile.php' },
    ];
  }

  function getAccountActiveKey(url) {
    var text = String(url || '');
    if (/\/message\.php/.test(text)) return 'message';
    if (/\/profile\.php/.test(text)) return 'settings';
    if (/action-show/.test(text)) return 'profile';
    if (/action-topic/.test(text)) return 'topic';
    if (/action-post/.test(text)) return 'post';
    if (/action-favor/.test(text)) return 'favor';
    if (/action-friend/.test(text)) return 'friend';
    if (/action-trade/.test(text)) return 'trade';
    if (/\/u\.php/.test(text)) return 'home';
    return '';
  }

  function shouldRestoreProfileInfobox(url) {
    var activeKey = getAccountActiveKey(url || (typeof location !== 'undefined' ? location.href : ''));
    return ['home', 'profile', 'topic', 'post', 'favor', 'friend', 'trade'].indexOf(activeKey) !== -1;
  }

  function getProfileInfoboxSourceUrl(origin) {
    return String(origin || (typeof location !== 'undefined' ? location.origin : 'https://south-plus.org')) + '/thread.php?fid-9.html';
  }

  function getProfileInfoboxCache(now) {
    var cache = loadMap(PROFILE_INFOBOX_CACHE_KEY);
    var updatedAt = Number(cache.updatedAt) || 0;
    if (!cache.html || !updatedAt) return null;
    if ((now === undefined ? Date.now() : Number(now)) - updatedAt > PROFILE_INFOBOX_CACHE_TTL) return null;
    return cache;
  }

  function saveProfileInfoboxCache(html, sourceUrl, now) {
    var value = String(html || '').trim();
    if (!value || value.indexOf('id="infobox"') === -1) return false;
    saveMap(PROFILE_INFOBOX_CACHE_KEY, {
      html: value.slice(0, 12000),
      sourceUrl: String(sourceUrl || ''),
      updatedAt: now === undefined ? Date.now() : Number(now),
    });
    return true;
  }

  function createProfileInfoboxFromHtml(html) {
    if (!html || typeof DOMParser === 'undefined') return null;
    var doc = new DOMParser().parseFromString(String(html || ''), 'text/html');
    var source = qs('#infobox', doc);
    if (!source) return null;
    return createProfileInfoboxFallback(source);
  }

  function createProfileInfoboxLink(label, href) {
    var link = createEl('a', '', label);
    link.href = href;
    return link;
  }

  function getProfileInfoboxSourceHref(sourceBox, label) {
    if (!sourceBox) return '';
    var sourceLink = qsa('a[href]', sourceBox).filter(function matchProfileInfoboxSourceLink(link) {
      return compactText(link.textContent) === label;
    })[0];
    return sourceLink ? sourceLink.getAttribute('href') || sourceLink.href || '' : '';
  }

  function cloneProfileInfoboxAvatar(sourceBox) {
    var avatar = (sourceBox && (qs('img[src*="face"]', sourceBox) || qs('img', sourceBox))) || qs('#u-portrait img') || qs('#user_info img');
    if (!avatar) return null;
    var clone = avatar.cloneNode(false);
    clone.removeAttribute('id');
    clone.removeAttribute('style');
    Array.prototype.slice.call(clone.attributes || []).forEach(function removeProfileAvatarHandler(attribute) {
      if (/^on/i.test(attribute.name || '')) clone.removeAttribute(attribute.name);
    });
    return clone;
  }

  function getProfileInfoboxHonor(sourceBox) {
    var currentHonor = compactText(qs('#honor') && qs('#honor').textContent);
    if (currentHonor) return currentHonor;
    var topHonor = compactText(qs('#u-top') && qs('#u-top').textContent).replace(/编辑.*$/, '');
    if (topHonor) return compactText(topHonor);
    var sourceText = compactText(sourceBox && sourceBox.textContent);
    var match = sourceText.match(/我的回复\s*(?:更多)?\s*(.*?)(?:\s*编辑|\s*热门版块[::]|$)/);
    return match ? compactText(match[1]) : '';
  }

  function createProfileInfoboxFallback(sourceBox) {
    var uid = extractAccountUserId(sourceBox || document, location.href);
    var origin = location.origin;
    var box = createEl('div', 'cc spx-profile-infobox-restored spx-profile-infobox-fallback');
    var main = createEl('div', 'spx-profile-infobox-main');
    var avatarWrap = createEl('div', 'spx-profile-infobox-avatar');
    var avatar = cloneProfileInfoboxAvatar(sourceBox);
    if (avatar) {
      avatarWrap.appendChild(avatar);
    }

    var body = createEl('div', 'spx-profile-infobox-body');
    var meta = createEl('div', 'spx-profile-infobox-meta');
    var sourceText = compactText(sourceBox && sourceBox.textContent);
    var sidebarText = compactText(qs('#u-sidebar') && qs('#u-sidebar').textContent);
    var detailText = sourceText || sidebarText;
    var levelMatch = detailText.match(/等级[::]?\s*(Lv\.\d+)/i);
    var postMatch = detailText.match(/帖子[::]?\s*(\d+)/);
    var topicHref = getProfileInfoboxSourceHref(sourceBox, '我的主题') || (uid ? origin + '/u.php?action-topic-uid-' + uid + '.html' : '');
    var postHref = getProfileInfoboxSourceHref(sourceBox, '我的回复') || (uid ? origin + '/u.php?action-post-uid-' + uid + '.html' : '');
    meta.appendChild(document.createTextNode('等级:' + (levelMatch ? levelMatch[1] : '未知')));
    if (postMatch) meta.appendChild(document.createTextNode(',帖子:' + postMatch[1]));
    if (topicHref || postHref) {
      meta.appendChild(document.createTextNode(','));
      if (topicHref) meta.appendChild(createProfileInfoboxLink('我的主题', topicHref));
      if (topicHref && postHref) meta.appendChild(document.createTextNode(','));
      if (postHref) meta.appendChild(createProfileInfoboxLink('我的回复', postHref));
    }
    body.appendChild(meta);

    var honor = getProfileInfoboxHonor(sourceBox);
    if (honor) body.appendChild(createEl('div', 'spx-profile-infobox-honor', honor));

    main.appendChild(avatarWrap);
    main.appendChild(body);

    var hot = createEl('div', 'spx-profile-infobox-hot');
    hot.appendChild(createEl('span', 'spx-profile-infobox-hot-label', '热门版块:'));
    getCommonForumNavigationItems(origin).slice(1, 7).forEach(function appendHotForum(item) {
      hot.appendChild(createProfileInfoboxLink(item.label, item.href));
    });

    box.id = 'infobox';
    box.appendChild(main);
    box.appendChild(hot);
    return box;
  }

  function mountProfileInfobox(box, replaceNative) {
    if (!box) return null;
    var existing = qs('#infobox');
    if (existing && !existing.classList.contains('spx-profile-infobox-restored') && !replaceNative) return existing;
    setImportantStyle(box, 'box-sizing', 'border-box');
    setImportantStyle(box, 'width', 'min(1480px, calc(100vw - 40px))');
    setImportantStyle(box, 'max-width', 'none');
    setImportantStyle(box, 'margin-left', 'auto');
    setImportantStyle(box, 'margin-right', 'auto');
    var main = qs('#main') || qs('#content');
    if (existing && existing.parentNode) {
      existing.parentNode.replaceChild(box, existing);
    } else if (main && main.parentNode) {
      main.parentNode.insertBefore(box, main);
    } else if (document.body) {
      document.body.insertBefore(box, document.body.firstChild);
    }
    return box;
  }

  function isStandardProfileInfobox(box) {
    return !!(
      box &&
      box.classList &&
      box.classList.contains('spx-profile-infobox-restored') &&
      qs('.spx-profile-infobox-main', box) &&
      qs('.spx-profile-infobox-hot', box)
    );
  }

  function standardizeExistingInfobox() {
    var existing = qs('#infobox');
    if (!existing) return null;
    if (isStandardProfileInfobox(existing)) return existing;
    return mountProfileInfobox(createProfileInfoboxFallback(existing), true);
  }

  function restoreProfileInfobox(settings) {
    if (!shouldRestoreProfileInfobox(location.href)) return;
    if (standardizeExistingInfobox()) return;
    var cached = getProfileInfoboxCache();
    var mounted = cached && mountProfileInfobox(createProfileInfoboxFromHtml(cached.html));
    if (!mounted) mountProfileInfobox(createProfileInfoboxFallback());
    if (document.documentElement.dataset.spxProfileInfoboxRefreshing === '1') return;
    if (cached && cached.updatedAt && Date.now() - Number(cached.updatedAt) < PROFILE_INFOBOX_CACHE_TTL / 2) return;
    if (testMode || typeof window === 'undefined' || typeof DOMParser === 'undefined') return;
    document.documentElement.dataset.spxProfileInfoboxRefreshing = '1';
    var sourceUrl = getProfileInfoboxSourceUrl(location.origin);
    requestWithPolicy(sourceUrl, { credentials: 'include', cache: 'force-cache' }, {
      mode: 'background',
      label: '个人页头部信息',
      networkFriendly: isNetworkFriendlyMode(settings),
    }).then(function readProfileInfoboxResponse(response) {
      if (!response || !response.ok) return '';
      return readScriptResponseText(response, { mode: 'background', label: '个人页头部信息' });
    }).then(function applyFetchedProfileInfobox(html) {
      var node = createProfileInfoboxFromHtml(html);
      if (!node) return;
      saveProfileInfoboxCache(node.outerHTML, sourceUrl);
      mountProfileInfobox(node);
    }).catch(function ignoreProfileInfoboxRefresh() {}).then(function clearProfileInfoboxRefreshFlag() {
      delete document.documentElement.dataset.spxProfileInfoboxRefreshing;
    });
  }

  function getFavoriteNavUrl(userId, origin) {
    var uid = String(userId || '').replace(/\D+/g, '');
    if (!uid) return '';
    var base = String(origin || location.origin || '').replace(/\/+$/, '');
    return base + '/u.php?action-favor-uid-' + uid + '.html';
  }

  function extractSiteVerifyHashFromText(text) {
    var value = String(text || '');
    var markerIndex = value.indexOf('verifyhash');
    if (markerIndex < 0) return '';
    var assignIndex = value.indexOf('=', markerIndex);
    if (assignIndex < 0) return '';
    var rest = value.slice(assignIndex + 1).replace(/^\s+/, '');
    var quote = rest.charAt(0);
    if (quote !== "'" && quote !== '"') return '';
    var endIndex = rest.indexOf(quote, 1);
    return endIndex > 0 ? rest.slice(1, endIndex) : '';
  }

  function getSiteVerifyHash(root) {
    if (typeof verifyhash !== 'undefined' && verifyhash) return String(verifyhash);
    if (typeof window !== 'undefined' && window.verifyhash) return String(window.verifyhash);
    var scope = root || (typeof document !== 'undefined' ? document : null);
    var scripts = scope && scope.querySelectorAll ? qsa('script', scope) : [];
    for (var i = 0; i < scripts.length; i += 1) {
      var scriptMatch = extractSiteVerifyHashFromText(scripts[i].textContent || '');
      if (scriptMatch) return scriptMatch;
    }
    var html = scope && scope.documentElement ? scope.documentElement.outerHTML : '';
    return extractSiteVerifyHashFromText(html);
  }

  function getThreadFavoriteUrl(threadId, origin, verify, now) {
    var tid = parseThreadId(threadId);
    if (!tid) return '';
    var base = String(origin || location.origin || '').replace(/\/+$/, '');
    var url = base + '/pw_ajax.php?action=favor&tid=' + encodeURIComponent(tid);
    var timestamp = now === undefined ? Date.now() : Number(now);
    url += '&nowtime=' + encodeURIComponent(isFinite(timestamp) ? timestamp : Date.now());
    if (verify) url += '&verify=' + encodeURIComponent(verify);
    return url;
  }

  function getThreadFavoriteResultText(text) {
    var value = compactText(String(text || '').replace(/^<\?xml[^>]*>/i, '').replace(/<ajax><!\[CDATA\[|\]\]><\/ajax>/gi, ''));
    if (!value) return '已收藏';
    if (/登录|权限|失败|错误|非法|重试/.test(value)) return '收藏失败';
    if (/已经|已收藏|重复/.test(value)) return '已收藏';
    if (/success|成功|收藏/.test(value)) return '已收藏';
    return '已提交';
  }

  function isNewThreadFavoriteResult(text) {
    var value = compactText(String(text || '').replace(/^<\?xml[^>]*>/i, '').replace(/<ajax><!\[CDATA\[|\]\]><\/ajax>/gi, ''));
    if (!value || /已经|已收藏|重复/.test(value)) return false;
    return /success|成功|收藏/.test(value);
  }

  function getSiteFavoriteDeleteResultText(text) {
    var value = compactText(String(text || '').replace(/^<\?xml[^>]*>/i, '').replace(/<ajax><!\[CDATA\[|\]\]><\/ajax>/gi, ''));
    if (/登录|权限|失败|错误|非法|重试/.test(value)) return '删除失败';
    if (/删除|取消|移除|success|成功/.test(value)) return '已删除';
    return value ? '已提交' : '已删除';
  }

  function buildFavoriteDeleteRequest(action, method, fields, pageUrl) {
    var targetUrl = normalizeNavigationHref(action || '', pageUrl || location.href);
    if (!targetUrl) return null;
    var requestMethod = String(method || 'GET').toUpperCase() === 'POST' ? 'POST' : 'GET';
    var params = new URLSearchParams();
    (fields || []).forEach(function appendFavoriteDeleteField(field) {
      if (!field || !field.name) return;
      params.append(field.name, field.value == null ? '' : String(field.value));
    });
    var body = params.toString();
    if (requestMethod === 'GET') {
      if (body) targetUrl += (targetUrl.indexOf('?') === -1 ? '?' : '&') + body;
      return { url: targetUrl, method: 'GET', body: '' };
    }
    return {
      url: targetUrl,
      method: 'POST',
      body: body,
      contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
    };
  }

  function normalizeFavoriteDeleteFields(fields) {
    var result = [];
    var hasJob = false;
    (fields || []).forEach(function keepFavoriteDeleteField(field) {
      if (!field || !field.name) return;
      if (field.name === 'job') {
        if (!hasJob) {
          result.push({ name: 'job', value: 'clear' });
          hasJob = true;
        }
        return;
      }
      if (field.name === 'type') return;
      result.push(field);
    });
    if (!hasJob) result.push({ name: 'job', value: 'clear' });
    return result;
  }

  function isFavoriteDeleteControl(node) {
    if (!node) return false;
    var text = [
      node.textContent,
      node.value,
      node.title,
      node.getAttribute && node.getAttribute('href'),
      node.getAttribute && node.getAttribute('onclick'),
    ].join(' ');
    return /删除|取消收藏|移除|delete|del/i.test(text) && /收藏|favor|selid|delete|del/i.test(text);
  }

  function getFavoriteDeleteUrlFromLink(node, pageUrl) {
    if (!isFavoriteDeleteControl(node)) return '';
    var values = [
      node.getAttribute && node.getAttribute('href'),
      node.getAttribute && node.getAttribute('onclick'),
    ];
    var candidates = [];
    values.forEach(function collectFavoriteDeleteCandidate(value) {
      var text = String(value || '').replace(/&amp;/g, '&').trim();
      if (!text) return;
      if (!/^javascript:/i.test(text)) candidates.push(text);
      var match;
      var quotedUrlRe = /['"]([^'"]*(?:u\.php|pw_ajax\.php|action[=-]favor|selid|delete|del)[^'"]*)['"]/ig;
      while ((match = quotedUrlRe.exec(text))) {
        candidates.push(match[1].replace(/&amp;/g, '&'));
      }
    });
    for (var index = 0; index < candidates.length; index += 1) {
      var candidate = candidates[index];
      if (!/(?:favor|selid|delete|del)/i.test(candidate)) continue;
      var url = normalizeNavigationHref(candidate, pageUrl || location.href);
      if (url) return url;
    }
    return '';
  }

  function collectFavoriteDeleteFormFields(form, targetControl, submitControl) {
    var fields = [];
    qsa('input,textarea,select', form).forEach(function collectFavoriteDeleteField(control) {
      if (!control || control.disabled || !control.name) return;
      var tag = String(control.tagName || '').toLowerCase();
      var type = String(control.type || '').toLowerCase();
      if (type === 'file' || type === 'reset' || type === 'button' || type === 'image') return;
      if (type === 'submit') {
        if (control === submitControl) fields.push({ name: control.name, value: control.value || '' });
        return;
      }
      if (type === 'checkbox') {
        if (control === targetControl) fields.push({ name: control.name, value: control.value || 'on' });
        return;
      }
      if (type === 'radio') {
        if (control.name === 'job' && control.value === 'clear') fields.push({ name: control.name, value: control.value });
        else if (control === targetControl) fields.push({ name: control.name, value: control.value || 'on' });
        return;
      }
      if (tag === 'select' && control.name !== 'type') {
        qsa('option', control).forEach(function collectFavoriteSelectOption(option) {
          if (option.selected) fields.push({ name: control.name, value: option.value });
        });
        return;
      }
      if (tag === 'select') return;
      fields.push({ name: control.name, value: control.value || '' });
    });
    if (submitControl && submitControl.name && submitControl.type !== 'submit') {
      fields.push({ name: submitControl.name, value: submitControl.value || submitControl.textContent || '' });
    }
    return fields;
  }

  function findFavoriteDeleteRequestFromForm(row, pageUrl) {
    if (!row || !row.closest) return null;
    var form = row.closest('form');
    if (!form) return null;
    var controls = qsa('input,button', form);
    var targetControl = qsa('input[type="checkbox"],input[type="radio"]', row).filter(function keepFavoriteCheckbox(control) {
      return /sel|id|favor|tid/i.test(control.name || '') || /\d+/.test(control.value || '');
    })[0];
    if (!targetControl) return null;
    var submitControl = controls.filter(isFavoriteDeleteControl)[0] || null;
    var fields = normalizeFavoriteDeleteFields(collectFavoriteDeleteFormFields(form, targetControl, submitControl));
    return buildFavoriteDeleteRequest(form.getAttribute('action') || form.action || pageUrl, form.getAttribute('method') || form.method || 'GET', fields, pageUrl);
  }

  function findFavoriteDeleteRequestFromLinks(row, pageUrl) {
    if (!row) return null;
    var deleteUrl = '';
    qsa('a[href],a[onclick],button[onclick],input[onclick]', row).some(function findFavoriteDeleteLink(node) {
      deleteUrl = getFavoriteDeleteUrlFromLink(node, pageUrl);
      return !!deleteUrl;
    });
    return deleteUrl ? buildFavoriteDeleteRequest(deleteUrl, 'GET', [], pageUrl) : null;
  }

  function getFavoriteNavDeleteKey(entry) {
    var item = entry || {};
    return [item.source || '', item.id || '', item.url || '', item.index == null ? '' : item.index].join('|');
  }

  function getSelectedSiteFavoriteEntries(entries, selectedMap) {
    var selected = selectedMap || {};
    return (entries || []).filter(function keepSelectedFavorite(entry) {
      return !!(entry && entry.deleteRequest && selected[getFavoriteNavDeleteKey(entry)]);
    });
  }

  function createSiteFavoriteDeleteRequest(row, pageUrl) {
    return findFavoriteDeleteRequestFromLinks(row, pageUrl) || findFavoriteDeleteRequestFromForm(row, pageUrl);
  }

  function markThreadFavoriteSeen(info) {
    var tid = parseThreadId(info && info.id);
    if (!tid) return false;
    var seenMap = loadMap(FAVORITE_NAV_SEEN_KEY);
    if (seenMap[tid]) return false;
    seenMap[tid] = Date.now();
    saveMap(FAVORITE_NAV_SEEN_KEY, seenMap);
    return true;
  }

  function isThreadFavoriteSeen(threadId) {
    var tid = parseThreadId(threadId);
    if (!tid) return false;
    return !!loadMap(FAVORITE_NAV_SEEN_KEY)[tid];
  }

  function saveThreadSiteFavorite(info, settings) {
    var verify = getSiteVerifyHash(document);
    var url = getThreadFavoriteUrl(info && info.id, location.origin, verify, Date.now());
    if (!url) return Promise.reject(new Error('缺少帖子ID'));
    if (!verify) return Promise.reject(new Error('缺少站点校验参数'));
    var policy = {
      mode: 'action',
      label: '收藏帖子',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    return requestWithPolicy(url, { credentials: 'include', cache: 'no-store' }, policy)
      .then(function readFavoriteResponse(response) {
        if (!response.ok) throw new Error('收藏失败');
        return readScriptResponseText(response, policy);
      })
      .then(function resolveFavoriteText(html) {
        var text = getThreadFavoriteResultText(html);
        if (text === '收藏失败') throw new Error('收藏失败');
        return {
          text: text,
          added: isNewThreadFavoriteResult(html) && markThreadFavoriteSeen(info),
        };
      });
  }

  function runThreadFavoriteAction(info, settings, state, button, doneText) {
    if (!button) return;
    var originalText = button.dataset.spxFavoriteOriginalText || button.textContent || '收藏';
    button.dataset.spxFavoriteOriginalText = originalText;
    button.disabled = true;
    button.textContent = '收藏中';
    saveThreadSiteFavorite(info, settings).then(
      function markFavoriteDone(result) {
        delete button.dataset.spxFavoriteOriginalText;
        button.textContent = doneText || result.text || '已收藏';
        syncFavoriteNavAfterSiteFavorite(info, settings, state, !!(result && result.added));
      },
      function markFavoriteFailed(error) {
        var message = error && error.message ? error.message : '收藏失败';
        button.dataset.spxFavoriteError = message;
        button.title = message;
        button.textContent = '收藏失败';
        if (typeof window === 'undefined' || typeof window.setTimeout !== 'function') {
          button.disabled = false;
          button.textContent = originalText;
          delete button.dataset.spxFavoriteOriginalText;
          return;
        }
        window.setTimeout(function restoreFavoriteButton() {
          if ('isConnected' in button && !button.isConnected) return;
          button.disabled = false;
          button.textContent = originalText;
          delete button.dataset.spxFavoriteOriginalText;
        }, 1400);
      }
    );
  }

  function normalizeFavoriteNavSource(value) {
    return value === 'watch' ? 'watch' : 'site';
  }

  function normalizeFavoriteNavFilter(value) {
    var filter = String(value || 'all');
    return /^(all|updated|recent|unread|resource|image|ai)$/.test(filter) ? filter : 'all';
  }

  function normalizeFavoriteNavSort(value) {
    var sort = String(value || 'recent');
    return /^(updated|recent|read|reply)$/.test(sort) ? sort : 'recent';
  }

  function formatFavoriteNavCount(count, loading) {
    if (loading) return '...';
    var value = Number(count);
    if (!isFinite(value) || value < 0) return '-';
    if (value >= 1000) return '999+';
    return String(value);
  }

  function getFavoriteNavCountCacheKey(favoriteUrl) {
    return String(favoriteUrl || '').replace(/#.*$/, '');
  }

  function normalizeFavoriteNavCountCacheEntry(entry, now) {
    var source = entry || {};
    var count = Number(source.count);
    var updatedAt = Number(source.updatedAt);
    var nextRefreshAt = Number(source.nextRefreshAt || 0);
    var currentTime = now === undefined ? Date.now() : Number(now);
    if (!isFinite(count) || count < 0 || !isFinite(updatedAt) || updatedAt <= 0) return null;
    return {
      count: Math.floor(count),
      updatedAt: updatedAt,
      expiresAt: updatedAt + FAVORITE_NAV_COUNT_CACHE_TTL,
      fresh: currentTime - updatedAt < FAVORITE_NAV_COUNT_CACHE_TTL,
      nextRefreshAt: isFinite(nextRefreshAt) && nextRefreshAt > 0 ? nextRefreshAt : 0,
      failedAt: Number(source.failedAt || 0) || 0,
    };
  }

  function shouldRefreshFavoriteNavCountCache(entry, now) {
    var cache = normalizeFavoriteNavCountCacheEntry(entry, now);
    var currentTime = now === undefined ? Date.now() : Number(now);
    if (!cache) {
      var nextRefreshAt = Number(entry && entry.nextRefreshAt || 0);
      return !(isFinite(nextRefreshAt) && nextRefreshAt > currentTime);
    }
    if (cache.fresh) return false;
    return !(cache.nextRefreshAt > currentTime);
  }

  function loadFavoriteNavCountCache(favoriteUrl, now) {
    var key = getFavoriteNavCountCacheKey(favoriteUrl);
    if (!key) return null;
    return normalizeFavoriteNavCountCacheEntry(loadMap(FAVORITE_NAV_COUNT_CACHE_KEY)[key], now);
  }

  function saveFavoriteNavCountCache(favoriteUrl, count, now) {
    var key = getFavoriteNavCountCacheKey(favoriteUrl);
    var value = Number(count);
    if (!key || !isFinite(value) || value < 0) return;
    var data = loadMap(FAVORITE_NAV_COUNT_CACHE_KEY);
    data[key] = {
      count: Math.floor(value),
      updatedAt: now === undefined ? Date.now() : Number(now),
    };
    var keys = Object.keys(data).sort(function sortFavoriteCountCache(left, right) {
      return Number(data[right] && data[right].updatedAt || 0) - Number(data[left] && data[left].updatedAt || 0);
    });
    keys.slice(12).forEach(function pruneFavoriteCountCache(item) {
      delete data[item];
    });
    saveMap(FAVORITE_NAV_COUNT_CACHE_KEY, data);
  }

  function delayFavoriteNavCountCacheRefresh(favoriteUrl, now) {
    var key = getFavoriteNavCountCacheKey(favoriteUrl);
    if (!key) return;
    var currentTime = now === undefined ? Date.now() : Number(now);
    var data = loadMap(FAVORITE_NAV_COUNT_CACHE_KEY);
    var entry = data[key] || {};
    entry.failedAt = currentTime;
    entry.nextRefreshAt = currentTime + FAVORITE_NAV_COUNT_RETRY_TTL;
    data[key] = entry;
    saveMap(FAVORITE_NAV_COUNT_CACHE_KEY, data);
  }

  function inferFavoriteNavTags(title, meta) {
    var text = compactText([title, meta].join(' '));
    var tags = [];
    if (/资源|磁力|网盘|种子|下载|合集|ed2k|torrent|magnet/i.test(text)) tags.push('资源');
    if (/图片|图集|预览|写真|画廊|gallery/i.test(text)) tags.push('图片');
    if (/\bAI\b|人工智能|模型|ChatGPT|Claude|Gemini/i.test(text)) tags.push('AI');
    if (/阅读|长帖|续读|楼层/i.test(text)) tags.push('阅读');
    return parseTagList(tags.join('\n'));
  }

  function getFavoriteNavEntrySearchText(entry) {
    return compactText([
      entry && entry.title,
      entry && entry.author,
      entry && entry.meta,
      entry && entry.updateText,
      entry && entry.progressText,
      entry && entry.tagText,
      entry && entry.tags && entry.tags.join ? entry.tags.join(' ') : entry && entry.tags,
    ].join(' ')).toLowerCase();
  }

  function matchesFavoriteNavFilter(entry, filter) {
    var normalized = normalizeFavoriteNavFilter(filter);
    var text = getFavoriteNavEntrySearchText(entry);
    if (normalized === 'all') return true;
    if (normalized === 'updated') return !!(entry && entry.hasNewReplies);
    if (normalized === 'recent') return !entry || !entry.savedAt || Date.now() - entry.savedAt <= 14 * 24 * 60 * 60 * 1000;
    if (normalized === 'unread') return !entry || !entry.read || !!entry.hasNewReplies;
    if (normalized === 'resource') return /资源|磁力|网盘|种子|下载|合集|ed2k|torrent|magnet/i.test(text);
    if (normalized === 'image') return /图片|图集|预览|写真|画廊|gallery/i.test(text);
    if (normalized === 'ai') return /\bai\b|人工智能|模型|chatgpt|claude|gemini/i.test(text);
    return true;
  }

  function filterFavoriteNavEntries(entries, options) {
    var query = normalizeCenterSearchQuery(options && options.query);
    var filter = normalizeFavoriteNavFilter(options && options.filter);
    return (entries || []).filter(function matchFavoriteEntry(entry) {
      if (query && getFavoriteNavEntrySearchText(entry).indexOf(query) === -1) return false;
      return matchesFavoriteNavFilter(entry, filter);
    });
  }

  function sortFavoriteNavEntries(entries, sort) {
    var normalized = normalizeFavoriteNavSort(sort);
    return (entries || []).slice().sort(function sortFavoriteEntry(left, right) {
      if (normalized === 'updated') {
        return (Number(right.unreadReplies) || 0) - (Number(left.unreadReplies) || 0) ||
          (Number(right.updateCheckedAt) || 0) - (Number(left.updateCheckedAt) || 0) ||
          (Number(right.savedAt) || 0) - (Number(left.savedAt) || 0);
      }
      if (normalized === 'reply') return (Number(right.replies) || 0) - (Number(left.replies) || 0);
      if (normalized === 'read') return (Number(right.progressAt) || 0) - (Number(left.progressAt) || 0);
      return (Number(right.savedAt) || 0) - (Number(left.savedAt) || 0) || (Number(left.index) || 0) - (Number(right.index) || 0);
    });
  }

  function parseFavoriteReplyCount(text) {
    var count = parseThreadReplyCount(text);
    return count === null ? 0 : count;
  }

  function parseFavoriteSavedAt(text, now) {
    var value = String(text || '').replace(/\s+/g, ' ');
    var base = new Date(now || Date.now());
    var fullMatch = value.match(/(20\d{2})[-/.年](\d{1,2})[-/.月](\d{1,2})日?\s+(\d{1,2}):(\d{2})/);
    if (fullMatch) {
      return new Date(
        Number(fullMatch[1]),
        Number(fullMatch[2]) - 1,
        Number(fullMatch[3]),
        Number(fullMatch[4]),
        Number(fullMatch[5])
      ).getTime() || 0;
    }
    var shortMatch = value.match(/(?:收藏|时间|保存)?\s*(\d{1,2})[-/.月](\d{1,2})日?\s+(\d{1,2}):(\d{2})/);
    if (shortMatch) {
      return new Date(
        base.getFullYear(),
        Number(shortMatch[1]) - 1,
        Number(shortMatch[2]),
        Number(shortMatch[3]),
        Number(shortMatch[4])
      ).getTime() || 0;
    }
    var todayMatch = value.match(/今天\s*(\d{1,2}):(\d{2})/);
    if (todayMatch) {
      return new Date(base.getFullYear(), base.getMonth(), base.getDate(), Number(todayMatch[1]), Number(todayMatch[2])).getTime() || 0;
    }
    var yesterdayMatch = value.match(/昨天\s*(\d{1,2}):(\d{2})/);
    if (yesterdayMatch) {
      return new Date(base.getFullYear(), base.getMonth(), base.getDate() - 1, Number(yesterdayMatch[1]), Number(yesterdayMatch[2])).getTime() || 0;
    }
    var relativeMatch = value.match(/(\d+)\s*(分钟|小时|天)前/);
    if (relativeMatch) {
      var amount = Number(relativeMatch[1]) || 0;
      var unit = relativeMatch[2];
      var duration = unit === '分钟' ? amount * 60 * 1000 : (unit === '小时' ? amount * 60 * 60 * 1000 : amount * 24 * 60 * 60 * 1000);
      return Date.now() - duration;
    }
    return 0;
  }

  function normalizeFavoriteNavSeenMap(map) {
    var result = {};
    Object.keys(map || {}).forEach(function normalizeFavoriteSeenKey(key) {
      var time = Number(map[key]) || 0;
      if (key && time > 0) result[key] = time;
    });
    return result;
  }

  function applyFavoriteNavSeenTimes(entries, seenMap, now) {
    var map = normalizeFavoriteNavSeenMap(seenMap);
    var changed = false;
    var currentTime = now || Date.now();
    (entries || []).forEach(function applyFavoriteSeen(entry) {
      if (!entry || entry.source !== 'site' || !entry.id) return;
      if (entry.savedAt) {
        entry.savedAtLabel = '收藏';
        if (!map[entry.id] || entry.savedAt < map[entry.id]) {
          map[entry.id] = entry.savedAt;
          changed = true;
        }
        return;
      }
      if (!map[entry.id]) {
        map[entry.id] = currentTime;
        changed = true;
      }
      entry.savedAt = map[entry.id];
      entry.savedAtLabel = '记录';
    });
    return { map: map, changed: changed };
  }

  function createSiteFavoriteEntryFromLink(link, pageUrl, index, readMap, now) {
    if (!link) return null;
    var href = normalizeNavigationHref(link.getAttribute('href') || link.href, pageUrl || location.href);
    var id = parseThreadId(href) || href;
    var title = compactText(link.textContent || link.title || '');
    if (!href || !title || !/read\.php/i.test(href)) return null;
    var row = link.closest ? (link.closest('tr') || link.closest('.tr3') || link.parentNode) : link.parentNode;
    var rowText = compactText(row ? row.textContent : '');
    var authorLink = row ? qsa('a[href*="u.php"]', row).filter(function keepAuthorLink(candidate) {
      return candidate !== link && !/read\.php/i.test(candidate.getAttribute('href') || candidate.href || '');
    })[0] : null;
    var author = compactText(authorLink ? authorLink.textContent : '');
    var tags = inferFavoriteNavTags(title, rowText);
    var savedAt = parseFavoriteSavedAt(rowText, now);
    var deleteRequest = createSiteFavoriteDeleteRequest(row, pageUrl);
    return {
      source: 'site',
      id: String(id),
      title: title,
      url: href,
      author: author,
      meta: rowText,
      savedAt: savedAt,
      savedAtLabel: savedAt ? '收藏' : '',
      progressAt: 0,
      replies: parseFavoriteReplyCount(rowText),
      read: !!(readMap && readMap[id]),
      tags: tags,
      tagText: formatTags(tags),
      index: index,
      deleteRequest: deleteRequest,
    };
  }

  function parseFavoriteNavEntriesFromDocument(root, pageUrl, readMap, limit, seenMap, now) {
    var seen = {};
    var entries = [];
    var maxItems = Math.max(1, Number(limit) || FAVORITE_NAV_MAX_FETCHED_ITEMS);
    qsa('a[href*="read.php"]', root).some(function collectFavoriteLink(link) {
      var entry = createSiteFavoriteEntryFromLink(link, pageUrl, entries.length, readMap, now);
      if (!entry || seen[entry.url]) return false;
      seen[entry.url] = true;
      entries.push(entry);
      return entries.length >= maxItems;
    });
    return entries;
  }

  function getFavoriteNavWatchEntries(state, progressOverride) {
    var source = state || {};
    var watchMap = source.watch || source;
    var progressMap = source.watch || source.progress ? (source.progress || {}) : (progressOverride || {});
    var updateMap = source.threadUpdates || {};
    return getWatchCenterEntries(watchMap || {}, progressMap || {}).map(function mapWatchFavorite(entry, index) {
      var updateRecord = getThreadUpdateStatusForEntry(entry, updateMap);
      return {
        source: 'watch',
        id: entry.id,
        title: entry.title,
        url: entry.progressUrl || entry.url,
        author: '',
        meta: entry.progressText || '',
        savedAt: entry.savedAt,
        progressAt: entry.progressAt,
        progressText: entry.progressText,
        floorLabel: entry.floorLabel,
        nextFloorLabel: entry.nextFloorLabel,
        replies: updateRecord ? updateRecord.knownReplies : 0,
        read: !!entry.isCompleted,
        tags: entry.tags,
        tagText: entry.tagText,
        index: index,
      };
    });
  }

  function createFavoriteNavEntryFromThreadInfo(info, now) {
    if (!info) return null;
    var tid = parseThreadId(info.id);
    var url = info.titleLink && info.titleLink.href ? info.titleLink.href : getThreadPreviewUrl(info);
    var title = compactText(info.title || (info.titleLink && info.titleLink.textContent) || '');
    if (!tid || !url || !title) return null;
    var timestamp = now === undefined ? Date.now() : Number(now);
    var savedAt = isFinite(timestamp) ? timestamp : Date.now();
    var rowText = compactText([title, info.author].filter(Boolean).join(' '));
    var tags = inferFavoriteNavTags(title, rowText);
    var replies = normalizeThreadUpdateReplyCount(info.replies);
    if (replies === null) replies = parseFavoriteReplyCount(rowText) || 0;
    return {
      source: 'site',
      id: tid,
      title: title,
      url: url,
      author: info.author || '',
      meta: rowText,
      savedAt: savedAt,
      savedAtLabel: '收藏',
      progressAt: 0,
      replies: replies,
      read: false,
      tags: tags,
      tagText: formatTags(tags),
      index: 0,
    };
  }

  function createReadPageFavoriteInfo(tid, title, author, url) {
    var id = parseThreadId(tid);
    if (!id) return null;
    return {
      id: id,
      title: compactText(title) || '未命名帖子',
      author: compactText(author),
      url: String(url || location.href || '').split('#')[0],
    };
  }

  function bumpFavoriteNavTriggerCount(wrapper, panelState, state) {
    var countNode = qs('.spx-favorite-nav-count', wrapper);
    if (!countNode) return;
    var current = panelState && isFinite(Number(panelState.lastKnownCount))
      ? Number(panelState.lastKnownCount)
      : parseInt(countNode.textContent, 10);
    if (!isFinite(current)) current = Object.keys((state && state.watch) || {}).length;
    if (!isFinite(current)) return;
    var next = current + 1;
    if (panelState) {
      var watchCount = Object.keys((state && state.watch) || {}).length;
      var nextSiteCount = Math.max(0, next - watchCount);
      panelState.cachedSiteCount = nextSiteCount;
      panelState.cachedSiteCountUpdatedAt = Date.now();
      panelState.cachedSiteCountFresh = true;
      panelState.lastKnownCount = next;
      saveFavoriteNavCountCache(panelState.favoriteUrl, nextSiteCount, panelState.cachedSiteCountUpdatedAt);
    }
    countNode.textContent = formatFavoriteNavCount(next);
    countNode.title = '我的收藏已更新 ' + next + ' 条';
  }

  function syncFavoriteNavAfterSiteFavorite(info, settings, state, added) {
    if (!added) return;
    var entry = createFavoriteNavEntryFromThreadInfo(info, Date.now());
    if (!entry) return;
    if (state) {
      state.threadUpdates = state.threadUpdates || loadThreadUpdates();
      var updateResult = updateThreadReplyState(state.threadUpdates, entry, { source: 'site-favorite' }, Date.now());
      state.threadUpdates = updateResult.map;
      if (updateResult.changed) saveThreadUpdates(state.threadUpdates);
    }
    qsa('#spx-favorite-nav').forEach(function syncFavoriteWrapper(wrapper) {
      var panel = qs('.spx-favorite-nav-panel', wrapper);
      if (!panel) return;
      var panelState = ensureFavoriteNavState(panel, panel.dataset.favoriteUrl || wrapper.dataset.favoriteUrl);
      var siteEntries = panelState.siteEntries || [];
      var exists = siteEntries.some(function matchFavoriteEntry(item) {
        return String(item.id || '') === entry.id || (item.url && item.url === entry.url);
      });
      if (!exists) {
        panelState.siteEntries = [entry].concat(siteEntries).map(function reindexFavoriteEntry(item, index) {
          item.index = index;
          return item;
        });
        if (panelState.siteLoaded) {
          panelState.cachedSiteCount = panelState.siteEntries.length;
          panelState.cachedSiteCountUpdatedAt = Date.now();
          panelState.cachedSiteCountFresh = true;
          saveFavoriteNavCountCache(panelState.favoriteUrl, panelState.siteEntries.length, panelState.cachedSiteCountUpdatedAt);
        }
      }
      if (panelState.siteLoaded) {
        updateFavoriteNavTrigger(wrapper, panelState, state);
        if (!panel.hidden) renderFavoriteNavPanel(panel, settings, state, wrapper);
      } else {
        bumpFavoriteNavTriggerCount(wrapper, panelState, state);
      }
    });
  }

  function deleteSiteFavoriteEntry(entry, settings) {
    var request = entry && entry.deleteRequest;
    if (!request || !request.url) return Promise.reject(new Error('缺少删除入口'));
    var policy = {
      mode: 'action',
      label: '删除收藏',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    var options = {
      method: request.method || 'GET',
      credentials: 'include',
      cache: 'no-store',
    };
    if (request.method === 'POST') {
      options.body = request.body || '';
      options.headers = { 'Content-Type': request.contentType || 'application/x-www-form-urlencoded;charset=UTF-8' };
    }
    return requestWithPolicy(request.url, options, policy)
      .then(function readFavoriteDeleteResponse(response) {
        if (!response.ok) throw new Error('删除收藏失败');
        return readScriptResponseText(response, policy);
      })
      .then(function resolveFavoriteDeleteText(html) {
        var text = getSiteFavoriteDeleteResultText(html);
        if (text === '删除失败') throw new Error('删除收藏失败');
        return entry;
      });
  }

  function deleteSiteFavoriteEntries(entries, settings) {
    var result = { deleted: [], errors: [] };
    return (entries || []).reduce(function queueFavoriteDelete(promise, entry) {
      return promise.then(function deleteNextFavorite() {
        return deleteSiteFavoriteEntry(entry, settings).then(
          function markFavoriteDeleted(deletedEntry) {
            result.deleted.push(deletedEntry);
          },
          function markFavoriteDeleteFailed(error) {
            result.errors.push({ entry: entry, error: error });
          }
        );
      });
    }, Promise.resolve()).then(function returnFavoriteDeleteResult() {
      return result;
    });
  }

  function removeDeletedSiteFavoriteEntries(panelState, entries) {
    if (!panelState || !entries || !entries.length) return;
    var deleted = {};
    entries.forEach(function collectDeletedFavorite(entry) {
      deleted[getFavoriteNavDeleteKey(entry)] = true;
    });
    panelState.siteEntries = (panelState.siteEntries || []).filter(function keepSiteFavorite(entry) {
      return !deleted[getFavoriteNavDeleteKey(entry)];
    }).map(function reindexFavoriteEntry(entry, index) {
      entry.index = index;
      return entry;
    });
    panelState.cachedSiteCount = panelState.siteEntries.length;
    panelState.cachedSiteCountUpdatedAt = Date.now();
    panelState.cachedSiteCountFresh = true;
    saveFavoriteNavCountCache(panelState.favoriteUrl, panelState.siteEntries.length, panelState.cachedSiteCountUpdatedAt);
    panelState.lastKnownCount = null;
    var seenMap = loadMap(FAVORITE_NAV_SEEN_KEY);
    var changed = false;
    entries.forEach(function removeDeletedFavoriteSeen(entry) {
      if (entry && entry.id && seenMap[entry.id]) {
        delete seenMap[entry.id];
        changed = true;
      }
    });
    if (changed) saveMap(FAVORITE_NAV_SEEN_KEY, seenMap);
    if (panelState.selectedSiteFavorites) {
      entries.forEach(function removeDeletedFavoriteSelection(entry) {
        delete panelState.selectedSiteFavorites[getFavoriteNavDeleteKey(entry)];
      });
    }
  }

  function ensureFavoriteNavState(panel, favoriteUrl) {
    if (!panel.spxFavoriteNavState) {
      panel.spxFavoriteNavState = {
        source: 'site',
        query: '',
        filter: 'all',
        sort: 'recent',
        limit: FAVORITE_NAV_BATCH_SIZE,
        favoriteUrl: favoriteUrl || '',
        siteEntries: [],
        siteLoaded: false,
        siteLoading: false,
        siteError: '',
        lastKnownCount: null,
        cachedSiteCount: null,
        cachedSiteCountUpdatedAt: 0,
        cachedSiteCountFresh: false,
        updateChecking: false,
        updateStatus: '',
        updateCheckedAt: 0,
        selectedSiteFavorites: {},
      };
    }
    if (!panel.spxFavoriteNavState.selectedSiteFavorites) panel.spxFavoriteNavState.selectedSiteFavorites = {};
    if (favoriteUrl) panel.spxFavoriteNavState.favoriteUrl = favoriteUrl;
    return panel.spxFavoriteNavState;
  }

  function getFavoriteNavPanelEntries(panelState, state) {
    var siteEntries = decorateFavoriteNavEntriesWithProgress((panelState && panelState.siteEntries) || [], state && state.progress);
    var watchEntries = getFavoriteNavWatchEntries(state || {});
    return siteEntries.concat(watchEntries);
  }

  function syncFavoriteNavUpdateIndicator(wrapper, summary) {
    if (!wrapper) return;
    var trigger = qs('.spx-favorite-nav-trigger', wrapper);
    if (!trigger) return;
    var data = summary || {};
    var count = Math.max(0, Number(data.count) || 0);
    var updateCount = qs('.spx-favorite-nav-update-count', trigger);
    if (!updateCount) {
      updateCount = createEl('span', 'spx-favorite-nav-update-count');
      trigger.appendChild(updateCount);
    }
    updateCount.hidden = !count;
    updateCount.textContent = count ? ('新 ' + formatFavoriteNavCount(count, false)) : '';

    var note = qs('.spx-favorite-nav-update-note', wrapper);
    if (!note) {
      note = createEl('span', 'spx-favorite-nav-update-note');
      wrapper.appendChild(note);
    }
    note.hidden = !count;
    note.textContent = count ? '收藏有新回复' : '';
    if (count) note.title = '有 ' + count + ' 个收藏或稍后看主题出现新回复,共 ' + (Number(data.unreadReplies) || 0) + ' 条';
  }

  function updateFavoriteNavTrigger(wrapper, panelState, state) {
    var countNode = qs('.spx-favorite-nav-count', wrapper);
    if (!countNode) return;
    var siteCount = panelState && panelState.siteLoaded ? (panelState.siteEntries || []).length : 0;
    var cachedSiteCount = Number(panelState && panelState.cachedSiteCount);
    var hasCachedSiteCount = isFinite(cachedSiteCount) && cachedSiteCount >= 0;
    var watchCount = Object.keys((state && state.watch) || {}).length;
    var totalCount = siteCount + watchCount;
    var loading = !!(panelState && panelState.siteLoading);
    var hasSiteCount = !!(panelState && panelState.siteLoaded);
    var hasError = !!(panelState && panelState.siteError);
    var updateSummary = getFavoriteNavUpdateSummary(getFavoriteNavPanelEntries(panelState, state), state && state.threadUpdates);
    if (hasSiteCount) {
      countNode.textContent = formatFavoriteNavCount(totalCount, false);
      countNode.title = '我的收藏已读取 ' + totalCount + ' 条(站内 ' + siteCount + ',稍后看 ' + watchCount + ')';
      panelState.lastKnownCount = totalCount;
    } else if (hasCachedSiteCount) {
      var cachedTotal = cachedSiteCount + watchCount;
      countNode.textContent = formatFavoriteNavCount(cachedTotal, false);
      if (loading) {
        countNode.title = '显示短期缓存 ' + cachedTotal + ' 条(站内 ' + cachedSiteCount + ',稍后看 ' + watchCount + '),正在刷新站内收藏。';
      } else if (hasError) {
        countNode.title = '显示短期缓存 ' + cachedTotal + ' 条;站内收藏刷新失败:' + panelState.siteError;
      } else {
        countNode.title = '显示短期缓存 ' + cachedTotal + ' 条(站内 ' + cachedSiteCount + ',稍后看 ' + watchCount + ')。';
      }
      panelState.lastKnownCount = cachedTotal;
    } else if (hasError) {
      countNode.textContent = formatFavoriteNavCount(-1, false);
      countNode.title = '站内收藏读取失败:' + panelState.siteError;
    } else if (loading) {
      countNode.textContent = formatFavoriteNavCount(0, true);
      countNode.title = '正在读取站内收藏数量';
    } else {
      countNode.textContent = formatFavoriteNavCount(watchCount, false);
      countNode.title = '仅显示本地稍后看 ' + watchCount + ' 条;打开我的收藏面板后读取站内收藏。';
    }
    syncFavoriteNavUpdateIndicator(wrapper, updateSummary);
  }

  function refreshFavoriteNavPanels() {
    qsa('#spx-favorite-nav').forEach(function refreshFavoriteWrapper(wrapper) {
      var panel = qs('.spx-favorite-nav-panel', wrapper);
      var panelState = panel ? ensureFavoriteNavState(panel, panel.dataset.favoriteUrl || wrapper.dataset.favoriteUrl) : null;
      updateFavoriteNavTrigger(wrapper, panelState, {
        watch: loadMap(WATCH_KEY),
        progress: loadReadProgress(),
        threadUpdates: loadThreadUpdates(),
      });
      if (panel && !panel.hidden && typeof panel.spxRenderFavoriteNav === 'function') panel.spxRenderFavoriteNav();
    });
  }

  function createFavoriteNavAction(text, action, value, primary) {
    var button = createEl('button', primary ? 'spx-primary' : '', text);
    button.type = 'button';
    button.dataset.action = action;
    if (value !== undefined) button.dataset.value = String(value);
    if (/delete|remove|clear/.test(String(action || ''))) button.classList.add('spx-danger');
    return button;
  }

  function appendFavoriteNavEntry(list, entry, panelState) {
    var hasUpdate = !!(entry && entry.hasNewReplies);
    var item = createEl('div', 'spx-favorite-item' + (hasUpdate ? ' spx-favorite-has-update' : ''));
    item.dataset.source = entry.source || 'site';
    item.dataset.id = String(entry.id || '');
    var body = createEl('div');
    var bodyHead = createEl('div', entry.source === 'site' && entry.deleteRequest ? 'spx-favorite-body-head' : '');
    if (entry.source === 'site' && entry.deleteRequest) {
      var select = createEl('input', 'spx-favorite-select');
      select.type = 'checkbox';
      select.title = '选择后可批量删除';
      select.dataset.spxFavoriteSelect = '1';
      select.dataset.value = getFavoriteNavDeleteKey(entry);
      select.checked = !!(panelState && panelState.selectedSiteFavorites && panelState.selectedSiteFavorites[select.dataset.value]);
      bodyHead.appendChild(select);
    }
    if (hasUpdate) {
      var updateBadge = createEl('span', 'spx-favorite-update-badge', '新 +' + entry.unreadReplies);
      updateBadge.title = '已发现新回复,点击读新回复直达未读分页和楼层';
      bodyHead.appendChild(updateBadge);
    }
    var title = createEl('a', 'spx-favorite-item-title', entry.title || '未命名帖子');
    title.href = entry.url || '#';
    title.target = '_blank';
    title.rel = 'noreferrer';
    bodyHead.appendChild(title);
    body.appendChild(bodyHead);
    var metaParts = [];
    if (entry.author) metaParts.push(entry.author);
    if (entry.savedAt) metaParts.push((entry.savedAtLabel || '保存') + ' ' + formatShortTime(entry.savedAt));
    if (entry.progressText) metaParts.push('进度 ' + entry.progressText);
    if (entry.nextFloorLabel) metaParts.push('续读 ' + entry.nextFloorLabel);
    if (hasUpdate) metaParts.push('新回复 +' + entry.unreadReplies);
    if (hasUpdate && entry.knownReplies) metaParts.push('回复 ' + entry.readReplies + ' -> ' + entry.knownReplies);
    if (entry.replies) metaParts.push('回复 ' + entry.replies);
    if (entry.tagText) metaParts.push('标签 ' + entry.tagText);
    body.appendChild(createEl('div', 'spx-favorite-meta', metaParts.join(' · ') || (entry.source === 'site' ? '站内收藏' : '本地稍后看')));
    item.appendChild(body);
    var actions = createEl('div', 'spx-favorite-actions');
    if (hasUpdate) {
      actions.appendChild(createFavoriteNavAction('读新回复', 'read-favorite-update', entry.id, true));
    } else {
      var open = createEl('a', 'spx-primary', entry.source === 'watch' && entry.progressAt ? '续读' : '打开');
      open.href = entry.url || '#';
      open.target = '_blank';
      open.rel = 'noreferrer';
      actions.appendChild(open);
    }
    actions.appendChild(createFavoriteNavAction('复制', 'copy-favorite', entry.url || '', false));
    if (entry.source === 'site' && entry.deleteRequest) {
      actions.appendChild(createFavoriteNavAction('删除', 'delete-favorite-site', getFavoriteNavDeleteKey(entry), false));
    }
    if (entry.source === 'watch') actions.appendChild(createFavoriteNavAction('移除', 'remove-favorite-watch', entry.id, false));
    item.appendChild(actions);
    list.appendChild(item);
  }

  function renderFavoriteNavPanel(panel, settings, state, wrapper) {
    var panelState = ensureFavoriteNavState(panel, panel && panel.dataset.favoriteUrl);
    var updateMap = (state && state.threadUpdates) || {};
    var rawWatchEntries = getFavoriteNavWatchEntries(state);
    var rawSiteEntries = decorateFavoriteNavEntriesWithProgress(panelState.siteEntries || [], state && state.progress);
    var updateSummary = getFavoriteNavUpdateSummary(rawSiteEntries.concat(rawWatchEntries), updateMap);
    var watchEntries = decorateFavoriteNavEntriesWithUpdates(rawWatchEntries, updateMap);
    var siteEntries = decorateFavoriteNavEntriesWithUpdates(rawSiteEntries, updateMap);
    var cachedSiteCount = Number(panelState.cachedSiteCount);
    var hasCachedSiteCount = isFinite(cachedSiteCount) && cachedSiteCount >= 0;
    var displaySiteCount = panelState.siteLoaded ? siteEntries.length : hasCachedSiteCount ? cachedSiteCount : siteEntries.length;
    var source = normalizeFavoriteNavSource(panelState.source);
    var sourceEntries = source === 'watch' ? watchEntries : siteEntries;
    var filtered = sortFavoriteNavEntries(filterFavoriteNavEntries(sourceEntries, panelState), panelState.sort);
    var visible = filtered.slice(0, Math.max(1, Number(panelState.limit) || FAVORITE_NAV_BATCH_SIZE));
    var isFiltered = !!(panelState.query || normalizeFavoriteNavFilter(panelState.filter) !== 'all');
    var deletableVisible = source === 'site' ? visible.filter(function keepDeletableFavorite(entry) {
      return !!(entry && entry.deleteRequest);
    }) : [];
    var selectedEntries = getSelectedSiteFavoriteEntries(siteEntries, panelState.selectedSiteFavorites);
    var selectedVisibleCount = getSelectedSiteFavoriteEntries(visible, panelState.selectedSiteFavorites).length;
    var allVisibleSelected = !!(deletableVisible.length && selectedVisibleCount === deletableVisible.length);

    panel.textContent = '';

    var header = createEl('div', 'spx-favorite-head');
    var title = createEl('div', 'spx-favorite-title');
    title.appendChild(createEl('h3', '', '我的收藏'));
    title.appendChild(createEl('div', 'spx-favorite-summary', panelState.siteLoading ? '正在读取站内收藏页...' : (panelState.updateStatus || '新回复状态按缓存与手动检查合并,优先使用已打开页面')));
    header.appendChild(title);
    var headerButtons = createEl('div', 'spx-favorite-head-actions');
    var checkButton = createFavoriteNavAction(panelState.updateChecking ? '检查中' : '检查更新', 'check-favorite-updates', '', false);
    checkButton.disabled = !!panelState.updateChecking;
    checkButton.title = '刷新站内收藏页,并按优先级最多检查 10 条稍后看主题';
    headerButtons.appendChild(checkButton);
    var openLink = createEl('a', 'spx-favorite-open', '打开收藏页');
    openLink.href = panelState.favoriteUrl || '#';
    openLink.target = '_blank';
    openLink.rel = 'noreferrer';
    headerButtons.appendChild(openLink);
    header.appendChild(headerButtons);
    panel.appendChild(header);

    var stats = createEl('div', 'spx-favorite-stats');
    [
      ['站内收藏', panelState.siteLoading && !hasCachedSiteCount ? '...' : displaySiteCount],
      ['本次显示', visible.length + ' / ' + filtered.length],
      ['稍后看', watchEntries.length],
      ['有新回复', panelState.updateChecking ? '...' : updateSummary.count],
    ].forEach(function appendStat(item) {
      var stat = createEl('div', 'spx-favorite-stat');
      stat.appendChild(createEl('strong', '', String(item[1])));
      stat.appendChild(createEl('span', '', item[0]));
      stats.appendChild(stat);
    });
    panel.appendChild(stats);

    var query = createEl('input', 'spx-favorite-search');
    query.type = 'search';
    query.placeholder = '筛选收藏标题、作者或标签';
    query.value = panelState.query || '';
    query.dataset.spxFavoriteQuery = '1';
    panel.appendChild(query);

    var tabs = createEl('div', 'spx-favorite-tabs');
    [
      { value: 'site', label: '站内收藏 ' + (panelState.siteLoading && !hasCachedSiteCount ? '...' : displaySiteCount) },
      { value: 'watch', label: '稍后看 ' + watchEntries.length },
    ].forEach(function appendTab(tab) {
      var button = createFavoriteNavAction(tab.label, 'favorite-source', tab.value, false);
      button.className = 'spx-favorite-tab' + (source === tab.value ? ' spx-active' : '');
      tabs.appendChild(button);
    });
    panel.appendChild(tabs);

    var tools = createEl('div', 'spx-favorite-tools');
    var chips = createEl('div', 'spx-favorite-chips');
    [
      { value: 'all', label: '全部' },
      { value: 'recent', label: '最近' },
      { value: 'updated', label: '有新回复 ' + updateSummary.count },
      { value: 'unread', label: '未读' },
      { value: 'resource', label: '资源' },
      { value: 'image', label: '图片' },
      { value: 'ai', label: 'AI' },
    ].forEach(function appendChip(chip) {
      var button = createFavoriteNavAction(chip.label, 'favorite-filter', chip.value, false);
      button.className = 'spx-favorite-chip' + (chip.value === 'updated' ? ' spx-favorite-chip-update' : '') + (normalizeFavoriteNavFilter(panelState.filter) === chip.value ? ' spx-active' : '');
      chips.appendChild(button);
    });
    tools.appendChild(chips);
    var sort = createEl('select', 'spx-favorite-sort');
    sort.dataset.spxFavoriteSort = '1';
    [
      { value: 'recent', label: '最近收藏' },
      { value: 'read', label: '最近阅读' },
      { value: 'updated', label: '新回复优先' },
      { value: 'reply', label: '回复最多' },
    ].forEach(function appendSortOption(item) {
      var option = createEl('option');
      option.value = item.value;
      option.textContent = item.label;
      option.selected = normalizeFavoriteNavSort(panelState.sort) === item.value;
      sort.appendChild(option);
    });
    tools.appendChild(sort);
    panel.appendChild(tools);

    var list = createEl('div', 'spx-favorite-list');
    if (source === 'site' && panelState.siteLoading) {
      list.appendChild(createEl('div', 'spx-favorite-empty', '正在读取站内收藏页...'));
    } else if (source === 'site' && panelState.siteError) {
      list.appendChild(createEl('div', 'spx-favorite-empty', panelState.siteError + ',可先打开收藏页查看。'));
    } else if (!sourceEntries.length) {
      list.appendChild(createEl('div', 'spx-favorite-empty', source === 'site' ? '暂无可展示的站内收藏。' : '还没有本地稍后看。'));
    } else if (!visible.length) {
      list.appendChild(createEl('div', 'spx-favorite-empty', '没有匹配的收藏。'));
    } else {
      var group = createEl('div', 'spx-favorite-group');
      var groupMain = createEl('div', 'spx-favorite-group-main');
      groupMain.appendChild(createEl('span', '', source === 'site' ? '站内收藏' : '本地稍后看'));
      groupMain.appendChild(createEl('span', '', (isFiltered ? filtered.length + ' / ' : '') + sourceEntries.length + ' 条'));
      group.appendChild(groupMain);
      var groupActions = createEl('div', 'spx-favorite-group-actions');
      if (source === 'site' && deletableVisible.length) {
        groupActions.appendChild(createFavoriteNavAction(allVisibleSelected ? '取消本页' : '全选本页', 'toggle-visible-favorite-site', allVisibleSelected ? 'off' : 'on', false));
      }
      if (source === 'site' && selectedEntries.length) {
        groupActions.appendChild(createEl('span', 'spx-favorite-selected-count', '已选 ' + selectedEntries.length));
        groupActions.appendChild(createFavoriteNavAction('删除所选', 'delete-selected-favorite-site', '', false));
      }
      group.appendChild(groupActions);
      list.appendChild(group);
      visible.forEach(function appendVisibleEntry(entry) {
        appendFavoriteNavEntry(list, entry, panelState);
      });
      if (filtered.length > visible.length) {
        var load = createEl('div', 'spx-favorite-load');
        load.appendChild(createEl('span', '', '已显示 ' + visible.length + ' 条,继续加载下一批。'));
        load.appendChild(createFavoriteNavAction('加载更多', 'favorite-load-more'));
        list.appendChild(load);
      }
    }
    panel.appendChild(list);
    updateFavoriteNavTrigger(wrapper, panelState, state);
  }

  function loadFavoriteNavSiteEntries(panel, settings, state, wrapper, force) {
    var panelState = ensureFavoriteNavState(panel, panel && panel.dataset.favoriteUrl);
    if ((!force && panelState.siteLoaded) || panelState.siteLoading || !panelState.favoriteUrl) return Promise.resolve(panelState);
    if (typeof DOMParser === 'undefined') return Promise.resolve(panelState);
    panelState.siteLoading = true;
    panelState.siteError = '';
    renderFavoriteNavPanel(panel, settings, state, wrapper);
    var policy = {
      mode: 'interactive',
      label: '我的收藏',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    return requestWithPolicy(panelState.favoriteUrl, { credentials: 'include', cache: 'no-store' }, policy)
      .then(function readFavoriteResponse(response) {
        if (!response.ok) throw new Error('读取收藏页失败');
        return readScriptResponseText(response, policy);
      })
      .then(function parseFavoriteHtml(html) {
        var doc = new DOMParser().parseFromString(html, 'text/html');
        var seenMap = loadMap(FAVORITE_NAV_SEEN_KEY);
        var checkedAt = Date.now();
        panelState.siteEntries = parseFavoriteNavEntriesFromDocument(
          doc,
          panelState.favoriteUrl,
          state && state.read,
          FAVORITE_NAV_MAX_FETCHED_ITEMS,
          seenMap,
          checkedAt
        );
        var updateResult = applyThreadUpdateEntries((state && state.threadUpdates) || {}, panelState.siteEntries, { source: 'favorite-page' }, checkedAt);
        if (state) state.threadUpdates = updateResult.map;
        if (updateResult.changed) saveThreadUpdates(updateResult.map);
        var seenResult = applyFavoriteNavSeenTimes(panelState.siteEntries, seenMap, checkedAt);
        if (seenResult.changed) saveMap(FAVORITE_NAV_SEEN_KEY, seenResult.map);
        panelState.cachedSiteCount = panelState.siteEntries.length;
        panelState.cachedSiteCountUpdatedAt = checkedAt;
        panelState.cachedSiteCountFresh = true;
        saveFavoriteNavCountCache(panelState.favoriteUrl, panelState.siteEntries.length, panelState.cachedSiteCountUpdatedAt);
        panelState.siteLoaded = true;
        panelState.updateCheckedAt = checkedAt;
      })
      .catch(function handleFavoriteLoadError(error) {
        panelState.siteError = error && error.message ? error.message : '读取收藏页失败';
        delayFavoriteNavCountCacheRefresh(panelState.favoriteUrl);
      })
      .then(function finishFavoriteLoad() {
        panelState.siteLoading = false;
        if (panel && !panel.hidden) renderFavoriteNavPanel(panel, settings, state, wrapper);
        else updateFavoriteNavTrigger(wrapper, panelState, state);
        return panelState;
      });
  }

  function getFavoriteNavWatchUpdateCandidates(state, now, manual) {
    var updateMap = (state && state.threadUpdates) || {};
    return getFavoriteNavWatchEntries(state || {})
      .filter(function keepWatchUpdateCandidate(entry) {
        return shouldCheckThreadUpdate(entry, updateMap[entry.id], now, manual);
      })
      .sort(function sortWatchUpdateCandidate(left, right) {
        var leftRecord = getThreadUpdateStatusForEntry(left, updateMap) || {};
        var rightRecord = getThreadUpdateStatusForEntry(right, updateMap) || {};
        if (!!rightRecord.hasNewReplies !== !!leftRecord.hasNewReplies) return rightRecord.hasNewReplies ? 1 : -1;
        var leftActivity = getThreadUpdateActivityAt(left, leftRecord);
        var rightActivity = getThreadUpdateActivityAt(right, rightRecord);
        if (rightActivity !== leftActivity) return rightActivity - leftActivity;
        return (Number(leftRecord.lastCheckedAt) || 0) - (Number(rightRecord.lastCheckedAt) || 0);
      });
  }

  function checkFavoriteNavWatchUpdates(settings, state, manual) {
    var currentTime = Date.now();
    var candidates = getFavoriteNavWatchUpdateCandidates(state || {}, currentTime, manual);
    var selected = candidates.slice(0, THREAD_UPDATE_CHECK_BATCH_SIZE);
    var result = { checked: 0, total: candidates.length, changed: false, errors: 0 };
    if (!selected.length || typeof DOMParser === 'undefined') return Promise.resolve(result);
    state.threadUpdates = state.threadUpdates || loadThreadUpdates();
    return selected.reduce(function queueWatchUpdateCheck(promise, entry) {
      return promise.then(function checkOneWatchEntry() {
        var targetUrl = buildThreadLatestReadUrl(entry.id, entry.url || entry.progressUrl);
        var policy = {
          mode: 'interactive',
          label: '稍后看更新检查',
          networkFriendly: isNetworkFriendlyMode(settings),
          cooldownMs: THREAD_UPDATE_RATE_LIMIT_COOLDOWN,
        };
        return requestWithPolicy(targetUrl, { credentials: 'include', cache: 'no-store' }, policy)
          .then(function readWatchUpdateResponse(response) {
            if (!response.ok) throw new Error('检查失败');
            return readScriptResponseText(response, policy);
          })
          .then(function parseWatchUpdateHtml(html) {
            var doc = new DOMParser().parseFromString(html, 'text/html');
            var replies = parseThreadReadReplyCountFromDocument(doc, targetUrl);
            var updateResult = updateThreadReplyState(state.threadUpdates, Object.assign({}, entry, {
              replies: replies,
              url: entry.url || targetUrl,
            }), { source: 'watch-manual-check' }, Date.now());
            state.threadUpdates = updateResult.map;
            if (updateResult.changed) result.changed = true;
            result.checked += 1;
          })
          .catch(function keepCheckingAfterWatchUpdateError(error) {
            if (error && error.spxRateLimited) throw error;
            result.errors += 1;
          });
      });
    }, Promise.resolve()).then(function finishWatchUpdateCheck() {
      if (result.changed) saveThreadUpdates(state.threadUpdates);
      return result;
    });
  }

  function checkFavoriteNavUpdates(panel, settings, state, wrapper) {
    var panelState = ensureFavoriteNavState(panel, panel && panel.dataset.favoriteUrl);
    if (panelState.updateChecking) return;
    panelState.updateChecking = true;
    panelState.updateStatus = '正在检查新回复,站内收藏优先走批量页,稍后看最多抽查 ' + THREAD_UPDATE_CHECK_BATCH_SIZE + ' 条';
    renderFavoriteNavPanel(panel, settings, state, wrapper);
    loadFavoriteNavSiteEntries(panel, settings, state, wrapper, true)
      .then(function checkWatchEntriesAfterSiteFavorites() {
        return checkFavoriteNavWatchUpdates(settings, state, true);
      })
      .then(function finishFavoriteUpdateCheck(result) {
        var summary = getFavoriteNavUpdateSummary(getFavoriteNavPanelEntries(panelState, state), state && state.threadUpdates);
        panelState.updateChecking = false;
        panelState.updateCheckedAt = Date.now();
        panelState.updateStatus = '已检查站内收藏和 ' + result.checked + '/' + result.total + ' 条稍后看;有新回复 ' + summary.count + ' 个';
        renderFavoriteNavPanel(panel, settings, state, wrapper);
        refreshFavoriteNavPanels();
      }, function failFavoriteUpdateCheck(error) {
        panelState.updateChecking = false;
        panelState.updateStatus = error && error.message ? error.message : '检查新回复失败';
        renderFavoriteNavPanel(panel, settings, state, wrapper);
      });
  }

  function findFavoriteNavEntryForUpdate(panelState, state, id) {
    var updateMap = (state && state.threadUpdates) || {};
    var entries = decorateFavoriteNavEntriesWithUpdates(getFavoriteNavPanelEntries(panelState, state), updateMap);
    return entries.filter(function matchFavoriteUpdateEntry(entry) {
      return String(entry.id || '') === String(id || '');
    })[0] || null;
  }

  function openFavoriteUpdateEntry(state, entry) {
    if (!entry || !entry.id) return;
    var firstUnreadReply = getFavoriteNavFirstUnreadReply(entry);
    var targetUrl = getFavoriteNavUnreadUrl(entry) || entry.url || entry.progressUrl;
    if (firstUnreadReply) {
      requestReadProgressRestore(entry.id, 'next', { reply: firstUnreadReply });
      if (targetUrl) location.href = targetUrl;
      return;
    }
    if (entry.progressAt && entry.progressUrl) {
      openProgressEntry(state, entry.id, targetUrl, 'next');
      return;
    }
    requestReadProgressRestore(entry.id, 'next');
    if (targetUrl) location.href = targetUrl;
  }

  function closeFavoriteNavPanel(wrapper) {
    var panel = qs('.spx-favorite-nav-panel', wrapper);
    var trigger = qs('.spx-favorite-nav-trigger', wrapper);
    if (panel) panel.hidden = true;
    if (trigger) trigger.setAttribute('aria-expanded', 'false');
  }

  function setFavoriteNavPanelHidden(wrapper, hidden, settings, state) {
    var panel = qs('.spx-favorite-nav-panel', wrapper);
    var trigger = qs('.spx-favorite-nav-trigger', wrapper);
    if (!panel || !trigger) return;
    panel.hidden = !!hidden;
    trigger.setAttribute('aria-expanded', String(!panel.hidden));
    if (!panel.hidden) {
      renderFavoriteNavPanel(panel, settings, state, wrapper);
      loadFavoriteNavSiteEntries(panel, settings, state, wrapper);
      var input = qs('.spx-favorite-search', panel);
      if (input && typeof input.focus === 'function') input.focus();
    }
  }

  function getFavoriteNavMountHost(scope) {
    return qs('#mainNav>div[style*="padding-left"]', scope) || qs('#mainNav', scope);
  }

  function enhanceFavoriteNavigation(settings, state, root) {
    var scope = root || document;
    var host = getFavoriteNavMountHost(scope);
    if (!host) return;
    var uid = extractAccountUserId(scope, location.href);
    var favoriteUrl = getFavoriteNavUrl(uid, location.origin);
    if (!favoriteUrl) return;

    var wrapper = qs('#spx-favorite-nav', host) || createEl('div', 'spx-favorite-nav');
    wrapper.id = 'spx-favorite-nav';
    wrapper.dataset.favoriteUrl = favoriteUrl;
    var panel = qs('.spx-favorite-nav-panel', wrapper);

    if (!panel) {
      var trigger = createEl('button', 'spx-favorite-nav-trigger');
      trigger.type = 'button';
      trigger.setAttribute('aria-haspopup', 'dialog');
      trigger.setAttribute('aria-expanded', 'false');
      trigger.setAttribute('aria-controls', 'spx-favorite-nav-panel');
      trigger.title = '打开我的收藏';
      trigger.appendChild(createEl('span', 'spx-favorite-nav-star', '★'));
      trigger.appendChild(createEl('span', '', '我的收藏'));
      trigger.appendChild(createEl('span', 'spx-favorite-nav-count', formatFavoriteNavCount(Object.keys((state && state.watch) || {}).length, false)));
      wrapper.appendChild(trigger);

      panel = createEl('section', 'spx-favorite-nav-panel');
      panel.id = 'spx-favorite-nav-panel';
      panel.hidden = true;
      panel.dataset.favoriteUrl = favoriteUrl;
      wrapper.appendChild(panel);

      trigger.addEventListener('click', function toggleFavoriteNav(event) {
        event.preventDefault();
        event.stopPropagation();
        setFavoriteNavPanelHidden(wrapper, !panel.hidden, settings, state);
      });
      panel.addEventListener('click', function stopFavoritePanelClick(event) {
        event.stopPropagation();
      });
      panel.addEventListener('input', function handleFavoriteInput(event) {
        var target = event.target;
        if (!target || !target.dataset || target.dataset.spxFavoriteQuery !== '1') return;
        var panelState = ensureFavoriteNavState(panel, favoriteUrl);
        panelState.query = target.value;
        panelState.limit = FAVORITE_NAV_BATCH_SIZE;
        renderFavoriteNavPanel(panel, settings, state, wrapper);
        var nextInput = qs('.spx-favorite-search', panel);
        if (nextInput) nextInput.focus();
      });
      panel.addEventListener('change', function handleFavoriteChange(event) {
        var target = event.target;
        var panelState = ensureFavoriteNavState(panel, favoriteUrl);
        if (!target || !target.dataset) return;
        if (target.dataset.spxFavoriteSelect === '1') {
          var selectionKey = target.dataset.value;
          if (!selectionKey) return;
          if (target.checked) panelState.selectedSiteFavorites[selectionKey] = true;
          else delete panelState.selectedSiteFavorites[selectionKey];
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (target.dataset.spxFavoriteSort !== '1') return;
        panelState.sort = normalizeFavoriteNavSort(target.value);
        panelState.limit = FAVORITE_NAV_BATCH_SIZE;
        renderFavoriteNavPanel(panel, settings, state, wrapper);
      });
      panel.addEventListener('click', function handleFavoriteAction(event) {
        var target = event.target;
        var action = target && target.dataset && target.dataset.action;
        if (!action) return;
        var panelState = ensureFavoriteNavState(panel, favoriteUrl);
        if (action === 'favorite-source') {
          panelState.source = normalizeFavoriteNavSource(target.dataset.value);
          panelState.limit = FAVORITE_NAV_BATCH_SIZE;
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (action === 'favorite-filter') {
          panelState.filter = normalizeFavoriteNavFilter(target.dataset.value);
          panelState.limit = FAVORITE_NAV_BATCH_SIZE;
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (action === 'favorite-load-more') {
          panelState.limit += FAVORITE_NAV_BATCH_SIZE;
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (action === 'check-favorite-updates') {
          checkFavoriteNavUpdates(panel, settings, state, wrapper);
          return;
        }
        if (action === 'read-favorite-update') {
          openFavoriteUpdateEntry(state, findFavoriteNavEntryForUpdate(panelState, state, target.dataset.value));
          return;
        }
        if (action === 'copy-favorite') {
          copyTextToClipboard(target.dataset.value).then(
            function showFavoriteCopySuccess() { setTemporaryText(target, '已复制', '复制'); },
            function showFavoriteCopyFailure() { setTemporaryText(target, '失败', '复制'); }
          );
          return;
        }
        if (action === 'toggle-visible-favorite-site') {
          var shouldSelectVisible = target.dataset.value !== 'off';
          var decoratedSiteEntries = decorateFavoriteNavEntriesWithUpdates(panelState.siteEntries || [], state && state.threadUpdates);
          var visibleForSelection = sortFavoriteNavEntries(
            filterFavoriteNavEntries(decoratedSiteEntries, panelState),
            panelState.sort
          ).slice(0, Math.max(1, Number(panelState.limit) || FAVORITE_NAV_BATCH_SIZE));
          visibleForSelection.forEach(function toggleVisibleFavoriteSelection(entry) {
            if (!entry || !entry.deleteRequest) return;
            var key = getFavoriteNavDeleteKey(entry);
            if (shouldSelectVisible) panelState.selectedSiteFavorites[key] = true;
            else delete panelState.selectedSiteFavorites[key];
          });
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          return;
        }
        if (action === 'delete-favorite-site' || action === 'delete-selected-favorite-site') {
          var entriesToDelete;
          if (action === 'delete-selected-favorite-site') {
            entriesToDelete = getSelectedSiteFavoriteEntries(panelState.siteEntries || [], panelState.selectedSiteFavorites);
          } else {
            entriesToDelete = (panelState.siteEntries || []).filter(function matchSiteFavorite(entry) {
              return getFavoriteNavDeleteKey(entry) === target.dataset.value;
            });
          }
          entriesToDelete = (entriesToDelete || []).filter(function keepDeletableSiteFavorite(entry) {
            return !!(entry && entry.deleteRequest);
          });
          if (!entriesToDelete.length) return;
          var confirmText = entriesToDelete.length > 1
            ? '确定删除选中的 ' + entriesToDelete.length + ' 条站内收藏?此操作会同步到原站收藏夹。'
            : '删除这个站内收藏?此操作会同步到原站收藏夹。';
          confirmBatchAction({
            title: entriesToDelete.length > 1 ? '删除所选站内收藏' : '删除站内收藏',
            message: '此操作会同步到原站收藏夹,不只是本地隐藏。',
            confirmText: '删除收藏',
            impacts: [{ label: '站内收藏', value: entriesToDelete.length, note: '同步原站' }],
            items: entriesToDelete.map(function mapFavoriteConfirmItem(entry) {
              return { title: entry.title, meta: [entry.author, entry.url].filter(Boolean).join(' · ') };
            }),
          }, confirmText).then(function deleteFavoriteAfterConfirm(ok) {
            if (!ok) return;
            var originalText = target.textContent || '删除';
            target.disabled = true;
            target.textContent = '删除中';
            deleteSiteFavoriteEntries(entriesToDelete, settings).then(function handleFavoriteDeleteResult(result) {
              if (result.deleted.length) {
                removeDeletedSiteFavoriteEntries(panelState, result.deleted);
                updateFavoriteNavTrigger(wrapper, panelState, state);
                renderFavoriteNavPanel(panel, settings, state, wrapper);
              }
              if (result.errors.length && !result.deleted.length) {
                target.disabled = false;
                setTemporaryText(target, '删除失败', originalText);
                return;
              }
              if (result.errors.length && typeof window.alert === 'function') {
                window.alert('部分收藏删除失败:' + result.errors.length + ' 条');
              }
            });
          });
          return;
        }
        if (action === 'remove-favorite-watch') {
          var id = target.dataset.value;
          if (!id || !state || !state.watch) return;
          delete state.watch[id];
          saveMap(WATCH_KEY, state.watch);
          refreshWatchCenter();
          renderFavoriteNavPanel(panel, settings, state, wrapper);
          refreshFavoriteNavPanels();
        }
      });
      document.addEventListener('click', function closeFavoriteNavOnOutside(event) {
        if (!wrapper.contains(event.target)) closeFavoriteNavPanel(wrapper);
      });
      document.addEventListener('keydown', function closeFavoriteNavOnEscape(event) {
        if (event.key === 'Escape') closeFavoriteNavPanel(wrapper);
      });
    } else {
      panel.dataset.favoriteUrl = favoriteUrl;
    }

    if (!wrapper.parentNode) host.appendChild(wrapper);
    var panelState = ensureFavoriteNavState(panel, favoriteUrl);
    panel.spxRenderFavoriteNav = function renderCurrentFavoriteNavPanel() {
      if (state) {
        state.read = loadMap(READ_KEY);
        state.watch = loadMap(WATCH_KEY);
        state.progress = loadReadProgress();
        state.threadUpdates = loadThreadUpdates();
      }
      renderFavoriteNavPanel(panel, settings, state, wrapper);
    };
    var countCacheKey = getFavoriteNavCountCacheKey(favoriteUrl);
    var countCacheSource = countCacheKey ? loadMap(FAVORITE_NAV_COUNT_CACHE_KEY)[countCacheKey] : null;
    var countCache = loadFavoriteNavCountCache(favoriteUrl);
    if (countCache) {
      panelState.cachedSiteCount = countCache.count;
      panelState.cachedSiteCountUpdatedAt = countCache.updatedAt;
      panelState.cachedSiteCountFresh = countCache.fresh;
    }
    updateFavoriteNavTrigger(wrapper, panel.spxFavoriteNavState, state);
    flushFavoriteNavStatusNotes();
    if (shouldRefreshFavoriteNavCountCache(countCacheSource)) {
      loadFavoriteNavSiteEntries(panel, settings, state, wrapper);
    }
  }

  function enhanceAccountNavigation(root) {
    if (!shouldUseProfilePage(location.href)) return;
    var scope = root || document;
    var uid = extractAccountUserId(scope, location.href);
    if (!uid) return;

    var tabs = qs('#spx-account-tabs', scope);
    if (!tabs) {
      tabs = createEl('nav', 'spx-account-tabs');
      tabs.id = 'spx-account-tabs';
    }

    var activeKey = getAccountActiveKey(location.href);
    tabs.textContent = '';
    getAccountTabItems(uid, location.origin).forEach(function appendAccountTab(item) {
      var link = createEl('a', item.key === activeKey ? 'spx-account-tab-active' : '', item.label);
      link.href = item.href;
      tabs.appendChild(link);
    });

    var mountBefore = qs('#u-wrap', scope) || qs('#set-wrap', scope) || qs('.bdbA', scope);
    var main = qs('#main', scope) || document.body;
    if (mountBefore && mountBefore.parentNode) {
      mountBefore.parentNode.insertBefore(tabs, mountBefore);
    } else if (tabs.parentNode !== main) {
      main.insertBefore(tabs, main.firstChild);
    }

    document.documentElement.classList.add('spx-account-tabs-ready');
  }

  function extractThreadCellInfo(cell) {
    var row = cell.closest('tr') || cell;
    var id = parseThreadId(cell.id);
    var titleLink = qs('#a_ajax_' + id, row) || qs('a[href*="read.php?tid"]', row);
    var authorLink = qs('a.bl[href*="u.php"]', row);
    return {
      id: id,
      cell: cell,
      row: row,
      titleLink: titleLink,
      title: titleLink ? titleLink.textContent.trim() : cell.textContent.trim(),
      author: authorLink ? authorLink.textContent.trim() : '',
      replies: extractThreadReplyCountFromRow(row),
    };
  }

  function extractThreadReplyCountFromRow(row) {
    if (!row) return null;
    var cells = qsa('td', row);
    for (var index = 0; index < cells.length; index += 1) {
      var cell = cells[index];
      var text = compactText(cell && cell.textContent);
      if (!text) continue;
      var withSlash = text.match(/^(\d{1,6})\s*\/\s*\d{1,7}$/);
      if (withSlash) return normalizeThreadUpdateReplyCount(withSlash[1]);
      var labeled = parseThreadReplyCount(text);
      if (labeled !== null) return labeled;
      var byTitle = parseThreadReplyCount(cell && cell.getAttribute ? cell.getAttribute('title') : '');
      if (byTitle !== null) return byTitle;
    }
    var joined = cells.map(function mapThreadReplyCell(cell) {
      return compactText(cell && cell.textContent);
    }).filter(Boolean).join(' ');
    var rowText = joined || compactText(row.textContent || '');
    var labeled = parseThreadReplyCount(rowText);
    if (labeled !== null) return labeled;
    return null;
  }

  function extractForumGalleryCardInfo(card) {
    var inner = qs('.inner', card) || card;
    var titleLink = qs('.section-title a[href*="read.php?tid"]', inner) || qs('a[href*="read.php?tid"]', inner);
    var id = parseThreadId(titleLink ? (titleLink.getAttribute('href') || titleLink.href) : '');
    var authorLink = qs('.section-intro a.bl[href*="u.php"]', inner) || qs('.section-intro a[href*="u.php"]', inner);
    return {
      id: id,
      cell: inner,
      row: card,
      titleLink: titleLink,
      title: titleLink ? titleLink.textContent.trim() : inner.textContent.trim(),
      author: authorLink ? authorLink.textContent.trim() : '',
      replies: extractThreadReplyCountFromRow(card),
    };
  }

  function syncThreadUpdateEntries(state, entries, source) {
    if (!state || !entries || !entries.length) return false;
    state.threadUpdates = state.threadUpdates || loadThreadUpdates();
    var favoriteSeen = loadMap(FAVORITE_NAV_SEEN_KEY);
    var watchMap = state.watch || {};
    var trackedEntries = entries.filter(function keepTrackedThreadUpdateEntry(entry) {
      var id = parseThreadId(entry && entry.id);
      return !!(id && (watchMap[id] || favoriteSeen[id] || state.threadUpdates[id]));
    });
    if (!trackedEntries.length) return false;
    var result = applyThreadUpdateEntries(state.threadUpdates, trackedEntries, { source: source || 'forum-list' }, Date.now());
    state.threadUpdates = result.map;
    if (result.changed) {
      saveThreadUpdates(state.threadUpdates);
      refreshFavoriteNavPanels();
    }
    return result.changed;
  }

  function renderThreadUpdateBadge(info, state) {
    if (!info || !info.cell || !info.titleLink) return;
    var previous = qs('.spx-thread-update-badge', info.cell);
    if (previous) previous.remove();
    var id = parseThreadId(info.id);
    if (!id) return;
    var favoriteSeen = loadMap(FAVORITE_NAV_SEEN_KEY);
    if (!((state && state.watch && state.watch[id]) || favoriteSeen[id])) return;
    var entry = decorateFavoriteNavEntryWithUpdate(info, state && state.threadUpdates);
    if (!entry.hasNewReplies) return;
    var badge = createEl('span', 'spx-thread-update-badge', '新 +' + entry.unreadReplies);
    badge.title = '收藏或稍后看主题有新回复,打开我的收藏可直达未读';
    info.titleLink.insertAdjacentElement('beforebegin', badge);
  }

  function dispatchResourceBadgeFilter(type) {
    if (!type || typeof document === 'undefined') return;
    var detail = { type: type };
    try {
      document.dispatchEvent(new CustomEvent('spx-resource-filter', { detail: detail }));
    } catch (error) {
      var event = document.createEvent && document.createEvent('CustomEvent');
      if (!event) return;
      event.initCustomEvent('spx-resource-filter', true, false, detail);
      document.dispatchEvent(event);
    }
  }

  function renderThreadResourceBadges(info, badges) {
    if (!info || !info.cell || !info.titleLink) return;
    var previous = qs('.spx-resource-badges', info.cell);
    if (previous) previous.remove();
    var list = mergeResourceBadges(badges || []);
    info.resourceBadges = list;
    info.resourceBadgeTypes = getResourceBadgeTypes(list);
    if (info.row && info.row.dataset) info.row.dataset.spxResourceTypes = info.resourceBadgeTypes.join(' ');
    if (!list.length) return;

    var wrap = createEl('span', 'spx-resource-badges');
    wrap.setAttribute('aria-label', '资源类型角标');
    list.forEach(function appendResourceBadge(badge) {
      var button = createEl('button', 'spx-resource-badge spx-resource-badge-' + badge.type + (badge.guessed ? ' spx-resource-badge-guess' : ''), badge.label);
      button.type = 'button';
      button.dataset.spxResourceType = badge.type;
      button.title = (badge.guessed ? '标题关键词推测:' : '已识别资源:') + badge.label + ',点击筛选同类主题';
      button.addEventListener('click', function filterByResourceBadge(event) {
        event.preventDefault();
        event.stopPropagation();
        dispatchResourceBadgeFilter(badge.type);
      });
      wrap.appendChild(button);
    });

    var anchor = qs('.spx-thread-tools', info.cell) || qs('.spx-watch-badge', info.cell) || info.titleLink;
    anchor.insertAdjacentElement('afterend', wrap);
  }

  function updateThreadResourceBadges(info, state, previewPayload) {
    if (!info) return;
    var badges = getThreadResourceBadges(info, getThreadResourceBadgeIndex(state && state.resources), previewPayload);
    renderThreadResourceBadges(info, badges);
  }

  function isStickyCell(cell) {
    var text = (cell.textContent || '').trim();
    if (!cell.id) return false;
    var tid = Number(parseThreadId(cell.id));
    if (!tid) return false;
    if (/\[公告\]|版规|指南|长期招人|Q&A|新人报道/.test(text)) return true;
    return tid < 1000000;
  }

  function hideStickyThreads(cells) {
    (cells || qsa('td[id^="td_"]')).filter(isStickyCell).forEach(function hideCell(cell) {
      var row = cell.closest('tr') || cell;
      if (row && row.style && typeof row.style.setProperty === 'function') {
        row.style.setProperty('display', 'none', 'important');
        return;
      }
      if (row && row.style) row.style.display = 'none';
    });
  }

  function hideForumAnnouncementPanels(nodes) {
    (nodes || qsa('.gonggao')).forEach(function hidePanel(panel) {
      if (panel && panel.style && typeof panel.style.setProperty === 'function') {
        panel.style.setProperty('display', 'none', 'important');
        return;
      }
      if (panel && panel.style) panel.style.display = 'none';
    });
  }

  function hideForumAnnouncements() {
    if (detectPageType(location.href) !== 'forum') {
      restoreStickyThreads();
      return;
    }
    var stickyCells = qsa('td[id^="td_"]').filter(isStickyCell);
    hideForumAnnouncementPanels();
    if (!stickyCells.length) return;
    hideStickyThreads(stickyCells);
  }

  function restoreStickyThreads() {
    var foldBox = qs('#spx-sticky-fold-box');
    if (foldBox) {
      var foldRow = foldBox.closest('tr');
      if (foldRow) foldRow.remove();
    }
    qsa('td[id^="td_"]').filter(isStickyCell).forEach(function showCell(cell) {
      var row = cell.closest('tr') || cell;
      row.style.display = '';
    });
  }

  function createWatchBadge(id) {
    var badge = createEl('span', 'spx-watch-badge', '★');
    badge.dataset.spxWatchId = String(id || '');
    return badge;
  }

  function syncThreadWatchState(info, state, button) {
    if (!info || !info.id || !info.cell || !info.titleLink) return;
    var watch = (state && state.watch) || {};
    var saved = !!watch[info.id];
    if (button) button.textContent = saved ? '已存' : '稍后';
    var badge = qs('.spx-watch-badge', info.cell);
    if (saved && !badge) {
      info.titleLink.insertAdjacentElement('afterend', createWatchBadge(info.id));
    }
    if (!saved && badge) badge.remove();
  }

  function toggleThreadWatch(info, state, button) {
    if (!info || !info.id || !info.titleLink) return;
    state.watch = state.watch || {};
    if (state.watch[info.id]) {
      delete state.watch[info.id];
    } else {
      state.watch[info.id] = {
        title: info.title,
        url: info.titleLink.href,
        savedAt: Date.now(),
      };
      state.threadUpdates = state.threadUpdates || loadThreadUpdates();
      var updateResult = updateThreadReplyState(state.threadUpdates, info, { source: 'watch' }, Date.now());
      state.threadUpdates = updateResult.map;
      if (updateResult.changed) saveThreadUpdates(state.threadUpdates);
    }
    syncThreadWatchState(info, state, button);
    saveMap(WATCH_KEY, state.watch);
    refreshWatchCenter();
    refreshFavoriteNavPanels();
  }

  function ensureForumGalleryCardTools(info, settings, state) {
    if (!info || !info.id || !info.cell || !info.titleLink) return null;
    var tools = qs('.spx-gallery-card-tools', info.cell);
    if (!tools) {
      tools = createEl('div', 'spx-gallery-card-tools');
    }

    var watchButton = qs('[data-spx-gallery-action="watch"]', tools);
    if (!watchButton) {
      watchButton = createEl('button', '', state.watch && state.watch[info.id] ? '已存' : '稍后');
      watchButton.type = 'button';
      watchButton.dataset.spxGalleryAction = 'watch';
      watchButton.title = '切换本地稍后看';
      tools.appendChild(watchButton);
    }
    if (watchButton.dataset.spxGalleryBound !== '1') {
      watchButton.dataset.spxGalleryBound = '1';
      watchButton.addEventListener('click', function toggleGalleryWatch(event) {
        event.preventDefault();
        event.stopPropagation();
        toggleThreadWatch(info, state, watchButton);
      });
    }

    var favoriteButton = qs('[data-spx-gallery-action="favorite"]', tools);
    if (!favoriteButton) {
      favoriteButton = createEl('button', '', isThreadFavoriteSeen(info.id) ? '已收藏' : '收藏');
      favoriteButton.type = 'button';
      favoriteButton.dataset.spxGalleryAction = 'favorite';
      favoriteButton.title = '收藏到站内收藏夹';
      tools.appendChild(favoriteButton);
    }
    if (favoriteButton.dataset.spxGalleryBound !== '1') {
      favoriteButton.dataset.spxGalleryBound = '1';
      favoriteButton.addEventListener('click', function favoriteGalleryThread(event) {
        event.preventDefault();
        event.stopPropagation();
        runThreadFavoriteAction(info, settings, state, favoriteButton, '已收藏');
      });
    }

    syncThreadWatchState(info, state, watchButton);
    favoriteButton.disabled = isThreadFavoriteSeen(info.id);
    favoriteButton.textContent = favoriteButton.disabled ? '已收藏' : '收藏';

    var sectionIntro = qs('.section-intro', info.cell);
    var introTail = sectionIntro;
    if (introTail && introTail.nextElementSibling && introTail.nextElementSibling.classList && introTail.nextElementSibling.classList.contains('clear')) {
      introTail = introTail.nextElementSibling;
    }
    if (introTail && introTail.parentNode === info.cell && tools.previousElementSibling !== introTail) {
      introTail.insertAdjacentElement('afterend', tools);
    } else if (tools.parentNode !== info.cell) {
      info.cell.appendChild(tools);
    }
    return tools;
  }

  function clearForumGalleryResourceBadges(info) {
    if (!info || !info.cell) return;
    var badges = qs('.spx-resource-badges', info.cell);
    if (badges) badges.remove();
  }

  function watchForumGalleryStream(wall, settings, state) {
    if (!wall || wall.dataset.spxGalleryObserver === '1' || typeof MutationObserver === 'undefined') return;
    var stream = qs('.stream', wall);
    if (!stream) return;
    wall.dataset.spxGalleryObserver = '1';
    var timer = null;
    var observer = new MutationObserver(function refreshGalleryCards(mutations) {
      var hasCardMutation = (mutations || []).some(function hasAddedOrRemovedCard(mutation) {
        return mutation && (mutation.addedNodes.length || mutation.removedNodes.length);
      });
      if (!hasCardMutation) return;
      if (timer) window.clearTimeout(timer);
      timer = window.setTimeout(function runDeferredGalleryEnhance() {
        timer = null;
        enhanceForumGallery(settings, state);
      }, 120);
    });
    observer.observe(stream, { childList: true });
  }

  function scheduleForumGalleryToolRepair(wall, settings, state) {
    if (!wall || wall.dataset.spxGalleryRepairScheduled === '1' || typeof window === 'undefined' || typeof window.setTimeout !== 'function') return;
    wall.dataset.spxGalleryRepairScheduled = '1';
    [300, 900, 1800].forEach(function scheduleRepair(delayMs) {
      window.setTimeout(function repairGalleryTools() {
        enhanceForumGallery(settings, state);
      }, delayMs);
    });
  }

  function formatShortTime(timestamp) {
    var value = Number(timestamp) || 0;
    if (!value) return '';
    var date = new Date(value);
    if (isNaN(date.getTime())) return '';
    function pad(number) {
      return String(number).padStart(2, '0');
    }
    return pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' ' +
      pad(date.getHours()) + ':' + pad(date.getMinutes());
  }

  function createCenterActionElement(action) {
    if (!action) return null;
    var node = action.href ? createEl('a', '', action.text || '') : createEl('button', '', action.text || '');
    var actionText = String(action.text || '');
    if (/^(打开|复制|继续阅读|上次楼层|全选筛选)$/.test(actionText)) {
      node.classList.add('spx-action-primary');
    } else {
      node.classList.add('spx-action-secondary');
    }
    if (/删除|移除|清空|失效/.test(actionText)) {
      node.classList.add('spx-danger');
    }
    if (action.href) {
      node.href = action.href;
      node.target = action.target || '_blank';
      node.rel = action.rel || 'noreferrer';
    } else {
      node.type = 'button';
      if (action.dataset) {
        Object.keys(action.dataset).forEach(function setActionDataset(key) {
          node.dataset[key] = action.dataset[key];
        });
      }
    }
    return node;
  }

  function normalizeBatchConfirmItems(items) {
    return (items || []).map(function normalizeBatchConfirmItem(item) {
      if (!item) return null;
      if (typeof item === 'string') return { title: item, meta: '' };
      return {
        title: compactText(item.title || item.text || item.url || item.key || '未命名项目'),
        meta: compactText(item.meta || item.description || item.url || ''),
      };
    }).filter(Boolean);
  }

  function showBatchConfirmDialog(options) {
    var config = options || {};
    var fallbackText = config.fallbackText || config.message || config.title || '确认执行此操作?';
    if (typeof document === 'undefined' || !document.body) {
      return Promise.resolve(typeof window === 'undefined' || typeof window.confirm !== 'function' || window.confirm(fallbackText));
    }

    return new Promise(function openBatchConfirmDialog(resolve) {
      var existing = qs('#spx-batch-confirm-overlay');
      if (existing) existing.remove();

      var overlay = createEl('div', 'spx-batch-confirm-overlay');
      overlay.id = 'spx-batch-confirm-overlay';
      var dialog = createEl('section', 'spx-batch-confirm' + (config.danger === false ? '' : ' spx-batch-danger'));
      dialog.setAttribute('role', 'dialog');
      dialog.setAttribute('aria-modal', 'true');
      dialog.setAttribute('aria-label', config.title || '确认操作');

      var header = createEl('div', 'spx-batch-confirm-head');
      header.appendChild(createEl('strong', '', config.title || '确认操作'));
      header.appendChild(createEl('span', '', config.message || '这个操作会修改本地或站内数据。'));
      dialog.appendChild(header);

      var impacts = config.impacts || [];
      if (impacts.length) {
        var impactGrid = createEl('div', 'spx-batch-impact-grid');
        impacts.forEach(function appendBatchImpact(item) {
          var card = createEl('div', 'spx-batch-impact' + (item.className ? ' ' + item.className : ''));
          card.appendChild(createEl('b', '', String(item.value === undefined ? '' : item.value)));
          card.appendChild(createEl('span', '', item.label || '影响项'));
          if (item.note) card.appendChild(createEl('em', '', item.note));
          impactGrid.appendChild(card);
        });
        dialog.appendChild(impactGrid);
      }

      var items = normalizeBatchConfirmItems(config.items);
      if (items.length) {
        var list = createEl('div', 'spx-batch-list');
        items.slice(0, 8).forEach(function appendBatchItem(item) {
          var row = createEl('div', 'spx-batch-item');
          row.appendChild(createEl('span', '', item.title));
          if (item.meta) row.appendChild(createEl('em', '', item.meta));
          list.appendChild(row);
        });
        if (items.length > 8) {
          list.appendChild(createEl('div', 'spx-batch-more', '其余 ' + (items.length - 8) + ' 项将在确认后一起处理。'));
        }
        dialog.appendChild(list);
      }

      var footer = createEl('div', 'spx-batch-confirm-foot');
      var cancel = createEl('button', '', config.cancelText || '取消');
      var confirm = createEl('button', 'spx-primary', config.confirmText || '确认执行');
      cancel.type = 'button';
      confirm.type = 'button';
      cancel.dataset.action = 'cancel';
      confirm.dataset.action = 'confirm';
      footer.appendChild(cancel);
      footer.appendChild(confirm);
      dialog.appendChild(footer);
      overlay.appendChild(dialog);
      document.body.appendChild(overlay);

      function closeBatchConfirm(result) {
        document.removeEventListener('keydown', handleBatchConfirmKeydown, true);
        if (overlay.isConnected) overlay.remove();
        resolve(!!result);
      }

      function handleBatchConfirmKeydown(event) {
        if (event.key !== 'Escape') return;
        event.preventDefault();
        closeBatchConfirm(false);
      }

      overlay.addEventListener('click', function handleBatchConfirmClick(event) {
        var target = event.target;
        if (target === overlay) {
          closeBatchConfirm(false);
          return;
        }
        var action = target && target.dataset && target.dataset.action;
        if (action === 'cancel') closeBatchConfirm(false);
        if (action === 'confirm') closeBatchConfirm(true);
      });
      document.addEventListener('keydown', handleBatchConfirmKeydown, true);
      if (typeof confirm.focus === 'function') confirm.focus();
    });
  }

  function confirmBatchAction(options, fallbackText) {
    var config = Object.assign({ fallbackText: fallbackText }, options || {});
    return showBatchConfirmDialog(config).catch(function fallbackBatchConfirm() {
      var message = config.fallbackText || config.message || config.title || '确认执行此操作?';
      return typeof window === 'undefined' || typeof window.confirm !== 'function' || window.confirm(message);
    });
  }

  function createCenterTitleElement(options) {
    var config = options || {};
    var node = createEl(config.href ? 'a' : 'div', 'spx-watch-title');
    if (config.href) {
      node.href = config.href;
      node.target = config.target || '_blank';
      node.rel = config.rel || 'noreferrer';
    }
    if (config.title) node.title = config.title;
    if (config.badgeText) {
      var badge = createEl('span', 'spx-status-badge' + (config.badgeClass ? ' ' + config.badgeClass : ''), config.badgeText);
      node.appendChild(badge);
    }
    node.appendChild(document.createTextNode(config.text || ''));
    return node;
  }

  function ensureCenterPanelState(panel, defaults) {
    if (!panel) return {};
    if (!panel.spxState) panel.spxState = {};
    Object.keys(defaults || {}).forEach(function keepCenterDefault(key) {
      if (panel.spxState[key] === undefined) panel.spxState[key] = defaults[key];
    });
    return panel.spxState;
  }

  function isCenterFiltered(panelState) {
    return !!(
      panelState &&
      (
        panelState.query ||
        (panelState.filter && panelState.filter !== 'all') ||
        (panelState.tag && panelState.tag !== 'all') ||
        (panelState.provider && panelState.provider !== 'all')
      )
    );
  }

  function createCenterControls(panelState, options) {
    var config = options || {};
    var controls = createEl('div', 'spx-watch-controls');
    if (config.searchPlaceholder !== false) {
      var queryInput = createEl('input');
      queryInput.type = 'search';
      queryInput.placeholder = config.searchPlaceholder || '搜索';
      queryInput.value = String(panelState.query || '');
      queryInput.dataset.spxCenterQuery = '1';
      controls.appendChild(queryInput);
    }
    if ((config.filters || []).length) {
      var filterSelect = createEl('select');
      filterSelect.dataset.spxCenterFilter = '1';
      (config.filters || []).forEach(function appendFilterOption(item) {
        var option = createEl('option');
        option.value = item.value;
        option.textContent = item.label;
        option.selected = String(panelState.filter || 'all') === String(item.value);
        filterSelect.appendChild(option);
      });
      controls.appendChild(filterSelect);
    }
    if ((config.providers || []).length) {
      var providerSelect = createEl('select');
      providerSelect.dataset.spxCenterProvider = '1';
      var allProviderOption = createEl('option');
      allProviderOption.value = 'all';
      allProviderOption.textContent = config.providerAllLabel || '全部来源';
      providerSelect.appendChild(allProviderOption);
      (config.providers || []).forEach(function appendProviderOption(item) {
        var option = createEl('option');
        option.value = item.value;
        option.textContent = item.label;
        option.selected = String(panelState.provider || 'all') === String(item.value);
        providerSelect.appendChild(option);
      });
      controls.appendChild(providerSelect);
    }
    if ((config.views || []).length) {
      var viewSelect = createEl('select');
      viewSelect.dataset.spxCenterView = '1';
      (config.views || []).forEach(function appendViewOption(item) {
        var option = createEl('option');
        option.value = item.value;
        option.textContent = item.label;
        option.selected = String(panelState.view || 'list') === String(item.value);
        viewSelect.appendChild(option);
      });
      controls.appendChild(viewSelect);
    }
    if ((config.tags || []).length) {
      var tagSelect = createEl('select');
      tagSelect.dataset.spxCenterTag = '1';
      var allTagOption = createEl('option');
      allTagOption.value = 'all';
      allTagOption.textContent = '全部标签';
      tagSelect.appendChild(allTagOption);
      (config.tags || []).forEach(function appendTagOption(item) {
        var option = createEl('option');
        option.value = item.value;
        option.textContent = item.label;
        option.selected = String(panelState.tag || 'all') === String(item.value);
        tagSelect.appendChild(option);
      });
      controls.appendChild(tagSelect);
    }
    return controls.children.length ? controls : null;
  }

  function renderCenterPanel(panel, options) {
    if (!panel || !options) return;
    var scrollTop = panel.scrollTop || 0;
    var panelState = ensureCenterPanelState(panel, options.stateDefaults || { query: '', filter: 'all', tag: 'all', provider: 'all' });
    var entries = options.entries || [];
    var visibleEntries = typeof options.filterEntries === 'function'
      ? options.filterEntries(entries, panelState)
      : entries.slice();
    var displayEntries = typeof options.transformVisibleEntries === 'function'
      ? options.transformVisibleEntries(visibleEntries, entries, panelState)
      : visibleEntries;
    panel.textContent = '';
    var header = createEl('div', 'spx-watch-center-header');
    var title = createEl('div');
    title.appendChild(createEl('h3', '', options.title || ''));
    title.appendChild(createEl(
      'div',
      'spx-watch-summary',
      (typeof options.summary === 'function' ? options.summary(visibleEntries, entries, panelState) : options.summary) || ''
    ));
    header.appendChild(title);

    var headerActions = createEl('div', 'spx-watch-actions');
    (typeof options.headerActions === 'function'
      ? options.headerActions(visibleEntries, entries, panelState)
      : (options.headerActions || []))
      .forEach(function appendHeaderAction(action) {
        var actionNode = createCenterActionElement(action);
        if (actionNode) headerActions.appendChild(actionNode);
      });
    header.appendChild(headerActions);
    panel.appendChild(header);

    var controls = createCenterControls(panelState, options.controls);
    if (controls) panel.appendChild(controls);

    if (!entries.length) {
      panel.appendChild(createEl('div', 'spx-watch-empty', options.emptyText || '暂无内容'));
      panel.scrollTop = scrollTop;
      return;
    }

    if (!displayEntries.length) {
      panel.appendChild(createEl('div', 'spx-watch-empty', options.emptyFilteredText || '没有匹配结果。'));
      panel.scrollTop = scrollTop;
      return;
    }

    var list = createEl('div', 'spx-watch-list');
    displayEntries.forEach(function appendEntry(entry) {
      var item = createEl('div', 'spx-watch-item');
      var itemData = options.getItemData ? options.getItemData(entry) : null;
      if (itemData) {
        Object.keys(itemData).forEach(function setItemData(key) {
          item.dataset[key] = itemData[key];
        });
      }
      item.appendChild(options.createTitle(entry));
      item.appendChild(createEl('div', 'spx-watch-meta', options.createMeta(entry)));
      var actions = createEl('div', 'spx-watch-actions');
      (options.createActions(entry) || []).forEach(function appendAction(action) {
        var actionNode = createCenterActionElement(action);
        if (actionNode) actions.appendChild(actionNode);
      });
      item.appendChild(actions);
      list.appendChild(item);
    });
    panel.appendChild(list);
    panel.scrollTop = scrollTop;
  }

  function setCenterPanelHidden(panel, hidden) {
    if (!panel) return;
    panel.hidden = !!hidden;
    if (panel.hidden && panel.classList && panel.classList.contains('spx-workbench-inline-panel')) {
      var workbench = panel.closest && panel.closest('#spx-workbench');
      if (workbench) hideWorkbenchPanel();
    }
  }

  function refreshCenterPanel(panelId) {
    var panel = qs(panelId);
    if (!panel || panel.hidden || typeof panel.spxRender !== 'function') return;
    panel.spxRender();
  }

  function commitCenterQueryInput(panel, config, target) {
    if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
    var state = ensureCenterPanelState(panel, config.stateDefaults || { query: '', filter: 'all', tag: 'all', provider: 'all' });
    if (state.query === target.value) return;
    state.query = target.value;
    var selectionStart = typeof target.selectionStart === 'number' ? target.selectionStart : null;
    var selectionEnd = typeof target.selectionEnd === 'number' ? target.selectionEnd : null;
    panel.spxRender();
    var nextInput = qs('input[data-spx-center-query="1"]', panel);
    if (nextInput) {
      nextInput.focus();
      if (
        selectionStart !== null &&
        selectionEnd !== null &&
        typeof nextInput.setSelectionRange === 'function'
      ) {
        nextInput.setSelectionRange(selectionStart, selectionEnd);
      }
    }
  }

  function createCenterPanel(options) {
    var config = options || {};
    var panel = qs('#' + config.id);
    if (panel) return panel;

    panel = createEl('div', 'spx-watch-center');
    panel.id = config.id;
    panel.hidden = true;
    document.body.appendChild(panel);

    panel.addEventListener('compositionstart', function handleCenterCompositionStart(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      panel.spxCenterQueryComposing = true;
    });

    panel.addEventListener('compositionend', function handleCenterCompositionEnd(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      panel.spxCenterQueryComposing = false;
      commitCenterQueryInput(panel, config, target);
    });

    panel.addEventListener('input', function handleCenterInput(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      if (event.isComposing || panel.spxCenterQueryComposing) return;
      commitCenterQueryInput(panel, config, target);
    });

    panel.addEventListener('change', function handleCenterChange(event) {
      var target = event.target;
      if (!target || !target.dataset) return;
      var state = ensureCenterPanelState(panel, config.stateDefaults || { query: '', filter: 'all', tag: 'all', provider: 'all' });
      if (target.dataset.spxCenterFilter === '1') {
        if (state.filter === target.value) return;
        state.filter = target.value;
      } else if (target.dataset.spxCenterTag === '1') {
        if (state.tag === target.value) return;
        state.tag = target.value;
      } else if (target.dataset.spxCenterProvider === '1') {
        if (state.provider === target.value) return;
        state.provider = target.value;
      } else if (target.dataset.spxCenterView === '1') {
        if (state.view === target.value) return;
        state.view = target.value;
      } else {
        return;
      }
      panel.spxRender();
    });

    panel.addEventListener('click', function handleCenterClick(event) {
      var target = event.target;
      var action = target && target.dataset && target.dataset.action;
      if (!action) return;
      config.onAction(action, target, panel);
    });

    panel.spxRender = function renderPanel() {
      config.render(panel);
    };
    panel.spxRender();
    return panel;
  }

  function getWorkbenchState(state) {
    return {
      read: state && state.read ? state.read : loadMap(READ_KEY),
      watch: state && state.watch ? state.watch : loadMap(WATCH_KEY),
      progress: state && state.progress ? state.progress : loadReadProgress(),
      threadUpdates: state && state.threadUpdates ? state.threadUpdates : loadThreadUpdates(),
      resources: state && state.resources ? state.resources : loadResourceLibrary(),
    };
  }

  function getWorkbenchHost() {
    return qs('.spx-module-body') || getModuleNavigationHost() || document.body;
  }

  function isWorkbenchInlinePanelId(panelId) {
    return [
      'spx-content-center',
      'spx-watch-center',
      'spx-history-center',
      'spx-auto-buy-center',
      'spx-resource-center',
    ].indexOf(panelId) !== -1;
  }

  function getWorkbenchCenterConfigs() {
    return getToolbarCenterConfigs().filter(function keepWorkbenchCenter(config) {
      return config && isWorkbenchInlinePanelId(config.panelId);
    });
  }

  function createWorkbenchTab(config, active) {
    var tab = createEl('button', 'spx-workbench-tab' + (active ? ' spx-active' : ''), config.label || '我的内容');
    tab.type = 'button';
    tab.dataset.spxWorkbenchPanel = config.panelId || '';
    tab.setAttribute('aria-selected', active ? 'true' : 'false');
    if (config.title) tab.title = config.title;
    return tab;
  }

  function renderWorkbenchTabs(shell, activePanelId) {
    var tabs = qs('.spx-workbench-tabs', shell);
    if (!tabs) return;
    tabs.textContent = '';
    getWorkbenchCenterConfigs().forEach(function appendWorkbenchTab(config) {
      tabs.appendChild(createWorkbenchTab(config, config.panelId === activePanelId));
    });
  }

  function ensureWorkbenchPanel(settings, state) {
    var host = getWorkbenchHost();
    if (!host) return null;
    var shell = qs('#spx-workbench', host) || qs('#spx-workbench');
    if (!shell) {
      shell = createEl('section', 'spx-workbench');
      shell.id = 'spx-workbench';
      shell.hidden = true;
      shell.setAttribute('aria-label', '我的工作台');
      var head = createEl('div', 'spx-workbench-head');
      var title = createEl('div', 'spx-workbench-title');
      title.appendChild(createEl('strong', '', '我的工作台'));
      title.appendChild(createEl('span', '', '从左侧导航进入,工具栏保持原来的页面动作和设置入口。'));
      var close = createEl('button', 'spx-workbench-close', '关闭');
      close.type = 'button';
      close.dataset.action = 'close-workbench';
      head.appendChild(title);
      head.appendChild(close);
      shell.appendChild(head);
      shell.appendChild(createEl('div', 'spx-workbench-tabs'));
      shell.appendChild(createEl('div', 'spx-workbench-stage'));
      shell.addEventListener('click', function handleWorkbenchClick(event) {
        var closeTarget = event.target && event.target.closest && event.target.closest('[data-action="close-workbench"]');
        if (closeTarget) {
          hideWorkbenchPanel();
          return;
        }
        var tab = event.target && event.target.closest && event.target.closest('[data-spx-workbench-panel]');
        if (!tab || !tab.dataset.spxWorkbenchPanel) return;
        if (!isWorkbenchInlinePanelId(tab.dataset.spxWorkbenchPanel)) return;
        openWorkbenchPanel(tab.dataset.spxWorkbenchPanel, settings, state, { scroll: false });
      });
    }
    if (shell.parentNode !== host) host.insertBefore(shell, host.firstChild);
    return shell;
  }

  function clearWorkbenchStage(stage, keepPanelId) {
    if (!stage) return;
    Array.prototype.slice.call(stage.children).forEach(function removePreviousWorkbenchPanel(child) {
      if (child.id === keepPanelId) return;
      child.classList.remove('spx-workbench-inline-panel');
      child.remove();
    });
  }

  function openCenterPanelFallback(config, settings, state) {
    if (!config || !config.createPanel) return false;
    var centerPanel = config.createPanel(settings, state);
    if (centerPanel.spxRender) centerPanel.spxRender();
    setCenterPanelHidden(centerPanel, false);
    return true;
  }

  function setWorkbenchBodyMode(shell, active) {
    var body = shell && shell.closest && shell.closest('.spx-module-body');
    if (body) body.classList.toggle('spx-workbench-mode', !!active);
    if (document.documentElement) document.documentElement.classList.toggle('spx-workbench-open', !!active);
  }

  function hideWorkbenchPanel() {
    var shell = qs('#spx-workbench');
    if (shell) {
      shell.hidden = true;
      setWorkbenchBodyMode(shell, false);
    }
  }

  function syncWorkbenchNavigationActive(panelId) {
    if (!panelId) return;
    var nav = qs('#spx-module-nav');
    if (!nav) return;
    var target = qsa('[data-spx-workbench-nav-panel]', nav).filter(function matchWorkbenchNavigation(item) {
      return item.dataset && item.dataset.spxWorkbenchNavPanel === panelId;
    })[0];
    if (target) setModuleNavActive(nav, target);
  }

  function openWorkbenchPanel(panelId, settings, state, options) {
    var config = getCommandCenterPanelConfig(panelId);
    var workbenchState = getWorkbenchState(state);
    if (!config || !config.createPanel) return false;
    if (!isWorkbenchInlinePanelId(panelId)) {
      hideWorkbenchPanel();
      return openCenterPanelFallback(config, settings, workbenchState);
    }
    var shell = ensureWorkbenchPanel(settings || loadSettings(), workbenchState);
    var stage = shell && qs('.spx-workbench-stage', shell);
    if (!shell || !stage) return openCenterPanelFallback(config, settings, workbenchState);
    clearWorkbenchStage(stage, panelId);
    var panel = config.createPanel(settings || loadSettings(), workbenchState);
    if (panel.spxRender) panel.spxRender();
    panel.hidden = false;
    panel.classList.add('spx-workbench-inline-panel');
    stage.appendChild(panel);
    shell.hidden = false;
    setWorkbenchBodyMode(shell, true);
    shell.dataset.spxWorkbenchPanel = panelId;
    renderWorkbenchTabs(shell, panelId);
    syncWorkbenchNavigationActive(panelId);
    if (!options || options.scroll !== false) shell.scrollIntoView({ behavior: 'smooth', block: 'start' });
    return true;
  }

  function openProgressEntry(state, id, url, mode) {
    if (!id) return;
    requestReadProgressRestore(id, mode);
    if (detectPageType(location.href) === 'read' && parseThreadId(location.href) === id) {
      restoreReadProgress(state, id, mode);
      return;
    }
    if (url) location.href = url;
  }

  function renderWatchCenter(panel, state) {
    state.watch = state.watch || {};
    state.progress = state.progress || {};
    var entries = getWatchCenterEntries(state.watch, state.progress);
    renderCenterPanel(panel, {
      title: '稍后看中心',
      summary: function summary(visibleEntries, entries, panelState) {
        return (
          (isCenterFiltered(panelState) ? (visibleEntries.length + ' / ') : '') +
          entries.length +
          ' 条已保存主题'
        );
      },
      headerActions: function headerActions(visibleEntries, entries, panelState) {
        var actions = [];
        if (visibleEntries.length && isCenterFiltered(panelState)) {
          actions.push({ text: '移除筛选', dataset: { action: 'remove-visible-watch' } });
        }
        if (entries.length) actions.push({ text: '清空', dataset: { action: 'clear-watch' } });
        actions.push({ text: '关闭', dataset: { action: 'close-watch' } });
        return actions;
      },
      controls: {
        searchPlaceholder: '搜索标题或楼层',
        filters: [
          { value: 'all', label: '全部' },
          { value: 'todo', label: '未读完' },
          { value: 'progress', label: '有进度' },
          { value: 'done', label: '已读完' },
        ],
        tags: getCenterTagOptions(entries),
      },
      emptyText: '还没有保存的主题。可在帖子列表点击“稍后”加入。',
      emptyFilteredText: '没有匹配的已保存主题。',
      entries: entries,
      filterEntries: filterWatchCenterEntries,
      getItemData: function getItemData(entry) {
        return { id: entry.id };
      },
      createTitle: function createTitle(entry) {
        return createCenterTitleElement({
          text: entry.title,
          href: entry.url || entry.progressUrl || '#',
        });
      },
      createMeta: function createMeta(entry) {
        var metaParts = [];
        if (entry.savedAt) metaParts.push('保存 ' + formatShortTime(entry.savedAt));
        if (entry.progressText) metaParts.push('进度 ' + entry.progressText);
        if (entry.nextFloorLabel) metaParts.push('续读 ' + entry.nextFloorLabel);
        if (entry.floorLabel && entry.floorLabel !== entry.nextFloorLabel) metaParts.push('上次 ' + entry.floorLabel);
        if (entry.tagText) metaParts.push('标签 ' + entry.tagText);
        return metaParts.join(' · ') || '暂无进度';
      },
      createActions: function createActions(entry) {
        var actions = [];
        if (entry.progressAt && entry.progressUrl) {
          actions.push({ text: '继续阅读', dataset: { action: 'continue-watch', id: entry.id } });
          if (entry.floorLabel) actions.push({ text: '上次楼层', dataset: { action: 'continue-last-watch', id: entry.id } });
        }
        actions.push({ text: '标签', dataset: { action: 'tag-watch', id: entry.id } });
        actions.push({ text: '打开', href: entry.url || entry.progressUrl || '#' });
        actions.push({ text: '移除', dataset: { action: 'remove-watch', id: entry.id } });
        return actions;
      },
    });
  }

  function refreshWatchCenter() {
    refreshCenterPanel('#spx-watch-center');
  }

  function createWatchCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-watch-center',
      render: function render(panel) {
        renderWatchCenter(panel, state);
      },
      onAction: function onAction(action, target, panel) {
        if (action === 'close-watch') {
          setCenterPanelHidden(panel, true);
          return;
        }
        if (action === 'remove-visible-watch') {
          var visibleEntries = filterWatchCenterEntries(
            getWatchCenterEntries(state.watch, state.progress),
            ensureCenterPanelState(panel, { query: '', filter: 'all', tag: 'all' })
          );
          if (!visibleEntries.length) return;
          confirmBatchAction({
            title: '移除稍后看筛选结果',
            message: '将从本地稍后看移除当前筛选结果,阅读进度不会删除。',
            confirmText: '移除筛选',
            impacts: [{ label: '稍后看主题', value: visibleEntries.length, note: '仅移除保存状态' }],
            items: visibleEntries.map(function mapWatchConfirmItem(entry) {
              return { title: entry.title, meta: [entry.progressText, entry.nextFloorLabel].filter(Boolean).join(' · ') };
            }),
          }, '移除当前筛选结果中的稍后看主题?').then(function removeVisibleWatchAfterConfirm(ok) {
            if (!ok) return;
            visibleEntries.forEach(function removeVisibleEntry(entry) {
              delete state.watch[entry.id];
            });
            saveMap(WATCH_KEY, state.watch);
            qsa('.spx-watch-badge').forEach(function removeMatchingBadge(badge) {
              if (!state.watch[badge.dataset.spxWatchId]) badge.remove();
            });
            renderWatchCenter(panel, state);
          });
          return;
        }
        if (action === 'clear-watch') {
          var allWatchEntries = getWatchCenterEntries(state.watch, state.progress);
          confirmBatchAction({
            title: '清空稍后看',
            message: '将清空全部本地稍后看主题,阅读进度仍会保留在最近浏览。',
            confirmText: '清空稍后看',
            impacts: [{ label: '稍后看主题', value: allWatchEntries.length, note: '不可自动恢复' }],
            items: allWatchEntries.map(function mapClearWatchConfirmItem(entry) {
              return { title: entry.title, meta: entry.url };
            }),
          }, '清空全部稍后看主题?').then(function clearWatchAfterConfirm(ok) {
            if (!ok) return;
            state.watch = {};
            saveMap(WATCH_KEY, state.watch);
            qsa('.spx-watch-badge').forEach(function removeBadge(badge) {
              badge.remove();
            });
            renderWatchCenter(panel, state);
          });
          return;
        }
        var id = target.dataset.id;
        if (!id) return;
        if (action === 'tag-watch') {
          var currentTags = parseTagList(state.watch[id] && state.watch[id].tags);
          var nextTags = typeof window.prompt === 'function'
            ? window.prompt('编辑标签,多个标签用逗号或换行分隔', currentTags.join(','))
            : null;
          if (nextTags === null) return;
          state.watch[id] = state.watch[id] || {};
          state.watch[id].tags = parseTagList(nextTags);
          saveMap(WATCH_KEY, state.watch);
          renderWatchCenter(panel, state);
          return;
        }
        if (action === 'remove-watch') {
          delete state.watch[id];
          saveMap(WATCH_KEY, state.watch);
          qsa('.spx-watch-badge').forEach(function removeMatchingBadge(badge) {
            if (badge.dataset.spxWatchId === id) badge.remove();
          });
          renderWatchCenter(panel, state);
          return;
        }
        if (action === 'continue-watch') {
          var entry = getWatchCenterEntries(state.watch, state.progress).filter(function matchEntry(item) {
            return item.id === id;
          })[0];
          if (!entry) return;
          openProgressEntry(state, id, entry.progressUrl || entry.url, 'next');
          return;
        }
        if (action === 'continue-last-watch') {
          var lastEntry = getWatchCenterEntries(state.watch, state.progress).filter(function matchLastEntry(item) {
            return item.id === id;
          })[0];
          if (!lastEntry) return;
          openProgressEntry(state, id, lastEntry.progressUrl || lastEntry.url, 'last');
        }
      },
    });
  }

  function renderHistoryCenter(panel, state) {
    state.progress = state.progress || {};
    var entries = getHistoryCenterEntries(state.progress);
    renderCenterPanel(panel, {
      title: '最近浏览',
      summary: function summary(visibleEntries, entries, panelState) {
        return (
          (isCenterFiltered(panelState) ? (visibleEntries.length + ' / ') : '') +
          entries.length +
          ' 条阅读记录'
        );
      },
      headerActions: function headerActions(visibleEntries, entries, panelState) {
        var actions = [];
        if (visibleEntries.length && isCenterFiltered(panelState)) {
          actions.push({ text: '移除筛选', dataset: { action: 'remove-visible-history' } });
        }
        if (entries.length) actions.push({ text: '清空', dataset: { action: 'clear-history' } });
        actions.push({ text: '关闭', dataset: { action: 'close-history' } });
        return actions;
      },
      controls: {
        searchPlaceholder: '搜索标题或楼层',
        filters: [
          { value: 'all', label: '全部' },
          { value: 'todo', label: '未读完' },
          { value: 'done', label: '已读完' },
        ],
        tags: getCenterTagOptions(entries),
      },
      emptyText: '还没有阅读记录。打开帖子后会自动记录进度。',
      emptyFilteredText: '没有匹配的阅读记录。',
      entries: entries,
      filterEntries: filterHistoryCenterEntries,
      getItemData: function getItemData(entry) {
        return { id: entry.id };
      },
      createTitle: function createTitle(entry) {
        return createCenterTitleElement({
          text: entry.title,
          href: entry.url || '#',
        });
      },
      createMeta: function createMeta(entry) {
        var metaParts = [];
        if (entry.progressAt) metaParts.push('浏览 ' + formatShortTime(entry.progressAt));
        if (entry.progressText) metaParts.push('进度 ' + entry.progressText);
        if (entry.nextFloorLabel) metaParts.push('续读 ' + entry.nextFloorLabel);
        if (entry.floorLabel && entry.floorLabel !== entry.nextFloorLabel) metaParts.push('上次 ' + entry.floorLabel);
        if (entry.tagText) metaParts.push('标签 ' + entry.tagText);
        return metaParts.join(' · ') || '暂无进度';
      },
      createActions: function createActions(entry) {
        var actions = [
          { text: '继续阅读', dataset: { action: 'continue-history', id: entry.id } },
          { text: '标签', dataset: { action: 'tag-history', id: entry.id } },
          { text: '打开', href: entry.url || '#' },
          { text: '移除', dataset: { action: 'remove-history', id: entry.id } },
        ];
        if (entry.floorLabel) actions.splice(1, 0, { text: '上次楼层', dataset: { action: 'continue-last-history', id: entry.id } });
        return actions;
      },
    });
  }

  function refreshHistoryCenter() {
    refreshCenterPanel('#spx-history-center');
  }

  function createHistoryCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-history-center',
      render: function render(panel) {
        renderHistoryCenter(panel, state);
      },
      onAction: function onAction(action, target, panel) {
        if (action === 'close-history') {
          setCenterPanelHidden(panel, true);
          return;
        }
        if (action === 'remove-visible-history') {
          var visibleEntries = filterHistoryCenterEntries(
            getHistoryCenterEntries(state.progress),
            ensureCenterPanelState(panel, { query: '', filter: 'all', tag: 'all' })
          );
          if (!visibleEntries.length) return;
          confirmBatchAction({
            title: '移除阅读记录筛选结果',
            message: '将移除当前筛选结果中的阅读进度记录,稍后看保存状态不会删除。',
            confirmText: '移除筛选',
            impacts: [{ label: '阅读记录', value: visibleEntries.length, note: '影响续读位置' }],
            items: visibleEntries.map(function mapHistoryConfirmItem(entry) {
              return { title: entry.title, meta: [entry.progressText, entry.nextFloorLabel].filter(Boolean).join(' · ') };
            }),
          }, '移除当前筛选结果中的阅读记录?').then(function removeVisibleHistoryAfterConfirm(ok) {
            if (!ok) return;
            visibleEntries.forEach(function removeVisibleHistory(entry) {
              delete state.progress[entry.id];
            });
            saveReadProgress(state.progress);
            refreshWatchCenter();
            renderHistoryCenter(panel, state);
          });
          return;
        }
        if (action === 'clear-history') {
          var allHistoryEntries = getHistoryCenterEntries(state.progress);
          confirmBatchAction({
            title: '清空阅读记录',
            message: '将清空全部阅读进度和最近浏览记录。',
            confirmText: '清空阅读记录',
            impacts: [{ label: '阅读记录', value: allHistoryEntries.length, note: '不可自动恢复' }],
            items: allHistoryEntries.map(function mapClearHistoryConfirmItem(entry) {
              return { title: entry.title, meta: entry.progressText || entry.url };
            }),
          }, '清空全部阅读记录?').then(function clearHistoryAfterConfirm(ok) {
            if (!ok) return;
            state.progress = {};
            saveReadProgress(state.progress);
            clearReadProgressRestoreRequest(parseThreadId(location.href));
            refreshWatchCenter();
            renderHistoryCenter(panel, state);
          });
          return;
        }
        var id = target.dataset.id;
        if (!id) return;
        if (action === 'tag-history') {
          var currentTags = parseTagList(state.progress[id] && state.progress[id].tags);
          var nextTags = typeof window.prompt === 'function'
            ? window.prompt('编辑标签,多个标签用逗号或换行分隔', currentTags.join(','))
            : null;
          if (nextTags === null) return;
          state.progress[id] = state.progress[id] || {};
          state.progress[id].tags = parseTagList(nextTags);
          saveReadProgress(state.progress);
          refreshWatchCenter();
          renderHistoryCenter(panel, state);
          return;
        }
        if (action === 'remove-history') {
          delete state.progress[id];
          saveReadProgress(state.progress);
          refreshWatchCenter();
          renderHistoryCenter(panel, state);
          return;
        }
        if (action === 'continue-history') {
          var entry = getHistoryCenterEntries(state.progress).filter(function matchEntry(item) {
            return item.id === id;
          })[0];
          if (!entry) return;
          openProgressEntry(state, id, entry.url, 'next');
          return;
        }
        if (action === 'continue-last-history') {
          var lastEntry = getHistoryCenterEntries(state.progress).filter(function matchLastEntry(item) {
            return item.id === id;
          })[0];
          if (!lastEntry) return;
          openProgressEntry(state, id, lastEntry.url, 'last');
        }
      },
    });
  }

  function renderAutoBuyCenter(panel) {
    renderCenterPanel(panel, {
      title: '自动购买记录',
      summary: function summary(visibleEntries, entries, panelState) {
        return (
          ((panelState.query || panelState.filter !== 'all') ? (visibleEntries.length + ' / ') : '') +
          entries.length +
          ' 条执行记录'
        );
      },
      headerActions: function headerActions(visibleEntries, entries, panelState) {
        var actions = [];
        if (visibleEntries.length && (panelState.query || panelState.filter !== 'all')) {
          actions.push({ text: '删除筛选', dataset: { action: 'remove-visible-auto-buy-records' } });
        }
        if (entries.length) actions.push({ text: '清空', dataset: { action: 'clear-auto-buy-records' } });
        actions.push({ text: '关闭', dataset: { action: 'close-auto-buy-center' } });
        return actions;
      },
      controls: {
        searchPlaceholder: '搜索帖子或结果',
        filters: [
          { value: 'all', label: '全部' },
          { value: 'checking', label: '检查中' },
          { value: 'skipped', label: '已跳过' },
          { value: 'buying', label: '购买中' },
          { value: 'done', label: '已完成' },
          { value: 'failed', label: '失败' },
        ],
      },
      emptyText: '还没有自动购买记录。开启自动购买并遇到付费帖后会记录执行状态。',
      emptyFilteredText: '没有匹配的自动购买记录。',
      entries: getAutoBuyCenterEntries(loadAutoBuyAttempts()),
      filterEntries: filterAutoBuyCenterEntries,
      getItemData: function getItemData(entry) {
        return { key: entry.key };
      },
      createTitle: function createTitle(entry) {
        return createCenterTitleElement({
          text: entry.key,
          href: entry.url,
          badgeText: entry.statusLabel,
          badgeClass: 'spx-status-' + entry.status,
        });
      },
      createMeta: function createMeta(entry) {
        var metaParts = [];
        if (entry.updatedAt) metaParts.push('记录 ' + formatShortTime(entry.updatedAt));
        if (entry.price !== null && isFinite(entry.price)) metaParts.push('价格 ' + entry.price + ' SP');
        if (entry.balance !== null && isFinite(entry.balance)) metaParts.push('余额 ' + entry.balance + ' SP');
        if (entry.message) metaParts.push(entry.message);
        return metaParts.join(' · ') || '暂无详情';
      },
      createActions: function createActions(entry) {
        var actions = [];
        if (entry.url) actions.push({ text: '打开', href: entry.url });
        actions.push({ text: '删除记录', dataset: { action: 'remove-auto-buy-record', key: entry.key } });
        return actions;
      },
    });
  }

  function refreshAutoBuyCenter() {
    refreshCenterPanel('#spx-auto-buy-center');
  }

  function createAutoBuyCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-auto-buy-center',
      render: function render(panel) {
        renderAutoBuyCenter(panel);
      },
      onAction: function onAction(action, target, panel) {
        if (action === 'close-auto-buy-center') {
          setCenterPanelHidden(panel, true);
          return;
        }
        if (action === 'remove-visible-auto-buy-records') {
          var attempts = loadAutoBuyAttempts();
          var visibleEntries = filterAutoBuyCenterEntries(
            getAutoBuyCenterEntries(attempts),
            ensureCenterPanelState(panel, { query: '', filter: 'all' })
          );
          if (!visibleEntries.length) return;
          confirmBatchAction({
            title: '删除自动购买筛选记录',
            message: '将删除当前筛选结果中的自动购买执行记录。',
            confirmText: '删除筛选',
            impacts: [{ label: '购买记录', value: visibleEntries.length, note: '可能影响重复购买拦截' }],
            items: visibleEntries.map(function mapAutoBuyConfirmItem(entry) {
              return { title: entry.key, meta: [entry.statusLabel, entry.message].filter(Boolean).join(' · ') };
            }),
          }, '删除当前筛选结果中的自动购买记录?').then(function removeVisibleAutoBuyAfterConfirm(ok) {
            if (!ok) return;
            visibleEntries.forEach(function removeVisibleAutoBuyEntry(entry) {
              delete attempts[entry.key];
            });
            saveAutoBuyAttempts(attempts);
            renderAutoBuyCenter(panel);
          });
          return;
        }
        if (action === 'clear-auto-buy-records') {
          var allAutoBuyEntries = getAutoBuyCenterEntries(loadAutoBuyAttempts());
          confirmBatchAction({
            title: '清空自动购买记录',
            message: '将清空全部自动购买执行记录。',
            confirmText: '清空记录',
            impacts: [{ label: '购买记录', value: allAutoBuyEntries.length, note: '不可自动恢复' }],
            items: allAutoBuyEntries.map(function mapClearAutoBuyConfirmItem(entry) {
              return { title: entry.key, meta: entry.statusLabel };
            }),
          }, '清空全部自动购买记录?').then(function clearAutoBuyAfterConfirm(ok) {
            if (!ok) return;
            saveAutoBuyAttempts({});
            delete document.documentElement.dataset.spxAutoBuyStatus;
            var status = qs('#spx-auto-buy-status');
            if (status) status.remove();
            renderAutoBuyCenter(panel);
          });
          return;
        }
        if (action === 'remove-auto-buy-record') {
          var key = target.dataset.key;
          if (!key) return;
          var attempts = loadAutoBuyAttempts();
          delete attempts[key];
          saveAutoBuyAttempts(attempts);
          renderAutoBuyCenter(panel);
        }
      },
    });
  }

  function syncTaskClaimRecordsFromDocument(root, sourceUrl) {
    if (typeof document === 'undefined') return 0;
    var scope = root || document;
    var rows = qsa('tr,.tr3,.t,.t3,.spx-task-main-block,.spx-task-main-stack', scope).map(function mapTaskClaimRow(row) {
      return String(row.textContent || '').replace(/\s+/g, ' ').trim();
    }).filter(function keepTaskClaimRow(text) {
      return /任务|奖励|完成时间|日常|周常/.test(text);
    });
    var options = {
      source: 'site-completed',
      sourceUrl: sourceUrl || (typeof location !== 'undefined' ? location.href : ''),
      recordedAt: Date.now(),
    };
    var records = parseTaskClaimRecordsFromRows(rows, options);
    if (!records.length) records = parseTaskClaimRecordsFromText(scope.textContent || '', options);
    if (!records.length) return 0;
    saveTaskClaimRecords(mergeTaskClaimRecords(loadTaskClaimRecords(), records));
    showDailyTaskDoneNavStatus(loadTaskClaimRecords(), Date.now());
    refreshTaskClaimInlineSection();
    return records.length;
  }

  function getTaskCompletedPageUrl() {
    var origin = (typeof location !== 'undefined' && location.origin) ? location.origin : 'https://south-plus.org';
    return origin + '/plugin.php?H_name-tasks-actions-endtasks.html.html';
  }

  function getTaskHomePageUrl() {
    var origin = (typeof location !== 'undefined' && location.origin) ? location.origin : 'https://south-plus.org';
    return origin + '/plugin.php?H_name-tasks.html';
  }

  function getTaskInProgressPageUrl() {
    var origin = (typeof location !== 'undefined' && location.origin) ? location.origin : 'https://south-plus.org';
    return origin + '/plugin.php?H_name-tasks-actions-newtasks.html.html';
  }

  function shouldSyncTaskClaimRecordsFromUrl(url) {
    return shouldUseTaskPage(url) && /(?:actions[-=]endtasks|endtasks)/i.test(String(url || ''));
  }

  function syncTaskClaimRecordsFromCurrentPage() {
    if (typeof document === 'undefined' || !shouldSyncTaskClaimRecordsFromUrl(location.href)) return 0;
    if (document.documentElement.dataset.spxTaskClaimsSynced === '1') return 0;
    document.documentElement.dataset.spxTaskClaimsSynced = '1';
    return syncTaskClaimRecordsFromDocument(document, location.href);
  }

  function getTaskAutoClaimContextText(control) {
    if (!control || !control.closest) return '';
    var row = control.closest('tr,.tr3,.f_one,.spx-task-main-block,.spx-task-main-stack,.t,.t3,.t5');
    return String(row && row.textContent || '').replace(/\s+/g, ' ').trim();
  }

  function getTaskAutoClaimControlText(control) {
    return String(
      control && (control.textContent || control.value || control.title || control.getAttribute && control.getAttribute('aria-label')) || ''
    ).replace(/\s+/g, ' ').trim();
  }

  function buildTaskAutoClaimFormRequest(control, pageUrl) {
    if (!control || !control.form) return null;
    var form = control.form;
    var method = String(form.method || 'get').toUpperCase();
    var url = '';
    try {
      url = new URL(form.action || pageUrl || location.href, pageUrl || location.href).href;
    } catch (error) {
      url = pageUrl || location.href;
    }
    var formData = new FormData(form);
    if (control.name && !formData.has(control.name)) formData.append(control.name, control.value || '');
    if (method === 'GET') {
      var queryUrl = new URL(url, pageUrl || location.href);
      formData.forEach(function appendTaskAutoClaimQuery(value, key) {
        queryUrl.searchParams.append(key, value);
      });
      return { url: queryUrl.href, options: { method: 'GET', credentials: 'include', cache: 'no-store' } };
    }
    return { url: url, options: { method: method, credentials: 'include', cache: 'no-store', body: formData } };
  }

  function createTaskAutoClaimTarget(control, pageUrl) {
    var label = getTaskAutoClaimControlText(control);
    var rawTarget = control && (control.getAttribute('href') || control.getAttribute('onclick') || '') || '';
    var contextText = getTaskAutoClaimContextText(control);
    var preliminaryActionType = getTaskAutoClaimActionType(label, rawTarget, contextText);
    var href = extractTaskAutoClaimUrl(rawTarget, pageUrl, preliminaryActionType, contextText);
    var formRequest = !href ? buildTaskAutoClaimFormRequest(control, pageUrl) : null;
    if (!href && formRequest) href = formRequest.url;
    if (!href || !isTaskAutoClaimCandidate(label, href, contextText)) return null;
    var taskName = parseTaskClaimTaskName(contextText) || parseTaskClaimTaskName(label);
    var taskKey = getTaskClaimTaskKey(taskName);
    if (taskKey !== 'daily' && taskKey !== 'weekly') return null;
    var actionType = getTaskAutoClaimActionType(label, href, contextText) || preliminaryActionType || 'claim';
    return {
      key: taskKey + '|' + actionType + '|' + href,
      taskKey: taskKey,
      taskName: getTaskClaimTaskLabel(taskKey, taskName),
      actionType: actionType,
      label: label || '领取',
      url: href,
      options: formRequest ? formRequest.options : { method: 'GET', credentials: 'include', cache: 'no-store' },
    };
  }

  function getTaskAutoClaimTargets(root, pageUrl) {
    var scope = root || document;
    var targets = [];
    var seen = {};
    qsa('a[href],a[onclick],button,input[type="button"],input[type="submit"],input[type="image"],[role="button"][onclick]', scope).forEach(function collectTaskAutoClaimControl(control) {
      if (
        control.closest &&
        control.closest('.spx-task-side-stack,.spx-task-side-block,.spx-module-nav,#spx-toolbar,#spx-settings,#spx-toolbox')
      ) return;
      var target = createTaskAutoClaimTarget(control, pageUrl);
      if (!target || seen[target.key]) return;
      seen[target.key] = true;
      targets.push(target);
    });
    return targets.slice(0, 4);
  }

  function getTaskAutoClaimTargetsFromHtml(html, pageUrl) {
    var parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null;
    if (!parser) return [];
    var doc = parser.parseFromString(String(html || ''), 'text/html');
    return getTaskAutoClaimTargets(doc, pageUrl);
  }

  function getTaskAutoClaimStatusBox(host) {
    if (!host) return null;
    var box = qs('#spx-task-auto-claim-status', host) || qs('#spx-task-auto-claim-status');
    if (!box) {
      box = createEl('div', 'spx-task-auto-claim-status');
      box.id = 'spx-task-auto-claim-status';
      host.insertBefore(box, host.firstChild || null);
    }
    return box;
  }

  function setTaskAutoClaimStatus(host, text, isError) {
    var box = getTaskAutoClaimStatusBox(host);
    if (!box) return;
    box.textContent = text || '';
    box.classList.toggle('spx-error', !!isError);
  }

  function showFavoriteNavStatusNote(className, text, detail) {
    if (typeof document === 'undefined') return null;
    var wrapper = qs('#spx-favorite-nav');
    if (!wrapper) {
      pendingFavoriteNavStatusNotes = pendingFavoriteNavStatusNotes.filter(function keepOtherPendingNote(note) {
        return note && note.className !== className;
      });
      pendingFavoriteNavStatusNotes.push({ className: className, text: text, detail: detail });
      return null;
    }
    var noteClass = String(className || 'spx-favorite-nav-note');
    var note = qs('.' + noteClass, wrapper);
    if (!note) {
      note = createEl('span', 'spx-favorite-nav-note ' + noteClass);
      note.setAttribute('role', 'status');
      note.setAttribute('aria-live', 'polite');
      wrapper.appendChild(note);
    }
    note.hidden = false;
    note.textContent = text || '';
    note.title = detail || note.textContent;
    return note;
  }

  function flushFavoriteNavStatusNotes() {
    if (!pendingFavoriteNavStatusNotes.length || typeof document === 'undefined') return;
    var wrapper = qs('#spx-favorite-nav');
    if (!wrapper) return;
    var notes = pendingFavoriteNavStatusNotes.slice();
    pendingFavoriteNavStatusNotes = [];
    notes.forEach(function showPendingFavoriteNavStatus(note) {
      showFavoriteNavStatusNote(note.className, note.text, note.detail);
    });
  }

  function showTaskAutoClaimNavSuccess(text, detail) {
    return showFavoriteNavStatusNote('spx-task-auto-claim-nav-note', text || '任务执行成功', detail);
  }

  function showDailyTaskDoneNavStatus(records, now) {
    if (!isTaskClaimCompletedToday(records, 'daily', now)) return null;
    var completedAt = getLatestTaskClaimCompletedAt(records, 'daily');
    var detail = completedAt ? ('日常任务已完成:' + formatShortTime(completedAt)) : '日常任务已完成';
    return showTaskAutoClaimNavSuccess('今日任务已完成', detail);
  }

  function hasSuccessfulTaskAutoClaimTask(results, taskKey) {
    var key = getTaskClaimTaskKey(taskKey);
    return (results || []).some(function hasSuccessfulTaskResult(result) {
      return result && result.target && result.target.taskKey === key && !isTaskAutoClaimResultBlocked(result);
    });
  }

  function showAutoBuyNavSuccess(record) {
    return showFavoriteNavStatusNote('spx-auto-buy-nav-note', '已购买', formatAutoBuyNavSuccessDetail(record));
  }

  function syncAutoBuyNavSuccessForThread(threadId) {
    var record = getAutoBuyDoneAttemptForThread(loadAutoBuyAttempts(), threadId);
    if (!record) return null;
    return showAutoBuyNavSuccess(record);
  }

  function fetchAndSyncTaskClaimRecordsAfterAutoClaim(host) {
    var url = getTaskCompletedPageUrl();
    return requestWithPolicy(url, { credentials: 'include', cache: 'no-store' }, { mode: 'action', label: '自动任务领取后同步记录' })
      .then(function readTaskAutoClaimCompletedPage(response) {
        return readScriptResponseText(response, { mode: 'action', label: '自动任务领取后同步记录' });
      })
      .then(function syncTaskAutoClaimCompletedPage(html) {
        var parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null;
        var doc = parser ? parser.parseFromString(html, 'text/html') : null;
        return syncTaskClaimRecordsFromDocument(doc || document, url);
      });
  }

  function getTaskAutoClaimResponseResult(html) {
    var value = compactText(String(html || '')
      .replace(/^<\?xml[^>]*>/i, '')
      .replace(/<ajax><!\[CDATA\[|\]\]><\/ajax>/gi, '')
      .replace(/<[^>]+>/g, ' '));
    var match = value.match(/^(success|confirm|error|fail|failure)\s*(.*)$/i);
    return {
      status: match ? match[1].toLowerCase() : '',
      message: match ? match[2] : value,
    };
  }

  function isTaskAutoClaimResultBlocked(result) {
    var status = result && result.response && result.response.status;
    return !!status && status !== 'success';
  }

  function hasSuccessfulTaskAutoClaimAction(results, actionType) {
    return (results || []).some(function hasTaskAutoClaimAction(result) {
      return result && result.target && result.target.actionType === actionType && !isTaskAutoClaimResultBlocked(result);
    });
  }

  function runTaskAutoClaimTargets(targets, host, index, results) {
    var list = targets || [];
    var output = results || [];
    if (index >= list.length) return Promise.resolve(output);
    var target = list[index];
    setTaskAutoClaimStatus(host, '自动领取:正在处理 ' + target.taskName + '...', false);
    return requestWithPolicy(target.url, target.options, { mode: 'action', label: '自动任务领取' })
      .then(function readTaskAutoClaimResponse(response) {
        if (!response.ok) throw new Error(target.taskName + ' 请求失败');
        return readScriptResponseText(response, { mode: 'action', label: '自动任务领取' });
      })
      .then(function keepTaskAutoClaimResult(html) {
        output.push({ target: target, html: html, response: getTaskAutoClaimResponseResult(html) });
        return runTaskAutoClaimTargets(list, host, index + 1, output);
      });
  }

  function resolveTaskAutoClaimTargets(host, options) {
    var opts = options || {};
    var canUseCurrentPage = opts.allowCurrentPage !== false;
    var targets = opts.initialTargets || (canUseCurrentPage ? getTaskAutoClaimTargets(host, location.href) : []);
    if (targets.length) return Promise.resolve({ source: 'current', targets: targets });
    if (canUseCurrentPage && /actions[-=]newtasks|newtasks/i.test(String(location.href || ''))) {
      return Promise.resolve({ source: 'current', targets: [] });
    }
    setTaskAutoClaimStatus(host, '自动领取:正在检查新任务页可领取入口...', false);
    var taskHomeUrl = getTaskHomePageUrl();
    var inProgressUrl = getTaskInProgressPageUrl();
    var requestPolicy = { mode: 'action', label: '自动任务领取检查' };
    return requestWithPolicy(taskHomeUrl, { credentials: 'include', cache: 'no-store' }, requestPolicy)
      .then(function readTaskAutoClaimHomePage(response) {
        if (!response.ok) throw new Error('新任务页检查失败');
        return readScriptResponseText(response, requestPolicy);
      })
      .then(function collectTaskAutoClaimHomeTargets(html) {
        var homeTargets = getTaskAutoClaimTargetsFromHtml(html, taskHomeUrl);
        if (homeTargets.length) return { source: 'task-home', targets: homeTargets };
        setTaskAutoClaimStatus(host, '自动领取:新任务页暂无入口,正在检查进行中任务奖励...', false);
        return requestWithPolicy(inProgressUrl, { credentials: 'include', cache: 'no-store' }, requestPolicy)
          .then(function readTaskAutoClaimInProgressPage(response) {
            if (!response.ok) throw new Error('进行中任务页检查失败');
            return readScriptResponseText(response, requestPolicy);
          })
          .then(function collectTaskAutoClaimInProgressTargets(html) {
            return { source: 'in-progress', targets: getTaskAutoClaimTargetsFromHtml(html, inProgressUrl) };
          });
      });
  }

  function fetchTaskAutoClaimRewardTargets(host, completedResults) {
    if (!hasSuccessfulTaskAutoClaimAction(completedResults, 'start')) return Promise.resolve([]);
    setTaskAutoClaimStatus(host, '自动领取:任务申请完成,正在检查进行中任务奖励...', false);
    var url = getTaskInProgressPageUrl();
    var seen = {};
    (completedResults || []).forEach(function rememberCompletedTaskAutoClaim(result) {
      if (result && result.target) seen[result.target.key] = true;
    });
    return requestWithPolicy(url, { credentials: 'include', cache: 'no-store' }, { mode: 'action', label: '自动任务奖励检查' })
      .then(function readTaskAutoClaimRewardPage(response) {
        if (!response.ok) throw new Error('进行中任务奖励检查失败');
        return readScriptResponseText(response, { mode: 'action', label: '自动任务奖励检查' });
      })
      .then(function collectTaskAutoClaimRewardTargets(html) {
        return getTaskAutoClaimTargetsFromHtml(html, url).filter(function keepTaskAutoClaimRewardTarget(target) {
          return target.actionType === 'finish' && !seen[target.key];
        });
      });
  }

  function maybeRunAutoTaskClaim(settings, host) {
    if (!settings || !settings.autoTaskClaim || typeof document === 'undefined' || typeof location === 'undefined') return Promise.resolve(null);
    if (!document.documentElement || document.documentElement.dataset.spxTaskAutoClaimRan === '1') return Promise.resolve(null);
    var isTaskPage = shouldUseTaskPage(location.href);
    var currentHost = host || (isTaskPage ? (qs('.spx-module-body') || qs('#main')) : null);
    var taskClaimRecords = loadTaskClaimRecords();
    showDailyTaskDoneNavStatus(taskClaimRecords, Date.now());
    document.documentElement.dataset.spxTaskAutoClaimRan = '1';
    if (shouldSyncTaskClaimRecordsFromUrl(location.href)) {
      setTaskAutoClaimStatus(currentHost, '自动领取:当前是已完成任务页,仅同步领取记录,不执行领取。', false);
      return Promise.resolve(null);
    }
    var currentTargets = isTaskPage && currentHost ? getTaskAutoClaimTargets(currentHost, location.href) : [];
    var gate = currentTargets.length ? { canRun: true, nextCheckAt: 0, dueTaskKeys: [], reason: 'visible-target' } : getTaskAutoClaimGate(taskClaimRecords, loadTaskAutoClaimState(), Date.now());
    if (!gate.canRun) {
      setTaskAutoClaimStatus(currentHost, '自动领取:仍在冷却中,下次检查约 ' + formatTaskAutoClaimNextCheck(gate.nextCheckAt) + '。', false);
      return Promise.resolve(null);
    }
    return resolveTaskAutoClaimTargets(currentHost, { allowCurrentPage: isTaskPage, initialTargets: currentTargets })
      .then(function runResolvedTaskAutoClaimTargets(result) {
        var targets = result && result.targets || [];
        if (!targets.length) {
          var message = result && result.source === 'in-progress'
            ? '自动领取:新任务页和进行中任务页都没有发现可领取入口,可能仍在冷却中。'
            : '自动领取:没有发现可领取的日常 / 周常入口,可能仍在冷却中。';
          rememberTaskAutoClaimCheck('no-targets', Date.now() + TASK_AUTO_CLAIM_PROBE_RETRY_TTL);
          setTaskAutoClaimStatus(currentHost, message, false);
          return null;
        }
        return runTaskAutoClaimTargets(targets, currentHost, 0, []).then(function runTaskAutoClaimRewards(results) {
          return fetchTaskAutoClaimRewardTargets(currentHost, results).then(function runTaskAutoClaimRewardTargets(rewardTargets) {
            if (!rewardTargets.length) return results;
            return runTaskAutoClaimTargets(rewardTargets, currentHost, 0, results);
          });
        });
      })
      .then(function syncTaskAutoClaimRecords(results) {
        if (!results) return null;
        var blocked = results.filter(isTaskAutoClaimResultBlocked);
        var succeeded = results.filter(function keepSucceededTaskAutoClaim(result) {
          return !isTaskAutoClaimResultBlocked(result);
        });
        blocked.forEach(function rememberBlockedTaskAutoClaim(result) {
          if (result && result.target) {
            rememberTaskAutoClaimTaskCooldown(result.target.taskKey, 'blocked', Date.now() + getTaskAutoClaimCooldownMs(result.target.taskKey));
          }
        });
        if (!succeeded.length) {
          var blockedMessage = blocked[0] && blocked[0].response && blocked[0].response.message || '当前任务暂不可领取';
          rememberTaskAutoClaimCheck('blocked', getTaskAutoClaimBlockedRetryAt(results));
          setTaskAutoClaimStatus(currentHost, '自动领取:暂不可领取,' + blockedMessage, false);
          return null;
        }
        return fetchAndSyncTaskClaimRecordsAfterAutoClaim(currentHost).then(function reportTaskAutoClaimSync(count) {
          var latestRecords = loadTaskClaimRecords();
          rememberTaskAutoClaimCheck('success', getTaskAutoClaimNextCheckAtFromRecords(latestRecords, Date.now()));
          var blockedText = blocked.length ? (',另有 ' + blocked.length + ' 个暂不可领取。') : '';
          var successMessage = '已处理 ' + succeeded.length + ' 个任务' + (count ? (',同步 ' + count + ' 条领取记录') : '');
          if (isTaskClaimCompletedToday(latestRecords, 'daily', Date.now()) || hasSuccessfulTaskAutoClaimTask(succeeded, 'daily')) {
            showTaskAutoClaimNavSuccess('今日任务已完成', successMessage + blockedText);
          } else {
            showTaskAutoClaimNavSuccess('任务执行成功', successMessage + blockedText);
          }
          setTaskAutoClaimStatus(
            currentHost,
            '自动领取:已处理 ' + succeeded.length + ' 个任务' + (count ? (',同步 ' + count + ' 条领取记录。') : ',可刷新页面查看最新状态。') + blockedText,
            false
          );
        });
      })
      .catch(function handleTaskAutoClaimError(error) {
        rememberTaskAutoClaimCheck('error', Date.now() + TASK_AUTO_CLAIM_ERROR_RETRY_TTL);
        setTaskAutoClaimStatus(currentHost, '自动领取失败:' + String(error && error.message || error || '未知错误'), true);
      });
  }

  function setTaskClaimInlineStatus(section, text, isError) {
    if (!section) return;
    section.spxTaskClaimStatus = text || '';
    section.spxTaskClaimStatusError = !!isError;
  }

  function renderTaskClaimInlineSection(section) {
    if (!section) return;
    var entries = getTaskClaimCenterEntries(loadTaskClaimRecords());
    var state = ensureCenterPanelState(section, { query: '', filter: 'all' });
    var visibleEntries = filterTaskClaimCenterEntries(entries, state);
    var filtered = isCenterFiltered(state);

    section.textContent = '';
    section.id = 'spx-task-claims';

    var header = createEl('div', 'spx-task-claim-head');
    var title = createEl('div');
    title.appendChild(createEl('h3', '', '任务领取记录'));
    title.appendChild(createEl(
      'p',
      'spx-task-claim-summary',
      (filtered ? (visibleEntries.length + ' / ') : '') + entries.length + ' / ' + TASK_CLAIM_RECORD_LIMIT + ' 条本地记录'
    ));
    header.appendChild(title);

    var actions = createEl('div', 'spx-task-claim-actions');
    var syncButton = createEl('button', '', '同步已完成页');
    syncButton.type = 'button';
    syncButton.dataset.action = 'sync-task-claims';
    actions.appendChild(syncButton);
    header.appendChild(actions);
    section.appendChild(header);

    var controls = createCenterControls(state, {
      searchPlaceholder: '搜索任务或来源',
      filters: [
        { value: 'all', label: '全部' },
        { value: 'daily', label: '日常' },
        { value: 'weekly', label: '周常' },
      ],
    });
    if (controls) section.appendChild(controls);

    if (section.spxTaskClaimStatus) {
      section.appendChild(createEl(
        'div',
        'spx-task-claim-status' + (section.spxTaskClaimStatusError ? ' spx-error' : ''),
        section.spxTaskClaimStatus
      ));
    }

    if (!entries.length) {
      section.appendChild(createEl('div', 'spx-task-claim-empty', '还没有任务领取记录。打开已完成任务页后会自动同步,并在本地保留最近 100 次。'));
      return;
    }

    if (!visibleEntries.length) {
      section.appendChild(createEl('div', 'spx-task-claim-empty', '没有匹配的任务领取记录。'));
      return;
    }

    var list = createEl('div', 'spx-task-claim-list');
    visibleEntries.forEach(function appendTaskClaimEntry(entry) {
      var row = createEl('div', 'spx-task-claim-row');
      row.dataset.key = entry.key;

      var body = createEl('div', 'spx-task-claim-body');
      var line = createEl('div', 'spx-task-claim-title');
      var name = entry.sourceUrl ? createEl('a', '', entry.taskName) : createEl('strong', '', entry.taskName);
      if (entry.sourceUrl) name.href = entry.sourceUrl;
      line.appendChild(name);
      if (entry.rewardSp !== null && isFinite(entry.rewardSp)) {
        line.appendChild(createEl('span', 'spx-task-claim-reward', '+' + entry.rewardSp + ' SP'));
      }
      body.appendChild(line);

      var metaParts = [];
      if (entry.completedAt) metaParts.push('领取 ' + formatShortTime(entry.completedAt));
      if (entry.recordedAt && entry.recordedAt !== entry.completedAt) metaParts.push('记录 ' + formatShortTime(entry.recordedAt));
      if (entry.source === 'site-completed') metaParts.push('来源 已完成任务页');
      body.appendChild(createEl('div', 'spx-task-claim-meta', metaParts.join(' · ') || '暂无详情'));
      row.appendChild(body);

      var removeButton = createEl('button', 'spx-task-claim-remove', '删除');
      removeButton.type = 'button';
      removeButton.dataset.action = 'remove-task-claim';
      removeButton.dataset.key = entry.key;
      row.appendChild(removeButton);
      list.appendChild(row);
    });
    section.appendChild(list);
  }

  function refreshTaskClaimInlineSection() {
    qsa('.spx-task-claim-inline').forEach(function refreshInlineTaskClaims(section) {
      renderTaskClaimInlineSection(section);
    });
  }

  function syncTaskClaimRecordsFromCompletedPage(section) {
    var url = getTaskCompletedPageUrl();
    setTaskClaimInlineStatus(section, '正在同步已完成任务页...', false);
    renderTaskClaimInlineSection(section);
    requestWithPolicy(url, { credentials: 'include', cache: 'no-store' }, { mode: 'action', label: '任务记录同步' })
      .then(function readTaskClaimInlineSyncResponse(response) {
        return readScriptResponseText(response, { mode: 'action', label: '任务记录同步' });
      })
      .then(function importTaskClaimInlineSyncHtml(html) {
        var parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null;
        var doc = parser ? parser.parseFromString(html, 'text/html') : null;
        var count = syncTaskClaimRecordsFromDocument(doc || document, url);
        setTaskClaimInlineStatus(section, count ? ('已同步 ' + count + ' 条已完成任务记录。') : '已完成页没有发现新的任务领取记录。', false);
        renderTaskClaimInlineSection(section);
      })
      .catch(function handleTaskClaimInlineSyncError(error) {
        setTaskClaimInlineStatus(section, String(error && error.message || '任务记录同步失败'), true);
        renderTaskClaimInlineSection(section);
      });
  }

  function handleTaskClaimInlineAction(action, target, section) {
    if (!section) return;
    if (action === 'sync-task-claims') {
      syncTaskClaimRecordsFromCompletedPage(section);
      return;
    }
    if (action === 'remove-task-claim') {
      var key = target && target.dataset && target.dataset.key;
      if (!key) return;
      var taskClaims = loadTaskClaimRecords();
      delete taskClaims[key];
      saveTaskClaimRecords(taskClaims);
      setTaskClaimInlineStatus(section, '已删除 1 条本地任务领取记录。', false);
      renderTaskClaimInlineSection(section);
    }
  }

  function createTaskClaimInlineSection(settings, state) {
    var section = createEl('section', 'spx-task-claim-inline');
    section.id = 'spx-task-claims';
    section.spxState = { query: '', filter: 'all' };
    section.spxRender = function renderInlineTaskClaims() {
      renderTaskClaimInlineSection(section);
    };
    section.addEventListener('compositionstart', function handleTaskClaimInlineCompositionStart(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      section.spxCenterQueryComposing = true;
    });
    section.addEventListener('compositionend', function handleTaskClaimInlineCompositionEnd(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      section.spxCenterQueryComposing = false;
      commitCenterQueryInput(section, { stateDefaults: { query: '', filter: 'all' } }, target);
    });
    section.addEventListener('input', function handleTaskClaimInlineInput(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterQuery !== '1') return;
      if (event.isComposing || section.spxCenterQueryComposing) return;
      commitCenterQueryInput(section, { stateDefaults: { query: '', filter: 'all' } }, target);
    });
    section.addEventListener('change', function handleTaskClaimInlineChange(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.spxCenterFilter !== '1') return;
      var currentState = ensureCenterPanelState(section, { query: '', filter: 'all' });
      if (currentState.filter === target.value) return;
      currentState.filter = target.value;
      renderTaskClaimInlineSection(section);
    });
    section.addEventListener('click', function handleTaskClaimInlineClick(event) {
      var target = event.target;
      var action = target && target.dataset && target.dataset.action;
      if (!action) return;
      handleTaskClaimInlineAction(action, target, section);
    });
    renderTaskClaimInlineSection(section);
    return section;
  }

  function ensureTaskClaimInlineSection(host) {
    if (!host || !shouldSyncTaskClaimRecordsFromUrl(location.href)) return null;
    var section = qs('#spx-task-claims', host) || qs('#spx-task-claims');
    if (!section) section = createTaskClaimInlineSection();
    if (section.parentNode !== host) host.appendChild(section);
    renderTaskClaimInlineSection(section);
    return section;
  }

  function createMyContentStat(label, value, note, className) {
    var stat = createEl('div', 'spx-content-stat' + (className ? ' ' + className : ''));
    stat.appendChild(createEl('b', '', String(value)));
    stat.appendChild(createEl('span', '', label));
    if (note) stat.appendChild(createEl('em', '', note));
    return stat;
  }

  function createMyContentShortcut(label, action, primary) {
    var button = createEl('button', primary ? 'spx-primary' : '', label);
    button.type = 'button';
    button.dataset.action = action;
    return button;
  }

  function appendMyContentRecentList(section, entries, emptyText, formatter) {
    var list = createEl('div', 'spx-content-recent-list');
    if (!(entries || []).length) {
      list.appendChild(createEl('div', 'spx-content-empty', emptyText));
    } else {
      entries.slice(0, 3).forEach(function appendRecentEntry(entry) {
        var row = createEl('div', 'spx-content-recent');
        var formatted = formatter(entry);
        row.appendChild(createEl('strong', '', formatted.title));
        row.appendChild(createEl('span', '', formatted.meta));
        list.appendChild(row);
      });
    }
    section.appendChild(list);
  }

  function renderMyContentCenter(panel, settings, state) {
    state.watch = state.watch || {};
    state.progress = state.progress || {};
    state.resources = pruneResourceLibrary(state.resources || {});
    var watchEntries = getWatchCenterEntries(state.watch, state.progress);
    var historyEntries = getHistoryCenterEntries(state.progress);
    var resourceEntries = getResourceCenterEntries(state.resources);
    var autoBuyEntries = getAutoBuyCenterEntries(loadAutoBuyAttempts());
    var taskClaimEntries = getTaskClaimCenterEntries(loadTaskClaimRecords());
    var favoriteSeenCount = Object.keys(loadMap(FAVORITE_NAV_SEEN_KEY)).length;
    var unreadWatchCount = watchEntries.filter(function countUnreadWatch(entry) {
      return !entry.progressAt || !isCompletedReadProgress(state.progress && state.progress[entry.id]);
    }).length;
    var queueCount = getResourceDownloadQueueEntries(resourceEntries).length;

    panel.textContent = '';
    var header = createEl('div', 'spx-watch-center-header spx-content-center-header');
    var title = createEl('div');
    title.appendChild(createEl('h3', '', '我的内容'));
    title.appendChild(createEl('p', '', '收藏、稍后、阅读进度、资源、购买和任务的本地汇总。'));
    header.appendChild(title);
    header.appendChild(createMyContentShortcut('关闭', 'close-content-center'));
    panel.appendChild(header);

    var stats = createEl('div', 'spx-content-stats');
    stats.appendChild(createMyContentStat('稍后看', watchEntries.length, unreadWatchCount ? ('未读 ' + unreadWatchCount) : '已同步进度', unreadWatchCount ? 'spx-warn' : 'spx-ok'));
    stats.appendChild(createMyContentStat('最近浏览', historyEntries.length, '阅读进度记录'));
    stats.appendChild(createMyContentStat('资源', resourceEntries.length, queueCount ? ('待下载 ' + queueCount) : '无待处理', queueCount ? 'spx-warn' : 'spx-ok'));
    stats.appendChild(createMyContentStat('购买记录', autoBuyEntries.length, '自动购买执行状态'));
    stats.appendChild(createMyContentStat('任务领取', taskClaimEntries.length, '最近 100 次'));
    stats.appendChild(createMyContentStat('收藏标记', favoriteSeenCount, '本地已识别站内收藏'));
    panel.appendChild(stats);

    var shortcuts = createEl('div', 'spx-content-shortcuts');
    shortcuts.appendChild(createMyContentShortcut('打开稍后看', 'open-watch-center', true));
    shortcuts.appendChild(createMyContentShortcut('打开最近浏览', 'open-history-center', false));
    shortcuts.appendChild(createMyContentShortcut('打开资源工作台', 'open-resource-center', false));
    shortcuts.appendChild(createMyContentShortcut('打开购买记录', 'open-auto-buy-center', false));
    shortcuts.appendChild(createMyContentShortcut('查看已完成任务', 'open-task-completed-page', false));
    panel.appendChild(shortcuts);

    var grid = createEl('div', 'spx-content-recent-grid');
    var watchSection = createEl('section', 'spx-content-recent-section');
    watchSection.appendChild(createEl('h4', '', '稍后看积压'));
    appendMyContentRecentList(watchSection, watchEntries, '暂无稍后看主题。', function formatWatchRecent(entry) {
      return { title: entry.title, meta: [entry.progressText, entry.nextFloorLabel, entry.tagText].filter(Boolean).join(' · ') || entry.url };
    });
    grid.appendChild(watchSection);

    var resourceSection = createEl('section', 'spx-content-recent-section');
    resourceSection.appendChild(createEl('h4', '', '资源待处理'));
    appendMyContentRecentList(resourceSection, getResourceDownloadQueueEntries(resourceEntries), '暂无待下载资源。', function formatResourceRecent(entry) {
      return { title: entry.label || entry.url, meta: [entry.sourceTitle, entry.floorLabel, entry.author].filter(Boolean).join(' · ') || entry.url };
    });
    grid.appendChild(resourceSection);

    var taskSection = createEl('section', 'spx-content-recent-section');
    taskSection.appendChild(createEl('h4', '', '任务领取'));
    appendMyContentRecentList(taskSection, taskClaimEntries, '暂无任务领取记录。', function formatTaskClaimRecent(entry) {
      return { title: entry.taskName, meta: [entry.rewardSp !== null ? ('+' + entry.rewardSp + ' SP') : '', formatShortTime(entry.completedAt)].filter(Boolean).join(' · ') };
    });
    grid.appendChild(taskSection);
    panel.appendChild(grid);
  }

  function createMyContentCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-content-center',
      render: function render(panel) {
        renderMyContentCenter(panel, settings, state);
      },
      onAction: function onAction(action, target, panel) {
        if (action === 'close-content-center') {
          setCenterPanelHidden(panel, true);
          return;
        }
        if (action === 'open-watch-center') {
          if (openWorkbenchPanel('spx-watch-center', settings, state)) return;
          setCenterPanelHidden(createWatchCenterPanel(settings, state), false);
          return;
        }
        if (action === 'open-history-center') {
          if (openWorkbenchPanel('spx-history-center', settings, state)) return;
          setCenterPanelHidden(createHistoryCenterPanel(settings, state), false);
          return;
        }
        if (action === 'open-resource-center') {
          if (openWorkbenchPanel('spx-resource-center', settings, state)) return;
          setCenterPanelHidden(createResourceCenterPanel(settings, state), false);
          return;
        }
        if (action === 'open-auto-buy-center') {
          if (openWorkbenchPanel('spx-auto-buy-center', settings, state)) return;
          setCenterPanelHidden(createAutoBuyCenterPanel(settings, state), false);
          return;
        }
        if (action === 'open-task-completed-page') {
          if (typeof location !== 'undefined') location.href = getTaskCompletedPageUrl() + '#spx-task-claims';
        }
      },
    });
  }

  function renderResourceCenter(panel, state) {
    state.resources = pruneResourceLibrary(state.resources || {});
    var entries = getResourceCenterEntries(state.resources);
    renderCenterPanel(panel, {
      title: '资源工作台',
      stateDefaults: { query: '', filter: 'all', provider: 'all', tag: 'all', view: 'list', selectedResources: {} },
      summary: function summary(visibleEntries, entries, panelState) {
        var queueCount = getResourceDownloadQueueEntries(entries).length;
        var selectedCount = getSelectedResourceKeys(entries, panelState).length;
        return (
          (isCenterFiltered(panelState) ? (visibleEntries.length + ' / ') : '') +
          entries.length +
          ' 条资源' +
          (queueCount ? (' · 待下载 ' + queueCount + ' 条') : '') +
          (selectedCount ? (' · 已选 ' + selectedCount + ' 条') : '') +
          (panelState.view === 'source' ? ' · 来源分组' : '')
        );
      },
      headerActions: function headerActions(visibleEntries, entries, panelState) {
        var actions = [];
        var selectedKeys = getSelectedResourceKeys(entries, panelState);
        var visibleQueue = getResourceDownloadQueueEntries(visibleEntries);
        if (visibleEntries.length) actions.push({ text: '全选筛选', dataset: { action: 'select-visible-resources' } });
        if (selectedKeys.length) {
          actions.push({ text: '取消选择', dataset: { action: 'clear-resource-selection' } });
          actions.push({ text: '复制选中', dataset: { action: 'copy-selected-resources' } });
          actions.push({ text: 'Markdown', dataset: { action: 'copy-selected-resource-markdown' } });
          actions.push({ text: '选中待下载', dataset: { action: 'mark-selected-resources', status: 'todo' } });
          actions.push({ text: '选中已处理', dataset: { action: 'mark-selected-resources', status: 'done' } });
          actions.push({ text: '选中失效', dataset: { action: 'mark-selected-resources', status: 'invalid' } });
          actions.push({ text: '选中标签', dataset: { action: 'tag-selected-resources' } });
          actions.push({ text: '选中备注', dataset: { action: 'note-selected-resources' } });
        }
        if (visibleEntries.length) actions.push({ text: '复制筛选', dataset: { action: 'copy-visible-resources' } });
        if (visibleQueue.length) actions.push({ text: '复制待下载', dataset: { action: 'copy-download-queue' } });
        if (visibleEntries.length) actions.push({ text: '导出清单', dataset: { action: 'export-visible-resources' } });
        if (visibleEntries.length && isCenterFiltered(panelState)) {
          actions.push({ text: '删除筛选', dataset: { action: 'remove-visible-resources' } });
        }
        if (entries.length) actions.push({ text: '清空', dataset: { action: 'clear-resources' } });
        actions.push({ text: '关闭', dataset: { action: 'close-resource-center' } });
        return actions;
      },
      controls: {
        searchPlaceholder: '搜索资源、帖子、作者、备注或标签',
        filters: [
          { value: 'all', label: '全部状态' },
          { value: 'saved', label: RESOURCE_STATUSES.saved },
          { value: 'todo', label: RESOURCE_STATUSES.todo },
          { value: 'done', label: RESOURCE_STATUSES.done },
          { value: 'invalid', label: RESOURCE_STATUSES.invalid },
        ],
        providers: getResourceProviderOptions(entries),
        providerAllLabel: '全部类型',
        tags: getCenterTagOptions(entries),
        views: [
          { value: 'list', label: '列表视图' },
          { value: 'source', label: '按来源帖' },
        ],
      },
      emptyText: '还没有保存的资源。可在阅读页资源面板或自动购买成功后存入。',
      emptyFilteredText: '没有匹配的资源。',
      entries: entries,
      filterEntries: filterResourceCenterEntries,
      transformVisibleEntries: function transformVisibleEntries(visibleEntries, entries, panelState) {
        return panelState.view === 'source' ? groupResourceCenterEntries(visibleEntries) : visibleEntries;
      },
      getItemData: function getItemData(entry) {
        return entry.entries ? { resourceGroupKey: entry.key } : { key: entry.key };
      },
      createTitle: function createTitle(entry) {
        if (entry.entries) {
          return createCenterTitleElement({
            text: entry.label + ' · ' + entry.entries.length + ' 条资源',
            href: entry.sourceUrl,
            badgeText: '来源分组',
            badgeClass: 'spx-status-saved',
            title: entry.label,
          });
        }
        var titleRow = createEl('div', 'spx-resource-title-row');
        var select = createEl('input', 'spx-resource-select');
        select.type = 'checkbox';
        select.dataset.action = 'toggle-resource-selection';
        select.dataset.key = entry.key;
        select.checked = !!ensureResourceSelection(ensureCenterPanelState(panel, { selectedResources: {} }))[entry.key];
        select.title = '选择资源';
        titleRow.appendChild(select);
        titleRow.appendChild(createCenterTitleElement({
          text: entry.label + ' · ' + entry.url,
          href: entry.url,
          badgeText: entry.statusLabel,
          badgeClass: 'spx-status-' + entry.status,
          title: entry.url,
        }));
        return titleRow;
      },
      createMeta: function createMeta(entry) {
        if (entry.entries) {
          var typeCounts = {};
          entry.entries.forEach(function countGroupResource(item) {
            var label = item.label || getResourceDisplayLabel(item);
            typeCounts[label] = (typeCounts[label] || 0) + 1;
          });
          var typeText = Object.keys(typeCounts).map(function formatTypeCount(label) {
            return label + ' ' + typeCounts[label];
          }).join(' / ');
          return [
            typeText,
            entry.updatedAt ? ('最新 ' + formatShortTime(entry.updatedAt)) : '',
            entry.sourceUrl ? entry.sourceUrl : '',
          ].filter(Boolean).join(' · ') || '暂无来源详情';
        }
        var metaParts = [];
        if (entry.savedAt) metaParts.push('保存 ' + formatShortTime(entry.savedAt));
        if (entry.provider) metaParts.push(entry.provider);
        if (entry.sourceText) metaParts.push(entry.sourceText);
        if (entry.accessCode) metaParts.push('提取码 ' + entry.accessCode);
        if (entry.tagText) metaParts.push('标签 ' + entry.tagText);
        if (entry.note) metaParts.push('备注 ' + entry.note);
        return metaParts.join(' · ') || '暂无来源';
      },
      createActions: function createActions(entry) {
        if (entry.entries) {
          var groupActions = [
            { text: '选择分组', dataset: { action: 'select-resource-group', sourceKey: entry.key } },
            { text: '复制分组', dataset: { action: 'copy-resource-group', sourceKey: entry.key } },
            { text: '分组 Markdown', dataset: { action: 'copy-resource-group-markdown', sourceKey: entry.key } },
            { text: '待下载', dataset: { action: 'mark-resource-group', sourceKey: entry.key, status: 'todo' } },
            { text: '已处理', dataset: { action: 'mark-resource-group', sourceKey: entry.key, status: 'done' } },
          ];
          if (entry.sourceUrl) groupActions.push({ text: '来源帖', href: entry.sourceUrl });
          return groupActions;
        }
        var actions = [
          { text: '复制', dataset: { action: 'copy-resource', key: entry.key } },
          { text: '备注', dataset: { action: 'note-resource', key: entry.key } },
          { text: '标签', dataset: { action: 'tag-resource', key: entry.key } },
          { text: '待下载', dataset: { action: 'mark-resource-todo', key: entry.key } },
          { text: '已处理', dataset: { action: 'mark-resource-done', key: entry.key } },
          { text: '失效', dataset: { action: 'mark-resource-invalid', key: entry.key } },
        ];
        if (entry.status !== 'saved') actions.push({ text: '已保存', dataset: { action: 'mark-resource-saved', key: entry.key } });
        if (entry.sourceUrl) actions.push({ text: '来源帖', href: entry.sourceUrl });
        actions.push({ text: '删除', dataset: { action: 'remove-resource', key: entry.key } });
        return actions;
      },
    });
  }

  function getVisibleResourceCenterEntries(panel, state) {
    return filterResourceCenterEntries(
      getResourceCenterEntries(state.resources),
      ensureCenterPanelState(panel, { query: '', filter: 'all', provider: 'all', tag: 'all', view: 'list', selectedResources: {} })
    );
  }

  function getResourceGroupEntriesFromPanel(panel, state, sourceKey) {
    var groups = groupResourceCenterEntries(getVisibleResourceCenterEntries(panel, state));
    var group = groups.filter(function matchResourceGroup(item) {
      return item.key === sourceKey;
    })[0];
    return group ? group.entries : [];
  }

  function updateResourceRecords(state, keys, updater) {
    var now = Date.now();
    (keys || []).forEach(function updateResourceKey(key) {
      if (!state.resources[key]) return;
      var record = normalizeResourceRecord(state.resources[key], key);
      if (!record) return;
      updater(record);
      record.updatedAt = now;
      state.resources[key] = record;
    });
  }

  function promptResourceTags(currentTags) {
    return typeof window.prompt === 'function'
      ? window.prompt('编辑标签,多个标签用逗号或换行分隔', normalizeResourceTags(currentTags).join(','))
      : null;
  }

  function promptResourceNote(currentNote) {
    return typeof window.prompt === 'function'
      ? window.prompt('编辑备注', String(currentNote || ''))
      : null;
  }

  function getResourceEntryKeys(entries) {
    return (entries || []).map(function mapResourceEntryKey(entry) {
      return entry && entry.key;
    }).filter(Boolean);
  }

  function copyResourceEntriesToClipboard(entries, formatter, target, successText, restoreText) {
    copyTextToClipboard(formatter(entries)).then(
      function showCopySuccess() {
        setTemporaryText(target, successText, restoreText);
      },
      function showCopyFailure() {
        setTemporaryText(target, '复制失败', restoreText);
      }
    );
  }

  function refreshResourceCenter() {
    refreshCenterPanel('#spx-resource-center');
  }

  function saveResourceCenterState(state) {
    state.resources = pruneResourceLibrary(state.resources || {});
    saveResourceLibrary(state.resources);
    refreshResourceCenter();
    refreshReadResourceRail();
    refreshReadThreadSummaryCard();
  }

  function createResourceCenterPanel(settings, state) {
    return createCenterPanel({
      id: 'spx-resource-center',
      stateDefaults: { query: '', filter: 'all', provider: 'all', tag: 'all', view: 'list', selectedResources: {} },
      render: function render(panel) {
        renderResourceCenter(panel, state);
      },
      onAction: function onAction(action, target, panel) {
        state.resources = pruneResourceLibrary(state.resources || {});
        if (action === 'close-resource-center') {
          setCenterPanelHidden(panel, true);
          return;
        }
        var panelState = ensureCenterPanelState(panel, { query: '', filter: 'all', provider: 'all', tag: 'all', view: 'list', selectedResources: {} });
        var allEntries = getResourceCenterEntries(state.resources);
        var visibleEntriesNow = getVisibleResourceCenterEntries(panel, state);
        var selectedKeys = getSelectedResourceKeys(allEntries, panelState);
        var selectedEntries = getResourceEntriesByKeys(allEntries, selectedKeys);
        if (action === 'toggle-resource-selection') {
          var toggleKey = target.dataset.key;
          if (!toggleKey) return;
          var selection = ensureResourceSelection(panelState);
          if (target.checked) selection[toggleKey] = true;
          else delete selection[toggleKey];
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'select-visible-resources') {
          setResourceSelection(visibleEntriesNow, panelState, true);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'clear-resource-selection') {
          panelState.selectedResources = {};
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'select-resource-group') {
          setResourceSelection(getResourceGroupEntriesFromPanel(panel, state, target.dataset.sourceKey), panelState, true);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'copy-selected-resources') {
          copyResourceEntriesToClipboard(selectedEntries, formatResourceLinks, target, '已复制 ' + selectedEntries.length + ' 条', '复制选中');
          return;
        }
        if (action === 'copy-selected-resource-markdown') {
          copyResourceEntriesToClipboard(selectedEntries, formatResourceMarkdownList, target, '已复制 Markdown', 'Markdown');
          return;
        }
        if (action === 'copy-resource-group' || action === 'copy-resource-group-markdown') {
          var groupEntriesForCopy = getResourceGroupEntriesFromPanel(panel, state, target.dataset.sourceKey);
          copyResourceEntriesToClipboard(
            groupEntriesForCopy,
            action === 'copy-resource-group-markdown' ? formatResourceMarkdownList : formatResourceLinks,
            target,
            action === 'copy-resource-group-markdown' ? '已复制 Markdown' : ('已复制 ' + groupEntriesForCopy.length + ' 条'),
            action === 'copy-resource-group-markdown' ? '分组 Markdown' : '复制分组'
          );
          return;
        }
        if (action === 'mark-selected-resources' || action === 'mark-resource-group') {
          var statusKeys = action === 'mark-resource-group'
            ? getResourceEntryKeys(getResourceGroupEntriesFromPanel(panel, state, target.dataset.sourceKey))
            : selectedKeys;
          if (!statusKeys.length) return;
          var nextStatus = normalizeResourceStatus(target.dataset.status);
          var statusLabel = getResourceStatusLabel(nextStatus);
          var statusEntries = getResourceEntriesByKeys(allEntries, statusKeys);
          confirmBatchAction({
            title: '批量标记资源',
            message: '将把选中的资源统一标记为“' + statusLabel + '”。',
            confirmText: '标记为' + statusLabel,
            danger: nextStatus === 'invalid',
            impacts: [{ label: '资源记录', value: statusKeys.length, note: '状态改为 ' + statusLabel }],
            items: statusEntries.map(function mapStatusConfirmItem(entry) {
              return { title: entry.label || entry.url, meta: [entry.sourceTitle, entry.floorLabel, entry.author].filter(Boolean).join(' · ') };
            }),
          }, '批量标记选中的资源?').then(function markResourcesAfterConfirm(ok) {
            if (!ok) return;
            updateResourceRecords(state, statusKeys, function markSelectedResource(record) {
              record.status = nextStatus;
            });
            saveResourceCenterState(state);
            renderResourceCenter(panel, state);
          });
          return;
        }
        if (action === 'tag-selected-resources') {
          var selectedTagText = promptResourceTags([]);
          if (selectedTagText === null) return;
          updateResourceRecords(state, selectedKeys, function tagSelectedResource(record) {
            record.tags = parseTagList(selectedTagText);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'note-selected-resources') {
          var selectedNoteText = promptResourceNote('');
          if (selectedNoteText === null) return;
          updateResourceRecords(state, selectedKeys, function noteSelectedResource(record) {
            record.note = String(selectedNoteText || '').replace(/\s+/g, ' ').trim().slice(0, 160);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'copy-visible-resources') {
          var visibleForCopy = visibleEntriesNow;
          copyTextToClipboard(formatResourceLinks(visibleForCopy)).then(
            function showCopySuccess() {
              setTemporaryText(target, '已复制 ' + visibleForCopy.length + ' 条', '复制筛选');
            },
            function showCopyFailure() {
              setTemporaryText(target, '复制失败', '复制筛选');
            }
          );
          return;
        }
        if (action === 'copy-download-queue') {
          var visibleQueueForCopy = getResourceDownloadQueueEntries(visibleEntriesNow);
          copyTextToClipboard(formatResourceDownloadList(visibleQueueForCopy)).then(
            function showQueueCopySuccess() {
              setTemporaryText(target, '已复制 ' + visibleQueueForCopy.length + ' 条', '复制待下载');
            },
            function showQueueCopyFailure() {
              setTemporaryText(target, '复制失败', '复制待下载');
            }
          );
          return;
        }
        if (action === 'export-visible-resources') {
          var visibleForExport = visibleEntriesNow;
          if (!visibleForExport.length) return;
          if (exportResourceDownloadList(visibleForExport)) {
            setTemporaryText(target, '已导出 ' + visibleForExport.length + ' 条', '导出清单');
          } else {
            copyTextToClipboard(formatResourceDownloadList(visibleForExport)).then(
              function showExportCopySuccess() {
                setTemporaryText(target, '已复制清单', '导出清单');
              },
              function showExportFailure() {
                setTemporaryText(target, '导出失败', '导出清单');
              }
            );
          }
          return;
        }
        if (action === 'remove-visible-resources') {
          var visibleEntries = visibleEntriesNow;
          if (!visibleEntries.length) return;
          confirmBatchAction({
            title: '删除资源筛选结果',
            message: '将从资源工作台删除当前筛选结果。',
            confirmText: '删除筛选',
            impacts: [{ label: '资源记录', value: visibleEntries.length, note: '不可自动恢复' }],
            items: visibleEntries.map(function mapVisibleResourceConfirmItem(entry) {
              return { title: entry.label || entry.url, meta: [entry.sourceTitle, entry.floorLabel, entry.author].filter(Boolean).join(' · ') };
            }),
          }, '删除当前筛选结果中的资源?').then(function removeVisibleResourcesAfterConfirm(ok) {
            if (!ok) return;
            visibleEntries.forEach(function removeVisibleResource(entry) {
              delete state.resources[entry.key];
              delete ensureResourceSelection(panelState)[entry.key];
            });
            saveResourceCenterState(state);
            renderResourceCenter(panel, state);
          });
          return;
        }
        if (action === 'clear-resources') {
          confirmBatchAction({
            title: '清空资源工作台',
            message: '将清空全部本地资源记录,包括备注、标签和处理状态。',
            confirmText: '清空资源',
            impacts: [{ label: '资源记录', value: allEntries.length, note: '不可自动恢复' }],
            items: allEntries.map(function mapClearResourceConfirmItem(entry) {
              return { title: entry.label || entry.url, meta: entry.sourceTitle || entry.url };
            }),
          }, '清空全部资源库记录?').then(function clearResourcesAfterConfirm(ok) {
            if (!ok) return;
            state.resources = {};
            panelState.selectedResources = {};
            saveResourceCenterState(state);
            renderResourceCenter(panel, state);
          });
          return;
        }

        var key = target.dataset.key;
        if (!key) return;
        if (action === 'copy-resource') {
          var entry = state.resources[key];
          copyTextToClipboard(formatResourceLinks(entry ? [entry] : [])).catch(function noop() {});
          return;
        }
        if (action === 'remove-resource') {
          delete state.resources[key];
          delete ensureResourceSelection(panelState)[key];
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'tag-resource' && state.resources[key]) {
          var tagText = promptResourceTags(state.resources[key].tags);
          if (tagText === null) return;
          updateResourceRecords(state, [key], function tagResource(record) {
            record.tags = parseTagList(tagText);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (action === 'note-resource' && state.resources[key]) {
          var noteText = promptResourceNote(state.resources[key].note);
          if (noteText === null) return;
          updateResourceRecords(state, [key], function noteResource(record) {
            record.note = String(noteText || '').replace(/\s+/g, ' ').trim().slice(0, 160);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
          return;
        }
        if (/^mark-resource-/.test(action) && state.resources[key]) {
          var status = action.replace('mark-resource-', '');
          updateResourceRecords(state, [key], function markResource(record) {
            record.status = normalizeResourceStatus(status);
          });
          saveResourceCenterState(state);
          renderResourceCenter(panel, state);
        }
      },
    });
  }

  function createForumQuickTools(settings, state, items) {
    if (qs('#spx-forum-tools')) return;
    var content = qs('#content') || qs('#main') || document.body;
    if (!content) return;

    var tools = createEl('div', 'spx-forum-tools');
    tools.id = 'spx-forum-tools';
    var activeResourceFilter = 'all';
    var input = createEl('input');
    input.type = 'search';
    input.placeholder = '快速过滤:关键词、!排除、作者:用户名';
    var resourceFilters = createEl('div', 'spx-forum-resource-filters');
    var markVisibleButton = createEl('button', '', '可见已读');
    var watchVisibleButton = createEl('button', '', '可见稍后');
    var preloadNextButton = createEl('button', '', '预载下页');
    var clearButton = createEl('button', '', '清空过滤');
    tools.appendChild(input);
    tools.appendChild(resourceFilters);
    tools.appendChild(markVisibleButton);
    tools.appendChild(watchVisibleButton);
    tools.appendChild(preloadNextButton);
    tools.appendChild(clearButton);

    function getAvailableResourceFilterTypes() {
      var seen = {};
      (items || []).forEach(function collectItemResourceTypes(item) {
        (item.resourceBadgeTypes || []).forEach(function collectType(type) {
          if (getResourceBadgeDefinition(type)) seen[type] = true;
        });
      });
      return RESOURCE_BADGE_ORDER.filter(function keepAvailableType(type) {
        return !!seen[type];
      });
    }

    function syncResourceFilterButtons() {
      qsa('button[data-spx-resource-filter]', resourceFilters).forEach(function syncFilterButton(button) {
        button.classList.toggle('spx-resource-filter-active', button.dataset.spxResourceFilter === activeResourceFilter);
      });
    }

    function setResourceFilter(type) {
      activeResourceFilter = getResourceBadgeDefinition(type) ? type : 'all';
      syncResourceFilterButtons();
      applyFilter();
    }

    function createResourceFilterButton(type, label) {
      var button = createEl('button', type === activeResourceFilter ? 'spx-resource-filter-active' : '', label);
      button.type = 'button';
      button.dataset.spxResourceFilter = type;
      button.title = type === 'all' ? '显示全部资源类型' : '只看' + label + '资源主题';
      button.addEventListener('click', function applyResourceFilter(event) {
        event.preventDefault();
        setResourceFilter(type);
      });
      return button;
    }

    function renderResourceFilters() {
      var types = getAvailableResourceFilterTypes();
      resourceFilters.textContent = '';
      if (!types.length) return;
      resourceFilters.appendChild(createResourceFilterButton('all', '全部资源'));
      types.forEach(function appendResourceFilter(type) {
        resourceFilters.appendChild(createResourceFilterButton(type, getResourceBadgeDefinition(type).label));
      });
    }

    function applyFilter() {
      var parsed = parseForumFilterQuery(input.value);
      items.forEach(function toggleItem(item) {
        var textHidden = !!input.value.trim() && !matchesForumFilter(item, parsed);
        var resourceHidden = activeResourceFilter !== 'all' && (item.resourceBadgeTypes || []).indexOf(activeResourceFilter) === -1;
        setThreadRowHiddenClass(item.row, 'spx-filter-hidden', textHidden);
        setThreadRowHiddenClass(item.row, 'spx-resource-filter-hidden', resourceHidden);
      });
    }

    renderResourceFilters();
    document.addEventListener('spx-resource-filter', function handleResourceBadgeFilter(event) {
      if (!tools.isConnected) return;
      setResourceFilter(event && event.detail && event.detail.type);
    });

    input.addEventListener('input', applyFilter);
    clearButton.addEventListener('click', function clearFilter() {
      input.value = '';
      activeResourceFilter = 'all';
      syncResourceFilterButtons();
      applyFilter();
      input.focus();
    });
    markVisibleButton.addEventListener('click', function markVisibleRead() {
      markThreadsRead(items, state);
      saveMap(READ_KEY, state.read);
      items.forEach(function syncRead(item) {
        if (item.row && state.read[item.id]) item.row.classList.add('spx-read-thread');
      });
    });
    watchVisibleButton.addEventListener('click', function watchVisibleThreads() {
      var count = 0;
      state.watch = state.watch || {};
      items.forEach(function saveVisibleWatch(item) {
        if (!item || !item.id || !item.titleLink || !isVisibleThreadRow(item.row)) return;
        if (state.watch[item.id]) return;
        state.watch[item.id] = {
          title: item.title,
          url: item.titleLink.href,
          savedAt: Date.now(),
        };
        count += 1;
        var button = qs('.spx-thread-tools button', item.cell);
        if (button && button.textContent === '稍后') button.textContent = '已存';
        if (!qs('.spx-watch-badge', item.cell)) {
          item.titleLink.insertAdjacentElement('afterend', createWatchBadge(item.id));
        }
      });
      saveMap(WATCH_KEY, state.watch);
      refreshWatchCenter();
      watchVisibleButton.textContent = count ? '已存 ' + count + ' 条' : '无新增';
      window.setTimeout(function resetWatchVisibleText() {
        watchVisibleButton.textContent = '可见稍后';
      }, 1600);
    });
    preloadNextButton.addEventListener('click', function preloadNextPage() {
      if (typeof window.fetch !== 'function') return;
      var nextUrl = buildPageUrl(location.href, currentPageNumber(location.href) + 1);
      var preloadPolicy = {
        mode: 'interactive',
        label: '预载下页',
        networkFriendly: isNetworkFriendlyMode(settings),
      };
      preloadNextButton.disabled = true;
      preloadNextButton.textContent = '预载中';
      requestWithPolicy(nextUrl, {
        credentials: 'include',
        cache: 'force-cache',
      }, preloadPolicy)
        .then(function readNextPage(response) {
          if (!response.ok) throw new Error('下一页加载失败');
          return readScriptResponseText(response, preloadPolicy);
        })
        .then(function showNextPageCount(html) {
          var doc = new DOMParser().parseFromString(html, 'text/html');
          var count = qsa('td[id^="td_"]', doc).length || qsa('#wall .stream li a[href*="read.php?tid"]', doc).length;
          preloadNextButton.textContent = count ? '已预载 ' + count + ' 条' : '已预载';
        })
        .catch(function showPreloadFailure() {
          preloadNextButton.textContent = '预载失败';
        })
        .then(function resetPreloadButton() {
          window.setTimeout(function resetPreloadText() {
            preloadNextButton.disabled = false;
            preloadNextButton.textContent = '预载下页';
          }, 1800);
        });
    });

    content.insertBefore(tools, content.firstChild);
  }

  function restoreModuleNavigation() {
    var nav = qs('#spx-module-nav');
    if (nav) nav.remove();
    document.documentElement.classList.remove('spx-module-nav-ready');
    qsa('.spx-module-body').forEach(function unwrapModuleBody(body) {
      var parent = body.parentNode;
      if (!parent) return;
      while (body.firstChild) {
        parent.insertBefore(body.firstChild, body);
      }
      body.remove();
    });
    qsa('.spx-module-nav-host').forEach(function restoreNavHost(host) {
      host.classList.remove('spx-module-nav-host');
    });
    qsa('.spx-module-filter-hidden').forEach(function restoreFilteredRow(row) {
      row.classList.remove('spx-module-filter-hidden');
    });
  }

  function getCompactNodeText(node, fallback) {
    if (!node) return fallback || '';
    var clone = node.cloneNode(true);
    qsa('button,input,select,textarea', clone).forEach(function removeControl(control) {
      control.remove();
    });
    return (clone.textContent || '').replace(/\s+/g, ' ').trim() || fallback || '';
  }

  function ensureModuleTargetId(node, prefix, index) {
    if (!node) return '';
    if (!node.id) node.id = 'spx-module-target-' + prefix + '-' + (index + 1);
    return node.id;
  }

  function setModuleNavActive(nav, activeItem) {
    qsa('.spx-module-nav-item', nav).forEach(function toggleActive(item) {
      item.classList.toggle('spx-active', item === activeItem);
    });
  }

  function getModuleNavigationConfigPinKey(config) {
    if (!config || !config.href) return '';
    var label = normalizeNavigationLabel(config.label || config.title);
    var baseUrl = typeof location !== 'undefined' ? location.href : 'https://south-plus.org/';
    var href = normalizeNavigationHref(config.href, baseUrl);
    if (!label || !href) return '';
    return getNavigationItemKey(config.section || '导航', label, href);
  }

  function toggleModuleNavigationPin(pinKey) {
    if (!pinKey) return;
    var pins = loadNavigationPins();
    if (pins[pinKey]) {
      delete pins[pinKey];
    } else {
      pins[pinKey] = true;
    }
    saveNavigationPins(pins);
    remountGlobalModuleNavigation();
  }

  function createModuleNavigationPin(config) {
    var pin = createEl('span', 'spx-module-nav-pin', config.pinned ? '★' : '☆');
    pin.setAttribute('role', 'button');
    pin.setAttribute('tabindex', '0');
    pin.setAttribute('aria-label', config.pinned ? '取消置顶' : '置顶导航');
    pin.title = config.pinned ? '取消置顶' : '置顶导航';
    pin.addEventListener('click', function handleModuleNavigationPinClick(event) {
      event.preventDefault();
      event.stopPropagation();
      toggleModuleNavigationPin(config.pinKey);
    });
    pin.addEventListener('keydown', function handleModuleNavigationPinKeydown(event) {
      if (event.key !== 'Enter' && event.key !== ' ') return;
      event.preventDefault();
      event.stopPropagation();
      toggleModuleNavigationPin(config.pinKey);
    });
    return pin;
  }

  function getModuleNavigationUsageKey(config) {
    if (!config) return '';
    var label = normalizeNavigationLabel(config.label || config.title);
    var section = normalizeNavigationLabel(config.section || '导航');
    var parentLabel = normalizeNavigationLabel(config.parentLabel || '');
    var baseUrl = typeof location !== 'undefined' ? location.href : 'https://south-plus.org/';
    var href = normalizeNavigationHref(config.href || '', baseUrl);
    if (!label || !section) return '';
    if (href) return getNavigationItemKey(section, label, href);
    if (config.panelId) return getNavigationItemKey(section, label, 'panel:' + config.panelId);
    if (config.target && config.target.id) return getNavigationItemKey(section, label, 'target:' + config.target.id);
    return getNavigationItemKey(section, label, parentLabel || label);
  }

  function rememberModuleNavigationUsage(config, options) {
    var key = getModuleNavigationUsageKey(config);
    if (!key) return null;
    var now = Date.now();
    var usage = loadNavigationUsage();
    var record = usage[key] || {};
    var clicked = !!(options && options.clicked);
    var minimumInterval = clicked ? 0 : 5 * 60 * 1000;
    if (!clicked && record.usedAt && (now - Number(record.usedAt)) < minimumInterval) return record;
    usage[key] = {
      usedAt: now,
      clickedAt: clicked ? now : Math.max(0, Number(record.clickedAt) || 0),
      hitCount: Math.min(999, Math.max(0, Math.floor(Number(record.hitCount) || 0)) + 1),
    };
    saveNavigationUsage(usage);
    return usage[key];
  }

  function rememberActiveModuleNavigationUsage(configs) {
    (configs || []).some(function rememberActiveConfig(config) {
      if (!config || !config.active) return false;
      rememberModuleNavigationUsage(config, { clicked: false });
      return true;
    });
  }

  function getModuleNavigationNumericCount(config) {
    if (!config) return 0;
    var direct = Number(config.count);
    if (isFinite(direct) && direct > 0) return direct;
    var match = String(config.countText === undefined ? '' : config.countText).match(/\d+/);
    return match ? Number(match[0]) : 0;
  }

  function getNavigationRecencyScore(timestamp, now) {
    var currentTime = now === undefined ? Date.now() : Number(now);
    var time = Number(timestamp) || 0;
    if (!time || time > currentTime) return 0;
    var ageHours = (currentTime - time) / (60 * 60 * 1000);
    if (ageHours <= 1) return 90;
    if (ageHours <= 24) return 72;
    if (ageHours <= 7 * 24) return 54;
    if (ageHours <= 30 * 24) return 28;
    return 8;
  }

  function getModuleNavigationSmartScore(config, usageMap, now) {
    var data = config || {};
    var usage = usageMap || {};
    var usageKey = getModuleNavigationUsageKey(data);
    var record = usageKey ? usage[usageKey] : null;
    var score = 0;
    if (data.active) score += 100000;
    if (data.pinned || data.section === '置顶导航') score += 50000;
    if (String(data.className || '').split(/\s+/).indexOf('spx-module-nav-workbench') !== -1) score += 90;
    if (record) {
      score += getNavigationRecencyScore(Math.max(Number(record.clickedAt) || 0, Number(record.usedAt) || 0), now);
      score += Math.min(80, Math.max(0, Number(record.hitCount) || 0) * 8);
    }
    score += Math.min(60, getModuleNavigationNumericCount(data));
    return score;
  }

  function getModuleNavigationNodeSmartScore(node, usageMap, now) {
    var ownScore = node && node.config ? getModuleNavigationSmartScore(node.config, usageMap, now) : 0;
    var childScore = (node && node.children || []).reduce(function getBestChildScore(best, child) {
      return Math.max(best, getModuleNavigationNodeSmartScore(child, usageMap, now));
    }, 0);
    return Math.max(ownScore, childScore);
  }

  function sortModuleNavigationNodeList(nodes, usageMap, now) {
    return (nodes || []).map(function mapNavigationNode(node, index) {
      if (node && node.children && node.children.length) {
        node.children = sortModuleNavigationNodeList(node.children, usageMap, now);
      }
      return {
        index: index,
        order: Number(node && node.config && node.config.order) || 0,
        score: getModuleNavigationNodeSmartScore(node, usageMap, now),
        node: node,
      };
    }).sort(function sortNavigationNodes(left, right) {
      if (right.score !== left.score) return right.score - left.score;
      if (left.order !== right.order) return left.order - right.order;
      return left.index - right.index;
    }).map(function unwrapNavigationNode(item) {
      return item.node;
    });
  }

  function sortModuleNavigationTree(groups, settings, usageMap, now) {
    if (settings && settings.smartModuleNavSort === false) return groups || [];
    var usage = usageMap || loadNavigationUsage();
    return (groups || []).map(function sortNavigationGroup(group) {
      group.nodes = sortModuleNavigationNodeList(group.nodes || [], usage, now);
      return group;
    });
  }

  function getModuleNavigationSearchText(config) {
    return compactText([
      config && config.section,
      config && config.parentLabel,
      config && config.label,
      config && config.title,
    ].join(' '));
  }

  function appendModuleNavigationItem(container, config, active, level) {
    var item = createEl(config.href ? 'a' : 'button', 'spx-module-nav-item');
    item.classList.add('spx-module-nav-level-' + (level || 2));
    if (config.className) {
      String(config.className).split(/\s+/).filter(Boolean).forEach(function addNavigationClass(className) {
        item.classList.add(className);
      });
    }
    item.classList.toggle('spx-module-nav-pinned', !!config.pinned);
    if (config.href) {
      item.href = config.href;
    } else {
      item.type = 'button';
    }
    item.title = config.title || config.label || '';
    item.dataset.spxModuleNavText = getModuleNavigationSearchText(config);
    if (config.panelId && String(config.className || '').split(/\s+/).indexOf('spx-module-nav-workbench') !== -1) {
      item.dataset.spxWorkbenchNavPanel = config.panelId;
    }
    item.appendChild(createEl('span', 'spx-module-nav-label', config.label));
    if (!config.navigationOnly && (config.countText !== undefined || config.count !== undefined)) {
      item.appendChild(createEl('span', 'spx-module-nav-count', String(config.countText !== undefined ? config.countText : config.count)));
    }
    if (config.pinKey) item.appendChild(createModuleNavigationPin(config));
    item.classList.toggle('spx-active', !!active);
    item.addEventListener('click', function activateModuleNav(event) {
      var nav = item.closest ? item.closest('.spx-module-nav') : null;
      rememberModuleNavigationUsage(config, { clicked: true });
      if (String(config.className || '').split(/\s+/).indexOf('spx-module-nav-workbench') === -1) {
        hideWorkbenchPanel();
      }
      if (typeof config.onClick === 'function') {
        event.preventDefault();
        config.onClick(event);
      }
      if (nav) setModuleNavActive(nav, item);
      if (config.target && config.target.scrollIntoView) {
        config.target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }
    });
    container.appendChild(item);
  }

  function filterModuleNavigation(nav, query) {
    if (!nav) return;
    var term = compactText(query).toLowerCase();
    var hiddenClass = 'spx-module-nav-search-hidden';
    qsa('.' + hiddenClass, nav).forEach(function clearPreviousSearch(node) {
      node.classList.remove(hiddenClass);
    });
    if (!term) {
      delete nav.dataset.spxModuleNavSearching;
      return;
    }
    nav.dataset.spxModuleNavSearching = '1';
    qsa('.spx-module-nav-item,.spx-module-nav-parent-title', nav).forEach(function toggleSearchItem(item) {
      var text = compactText(item.dataset.spxModuleNavText || item.textContent).toLowerCase();
      item.classList.toggle(hiddenClass, text.indexOf(term) === -1);
    });
    qsa('.spx-module-nav-node', nav).reverse().forEach(function toggleSearchNode(node) {
      var own = node.firstElementChild;
      var hasVisibleItem = qsa('.spx-module-nav-item:not(.' + hiddenClass + '),.spx-module-nav-parent-title:not(.' + hiddenClass + ')', node).length > 0;
      if (hasVisibleItem && own && own.classList && own.classList.contains(hiddenClass)) {
        own.classList.remove(hiddenClass);
      }
      node.classList.toggle(hiddenClass, !hasVisibleItem);
    });
    qsa('.spx-module-nav-group', nav).forEach(function toggleSearchGroup(group) {
      var hasVisibleNode = qsa('.spx-module-nav-node:not(.' + hiddenClass + ')', group).length > 0;
      group.classList.toggle(hiddenClass, !hasVisibleNode);
    });
  }

  function createModuleNavigationSearch(nav) {
    var controls = createEl('div', 'spx-module-nav-controls');
    var search = createEl('input', 'spx-module-nav-search');
    search.type = 'search';
    search.placeholder = '搜索导航';
    search.setAttribute('aria-label', '搜索导航');
    search.addEventListener('input', function handleModuleNavigationSearchInput() {
      filterModuleNavigation(nav, search.value);
    });
    controls.appendChild(search);
    return controls;
  }

  function createModuleNavigationTitle(title, count, smartSort) {
    var node = createEl('div', 'spx-module-nav-title');
    node.appendChild(createEl('strong', '', title || '导航中心'));
    node.appendChild(createEl('span', '', (Number(count) || 0) + ' 项' + (smartSort ? ' · 智能' : '')));
    return node;
  }

  function getModuleNavigationHost() {
    return qs('#content') || qs('#main') || qs('#wrapA');
  }

  function createModuleNavigationNode(config) {
    return {
      key: getNavigationItemKey(config.section, config.label, config.href || config.parentLabel || ''),
      label: config.label,
      config: config,
      children: [],
      childKeys: {},
    };
  }

  function dedupeModuleNavigationTree(groups) {
    var topLevelLabels = {};
    var childLabels = {};
    (groups || []).forEach(function collectTopLevelLabels(group) {
      (group.nodes || []).forEach(function collectNodeLabel(node) {
        var label = normalizeNavigationLabel(node && node.label).toLowerCase();
        if (label) topLevelLabels[label] = true;
      });
    });
    (groups || []).forEach(function pruneGroupChildren(group) {
      (group.nodes || []).forEach(function pruneNodeChildren(node) {
        node.children = (node.children || []).filter(function keepUniqueChild(child) {
          var label = normalizeNavigationLabel(child && child.label).toLowerCase();
          if (!label || topLevelLabels[label] || childLabels[label]) return false;
          childLabels[label] = true;
          return true;
        });
      });
    });
    return groups;
  }

  function buildModuleNavigationTree(configs) {
    var groups = [];
    var groupMap = {};
    (configs || []).forEach(function appendTreeConfig(config) {
      var section = config.section || '导航';
      if (!groupMap[section]) {
        groupMap[section] = {
          label: section,
          nodes: [],
          nodeMap: {},
          parentMap: {},
        };
        groups.push(groupMap[section]);
      }
      var group = groupMap[section];
      var parentLabel = normalizeNavigationLabel(config.parentLabel);
      var label = normalizeNavigationLabel(config.label);
      var labelKey = label.toLowerCase();
      if (parentLabel && parentLabel !== label) {
        var parentMapKey = parentLabel.toLowerCase();
        var parentKey = 'parent|' + parentMapKey;
        var parentNode = group.parentMap[parentMapKey] || group.nodeMap[parentKey];
        if (!parentNode) {
          parentNode = {
            key: parentKey,
            label: parentLabel,
            config: null,
            children: [],
            childKeys: {},
          };
          group.nodeMap[parentKey] = parentNode;
          group.parentMap[parentMapKey] = parentNode;
          group.nodes.push(parentNode);
        }
        var childKey = label.toLowerCase();
        if (parentNode.childKeys[childKey] === undefined) {
          parentNode.childKeys[childKey] = parentNode.children.length;
          parentNode.children.push(createModuleNavigationNode(config));
        } else {
          var childIndex = parentNode.childKeys[childKey];
          parentNode.children[childIndex] = createModuleNavigationNode(
            Object.assign({}, parentNode.children[childIndex].config || {}, config)
          );
        }
        return;
      }

      var key = getNavigationItemKey(section, label, config.href || label);
      var existingParentNode = group.parentMap[labelKey];
      if (existingParentNode) {
        existingParentNode.config = Object.assign({}, existingParentNode.config || {}, config);
        existingParentNode.label = label;
        group.nodeMap[key] = existingParentNode;
        return;
      }
      if (group.nodeMap[key]) {
        group.nodeMap[key].config = Object.assign({}, group.nodeMap[key].config || {}, config);
        if (!group.parentMap[labelKey]) group.parentMap[labelKey] = group.nodeMap[key];
        return;
      }
      group.nodeMap[key] = createModuleNavigationNode(config);
      group.parentMap[labelKey] = group.nodeMap[key];
      group.nodes.push(group.nodeMap[key]);
    });
    return dedupeModuleNavigationTree(groups);
  }

  function appendModuleNavigationNode(nav, parent, node, hasActive, state) {
    var wrapper = createEl('div', 'spx-module-nav-node');
    var active = !!(node.config && node.config.active);
    if (node.config) {
      appendModuleNavigationItem(wrapper, node.config, active || (!hasActive && state.index === 0), node.children.length ? 2 : 2);
      state.index += 1;
    } else {
      var parentTitle = createEl('div', 'spx-module-nav-parent-title', node.label);
      parentTitle.dataset.spxModuleNavText = compactText(node.label);
      wrapper.appendChild(parentTitle);
    }
    if (node.children.length) {
      var children = createEl('div', 'spx-module-nav-children');
      node.children.forEach(function appendChildNavigation(childNode) {
        appendModuleNavigationItem(children, childNode.config, !!childNode.config.active, 3);
        state.index += 1;
      });
      wrapper.appendChild(children);
    }
    parent.appendChild(wrapper);
  }

  function setModuleNavigationGroupCollapsed(groupNode, button, label, collapsed) {
    if (!groupNode || !button) return;
    groupNode.classList.toggle('spx-module-nav-collapsed', !!collapsed);
    button.setAttribute('aria-expanded', String(!collapsed));
    button.title = (collapsed ? '展开 ' : '折叠 ') + label;
  }

  function appendModuleNavigationSection(groupNode, label, collapseState) {
    var button = createEl('button', 'spx-module-nav-section', label);
    var key = getModuleNavigationGroupKey(label);
    var collapsed = isModuleNavigationGroupCollapsed(collapseState, label);
    button.type = 'button';
    button.dataset.spxModuleNavGroup = key;
    setModuleNavigationGroupCollapsed(groupNode, button, label, collapsed);
    button.addEventListener('click', function toggleModuleNavigationGroup() {
      var nextCollapsed = !isModuleNavigationGroupCollapsed(collapseState, label);
      if (nextCollapsed) {
        collapseState[key] = true;
      } else {
        delete collapseState[key];
      }
      saveNavigationCollapseState(collapseState);
      setModuleNavigationGroupCollapsed(groupNode, button, label, nextCollapsed);
    });
    groupNode.appendChild(button);
  }

  function mountModuleNavigation(title, configs, settings) {
    restoreModuleNavigation();
    var content = getModuleNavigationHost();
    var navSettings = normalizeSettings(settings);
    var visibleConfigs = (configs || []).filter(function keepModuleConfig(config) {
      return config && config.label && (
        config.count ||
        config.count === 0 ||
        config.countText !== undefined ||
        config.alwaysShow ||
        config.href ||
        config.onClick ||
        config.target
      );
    });
    if (!content || !visibleConfigs.length) return null;

    var nav = createEl('nav', 'spx-module-nav');
    nav.id = 'spx-module-nav';
    nav.dataset.spxModuleNavSort = navSettings.smartModuleNavSort === false ? 'default' : 'smart';
    nav.setAttribute('aria-label', title || '导航中心');
    nav.appendChild(createModuleNavigationTitle(title || '导航中心', visibleConfigs.length, navSettings.smartModuleNavSort !== false));
    nav.appendChild(createModuleNavigationSearch(nav));
    var hasActive = visibleConfigs.some(function hasActiveConfig(config) {
      return !!config.active;
    });
    var state = { index: 0 };
    rememberActiveModuleNavigationUsage(visibleConfigs);
    var groups = sortModuleNavigationTree(buildModuleNavigationTree(visibleConfigs), navSettings);
    var collapseState = applyInitialModuleNavigationCollapseState(groups, loadNavigationCollapseState());
    groups.forEach(function appendGroup(group) {
      var groupNode = createEl('div', 'spx-module-nav-group');
      appendModuleNavigationSection(groupNode, group.label, collapseState);
      group.nodes.forEach(function appendNode(node) {
        appendModuleNavigationNode(nav, groupNode, node, hasActive, state);
      });
      nav.appendChild(groupNode);
    });
    var body = createEl('div', 'spx-module-body');
    while (content.firstChild) {
      body.appendChild(content.firstChild);
    }
    content.classList.add('spx-module-nav-host');
    content.appendChild(nav);
    content.appendChild(body);
    document.documentElement.classList.add('spx-module-nav-ready');
    return nav;
  }

  function queueModuleNavigationConfigs(configs) {
    var nextConfigs = configs || [];
    pendingModuleNavigationConfigs = pendingModuleNavigationConfigs.concat(nextConfigs);
    rememberModuleNavigationConfigs(nextConfigs);
  }

  function getHomeNavigationBaseUrl(origin) {
    return String(origin || location.origin || '') + '/index.php';
  }

  function getSeedModuleNavigationConfigs(origin) {
    var homeUrl = getHomeNavigationBaseUrl(origin);
    return [
      { label: '漫区特设', href: homeUrl + '#spx-module-target-home-1', order: 10 },
      { label: '蜜柑计划', href: homeUrl + '#spx-module-target-home-2', order: 20 },
      { label: '综合交流', href: homeUrl + '#spx-module-target-home-3', order: 30 },
      { label: '人民囧府', href: homeUrl + '#spx-module-target-home-4', order: 40 },
    ].map(function mapSeedNavigation(item) {
      return {
        section: '子栏目',
        label: item.label,
        href: item.href,
        title: item.label,
        alwaysShow: true,
        navigationOnly: true,
        order: item.order,
      };
    });
  }

  function rememberModuleNavigationConfigs(configs) {
    var source = configs || [];
    if (!source.length) return false;
    var pool = loadNavigationPool();
    var changed = false;
    var now = Date.now();
    var baseUrl = typeof location !== 'undefined' ? location.href : '';
    var maxOrder = Object.keys(pool).reduce(function getMaxOrder(max, key) {
      return Math.max(max, Number(pool[key] && pool[key].order) || 0);
    }, 0);
    source.forEach(function rememberConfig(config, index) {
      var normalized = normalizePersistentNavigationConfig(config, baseUrl, maxOrder + index + 1);
      if (!normalized) return;
      if (!isPersistentNavigationSection(normalized.section)) return;
      normalized.updatedAt = now;
      var key = getNavigationItemKey(normalized.section, normalized.label, normalized.href);
      var existing = pool[key];
      if (existing) {
        normalized.order = existing.order || normalized.order;
      }
      if (!existing || JSON.stringify(existing) !== JSON.stringify(normalized)) {
        pool[key] = normalized;
        changed = true;
      }
    });
    if (changed) saveNavigationPool(pool);
    return changed;
  }

  function getPersistentModuleNavigationConfigs() {
    var sectionOrder = {
      子栏目: 1,
      版块导航: 2,
    };
    var pool = loadNavigationPool();
    var poolItems = Object.keys(pool).map(function mapPoolItem(key) {
      return pool[key];
    }).filter(function keepPersistentPoolSection(item) {
      return item && isPersistentNavigationSection(item.section);
    });
    return getSeedModuleNavigationConfigs(location.origin)
      .concat(poolItems)
      .map(function mapPersistentNavigation(item) {
        return {
          section: item.section || '子栏目',
          parentLabel: item.parentLabel || '',
          label: item.label,
          href: item.href,
          title: item.title || item.label,
          className: item.className || 'spx-module-nav-persisted',
          active: isModuleNavigationConfigActive(item, location.href),
          alwaysShow: true,
          navigationOnly: true,
          order: Number(item.order) || 0,
          updatedAt: Number(item.updatedAt) || 0,
        };
      })
      .sort(function sortPersistentNavigation(left, right) {
        var leftSection = sectionOrder[left.section] || 99;
        var rightSection = sectionOrder[right.section] || 99;
        if (leftSection !== rightSection) return leftSection - rightSection;
        return (left.order || 0) - (right.order || 0) || String(left.label).localeCompare(String(right.label));
      });
  }

  function mergeModuleNavigationConfigs(configs) {
    var result = [];
    var positions = {};
    (configs || []).forEach(function mergeNavigationConfig(config) {
      if (!config || !config.label) return;
      var label = normalizeNavigationLabel(config.label);
      if (!shouldKeepNavigationLabel(label, { allowForumViewSwitch: !!config.allowForumViewSwitch })) return;
      var href = normalizeNavigationHref(config.href || '', location.href);
      var section = config.section || '';
      var parentLabel = normalizeNavigationLabel(config.parentLabel || '');
      var key = [section, parentLabel, label, (href || '').replace(/#.*$/, '') || config.target || config.onClick || result.length].join('|');
      var nextConfig = Object.assign({}, config, {
        parentLabel: parentLabel,
        label: label,
        href: href || config.href,
        active: !!config.active || isModuleNavigationConfigActive(Object.assign({}, config, { href: href || config.href }), location.href),
        alwaysShow: config.alwaysShow !== false,
        navigationOnly: config.navigationOnly !== false,
      });
      if (positions[key] !== undefined) {
        result[positions[key]] = Object.assign({}, result[positions[key]], nextConfig);
        return;
      }
      positions[key] = result.length;
      result.push(nextConfig);
    });
    return result;
  }

  function getPinnedModuleNavigationClassName(config) {
    var classNames = String(config && config.className || '').split(/\s+/).filter(Boolean);
    if (classNames.indexOf('spx-module-nav-pinned') === -1) classNames.push('spx-module-nav-pinned');
    return classNames.join(' ');
  }

  function withPinnedModuleNavigationConfigs(configs) {
    var pins = loadNavigationPins();
    var pinnedConfigs = [];
    var sourceConfigs = (configs || []).map(function markPinnedNavigationConfig(config) {
      var pinKey = getModuleNavigationConfigPinKey(config);
      var pinned = !!(pinKey && pins[pinKey]);
      var nextConfig = Object.assign({}, config, {
        pinKey: pinKey,
        pinned: pinned,
      });
      if (pinned && nextConfig.href) {
        pinnedConfigs.push(Object.assign({}, nextConfig, {
          section: '置顶导航',
          parentLabel: '',
          title: (nextConfig.title || nextConfig.label) + ' · 置顶',
          className: getPinnedModuleNavigationClassName(nextConfig),
          active: !!nextConfig.active,
          alwaysShow: true,
          navigationOnly: true,
        }));
      }
      return nextConfig;
    });
    return pinnedConfigs.concat(sourceConfigs);
  }

  function collectHomeNavigationConfigsFromDocument(root, pageUrl) {
    var baseUrl = normalizeNavigationHref(pageUrl || getHomeNavigationBaseUrl(location.origin), location.href);
    return qsa('#content .t[id^="t_"]', root).map(function mapHomeDocumentModule(module, index) {
      var header = qs('.h', module) || qs('h2', module);
      var moduleId = module.id || 'spx-module-target-home-' + (index + 1);
      return {
        section: '子栏目',
        label: getCompactNodeText(header, '模块 ' + (index + 1)).slice(0, 18),
        href: baseUrl.replace(/#.*$/, '') + '#' + moduleId,
        title: getCompactNodeText(header, '模块 ' + (index + 1)).slice(0, 18),
        alwaysShow: true,
        navigationOnly: true,
      };
    });
  }

  function collectForumNavigationConfigsFromDocument(root, pageUrl) {
    var currentFid = getCurrentForumId(pageUrl || location.href);
    var seen = {};
    var configs = [];
    qsa('a[href*="thread.php?fid"]', root).forEach(function collectFetchedForumLink(link) {
      if (!isUsefulForumNavigationLink(link, currentFid)) return;
      var href = normalizeNavigationHref(link.getAttribute('href') || link.href, pageUrl || location.href);
      var fid = getCurrentForumId(href);
      var label = normalizeNavigationLabel(link.textContent);
      var parentLabel = getForumNavigationParentLabel(link);
      var key = [parentLabel, fid || href].join('|');
      if (!label || !href || seen[key]) return;
      seen[key] = true;
      configs.push({
        section: '子栏目',
        parentLabel: parentLabel,
        label: label,
        href: href,
        title: label,
        alwaysShow: true,
        navigationOnly: true,
      });
    });
    return configs;
  }

  function collectRemoteNavigationConfigsFromHtml(html, pageUrl) {
    if (!html || typeof DOMParser === 'undefined') return [];
    var doc = new DOMParser().parseFromString(html, 'text/html');
    return collectHomeNavigationConfigsFromDocument(doc, pageUrl)
      .concat(collectForumNavigationConfigsFromDocument(doc, pageUrl));
  }

  function getNavigationDiscoveryUrls() {
    var seen = {};
    function append(url) {
      var href = normalizeNavigationHref(url, location.href).replace(/#.*$/, '');
      try {
        if (!href || seen[href] || new URL(href, location.href).origin !== location.origin) return;
      } catch (error) {
        return;
      }
      seen[href] = true;
    }
    append(getHomeNavigationBaseUrl(location.origin));
    if (detectPageType(location.href) === 'forum') append(location.href);
    qsa('.bdbA a[href*="thread.php?fid"], #breadcrumbs a[href*="thread.php?fid"], .crumbs-item[href*="thread.php?fid"], .crumbs-item a[href*="thread.php?fid"]').forEach(function collectCrumbDiscoveryUrl(link) {
      append(link.href || link.getAttribute('href'));
    });
    return Object.keys(seen).slice(0, 4);
  }

  function remountGlobalModuleNavigation() {
    var settings = loadSettings();
    mountModuleNavigation('导航中心', getAllModuleNavigationConfigs(settings, getWorkbenchState()), settings);
  }

  function scheduleNavigationPoolRefresh(settings) {
    if (testMode || typeof window === 'undefined' || typeof window.fetch !== 'function') return;
    var urls = getNavigationDiscoveryUrls();
    if (isNetworkFriendlyMode(settings)) {
      urls = urls.slice(0, 2);
      if (!shouldRefreshNavigationPool(urls)) return;
    }
    var refreshKey = urls.join('|');
    if (!urls.length || document.documentElement.dataset.spxNavigationRefreshKey === refreshKey) return;
    document.documentElement.dataset.spxNavigationRefreshKey = refreshKey;
    var navigationPolicy = {
      mode: 'background',
      label: '导航中心后台刷新',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    var navigationRateLimited = false;
    urls.reduce(function chainNavigationFetch(chain, url) {
      return chain.then(function fetchNavigationUrl(results) {
        return requestWithPolicy(url, {
          credentials: 'include',
          cache: 'force-cache',
        }, navigationPolicy)
          .then(function readNavigationResponse(response) {
            if (!response.ok) return '';
            return readScriptResponseText(response, navigationPolicy);
          })
          .then(function collectFetchedNavigation(html) {
            results.push(collectRemoteNavigationConfigsFromHtml(html, url));
            return results;
          })
          .catch(function ignoreNavigationFetchFailure(error) {
            if (error && error.spxRateLimited) navigationRateLimited = true;
            results.push([]);
            return results;
          });
      });
    }, Promise.resolve([])).then(function applyFetchedNavigation(results) {
      var configs = [];
      results.forEach(function concatNavigationItems(items) {
        configs = configs.concat(items || []);
      });
      if (!navigationRateLimited) rememberNavigationPoolRefresh(urls);
      if (rememberModuleNavigationConfigs(configs)) remountGlobalModuleNavigation();
    });
  }

  function createHomeModuleNavigation(modules) {
    var configs = (modules || []).map(function mapHomeModule(module, index) {
      var header = qs('.h', module) || qs('h2', module);
      var id = ensureModuleTargetId(module, 'home', index);
      var label = getCompactNodeText(header, '模块 ' + (index + 1)).slice(0, 18);
      return {
        section: '子栏目',
        label: label,
        href: getHomeNavigationBaseUrl(location.origin) + '#' + id,
        title: label,
        target: module,
        alwaysShow: true,
        navigationOnly: true,
      };
    });
    queueModuleNavigationConfigs(configs);
  }

  function getForumBoardTitle() {
    var currentCrumb = qs('.bdbA .crumbs-item.current');
    var title = getCompactNodeText(currentCrumb, '').replace(/\[.*?\]/g, '').trim();
    if (title) return title.slice(0, 24);
    var documentTitle = (document.title || '').replace(/\s*[-|].*$/, '').trim();
    return documentTitle || '全部主题';
  }

  function getForumPostLinkConfig(root, fid) {
    var currentFid = String(fid || getCurrentForumId(location.href) || '').replace(/\D/g, '');
    if (!currentFid) return null;
    var scope = root || document;
    var postPattern = new RegExp('post\\.php\\?fid[-=]' + currentFid + '(?:\\.html|$|[&#])', 'i');
    var baseUrl = typeof location !== 'undefined' && location.href ? location.href : 'https://south-plus.org/';
    var links = qsa('a[href*="post.php?fid"]', scope);
    for (var index = 0; index < links.length; index += 1) {
      var rawHref = links[index].getAttribute('href') || links[index].href || '';
      if (!rawHref || /special[-=]/i.test(rawHref) || !postPattern.test(rawHref)) continue;
      var href = normalizeNavigationHref(rawHref, baseUrl);
      if (!href) continue;
      var image = links[index] && typeof links[index].querySelector === 'function' ? qs('img', links[index]) : null;
      var imageSrc = image && normalizeNavigationHref(image.getAttribute('src') || image.src || '', baseUrl);
      var config = { label: '发布新帖', href: href, title: '在当前版块发布新帖' };
      if (imageSrc) config.imageSrc = imageSrc;
      return config;
    }
    return null;
  }

  function createForumSectionTitle(items, target) {
    var table = target && target.closest ? target.closest('.spx-thread-list-table') || target : target;
    if (!table || !table.parentNode) return null;
    qsa('.spx-forum-section-title').forEach(function removeOldTitle(node) {
      node.remove();
    });
    var title = createEl('div', 'spx-forum-section-title');
    var main = createEl('div', 'spx-forum-section-main');
    var currentFid = getCurrentForumId(location.href);
    var postConfig = getForumPostLinkConfig(document, currentFid);
    var galleryConfig = collectForumViewNavigationConfigs(table ? [table, document] : [document], currentFid)[0];
    main.appendChild(createEl('strong', '', getForumBoardTitle()));
    main.appendChild(createEl('span', '', '当前定位 · ' + (items ? items.length : 0) + ' 个帖子'));
    title.appendChild(main);
    var actions = createEl('div', 'spx-forum-section-actions');
    if (postConfig && postConfig.href) {
      var postLink = createEl('a', 'spx-forum-post-link', postConfig.imageSrc ? '' : (postConfig.label || '发布新帖'));
      postLink.href = postConfig.href;
      postLink.title = postConfig.title || '发布新帖';
      if (postConfig.imageSrc) {
        var postImage = createEl('img');
        postImage.src = postConfig.imageSrc;
        postImage.alt = postConfig.label || '发布新帖';
        postLink.appendChild(postImage);
      }
      actions.appendChild(postLink);
    }
    if (galleryConfig && galleryConfig.href) {
      var galleryLink = createEl('a', 'spx-forum-gallery-link', galleryConfig.label || '图墙模式');
      galleryLink.href = galleryConfig.href;
      if (galleryConfig.label === '列表模式') galleryLink.classList.add('spx-forum-list-link');
      galleryLink.title = galleryConfig.title || (galleryConfig.label === '列表模式' ? '切换回列表模式' : '点击进入图墙模式');
      actions.appendChild(galleryLink);
    }
    if (actions.childNodes.length) title.appendChild(actions);
    table.parentNode.insertBefore(title, table);
    return title;
  }

  function compactForumPrelude(threadTable) {
    var table = threadTable && threadTable.closest ? threadTable.closest('.spx-thread-list-table') || threadTable : threadTable;
    var content = qs('#content') || qs('#main');
    if (!content || !table) return;
    var node = content.firstElementChild;
    while (node && node !== table) {
      var next = node.nextElementSibling;
      if (
        node.id !== 'spx-forum-tools' &&
        !node.classList.contains('spx-forum-section-title') &&
        !node.classList.contains('spx-module-nav')
      ) {
        node.classList.add('spx-forum-prelude-hidden');
      }
      node = next;
    }
  }

  function getForumNavigationScopeNodes(threadTable) {
    var content = qs('#content') || qs('#main');
    if (!content) return [];
    if (!threadTable) return [content];
    var nodes = [];
    var node = content.firstElementChild;
    while (node && node !== threadTable) {
      if (
        node.id !== 'spx-forum-tools' &&
        !node.classList.contains('spx-forum-section-title') &&
        !node.classList.contains('spx-module-nav')
      ) {
        nodes.push(node);
      }
      node = node.nextElementSibling;
    }
    return nodes;
  }

  function normalizeNavigationLabel(text) {
    return String(text || '')
      .replace(/\s+/g, ' ')
      .replace(/\s*\(\d+\)\s*$/, '')
      .trim();
  }

  function getCommonForumNavigationLabelByFid() {
    return {
      9: '茶馆',
      48: '询问&求物',
      13: '免空资源区',
      128: '同人音声',
      221: 'GALGAME汉化区',
      208: 'AI交流',
    };
  }

  function getCanonicalForumNavigationLabel(label, href) {
    var fid = getCurrentForumId(href);
    var fidMap = getCommonForumNavigationLabelByFid();
    return fid && fidMap[fid] ? fidMap[fid] : '';
  }

  function isGlobalSiteNavigationTarget(label, href) {
    var normalized = normalizeNavigationLabel(label);
    var fid = getCurrentForumId(href);
    var fidMap = getCommonForumNavigationLabelByFid();
    if (fid && fidMap[fid] && normalizeNavigationLabel(fidMap[fid]) === normalized) return true;
    return normalized === '最新帖子' && /search2?\.php/.test(String(href || '')) && /newatc/.test(String(href || ''));
  }

  function getHomeNavigationParentLabel(link) {
    var module = link && link.closest ? link.closest('.spx-home-module,.t[id^="t_"]') : null;
    if (!module) return '';
    var header = qs('.h', module) || qs('h2', module);
    return normalizeNavigationLabel(getCompactNodeText(header, '')).slice(0, 32);
  }

  function getForumNavigationParentLabel(link) {
    var ownLabel = normalizeNavigationLabel(link && link.textContent);
    var row = link && link.closest ? link.closest('tr') : null;
    var isRowTitle = false;
    var selectors = [
      'a[id^="a_ajax_"]',
      '[id^="fn_"] h3 a',
      '[id^="fn_"] > a',
      '[id^="fn_"] a[href*="thread.php?fid"]',
    ];
    if (row) {
      for (var index = 0; index < selectors.length; index += 1) {
        var candidate = qs(selectors[index], row);
        var label = normalizeNavigationLabel(candidate && candidate.textContent);
        if (label && label === ownLabel) isRowTitle = true;
        if (label && label !== ownLabel) return label.slice(0, 32);
      }
    }
    if (isRowTitle) return '';
    var homeParent = getHomeNavigationParentLabel(link);
    if (homeParent && homeParent !== ownLabel) return homeParent;
    return '';
  }

  function isUsefulForumNavigationLink(link, currentFid) {
    if (!link) return false;
    var href = String(link.getAttribute('href') || link.href || '');
    var label = normalizeNavigationLabel(link.textContent);
    if (!href || !/thread\.php\?fid[=-]?\d+/.test(href)) return false;
    if (/skinco|page-\d+|search|rss/i.test(href)) return false;
    if (!shouldKeepNavigationLabel(label) || label.length > 28) return false;
    if (/^(折叠|展开|版块|最后发表|主题\/文章|版主|上一页|下一页|首页)$/.test(label)) return false;
    if (isGlobalSiteNavigationTarget(label, href)) return false;
    return getCurrentForumId(href) !== String(currentFid || '');
  }

  function getForumViewNavigationLabel(label) {
    var normalized = normalizeNavigationLabel(label);
    return /图墙模式/.test(normalized) ? '图墙模式' : normalized;
  }

  function appendForumViewNavigationConfig(configs, seen, label, href, title, active) {
    var normalizedLabel = normalizeNavigationLabel(label);
    var normalizedHref = normalizeNavigationHref(href, location.href);
    var key = normalizedHref.replace(/#.*$/, '');
    if (!normalizedLabel || !normalizedHref || seen[key]) return;
    seen[key] = true;
    configs.push({
      section: '版块导航',
      parentLabel: getForumBoardTitle(),
      label: normalizedLabel,
      href: normalizedHref,
      title: normalizeNavigationLabel(title) || normalizedLabel,
      className: 'spx-module-nav-forum-view',
      active: !!active,
      alwaysShow: true,
      navigationOnly: true,
      transient: true,
      allowForumViewSwitch: true,
    });
  }

  function isForumGalleryModeLink(link, currentFid) {
    if (!link) return false;
    var href = String(link.getAttribute('href') || link.href || '');
    var label = normalizeNavigationLabel(link.textContent);
    var fid = getCurrentForumId(href);
    if (!href || !isForumGalleryModeUrl(href) || !/图墙模式/.test(label)) return false;
    return !currentFid || !fid || fid === String(currentFid);
  }

  function collectForumViewNavigationConfigs(scopeNodes, currentFid) {
    var configs = [];
    var seen = {};
    if (isForumGalleryModeUrl(location.href)) {
      appendForumViewNavigationConfig(configs, seen, '列表模式', getForumListModeUrl(location.href), '切换回列表模式', false);
    }
    (scopeNodes || []).forEach(function collectForumViewLinks(scope) {
      qsa('a[href*="thread_new.php"]', scope).forEach(function appendForumViewLink(link) {
        if (!isForumGalleryModeLink(link, currentFid)) return;
        var href = normalizeNavigationHref(link.getAttribute('href') || link.href, location.href);
        var label = getForumViewNavigationLabel(link.textContent);
        appendForumViewNavigationConfig(configs, seen, label, href, normalizeNavigationLabel(link.textContent) || label, isForumGalleryModeUrl(location.href));
      });
    });
    return configs;
  }

  function getCurrentForumViewNavigationConfigs() {
    if (detectPageType(location.href) !== 'forum') return [];
    return collectForumViewNavigationConfigs([document], getCurrentForumId(location.href));
  }

  function createForumModuleNavigation(items) {
    if (!items || !items.length) return;
    var listRoot = items[0].row && items[0].row.closest ? items[0].row.closest('.spx-thread-list-table') || items[0].row.closest('#wall') : null;
    var currentFid = getCurrentForumId(location.href);
    var seen = {};
    var configs = [];
    var scopeNodes = getForumNavigationScopeNodes(listRoot);
    scopeNodes.forEach(function collectForumNavLinks(scope) {
      qsa('a[href*="thread.php?fid"]', scope).forEach(function appendForumNavLink(link) {
        if (!isUsefulForumNavigationLink(link, currentFid)) return;
        var href = link.href || link.getAttribute('href');
        var fid = getCurrentForumId(href);
        var label = normalizeNavigationLabel(link.textContent);
        var parentLabel = getForumNavigationParentLabel(link);
        var key = [parentLabel, fid || href].join('|');
        if (seen[key]) return;
        seen[key] = true;
        configs.push({
          section: '子栏目',
          parentLabel: parentLabel,
          label: label,
          href: href,
          title: label,
          alwaysShow: true,
          navigationOnly: true,
        });
      });
    });
    configs = configs.concat(collectForumViewNavigationConfigs(scopeNodes.concat(listRoot ? [listRoot] : []), currentFid));
    queueModuleNavigationConfigs(configs);
  }

  function getCommonForumNavigationItems(origin) {
    var base = String(origin || location.origin || '');
    return [
      { label: '首页', href: base + '/index.php' },
      { label: '茶馆', fid: '9' },
      { label: '询问&求物', fid: '48' },
      { label: '免空资源区', fid: '13' },
      { label: '同人音声', fid: '128' },
      { label: 'GALGAME汉化区', fid: '221' },
      { label: 'AI交流', fid: '208' },
      { label: '最新帖子', href: base + '/search2.php?orderway-postdate-asc-desc-newatc-1.html' },
    ].map(function buildForumNavigationItem(item) {
      if (item.href) return item;
      return {
        label: item.label,
        fid: item.fid,
        href: base + '/thread.php?fid-' + item.fid + '.html',
      };
    });
  }

  function getCurrentForumId(url) {
    var text = String(url || '');
    var match = text.match(/fid[=-](\d+)/) || text.match(/fid-(\d+)/);
    return match ? match[1] : '';
  }

  function getNavigationComparableUrl(href, baseUrl, includeHash) {
    if (!href || /^javascript:/i.test(String(href))) return '';
    try {
      var parsed = new URL(String(href), baseUrl || location.href);
      return parsed.origin + parsed.pathname + parsed.search + (includeHash ? parsed.hash : '');
    } catch (error) {
      return '';
    }
  }

  function isModuleNavigationConfigActive(config, url) {
    var href = config && config.href;
    if (!href) return false;
    var currentUrl = String(url || (typeof location !== 'undefined' ? location.href : ''));
    var includeHash = String(href).indexOf('#') !== -1;
    var targetKey = getNavigationComparableUrl(href, currentUrl, includeHash);
    var currentKey = getNavigationComparableUrl(currentUrl, currentUrl, includeHash);
    if (targetKey && currentKey && targetKey === currentKey) return true;
    if (includeHash) return false;
    var currentFid = getCurrentForumId(currentUrl);
    var targetFid = getCurrentForumId(href);
    if (isForumGalleryModeUrl(currentUrl) || isForumGalleryModeUrl(href)) {
      return !!(currentFid && targetFid && currentFid === targetFid && isForumGalleryModeUrl(currentUrl) === isForumGalleryModeUrl(href));
    }
    return !!(currentFid && targetFid && currentFid === targetFid);
  }

  function isCommonNavigationActive(item, url) {
    var text = String(url || '');
    if (item.fid) return getCurrentForumId(text) === String(item.fid);
    return item.label === '最新帖子' && isModuleNavigationConfigActive(item, url);
  }

  function getGlobalSiteNavigationConfigs(url) {
    return getCommonForumNavigationItems(location.origin).map(function mapSiteNavigation(item) {
      return {
        section: '站点导航',
        label: item.label,
        href: item.href,
        title: item.label,
        className: 'spx-module-nav-site',
        active: isCommonNavigationActive(item, url),
        alwaysShow: true,
        navigationOnly: true,
      };
    });
  }

  function getReadPageNavigationConfigs() {
    if (detectPageType(location.href) !== 'read') return [];
    var configs = [];
    var seen = {};
    function appendConfig(section, label, href, options) {
      var normalized = normalizeNavigationLabel(label);
      var url = String(href || '');
      if (!normalized || !url || /^javascript:/i.test(url)) return;
      var key = section + '|' + normalized + '|' + url.replace(/#.*$/, '');
      if (seen[key]) return;
      seen[key] = true;
      var config = {
        section: section,
        label: normalized,
        href: url,
        title: normalized,
        alwaysShow: true,
        navigationOnly: true,
      };
      if (options && options.active) config.active = true;
      if (options && options.className) config.className = options.className;
      configs.push(config);
    }

    var crumbSeen = {};
    var crumbLinks = [];
    qsa('.bdbA a[href*="thread.php?fid"], #breadcrumbs a[href*="thread.php?fid"], .crumbs-item[href*="thread.php?fid"], .crumbs-item a[href*="thread.php?fid"], .breadcrumbs a[href*="thread.php?fid"], #breadCrumb a[href*="thread.php?fid"]').forEach(function collectReadCrumb(link) {
      var label = normalizeNavigationLabel(link.textContent);
      var href = link.href || link.getAttribute('href');
      var fid = getCurrentForumId(href);
      var key = fid || href;
      if (!label || !href || !key || crumbSeen[key]) return;
      if (/^(南\+ South Plus|首页)$/.test(label)) return;
      crumbSeen[key] = true;
      crumbLinks.push({ label: label, href: href });
    });
    crumbLinks.forEach(function appendReadCrumb(item, index) {
      appendConfig('当前位置', item.label, item.href, {
        active: index === crumbLinks.length - 1,
        className: 'spx-module-nav-current',
      });
    });
    rememberModuleNavigationConfigs(crumbLinks.map(function mapReadCrumbToPersistentNav(item) {
      return {
        section: '子栏目',
        label: item.label,
        href: item.href,
        title: item.label,
        alwaysShow: true,
        navigationOnly: true,
      };
    }));

    var topicSeen = {};
    qsa('a[href]').forEach(function collectTopicNavigation(link) {
      var rawLabel = normalizeNavigationLabel(link.textContent);
      var label = '';
      if (/上一主题/.test(rawLabel)) label = '上一主题';
      if (/下一主题/.test(rawLabel)) label = '下一主题';
      if (!label || topicSeen[label]) return;
      var href = link.href || link.getAttribute('href');
      if (!href || /^javascript:/i.test(href)) return;
      topicSeen[label] = true;
      appendConfig('主题导航', label, href, {
        className: 'spx-module-nav-topic',
      });
    });
    return configs;
  }

  function getSearchPageNavigationConfigs() {
    if (detectPageType(location.href) !== 'search') return [];
    var isLatest = /search2?\.php/.test(String(location.href || '')) && /newatc/.test(String(location.href || ''));
    return [{
      section: '站点导航',
      label: '搜索',
      href: location.origin + '/search.php',
      title: '搜索',
      className: 'spx-module-nav-search-page',
      active: !isLatest,
      alwaysShow: true,
      navigationOnly: true,
    }];
  }

  function getProfilePageNavigationConfigs() {
    if (!shouldUseProfilePage(location.href)) return [];
    var uid = extractAccountUserId(document, location.href);
    if (!uid) return [];
    var activeKey = getAccountActiveKey(location.href);
    return getAccountTabItems(uid, location.origin).map(function mapProfileListNav(item) {
      var label = item.key === 'topic' ? '我的主题' : (item.key === 'post' ? '我的回复' : item.label);
      return {
        section: '个人导航',
        label: label,
        href: item.href,
        title: label,
        className: 'spx-module-nav-profile',
        active: item.key === activeKey,
        alwaysShow: true,
        navigationOnly: true,
      };
    });
  }

  function getTaskPageNavigationConfigs() {
    if (!shouldUseTaskPage(location.href)) return [];
    return [{
      section: '站点导航',
      label: '社区论坛任务',
      href: location.origin + '/plugin.php?H_name-tasks.html',
      title: '社区论坛任务',
      className: 'spx-module-nav-task',
      active: true,
      alwaysShow: true,
      navigationOnly: true,
    }];
  }

  function getAllModuleNavigationConfigs(settings, state) {
    return withPinnedModuleNavigationConfigs(mergeModuleNavigationConfigs(
      getGlobalSiteNavigationConfigs(location.href)
        .concat(getPersistentModuleNavigationConfigs())
        .concat(getWorkbenchNavigationConfigs(settings, state))
        .concat(getSearchPageNavigationConfigs())
        .concat(getProfilePageNavigationConfigs())
        .concat(getTaskPageNavigationConfigs())
        .concat(getCurrentForumViewNavigationConfigs())
        .concat(getReadPageNavigationConfigs())
        .concat(pendingModuleNavigationConfigs)
    ));
  }

  function createGlobalModuleNavigation(settings, state) {
    if (!shouldUseModuleNavigation(settings, location.href, document)) return;
    mountModuleNavigation('导航中心', getAllModuleNavigationConfigs(settings, state), settings);
    scheduleNavigationPoolRefresh(settings);
  }

  function positionPreviewPanel(panel, event) {
    var margin = 14;
    var left = Math.min((event && event.clientX ? event.clientX + 16 : 24), window.innerWidth - panel.offsetWidth - margin);
    var top = Math.min((event && event.clientY ? event.clientY + 16 : 24), window.innerHeight - panel.offsetHeight - margin);
    panel.style.left = Math.max(margin, left) + 'px';
    panel.style.top = Math.max(margin, top) + 'px';
  }

  function removeThreadPreview() {
    var panel = qs('#spx-preview-popover');
    if (!panel) return;
    if (panel.spxPreviewImageTimer && typeof window !== 'undefined' && typeof window.clearTimeout === 'function') {
      window.clearTimeout(panel.spxPreviewImageTimer);
      panel.spxPreviewImageTimer = null;
    }
    panel.remove();
  }

  function getThreadPreviewUrl(info) {
    return String(info && info.titleLink && info.titleLink.href || info && info.url || '');
  }

  function getThreadPreviewMetaText(info, payload) {
    var status = payload && payload.status
      ? String(payload.status)
      : (payload && payload.cached ? '已缓存' : '悬停预览');
    return (info && info.author ? '作者:' + info.author + ' · ' : '') + status;
  }

  function getThreadPreviewImageUrls(payload) {
    return (payload && payload.images || []).slice(0, THREAD_PREVIEW_IMAGE_LIMIT);
  }

  function getThreadPreviewImageSummary(payload) {
    var count = payload && payload.images ? payload.images.length : 0;
    if (!count) return '没有可预览图片';
    if (count > THREAD_PREVIEW_IMAGE_LIMIT) return '已显示前 ' + THREAD_PREVIEW_IMAGE_LIMIT + ' 张,打开帖子查看其余 ' + (count - THREAD_PREVIEW_IMAGE_LIMIT) + ' 张';
    return '共 ' + count + ' 张预览图';
  }

  function getThreadPreviewStatusChips(info, payload, state) {
    var chips = [];
    var tid = parseThreadId(info && info.id);
    chips.push({ label: tid && isThreadFavoriteSeen(tid) ? '已收藏' : '未收藏', className: tid && isThreadFavoriteSeen(tid) ? 'spx-ok' : '' });
    chips.push({ label: state && state.watch && state.watch[tid] ? '已稍后' : '未稍后', className: state && state.watch && state.watch[tid] ? 'spx-ok' : '' });
    getThreadResourceBadges(info, getThreadResourceBadgeIndex(state && state.resources), payload).forEach(function appendPreviewResourceChip(badge) {
      chips.push({
        label: badge.label,
        className: badge.guessed ? 'spx-guess' : 'spx-resource',
        resourceType: badge.type,
        title: (badge.guessed ? '标题推测:' : '已识别资源:') + badge.label,
      });
    });
    return chips;
  }

  function appendThreadPreviewStatusChips(panel, info, payload, state) {
    var chips = getThreadPreviewStatusChips(info, payload, state);
    if (!chips.length) return;
    var row = createEl('div', 'spx-preview-chip-row');
    chips.forEach(function appendPreviewStatusChip(chip) {
      var node = chip.resourceType
        ? createEl('button', 'spx-preview-chip ' + (chip.className || ''), chip.label)
        : createEl('span', 'spx-preview-chip ' + (chip.className || ''), chip.label);
      if (chip.title) node.title = chip.title;
      if (chip.resourceType) {
        node.type = 'button';
        node.dataset.spxResourceType = chip.resourceType;
        node.addEventListener('click', function filterPreviewResource(event) {
          event.preventDefault();
          event.stopPropagation();
          dispatchResourceBadgeFilter(chip.resourceType);
        });
      }
      row.appendChild(node);
    });
    panel.appendChild(row);
  }

  function getThreadPreviewHoverDelay(settings) {
    return isNetworkFriendlyMode(settings) ? THREAD_PREVIEW_HOVER_DELAY : THREAD_PREVIEW_FAST_HOVER_DELAY;
  }

  function loadThreadPreviewPanelImage(image) {
    if (!image || image.src) return false;
    var src = image.dataset && image.dataset.spxPreviewLazySrc;
    if (!src) return false;
    preparePreviewImageReveal(image);
    image.src = src;
    delete image.dataset.spxPreviewLazySrc;
    if (image.complete && image.naturalWidth) markPreviewImageLoaded(image);
    return true;
  }

  function loadThreadPreviewPanelImages(panel, batchSize) {
    if (!panel || !panel.isConnected) return 0;
    var limit = Math.max(1, Number(batchSize) || THREAD_PREVIEW_IMAGE_BATCH_SIZE);
    var loaded = 0;
    qsa('img[data-spx-preview-lazy-src]', panel).some(function loadPreviewImage(image) {
      if (loaded >= limit) return true;
      if (loadThreadPreviewPanelImage(image)) loaded += 1;
      return false;
    });
    return loaded;
  }

  function scheduleThreadPreviewPanelImages(panel) {
    if (!panel || typeof window === 'undefined' || typeof window.setTimeout !== 'function') return;
    var loadNextBatch = function loadNextThreadPreviewImageBatch() {
      if (!panel.isConnected) return;
      panel.spxPreviewImageTimer = null;
      loadThreadPreviewPanelImages(panel, THREAD_PREVIEW_IMAGE_BATCH_SIZE);
      if (qs('img[data-spx-preview-lazy-src]', panel)) {
        panel.spxPreviewImageTimer = window.setTimeout(loadNextBatch, THREAD_PREVIEW_IMAGE_LOAD_DELAY);
      }
    };
    panel.spxPreviewImageTimer = window.setTimeout(loadNextBatch, THREAD_PREVIEW_IMAGE_LOAD_DELAY);
    panel.addEventListener('mouseenter', function loadVisibleThreadPreviewImages() {
      loadThreadPreviewPanelImages(panel, THREAD_PREVIEW_IMAGE_BATCH_SIZE);
    }, { once: true });
    panel.addEventListener('focusin', function loadFocusedThreadPreviewImages() {
      loadThreadPreviewPanelImages(panel, THREAD_PREVIEW_IMAGE_BATCH_SIZE);
    }, { once: true });
  }

  function saveThreadPreviewWatch(info, state) {
    if (!info || !info.id || !state) return false;
    state.watch = state.watch || {};
    if (state.watch[info.id]) return false;
    state.watch[info.id] = {
      title: info.title,
      url: getThreadPreviewUrl(info),
      savedAt: Date.now(),
    };
    saveMap(WATCH_KEY, state.watch);
    if (info.cell && info.titleLink && !qs('.spx-watch-badge', info.cell)) {
      info.titleLink.insertAdjacentElement('afterend', createWatchBadge(info.id));
    }
    refreshWatchCenter();
    return true;
  }

  function setPreviewActionText(button, text) {
    if (!button) return;
    var previous = button.dataset.spxPreviewOriginalText || button.textContent;
    button.dataset.spxPreviewOriginalText = previous;
    button.textContent = text;
    if (typeof window === 'undefined' || typeof window.setTimeout !== 'function') return;
    window.setTimeout(function restorePreviewActionText() {
      if ('isConnected' in button && !button.isConnected) return;
      button.textContent = button.dataset.spxPreviewOriginalText || previous;
      delete button.dataset.spxPreviewOriginalText;
    }, 1400);
  }

  function appendPreviewActions(panel, info, state, settings) {
    var url = getThreadPreviewUrl(info);
    if (!url) return;
    var actions = createEl('div', 'spx-preview-popover-actions');
    var openLink = createEl('a', '', '打开帖子');
    openLink.href = url;
    openLink.target = '_blank';
    openLink.rel = 'noopener noreferrer';
    actions.appendChild(openLink);

    var watchButton = createEl('button', '', state && state.watch && state.watch[info.id] ? '已稍后' : '加入稍后');
    watchButton.type = 'button';
    watchButton.disabled = !!(state && state.watch && state.watch[info.id]);
    watchButton.addEventListener('click', function addPreviewWatch(event) {
      event.preventDefault();
      event.stopPropagation();
      if (saveThreadPreviewWatch(info, state)) {
        watchButton.textContent = '已加入稍后';
        watchButton.disabled = true;
      } else {
        setPreviewActionText(watchButton, state ? '已稍后' : '无法保存');
      }
    });
    actions.appendChild(watchButton);

    var favoriteButton = createEl('button', '', isThreadFavoriteSeen(info && info.id) ? '已收藏' : '收藏');
    favoriteButton.type = 'button';
    favoriteButton.title = '收藏到站内收藏夹';
    favoriteButton.disabled = isThreadFavoriteSeen(info && info.id);
    favoriteButton.addEventListener('click', function addPreviewFavorite(event) {
      event.preventDefault();
      event.stopPropagation();
      runThreadFavoriteAction(info, settings, state, favoriteButton, '已收藏');
    });
    actions.appendChild(favoriteButton);

    var copyButton = createEl('button', '', '复制链接');
    copyButton.type = 'button';
    copyButton.addEventListener('click', function copyPreviewLink(event) {
      event.preventDefault();
      event.stopPropagation();
      copyTextToClipboard(url).then(
        function markCopied() { setPreviewActionText(copyButton, '已复制'); },
        function markCopyFailed() { setPreviewActionText(copyButton, '复制失败'); }
      );
    });
    actions.appendChild(copyButton);
    panel.appendChild(actions);
  }

  function renderPreviewPanel(info, payload, event, state, settings) {
    removeThreadPreview();
    var panel = createEl('div', 'spx-preview-popover');
    panel.id = 'spx-preview-popover';
    var title = createEl('h4', '', info.title || '帖子预览');
    var meta = createEl('div', 'spx-preview-meta', getThreadPreviewMetaText(info, payload));
    var text = createEl('div', 'spx-preview-text', payload.text || '未提取到文字预览');
    panel.appendChild(title);
    panel.appendChild(meta);
    appendThreadPreviewStatusChips(panel, info, payload, state);
    appendPreviewActions(panel, info, state, settings);
    panel.appendChild(text);

    var previewImages = getThreadPreviewImageUrls(payload);
    if (previewImages.length) {
      var grid = createEl('div', 'spx-preview-images');
      previewImages.forEach(function appendPreviewImage(url) {
        var link = createEl('a');
        link.href = url;
        link.target = '_blank';
        link.rel = 'noopener noreferrer';
        var image = createEl('img');
        image.loading = 'lazy';
        image.decoding = 'async';
        image.alt = '预览图';
        preparePreviewImageReveal(image);
        image.dataset.spxPreviewLazySrc = url;
        link.appendChild(image);
        grid.appendChild(link);
      });
      panel.appendChild(grid);
    }
    panel.appendChild(createEl('div', 'spx-preview-status', getThreadPreviewImageSummary(payload)));

    panel.addEventListener('mouseleave', removeThreadPreview);
    document.body.appendChild(panel);
    positionPreviewPanel(panel, event || {});
    scheduleThreadPreviewPanelImages(panel);
  }

  function extractPreviewPayloadFromDocument(doc, url) {
    var content = doc && doc.querySelector ? doc.querySelector('.tpc_content') : null;
    var images = content ? Array.prototype.slice.call(content.querySelectorAll('img')) : [];
    var resourceLinks = doc && doc.querySelectorAll ? getJumpResourceLinks(extractReadPageResourceLinks(qsa('table.js-post', doc), url)) : [];
    return {
      text: content ? content.textContent.replace(/\s+/g, ' ').trim().slice(0, 260) : '',
      images: extractPreviewImageUrls(
        images.map(function mapPreviewImage(image) {
          return {
            src: image.getAttribute('src') || image.src,
            naturalWidth: image.naturalWidth || image.width || 0,
            naturalHeight: image.naturalHeight || image.height || 0,
            postIndex: 0,
          };
        }),
        url
      ),
      resourceBadges: getResourceBadgesFromItems(resourceLinks, 'preview'),
    };
  }

  function attachThreadHoverPreview(info, settings, state) {
    if (!info || !info.titleLink || info.titleLink.dataset.spxPreviewReady) return;
    info.titleLink.dataset.spxPreviewReady = '1';
    var timer = null;
    var previewToken = 0;
    var lastPreviewEvent = null;
    var previewAbortController = null;
    info.titleLink.addEventListener('mouseenter', function schedulePreview(event) {
      lastPreviewEvent = event;
      previewToken += 1;
      var currentToken = previewToken;
      var url = info.titleLink.href;
      var cached = getCachedThreadPreview(url);
      if (timer) window.clearTimeout(timer);
      if (previewAbortController) {
        previewAbortController.abort();
        previewAbortController = null;
      }
      timer = window.setTimeout(function loadPreview() {
        timer = null;
        if (currentToken !== previewToken) return;
        if (cached) {
          cached.cached = true;
          updateThreadResourceBadges(info, state, cached);
          renderPreviewPanel(info, cached, lastPreviewEvent || event, state, settings);
          return;
        }
        if (isScriptRequestCoolingDown()) {
          renderPreviewPanel(info, { text: '站点提示操作频繁,悬停预览已临时暂停。', images: [], status: '请求冷却中' }, lastPreviewEvent || event, state, settings);
          return;
        }
        var previewPolicy = {
          mode: 'preview',
          label: '悬停预览',
          networkFriendly: isNetworkFriendlyMode(settings),
        };
        var requestOptions = { credentials: 'include' };
        var requestController = null;
        if (typeof AbortController === 'function') {
          requestController = new AbortController();
          previewAbortController = requestController;
          requestOptions.signal = requestController.signal;
        }
        renderPreviewPanel(info, { text: '正在加载预览...', images: [], status: '加载中' }, lastPreviewEvent || event, state, settings);
        requestWithPolicy(url, requestOptions, previewPolicy)
          .then(function parseResponse(response) {
            return readScriptResponseText(response, previewPolicy);
          })
          .then(function renderHtml(html) {
            if (previewAbortController === requestController) previewAbortController = null;
            if (currentToken !== previewToken) return;
            var doc = new DOMParser().parseFromString(html, 'text/html');
            var payload = extractPreviewPayloadFromDocument(doc, url);
            rememberThreadPreview(url, payload);
            updateThreadResourceBadges(info, state, payload);
            renderPreviewPanel(info, payload, lastPreviewEvent || event, state, settings);
          })
          .catch(function showPreviewError(error) {
            if (previewAbortController === requestController) previewAbortController = null;
            if (error && error.name === 'AbortError') return;
            if (currentToken !== previewToken) return;
            var failurePayload = { text: '预览加载失败,已短暂缓存失败状态,避免重复请求同一主题。', images: [], status: '加载失败' };
            rememberThreadPreview(url, failurePayload, undefined, THREAD_PREVIEW_FAILURE_TTL);
            renderPreviewPanel(info, failurePayload, lastPreviewEvent || event, state, settings);
          });
      }, cached ? Math.min(120, getThreadPreviewHoverDelay(settings)) : getThreadPreviewHoverDelay(settings));
    });
    info.titleLink.addEventListener('mousemove', function movePreview(event) {
      lastPreviewEvent = event;
      var panel = qs('#spx-preview-popover');
      if (panel) positionPreviewPanel(panel, event);
    });
    info.titleLink.addEventListener('mouseleave', function cancelPreview() {
      previewToken += 1;
      if (timer) window.clearTimeout(timer);
      timer = null;
      if (previewAbortController) {
        previewAbortController.abort();
        previewAbortController = null;
      }
      window.setTimeout(function removePreviewIfPanelInactive() {
        var panel = qs('#spx-preview-popover');
        if (panel && panel.matches && panel.matches(':hover')) return;
        removeThreadPreview();
      }, 160);
    });
  }

  function enhanceForumGallery(settings, state) {
    if (!isForumGalleryModeUrl(location.href)) return false;
    var wall = qs('#wall');
    if (!wall) return false;
    var cards = qsa('.stream li', wall).filter(function keepGalleryThreadCard(card) {
      var info = extractForumGalleryCardInfo(card);
      return !!(info.id && info.titleLink);
    });
    if (!cards.length) return false;

    wall.classList.add('spx-forum-gallery-wall');
    watchForumGalleryStream(wall, settings, state);
    scheduleForumGalleryToolRepair(wall, settings, state);
    var items = [];
    var resourceBadgeIndex = getThreadResourceBadgeIndex(state.resources);

    cards.forEach(function enhanceGalleryCard(card) {
      var info = extractForumGalleryCardInfo(card);
      if (!info.id || !info.titleLink) return;
      items.push(info);
      card.classList.add('spx-gallery-thread-card');
      info.cell.classList.add('spx-gallery-card-inner');
      if (card.dataset) card.dataset.spxGalleryThread = '1';

      var isRead = !!state.read[info.id];
      card.classList.toggle('spx-read-thread', isRead);
      setThreadRowHiddenClass(card, 'spx-unread-hidden', !!settings.unreadOnly && isRead);
      setThreadRowHiddenClass(card, 'spx-hidden-rule', matchesBlockRules(info, settings));

      var resourceBadges = getThreadResourceBadges(info, resourceBadgeIndex);
      info.resourceBadges = resourceBadges;
      info.resourceBadgeTypes = getResourceBadgeTypes(resourceBadges);
      if (card.dataset) card.dataset.spxResourceTypes = info.resourceBadgeTypes.join(' ');

      ensureForumGalleryCardTools(info, settings, state);
      clearForumGalleryResourceBadges(info);

      if (info.titleLink.dataset && info.titleLink.dataset.spxGalleryReadBound !== '1') {
        info.titleLink.dataset.spxGalleryReadBound = '1';
        info.titleLink.addEventListener('click', function markGalleryThreadRead() {
          state.read[info.id] = Date.now();
          saveMap(READ_KEY, state.read);
          refreshFavoriteNavPanels();
        }, { capture: true });
      }
    });

    syncThreadUpdateEntries(state, items, 'gallery-list');
    items.forEach(function renderGalleryUpdateBadge(info) {
      renderThreadUpdateBadge(info, state);
    });
    createForumQuickTools(settings, state, items);
    createForumSectionTitle(items, wall);
    compactForumPrelude(wall);
    createForumModuleNavigation(items, state);
    hideForumAnnouncements();
    return true;
  }

  function enhanceThreadList(settings, state) {
    if (detectPageType(location.href) !== 'forum') return;
    if (enhanceForumGallery(settings, state)) return;
    qsa('.spx-thread-list-table').forEach(function clearThreadListTable(table) {
      table.classList.remove('spx-thread-list-table');
      delete table.dataset.spxThreadListLayout;
    });
    var cells = qsa('td[id^="td_"]').filter(function realThreadCell(cell) {
      return parseThreadId(cell.id) && qs('a[id^="a_ajax_"]', cell);
    });
    var items = [];
    var resourceBadgeIndex = getThreadResourceBadgeIndex(state.resources);

    cells.forEach(function enhanceCell(cell) {
      var table = cell.closest ? cell.closest('.t') : null;
      if (table) {
        table.classList.add('spx-thread-list-table');
        if (table.dataset.spxThreadListLayout !== '1') {
          table.dataset.spxThreadListLayout = '1';
          qsa('table,tbody', table).forEach(function blockThreadTable(node) {
            setImportantStyle(node, 'display', 'block');
            setImportantStyle(node, 'width', '100%');
          });
          qsa('tr.tr2,tr.tr3', table).forEach(function gridThreadRow(row) {
            var isDecorativeThreadRow = row.classList.contains('tr3') && !qs('td[id^="td_"]', row);
            if (row.dataset) row.dataset.spxThreadListRow = '1';
            setThreadRowHiddenClass(row, 'spx-thread-row-hidden', isDecorativeThreadRow);
          });
        }
      }
      var info = extractThreadCellInfo(cell);
      if (!info.id || !info.titleLink) return;
      items.push(info);

      var isRead = !!state.read[info.id];
      info.row.classList.toggle('spx-read-thread', isRead);
      if (info.row.dataset) info.row.dataset.spxThreadListRow = '1';
      setThreadRowHiddenClass(info.row, 'spx-unread-hidden', !!settings.unreadOnly && isRead);
      setThreadRowHiddenClass(info.row, 'spx-hidden-rule', matchesBlockRules(info, settings));
      var resourceBadges = getThreadResourceBadges(info, resourceBadgeIndex);
      info.resourceBadges = resourceBadges;
      info.resourceBadgeTypes = getResourceBadgeTypes(resourceBadges);

      if (state.watch[info.id] && !qs('.spx-watch-badge', info.cell)) {
        info.titleLink.insertAdjacentElement('afterend', createWatchBadge(info.id));
      }

      if (qs('.spx-thread-tools', info.cell)) {
        renderThreadResourceBadges(info, resourceBadges);
        attachThreadHoverPreview(info, settings, state);
        return;
      }
      var tools = createEl('span', 'spx-thread-tools');
      var watchButton = createEl('button', '', state.watch[info.id] ? '已存' : '稍后');
      var titleBlockButton = createEl('button', '', '屏题');
      var authorBlockButton = createEl('button', '', '屏人');
      var hideRowButton = createEl('button', '', '隐藏');
      var favoriteButton = createEl('button', '', isThreadFavoriteSeen(info.id) ? '已收藏' : '收藏');

      watchButton.title = '切换本地稍后看';
      titleBlockButton.title = '把标题加入本地屏蔽关键词';
      authorBlockButton.title = '把作者加入本地屏蔽关键词';
      hideRowButton.title = '临时隐藏当前行';
      favoriteButton.title = '收藏到站内收藏夹';
      favoriteButton.disabled = isThreadFavoriteSeen(info.id);

      watchButton.addEventListener('click', function toggleWatch(event) {
        event.preventDefault();
        event.stopPropagation();
        toggleThreadWatch(info, state, watchButton);
      });

      titleBlockButton.addEventListener('click', function blockTitle(event) {
        event.preventDefault();
        event.stopPropagation();
        var keyword = window.prompt('添加标题屏蔽关键词', info.title.slice(0, 30));
        if (!keyword) return;
        settings.titleKeywords = parseLineList(settings.titleKeywords.concat([keyword]).join('\n'));
        saveSettings(settings);
        enhanceAll(settings, state);
      });

      authorBlockButton.addEventListener('click', function blockAuthor(event) {
        event.preventDefault();
        event.stopPropagation();
        if (!info.author) return;
        settings.authorKeywords = parseLineList(settings.authorKeywords.concat([info.author]).join('\n'));
        saveSettings(settings);
        enhanceAll(settings, state);
      });

      hideRowButton.addEventListener('click', function hideCurrentRow(event) {
        event.preventDefault();
        event.stopPropagation();
        setThreadRowHiddenClass(info.row, 'spx-filter-hidden', true);
      });

      favoriteButton.addEventListener('click', function favoriteCurrentThread(event) {
        event.preventDefault();
        event.stopPropagation();
        runThreadFavoriteAction(info, settings, state, favoriteButton, '已收藏');
      });

      tools.appendChild(watchButton);
      tools.appendChild(titleBlockButton);
      if (info.author) tools.appendChild(authorBlockButton);
      tools.appendChild(hideRowButton);
      tools.appendChild(favoriteButton);
      info.titleLink.insertAdjacentElement('afterend', tools);
      renderThreadResourceBadges(info, resourceBadges);
      attachThreadHoverPreview(info, settings, state);

      info.titleLink.addEventListener('click', function markRead() {
        state.read[info.id] = Date.now();
        saveMap(READ_KEY, state.read);
        refreshFavoriteNavPanels();
      }, { capture: true });
    });

    syncThreadUpdateEntries(state, items, 'forum-list');
    items.forEach(function renderListUpdateBadge(info) {
      renderThreadUpdateBadge(info, state);
    });
    createForumQuickTools(settings, state, items);
    var threadTable = items[0] && items[0].row && items[0].row.closest ? items[0].row.closest('.spx-thread-list-table') : null;
    createForumSectionTitle(items, threadTable);
    compactForumPrelude(threadTable);
    createForumModuleNavigation(items, state);
    hideForumAnnouncements();
  }

  function getPostAuthor(post) {
    var profileText = '';
    var profile = qs('.readprofile', post) || qs('.user-info', post);
    if (profile) profileText = profile.textContent.replace(/\s+/g, ' ').trim();
    var userLink = getPostUserLink(post);
    var userText = userLink ? userLink.textContent.trim() : '';
    return userText || profileText;
  }

  function compactText(value) {
    return String(value || '').replace(/\s+/g, ' ').trim();
  }

  function getSessionStorage() {
    try {
      if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage;
    } catch (error) {
      return null;
    }
    return null;
  }

  function requestReadProgressRestore(tid, mode, options) {
    var storage = getSessionStorage();
    if (!storage || !tid) return;
    var request = {
      tid: String(tid),
      mode: mode === 'last' ? 'last' : 'next',
    };
    var targetReply = normalizeThreadUpdateReplyCount(options && options.reply);
    if (targetReply !== null && targetReply > 0) request.reply = targetReply;
    storage.setItem(RESTORE_PROGRESS_KEY, JSON.stringify(request));
  }

  function getReadProgressRestoreRequest(tid) {
    var storage = getSessionStorage();
    if (!storage || !tid) return null;
    var rawValue = storage.getItem(RESTORE_PROGRESS_KEY);
    if (!rawValue) return null;
    if (rawValue === String(tid)) return { tid: String(tid), mode: 'next' };
    try {
      var request = JSON.parse(rawValue);
      if (!request || String(request.tid || '') !== String(tid)) return null;
      var targetReply = normalizeThreadUpdateReplyCount(request.reply);
      var result = {
        tid: String(request.tid),
        mode: request.mode === 'last' ? 'last' : 'next',
      };
      if (targetReply !== null && targetReply > 0) result.reply = targetReply;
      return result;
    } catch (error) {
      return null;
    }
  }

  function clearReadProgressRestoreRequest(tid) {
    var storage = getSessionStorage();
    if (!storage || !tid) return;
    if (getReadProgressRestoreRequest(tid)) {
      storage.removeItem(RESTORE_PROGRESS_KEY);
    }
  }

  function getReadPageTitle(root) {
    var scope = root || document;
    var titleNode =
      qs('[id^="subject_"]', scope) ||
      qs('.h1', scope) ||
      qs('h1', scope) ||
      qs('title', scope);
    return compactText(titleNode && titleNode.textContent) || compactText(document.title) || '未命名帖子';
  }

  function syncNativeReadFavoriteFromGuide(info, settings, state, attemptsLeft) {
    var guide = qs('#ajax_guide');
    var guideText = compactText(guide && guide.textContent);
    if (guideText) {
      if (isNewThreadFavoriteResult(guideText)) {
        var added = markThreadFavoriteSeen(info);
        syncFavoriteNavAfterSiteFavorite(info, settings, state, added);
      }
      if (getThreadFavoriteResultText(guideText) !== '已提交') return;
    }
    if (!attemptsLeft) return;
    window.setTimeout(function retryNativeFavoriteSync() {
      syncNativeReadFavoriteFromGuide(info, settings, state, attemptsLeft - 1);
    }, 800);
  }

  function bindNativeReadFavoriteSync(settings, state, tid, originalAuthor) {
    if (!tid) return;
    var nativeFavorite = qs('a[onclick*="action=favor"][onclick*="tid"],a[title*="收藏该主题"]');
    if (!nativeFavorite || nativeFavorite.dataset.spxNativeFavoriteSync === '1') return;
    nativeFavorite.dataset.spxNativeFavoriteSync = '1';
    nativeFavorite.addEventListener('click', function syncNativeFavoriteAfterClick() {
      var info = createReadPageFavoriteInfo(tid, getReadPageTitle(document), originalAuthor, location.href);
      window.setTimeout(function syncNativeFavoriteFromGuide() {
        syncNativeReadFavoriteFromGuide(info, settings, state, 3);
      }, 1800);
    }, true);
  }

  function getCurrentResourceSourceMeta() {
    return {
      sourceTitle: getReadPageTitle(document),
      sourceUrl: location.href.split('#')[0],
    };
  }

  function getScrollTop() {
    return Number(
      window.pageYOffset ||
      document.documentElement.scrollTop ||
      (document.body && document.body.scrollTop) ||
      0
    ) || 0;
  }

  function getPostFloorLabel(index) {
    return index === 0 ? '楼主' : 'B' + index + 'F';
  }

  function getPostAnchorHash(post) {
    if (!post) return '';
    var anchor =
      qs('[id^="td_"]', post) ||
      qs('[id^="read_"]', post) ||
      qs('[id^="p_"]', post) ||
      qs('a[id^="td_read_"]', post) ||
      qs('[id^="subject_"]', post) ||
      qs('a[name]:not([name="topic"]):not([name="avatar"])', post) ||
      qs('a[name]', post);
    if (!anchor) return '';
    var value = anchor.getAttribute('name') || anchor.id || '';
    return value ? ('#' + value) : '';
  }

  function getAbsoluteNodeTop(node) {
    if (!node || !node.getBoundingClientRect) return 0;
    var rect = node.getBoundingClientRect();
    return Math.max(0, Math.round(rect.top + getScrollTop()));
  }

  function getCurrentReadFloorRecord(posts) {
    var list = (posts || qsa('table.js-post'))
      .map(function toReadFloorItem(post, index) {
        return {
          floorIndex: index,
          floorLabel: getPostFloorLabel(index),
          floorHash: getPostAnchorHash(post),
          floorTop: getAbsoluteNodeTop(post),
        };
      })
      .filter(function keepReadFloorItem(item) {
        return !!item.floorLabel;
      });
    if (!list.length) return null;

    var targetTop = getScrollTop() + Math.max(80, Math.min((window.innerHeight || 0) * 0.35, 320));
    var current = list[0];
    list.forEach(function updateCurrentFloor(item) {
      if (item.floorTop <= targetTop) current = item;
    });

    var next = list[Math.min(list.length - 1, current.floorIndex + 1)] || current;
    return {
      floorIndex: current.floorIndex,
      floorLabel: current.floorLabel,
      floorHash: current.floorHash,
      floorTop: current.floorTop,
      nextFloorIndex: next.floorIndex,
      nextFloorLabel: next.floorLabel,
      nextFloorHash: next.floorHash,
      nextFloorTop: next.floorTop,
    };
  }

  function getReadScrollRatio() {
    var doc = document.documentElement;
    var body = document.body;
    var scrollHeight = Math.max(
      doc ? doc.scrollHeight : 0,
      body ? body.scrollHeight : 0
    );
    var viewportHeight = window.innerHeight || (doc && doc.clientHeight) || 0;
    var maxScroll = scrollHeight - viewportHeight;
    if (!(maxScroll > 0)) return 0;
    return clampRatio(getScrollTop() / maxScroll);
  }

  function buildReadProgressRecord(tid) {
    if (!tid) return null;
    var record = {
      title: getReadPageTitle(document),
      url: location.href.split('#')[0],
      page: currentPageNumber(location.href),
      scrollY: Math.max(0, Math.round(getScrollTop())),
      progress: getReadScrollRatio(),
      updatedAt: Date.now(),
    };
    var floorRecord = getCurrentReadFloorRecord();
    if (floorRecord) {
      record.floorIndex = floorRecord.floorIndex;
      record.floorLabel = floorRecord.floorLabel;
      record.floorHash = floorRecord.floorHash;
      record.floorTop = floorRecord.floorTop;
      record.nextFloorIndex = floorRecord.nextFloorIndex;
      record.nextFloorLabel = floorRecord.nextFloorLabel;
      record.nextFloorHash = floorRecord.nextFloorHash;
      record.nextFloorTop = floorRecord.nextFloorTop;
    }
    return record;
  }

  function saveReadProgressRecord(state, tid) {
    var record = buildReadProgressRecord(tid);
    if (!record) return null;
    state.progress = state.progress || {};
    state.progress[tid] = mergeReadProgressRecord(state.progress[tid], record);
    state.progress = pruneReadProgress(state.progress);
    saveReadProgress(state.progress);
    refreshWatchCenter();
    refreshHistoryCenter();
    refreshReadThreadSummaryCard();
    refreshFavoriteNavPanels();
    return state.progress[tid];
  }

  function getReadProgressAnchorNode(hash) {
    var targetId = String(hash || '').replace(/^#/, '');
    if (!targetId) return null;
    return (
      document.getElementById(targetId) ||
      qsa('a[name]', document).filter(function matchAnchor(node) {
        return node.getAttribute('name') === targetId;
      })[0] ||
      null
    );
  }

  function getReadPostReplyNumber(post, index, pageNumber) {
    var text = compactText(post && post.textContent);
    if (/楼主/.test(text)) return 0;
    var labelMatch = text.match(/\bB\s*(\d+)\s*F\b/i) || text.match(/(?:^|\D)(\d+)\s*楼(?:\D|$)/);
    if (labelMatch) return Number(labelMatch[1]) || 0;
    var page = Math.max(1, Number(pageNumber) || currentPageNumber(location.href) || 1);
    var localIndex = Math.max(0, Number(index) || 0);
    return page <= 1
      ? localIndex
      : (page - 1) * READ_REPLIES_PER_PAGE + localIndex + 1;
  }

  function getReadReplyFloorTarget(replyNumber) {
    var targetReply = normalizeThreadUpdateReplyCount(replyNumber);
    if (targetReply === null || targetReply <= 0) return null;
    var posts = qsa('table.js-post');
    if (!posts.length) return null;
    var page = currentPageNumber(location.href) || getReadReplyPage(targetReply);
    var matched = null;
    posts.forEach(function matchReadReplyPost(post, index) {
      if (matched) return;
      if (getReadPostReplyNumber(post, index, page) !== targetReply) return;
      matched = post;
    });
    if (!matched) {
      var localIndex = page <= 1
        ? targetReply
        : targetReply - (page - 1) * READ_REPLIES_PER_PAGE - 1;
      matched = posts[localIndex] || null;
    }
    if (!matched) return null;
    return {
      hash: getPostAnchorHash(matched),
      node: matched,
      top: getAbsoluteNodeTop(matched),
      label: getPostFloorLabel(targetReply),
    };
  }

  function restoreReadReplyFloor(state, tid, replyNumber) {
    var targetReply = normalizeThreadUpdateReplyCount(replyNumber);
    if (targetReply === null || targetReply <= 0) return false;
    clearReadProgressRestoreRequest(tid);
    window.setTimeout(function restoreUnreadReplyFloor() {
      var target = getReadReplyFloorTarget(targetReply);
      if (!target) {
        restoreReadProgress(state, tid, 'next');
        return;
      }
      var top = Math.max(0, Number(target.top) || 0);
      var anchorNode = getReadProgressAnchorNode(target.hash) || target.node;
      if (anchorNode && typeof anchorNode.scrollIntoView === 'function') {
        anchorNode.scrollIntoView({ block: 'start', behavior: 'smooth' });
      } else {
        window.scrollTo({ top: top, behavior: 'smooth' });
      }
      window.setTimeout(function fallbackUnreadReplyScroll() {
        if (Math.abs(getScrollTop() - top) <= 8) return;
        if (document.scrollingElement) document.scrollingElement.scrollTop = top;
        if (document.documentElement) document.documentElement.scrollTop = top;
        if (document.body) document.body.scrollTop = top;
      }, 260);
    }, 80);
    return true;
  }

  function restoreReadProgress(state, tid, mode) {
    var latestProgress = loadReadProgress();
    if (state && latestProgress) state.progress = latestProgress;
    var record = latestProgress && latestProgress[tid]
      ? latestProgress[tid]
      : (state && state.progress ? state.progress[tid] : null);
    if (!record) {
      clearReadProgressRestoreRequest(tid);
      return false;
    }
    clearReadProgressRestoreRequest(tid);
    var target = getReadProgressRestoreTarget(record, mode);
    window.setTimeout(function restoreScroll() {
      var top = Math.max(0, Number(target.top) || 0);
      var anchorNode = getReadProgressAnchorNode(target.hash);
      if (anchorNode && typeof anchorNode.scrollIntoView === 'function') {
        anchorNode.scrollIntoView({ block: 'start', behavior: 'smooth' });
      } else {
        window.scrollTo({ top: top, behavior: 'smooth' });
      }
      window.setTimeout(function fallbackRestoreScroll() {
        if (Math.abs(getScrollTop() - top) <= 8) return;
        if (document.scrollingElement) document.scrollingElement.scrollTop = top;
        if (document.documentElement) document.documentElement.scrollTop = top;
        if (document.body) document.body.scrollTop = top;
      }, 260);
    }, 80);
    return true;
  }

  function restorePendingReadProgress(state, tid) {
    var request = getReadProgressRestoreRequest(tid);
    if (!request) return false;
    if (request.reply) return restoreReadReplyFloor(state, tid, request.reply);
    return restoreReadProgress(state, tid, request.mode);
  }

  function bindReadPageJumpButtons(state, tid) {
    if (!tid || !document.documentElement) return;
    if (document.documentElement.dataset.spxReadJumpTid === String(tid)) return;
    document.documentElement.dataset.spxReadJumpTid = String(tid);
    document.addEventListener('click', function handleReadJumpClick(event) {
      var target = event.target && event.target.closest
        ? event.target.closest('[data-spx-read-jump]')
        : null;
      if (!target) return;
      var targetTid = target.dataset.spxReadTid || tid;
      if (String(targetTid) !== String(tid)) return;
      event.preventDefault();
      restoreReadProgress(state, tid, target.dataset.spxReadJump);
    }, true);
  }

  function getReadPageImageCount(posts) {
    var count = 0;
    (posts || []).forEach(function countPostImages(post, postIndex) {
      var content = qs('.tpc_content', post) || post;
      qsa('img', content).forEach(function countPreviewCandidate(image) {
        if (isPreviewImageCandidate({
          src: image.currentSrc || image.src,
          naturalWidth: image.naturalWidth,
          naturalHeight: image.naturalHeight,
          width: image.width,
          height: image.height,
          className: image.className,
          alt: image.alt,
          postIndex: postIndex,
        })) count += 1;
      });
    });
    return count;
  }

  function getReadSummaryAutoBuyStatus(settings) {
    if (!settings || !settings.autoBuyPost) return { text: '已关闭', detail: '设置中未开启自动购买', className: 'spx-muted' };
    var status = document.documentElement && document.documentElement.dataset
      ? document.documentElement.dataset.spxAutoBuyStatus
      : '';
    var labels = {
      checking: '检查中',
      skipped: '已跳过',
      buying: '购买中',
      done: '已完成',
      failed: '失败',
      blocked: '已拦截',
    };
    if (status) {
      return {
        text: labels[status] || status,
        detail: '当前页执行状态',
        className: status === 'done' ? 'spx-ok' : status === 'failed' || status === 'blocked' ? 'spx-danger' : 'spx-warn',
      };
    }
    var target = findAutoBuyTarget(document, location.href);
    if (target) {
      return { text: target.price + ' SP', detail: '待自动购买判断', className: 'spx-warn' };
    }
    return { text: '无购买项', detail: '当前页未发现付费入口', className: 'spx-ok' };
  }

  function getReadThreadSummaryData(settings, state, posts, tid, originalAuthor) {
    state.progress = state.progress || {};
    state.watch = state.watch || {};
    var record = tid ? state.progress[tid] : null;
    var floors = getReadProgressFloorLabels(record);
    var resources = getCurrentReadResourceRailEntries(posts, state);
    var resourceSummary = formatResourceRailSummary(resources);
    var autoBuy = getReadSummaryAutoBuyStatus(settings);
    return {
      tid: tid,
      title: getReadPageTitle(document) || '当前帖子',
      author: originalAuthor || '',
      url: location.href.split('#')[0],
      watched: !!(tid && state.watch[tid]),
      favorited: isThreadFavoriteSeen(tid),
      progressText: formatReadProgress(record) || '暂无记录',
      progressPercent: getReadProgressPercent(record),
      lastFloor: floors.last || '暂无',
      nextFloor: floors.next || floors.last || '暂无',
      resources: resources,
      resourceSummary: resourceSummary,
      imageCount: getReadPageImageCount(posts),
      autoBuy: autoBuy,
    };
  }

  function createReadSummaryMetric(label, value, note, className) {
    var metric = createEl('div', 'spx-read-summary-metric' + (className ? ' ' + className : ''));
    metric.appendChild(createEl('b', '', String(value)));
    metric.appendChild(createEl('span', '', label));
    if (note) metric.appendChild(createEl('em', '', note));
    return metric;
  }

  function createReadSummaryAction(action, text, primary) {
    var button = createEl('button', primary ? 'spx-primary' : '', text);
    button.type = 'button';
    button.dataset.spxReadSummaryAction = action;
    return button;
  }

  function setReadSummaryWatchState(data, state) {
    if (!data || !data.tid || !state) return;
    state.watch = state.watch || {};
    if (state.watch[data.tid]) {
      delete state.watch[data.tid];
    } else {
      state.watch[data.tid] = {
        title: data.title,
        url: data.url,
        savedAt: Date.now(),
      };
    }
    saveMap(WATCH_KEY, state.watch);
    refreshWatchCenter();
  }

  function openReadSummaryResourceCenter(settings, state) {
    var panel = createResourceCenterPanel(settings, state || { resources: loadResourceLibrary() });
    setCenterPanelHidden(panel, false);
  }

  function renderReadThreadSummaryCard(card, settings, state, posts, tid, originalAuthor) {
    var data = getReadThreadSummaryData(settings, state, posts, tid, originalAuthor);
    card.textContent = '';
    card.dataset.spxReadTid = data.tid || '';
    card.spxSummaryData = data;

    var head = createEl('div', 'spx-read-summary-head');
    var copy = createEl('div', 'spx-read-summary-copy');
    copy.appendChild(createEl('div', 'spx-read-summary-eyebrow', '帖子详情摘要'));
    copy.appendChild(createEl('h3', '', data.title));
    copy.appendChild(createEl('p', '', [data.author ? ('楼主 ' + data.author) : '', data.tid ? ('TID ' + data.tid) : ''].filter(Boolean).join(' · ') || '当前阅读页'));
    head.appendChild(copy);
    var chips = createEl('div', 'spx-read-summary-chips');
    chips.appendChild(createEl('span', data.favorited ? 'spx-chip spx-ok' : 'spx-chip', data.favorited ? '已收藏' : '未收藏'));
    chips.appendChild(createEl('span', data.watched ? 'spx-chip spx-ok' : 'spx-chip', data.watched ? '已稍后' : '未稍后'));
    chips.appendChild(createEl('span', 'spx-chip ' + data.autoBuy.className, '自动购买 ' + data.autoBuy.text));
    head.appendChild(chips);
    card.appendChild(head);

    var progress = createEl('div', 'spx-read-summary-progress');
    progress.appendChild(createEl('span', '', '阅读进度 ' + data.progressText));
    var track = createEl('div', 'spx-read-summary-track');
    var bar = createEl('i');
    bar.style.width = Math.max(0, Math.min(100, data.progressPercent)) + '%';
    track.appendChild(bar);
    progress.appendChild(track);
    card.appendChild(progress);

    var metrics = createEl('div', 'spx-read-summary-metrics');
    metrics.appendChild(createReadSummaryMetric('未读楼层', data.nextFloor, '继续阅读位置'));
    metrics.appendChild(createReadSummaryMetric('上次楼层', data.lastFloor, '最近停留位置'));
    metrics.appendChild(createReadSummaryMetric('资源', data.resourceSummary.total, formatResourceRailSummaryText(data.resources)));
    metrics.appendChild(createReadSummaryMetric('图片', data.imageCount, data.imageCount ? '正文可预览图片' : '暂无正文图片'));
    metrics.appendChild(createReadSummaryMetric('自动购买', data.autoBuy.text, data.autoBuy.detail, data.autoBuy.className));
    card.appendChild(metrics);

    var actions = createEl('div', 'spx-read-summary-actions');
    if (data.tid) {
      var continueButton = createReadSummaryAction('continue', '继续阅读', true);
      var lastButton = createReadSummaryAction('last', '上次楼层', false);
      continueButton.disabled = !state.progress || !state.progress[data.tid];
      lastButton.disabled = !state.progress || !state.progress[data.tid];
      actions.appendChild(continueButton);
      actions.appendChild(lastButton);
      actions.appendChild(createReadSummaryAction('watch', data.watched ? '取消稍后' : '加入稍后', false));
      var favoriteButton = createReadSummaryAction('favorite', data.favorited ? '已收藏' : '收藏', false);
      favoriteButton.disabled = data.favorited;
      actions.appendChild(favoriteButton);
    }
    actions.appendChild(createReadSummaryAction('resources', '资源工作台', false));
    card.appendChild(actions);
  }

  function refreshReadThreadSummaryCard() {
    var card = qs('#spx-read-summary-card');
    if (!card || !readSummaryContext.tid) return;
    renderReadThreadSummaryCard(
      card,
      readSummaryContext.settings,
      readSummaryContext.state,
      readSummaryContext.posts || [],
      readSummaryContext.tid,
      readSummaryContext.originalAuthor
    );
  }

  function createReadThreadSummaryCard(settings, state, posts, tid, originalAuthor) {
    if (!document.body || !tid) return null;
    var existing = qs('#spx-read-summary-card');
    if (existing) existing.remove();
    var card = createEl('section', 'spx-read-summary-card');
    card.id = 'spx-read-summary-card';
    readSummaryContext = { settings: settings, state: state, posts: posts || [], tid: tid, originalAuthor: originalAuthor || '' };
    renderReadThreadSummaryCard(card, settings, state, posts || [], tid, originalAuthor || '');
    card.addEventListener('click', function handleReadSummaryClick(event) {
      var target = event.target && event.target.closest
        ? event.target.closest('[data-spx-read-summary-action]')
        : null;
      if (!target || !card.contains(target)) return;
      var action = target.dataset.spxReadSummaryAction;
      var data = card.spxSummaryData || getReadThreadSummaryData(settings, state, posts, tid, originalAuthor);
      if (action === 'continue') {
        restoreReadProgress(state, tid, 'next');
        return;
      }
      if (action === 'last') {
        restoreReadProgress(state, tid, 'last');
        return;
      }
      if (action === 'watch') {
        setReadSummaryWatchState(data, state);
        refreshReadThreadSummaryCard();
        return;
      }
      if (action === 'favorite') {
        runThreadFavoriteAction(createReadPageFavoriteInfo(tid, data.title, data.author, data.url), settings, state, target, '已收藏');
        window.setTimeout(refreshReadThreadSummaryCard, 900);
        return;
      }
      if (action === 'resources') {
        openReadSummaryResourceCenter(settings, state);
      }
    });

    var firstPost = posts && posts[0];
    if (firstPost && firstPost.parentNode) {
      firstPost.parentNode.insertBefore(card, firstPost);
    } else {
      var host = qs('#content') || qs('#main') || document.body;
      host.insertBefore(card, host.firstChild || null);
    }
    return card;
  }

  function bindReadProgressTracking(state, tid) {
    if (!tid || !document.documentElement) return;
    if (document.documentElement.dataset.spxReadProgressTid === String(tid)) return;
    document.documentElement.dataset.spxReadProgressTid = String(tid);

    var saveTimer = null;
    function saveNow() {
      if (saveTimer) window.clearTimeout(saveTimer);
      saveTimer = null;
      saveReadProgressRecord(state, tid);
    }
    function scheduleSave() {
      if (saveTimer) window.clearTimeout(saveTimer);
      saveTimer = window.setTimeout(saveNow, 500);
    }

    window.addEventListener('scroll', scheduleSave, { passive: true });
    window.addEventListener('pagehide', saveNow);
    document.addEventListener('visibilitychange', function saveBeforeHidden() {
      if (document.visibilityState === 'hidden') saveNow();
    });
    window.setTimeout(saveNow, 700);
  }

  function getPostUserLink(scope) {
    return (
      qsa('a[href*="u.php?action-show"],a[href*="u.php?uid"],a[href*="u.php?action=show"],a[href*="profile.php?action-show"],.readprofile a[href],.user-info a[href],a[href*="u.php"]', scope)
        .filter(function hasUsefulUserText(link) {
          return !!compactText(link.textContent);
        })[0] ||
      qs('a[href*="u.php?action-show"]', scope) ||
      qs('a[href*="u.php?uid"]', scope) ||
      qs('a[href*="u.php?action=show"]', scope) ||
      qs('a[href*="profile.php?action-show"]', scope) ||
      qs('.readprofile a[href]', scope) ||
      qs('.user-info a[href]', scope) ||
      qs('a[href*="u.php"]', scope)
    );
  }

  function getVisiblePostAuthorName(post) {
    var authorCell = qs('th.r_two', post) || qs('td.r_two', post) || qs('.r_two', post);
    var userLink = getPostUserLink(authorCell || post);
    var linkText = compactText(userLink && userLink.textContent);
    if (linkText) return linkText;

    var authorText = compactText(authorCell && authorCell.textContent);
    if (!authorText) return '';
    return authorText
      .split(/\s{2,}|(?=作者资料)|(?=发送短消息)|(?=加为好友)|(?=使用道具)|(?=等级[::])|(?=帖子[::])|(?=注册[::])|(?=威望[::])|(?=金币[::])/)
      .map(compactText)
      .filter(Boolean)[0] || '';
  }

  function getPostAuthorHoverTargets(post, info) {
    var authorCell = qs('th.r_two', post) || qs('td.r_two', post) || qs('.r_two', post);
    var targets = [
      authorCell,
      info && info.source,
      getPostUserLink(post),
      qs('.readprofile', post),
      qs('.user-info', post),
      qs('.user-pic', post),
    ];
    var seen = [];
    return targets.filter(function keepUniqueTarget(node) {
      if (!node || seen.indexOf(node) !== -1) return false;
      seen.push(node);
      return true;
    });
  }

  function extractPostAuthorInfo(post) {
    var authorCell = qs('th.r_two', post) || qs('td.r_two', post) || qs('.r_two', post);
    var scope = authorCell || post;
    var userLink = getPostUserLink(scope) || getPostUserLink(post);
    var avatar = qs('.user-pic img', scope) || qs('img', scope);
    var name = compactText(userLink && userLink.textContent) || getVisiblePostAuthorName(post) || getPostAuthor(post);
    var lines = [];
    var seen = {};

    qsa('.readprofile,.user-info,.user-pic,.f12,div,span', scope).forEach(function collectAuthorLine(node) {
      var text = compactText(node.textContent);
      if (!text || text === name || seen[text]) return;
      if (text.length > 90) return;
      seen[text] = true;
      lines.push(text);
    });

    if (!lines.length && authorCell) {
      compactText(authorCell.textContent)
        .split(/\s{2,}|(?=等级[::])|(?=帖子[::])|(?=注册[::])|(?=威望[::])|(?=金币[::])/)
        .map(compactText)
        .filter(Boolean)
        .forEach(function addFallbackLine(text) {
          if (text === name || seen[text]) return;
          seen[text] = true;
          lines.push(text);
        });
    }

    return {
      name: name || '未知用户',
      url: userLink ? userLink.href : '',
      avatar: avatar ? (avatar.currentSrc || avatar.src || '') : '',
      lines: lines.slice(0, 8),
      source: authorCell || userLink || qs('.readprofile', scope),
    };
  }

  function removeAuthorPopover() {
    var popover = qs('#spx-author-popover');
    if (popover) popover.remove();
  }

  function positionAuthorPopover(popover, event) {
    if (!popover) return;
    var margin = 12;
    var left = (event && event.clientX ? event.clientX : 24) + 14;
    var top = (event && event.clientY ? event.clientY : 24) + 14;
    var width = popover.offsetWidth || 320;
    var height = popover.offsetHeight || 180;
    if (left + width + margin > window.innerWidth) left = window.innerWidth - width - margin;
    if (top + height + margin > window.innerHeight) top = window.innerHeight - height - margin;
    popover.style.left = Math.max(margin, left) + 'px';
    popover.style.top = Math.max(margin, top) + 'px';
  }

  function renderAuthorPopover(info, event) {
    removeAuthorPopover();
    if (!info || !info.name) return;

    var popover = createEl('div', 'spx-author-popover');
    popover.id = 'spx-author-popover';

    var header = createEl('div', 'spx-author-popover-header');
    if (info.avatar) {
      var avatar = createEl('img', 'spx-author-popover-avatar');
      avatar.src = info.avatar;
      avatar.alt = info.name;
      header.appendChild(avatar);
    } else {
      header.appendChild(createEl('div', 'spx-author-popover-avatar'));
    }

    var title = createEl('div');
    title.appendChild(createEl('div', 'spx-author-popover-name', info.name));
    if (info.url) {
      var link = createEl('a', 'spx-author-popover-link', '查看用户主页');
      link.href = info.url;
      link.target = '_blank';
      link.rel = 'noreferrer';
      title.appendChild(link);
    }
    header.appendChild(title);
    popover.appendChild(header);

    var lines = createEl('div', 'spx-author-popover-lines');
    (info.lines && info.lines.length ? info.lines : ['页面未提供更多资料']).forEach(function appendLine(line) {
      lines.appendChild(createEl('div', 'spx-author-popover-line', line));
    });
    popover.appendChild(lines);

    popover.addEventListener('mouseenter', function keepPopover() {
      if (popover.spxHideTimer) window.clearTimeout(popover.spxHideTimer);
    });
    popover.addEventListener('mouseleave', removeAuthorPopover);
    document.body.appendChild(popover);
    positionAuthorPopover(popover, event || {});
  }

  function attachAuthorHoverCard(post) {
    if (!post || post.dataset.spxAuthorHoverReady === '1') return;
    var info = extractPostAuthorInfo(post);
    var targets = getPostAuthorHoverTargets(post, info);
    if (!targets.length) return;
    post.dataset.spxAuthorHoverReady = '1';

    targets.forEach(function bindAuthorTarget(target) {
      target.classList.add('spx-author-hover-source');
      target.addEventListener('mouseenter', function showAuthor(event) {
        renderAuthorPopover(info, event);
      });
      target.addEventListener('mouseover', function showAuthorFallback(event) {
        if (!qs('#spx-author-popover')) renderAuthorPopover(info, event);
      });
      target.addEventListener('mousemove', function moveAuthor(event) {
        positionAuthorPopover(qs('#spx-author-popover'), event);
      });
      target.addEventListener('mouseleave', function hideAuthor() {
        var popover = qs('#spx-author-popover');
        if (!popover) return;
        popover.spxHideTimer = window.setTimeout(removeAuthorPopover, 120);
      });
    });
  }

  function setImportantStyle(node, name, value) {
    if (!node || !node.style) return;
    if (typeof node.style.setProperty === 'function') {
      node.style.setProperty(name, value, 'important');
      return;
    }
    node.style[name] = value;
  }

  function applySiteShellLayout(root) {
    var scope = root || document;
    qsa('#header,#main,#content,#infobox,#notice', scope).forEach(function widenShellNode(node) {
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', 'min(1480px, calc(100vw - 40px))');
      setImportantStyle(node, 'max-width', 'none');
      setImportantStyle(node, 'margin-left', 'auto');
      setImportantStyle(node, 'margin-right', 'auto');
    });
    qsa('#mainNav', scope).forEach(function widenNav(node) {
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', 'min(1480px, calc(100vw - 40px))');
      setImportantStyle(node, 'max-width', 'none');
      setImportantStyle(node, 'margin-left', 'auto');
      setImportantStyle(node, 'margin-right', 'auto');
      setImportantStyle(node, 'height', '38px');
      setImportantStyle(node, 'overflow', 'visible');
      qsa(':scope>div', node).forEach(function hideLegacyNavChild(child) {
        if (qs('#guide', child)) return;
        setImportantStyle(child, 'display', 'none');
      });
    });
    qsa('#mainNav>div[style*="padding-left"]', scope).forEach(function resetNavMenuHost(node) {
      setImportantStyle(node, 'display', 'flex');
      setImportantStyle(node, 'align-items', 'center');
      setImportantStyle(node, 'gap', '6px');
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', '100%');
      setImportantStyle(node, 'max-width', '100%');
      setImportantStyle(node, 'height', '38px');
      setImportantStyle(node, 'padding-left', '8px');
      setImportantStyle(node, 'overflow', 'visible');
    });
    qsa('#mainNav>div[style*="padding-left"] table,#mainNav>div[style*="padding-left"] tbody,#mainNav>div[style*="padding-left"] tr,#mainNav>div[style*="padding-left"] td', scope).forEach(function resetNavTableNode(node) {
      setImportantStyle(node, 'display', 'block');
      setImportantStyle(node, 'box-sizing', 'border-box');
      setImportantStyle(node, 'width', 'auto');
      setImportantStyle(node, 'height', '38px');
      setImportantStyle(node, 'margin', '0');
      setImportantStyle(node, 'padding', '0');
      setImportantStyle(node, 'border', '0');
    });
  }

  function getDirectChildByTag(node, tagName) {
    if (!node || !node.children) return null;
    var expected = String(tagName || '').toUpperCase();
    for (var index = 0; index < node.children.length; index += 1) {
      if (node.children[index].tagName === expected) return node.children[index];
    }
    return null;
  }

  function enhanceSiteNavigation(root) {
    var scope = root || document;
    var configLink = qs('#peacemakerconfig', scope);
    var guide = qs('#guide', scope);

    if (guide) {
      var guideHost = guide.closest ? guide.closest('#mainNav>div') : null;
      if (guideHost && !qs('#spx-nav-brand', guideHost)) {
        var brand = createEl('strong', 'spx-nav-brand', 'South Plus +++');
        brand.id = 'spx-nav-brand';
        guideHost.insertBefore(brand, guideHost.firstChild);
      }
    }

    function getNavUrlKey(href) {
      if (!href || /^javascript:/i.test(String(href))) return '';
      try {
        var parsed = new URL(href, location.href);
        return parsed.origin + parsed.pathname + parsed.search;
      } catch (error) {
        return '';
      }
    }

    function syncCurrentNavItem() {
      if (!guide) return;
      var currentUrl = new URL(location.href);
      var currentKey = currentUrl.origin + currentUrl.pathname + currentUrl.search;
      var discussionPage = /\/(?:thread|read|post|index)\.php$/i.test(currentUrl.pathname) || currentUrl.pathname === '/';
      var matched = null;

      qsa('li', guide).forEach(function resetNavItem(navItem) {
        navItem.classList.remove('spx-nav-current');
      });
      qsa('li>a', guide).forEach(function resetNavLink(link) {
        link.classList.remove('spx-nav-current');
        if (!matched && getNavUrlKey(link.getAttribute('href') || link.href) === currentKey) matched = link;
      });

      if (!matched && discussionPage) matched = qs('#h_index>a', guide);
      if (!matched && currentUrl.pathname === '/search.php') matched = qs('#h_search>a', guide);
      if (!matched) return;

      matched.classList.add('spx-nav-current');
      if (matched.parentElement) matched.parentElement.classList.add('spx-nav-current');
    }

    syncCurrentNavItem();

    if (!configLink) return;

    var item = configLink.closest ? configLink.closest('li') : null;
    var menu = getDirectChildByTag(configLink, 'div');

    if (item) {
      item.classList.add('spx-peacemaker-nav');
      if (item.style && item.style.removeProperty) item.style.removeProperty('width');
    }

    function syncPeacemakerState() {
      var open = !!(menu && !menu.hasAttribute('hidden'));
      configLink.classList.toggle('spx-menu-open', open);
      if (item) item.classList.toggle('spx-nav-active', open);
    }

    if (!configLink.dataset.spxNavReady) {
      configLink.dataset.spxNavReady = '1';
      configLink.addEventListener('click', function syncAfterClick() {
        window.setTimeout(syncPeacemakerState, 0);
      });
      if (menu && typeof MutationObserver !== 'undefined') {
        new MutationObserver(syncPeacemakerState).observe(menu, {
          attributes: true,
          attributeFilter: ['hidden'],
        });
      }
    }

    syncPeacemakerState();
  }

  function applyReadPostLayout(post, settings) {
    if (!post) return;
    var authorCell = qs('th.r_two', post);
    var contentCell = qs('th.r_one', post);
    var compactAuthor = !!(settings && (settings.compactRead || settings.hideUserProfile));
    var authorWidth = compactAuthor ? '112px' : '128px';

    setImportantStyle(post, 'width', '100%');
    setImportantStyle(post, 'max-width', '100%');
    setImportantStyle(post, 'table-layout', 'fixed');

    setImportantStyle(authorCell, 'width', authorWidth);
    setImportantStyle(authorCell, 'max-width', authorWidth);
    setImportantStyle(authorCell, 'min-width', authorWidth);
    setImportantStyle(authorCell, 'padding', '18px 8px');
    setImportantStyle(authorCell, 'overflow', 'hidden');

    qsa('th.r_two img', post).forEach(function shrinkAuthorImage(image) {
      setImportantStyle(image, 'max-width', compactAuthor ? '72px' : '96px');
      setImportantStyle(image, 'height', 'auto');
    });

    setImportantStyle(contentCell, 'width', 'auto');
    setImportantStyle(contentCell, 'min-width', '0');
  }

  function getPostToolsHost(post, content) {
    return qs('th.r_one', post) || (content && content.parentNode) || post;
  }

  function syncHiddenPostShell(post) {
    if (!post || !post.closest || !post.classList) return;
    var shell = post.closest('div.t5.t2');
    if (!shell || !shell.classList) return;
    shell.classList.toggle('spx-post-shell-hidden', post.classList.contains('spx-post-hidden'));
  }

  function setHomeModuleCollapsed(rows, collapsed) {
    (rows || []).forEach(function toggleRow(row) {
      if (!row || !row.style) return;
      if (collapsed) {
        row.style.setProperty('display', 'none', 'important');
      } else {
        row.style.removeProperty('display');
      }
    });
  }

  function mountPreviewPanel(firstPost, content, panel) {
    if (!firstPost || !content || !panel || !content.parentNode) return;
    var split = qs('.spx-post-body-split', firstPost);
    if (!split) {
      split = createEl('div', 'spx-post-body-split');
      content.parentNode.insertBefore(split, content);
    }
    split.appendChild(panel);
    split.appendChild(content);
  }

  function findAutoBuyTarget(root, pageUrl) {
    var scope = root || document;
    var controls = qsa(
      'input[onclick*="buytopic"],button[onclick*="buytopic"],a[href*="buytopic"]',
      scope
    );

    for (var index = 0; index < controls.length; index += 1) {
      var control = controls[index];
      var rawTarget = control.getAttribute('href') || control.getAttribute('onclick') || '';
      var url = extractBuyTopicUrl(rawTarget, pageUrl || location.href);
      if (!url) continue;

      var host = control.closest
        ? control.closest('h6.quote,.quote,.tpc_content,table.js-post')
        : control.parentNode;
      var price = parsePostPrice(host ? host.textContent : '');
      if (price === null) {
        var body = scope.body || scope.documentElement;
        price = parsePostPrice(body ? body.textContent : '');
      }
      if (price === null) continue;

      return {
        control: control,
        host: host || control.parentNode,
        price: price,
        url: url,
      };
    }

    return null;
  }

  function setAutoBuyStatus(target, message, isError) {
    var status = qs('#spx-auto-buy-status');
    if (!status) {
      status = createEl('div', 'spx-auto-buy-status');
      status.id = 'spx-auto-buy-status';
      var host = target && target.host;
      if (host && host.parentNode) {
        host.parentNode.insertBefore(status, host.nextSibling);
      } else {
        document.body.appendChild(status);
      }
    }
    status.textContent = message;
    status.classList.toggle('spx-error', !!isError);
  }

  function fetchCurrentUserSpBalance(settings) {
    var cachedBalance = getCachedUserSpBalance();
    if (cachedBalance !== null) return Promise.resolve(cachedBalance);
    var url = new URL('/userpay.php', location.href).href;
    var balancePolicy = {
      mode: 'interactive',
      label: 'SP 余额检查',
      networkFriendly: isNetworkFriendlyMode(settings),
    };
    return requestWithPolicy(url, {
      credentials: 'include',
      cache: 'no-store',
    }, balancePolicy)
      .then(function readBalanceResponse(response) {
        if (!response.ok) throw new Error('读取 SP 余额失败');
        return readScriptResponseText(response, balancePolicy);
      })
      .then(function parseBalancePage(html) {
        var doc = new DOMParser().parseFromString(html, 'text/html');
        var balance = parseUserSpBalance(doc.body ? doc.body.textContent : '');
        if (balance === null) throw new Error('未识别到 SP 余额');
        rememberUserSpBalance(balance);
        return balance;
      });
  }

  function replaceReadPageContent(html, settings, state) {
    var doc = new DOMParser().parseFromString(html, 'text/html');
    var currentContent = qs('#content') || qs('#main');
    var refreshedContent = qs('#content', doc) || qs('#main', doc);
    if (!currentContent || !refreshedContent) return false;

    currentContent.innerHTML = refreshedContent.innerHTML;
    enhanceAll(settings, state);
    return true;
  }

  function clearAutoBuyPageState(pageRoot) {
    if (!pageRoot) return;
    delete pageRoot.dataset.spxAutoBuyStatus;
    var status = qs('#spx-auto-buy-status');
    if (status) status.remove();
  }

  function createAutoBuyContext(settings) {
    var target = findAutoBuyTarget(document, location.href);
    var maxPrice = Number(settings.autoBuyMaxSp);
    if (!target || !(maxPrice > 0) || !(target.price < maxPrice)) return null;

    var attemptKey = getAutoBuyAttemptKey(target.url, location.href);
    var attempts = loadAutoBuyAttempts();
    return {
      pageRoot: document.documentElement,
      target: target,
      attemptKey: attemptKey,
      networkFriendly: isNetworkFriendlyMode(settings),
      previousAttempt: attemptKey ? attempts[attemptKey] : null,
    };
  }

  function blockAutoBuyContext(context) {
    if (!context || !isAutoBuyAttemptBlocked(context.previousAttempt)) return false;
    context.pageRoot.dataset.spxAutoBuyStatus = 'blocked';
    setAutoBuyStatus(context.target, formatAutoBuyAttemptMessage(context.previousAttempt), context.previousAttempt.status !== 'done');
    if (context.previousAttempt.status === 'done') showAutoBuyNavSuccess(context.previousAttempt);
    return true;
  }

  function markAutoBuyChecking(context) {
    context.pageRoot.dataset.spxAutoBuyStatus = 'checking';
    recordAutoBuyAttempt(
      context.attemptKey,
      'checking',
      '正在检查账户 SP 余额',
      { price: context.target.price, url: context.target.url }
    );
    setAutoBuyStatus(context.target, '自动购买:正在检查账户 SP 余额…', false);
  }

  function skipAutoBuyContext(context, balance) {
    context.pageRoot.dataset.spxAutoBuyStatus = 'skipped';
    recordAutoBuyAttempt(
      context.attemptKey,
      'skipped',
      '余额 ' + balance + ' SP,帖子价格 ' + context.target.price + ' SP',
      { balance: balance, price: context.target.price, url: context.target.url }
    );
    setAutoBuyStatus(
      context.target,
      '自动购买未执行:当前余额 ' + balance + ' SP,帖子价格 ' + context.target.price + ' SP。',
      true
    );
    return null;
  }

  function requestAutoBuyPurchase(context, balance) {
    context.pageRoot.dataset.spxAutoBuyStatus = 'buying';
    recordAutoBuyAttempt(
      context.attemptKey,
      'buying',
      '已发起支付 ' + context.target.price + ' SP 的购买请求',
      { balance: balance, price: context.target.price, url: context.target.url }
    );
    context.target.control.disabled = true;
    context.target.control.setAttribute('aria-disabled', 'true');
    setAutoBuyStatus(
      context.target,
      '自动购买:正在支付 ' + context.target.price + ' SP 并加载帖子内容…',
      false
    );

    var purchasePolicy = {
      mode: 'action',
      label: '自动购买',
      networkFriendly: context.networkFriendly,
    };
    return requestWithPolicy(context.target.url, {
      credentials: 'include',
      redirect: 'follow',
      cache: 'no-store',
    }, purchasePolicy).then(function readPurchaseResponse(response) {
      if (!response.ok) throw new Error('购买请求失败');
      clearUserSpBalanceCache();
      return readScriptResponseText(response, purchasePolicy);
    });
  }

  function reloadAutoBuyThreadHtml(context, purchaseResult) {
    if (purchaseResult === null) return null;
    var reloadPolicy = {
      mode: 'interactive',
      label: '自动购买后刷新帖子',
      networkFriendly: context && context.networkFriendly,
    };
    return requestWithPolicy(location.href, {
      credentials: 'include',
      cache: 'no-store',
    }, reloadPolicy)
      .then(function readRefreshedThread(response) {
        if (!response.ok) throw new Error('重新加载帖子失败');
        return readScriptResponseText(response, reloadPolicy);
      });
  }

  function applyAutoBuySuccess(context, settings, state, html) {
    if (!html) return;
    var refreshedDoc = new DOMParser().parseFromString(html, 'text/html');
    if (findAutoBuyTarget(refreshedDoc, location.href)) {
      throw new Error('购买后仍存在购买按钮');
    }
    if (!replaceReadPageContent(html, settings, state)) {
      throw new Error('无法更新帖子内容');
    }
    var resourceLinks = getJumpResourceLinks(extractReadPageResourceLinks(qsa('table.js-post'), location.href));
    var resourceSummary = formatResourceJumpSummary(resourceLinks);
    var savedResources = saveResourceLinksToLibrary(resourceLinks, state.resources, getCurrentResourceSourceMeta());
    state.resources = savedResources.resources;
    saveResourceLibrary(state.resources);
    refreshResourceCenter();
    refreshReadResourceRail();
    showAutoBuyResourceJump(resourceLinks, state, savedResources.saved);
    context.pageRoot.dataset.spxAutoBuyStatus = 'done';
    var doneRecord = recordAutoBuyAttempt(
      context.attemptKey,
      'done',
      '已支付 ' + context.target.price + ' SP 并加载帖子内容' + (resourceSummary ? ',识别资源:' + resourceSummary : ''),
      { price: context.target.price, url: context.target.url, resourceSummary: resourceSummary }
    );
    showAutoBuyNavSuccess(doneRecord);
  }

  function failAutoBuyContext(context, error) {
    var reason = error && error.message ? error.message : '未知错误';
    context.pageRoot.dataset.spxAutoBuyStatus = 'failed';
    context.target.control.disabled = false;
    context.target.control.removeAttribute('aria-disabled');
    recordAutoBuyAttempt(
      context.attemptKey,
      'failed',
      reason,
      { price: context.target.price, url: context.target.url }
    );
    setAutoBuyStatus(
      context.target,
      '自动购买失败:' + reason + '。已保留原购买按钮,请手动购买。',
      true
    );
  }

  function enhanceAutoBuyPost(settings, state) {
    if (detectPageType(location.href) !== 'read') return;

    var pageRoot = document.documentElement;
    if (!settings.autoBuyPost) {
      clearAutoBuyPageState(pageRoot);
      return;
    }
    if (pageRoot.dataset.spxAutoBuyStatus) return;

    var context = createAutoBuyContext(settings);
    if (!context) return;
    if (blockAutoBuyContext(context)) return;
    markAutoBuyChecking(context);

    fetchCurrentUserSpBalance(settings)
      .then(function purchaseWhenAffordable(balance) {
        if (!shouldAutoBuyPost(settings, context.target.price, balance)) {
          return skipAutoBuyContext(context, balance);
        }
        return requestAutoBuyPurchase(context, balance);
      })
      .then(function reloadPurchasedThread(purchaseResult) {
        return reloadAutoBuyThreadHtml(context, purchaseResult);
      })
      .then(function applyRefreshedThread(html) {
        applyAutoBuySuccess(context, settings, state, html);
      })
      .catch(function handleAutoBuyError(error) {
        failAutoBuyContext(context, error);
      });
  }

  function syncReadPageThreadUpdateState(state, tid) {
    if (!state || !tid) return;
    state.threadUpdates = state.threadUpdates || loadThreadUpdates();
    var favoriteSeen = loadMap(FAVORITE_NAV_SEEN_KEY);
    if (!(state.watch && state.watch[tid]) && !favoriteSeen[tid] && !state.threadUpdates[tid]) return;
    var replies = parseThreadReadReplyCountFromDocument(document, location.href);
    var updateResult = updateThreadReplyState(state.threadUpdates, {
      id: tid,
      title: getReadPageTitle(document),
      url: location.href.split('#')[0],
      replies: replies,
    }, { markRead: true, source: 'read-page' }, Date.now());
    state.threadUpdates = updateResult.map;
    if (updateResult.changed) {
      saveThreadUpdates(state.threadUpdates);
      refreshFavoriteNavPanels();
    }
  }

  function enhanceReadPage(settings, state) {
    if (detectPageType(location.href) !== 'read') return;
    var tid = parseThreadId(location.href);
    if (tid) {
      state.read[tid] = Date.now();
      saveMap(READ_KEY, state.read);
      syncAutoBuyNavSuccessForThread(tid);
      restorePendingReadProgress(state, tid);
      bindReadProgressTracking(state, tid);
      bindReadPageJumpButtons(state, tid);
    }

    var posts = qsa('table.js-post');
    syncReadPageThreadUpdateState(state, tid);
    var originalAuthor = posts.length ? getPostAuthor(posts[0]) : '';
    bindNativeReadFavoriteSync(settings, state, tid, originalAuthor);
    createReadThreadSummaryCard(settings, state, posts, tid, originalAuthor);

    posts.forEach(function enhancePost(post, index) {
      var author = getPostAuthor(post);
      var content = qs('.tpc_content', post);
      if (!content) return;

      applyReadPostLayout(post, settings);
      attachAuthorHoverCard(post);

      post.classList.toggle('spx-post-hidden', matchesBlockRules({ title: content.textContent, author: author }, settings));
      if (settings.onlyOriginalAuthor && originalAuthor && author && author !== originalAuthor) {
        post.classList.add('spx-post-hidden');
      }
      syncHiddenPostShell(post);

      var toolsHost = getPostToolsHost(post, content);
      var existingTools = qs('.spx-post-tools', post);
      if (existingTools && existingTools.parentNode !== toolsHost) {
        toolsHost.insertBefore(existingTools, toolsHost.firstChild);
      }

      function openPostQuickReply(event) {
        if (event) {
          event.preventDefault();
          event.stopPropagation();
          if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation();
        }
        var floorLabel = getPostFloorLabel(index);
        var contextLabel = '回复 ' + (author || '用户') + ' · ' + floorLabel;
        var replyText = '回 ' + floorLabel + (author ? '(' + author + ')' : '') + ' 的帖子\n';
        openFloatingQuickReplyContext(contextLabel, replyText);
      }

      qsa('.readbot .huifu a,a[onclick*="postreply"]', post).forEach(function bindNativeQuickReply(link) {
        if (link.dataset.spxQuickReplyBound === '1') return;
        link.dataset.spxQuickReplyBound = '1';
        link.addEventListener('click', openPostQuickReply, true);
      });

      if (!existingTools) {
        var tools = createEl('div', 'spx-post-tools');
        var floor = createEl('span', '', getPostFloorLabel(index));
        var quickReply = createEl('button', '', '回复');
        var blockAuthor = createEl('button', '', '屏蔽此人');
        var copyLink = createEl('button', '', '复制链接');
        var extractResources = createEl('button', '', '资源');
        var jumpNextFloor = null;
        var jumpLastFloor = null;
        floor.style.marginRight = 'auto';
        quickReply.type = 'button';
        blockAuthor.type = 'button';
        copyLink.type = 'button';
        extractResources.type = 'button';
        extractResources.title = '提取本页资源链接,可按本楼、作者或类型复制';

        if (index === 0 && tid) {
          jumpNextFloor = createEl('button', '', '未读楼层');
          jumpLastFloor = createEl('button', '', '上次楼层');
          jumpNextFloor.type = 'button';
          jumpLastFloor.type = 'button';
          jumpNextFloor.title = '跳到上次未读位置';
          jumpLastFloor.title = '跳到上次读到的楼层';
          jumpNextFloor.dataset.spxReadJump = 'next';
          jumpNextFloor.dataset.spxReadTid = tid;
          jumpLastFloor.dataset.spxReadJump = 'last';
          jumpLastFloor.dataset.spxReadTid = tid;
        }

        blockAuthor.addEventListener('click', function addAuthorBlock() {
          if (!author) return;
          settings.authorKeywords = parseLineList(settings.authorKeywords.concat([author]).join('\n'));
          saveSettings(settings);
          enhanceAll(settings, state);
        });

        copyLink.addEventListener('click', function copyPostLink() {
          var hash = getPostAnchorHash(post);
          copyTextToClipboard(location.href.split('#')[0] + hash).catch(function noop() {});
        });

        extractResources.addEventListener('click', function openResources() {
          openResourcePanel(posts, index, author, { state: state });
        });

        quickReply.addEventListener('click', openPostQuickReply);

        tools.appendChild(floor);
        if (jumpNextFloor) tools.appendChild(jumpNextFloor);
        if (jumpLastFloor) tools.appendChild(jumpLastFloor);
        tools.appendChild(quickReply);
        tools.appendChild(blockAuthor);
        tools.appendChild(copyLink);
        tools.appendChild(extractResources);
        toolsHost.insertBefore(tools, toolsHost.firstChild);
      }
    });

    cleanupReadSeparators();
    createReadResourceRail(posts, state);
    enhancePreviewGallery(settings, posts);
    if (settings.foldQuotes) foldLongReadBlocks();
    enhanceAutoBuyPost(settings, state);
  }

  function fallbackCopyText(value) {
    return new Promise(function copyWithSelection(resolve, reject) {
      if (!document.body || typeof document.execCommand !== 'function') {
        reject(new Error('当前浏览器不支持复制'));
        return;
      }
      var textarea = createEl('textarea');
      textarea.value = value;
      textarea.setAttribute('readonly', 'readonly');
      textarea.style.position = 'fixed';
      textarea.style.left = '-9999px';
      textarea.style.top = '0';
      document.body.appendChild(textarea);
      textarea.select();
      var copied = false;
      try {
        copied = document.execCommand('copy');
      } catch (error) {
        copied = false;
      }
      textarea.remove();
      if (copied) resolve();
      else reject(new Error('复制失败'));
    });
  }

  function copyTextToClipboard(value) {
    var text = String(value || '');
    if (!text) return Promise.reject(new Error('没有可复制的地址'));
    if (
      typeof navigator !== 'undefined' &&
      navigator.clipboard &&
      typeof navigator.clipboard.writeText === 'function'
    ) {
      return navigator.clipboard.writeText(text).catch(function fallbackClipboard() {
        return fallbackCopyText(text);
      });
    }
    return fallbackCopyText(text);
  }

  function formatResourceDownloadFileName(timestamp) {
    var rawTime = Number(timestamp);
    var date = new Date(isFinite(rawTime) ? rawTime : Date.now());
    var pad = function padDatePart(value) {
      return String(value).padStart(2, '0');
    };
    return [
      'southplus-resources-',
      date.getFullYear(),
      pad(date.getMonth() + 1),
      pad(date.getDate()),
      '-',
      pad(date.getHours()),
      pad(date.getMinutes()),
      '.txt',
    ].join('');
  }

  function downloadBlobFile(filename, blob) {
    if (
      typeof Blob === 'undefined' ||
      typeof URL === 'undefined' ||
      typeof URL.createObjectURL !== 'function' ||
      !document.body ||
      !blob
    ) return false;
    var href = URL.createObjectURL(blob);
    var link = createEl('a');
    link.href = href;
    link.download = filename || 'download';
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.setTimeout(function revokeObjectUrl() {
      URL.revokeObjectURL(href);
    }, 0);
    return true;
  }

  function downloadTextFile(filename, text) {
    if (typeof Blob === 'undefined') return false;
    return downloadBlobFile(filename || formatResourceDownloadFileName(), new Blob([String(text || '')], { type: 'text/plain;charset=utf-8' }));
  }

  function exportResourceDownloadList(entries) {
    var text = formatResourceDownloadList(entries);
    if (!text) return false;
    return downloadTextFile(formatResourceDownloadFileName(), text);
  }

  function setTemporaryText(node, text, restoreText, delay) {
    if (!node) return;
    node.textContent = text;
    window.setTimeout(function restoreTextLater() {
      if (node.isConnected) node.textContent = restoreText;
    }, delay || 1400);
  }

  function isReadResourceRailCollapsed() {
    var storage = getStorage();
    return !!(storage && storage.getItem(READ_RESOURCE_RAIL_COLLAPSED_KEY) === '1');
  }

  function setReadResourceRailCollapsed(collapsed) {
    var storage = getStorage();
    if (storage) storage.setItem(READ_RESOURCE_RAIL_COLLAPSED_KEY, collapsed ? '1' : '0');
    qsa('#spx-read-resource-rail').forEach(function toggleRail(rail) {
      rail.hidden = !!collapsed;
    });
    qsa('#spx-read-resource-launcher').forEach(function toggleLauncher(launcher) {
      launcher.hidden = !collapsed;
    });
  }

  function closeReadResourceRail() {
    qsa('#spx-read-resource-rail,#spx-read-resource-launcher').forEach(function removeResourceRail(node) {
      node.remove();
    });
  }

  function getCurrentReadResourceRailEntries(posts, state) {
    return getResourceRailEntries(
      extractReadPageResourceLinks(posts || [], typeof location !== 'undefined' ? location.href : ''),
      state && state.resources
    );
  }

  function copyReadResourceRailEntries(entries, target, restoreText) {
    copyTextToClipboard(formatResourceLinks(entries)).then(
      function showReadResourceRailCopySuccess() {
        setTemporaryText(target, '已复制 ' + (entries || []).length + ' 条', restoreText);
      },
      function showReadResourceRailCopyFailure() {
        setTemporaryText(target, '复制失败', restoreText);
      }
    );
  }

  function copyReadResourceRailCodes(entries, target) {
    var text = formatResourceRailCodes(entries);
    if (!text) {
      setTemporaryText(target, '无口令', '复制口令');
      return;
    }
    copyTextToClipboard(text).then(
      function showReadResourceRailCodeCopySuccess() {
        setTemporaryText(target, '已复制口令', '复制口令');
      },
      function showReadResourceRailCodeCopyFailure() {
        setTemporaryText(target, '复制失败', '复制口令');
      }
    );
  }

  function saveReadResourceRailEntries(entries, state, target, restoreText) {
    if (!state) {
      setTemporaryText(target, '无法保存', restoreText || '保存当前');
      return;
    }
    var savedResources = saveResourceLinksToLibrary(entries, state.resources, getCurrentResourceSourceMeta());
    state.resources = savedResources.resources;
    saveResourceLibrary(state.resources);
    refreshResourceCenter();
    refreshReadResourceRail();
    setTemporaryText(target, savedResources.saved ? ('已保存 ' + savedResources.saved + ' 条') : '无可保存资源', restoreText || '保存当前');
  }

  function markReadResourceRailEntry(entry, state, status, target) {
    if (!entry || !state) return;
    var savedResources = saveResourceLinksToLibrary([entry], state.resources, getCurrentResourceSourceMeta());
    state.resources = savedResources.resources;
    updateResourceRecords(state, [entry.key], function markReadRailResource(record) {
      record.status = normalizeResourceStatus(status);
    });
    saveResourceCenterState(state);
    refreshReadResourceRail();
    setTemporaryText(target, getResourceStatusLabel(status), target.textContent || '标记');
  }

  function jumpToReadResourceRailEntry(entry, posts) {
    var post = posts && posts[Number(entry && entry.postIndex) || 0];
    if (!post) return;
    post.classList.add('spx-read-resource-floor-active');
    if (typeof post.scrollIntoView === 'function') {
      post.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
    window.setTimeout(function clearResourceFloorHighlight() {
      if (post.isConnected) post.classList.remove('spx-read-resource-floor-active');
    }, 1800);
  }

  function createReadResourceRail(posts, state) {
    if (detectPageType(location.href) !== 'read' || !document.body) return null;
    readResourceRailContext.posts = posts || [];
    readResourceRailContext.state = state;
    closeReadResourceRail();

    var entries = getCurrentReadResourceRailEntries(posts, state);
    if (!entries.length) return null;

    var rail = createEl('aside', 'spx-read-resource-rail');
    var launcher = createEl('button', 'spx-read-resource-launcher', '资源 ' + entries.length);
    rail.id = 'spx-read-resource-rail';
    rail.setAttribute('role', 'complementary');
    rail.setAttribute('aria-label', '当前帖子资源');
    launcher.id = 'spx-read-resource-launcher';
    launcher.type = 'button';
    launcher.title = '展开当前帖资源栏';

    function setRailAction(button, action, key, status) {
      button.dataset.spxRailAction = action;
      if (key) button.dataset.spxResourceKey = key;
      if (status) button.dataset.spxResourceStatus = status;
      return button;
    }

    function getRailEntryByKey(key) {
      return (rail.spxEntriesByKey || {})[String(key || '')] || null;
    }

    function handleReadResourceRailClick(event) {
      var target = event.target && event.target.closest
        ? event.target.closest('[data-spx-rail-action]')
        : null;
      if (!target || !rail.contains(target)) return;
      var action = target.dataset.spxRailAction;
      var visibleEntries = rail.spxVisibleEntries || [];
      var entry = getRailEntryByKey(target.dataset.spxResourceKey);
      var card = target.closest ? target.closest('.spx-read-resource-card') : null;

      if (action === 'collapse') {
        setReadResourceRailCollapsed(true);
        return;
      }
      if (action === 'copy-all') {
        copyReadResourceRailEntries(visibleEntries, target, '复制全部');
        return;
      }
      if (action === 'save-visible') {
        saveReadResourceRailEntries(visibleEntries, readResourceRailContext.state, target, '保存当前');
        return;
      }
      if (action === 'copy-codes') {
        copyReadResourceRailCodes(visibleEntries, target);
        return;
      }
      if (action === 'filter') {
        readResourceRailContext.filter = target.dataset.spxResourceRailFilter || 'all';
        renderRail();
        return;
      }
      if (!entry) return;
      if (action === 'jump') {
        if (card) card.classList.add('spx-active');
        jumpToReadResourceRailEntry(entry, readResourceRailContext.posts);
        window.setTimeout(function clearActiveCard() {
          if (card && card.isConnected) card.classList.remove('spx-active');
        }, 1800);
        return;
      }
      if (action === 'copy-one') {
        copyReadResourceRailEntries([entry], target, '复制');
        return;
      }
      if (action === 'save-one') {
        saveReadResourceRailEntries([entry], readResourceRailContext.state, target, entry.saved ? '已保存' : '保存');
        return;
      }
      if (action === 'status') {
        markReadResourceRailEntry(entry, readResourceRailContext.state, target.dataset.spxResourceStatus, target);
      }
    }

    function renderRail() {
      var allEntries = getCurrentReadResourceRailEntries(readResourceRailContext.posts, readResourceRailContext.state);
      if (!allEntries.length) {
        closeReadResourceRail();
        return;
      }
      var availableTypes = getAvailableResourceRailFilterTypes(allEntries);
      if (readResourceRailContext.filter !== 'all' && availableTypes.indexOf(readResourceRailContext.filter) === -1) {
        readResourceRailContext.filter = 'all';
      }
      var visibleEntries = filterResourceRailEntries(allEntries, readResourceRailContext.filter);
      var entriesByKey = {};
      rail.textContent = '';
      rail.spxVisibleEntries = visibleEntries;
      visibleEntries.forEach(function indexVisibleRailEntry(entry) {
        entriesByKey[entry.key] = entry;
      });
      rail.spxEntriesByKey = entriesByKey;
      launcher.textContent = '资源 ' + allEntries.length;

      var header = createEl('div', 'spx-read-resource-rail-head');
      var title = createEl('div');
      title.appendChild(createEl('strong', '', '当前帖资源'));
      title.appendChild(createEl('span', 'spx-read-resource-summary', formatResourceRailSummaryText(allEntries)));
      var collapseButton = createEl('button', '', '收起');
      collapseButton.type = 'button';
      setRailAction(collapseButton, 'collapse');
      header.appendChild(title);
      header.appendChild(collapseButton);
      rail.appendChild(header);

      var actions = createEl('div', 'spx-read-resource-actions');
      var copyAllButton = createEl('button', '', '复制全部');
      var saveButton = createEl('button', '', '保存当前');
      var copyCodeButton = createEl('button', '', '复制口令');
      [copyAllButton, saveButton, copyCodeButton].forEach(function setRailActionType(button) {
        button.type = 'button';
        actions.appendChild(button);
      });
      setRailAction(copyAllButton, 'copy-all');
      setRailAction(saveButton, 'save-visible');
      setRailAction(copyCodeButton, 'copy-codes');
      copyAllButton.disabled = !visibleEntries.length;
      saveButton.disabled = !getJumpResourceLinks(visibleEntries).length;
      copyCodeButton.disabled = !visibleEntries.some(function hasCode(entry) { return !!entry.accessCode; });
      rail.appendChild(actions);

      var filters = createEl('div', 'spx-read-resource-filters');
      [{ value: 'all', label: '全部' }].concat(availableTypes.map(function mapResourceRailType(type) {
        return { value: type, label: getResourceRailTypeLabel(type) };
      })).forEach(function appendResourceRailFilter(item) {
        var button = createEl('button', item.value === readResourceRailContext.filter ? 'spx-active' : '', item.label);
        button.type = 'button';
        button.dataset.spxResourceRailFilter = item.value;
        setRailAction(button, 'filter');
        filters.appendChild(button);
      });
      rail.appendChild(filters);

      var list = createEl('div', 'spx-read-resource-list');
      if (!visibleEntries.length) {
        list.appendChild(createEl('div', 'spx-watch-empty', '没有匹配的资源。'));
      }
      visibleEntries.forEach(function appendReadResourceRailEntry(entry) {
        var item = createEl('article', 'spx-read-resource-card');
        item.dataset.key = entry.key;
        item.dataset.type = entry.type;
        var top = createEl('div', 'spx-read-resource-card-top');
        top.appendChild(createEl('span', 'spx-read-resource-type', entry.typeLabel));
        top.appendChild(createEl('span', 'spx-status-badge spx-status-' + entry.status, entry.statusLabel));
        item.appendChild(top);
        var link = createEl('a', 'spx-read-resource-url', entry.url);
        link.href = entry.url;
        link.target = '_blank';
        link.rel = 'noreferrer';
        link.title = entry.url;
        item.appendChild(link);
        var metaParts = [entry.floorLabel, entry.author, entry.accessCode ? ('提取码 ' + entry.accessCode) : '', entry.note ? ('备注 ' + entry.note) : ''];
        item.appendChild(createEl('div', 'spx-read-resource-meta', metaParts.filter(Boolean).join(' · ') || '暂无来源'));
        var itemActions = createEl('div', 'spx-read-resource-card-actions');
        var jumpButton = createEl('button', '', '定位楼层');
        var copyButton = createEl('button', '', '复制');
        var saveOneButton = createEl('button', '', entry.saved ? '已保存' : '保存');
        var todoButton = createEl('button', '', '待下载');
        var doneButton = createEl('button', '', '已处理');
        var invalidButton = createEl('button', '', '失效');
        [jumpButton, copyButton, saveOneButton, todoButton, doneButton, invalidButton].forEach(function setupResourceCardButton(button) {
          button.type = 'button';
          itemActions.appendChild(button);
        });
        setRailAction(jumpButton, 'jump', entry.key);
        setRailAction(copyButton, 'copy-one', entry.key);
        setRailAction(saveOneButton, 'save-one', entry.key);
        setRailAction(todoButton, 'status', entry.key, 'todo');
        setRailAction(doneButton, 'status', entry.key, 'done');
        setRailAction(invalidButton, 'status', entry.key, 'invalid');
        saveOneButton.classList.toggle('spx-action-secondary', entry.saved);
        item.appendChild(itemActions);
        list.appendChild(item);
      });
      rail.appendChild(list);
      setReadResourceRailCollapsed(isReadResourceRailCollapsed());
    }

    rail.spxRender = renderRail;
    rail.addEventListener('click', handleReadResourceRailClick);
    launcher.addEventListener('click', function expandReadResourceRail() {
      setReadResourceRailCollapsed(false);
    });
    document.body.appendChild(rail);
    document.body.appendChild(launcher);
    renderRail();
    return rail;
  }

  function refreshReadResourceRail() {
    var rail = qs('#spx-read-resource-rail');
    if (rail && typeof rail.spxRender === 'function') {
      rail.spxRender();
      return;
    }
    if (readResourceRailContext.posts && readResourceRailContext.posts.length) {
      createReadResourceRail(readResourceRailContext.posts, readResourceRailContext.state);
    }
  }

  function closeResourcePanel() {
    var panel = qs('#spx-resource-panel');
    if (panel) panel.remove();
  }

  function closeAutoBuyResourceJump() {
    var panel = qs('#spx-auto-resource-jump');
    if (panel) panel.remove();
  }

  function mountAutoBuyResourceJump(panel) {
    if (!panel) return;
    var content = qs('#content') || qs('#main') || document.body;
    var firstPost = qsa('table.js-post', content)[0];
    if (firstPost && firstPost.parentNode) {
      firstPost.parentNode.insertBefore(panel, firstPost);
      return;
    }
    content.insertBefore(panel, content.firstChild);
  }

  function showAutoBuyResourceJump(links, state, savedCount) {
    var jumpLinks = getJumpResourceLinks(links);
    closeAutoBuyResourceJump();
    if (!jumpLinks.length || !document.body) return null;

    var panel = createEl('div', 'spx-auto-resource-jump');
    var title = createEl('strong', '', '购买完成,识别到资源:' + formatResourceJumpSummary(jumpLinks) + (savedCount ? ' · 已存资源库' : ''));
    var actions = createEl('div', 'spx-auto-resource-actions');
    var copyButton = createEl('button', '', '复制全部资源');
    var detailButton = createEl('button', '', '资源面板');
    var centerButton = createEl('button', '', '资源工作台');

    panel.id = 'spx-auto-resource-jump';
    copyButton.type = 'button';
    detailButton.type = 'button';
    centerButton.type = 'button';

    jumpLinks.slice(0, 8).forEach(function appendJumpLink(item, index) {
      var label = item.label || getResourceDisplayLabel(item);
      var link = createEl('a', '', label + (item.accessCode ? ' 提取码' : '') + ' #' + (index + 1));
      link.href = item.url;
      link.target = '_blank';
      link.rel = 'noreferrer';
      link.title = item.url + (item.accessCode ? ' 提取码:' + item.accessCode : '');
      actions.appendChild(link);
    });

    copyButton.addEventListener('click', function copyAutoBuyResources() {
      copyButton.disabled = true;
      copyTextToClipboard(formatResourceLinks(jumpLinks)).then(
        function showCopySuccess() {
          copyButton.disabled = false;
          setTemporaryText(copyButton, '已复制 ' + jumpLinks.length + ' 条', '复制全部资源');
        },
        function showCopyFailure() {
          copyButton.disabled = false;
          setTemporaryText(copyButton, '复制失败', '复制全部资源');
        }
      );
    });
    detailButton.addEventListener('click', function openAllAutoBuyResources() {
      openResourcePanel(qsa('table.js-post'), 0, '', { defaultScope: 'all', state: state });
    });
    centerButton.addEventListener('click', function openAutoBuyResourceCenter() {
      var panel = createResourceCenterPanel(null, state || { resources: loadResourceLibrary() });
      if (panel.spxRender) panel.spxRender();
      setCenterPanelHidden(panel, false);
    });

    actions.appendChild(copyButton);
    actions.appendChild(detailButton);
    actions.appendChild(centerButton);
    panel.appendChild(title);
    panel.appendChild(actions);
    mountAutoBuyResourceJump(panel);
    return panel;
  }

  function openResourcePanel(posts, currentPostIndex, currentAuthor, options) {
    if (!document.body) return;
    var config = options || {};
    var allLinks = extractReadPageResourceLinks(posts, location.href);
    closeResourcePanel();

    var panel = createEl('div', 'spx-watch-center spx-resource-panel');
    panel.id = 'spx-resource-panel';
    var header = createEl('div', 'spx-watch-center-header');
    var title = createEl('h3', '', '资源链接');
    var summary = createEl('span', 'spx-watch-summary');
    var closeButton = createEl('button', '', '关闭');
    var controls = createEl('div', 'spx-watch-controls');
    var scopeSelect = createEl('select');
    var typeSelect = createEl('select');
    var list = createEl('div', 'spx-watch-list');
    var copyButton = createEl('button', '', '复制当前');
    var saveButton = createEl('button', '', '保存当前');
    var visibleLinks = [];

    closeButton.type = 'button';
    closeButton.addEventListener('click', closeResourcePanel);
    header.appendChild(title);
    header.appendChild(summary);
    header.appendChild(closeButton);

    [
      { value: 'all', text: '全部楼层' },
      { value: 'floor', text: '仅本楼' },
      { value: 'author', text: currentAuthor ? '仅此作者' : '仅此作者' },
    ].forEach(function appendScopeOption(option) {
      var item = createEl('option', '', option.text);
      item.value = option.value;
      if (option.value === (config.defaultScope || 'floor')) item.selected = true;
      if (option.value === 'author' && !currentAuthor) item.disabled = true;
      scopeSelect.appendChild(item);
    });

    [
      { value: 'all', text: '全部类型' },
      { value: 'magnet', text: '磁力' },
      { value: 'ed2k', text: '电驴' },
      { value: 'torrent', text: '种子' },
      { value: 'archive', text: '压缩包' },
      { value: 'cloud', text: '网盘' },
      { value: 'image', text: '图片' },
      { value: 'external', text: '外链' },
    ].forEach(function appendTypeOption(option) {
      var item = createEl('option', '', option.text);
      item.value = option.value;
      if (option.value === config.defaultCategory) item.selected = true;
      typeSelect.appendChild(item);
    });

    controls.appendChild(scopeSelect);
    controls.appendChild(typeSelect);
    controls.appendChild(copyButton);
    controls.appendChild(saveButton);

    function renderLinks() {
      visibleLinks = filterResourceLinks(allLinks, {
        scope: scopeSelect.value,
        category: typeSelect.value,
        postIndex: currentPostIndex,
        author: currentAuthor,
      });
      list.textContent = '';
      summary.textContent = '当前 ' + visibleLinks.length + ' / 全部 ' + allLinks.length + ' 条';
      copyButton.disabled = !visibleLinks.length;
      saveButton.disabled = !getJumpResourceLinks(visibleLinks).length;

      if (!visibleLinks.length) {
        list.appendChild(createEl('div', 'spx-watch-empty', '没有匹配的资源链接。'));
        return;
      }

      visibleLinks.forEach(function appendResourceLink(item) {
        var row = createEl('div', 'spx-watch-item');
        var meta = createEl(
          'div',
          'spx-watch-meta',
          '[' + (item.label || getResourceDisplayLabel(item)) + '] ' +
            [item.floorLabel, item.author].filter(Boolean).join(' · ')
        );
        var link = createEl('a', 'spx-resource-url', item.url);
        link.href = item.url;
        link.target = '_blank';
        link.rel = 'noreferrer';
        row.appendChild(meta);
        if (item.accessCode) {
          row.appendChild(createEl('div', 'spx-watch-meta', '提取码:' + item.accessCode));
        }
        row.appendChild(link);
        list.appendChild(row);
      });
    }

    scopeSelect.addEventListener('change', renderLinks);
    typeSelect.addEventListener('change', renderLinks);
    copyButton.addEventListener('click', function copyVisibleResourceLinks() {
      copyButton.disabled = true;
      copyTextToClipboard(formatResourceLinks(visibleLinks)).then(
        function showCopySuccess() {
          copyButton.disabled = false;
          setTemporaryText(copyButton, '已复制 ' + visibleLinks.length + ' 条', '复制当前');
        },
        function showCopyFailure() {
          copyButton.disabled = false;
          setTemporaryText(copyButton, '复制失败', '复制当前');
        }
      );
    });
    saveButton.addEventListener('click', function saveVisibleResourceLinks() {
      var targetState = config.state;
      if (!targetState) {
        setTemporaryText(saveButton, '无法保存', '保存当前');
        return;
      }
      var savedResources = saveResourceLinksToLibrary(visibleLinks, targetState.resources, getCurrentResourceSourceMeta());
      targetState.resources = savedResources.resources;
      saveResourceLibrary(targetState.resources);
      refreshResourceCenter();
      refreshReadResourceRail();
      setTemporaryText(saveButton, savedResources.saved ? ('已保存 ' + savedResources.saved + ' 条') : '无可保存资源', '保存当前');
    });

    panel.appendChild(header);
    panel.appendChild(controls);
    panel.appendChild(list);
    document.body.appendChild(panel);
    renderLinks();
  }

  function closePreviewLightbox() {
    var lightbox = qs('#spx-preview-lightbox');
    if (!lightbox) return;
    if (typeof lightbox.spxClose === 'function') {
      lightbox.spxClose();
      return;
    }
    lightbox.remove();
  }

  function openPreviewLightbox(items, initialIndex) {
    var images = (items || []).filter(function hasPreviewSource(item) {
      return item && item.src;
    });
    if (!images.length || typeof document === 'undefined' || !document.body) return;

    closePreviewLightbox();

    var currentIndex = Math.min(
      images.length - 1,
      Math.max(0, Number(initialIndex) || 0)
    );
    var zoom = 1;
    var previousBodyOverflow = document.body.style.overflow || '';
    var copyStatusTimer = null;
    var closed = false;

    var lightbox = createEl('div', 'spx-preview-lightbox');
    lightbox.id = 'spx-preview-lightbox';
    lightbox.setAttribute('role', 'dialog');
    lightbox.setAttribute('aria-modal', 'true');
    lightbox.setAttribute('aria-label', '预览图灯箱');

    var shell = createEl('div', 'spx-preview-lightbox-shell');
    var toolbar = createEl('div', 'spx-preview-lightbox-toolbar');
    var counter = createEl('span', 'spx-preview-lightbox-counter');
    var actions = createEl('div', 'spx-preview-lightbox-actions');
    var stage = createEl('div', 'spx-preview-lightbox-stage');
    var viewport = createEl('div', 'spx-preview-lightbox-viewport');
    var canvas = createEl('div', 'spx-preview-lightbox-canvas');
    var image = createEl('img', 'spx-preview-lightbox-image');
    var caption = createEl('div', 'spx-preview-lightbox-caption');
    var urlText = createEl('span', 'spx-preview-lightbox-url');
    var help = createEl(
      'span',
      'spx-preview-lightbox-help',
      '←/→ 切图 · +/- 缩放 · 0 重置 · Esc 关闭'
    );
    var strip = createEl('div', 'spx-preview-lightbox-strip');

    function createActionButton(text, title, className, onClick) {
      var button = createEl('button', className || '', text);
      button.type = 'button';
      button.title = title;
      button.addEventListener('click', onClick);
      return button;
    }

    var zoomOutButton = createActionButton('−', '缩小(快捷键 -)', '', function zoomOut() {
      setZoom(zoom - 0.25);
    });
    var zoomText = createEl('span', 'spx-preview-lightbox-zoom', '100%');
    var zoomInButton = createActionButton('+', '放大(快捷键 +)', '', function zoomIn() {
      setZoom(zoom + 0.25);
    });
    var resetButton = createActionButton('重置', '恢复适应窗口大小(快捷键 0)', '', function resetZoom() {
      setZoom(1);
    });
    var copyButton = createActionButton('复制地址', '复制当前原图地址', '', function copyUrl() {
      copyButton.disabled = true;
      copyTextToClipboard(images[currentIndex].src).then(
        function showCopySuccess() {
          copyButton.textContent = '已复制';
          copyButton.disabled = false;
          clearTimeout(copyStatusTimer);
          copyStatusTimer = setTimeout(function restoreCopyButton() {
            if (copyButton.isConnected) copyButton.textContent = '复制地址';
          }, 1400);
        },
        function showCopyFailure() {
          copyButton.textContent = '复制失败';
          copyButton.disabled = false;
          clearTimeout(copyStatusTimer);
          copyStatusTimer = setTimeout(function restoreCopyButton() {
            if (copyButton.isConnected) copyButton.textContent = '复制地址';
          }, 1400);
        }
      );
    });
    var closeButton = createActionButton('关闭', '关闭灯箱(快捷键 Esc)', '', close);
    var previousButton = createActionButton(
      '‹',
      '上一张(快捷键 ←)',
      'spx-preview-lightbox-nav spx-preview-lightbox-prev',
      function showPrevious() {
        showImage(currentIndex - 1);
      }
    );
    var nextButton = createActionButton(
      '›',
      '下一张(快捷键 →)',
      'spx-preview-lightbox-nav spx-preview-lightbox-next',
      function showNext() {
        showImage(currentIndex + 1);
      }
    );

    function applyImageSize() {
      var naturalWidth = Number(image.naturalWidth || images[currentIndex].naturalWidth || 0);
      var naturalHeight = Number(image.naturalHeight || images[currentIndex].naturalHeight || 0);
      if (!naturalWidth || !naturalHeight || !viewport.clientWidth || !viewport.clientHeight) return;

      var sidePadding = window.innerWidth <= 760 ? 104 : 172;
      var verticalPadding = window.innerWidth <= 760 ? 52 : 76;
      var availableWidth = Math.max(120, viewport.clientWidth - sidePadding);
      var availableHeight = Math.max(120, viewport.clientHeight - verticalPadding);
      var fitScale = Math.min(
        availableWidth / naturalWidth,
        availableHeight / naturalHeight,
        1
      );
      var displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * zoom));
      var displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * zoom));
      var canvasPadding = window.innerWidth <= 760 ? 100 : 156;

      image.style.width = displayWidth + 'px';
      image.style.height = displayHeight + 'px';
      canvas.style.width = Math.max(viewport.clientWidth, displayWidth + canvasPadding) + 'px';
      canvas.style.height = Math.max(viewport.clientHeight, displayHeight + 72) + 'px';

      window.requestAnimationFrame(function centerZoomedImage() {
        viewport.scrollLeft = Math.max(0, (canvas.scrollWidth - viewport.clientWidth) / 2);
        viewport.scrollTop = Math.max(0, (canvas.scrollHeight - viewport.clientHeight) / 2);
      });
    }

    function setZoom(nextZoom) {
      zoom = clampPreviewZoom(nextZoom);
      zoomText.textContent = Math.round(zoom * 100) + '%';
      applyImageSize();
    }

    function showImage(nextIndex) {
      currentIndex = (nextIndex + images.length) % images.length;
      var nextSrc = images[currentIndex].src;
      zoom = 1;
      zoomText.textContent = '100%';
      counter.textContent = '图片 ' + (currentIndex + 1) + ' / ' + images.length;
      urlText.textContent = nextSrc;
      urlText.title = nextSrc;
      copyButton.textContent = '复制地址';
      image.alt = '预览图 ' + (currentIndex + 1);
      image.dataset.spxPreviewReady = '0';
      image.onload = function handleLightboxImageLoad() {
        markPreviewImageLoaded(image);
        applyImageSize();
      };
      image.onerror = function showImageError() {
        markPreviewImageLoaded(image);
        urlText.textContent = '原图加载失败:' + nextSrc;
      };
      image.src = nextSrc;
      if (image.complete && image.naturalWidth) {
        markPreviewImageLoaded(image);
        applyImageSize();
      }
      qsa('.spx-preview-lightbox-thumb', strip).forEach(function syncThumb(button, thumbIndex) {
        var active = thumbIndex === currentIndex;
        button.classList.toggle('spx-active', active);
        button.setAttribute('aria-current', active ? 'true' : 'false');
        if (active && button.scrollIntoView) button.scrollIntoView({ block: 'nearest', inline: 'center' });
      });
    }

    function handleKeydown(event) {
      var action = getPreviewLightboxKeyAction(event);
      if (!action) return;
      event.preventDefault();
      if (action === 'close') close();
      else if (action === 'previous') showImage(currentIndex - 1);
      else if (action === 'next') showImage(currentIndex + 1);
      else if (action === 'zoomIn') setZoom(zoom + 0.25);
      else if (action === 'zoomOut') setZoom(zoom - 0.25);
      else if (action === 'zoomReset') setZoom(1);
    }

    function handleResize() {
      applyImageSize();
    }

    function close() {
      if (closed) return;
      closed = true;
      clearTimeout(copyStatusTimer);
      document.removeEventListener('keydown', handleKeydown, true);
      window.removeEventListener('resize', handleResize);
      document.body.style.overflow = previousBodyOverflow;
      lightbox.remove();
    }

    lightbox.spxClose = close;
    lightbox.addEventListener('click', function closeFromBackdrop(event) {
      if (event.target === lightbox) close();
    });

    actions.appendChild(zoomOutButton);
    actions.appendChild(zoomText);
    actions.appendChild(zoomInButton);
    actions.appendChild(resetButton);
    actions.appendChild(copyButton);
    actions.appendChild(closeButton);
    toolbar.appendChild(counter);
    toolbar.appendChild(actions);
    canvas.appendChild(image);
    viewport.appendChild(canvas);
    stage.appendChild(viewport);
    stage.appendChild(previousButton);
    stage.appendChild(nextButton);
    caption.appendChild(urlText);
    caption.appendChild(help);
    shell.appendChild(toolbar);
    shell.appendChild(stage);
    shell.appendChild(caption);
    images.forEach(function appendLightboxThumb(item, index) {
      var thumbButton = createEl('button', 'spx-preview-lightbox-thumb');
      var thumbImage = createEl('img');
      thumbButton.type = 'button';
      thumbButton.title = '查看第 ' + (index + 1) + ' 张';
      thumbButton.dataset.spxPreviewThumbIndex = String(index);
      thumbImage.loading = 'lazy';
      thumbImage.decoding = 'async';
      thumbImage.alt = '预览缩略图 ' + (index + 1);
      preparePreviewImageReveal(thumbImage);
      thumbImage.src = item.src;
      if (thumbImage.complete && thumbImage.naturalWidth) markPreviewImageLoaded(thumbImage);
      thumbButton.appendChild(thumbImage);
      strip.appendChild(thumbButton);
    });
    strip.addEventListener('click', function handleLightboxThumbClick(event) {
      var thumb = event.target && event.target.closest
        ? event.target.closest('[data-spx-preview-thumb-index]')
        : null;
      if (!thumb || !strip.contains(thumb)) return;
      showImage(Number(thumb.dataset.spxPreviewThumbIndex) || 0);
    });
    if (images.length > 1) shell.appendChild(strip);
    lightbox.appendChild(shell);
    document.body.appendChild(lightbox);

    previousButton.hidden = images.length < 2;
    nextButton.hidden = images.length < 2;
    document.body.style.overflow = 'hidden';
    document.addEventListener('keydown', handleKeydown, true);
    window.addEventListener('resize', handleResize);
    showImage(currentIndex);
    closeButton.focus();
  }

  function enhancePreviewGallery(settings, posts) {
    restorePreviewGallery();
    if (!settings.unifiedPreviewGallery || !posts || !posts.length) return;

    var firstPost = posts[0];
    var content = qs('.tpc_content', firstPost);
    if (!content) return;

    var panel = createEl('section', 'spx-preview-panel spx-preview-drawer spx-preview-collapsed');
    panel.id = 'spx-preview-panel';
    var header = createEl('div', 'spx-preview-header');
    var title = createEl('strong', '', '预览图');
    var summary = createEl('span', 'spx-preview-summary');
    var actions = createEl('div', 'spx-preview-actions');
    var downloadAllButton = createEl('button', '', '下载全部');
    var copyAllButton = createEl('button', '', '复制全部链接');
    var copyFloorButton = createEl('button', '', '按楼层复制');
    var copyMarkdownButton = createEl('button', '', '复制Markdown');
    var largeOnlyButton = createEl('button', '', '只看大图');
    var masonryButton = createEl('button', '', '瀑布流');
    var drawerButton = createEl('button', '', '侧栏');
    var collapseDrawerButton = createEl('button', '', '收起');
    var downloadPanel = createEl('section', 'spx-preview-download');
    var downloadTop = createEl('div', 'spx-preview-download-top');
    var downloadTitle = createEl('strong', '', '下载准备中');
    var downloadPercent = createEl('span', 'spx-preview-download-percent', '0%');
    var downloadTrack = createEl('div', 'spx-preview-download-track');
    var downloadProgress = createEl('span', 'spx-preview-download-progress');
    var downloadCompact = createEl('div', 'spx-preview-download-compact');
    var downloadBadges = createEl('div', 'spx-preview-download-badges');
    var downloadActions = createEl('div', 'spx-preview-download-actions');
    var packDownloadedButton = createEl('button', 'spx-primary', '打包已完成');
    var retryFailedButton = createEl('button', '', '重试失败');
    var cancelDownloadButton = createEl('button', '', '取消');
    var downloadDetail = createEl('details', 'spx-preview-download-detail');
    var downloadDetailSummary = createEl('summary');
    var downloadDetailText = createEl('span', 'spx-preview-download-detail-text');
    var downloadQueue = createEl('div', 'spx-preview-download-queue');
    var grid = createEl('div', 'spx-preview-grid');
    var loadMoreButton = createEl('button', 'spx-preview-load-more', '加载更多图片');
    var drawerTab = createEl('button', 'spx-preview-drawer-tab', '预览图');
    var showLargeOnly = false;
    var previewImages = null;
    var visiblePreviewImages = [];
    var previewDownloadState = null;
    var renderedPreviewLimit = PREVIEW_GALLERY_BATCH_SIZE;
    var idleScanId = null;
    var idleScanType = '';

    downloadAllButton.type = 'button';
    copyAllButton.type = 'button';
    copyFloorButton.type = 'button';
    copyMarkdownButton.type = 'button';
    largeOnlyButton.type = 'button';
    masonryButton.type = 'button';
    drawerButton.type = 'button';
    collapseDrawerButton.type = 'button';
    packDownloadedButton.type = 'button';
    retryFailedButton.type = 'button';
    cancelDownloadButton.type = 'button';
    loadMoreButton.type = 'button';
    drawerTab.type = 'button';
    downloadAllButton.title = '下载当前筛选范围内的全部原图并打包为 ZIP';
    copyAllButton.title = '复制当前筛选范围内的全部原图地址';
    copyFloorButton.title = '按楼层分组复制当前筛选范围内的原图地址';
    copyMarkdownButton.title = '复制当前筛选范围内的 Markdown 图片清单';
    largeOnlyButton.title = '只显示尺寸较大的预览图';
    masonryButton.title = '切换瀑布流缩略图排列';
    drawerButton.title = '将预览图固定到右侧侧栏';
    collapseDrawerButton.title = '收起右侧预览图侧栏';
    packDownloadedButton.title = '跳过仍失败的图片,只将已下载图片打包为 ZIP';
    retryFailedButton.title = '重新下载失败图片,单轮最多重试 6 次';
    cancelDownloadButton.title = '取消当前批量下载';
    drawerTab.title = '展开预览图侧栏';
    loadMoreButton.title = '继续加载下一批预览图';
    largeOnlyButton.setAttribute('aria-pressed', 'false');
    masonryButton.setAttribute('aria-pressed', 'false');
    drawerButton.setAttribute('aria-pressed', 'false');
    collapseDrawerButton.hidden = true;
    downloadPanel.hidden = true;
    downloadTrack.appendChild(downloadProgress);
    downloadTop.appendChild(downloadTitle);
    downloadTop.appendChild(downloadPercent);
    downloadActions.appendChild(packDownloadedButton);
    downloadActions.appendChild(retryFailedButton);
    downloadActions.appendChild(cancelDownloadButton);
    downloadCompact.appendChild(downloadBadges);
    downloadCompact.appendChild(downloadActions);
    downloadDetailSummary.appendChild(downloadDetailText);
    downloadDetail.appendChild(downloadDetailSummary);
    downloadDetail.appendChild(downloadQueue);
    downloadPanel.appendChild(downloadTop);
    downloadPanel.appendChild(downloadTrack);
    downloadPanel.appendChild(downloadCompact);
    downloadPanel.appendChild(downloadDetail);

    function cancelIdlePreviewScan() {
      if (idleScanId === null) return;
      if (idleScanType === 'idle' && typeof window.cancelIdleCallback === 'function') {
        window.cancelIdleCallback(idleScanId);
      } else {
        window.clearTimeout(idleScanId);
      }
      idleScanId = null;
      idleScanType = '';
    }

    function scanPreviewImages() {
      if (previewImages) return previewImages;
      cancelIdlePreviewScan();
      var nextImages = [];
      posts.forEach(function collectPostImages(post, postIndex) {
        var postContent = qs('.tpc_content', post);
        if (!postContent) return;
        var floorLabel = getPostFloorLabel(postIndex);
        var author = getPostAuthor(post);
        qsa('img', postContent).forEach(function collectImage(img) {
          var item = {
            node: img,
            src: img.currentSrc || img.src,
            naturalWidth: img.naturalWidth,
            naturalHeight: img.naturalHeight,
            width: img.width,
            height: img.height,
            className: img.className,
            alt: img.alt,
            postIndex: postIndex,
            floorLabel: floorLabel,
            author: author,
          };
          if (isPreviewImageCandidate(item)) nextImages.push(item);
        });
      });

      var seen = {};
      previewImages = nextImages.filter(function uniqueImage(item) {
        if (!item.src || seen[item.src]) return false;
        seen[item.src] = true;
        return true;
      });
      visiblePreviewImages = previewImages.slice();
      return previewImages;
    }

    function getVisiblePreviewImages() {
      var images = previewImages || [];
      return showLargeOnly ? images.filter(isLargePreviewImage) : images.slice();
    }

    function scheduleIdlePreviewScan() {
      if (idleScanId !== null) return;
      var callback = function runIdlePreviewScan() {
        idleScanId = null;
        idleScanType = '';
        if (!panel.isConnected) return;
        var images = scanPreviewImages();
        if (!images.length && panel.classList.contains('spx-preview-collapsed')) {
          restorePreviewGallery();
          return;
        }
        visiblePreviewImages = getVisiblePreviewImages();
        if (panel.classList.contains('spx-preview-collapsed')) syncPreviewHeader();
        else renderPreviewGrid();
      };
      if (typeof window.requestIdleCallback === 'function') {
        idleScanType = 'idle';
        idleScanId = window.requestIdleCallback(callback, { timeout: 2200 });
      } else {
        idleScanType = 'timer';
        idleScanId = window.setTimeout(callback, 1200);
      }
    }

    function setPreviewButtonText(button, text, delay) {
      clearTimeout(button.spxPreviewTimer);
      if (!button.dataset.spxOriginalText) button.dataset.spxOriginalText = button.textContent;
      button.textContent = text;
      button.spxPreviewTimer = setTimeout(function restorePreviewButtonText() {
        if (button.isConnected) button.textContent = button.dataset.spxOriginalText || '复制全部链接';
        delete button.dataset.spxOriginalText;
      }, delay || 1400);
    }

    function copyPreviewText(button, text, count) {
      button.disabled = true;
      copyTextToClipboard(text).then(
        function showCopySuccess() {
          button.disabled = false;
          setPreviewButtonText(button, '已复制 ' + count + ' 条');
        },
        function showCopyFailure() {
          button.disabled = false;
          setPreviewButtonText(button, '复制失败');
        }
      );
    }

    function getPreviewDownloadCounts() {
      var entries = previewDownloadState && previewDownloadState.entries ? previewDownloadState.entries : [];
      return entries.reduce(function countPreviewDownload(result, entry) {
        result.total += 1;
        if (entry.status === 'done') result.done += 1;
        else if (entry.status === 'failed') result.failed += 1;
        else if (entry.status === 'downloading' || entry.status === 'retrying') result.active += 1;
        else if (entry.status === 'queued') result.queued += 1;
        else if (entry.status === 'cancelled') result.cancelled += 1;
        return result;
      }, { total: 0, done: 0, failed: 0, active: 0, queued: 0, cancelled: 0, packing: !!(previewDownloadState && previewDownloadState.packing) });
    }

    function getPreviewDownloadProgressPercent() {
      var entries = previewDownloadState && previewDownloadState.entries ? previewDownloadState.entries : [];
      if (!entries.length) return 0;
      var total = entries.reduce(function sumPreviewDownloadProgress(sum, entry) {
        if (entry.status === 'done') return sum + 100;
        if (entry.status === 'failed' || entry.status === 'cancelled') return sum;
        return sum + Math.max(0, Math.min(99, Number(entry.progress) || 0));
      }, 0);
      return Math.round(total / entries.length);
    }

    function createPreviewDownloadBadge(text, className) {
      return createEl('span', ['spx-preview-download-badge', className || ''].filter(Boolean).join(' '), text);
    }

    function getPreviewDownloadEntryStatusText(entry) {
      if (!entry) return '';
      if (entry.status === 'done') return '已完成';
      if (entry.status === 'failed') return entry.attempts >= PREVIEW_DOWNLOAD_MAX_RETRIES ? '已达上限' : '失败';
      if (entry.status === 'cancelled') return '已取消';
      if (entry.status === 'retrying') return '重试中';
      if (entry.status === 'downloading') return (entry.progress || 0) + '%';
      return '等待';
    }

    function renderPreviewDownloadQueue() {
      if (!previewDownloadState) return;
      var counts = getPreviewDownloadCounts();
      var activeEntries = previewDownloadState.entries.filter(function keepVisiblePreviewDownload(entry) {
        return entry.status !== 'done';
      });
      downloadQueue.textContent = '';

      activeEntries.forEach(function appendPreviewDownloadEntry(entry) {
        var row = createEl('div', 'spx-preview-download-row');
        var main = createEl('div');
        var name = createEl('div', 'spx-preview-download-name');
        var label = createEl('b', '', entry.fileName || formatPreviewImageDownloadFileName(entry.item, entry.index, entry.contentType));
        var size = createEl('em', '', entry.loaded && entry.total ? Math.round(entry.loaded / 1024) + ' / ' + Math.round(entry.total / 1024) + ' KB' : ('第 ' + entry.attempts + ' / ' + PREVIEW_DOWNLOAD_MAX_RETRIES + ' 次'));
        var track = createEl('div', 'spx-preview-download-track');
        var progress = createEl('span', 'spx-preview-download-progress');
        var meta = createEl('div', 'spx-preview-download-meta');
        var source = [entry.item && entry.item.floorLabel, entry.item && entry.item.author].filter(Boolean).join(' · ') || '预览图';
        var status = entry.error || getPreviewDownloadEntryStatusText(entry);
        progress.style.width = Math.max(0, Math.min(100, Number(entry.progress) || 0)) + '%';
        name.appendChild(label);
        name.appendChild(size);
        track.appendChild(progress);
        meta.appendChild(createEl('span', '', source));
        meta.appendChild(createEl('span', '', status));
        main.appendChild(name);
        main.appendChild(track);
        main.appendChild(meta);
        row.appendChild(main);
        if (entry.status === 'failed') {
          var retryButton = createEl('button', '', entry.attempts >= PREVIEW_DOWNLOAD_MAX_RETRIES ? '已达上限' : '重试');
          retryButton.type = 'button';
          retryButton.disabled = previewDownloadState.running || previewDownloadState.packing || entry.attempts >= PREVIEW_DOWNLOAD_MAX_RETRIES;
          retryButton.dataset.spxPreviewDownloadRetry = String(entry.index);
          row.appendChild(retryButton);
        } else {
          row.appendChild(createPreviewDownloadBadge(getPreviewDownloadEntryStatusText(entry), entry.status === 'failed' ? 'spx-fail' : ''));
        }
        downloadQueue.appendChild(row);
      });

      if (counts.done) {
        var doneRow = createEl('div', 'spx-preview-download-row');
        var doneMain = createEl('div');
        var doneName = createEl('div', 'spx-preview-download-name');
        var doneTrack = createEl('div', 'spx-preview-download-track');
        var doneProgress = createEl('span', 'spx-preview-download-progress');
        var doneMeta = createEl('div', 'spx-preview-download-meta');
        doneName.appendChild(createEl('b', '', '已完成 ' + counts.done + ' 张'));
        doneName.appendChild(createEl('em', '', '可打包'));
        doneProgress.style.width = '100%';
        doneTrack.appendChild(doneProgress);
        doneMeta.appendChild(createEl('span', '', '完成项默认合并,避免列表过长'));
        doneMeta.appendChild(createEl('span', '', '100%'));
        doneMain.appendChild(doneName);
        doneMain.appendChild(doneTrack);
        doneMain.appendChild(doneMeta);
        doneRow.appendChild(doneMain);
        doneRow.appendChild(createPreviewDownloadBadge('完成', 'spx-ok'));
        downloadQueue.appendChild(doneRow);
      }

      if (counts.failed && counts.done) {
        var report = createEl('div', 'spx-preview-download-report');
        var reportTitle = createEl('strong');
        reportTitle.appendChild(createEl('span', '', 'ZIP 打包'));
        reportTitle.appendChild(createEl('span', '', '可只打包已完成图片'));
        report.appendChild(reportTitle);
        report.appendChild(createEl('span', '', '失败图片和原始链接会写入 download-report.txt。'));
        downloadQueue.appendChild(report);
      }
    }

    function renderPreviewDownload() {
      if (!previewDownloadState || !previewDownloadState.entries.length) {
        downloadPanel.hidden = true;
        return;
      }
      var counts = getPreviewDownloadCounts();
      var percent = getPreviewDownloadProgressPercent();
      downloadPanel.hidden = false;
      downloadAllButton.disabled = previewDownloadState.running || previewDownloadState.packing;
      if (previewDownloadState.error) {
        downloadTitle.textContent = 'ZIP 打包失败:' + previewDownloadState.error;
      } else if (previewDownloadState.packed) {
        downloadTitle.textContent = counts.failed ? ('已打包 ' + counts.done + ' 张 · ' + counts.failed + ' 张失败已记录') : ('已打包 ' + counts.done + ' 张图片');
      } else {
        downloadTitle.textContent = getPreviewDownloadStatusSummary(counts);
      }
      downloadPercent.textContent = percent + '%';
      downloadProgress.style.width = percent + '%';
      downloadBadges.textContent = '';
      if (counts.done) downloadBadges.appendChild(createPreviewDownloadBadge(counts.done + ' 完成', 'spx-ok'));
      if (counts.active) downloadBadges.appendChild(createPreviewDownloadBadge(counts.active + ' 下载中', ''));
      if (counts.queued) downloadBadges.appendChild(createPreviewDownloadBadge(counts.queued + ' 等待', ''));
      if (counts.failed) downloadBadges.appendChild(createPreviewDownloadBadge(counts.failed + ' 失败', 'spx-fail'));
      if (counts.done && counts.failed && !counts.active && !counts.queued) downloadBadges.appendChild(createPreviewDownloadBadge('可打包', 'spx-zip'));
      packDownloadedButton.hidden = !counts.done;
      packDownloadedButton.disabled = previewDownloadState.packing;
      retryFailedButton.disabled = previewDownloadState.running || previewDownloadState.packing || !counts.failed;
      cancelDownloadButton.hidden = !previewDownloadState.running && !previewDownloadState.packing;
      cancelDownloadButton.disabled = previewDownloadState.packing;
      downloadDetailText.textContent = '下载详情:' + counts.active + ' 张下载中,' + counts.failed + ' 张失败,' + counts.done + ' 张已完成';
      renderPreviewDownloadQueue();
    }

    function formatPreviewDownloadError(error) {
      if (!error) return '下载失败';
      if (error.name === 'AbortError') return '已取消';
      var message = String(error.message || error || '下载失败');
      if (/Failed to fetch|NetworkError|Load failed|fetch/i.test(message)) return '网络或跨域限制';
      return message.slice(0, 80);
    }

    function readPreviewImageResponseBlob(response, entry) {
      var type = response && response.headers ? (response.headers.get('content-type') || '') : '';
      var total = Number(response && response.headers ? response.headers.get('content-length') : 0) || 0;
      entry.total = total;
      if (!response || !response.body || typeof response.body.getReader !== 'function') {
        return response.blob().then(function usePreviewResponseBlob(blob) {
          entry.progress = 100;
          entry.loaded = blob.size || total || 0;
          return { blob: blob, contentType: type || blob.type || '' };
        });
      }
      var reader = response.body.getReader();
      var chunks = [];
      var loaded = 0;
      function readNextChunk() {
        return reader.read().then(function handlePreviewChunk(result) {
          if (result.done) {
            entry.progress = 100;
            entry.loaded = loaded;
            return { blob: new Blob(chunks, { type: type || 'application/octet-stream' }), contentType: type };
          }
          var chunk = result.value;
          if (chunk) {
            chunks.push(chunk);
            loaded += chunk.length || chunk.byteLength || 0;
            entry.loaded = loaded;
            var nextProgress = total ? Math.floor((loaded / total) * 100) : Math.min(95, (Number(entry.progress) || 0) + 6);
            if (nextProgress !== entry.progress) {
              entry.progress = Math.max(1, Math.min(99, nextProgress));
              renderPreviewDownload();
            }
          }
          return readNextChunk();
        });
      }
      return readNextChunk();
    }

    function fetchPreviewDownloadBlob(entry) {
      var fetchImpl = typeof window !== 'undefined' && typeof window.fetch === 'function' ? window.fetch.bind(window) : null;
      if (!fetchImpl) return Promise.reject(new Error('当前浏览器不支持 fetch'));
      var controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
      var options = { credentials: 'same-origin', cache: 'force-cache' };
      if (controller) {
        options.signal = controller.signal;
        previewDownloadState.controllers.push(controller);
      }
      return fetchImpl(entry.src, options).then(function inspectPreviewDownloadResponse(response) {
        if (!response || !response.ok) throw new Error('HTTP ' + (response ? response.status : 0));
        return readPreviewImageResponseBlob(response, entry);
      }).finally(function removePreviewDownloadController() {
        if (!controller || !previewDownloadState) return;
        previewDownloadState.controllers = previewDownloadState.controllers.filter(function keepController(item) {
          return item !== controller;
        });
      });
    }

    function delayPreviewDownloadRetry(ms) {
      return new Promise(function waitPreviewDownload(resolve) {
        window.setTimeout(resolve, ms);
      });
    }

    function runPreviewDownloadEntry(entry) {
      function attemptPreviewDownload() {
        if (!previewDownloadState || previewDownloadState.cancelled) {
          entry.status = 'cancelled';
          entry.error = '已取消';
          return Promise.resolve();
        }
        entry.attempts += 1;
        entry.status = entry.attempts > 1 ? 'retrying' : 'downloading';
        entry.progress = 0;
        entry.error = '';
        renderPreviewDownload();
        return fetchPreviewDownloadBlob(entry).then(function markPreviewDownloadDone(result) {
          entry.status = 'done';
          entry.blob = result.blob;
          entry.contentType = result.contentType || (result.blob && result.blob.type) || '';
          entry.fileName = formatPreviewImageDownloadFileName(entry.item, entry.index, entry.contentType);
          entry.progress = 100;
          entry.error = '';
          renderPreviewDownload();
        }).catch(function handlePreviewDownloadFailure(error) {
          entry.error = formatPreviewDownloadError(error);
          if (previewDownloadState && previewDownloadState.cancelled) {
            entry.status = 'cancelled';
            renderPreviewDownload();
            return;
          }
          if (entry.attempts < PREVIEW_DOWNLOAD_MAX_RETRIES) {
            entry.status = 'retrying';
            renderPreviewDownload();
            return delayPreviewDownloadRetry(Math.min(1500, 220 * entry.attempts)).then(attemptPreviewDownload);
          }
          entry.status = 'failed';
          entry.progress = 0;
          renderPreviewDownload();
        });
      }
      return attemptPreviewDownload();
    }

    function runPreviewDownloadQueue(entries) {
      var queue = (entries || []).slice();
      var active = 0;
      return new Promise(function resolvePreviewDownloadQueue(resolve) {
        function pumpPreviewDownloadQueue() {
          if (!previewDownloadState || previewDownloadState.cancelled) {
            if (!active) resolve();
            return;
          }
          while (active < PREVIEW_DOWNLOAD_CONCURRENCY && queue.length) {
            active += 1;
            runPreviewDownloadEntry(queue.shift()).then(function finishOnePreviewDownload() {
              active -= 1;
              pumpPreviewDownloadQueue();
            });
          }
          if (!active && !queue.length) resolve();
        }
        pumpPreviewDownloadQueue();
      });
    }

    function createPreviewDownloadEntries(images) {
      return (images || []).map(function createPreviewDownloadEntry(item, index) {
        return {
          index: index,
          item: item,
          src: getPreviewImageSource(item),
          status: 'queued',
          progress: 0,
          attempts: 0,
          loaded: 0,
          total: 0,
          error: '',
          blob: null,
          fileName: formatPreviewImageDownloadFileName(item, index, ''),
        };
      }).filter(function keepPreviewDownloadEntry(entry) {
        return !!entry.src;
      });
    }

    function getPreviewDownloadedZipFiles() {
      if (!previewDownloadState) return [];
      var files = previewDownloadState.entries.filter(function keepDonePreviewDownload(entry) {
        return entry.status === 'done' && entry.blob;
      }).map(function mapDonePreviewDownload(entry) {
        return { name: entry.fileName || formatPreviewImageDownloadFileName(entry.item, entry.index, entry.contentType), blob: entry.blob };
      });
      var hasFailed = previewDownloadState.entries.some(function hasFailedPreviewDownload(entry) {
        return entry.status === 'failed';
      });
      if (hasFailed) {
        files.push({ name: 'download-report.txt', blob: new Blob([formatPreviewDownloadReport(previewDownloadState.entries)], { type: 'text/plain;charset=utf-8' }) });
      }
      return files;
    }

    function packPreviewDownloadedImages() {
      if (!previewDownloadState || previewDownloadState.packing) return;
      var files = getPreviewDownloadedZipFiles();
      if (!files.length) {
        downloadTitle.textContent = '没有已下载图片可打包';
        return;
      }
      previewDownloadState.packing = true;
      previewDownloadState.error = '';
      renderPreviewDownload();
      createPreviewZipBlob(files).then(function downloadPreviewZip(blob) {
        downloadBlobFile(formatPreviewImageArchiveFileName(), blob);
        previewDownloadState.packed = true;
        previewDownloadState.packing = false;
        renderPreviewDownload();
      }).catch(function handlePreviewZipFailure(error) {
        previewDownloadState.packing = false;
        previewDownloadState.error = formatPreviewDownloadError(error);
        downloadTitle.textContent = 'ZIP 打包失败:' + previewDownloadState.error;
        renderPreviewDownload();
      });
    }

    function finishPreviewDownloadQueue() {
      if (!previewDownloadState) return;
      previewDownloadState.running = false;
      previewDownloadState.controllers = [];
      renderPreviewDownload();
      var counts = getPreviewDownloadCounts();
      if (counts.done && !counts.failed && !counts.cancelled) packPreviewDownloadedImages();
    }

    function startPreviewDownload(entries) {
      var downloadEntries = (entries || []).filter(function keepEntryForPreviewDownload(entry) {
        return entry && entry.src && entry.status !== 'done';
      });
      if (!downloadEntries.length) {
        renderPreviewDownload();
        return;
      }
      previewDownloadState.running = true;
      previewDownloadState.cancelled = false;
      previewDownloadState.packing = false;
      previewDownloadState.packed = false;
      previewDownloadState.error = '';
      renderPreviewDownload();
      runPreviewDownloadQueue(downloadEntries).then(finishPreviewDownloadQueue);
    }

    function startPreviewDownloadAll() {
      if (previewDownloadState && previewDownloadState.running) return;
      ensurePreviewImages(false);
      previewDownloadState = {
        entries: createPreviewDownloadEntries(visiblePreviewImages),
        running: false,
        cancelled: false,
        packing: false,
        packed: false,
        error: '',
        controllers: [],
      };
      if (!previewDownloadState.entries.length) {
        renderPreviewDownload();
        setPreviewButtonText(downloadAllButton, '无可下载图片');
        return;
      }
      startPreviewDownload(previewDownloadState.entries);
    }

    function cancelPreviewDownload() {
      if (!previewDownloadState) return;
      previewDownloadState.cancelled = true;
      previewDownloadState.running = false;
      previewDownloadState.controllers.forEach(function abortPreviewDownload(controller) {
        try { controller.abort(); } catch (error) {}
      });
      previewDownloadState.entries.forEach(function markQueuedPreviewDownloadCancelled(entry) {
        if (entry.status === 'queued' || entry.status === 'downloading' || entry.status === 'retrying') {
          entry.status = 'cancelled';
          entry.error = '已取消';
        }
      });
      renderPreviewDownload();
    }

    function retryFailedPreviewDownloads(resetAll) {
      if (!previewDownloadState || previewDownloadState.running || previewDownloadState.packing) return;
      var failedEntries = previewDownloadState.entries.filter(function keepFailedPreviewDownload(entry) {
        return entry.status === 'failed' && (resetAll || entry.attempts < PREVIEW_DOWNLOAD_MAX_RETRIES);
      });
      failedEntries.forEach(function resetFailedPreviewDownload(entry) {
        entry.status = 'queued';
        entry.progress = 0;
        entry.loaded = 0;
        entry.total = 0;
        entry.error = '';
        if (resetAll) entry.attempts = 0;
      });
      startPreviewDownload(failedEntries);
    }

    function syncPreviewLayoutButtons() {
      var isDrawer = panel.classList.contains('spx-preview-drawer');
      var isMasonry = panel.classList.contains('spx-preview-masonry');
      drawerButton.textContent = isDrawer ? '回正文' : '侧栏';
      drawerButton.setAttribute('aria-pressed', isDrawer ? 'true' : 'false');
      masonryButton.setAttribute('aria-pressed', isMasonry ? 'true' : 'false');
      collapseDrawerButton.hidden = !isDrawer;
    }

    function syncPreviewHeader() {
      var scanned = Array.isArray(previewImages);
      var allImages = previewImages || [];
      var largeCount = scanned ? allImages.filter(isLargePreviewImage).length : 0;
      var renderState = getPreviewGalleryRenderState(visiblePreviewImages.length, renderedPreviewLimit);
      summary.textContent = scanned
        ? formatPreviewGallerySummary(allImages.length, visiblePreviewImages.length, renderState.rendered, showLargeOnly)
        : '展开后加载当前页图片';
      drawerTab.textContent = scanned && allImages.length ? ('预览图 ' + allImages.length) : '预览图';
      largeOnlyButton.hidden = !scanned || !allImages.length || largeCount === allImages.length;
      largeOnlyButton.setAttribute('aria-pressed', showLargeOnly ? 'true' : 'false');
      downloadAllButton.disabled = (previewDownloadState && previewDownloadState.running) || !scanned || !visiblePreviewImages.length;
      copyAllButton.disabled = !scanned || !visiblePreviewImages.length;
      copyFloorButton.disabled = !scanned || !visiblePreviewImages.length;
      copyMarkdownButton.disabled = !scanned || !visiblePreviewImages.length;
      loadMoreButton.hidden = !scanned || !renderState.hasMore;
      loadMoreButton.textContent = '加载更多图片(' + renderState.rendered + ' / ' + renderState.total + ')';
      syncPreviewLayoutButtons();
    }

    function renderPreviewGrid() {
      scanPreviewImages();
      visiblePreviewImages = getVisiblePreviewImages();
      grid.textContent = '';
      var renderState = getPreviewGalleryRenderState(visiblePreviewImages.length, renderedPreviewLimit);
      var renderedImages = visiblePreviewImages.slice(0, renderState.rendered);
      syncPreviewHeader();

      if (!visiblePreviewImages.length) {
        grid.appendChild(createEl('div', 'spx-preview-empty', '当前页没有符合条件的大图'));
        return;
      }

      renderedImages.forEach(function appendPreview(item, index) {
        var link = createEl('a', 'spx-preview-item');
        link.href = item.src;
        link.target = '_blank';
        link.rel = 'noreferrer';
        link.title = '在灯箱中查看第 ' + (index + 1) + ' 张图';
        link.dataset.spxPreviewIndex = String(index);

        var thumb = createEl('img');
        thumb.loading = 'lazy';
        thumb.decoding = 'async';
        thumb.alt = '预览图 ' + (index + 1);
        preparePreviewImageReveal(thumb);
        thumb.src = item.src;
        if (thumb.complete && thumb.naturalWidth) markPreviewImageLoaded(thumb);

        var hoverImage = createEl('img', 'spx-preview-hover-image');
        hoverImage.loading = 'lazy';
        hoverImage.decoding = 'async';
        preparePreviewImageReveal(hoverImage);
        hoverImage.dataset.src = item.src;
        hoverImage.alt = '预览图 ' + (index + 1) + ' 放大预览';

        var label = createEl('span', '', getPreviewImageMetaText(item, index));
        link.appendChild(thumb);
        link.appendChild(hoverImage);
        link.appendChild(label);
        grid.appendChild(link);
      });
    }

    function ensurePreviewImages(render) {
      var images = scanPreviewImages();
      if (render) renderPreviewGrid();
      else {
        visiblePreviewImages = getVisiblePreviewImages();
        syncPreviewHeader();
      }
      return images;
    }

    function loadDelegatedPreviewHoverImage(target) {
      var link = target && target.closest ? target.closest('.spx-preview-item') : null;
      if (!link || !grid.contains(link)) return;
      var hoverImage = qs('.spx-preview-hover-image', link);
      if (hoverImage && !hoverImage.src) {
        hoverImage.src = hoverImage.dataset.src || link.href;
        if (hoverImage.complete && hoverImage.naturalWidth) markPreviewImageLoaded(hoverImage);
      }
    }

    function loadNextPreviewBatch() {
      var renderState = getPreviewGalleryRenderState(visiblePreviewImages.length, renderedPreviewLimit);
      if (!renderState.hasMore) return;
      renderedPreviewLimit = renderState.nextLimit;
      renderPreviewGrid();
    }

    function handlePreviewPanelScroll() {
      if (loadMoreButton.hidden) return;
      if (panel.scrollTop + panel.clientHeight >= panel.scrollHeight - 160) loadNextPreviewBatch();
    }

    downloadAllButton.addEventListener('click', startPreviewDownloadAll);
    copyAllButton.addEventListener('click', function copyAllPreviewLinks() {
      ensurePreviewImages(false);
      copyPreviewText(copyAllButton, formatPreviewImageLinks(visiblePreviewImages), visiblePreviewImages.length);
    });
    copyFloorButton.addEventListener('click', function copyPreviewLinksByFloor() {
      ensurePreviewImages(false);
      copyPreviewText(copyFloorButton, formatPreviewImageLinksByFloor(visiblePreviewImages), visiblePreviewImages.length);
    });
    copyMarkdownButton.addEventListener('click', function copyPreviewMarkdownLinks() {
      ensurePreviewImages(false);
      copyPreviewText(copyMarkdownButton, formatPreviewImageMarkdownLinks(visiblePreviewImages), visiblePreviewImages.length);
    });
    packDownloadedButton.addEventListener('click', packPreviewDownloadedImages);
    retryFailedButton.addEventListener('click', function retryAllFailedPreviewDownloads() {
      retryFailedPreviewDownloads(true);
    });
    cancelDownloadButton.addEventListener('click', cancelPreviewDownload);
    downloadQueue.addEventListener('click', function handlePreviewDownloadQueueClick(event) {
      var button = event.target && event.target.closest ? event.target.closest('[data-spx-preview-download-retry]') : null;
      if (!button || !downloadQueue.contains(button) || !previewDownloadState) return;
      var index = Number(button.dataset.spxPreviewDownloadRetry);
      var entry = previewDownloadState.entries[index];
      if (!entry || entry.status !== 'failed') return;
      entry.status = 'queued';
      entry.progress = 0;
      entry.loaded = 0;
      entry.total = 0;
      entry.error = '';
      startPreviewDownload([entry]);
    });

    largeOnlyButton.addEventListener('click', function toggleLargeOnly() {
      ensurePreviewImages(false);
      showLargeOnly = !showLargeOnly;
      renderedPreviewLimit = PREVIEW_GALLERY_BATCH_SIZE;
      renderPreviewGrid();
    });
    masonryButton.addEventListener('click', function togglePreviewMasonry() {
      panel.classList.toggle('spx-preview-masonry');
      syncPreviewLayoutButtons();
    });
    drawerButton.addEventListener('click', function togglePreviewDrawer() {
      var nextDrawer = !panel.classList.contains('spx-preview-drawer');
      panel.classList.toggle('spx-preview-drawer', nextDrawer);
      panel.classList.remove('spx-preview-collapsed');
      syncPreviewLayoutButtons();
    });
    collapseDrawerButton.addEventListener('click', function collapsePreviewDrawer() {
      panel.classList.add('spx-preview-collapsed');
    });
    drawerTab.addEventListener('click', function expandPreviewDrawer() {
      panel.classList.remove('spx-preview-collapsed');
      ensurePreviewImages(true);
    });
    loadMoreButton.addEventListener('click', loadNextPreviewBatch);
    panel.addEventListener('scroll', handlePreviewPanelScroll);
    grid.addEventListener('click', function handlePreviewGridClick(event) {
      var link = event.target && event.target.closest ? event.target.closest('.spx-preview-item') : null;
      if (!link || !grid.contains(link)) return;
      if (event.button > 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
      event.preventDefault();
      openPreviewLightbox(visiblePreviewImages, Number(link.dataset.spxPreviewIndex) || 0);
    });
    grid.addEventListener('mouseover', function handlePreviewGridHover(event) {
      loadDelegatedPreviewHoverImage(event.target);
    });
    grid.addEventListener('focusin', function handlePreviewGridFocus(event) {
      loadDelegatedPreviewHoverImage(event.target);
    });

    actions.appendChild(downloadAllButton);
    actions.appendChild(copyAllButton);
    actions.appendChild(copyFloorButton);
    actions.appendChild(copyMarkdownButton);
    actions.appendChild(largeOnlyButton);
    actions.appendChild(masonryButton);
    actions.appendChild(drawerButton);
    actions.appendChild(collapseDrawerButton);
    header.appendChild(title);
    header.appendChild(summary);
    header.appendChild(actions);
    panel.appendChild(header);
    panel.appendChild(downloadPanel);
    panel.appendChild(grid);
    panel.appendChild(loadMoreButton);
    panel.appendChild(drawerTab);
    panel.spxCleanup = function cleanupPreviewPanel() {
      cancelIdlePreviewScan();
      cancelPreviewDownload();
    };
    syncPreviewHeader();

    mountPreviewPanel(firstPost, content, panel);
    scheduleIdlePreviewScan();
  }

  function restorePreviewGallery() {
    closePreviewLightbox();
    var panel = qs('#spx-preview-panel');
    if (panel) {
      if (typeof panel.spxCleanup === 'function') panel.spxCleanup();
      panel.remove();
    }
    var split = qs('.spx-post-body-split');
    if (split) {
      var content = qs('.tpc_content', split);
      if (content && split.parentNode) split.parentNode.insertBefore(content, split);
      split.remove();
    }
    qsa('[data-spx-preview-source="1"]').forEach(function restoreImage(img) {
      img.classList.remove('spx-preview-source');
      delete img.dataset.spxPreviewSource;
    });
  }

  function isEmptyReadSeparatorNode(node) {
    if (!node) return false;
    if (node.classList && node.classList.contains('js-post')) return false;
    if (node.closest && node.closest('table.js-post')) return false;
    if (qs('table.js-post,.tpc_content,.pages,.page,#breadcrumbs,form,input,textarea,select,button,a[href],img,iframe,video,object,embed', node)) return false;

    var text = compactText(node.textContent);
    if (text) return false;

    return true;
  }

  function cleanupReadSeparators() {
    if (detectPageType(location.href) !== 'read') return;
    qsa('#content>*:not(.js-post),#main>*:not(.js-post)').forEach(function hideSeparator(node) {
      if (!isEmptyReadSeparatorNode(node)) return;
      node.dataset.spxReadSeparatorHidden = '1';
      if (node.style && typeof node.style.setProperty === 'function') {
        node.style.setProperty('display', 'none', 'important');
      } else if (node.style) {
        node.style.display = 'none';
      }
    });
  }

  function foldLongReadBlocks() {
    qsa('.quote, blockquote, .blockquote, .tpc_content .f12').forEach(function fold(node) {
      if (node.dataset.spxFolded) return;
      if ((node.textContent || '').length < 220 && node.scrollHeight < 150) return;
      node.dataset.spxFolded = '1';
      node.classList.add('spx-folded-quote');
      var button = createEl('button', '', '展开引用');
      button.style.margin = '4px 0 8px';
      button.addEventListener('click', function expand() {
        node.classList.remove('spx-folded-quote');
        button.remove();
      });
      node.insertAdjacentElement('afterend', button);
    });
  }

  function getQuickReplyEditor(root) {
    var scope = root || document;
    var selectors = [
      'textarea[name="atc_content"]',
      'textarea[name="content"]',
      'textarea#atc_content',
      'textarea[id*="content"]',
      'textarea',
    ];
    for (var index = 0; index < selectors.length; index += 1) {
      var editor = qsa(selectors[index], scope).filter(isQuickReplyEditorCandidate)[0];
      if (editor) return editor;
    }
    return null;
  }

  function isQuickReplyEditorCandidate(editor) {
    if (!editor) return false;
    if (editor.disabled || editor.readOnly) return false;
    if (
      editor.closest &&
      editor.closest('#spx-settings,#spx-toolbox,#spx-watch-center,#spx-history-center,#spx-auto-buy-center,#spx-resource-center,#spx-read-resource-rail,#spx-preview-lightbox,#spx-quick-reply')
    ) {
      return false;
    }
    return true;
  }

  function createQuickReplyRequest(form, submitter, pageUrl, FormDataCtor) {
    if (!form || typeof FormDataCtor !== 'function') return null;
    if (String(form.method || '').toUpperCase() !== 'POST') return null;

    var body;
    var url;
    try {
      body = new FormDataCtor(form);
      var formAction = typeof form.getAttribute === 'function'
        ? form.getAttribute('action')
        : form.action;
      url = new URL(formAction || '', pageUrl).href;
    } catch (error) {
      return null;
    }
    if (!body || typeof body.has !== 'function' || typeof body.append !== 'function') return null;

    if (submitter && submitter.name && !body.has(submitter.name)) {
      body.append(submitter.name, submitter.value == null ? '' : submitter.value);
    }

    return {
      url: url,
      options: {
        method: 'POST',
        body: body,
        credentials: 'include',
        redirect: 'follow',
        cache: 'no-store',
      },
    };
  }

  function getQuickReplyFormValue(request, name) {
    var body = request && request.options && request.options.body;
    if (!body || typeof body.get !== 'function') return '';
    try {
      var value = body.get(name);
      return value == null ? '' : String(value).trim();
    } catch (error) {
      return '';
    }
  }

  function buildQuickReplyLastPageUrl(pageUrl, tid) {
    var id = String(tid || '').trim();
    if (!id) return '';
    var parsed = new URL(String(pageUrl || ''), 'https://south-plus.org/');
    return parsed.origin + '/read.php?tid=' + encodeURIComponent(id) + '&page=e#a';
  }

  function resolveQuickReplyRefreshUrl(pageUrl, request, submitResponse) {
    var baseUrl = pageUrl || (typeof location !== 'undefined' ? location.href : 'https://south-plus.org/');
    var responseUrl = '';

    try {
      responseUrl = submitResponse && submitResponse.url
        ? new URL(String(submitResponse.url), baseUrl).href
        : '';
    } catch (error) {
      responseUrl = '';
    }

    if (responseUrl && detectPageType(responseUrl) === 'read') return responseUrl;

    var tid =
      getQuickReplyFormValue(request, 'tid') ||
      parseThreadId(responseUrl) ||
      parseThreadId(baseUrl);
    return tid ? buildQuickReplyLastPageUrl(responseUrl || baseUrl, tid) : baseUrl;
  }

  function shouldUseQuickReplySubmitHtml(html, submitResponse) {
    var text = String(html || '');
    if (!text) return false;
    var responseUrl = '';
    try {
      responseUrl = submitResponse && submitResponse.url ? String(submitResponse.url) : '';
    } catch (error) {
      responseUrl = '';
    }
    if (responseUrl && detectPageType(responseUrl) === 'read') return true;
    return /(?:\bjs-post\b|\btpc_content\b|id=["']read_tpc["'])/i.test(text);
  }

  function performQuickReplySubmit(options) {
    if (
      !options ||
      !options.request ||
      typeof options.fetch !== 'function' ||
      typeof options.applyHtml !== 'function'
    ) {
      return Promise.resolve(false);
    }

    var setPending = typeof options.setPending === 'function' ? options.setPending : function noop() {};
    var onError = typeof options.onError === 'function' ? options.onError : null;
    var shouldResetPending = false;
    var refreshUrl = '';

    function reportQuickReplyError(error) {
      if (!onError) return;
      try {
        onError(error);
      } catch (onErrorFailure) {}
    }

    return Promise.resolve()
      .then(function startQuickReplySubmit() {
        if (typeof options.isPending === 'function' && options.isPending()) return false;

        shouldResetPending = true;
        setPending(true);

        return Promise.resolve()
          .then(function submitReply() {
            return options.fetch(options.request.url, options.request.options);
          })
          .then(function checkSubmitResponse(response) {
            if (!response || response.ok !== true) throw new Error('快捷回复提交失败');
            refreshUrl = typeof options.resolveRefreshUrl === 'function'
              ? options.resolveRefreshUrl(response)
              : resolveQuickReplyRefreshUrl(options.pageUrl, options.request, response);
            if (typeof response.text !== 'function') return false;
            return readScriptResponseText(response, { mode: 'action', label: '快捷回复' })
              .then(function applySubmitHtml(html) {
                if (!shouldUseQuickReplySubmitHtml(html, response)) return false;
                return options.applyHtml(html, refreshUrl || options.pageUrl) !== false;
              });
          })
          .then(function reloadThreadWhenSubmitHtmlUnavailable(appliedSubmitHtml) {
            if (appliedSubmitHtml) return true;
            return options.fetch(refreshUrl || options.pageUrl, {
              credentials: 'include',
              cache: 'no-store',
            });
          })
          .then(function checkReloadResponse(response) {
            if (response === true) return true;
            if (!response || response.ok !== true) throw new Error('重新加载帖子失败');
            return readScriptResponseText(response, { mode: 'action', label: '快捷回复刷新' });
          })
          .then(function applyReloadedHtml(html) {
            if (html === true) return true;
            if (options.applyHtml(html, refreshUrl || options.pageUrl) === false) throw new Error('无法更新帖子内容');
            return true;
          });
      })
      .catch(function handleQuickReplyError(error) {
        reportQuickReplyError(error);
        return false;
      })
      .then(function finishQuickReply(result) {
        if (!shouldResetPending) return result;
        try {
          setPending(false);
        } catch (error) {
          reportQuickReplyError(error);
          return false;
        }
        return result;
      });
  }

  function submitQuickReplyNative(editor) {
    if (!editor) return false;
    var form = editor.closest && editor.closest('form');
    if (form) {
      var submitter = getQuickReplySubmitter(form);
      if (submitter && typeof submitter.click === 'function') {
        submitter.click();
        return true;
      }
      if (typeof form.requestSubmit === 'function') {
        form.requestSubmit();
        return true;
      }
      if (typeof form.submit === 'function') {
        form.submit();
        return true;
      }
    }

    var pageSubmit =
      qs('input[type="submit"]') ||
      qs('button[type="submit"]') ||
      qs('input[name="submit"]') ||
      qs('button[name="submit"]');
    if (pageSubmit && typeof pageSubmit.click === 'function') {
      pageSubmit.click();
      return true;
    }
    return false;
  }

  function isQuickReplySubmitter(node) {
    if (!node || !node.tagName) return false;
    var tag = String(node.tagName || '').toUpperCase();
    var type = String(node.getAttribute && node.getAttribute('type') || '').toLowerCase();
    if (tag === 'BUTTON') return !type || type === 'submit';
    if (tag === 'INPUT') return type === 'submit' || type === 'image';
    return false;
  }

  function getQuickReplySubmitter(form, preferred) {
    if (!form) return null;
    if (preferred && isQuickReplySubmitter(preferred)) return preferred;
    if (form.spxQuickReplySubmitter && isQuickReplySubmitter(form.spxQuickReplySubmitter)) {
      return form.spxQuickReplySubmitter;
    }
    return (
      qs('input[type="submit"]', form) ||
      qs('button[type="submit"]', form) ||
      qs('button:not([type])', form) ||
      qs('input[name="submit"]', form) ||
      qs('button[name="submit"]', form)
    );
  }

  function getQuickReplyClickedSubmitter(target, form) {
    if (!target || !form) return null;
    var node = target.closest ? target.closest('input,button') : target;
    if (!node || (typeof form.contains === 'function' && !form.contains(node))) return null;
    return isQuickReplySubmitter(node) ? node : null;
  }

  function setQuickReplyPanelPending(panel, pending) {
    if (!panel) return;
    if (pending) {
      panel.dataset.spxQuickReplyPending = '1';
    } else {
      delete panel.dataset.spxQuickReplyPending;
    }
    qsa('button', panel).forEach(function toggleQuickReplyButton(button) {
      button.disabled = !!pending;
    });

    var status = qs('.spx-quick-reply-status', panel);
    if (pending && status) {
      status.classList.remove('spx-error');
      status.textContent = '正在提交并更新帖子…';
    }
  }

  function setQuickReplyFormPending(form, pending) {
    if (!form || !form.dataset) return;
    if (pending) {
      form.dataset.spxQuickReplyPending = '1';
    } else {
      delete form.dataset.spxQuickReplyPending;
    }
    qsa('input[type="submit"],button[type="submit"],button:not([type])', form)
      .forEach(function toggleFormSubmit(submitter) {
        submitter.disabled = !!pending;
      });
  }

  function setQuickReplyPending(form, panel, pending) {
    setQuickReplyFormPending(form, pending);
    setQuickReplyPanelPending(panel, pending);
  }

  function setQuickReplyPanelError(panel, error) {
    if (!panel) return;
    var status = qs('.spx-quick-reply-status', panel);
    if (!status) return;
    status.classList.add('spx-error');
    status.textContent = error && error.spxRateLimited
      ? '站点提示操作频繁,内容已保留,请稍后重试。'
      : '提交失败,内容已保留,请重试。';
  }

  function submitQuickReplyAjax(editor, form, submitter, settings, state, panel) {
    if (!editor || !form) return false;
    var fetchImpl = typeof window !== 'undefined' && typeof window.fetch === 'function'
      ? function queueQuickReplyFetch(url, options) {
        return requestWithPolicy(url, options, {
          mode: 'action',
          label: '快捷回复',
          networkFriendly: isNetworkFriendlyMode(settings),
        });
      }
      : null;
    var FormDataCtor = typeof FormData === 'function' ? FormData : null;
    var request = createQuickReplyRequest(
      form,
      submitter,
      location.href,
      FormDataCtor
    );

    var pageType = detectPageType(location.href);
    if (!request || !fetchImpl || (pageType !== 'read' && pageType !== 'post')) {
      return false;
    }

    return performQuickReplySubmit({
      request: request,
      pageUrl: location.href,
      fetch: fetchImpl,
      isPending: function isPending() {
        return !!(
          (panel && panel.dataset.spxQuickReplyPending === '1') ||
          (form.dataset && form.dataset.spxQuickReplyPending === '1')
        );
      },
      setPending: function setPending(pending) {
        setQuickReplyPending(form, panel, pending);
      },
      applyHtml: function applyHtml(html, refreshUrl) {
        var currentUrl = location.href;
        var shouldReplaceUrl =
          refreshUrl &&
          detectPageType(refreshUrl) === 'read' &&
          refreshUrl !== currentUrl &&
          typeof window !== 'undefined' &&
          window.history &&
          typeof window.history.replaceState === 'function';

        if (shouldReplaceUrl) {
          window.history.replaceState(window.history.state, document.title, refreshUrl);
        }

        if (replaceReadPageContent(html, settings, state)) return true;

        if (shouldReplaceUrl) {
          window.history.replaceState(window.history.state, document.title, currentUrl);
        }
        return false;
      },
      onError: function onError(error) {
        setQuickReplyPanelError(panel, error);
      },
    });
  }

  function submitQuickReply(editor, settings, state, submitter) {
    if (!editor) return false;
    var form = editor.closest && editor.closest('form');
    var panel = qs('#spx-quick-reply');
    var ajaxResult = submitQuickReplyAjax(
      editor,
      form,
      getQuickReplySubmitter(form, submitter),
      settings,
      state,
      panel
    );
    if (ajaxResult === false) return submitQuickReplyNative(editor);
    return ajaxResult;
  }

  function bindQuickReplyFormSubmit(editor, settings, state, panel) {
    if (!editor || !editor.closest) return false;
    var form = editor.closest('form');
    if (!form || form.dataset.spxQuickReplySubmitBound === '1') return false;
    form.dataset.spxQuickReplySubmitBound = '1';

    form.addEventListener('click', function rememberQuickReplySubmitter(event) {
      var submitter = getQuickReplyClickedSubmitter(event.target, form);
      if (submitter) form.spxQuickReplySubmitter = submitter;
    }, true);

    form.addEventListener('submit', function handleQuickReplySubmit(event) {
      var submitter = getQuickReplySubmitter(form, event && event.submitter);
      var currentPanel = qs('#spx-quick-reply') || panel;
      var ajaxResult = submitQuickReplyAjax(editor, form, submitter, settings, state, currentPanel);
      if (ajaxResult === false) return;
      if (event && typeof event.preventDefault === 'function') event.preventDefault();
    });
    return true;
  }

  function ensureQuickReplyControlId(control, prefix) {
    if (!control) return '';
    if (!control.id) {
      control.id = prefix + '-' + Math.random().toString(36).slice(2, 9);
    }
    return control.id;
  }

  function getQuickReplyAttachmentInput(form) {
    if (!form) return null;
    return qsa('input[type="file"]', form).filter(function usableAttachmentInput(input) {
      return input && !input.disabled;
    })[0] || null;
  }

  function getQuickReplyAttachmentDescriptionInput(form, attachmentInput) {
    if (!form || !attachmentInput) return null;
    var row = attachmentInput.closest && attachmentInput.closest('tr,div,p,section');
    var rowInputs = row ? qsa('input[type="text"],input:not([type])', row).filter(function usableDescInput(input) {
      return input && input !== attachmentInput && !input.disabled && !input.readOnly;
    }) : [];
    if (rowInputs.length) return rowInputs[0];
    return qsa('input[type="text"],input:not([type])', form).filter(function likelyDescInput(input) {
      var name = String(input.name || input.id || input.className || '').toLowerCase();
      return !input.disabled && !input.readOnly && /(?:desc|describe|intro|atc)/.test(name);
    })[0] || null;
  }

  function getQuickReplyAttachmentFiles(input) {
    if (!input || !input.files) return [];
    return Array.prototype.slice.call(input.files).filter(function keepImageAttachment(file) {
      return file && (!file.type || /^image\//i.test(file.type));
    });
  }

  function formatQuickReplyFileSize(size) {
    var value = Number(size);
    if (!isFinite(value) || value <= 0) return '未知大小';
    if (value < 1024 * 1024) return Math.max(1, Math.round(value / 1024)) + ' KB';
    return (value / 1024 / 1024).toFixed(1) + ' MB';
  }

  function formatQuickReplyAttachmentSummary(files) {
    var count = (files || []).length;
    return count
      ? '已选择 ' + count + ' 张图片;正式提交会随原站回复表单一起发送。'
      : '沿用原站附件上传;正式提交时随原站回复表单一起发送。';
  }

  function openFloatingQuickReplyContext(label, text) {
    var panel = qs('#spx-quick-reply');
    if (!panel || typeof panel.spxOpenWithContext !== 'function') return false;
    panel.spxOpenWithContext(label, text);
    return true;
  }

  function createQuickReplyPanel(settings, editor, state) {
    var replies = parseQuickReplyList((settings && settings.quickReplies || []).join('\n'));
    if (!editor || !replies.length) return null;

    var form = editor.closest && editor.closest('form');
    var attachmentInput = getQuickReplyAttachmentInput(form);
    var attachmentDesc = getQuickReplyAttachmentDescriptionInput(form, attachmentInput);
    var attachmentId = ensureQuickReplyControlId(attachmentInput, 'spx-quick-reply-attachment');

    if (attachmentInput && !attachmentInput.getAttribute('accept')) {
      attachmentInput.setAttribute('accept', 'image/*');
    }

    var panel = createEl('div', 'spx-quick-reply');
    panel.id = 'spx-quick-reply';
    panel.classList.add('spx-quick-reply-collapsed');
    panel.setAttribute('aria-label', '浮动回复框');

    var launcher = createEl('button', 'spx-quick-reply-launcher', '继续回复');
    launcher.type = 'button';
    launcher.id = 'spx-quick-reply-launcher';

    var selectionMenu = createEl('div', 'spx-quick-reply-selection');
    selectionMenu.id = 'spx-quick-reply-selection';
    selectionMenu.setAttribute('aria-label', '选中文字操作');
    var selectionQuote = createEl('button', '', '引用所选');
    selectionQuote.type = 'button';
    var selectionCancel = createEl('button', '', '取消');
    selectionCancel.type = 'button';
    selectionMenu.append(selectionQuote, selectionCancel);

    var header = createEl('div', 'spx-quick-reply-header');
    var title = createEl('div');
    title.appendChild(createEl('strong', '', '快速回复'));
    var status = createEl('span', 'spx-quick-reply-status', '草稿已保存 · Ctrl+Enter 发送');
    title.appendChild(status);
    var headerActions = createEl('div', 'spx-quick-reply-actions');
    var collapse = createEl('button', 'spx-quick-reply-icon', '最小化');
    collapse.type = 'button';
    collapse.title = '最小化回复框';
    headerActions.appendChild(collapse);
    header.append(title, headerActions);

    var context = createEl('div', 'spx-quick-reply-context');
    var contextText = createEl('span', '', '');
    var clearContext = createEl('button', '', '×');
    clearContext.type = 'button';
    clearContext.title = '清除引用';
    context.append(contextText, clearContext);

    var body = createEl('div', 'spx-quick-reply-body');
    var list = createEl('div', 'spx-quick-reply-list');

    replies.slice(0, 8).forEach(function appendReply(reply, index) {
      var button = createEl('button', 'spx-quick-reply-chip' + (index < 2 ? ' spx-hot' : ''), reply);
      button.type = 'button';
      button.title = '填入回复:' + reply;
      button.addEventListener('click', function useQuickReply() {
        insertMirrorText(reply);
      });
      list.appendChild(button);
    });

    var emotes = createEl('div', 'spx-quick-reply-emotes');
    var emoteHead = createEl('div', 'spx-quick-reply-emote-head');
    emoteHead.append(createEl('span', '', '原站表情'), createEl('span', '', '点击插入表情码'));
    var emoteGrid = createEl('div', 'spx-quick-reply-emote-grid');
    QUICK_REPLY_EMOTES.forEach(function appendEmote(emote) {
      var button = createEl('button', 'spx-quick-reply-emote');
      button.type = 'button';
      button.title = emote.fileName;
      button.dataset.spxEmote = emote.code;
      var img = createEl('img');
      img.src = emote.src;
      img.alt = emote.fileName;
      img.referrerPolicy = 'no-referrer';
      button.appendChild(img);
      emoteGrid.appendChild(button);
    });
    emotes.append(emoteHead, emoteGrid);

    var tools = createEl('div', 'spx-quick-reply-tools');
    tools.append(createEl('span', 'spx-quick-reply-note', '最近常用优先 · 支持楼层引用和选中文字引用'));
    var counter = createEl('span', 'spx-quick-reply-counter', '0 字');
    tools.appendChild(counter);

    var mirror = createEl('textarea', 'spx-quick-reply-editor');
    mirror.placeholder = '输入回复内容。点击楼层“回复”会自动带上下文。';
    mirror.value = String(editor.value || '');

    var attach = createEl('div', 'spx-quick-reply-attach');
    var desc = createEl('input', 'spx-quick-reply-desc');
    desc.type = 'text';
    desc.placeholder = '图片描述,可留空';
    desc.value = attachmentDesc ? String(attachmentDesc.value || '') : '';
    if (!attachmentDesc) desc.disabled = true;
    var picker = attachmentInput
      ? createEl('label', 'spx-quick-reply-picker', '选择图片')
      : createEl('span', 'spx-quick-reply-picker', '无附件入口');
    if (attachmentInput) picker.setAttribute('for', attachmentId);
    var help = createEl('div', 'spx-quick-reply-help', formatQuickReplyAttachmentSummary(getQuickReplyAttachmentFiles(attachmentInput)));
    var attachmentList = createEl('div', 'spx-quick-reply-attachments');
    attach.append(desc, picker, help, attachmentList);

    var footer = createEl('div', 'spx-quick-reply-footer');
    footer.appendChild(createEl('div', 'spx-quick-reply-hint', '失败时保留草稿,成功后局部刷新新楼层。'));
    var submitActions = createEl('div', 'spx-quick-reply-submit-actions');
    var saveDraft = createEl('button', 'spx-quick-reply-action', '保存草稿');
    saveDraft.type = 'button';
    var send = createEl('button', 'spx-quick-reply-action spx-primary', '发送回复');
    send.type = 'button';
    submitActions.append(saveDraft, send);
    footer.appendChild(submitActions);

    body.append(list, emotes, tools, mirror, attach, footer);
    panel.append(header, context, body);

    var draftTimer = null;
    var activeContext = '';
    var previewUrls = [];

    function syncEditor() {
      editor.value = mirror.value;
      editor.dispatchEvent(new Event('input', { bubbles: true }));
      editor.dispatchEvent(new Event('change', { bubbles: true }));
    }

    function updateCounter() {
      counter.textContent = mirror.value.length + ' 字';
    }

    function markDraftSaved() {
      status.classList.remove('spx-error');
      status.textContent = '草稿已保存 · Ctrl+Enter 发送';
    }

    function scheduleDraftSave() {
      syncEditor();
      status.classList.remove('spx-error');
      status.textContent = '正在保存草稿...';
      window.clearTimeout(draftTimer);
      draftTimer = window.setTimeout(markDraftSaved, 420);
    }

    function openReply() {
      panel.classList.remove('spx-quick-reply-collapsed');
      launcher.classList.remove('spx-visible');
      if (typeof mirror.focus === 'function') mirror.focus();
    }

    function collapseReply() {
      panel.classList.add('spx-quick-reply-collapsed');
      launcher.classList.add('spx-visible');
    }

    function setContext(label) {
      activeContext = String(label || '').trim();
      contextText.textContent = activeContext;
      context.classList.toggle('spx-visible', !!activeContext);
    }

    function insertMirrorText(text) {
      var value = mirror.value;
      var start = typeof mirror.selectionStart === 'number' ? mirror.selectionStart : value.length;
      var end = typeof mirror.selectionEnd === 'number' ? mirror.selectionEnd : value.length;
      var prefix = value && !/\n$/.test(value.slice(0, start)) ? '\n' : '';
      mirror.value = value.slice(0, start) + prefix + text + value.slice(end);
      var cursor = start + prefix.length + String(text || '').length;
      if (typeof mirror.setSelectionRange === 'function') mirror.setSelectionRange(cursor, cursor);
      updateCounter();
      scheduleDraftSave();
      openReply();
    }

    function revokePreviewUrls() {
      previewUrls.forEach(function revoke(url) {
        try { URL.revokeObjectURL(url); } catch (error) {}
      });
      previewUrls = [];
    }

    function renderAttachments() {
      var files = getQuickReplyAttachmentFiles(attachmentInput);
      attachmentList.innerHTML = '';
      attachmentList.classList.toggle('spx-visible', !!files.length);
      help.textContent = formatQuickReplyAttachmentSummary(files);
      revokePreviewUrls();
      files.forEach(function appendAttachment(file, index) {
        var row = createEl('div', 'spx-quick-reply-attachment');
        var thumb = createEl('img', 'spx-quick-reply-thumb');
        try {
          thumb.src = URL.createObjectURL(file);
          previewUrls.push(thumb.src);
        } catch (error) {}
        thumb.alt = file.name || '图片附件';
        var meta = createEl('div');
        meta.append(
          createEl('div', 'spx-quick-reply-attachment-name', file.name || '图片附件'),
          createEl('div', 'spx-quick-reply-attachment-size', formatQuickReplyFileSize(file.size))
        );
        var remove = createEl('button', 'spx-quick-reply-remove', '×');
        remove.type = 'button';
        remove.title = '移除图片';
        remove.dataset.spxRemoveAttachment = String(index);
        row.append(thumb, meta, remove);
        attachmentList.appendChild(row);
      });
    }

    function removeAttachment(index) {
      if (!attachmentInput || !attachmentInput.files || typeof DataTransfer !== 'function') {
        status.textContent = '当前浏览器不支持单张移除,请重新选择图片';
        return;
      }
      var dt = new DataTransfer();
      getQuickReplyAttachmentFiles(attachmentInput).forEach(function keepFile(file, fileIndex) {
        if (fileIndex !== index) dt.items.add(file);
      });
      attachmentInput.files = dt.files;
      renderAttachments();
      scheduleDraftSave();
    }

    function selectedText() {
      return String(window.getSelection && window.getSelection().toString() || '').trim();
    }

    function sendReply() {
      syncEditor();
      if (attachmentDesc) {
        attachmentDesc.value = desc.value;
        attachmentDesc.dispatchEvent(new Event('input', { bubbles: true }));
        attachmentDesc.dispatchEvent(new Event('change', { bubbles: true }));
      }
      if (!mirror.value.trim() && !getQuickReplyAttachmentFiles(attachmentInput).length) {
        status.classList.add('spx-error');
        status.textContent = '请输入回复内容或选择图片';
        mirror.focus();
        return;
      }
      window.clearTimeout(draftTimer);
      var result = submitQuickReply(editor, settings, state);
      if (result && typeof result.then === 'function') {
        result.then(function markResult(ok) {
          if (ok) {
            status.classList.remove('spx-error');
            status.textContent = getQuickReplyAttachmentFiles(attachmentInput).length
              ? '提交成功 · 已带图片刷新帖子'
              : '提交成功 · 已刷新帖子';
          }
        });
        return;
      }
      if (result) status.textContent = '已交给原站提交';
    }

    panel.spxOpenWithContext = function spxOpenWithContext(label, text) {
      setContext(label);
      if (text) insertMirrorText(text);
      else openReply();
    };
    function showSelectionMenu() {
      var text = selectedText();
      selectionMenu.classList.toggle('spx-visible', text.length > 2 && !panel.contains(document.activeElement));
    }

    panel.spxCleanup = function spxCleanupQuickReply() {
      revokePreviewUrls();
      window.clearTimeout(draftTimer);
      document.removeEventListener('selectionchange', showSelectionMenu);
      if (launcher.parentNode) launcher.remove();
      if (selectionMenu.parentNode) selectionMenu.remove();
    };
    panel.spxLauncher = launcher;
    panel.spxSelectionMenu = selectionMenu;

    collapse.addEventListener('click', collapseReply);
    launcher.addEventListener('click', openReply);
    clearContext.addEventListener('click', function clearQuickReplyContext() {
      activeContext = '';
      context.classList.remove('spx-visible');
    });
    mirror.addEventListener('input', function handleMirrorInput() {
      updateCounter();
      scheduleDraftSave();
    });
    mirror.addEventListener('keydown', function handleMirrorShortcut(event) {
      if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
        event.preventDefault();
        sendReply();
      }
    });
    desc.addEventListener('input', function handleDescInput() {
      if (attachmentDesc) attachmentDesc.value = desc.value;
      scheduleDraftSave();
    });
    if (attachmentInput) {
      attachmentInput.addEventListener('change', function handleAttachmentInputChange() {
        renderAttachments();
        scheduleDraftSave();
        openReply();
      });
    }
    saveDraft.addEventListener('click', function saveQuickReplyDraft() {
      scheduleDraftSave();
      markDraftSaved();
    });
    send.addEventListener('click', sendReply);
    panel.addEventListener('click', function handleQuickReplyPanelClick(event) {
      var target = event.target;
      if (target.closest('[data-spx-emote]')) {
        insertMirrorText(target.closest('[data-spx-emote]').dataset.spxEmote || '');
        return;
      }
      if (target.closest('[data-spx-remove-attachment]')) {
        removeAttachment(Number(target.closest('[data-spx-remove-attachment]').dataset.spxRemoveAttachment));
      }
    });
    selectionQuote.addEventListener('click', function quoteSelectedText() {
      var text = selectedText();
      if (!text) return;
      setContext('引用所选文字');
      insertMirrorText('[quote]' + text.slice(0, 160) + '[/quote]\n');
      selectionMenu.classList.remove('spx-visible');
    });
    selectionCancel.addEventListener('click', function cancelSelectedQuote() {
      selectionMenu.classList.remove('spx-visible');
    });
    document.addEventListener('selectionchange', showSelectionMenu);

    updateCounter();
    renderAttachments();
    return panel;
  }

  function enhanceQuickReply(settings, state) {
    if (detectPageType(location.href) !== 'read' && detectPageType(location.href) !== 'post') return;
    var oldPanel = qs('#spx-quick-reply');
    if (oldPanel && typeof oldPanel.spxCleanup === 'function') oldPanel.spxCleanup();
    if (oldPanel) oldPanel.remove();
    var oldLauncher = qs('#spx-quick-reply-launcher');
    if (oldLauncher) oldLauncher.remove();
    var oldSelection = qs('#spx-quick-reply-selection');
    if (oldSelection) oldSelection.remove();

    var editor = getQuickReplyEditor(document);
    var panel = createQuickReplyPanel(settings, editor, state);
    bindQuickReplyFormSubmit(editor, settings, state, panel);
    if (!panel || !document.body) return;
    document.body.appendChild(panel);
    if (panel.spxLauncher) document.body.appendChild(panel.spxLauncher);
    if (panel.spxSelectionMenu) document.body.appendChild(panel.spxSelectionMenu);
  }

  function createSettingsPanel(settings, state) {
    var panel = qs('#spx-settings');
    if (panel) return panel;

    var settingLabels = {
      cleanMode: '清爽模式',
      readerMode: '阅读排版优化',
      immersiveRead: '帖子页沉浸全屏',
      nightMode: '夜间模式',
      unifiedPreviewGallery: '预览图集中显示',
      forumDashboard: '论坛仪表盘',
      compactRead: '阅读页紧凑',
      foldQuotes: '折叠长引用',
      hideUserProfile: '隐藏头像资料',
      networkFriendly: '网络友好模式',
      autoTaskClaim: '自动领取任务',
      autoBuyPost: '自动购买低价帖子',
      smartModuleNavSort: '导航智能排序',
      unreadOnly: '列表只看未读',
      onlyOriginalAuthor: '阅读页只看楼主',
    };
    var settingKeys = getSettingsPanelKeys(location.href, document);
    function renderSettingControl(key) {
      return '<label><input type="checkbox" data-key="' + key + '"> ' + settingLabels[key] + '</label>';
    }
    function renderSettingsSection(title, keys, helpText) {
      var controls = (keys || []).filter(function keepSettingKey(key) {
        return settingKeys.indexOf(key) !== -1;
      }).map(renderSettingControl);
      if (!controls.length) return [];
      return [
        '<section class="spx-settings-section">',
        '<h4>' + title + '</h4>',
        helpText ? '<div class="spx-help">' + helpText + '</div>' : '',
        '<div class="spx-settings-grid">',
      ].concat(controls, [
        '</div>',
        '</section>',
      ]);
    }
    var readingControls = renderSettingsSection(
      '阅读体验',
      ['readerMode', 'immersiveRead', 'nightMode', 'compactRead', 'foldQuotes', 'hideUserProfile', 'onlyOriginalAuthor', 'unifiedPreviewGallery'],
      '控制正文排版、沉浸阅读、夜间模式、头像资料和图集显示。'
    );
    var listControls = renderSettingsSection(
      '列表增强',
      ['cleanMode', 'unreadOnly', 'forumDashboard'],
      '控制列表页、首页仪表盘和通用页面的低频内容、未读筛选。'
    );
    var networkControls = renderSettingsSection(
      '网络请求',
      ['networkFriendly'],
      '限制脚本后台请求频率,缓存悬停预览和导航刷新,遇到 504 或操作频繁提示时自动冷却。'
    );
    var taskControls = renderSettingsSection(
      '社区任务',
      ['autoTaskClaim'],
      '保守模式:任意页面都会检查日常是否今日已完成;未完成会自动申请并领取奖励,周常仍按 7 天冷却。'
    );
    var autoBuyControls = settingKeys.indexOf('autoBuyPost') !== -1 ? [
      '<section class="spx-settings-section">',
      '<h4>自动购买</h4>',
      renderSettingControl('autoBuyPost'),
      '<label class="spx-number-setting"><span>价格上限</span><span><input type="number" min="0" step="1" data-number="autoBuyMaxSp"> SP</span></label>',
      '<div class="spx-help">仅当帖子价格严格小于该值、账户 SP 足够且页面存在购买按钮时自动购买;默认关闭。</div>',
      '</section>',
    ] : [];
    var navigationControls = [
      '<section class="spx-settings-section">',
      '<h4>导航中心</h4>',
      '<div class="spx-help">控制左侧导航中心的块区域密度和排序;智能排序会结合当前页、置顶、最近使用和入口数量前置常用入口。</div>',
      renderSettingControl('smartModuleNavSort'),
      '<label class="spx-choice-setting"><span>导航密度</span><select data-choice="moduleNavDensity">',
      '<option value="compact">紧凑</option>',
      '<option value="standard">标准</option>',
      '<option value="comfortable">舒适</option>',
      '</select></label>',
      '</section>',
    ];
    var presetDefinitions = getSettingsPresetDefinitions();
    var presetControls = [
      '<section class="spx-settings-section spx-settings-presets">',
      '<h4>配置预设</h4>',
      '<div class="spx-help">按使用场景批量切换本地设置,不覆盖收藏、稍后看、资源和阅读进度。</div>',
      '<div class="spx-preset-tabs">',
    ].concat(Object.keys(presetDefinitions).map(function renderPresetButton(key) {
      var preset = presetDefinitions[key];
      return '<button type="button" data-action="apply-settings-preset" data-preset="' + key + '" title="' + preset.description + '">' + preset.label + '</button>';
    }), [
      '</div>',
      '</section>',
    ]);

    panel = createEl('div', 'spx-settings');
    panel.id = 'spx-settings';
    panel.hidden = true;
    panel.innerHTML = [
      '<div class="spx-settings-header">',
      '<h3>South Plus 增强设置</h3>',
      '<p class="spx-settings-subtitle">按场景分组管理阅读、列表、购买和本地数据。</p>',
      '</div>',
      '<div class="spx-settings-body">',
    ].concat(presetControls, readingControls, listControls, networkControls, taskControls, navigationControls, autoBuyControls, [
      '<section class="spx-settings-section">',
      '<h4>本地屏蔽</h4>',
      '<div class="spx-help">标题和作者关键词每行一个,保存后立即应用到当前页面。</div>',
      '<div>标题屏蔽关键词</div>',
      '<textarea data-list="titleKeywords"></textarea>',
      '<div>作者屏蔽关键词</div>',
      '<textarea data-list="authorKeywords"></textarea>',
      '</section>',
      '<section class="spx-settings-section">',
      '<h4>快捷回复</h4>',
      '<div class="spx-help">帖子详情页或回复页会显示为快捷按钮;快捷语句和帖子页原生提交按钮都会无刷新展示新回复。</div>',
      '<textarea data-list="quickReplies"></textarea>',
      '</section>',
      '<section class="spx-settings-section">',
      '<h4>备份恢复</h4>',
      '<div class="spx-row">',
      '<button data-action="export-backup">导出备份</button>',
      '<button data-action="import-backup">导入备份</button>',
      '</div>',
      '</section>',
      '<section class="spx-settings-section">',
      '<h4>数据管理</h4>',
      '<div class="spx-help">数据健康和体积只读取本地 localStorage;清空类操作不可自动恢复,请先导出备份。</div>',
      '<div class="spx-row">',
      '<button data-action="show-data-health">数据健康</button>',
      '<button data-action="show-storage-usage">本地体积</button>',
      '<button class="spx-danger" data-action="clear-read">清空已读</button>',
      '<button class="spx-danger" data-action="clear-progress">清空进度</button>',
      settingKeys.indexOf('autoBuyPost') !== -1 ? '<button class="spx-danger" data-action="clear-auto-buy">清空自动购买记录</button>' : '',
      '</div>',
      '<div class="spx-data-health" data-role="data-health" hidden></div>',
      '</section>',
      '</div>',
      '<div class="spx-settings-footer">',
      '<button data-action="close">关闭</button>',
      '<button class="spx-primary" data-action="save">保存</button>',
      '</div>',
      '<input type="file" accept="application/json,.json" data-action="import-backup-file" hidden>',
    ]).join('');
    document.body.appendChild(panel);

    function setTemporaryButtonText(button, text) {
      if (!button) return;
      var previous = button.dataset.spxOriginalText || button.textContent;
      button.dataset.spxOriginalText = previous;
      button.textContent = text;
      window.setTimeout(function restoreButtonText() {
        button.textContent = button.dataset.spxOriginalText || previous;
        delete button.dataset.spxOriginalText;
      }, 1800);
    }

    function syncForm() {
      qsa('input[data-key]', panel).forEach(function syncCheckbox(input) {
        input.checked = !!settings[input.dataset.key];
      });
      qsa('textarea[data-list]', panel).forEach(function syncList(textarea) {
        textarea.value = (settings[textarea.dataset.list] || []).join('\n');
      });
      qsa('input[data-number]', panel).forEach(function syncNumber(input) {
        input.value = String(Number(settings[input.dataset.number]) || 0);
      });
      qsa('select[data-choice]', panel).forEach(function syncChoice(select) {
        select.value = settings[select.dataset.choice] || '';
      });
    }

    function saveForm() {
      qsa('input[data-key]', panel).forEach(function readCheckbox(input) {
        settings[input.dataset.key] = input.checked;
      });
      qsa('textarea[data-list]', panel).forEach(function readList(textarea) {
        settings[textarea.dataset.list] = textarea.dataset.list === 'quickReplies'
          ? parseQuickReplyList(textarea.value)
          : parseLineList(textarea.value);
      });
      qsa('input[data-number]', panel).forEach(function readNumber(input) {
        settings[input.dataset.number] = Math.max(0, Number(input.value) || 0);
      });
      qsa('select[data-choice]', panel).forEach(function readChoice(select) {
        if (select.dataset.choice === 'moduleNavDensity') {
          settings.moduleNavDensity = normalizeModuleNavDensity(select.value);
        }
      });
      delete document.documentElement.dataset.spxAutoBuyStatus;
      var autoBuyStatus = qs('#spx-auto-buy-status');
      if (autoBuyStatus) autoBuyStatus.remove();
      Object.assign(settings, normalizeSettings(settings));
      saveSettings(settings);
      enhanceAll(settings, state);
    }

    function getCurrentDataHealthPayload() {
      return {
        settings: settings,
        read: state && state.read,
        watch: state && state.watch,
        progress: state && state.progress,
        autoBuyAttempts: loadAutoBuyAttempts(),
        taskClaims: loadTaskClaimRecords(),
        resources: state && state.resources,
        navigation: loadNavigationPool(),
        navigationPins: loadNavigationPins(),
        navigationUsage: loadNavigationUsage(),
      };
    }

    function appendStorageUsageDetails(box, storageReport) {
      box.appendChild(createEl('strong', '', '本地数据体积'));
      box.appendChild(createEl('div', 'spx-help', formatStorageUsageSummary(storageReport)));
      var usageList = createEl('div', 'spx-storage-usage');
      storageReport.entries.forEach(function appendStorageEntry(entry) {
        var row = createEl('div', 'spx-storage-usage-row spx-storage-' + getStorageUsageLevel(entry));
        var main = createEl('span', 'spx-storage-usage-main');
        main.appendChild(createEl('b', '', entry.label));
        main.appendChild(createEl('em', '', entry.key));
        row.appendChild(main);
        row.appendChild(createEl('span', 'spx-storage-usage-meta', entry.size + ' · ' + formatStorageUsageLimit(entry)));
        usageList.appendChild(row);
      });
      box.appendChild(usageList);
      box.appendChild(createEl('div', 'spx-storage-suggestions', '清理建议:' + formatStorageUsageWarnings(storageReport)));
    }

    function appendDataHealthActions(box, report, refreshAction) {
      var actions = createEl('div', 'spx-row');
      var refresh = createEl('button', '', refreshAction === 'refresh-storage-usage' ? '刷新体积' : '刷新统计');
      var cleanup = createEl('button', '', '清理重复/过期');
      refresh.type = 'button';
      cleanup.type = 'button';
      refresh.dataset.action = refreshAction || 'refresh-data-health';
      cleanup.dataset.action = 'cleanup-data-health';
      cleanup.disabled = !report.cleanupCount;
      actions.appendChild(refresh);
      actions.appendChild(cleanup);
      box.appendChild(actions);
    }

    function renderDataHealthPanel(message) {
      var box = qs('[data-role="data-health"]', panel);
      if (!box) return;
      var payload = getCurrentDataHealthPayload();
      var report = collectDataHealthReport(payload);
      var storageReport = collectStorageUsageReport(payload);
      box.hidden = false;
      box.textContent = '';
      box.appendChild(createEl('strong', '', '本地数据健康'));
      box.appendChild(createEl('div', 'spx-help', formatDataHealthSummary(report)));
      box.appendChild(createEl('div', 'spx-help', formatDataHealthWarnings(report)));
      appendStorageUsageDetails(box, storageReport);
      if (message) box.appendChild(createEl('div', 'spx-help', message));
      appendDataHealthActions(box, report, 'refresh-data-health');
    }

    function renderStorageUsagePanel(message) {
      var box = qs('[data-role="data-health"]', panel);
      if (!box) return;
      var payload = getCurrentDataHealthPayload();
      var report = collectDataHealthReport(payload);
      var storageReport = collectStorageUsageReport(payload);
      box.hidden = false;
      box.textContent = '';
      appendStorageUsageDetails(box, storageReport);
      if (message) box.appendChild(createEl('div', 'spx-help', message));
      appendDataHealthActions(box, report, 'refresh-storage-usage');
    }

    function applyDataHealthCleanup(button) {
      var cleanup = cleanupDataHealthPayload(getCurrentDataHealthPayload());
      if (!cleanup.before.cleanupCount) {
        renderDataHealthPanel('暂无重复或过期数据需要清理。');
        return;
      }
      confirmBatchAction({
        title: '清理本地数据',
        message: '将清理重复稍后看、重复 / 过期阅读进度和异常记录。',
        confirmText: '清理数据',
        impacts: [{ label: '待清理项目', value: cleanup.before.cleanupCount, note: '按健康检查结果处理' }],
      }, '将清理重复稍后看、重复/过期阅读进度和异常记录,确认继续?').then(function cleanupAfterConfirm(ok) {
        if (!ok) return;
        var done = applyBackupPayload(cleanup.payload, settings, state);
        renderDataHealthPanel(done ? '已清理 ' + cleanup.before.cleanupCount + ' 项数据。' : '清理失败。');
        setTemporaryButtonText(button, done ? '已清理' : '清理失败');
      });
    }

    panel.addEventListener('click', function handleSettingsClick(event) {
      var action = event.target && event.target.dataset && event.target.dataset.action;
      if (!action) return;
      if (action === 'save') {
        saveForm();
        setSettingsPanelHidden(panel, true, SETTINGS_BUTTON_SELECTOR);
      }
      if (action === 'close') setSettingsPanelHidden(panel, true, SETTINGS_BUTTON_SELECTOR);
      if (action === 'export-backup') {
        var exported = downloadBackupPayload(collectBackupPayload(settings, state));
        setTemporaryButtonText(event.target, exported ? '已导出备份' : '导出失败');
      }
      if (action === 'import-backup') {
        var fileInput = qs('input[data-action="import-backup-file"]', panel);
        if (!fileInput) return;
        fileInput.value = '';
        fileInput.click();
      }
      if (action === 'show-data-health' || action === 'refresh-data-health') {
        renderDataHealthPanel();
      }
      if (action === 'show-storage-usage' || action === 'refresh-storage-usage') {
        renderStorageUsagePanel();
      }
      if (action === 'cleanup-data-health') {
        applyDataHealthCleanup(event.target);
      }
      if (action === 'apply-settings-preset') {
        var presetKey = event.target.dataset.preset;
        Object.assign(settings, applySettingsPreset(settings, presetKey));
        saveSettings(settings);
        syncForm();
        enhanceAll(settings, state);
        setTemporaryButtonText(event.target, '已应用');
      }
      if (action === 'clear-read') {
        var readCount = Object.keys((state && state.read) || {}).length;
        confirmBatchAction({
          title: '清空已读记录',
          message: '将清空本地已读主题标记。',
          confirmText: '清空已读',
          impacts: [{ label: '已读主题', value: readCount, note: '影响列表未读筛选' }],
        }, '清空全部已读记录?').then(function clearReadAfterConfirm(ok) {
          if (!ok) return;
          state.read = {};
          saveMap(READ_KEY, state.read);
          enhanceAll(settings, state);
          setTemporaryButtonText(event.target, '已清空已读');
        });
      }
      if (action === 'clear-progress') {
        var progressEntries = getHistoryCenterEntries(state.progress);
        confirmBatchAction({
          title: '清空阅读进度',
          message: '将清空全部阅读进度、楼层位置和最近浏览。',
          confirmText: '清空进度',
          impacts: [{ label: '阅读记录', value: progressEntries.length, note: '影响继续阅读' }],
          items: progressEntries.map(function mapProgressConfirmItem(entry) {
            return { title: entry.title, meta: entry.progressText || entry.url };
          }),
        }, '清空全部阅读进度?').then(function clearProgressAfterConfirm(ok) {
          if (!ok) return;
          state.progress = {};
          saveReadProgress(state.progress);
          clearReadProgressRestoreRequest(parseThreadId(location.href));
          refreshWatchCenter();
          refreshHistoryCenter();
          refreshReadThreadSummaryCard();
          setTemporaryButtonText(event.target, '已清空阅读进度');
        });
      }
      if (action === 'clear-auto-buy') {
        var autoBuyEntries = getAutoBuyCenterEntries(loadAutoBuyAttempts());
        confirmBatchAction({
          title: '清空自动购买记录',
          message: '将清空全部自动购买执行记录。',
          confirmText: '清空购买记录',
          impacts: [{ label: '购买记录', value: autoBuyEntries.length, note: '可能影响重复购买拦截' }],
          items: autoBuyEntries.map(function mapSettingsAutoBuyConfirmItem(entry) {
            return { title: entry.key, meta: entry.statusLabel };
          }),
        }, '清空全部自动购买记录?').then(function clearAutoBuySettingsAfterConfirm(ok) {
          if (!ok) return;
          saveAutoBuyAttempts({});
          delete document.documentElement.dataset.spxAutoBuyStatus;
          var autoBuyStatus = qs('#spx-auto-buy-status');
          if (autoBuyStatus) autoBuyStatus.remove();
          refreshAutoBuyCenter();
          refreshReadThreadSummaryCard();
          setTemporaryButtonText(event.target, '已清空购买记录');
        });
      }
    });

    panel.addEventListener('change', function handleSettingsChange(event) {
      var target = event.target;
      if (!target || !target.dataset || target.dataset.action !== 'import-backup-file') return;
      var file = target.files && target.files[0];
      if (!file) return;
      var importButton = qs('button[data-action="import-backup"]', panel);
      importBackupFile(file, settings, state, function handleBackupImport(done, message) {
        setTemporaryButtonText(importButton, message || (done ? '已导入备份' : '导入失败'));
      });
    });

    panel.spxSync = syncForm;
    syncForm();
    return panel;
  }

  function getToolbarNavigationConfigs(url, root) {
    var page = currentPageNumber(url);
    return [
      {
        show: true,
        group: '页面导航',
        text: '顶',
        label: '回到顶部',
        title: '回到顶部',
        description: '快速滚动到页面开头',
        onClick: function top() {
          window.scrollTo({ top: 0, behavior: 'smooth' });
        },
      },
      {
        show: true,
        group: '页面导航',
        text: '底',
        label: '滚到底部',
        title: '滚到底部',
        description: '快速滚动到页面末尾',
        onClick: function bottom() {
          window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' });
        },
      },
      {
        show: shouldShowToolbarAction('prevPage', url, root),
        group: '页面导航',
        text: '上',
        label: '上一页',
        title: '上一页',
        description: '切换到上一页内容',
        href: buildPageUrl(url, Math.max(1, page - 1)),
      },
      {
        show: shouldShowToolbarAction('nextPage', url, root),
        group: '页面导航',
        text: '下',
        label: '下一页',
        title: '下一页',
        description: '切换到下一页内容',
        href: buildPageUrl(url, page + 1),
      },
      {
        show: shouldShowToolbarFeature('latest'),
        group: '页面导航',
        text: '新',
        label: '最新帖子',
        title: '最新帖子',
        description: '打开站点最新帖子列表',
        href: location.origin + '/search2.php?orderway-postdate-asc-desc-newatc-1.html',
      },
      {
        show: shouldShowToolbarAction('home', url, root),
        group: '页面导航',
        text: '首',
        label: '论坛首页',
        title: '论坛首页',
        description: '回到当前站点首页',
        href: location.origin + '/index.php',
      },
    ];
  }

  function getToolbarToggleConfigs(url, root) {
    return [
      { show: shouldShowToolbarFeature('clean'), group: '阅读模式', key: 'cleanMode', text: '净', label: '清爽模式', title: '切换清爽模式', description: '隐藏低频模块和干扰内容' },
      { show: shouldShowToolbarAction('reader', url, root), group: '阅读模式', key: 'readerMode', text: '字', label: '阅读排版', title: '切换阅读排版优化', description: '优化字体、宽度和正文间距' },
      { show: true, group: '阅读模式', key: 'nightMode', text: '夜', label: '夜间模式', title: '切换夜间模式', description: '切换深色影院式阅读和图集界面' },
      { show: shouldShowToolbarAction('adBlock', url, root), group: '阅读模式', key: 'adBlock', text: '广', label: '隐藏广告', title: '切换隐藏广告', description: '隐藏广告链接、图片和容器' },
      { show: shouldShowToolbarAction('homeDashboard', url, root), group: '阅读模式', key: 'homeDashboard', text: '模', label: '首页模块', title: '切换首页模块全屏', description: '以网格方式整理首页板块' },
      { show: shouldShowToolbarAction('homeDashboard', url, root), group: '阅读模式', key: 'forumDashboard', text: '盘', label: '论坛仪表盘', title: '切换首页论坛仪表盘', description: '展示浏览、收藏、资源和本地积压摘要' },
      { show: shouldShowToolbarAction('immersiveRead', url, root), group: '阅读模式', key: 'immersiveRead', text: '屏', label: '沉浸阅读', title: '切换帖子页沉浸全屏', description: '放大帖子内容并弱化周边' },
      { show: shouldShowToolbarAction('previewGallery', url, root), group: '阅读模式', key: 'unifiedPreviewGallery', text: '图', label: '预览图集', title: '切换预览图集中显示', description: '集中查看当前页正文图片' },
      { show: shouldShowToolbarAction('unreadOnly', url, root), group: '阅读模式', key: 'unreadOnly', text: '未', label: '只看未读', title: '只看未读', description: '论坛列表隐藏已读主题' },
      { show: shouldShowToolbarAction('onlyOriginalAuthor', url, root), group: '阅读模式', key: 'onlyOriginalAuthor', text: '楼', label: '只看楼主', title: '只看楼主', description: '阅读页隐藏非楼主回复' },
    ];
  }

  function getToolbarCenterConfigs() {
    return [
      {
        show: true,
        group: '我的中心',
        text: '合',
        label: '我的内容',
        title: '打开我的内容中心',
        description: '汇总收藏、稍后看、阅读进度、资源、购买和任务概览',
        panelId: 'spx-content-center',
        createPanel: createMyContentCenterPanel,
      },
      {
        show: true,
        group: '我的中心',
        text: '存',
        label: '稍后看',
        title: '打开稍后看中心',
        description: '管理保存的主题和续读入口',
        panelId: 'spx-watch-center',
        createPanel: createWatchCenterPanel,
      },
      {
        show: true,
        group: '我的中心',
        text: '历',
        label: '最近浏览',
        title: '打开最近浏览',
        description: '查看阅读历史和进度',
        panelId: 'spx-history-center',
        createPanel: createHistoryCenterPanel,
      },
      {
        show: true,
        group: '我的中心',
        text: '买',
        label: '购买记录',
        title: '打开自动购买记录',
        description: '查看自动购买执行状态',
        panelId: 'spx-auto-buy-center',
        createPanel: createAutoBuyCenterPanel,
      },
      {
        show: true,
        group: '我的中心',
        text: '源',
        label: '资源工作台',
        title: '打开资源工作台',
        description: '管理磁力、网盘和种子资源',
        panelId: 'spx-resource-center',
        createPanel: createResourceCenterPanel,
      },
    ];
  }

  function getWorkbenchNavigationCount(panelId, state) {
    var workbenchState = getWorkbenchState(state);
    if (panelId === 'spx-content-center') return '总览';
    if (panelId === 'spx-watch-center') return Object.keys(workbenchState.watch || {}).length;
    if (panelId === 'spx-history-center') return Object.keys(workbenchState.progress || {}).length;
    if (panelId === 'spx-auto-buy-center') return getAutoBuyCenterEntries(loadAutoBuyAttempts()).length;
    if (panelId === 'spx-resource-center') return Object.keys(workbenchState.resources || {}).length;
    return '';
  }

  function getWorkbenchNavigationConfigs(settings, state) {
    return getToolbarCenterConfigs().map(function mapWorkbenchNavigation(config) {
      return {
        section: '我的工作台',
        label: config.label,
        title: config.title || config.label,
        className: 'spx-module-nav-workbench',
        alwaysShow: true,
        navigationOnly: false,
        panelId: config.panelId,
        countText: getWorkbenchNavigationCount(config.panelId, state),
        onClick: function openWorkbenchNavigationItem() {
          openWorkbenchPanel(config.panelId, settings || loadSettings(), state);
        },
      };
    });
  }

  function getToolbarSettingsConfig() {
    return {
      show: true,
      group: '设置',
      text: '设',
      label: '脚本设置',
      title: '打开脚本设置面板',
      description: '配置开关、备份和数据健康',
      kind: 'settings',
    };
  }

  function getToolboxActionConfigs(url, root) {
    return getToolbarNavigationConfigs(url, root)
      .concat(getToolbarToggleConfigs(url, root))
      .filter(function keepVisibleToolboxAction(config) {
        return config && config.show !== false;
      });
  }

  function getToolboxPriority(config, url) {
    var type = detectPageType(url);
    var key = config.key || config.panelId || config.kind || config.label || '';
    var readPriority = {
      readerMode: 0,
      nightMode: 1,
      immersiveRead: 2,
      onlyOriginalAuthor: 3,
      unifiedPreviewGallery: 4,
      'spx-resource-center': 4,
      'spx-history-center': 5,
    };
    var forumPriority = {
      unreadOnly: 0,
      'spx-watch-center': 1,
      nightMode: 2,
      cleanMode: 3,
      readerMode: 4,
      latest: 5,
    };
    var map = type === 'read' ? readPriority : (type === 'forum' ? forumPriority : null);
    return map && map[key] !== undefined ? map[key] : 50;
  }

  function getToolboxGroups(url, root) {
    var order = ['页面导航', '阅读模式'];
    var groups = {};
    getToolboxActionConfigs(url, root).forEach(function collectToolboxConfig(config) {
      var group = config.group || '其他';
      if (!groups[group]) groups[group] = [];
      groups[group].push(config);
    });
    return order.concat(Object.keys(groups).filter(function keepExtraGroup(group) {
      return order.indexOf(group) === -1;
    })).filter(function hasGroupItems(group) {
      return groups[group] && groups[group].length;
    }).map(function toToolboxGroup(group) {
      return {
        title: group,
        items: groups[group].slice().sort(function sortToolboxItems(a, b) {
          return getToolboxPriority(a, url) - getToolboxPriority(b, url);
        }),
      };
    });
  }

  function setPopupPanelHidden(panel, hidden, buttonSelector) {
    if (!panel) return;
    panel.hidden = !!hidden;
    if (buttonSelector) {
      qsa(buttonSelector).forEach(function togglePopupButton(button) {
        button.classList.toggle('spx-active', !panel.hidden);
        button.setAttribute('aria-expanded', panel.hidden ? 'false' : 'true');
      });
    }
  }

  function setToolboxHidden(panel, hidden, buttonSelector) {
    setPopupPanelHidden(panel, hidden, buttonSelector);
  }

  function setSettingsPanelHidden(panel, hidden, buttonSelector) {
    setPopupPanelHidden(panel, hidden, buttonSelector);
  }

  function isToolboxActionActive(config, settings) {
    if (!config) return false;
    if (config.key) return !!(settings && settings[config.key]);
    if (config.panelId) {
      var panel = qs('#' + config.panelId);
      return !!(panel && !panel.hidden);
    }
    if (config.kind === 'settings') {
      var settingsPanel = qs('#spx-settings');
      return !!(settingsPanel && !settingsPanel.hidden);
    }
    return false;
  }

  function appendToolboxActionContent(node, config) {
    node.appendChild(createEl('span', 'spx-toolbox-key', config.text || ''));
    var copy = createEl('span', 'spx-toolbox-copy');
    copy.appendChild(createEl('span', 'spx-toolbox-name', config.label || config.title || config.text || '工具'));
    copy.appendChild(createEl('span', 'spx-toolbox-desc', config.description || config.title || ''));
    node.appendChild(copy);
  }

  function createToolboxActionElement(config, settings, state, toolbox) {
    var node = createEl(config.href ? 'a' : 'button', 'spx-toolbox-action');
    node.title = config.title || config.label || '';
    if (config.href) {
      node.href = config.href;
    } else {
      node.type = 'button';
    }
    node.classList.toggle('spx-active', isToolboxActionActive(config, settings));
    appendToolboxActionContent(node, config);

    if (config.href) return node;

    node.addEventListener('click', function handleToolboxAction() {
      if (config.kind === 'settings') {
        var settingsPanel = createSettingsPanel(settings, state);
        if (settingsPanel.spxSync) settingsPanel.spxSync();
        setSettingsPanelHidden(settingsPanel, !settingsPanel.hidden, SETTINGS_BUTTON_SELECTOR);
        setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
        return;
      }
      if (config.key) {
        settings[config.key] = !settings[config.key];
        saveSettings(settings);
        enhanceAll(settings, state);
        if (toolbox && toolbox.spxRender) toolbox.spxRender();
        return;
      }
      if (config.createPanel) {
        if (config.panelId && openWorkbenchPanel(config.panelId, settings, state)) {
          setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
          return;
        }
        var centerPanel = config.createPanel(settings, state);
        if (centerPanel.spxRender) centerPanel.spxRender();
        setCenterPanelHidden(centerPanel, !centerPanel.hidden);
        setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
        return;
      }
      if (typeof config.onClick === 'function') {
        config.onClick();
        setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
      }
    });

    return node;
  }

  function createToolboxPanel(settings, state) {
    var panel = qs('#spx-toolbox');
    if (panel) return panel;

    panel = createEl('div', 'spx-toolbox');
    panel.id = 'spx-toolbox';
    panel.setAttribute('role', 'dialog');
    panel.setAttribute('aria-label', 'South Plus 工具箱');
    panel.hidden = true;
    panel.spxRender = function renderToolbox() {
      panel.textContent = '';
      var header = createEl('div', 'spx-toolbox-header');
      var title = createEl('div');
      title.appendChild(createEl('div', 'spx-toolbox-eyebrow', '功能分组'));
      title.appendChild(createEl('h3', '', 'South Plus 工具箱'));
      var closeButton = createEl('button', 'spx-toolbox-close', '×');
      closeButton.type = 'button';
      closeButton.title = '关闭工具箱';
      closeButton.addEventListener('click', function closeToolbox() {
        setToolboxHidden(panel, true, TOOLBOX_BUTTON_SELECTOR);
      });
      header.appendChild(title);
      header.appendChild(closeButton);
      panel.appendChild(header);

      var body = createEl('div', 'spx-toolbox-body');
      getToolboxGroups(location.href, document).forEach(function appendGroup(group) {
        var section = createEl('section', 'spx-toolbox-section');
        var sectionTitle = createEl('div', 'spx-toolbox-section-title');
        sectionTitle.appendChild(createEl('span', '', group.title));
        sectionTitle.appendChild(createEl('span', 'spx-toolbox-count', String(group.items.length)));
        section.appendChild(sectionTitle);
        var grid = createEl('div', 'spx-toolbox-grid');
        group.items.forEach(function appendToolboxAction(config) {
          grid.appendChild(createToolboxActionElement(config, settings, state, panel));
        });
        section.appendChild(grid);
        body.appendChild(section);
      });
      panel.appendChild(body);
    };
    document.body.appendChild(panel);
    return panel;
  }

  function collectCurrentPageCommandItems(root) {
    var doc = root || document;
    var seen = {};
    var items = [];
    qsa('a[href]', doc).forEach(function collectPageCommandLink(link) {
      var href = link.href || link.getAttribute('href') || '';
      var text = compactText(link.textContent || link.title || '');
      if (!href || !text || /^javascript:/i.test(href)) return;
      var isThread = /read\.php\?tid[=-]?\d+|read\.php\?tid-\d+/i.test(href);
      var isForum = /thread\.php\?fid[=-]?\d+|thread\.php\?fid-\d+/i.test(href);
      if (!isThread && !isForum) return;
      if (/^(上一页|下一页|首页|尾页|收藏|回复|引用|编辑|删除)$/.test(text)) return;
      var key = href.replace(/#.*$/, '') + '|' + text;
      if (seen[key]) return;
      seen[key] = true;
      items.push({
        title: text.slice(0, 80),
        href: href,
        category: 'navigate',
        icon: isThread ? '帖' : '版',
        source: isThread ? '当前页面主题' : '当前页面版块',
        description: isThread ? '跳转到当前页面中的主题' : '跳转到当前页面中的版块入口',
        keywords: compactText((link.closest && link.closest('tr') && link.closest('tr').textContent) || ''),
      });
    });
    return items.slice(0, 40);
  }

  function getCommandPaletteData(settings, state) {
    return {
      toolboxConfigs: getToolboxActionConfigs(location.href, document),
      centerConfigs: getToolbarCenterConfigs(),
      settingsConfigs: [getToolbarSettingsConfig()],
      navigationConfigs: getAllModuleNavigationConfigs(settings, state),
      pageItems: collectCurrentPageCommandItems(document),
      watch: state && state.watch,
      progress: state && state.progress,
      resources: state && state.resources,
      settings: settings,
    };
  }

  function getStateMapSize(map) {
    return Object.keys(map || {}).length;
  }

  function getCommandPaletteCacheKey(settings, state) {
    var optionKeys = [
      'cleanMode',
      'readerMode',
      'immersiveRead',
      'nightMode',
      'unifiedPreviewGallery',
      'forumDashboard',
      'networkFriendly',
      'unreadOnly',
      'onlyOriginalAuthor',
      'moduleNavDensity',
    ];
    var optionText = optionKeys.map(function mapCommandOption(key) {
      return key + ':' + String(settings && settings[key]);
    }).join('|');
    return [
      location.href,
      enhanceCycle,
      getStateMapSize(state && state.watch),
      getStateMapSize(state && state.progress),
      getStateMapSize(state && state.resources),
      optionText,
    ].join('||');
  }

  function getCommandPaletteEntries(panel, settings, state) {
    var cacheKey = getCommandPaletteCacheKey(settings, state);
    if (!panel.spxAllEntries || panel.spxEntriesCacheKey !== cacheKey) {
      panel.spxAllEntries = collectCommandPaletteEntries(getCommandPaletteData(settings, state));
      panel.spxEntriesCacheKey = cacheKey;
    }
    return panel.spxAllEntries || [];
  }

  function invalidateCommandPaletteCache() {
    var panel = qs('#spx-command-palette-overlay');
    if (!panel) return;
    panel.spxAllEntries = null;
    panel.spxEntriesCacheKey = '';
  }

  function setCommandPaletteHidden(panel, hidden) {
    if (!panel) return;
    panel.hidden = !!hidden;
    if (panel.hidden && panel.spxSearchTimer) {
      window.clearTimeout(panel.spxSearchTimer);
      panel.spxSearchTimer = null;
    }
    qsa(COMMAND_PALETTE_BUTTON_SELECTOR).forEach(function toggleCommandButton(button) {
      button.classList.toggle('spx-active', !panel.hidden);
      button.setAttribute('aria-expanded', panel.hidden ? 'false' : 'true');
    });
    if (!panel.hidden) {
      panel.spxRender();
      window.setTimeout(function focusCommandSearch() {
        var input = qs('.spx-command-search', panel);
        if (input && typeof input.focus === 'function') input.focus();
      }, 0);
    }
  }

  function getCommandCenterPanelConfig(panelId) {
    return getToolbarCenterConfigs().filter(function matchCenterConfig(config) {
      return config.panelId === panelId;
    })[0] || null;
  }

  function openCommandCenterPanel(panelId, settings, state) {
    var config = getCommandCenterPanelConfig(panelId);
    if (!config || !config.createPanel) return false;
    if (openWorkbenchPanel(panelId, settings, state)) return true;
    var centerPanel = config.createPanel(settings, state);
    if (centerPanel.spxRender) centerPanel.spxRender();
    setCenterPanelHidden(centerPanel, false);
    return true;
  }

  function openCommandUrl(href) {
    var value = String(href || '');
    if (!value) return false;
    if (/^(?:magnet|ed2k):/i.test(value)) {
      location.href = value;
      return true;
    }
    try {
      var url = new URL(value, location.href);
      if (url.origin === location.origin) {
        location.href = url.href;
      } else if (typeof window.open === 'function') {
        window.open(url.href, '_blank', 'noopener');
      } else {
        location.href = url.href;
      }
      return true;
    } catch (error) {
      location.href = value;
      return true;
    }
  }

  function closeCommandRelatedPanels() {
    var toolbox = qs('#spx-toolbox');
    if (toolbox) setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
    var settingsPanel = qs('#spx-settings');
    if (settingsPanel) setSettingsPanelHidden(settingsPanel, true, SETTINGS_BUTTON_SELECTOR);
  }

  function executeCommandPaletteEntry(entry, settings, state, panel) {
    if (!entry) return false;
    var config = entry.payload || null;
    setCommandPaletteHidden(panel, true);
    if (config && config.href) return openCommandUrl(config.href);
    if (config && config.kind === 'settings') {
      closeCommandRelatedPanels();
      var settingsPanel = createSettingsPanel(settings, state);
      if (settingsPanel.spxSync) settingsPanel.spxSync();
      setSettingsPanelHidden(settingsPanel, false, SETTINGS_BUTTON_SELECTOR);
      return true;
    }
    if (config && config.key) {
      settings[config.key] = !settings[config.key];
      saveSettings(settings);
      enhanceAll(settings, state);
      return true;
    }
    if (config && config.createPanel && config.panelId) {
      closeCommandRelatedPanels();
      return openCommandCenterPanel(config.panelId, settings, state);
    }
    if (config && typeof config.onClick === 'function') {
      config.onClick();
      return true;
    }
    if (entry.action === 'open-settings') {
      var panelNode = createSettingsPanel(settings, state);
      if (panelNode.spxSync) panelNode.spxSync();
      setSettingsPanelHidden(panelNode, false, SETTINGS_BUTTON_SELECTOR);
      return true;
    }
    if (entry.action === 'open-panel' && entry.panelId) {
      return openCommandCenterPanel(entry.panelId, settings, state);
    }
    if (entry.action === 'open-watch-entry') {
      var watchEntry = getWatchCenterEntries(state.watch, state.progress).filter(function matchWatchEntry(item) {
        return item.id === entry.recordId;
      })[0];
      if (watchEntry) {
        openProgressEntry(state, watchEntry.id, watchEntry.progressUrl || watchEntry.url, 'next');
        return true;
      }
    }
    if (entry.action === 'open-history-entry') {
      var historyEntry = getHistoryCenterEntries(state.progress).filter(function matchHistoryEntry(item) {
        return item.id === entry.recordId;
      })[0];
      if (historyEntry) {
        openProgressEntry(state, historyEntry.id, historyEntry.url, 'next');
        return true;
      }
    }
    if (entry.href) return openCommandUrl(entry.href);
    return false;
  }

  function isCommandPaletteSearchInput(target) {
    return !!(target && target.classList && target.classList.contains('spx-command-search'));
  }

  function rerenderCommandPaletteAfterSearch(panel, target) {
    if (!panel) return;
    panel.spxState.query = target ? target.value : panel.spxState.query;
    panel.spxState.activeIndex = 0;
    panel.spxRender();
    var nextInput = qs('.spx-command-search', panel);
    if (nextInput) {
      nextInput.focus();
      if (typeof nextInput.setSelectionRange === 'function') {
        nextInput.setSelectionRange(nextInput.value.length, nextInput.value.length);
      }
    }
  }

  function renderCommandPaletteDetail(panel, entry) {
    var detail = qs('.spx-command-detail', panel);
    if (!detail) return;
    detail.textContent = '';
    detail.appendChild(createEl('h3', '', entry ? entry.title : '没有匹配命令'));
    detail.appendChild(createEl('p', '', entry ? entry.description : '换一个关键词或分类继续搜索。'));
    var card = createEl('div', 'spx-command-detail-card');
    [
      ['来源', entry ? getCommandPaletteSourceLabel(entry) : '-'],
      ['目标', entry ? (entry.target || entry.href || entry.panelId || entry.key || '-') : '-'],
      ['风险', entry ? (entry.risk || '低') : '-'],
    ].forEach(function appendDetailRow(item) {
      var isTarget = item[0] === '目标';
      var row = createEl('div', 'spx-command-detail-row' + (isTarget ? ' spx-command-detail-target' : ''));
      row.appendChild(createEl('span', '', item[0]));
      if (isTarget && entry && entry.href && /^https?:/i.test(entry.href)) {
        var link = createEl('a', '', item[1]);
        link.href = entry.href;
        link.target = '_blank';
        link.rel = 'noopener noreferrer';
        link.title = entry.href;
        row.appendChild(link);
      } else {
        row.appendChild(createEl('strong', '', item[1]));
      }
      card.appendChild(row);
    });
    detail.appendChild(card);
  }

  function renderCommandPalettePanel(panel, settings, state) {
    var panelState = panel.spxState || { query: '', filter: 'all', activeIndex: 0 };
    panel.spxState = panelState;
    var allEntries = getCommandPaletteEntries(panel, settings, state);
    var visibleEntries = filterCommandPaletteEntries(allEntries, panelState);
    panelState.activeIndex = Math.min(Math.max(0, Number(panelState.activeIndex) || 0), Math.max(0, visibleEntries.length - 1));
    panel.spxEntries = visibleEntries;
    panel.textContent = '';

    var wrap = createEl('div', 'spx-command-wrap');
    var palette = createEl('section', 'spx-command-palette');
    palette.setAttribute('aria-label', '全局命令面板');
    var head = createEl('div', 'spx-command-head');
    var title = createEl('div', 'spx-command-title');
    var titleCopy = createEl('div');
    titleCopy.appendChild(createEl('strong', '', '全局命令'));
    titleCopy.appendChild(createEl('span', '', '本地数据优先 · Ctrl+K'));
    var closeButton = createEl('button', 'spx-command-close', '×');
    closeButton.type = 'button';
    closeButton.title = '关闭命令面板';
    closeButton.dataset.action = 'close-command-palette';
    title.append(titleCopy, closeButton);
    head.appendChild(title);

    var searchWrap = createEl('div', 'spx-command-search-wrap');
    var search = createEl('input', 'spx-command-search');
    search.type = 'search';
    search.placeholder = '搜索导航、收藏、资源、设置或页面动作';
    search.value = panelState.query || '';
    search.setAttribute('aria-label', '搜索命令');
    searchWrap.appendChild(search);
    searchWrap.appendChild(createEl('span', 'spx-command-search-mark', '/'));
    head.appendChild(searchWrap);

    var tabs = createEl('div', 'spx-command-tabs');
    [
      ['all', '全部'],
      ['navigate', '导航'],
      ['center', '我的中心'],
      ['resource', '资源'],
      ['setting', '设置'],
      ['action', '页面动作'],
    ].forEach(function appendCommandTab(item) {
      var tab = createEl('button', 'spx-command-tab' + (normalizeCommandPaletteFilter(panelState.filter) === item[0] ? ' spx-active' : ''), item[1]);
      tab.type = 'button';
      tab.dataset.filter = item[0];
      tabs.appendChild(tab);
    });
    head.appendChild(tabs);
    palette.appendChild(head);

    var list = createEl('div', 'spx-command-list');
    if (!visibleEntries.length) {
      list.appendChild(createEl('div', 'spx-command-empty', '没有匹配的命令'));
    } else {
      visibleEntries.slice(0, 80).forEach(function appendCommandEntry(entry, index) {
        var item = createEl('button', 'spx-command-item' + (index === panelState.activeIndex ? ' spx-active' : ''));
        item.type = 'button';
        item.dataset.commandId = entry.id;
        item.appendChild(createEl('span', 'spx-command-icon', entry.icon || '令'));
        var main = createEl('span', 'spx-command-main');
        main.appendChild(createEl('strong', '', entry.title));
        main.appendChild(createEl('span', '', entry.description || getCommandPaletteSourceLabel(entry)));
        item.appendChild(main);
        var tail = createEl('span', 'spx-command-tail');
        tail.appendChild(createEl('span', 'spx-command-pill' + (entry.category === 'resource' ? ' spx-ok' : entry.category === 'center' ? ' spx-warn' : ''), getCommandPaletteCategoryLabel(entry.category)));
        if (index === panelState.activeIndex) tail.appendChild(createEl('span', 'spx-command-kbd', 'Enter'));
        item.appendChild(tail);
        list.appendChild(item);
      });
    }
    palette.appendChild(list);
    var foot = createEl('div', 'spx-command-foot');
    foot.appendChild(createEl('span', '', formatCommandPaletteResultSummary(visibleEntries, allEntries, panelState)));
    var keys = createEl('span', 'spx-command-keys');
    ['↑ ↓', 'Enter', 'Esc'].forEach(function appendKeyLabel(label) {
      keys.appendChild(createEl('span', 'spx-command-kbd', label));
    });
    foot.appendChild(keys);
    palette.appendChild(foot);
    wrap.appendChild(palette);

    var detail = createEl('aside', 'spx-command-detail');
    wrap.appendChild(detail);
    panel.appendChild(wrap);
    renderCommandPaletteDetail(panel, visibleEntries[panelState.activeIndex] || null);
  }

  function createCommandPalettePanel(settings, state) {
    var panel = qs('#spx-command-palette-overlay');
    if (panel) return panel;
    panel = createEl('div', 'spx-command-overlay');
    panel.id = 'spx-command-palette-overlay';
    panel.hidden = true;
    panel.setAttribute('role', 'dialog');
    panel.setAttribute('aria-label', '全局命令面板');
    panel.spxState = { query: '', filter: 'all', activeIndex: 0 };
    panel.spxComposing = false;
    panel.spxAllEntries = null;
    panel.spxEntriesCacheKey = '';
    panel.spxSearchTimer = null;
    panel.spxRender = function renderCommandPanel() {
      renderCommandPalettePanel(panel, settings, state);
    };
    panel.addEventListener('compositionstart', function handleCommandCompositionStart(event) {
      if (!isCommandPaletteSearchInput(event.target)) return;
      panel.spxComposing = true;
    });
    panel.addEventListener('compositionend', function handleCommandCompositionEnd(event) {
      if (!isCommandPaletteSearchInput(event.target)) return;
      panel.spxComposing = false;
      rerenderCommandPaletteAfterSearch(panel, event.target);
    });
    panel.addEventListener('input', function handleCommandInput(event) {
      var target = event.target;
      if (!isCommandPaletteSearchInput(target)) return;
      panel.spxState.query = target.value;
      if (panel.spxComposing || event.isComposing) return;
      if (panel.spxSearchTimer) window.clearTimeout(panel.spxSearchTimer);
      panel.spxSearchTimer = window.setTimeout(function renderDebouncedCommandSearch() {
        panel.spxSearchTimer = null;
        rerenderCommandPaletteAfterSearch(panel, target);
      }, 80);
    });
    panel.addEventListener('click', function handleCommandClick(event) {
      if (event.target === panel) {
        setCommandPaletteHidden(panel, true);
        return;
      }
      var close = event.target && event.target.closest && event.target.closest('[data-action="close-command-palette"]');
      if (close) {
        setCommandPaletteHidden(panel, true);
        return;
      }
      var tab = event.target && event.target.closest && event.target.closest('.spx-command-tab');
      if (tab) {
        panel.spxState.filter = normalizeCommandPaletteFilter(tab.dataset.filter);
        panel.spxState.activeIndex = 0;
        panel.spxRender();
        var input = qs('.spx-command-search', panel);
        if (input) input.focus();
        return;
      }
      var item = event.target && event.target.closest && event.target.closest('.spx-command-item');
      if (item) {
        var entries = panel.spxEntries || [];
        var entry = entries.filter(function matchCommand(itemEntry) { return itemEntry.id === item.dataset.commandId; })[0];
        executeCommandPaletteEntry(entry, settings, state, panel);
      }
    });
    panel.addEventListener('keydown', function handleCommandKeys(event) {
      if (panel.spxComposing || event.isComposing || event.keyCode === 229) return;
      if (event.key === 'Escape') {
        event.preventDefault();
        setCommandPaletteHidden(panel, true);
        return;
      }
      var entries = panel.spxEntries || [];
      if (!entries.length) return;
      if (event.key === 'ArrowDown') {
        event.preventDefault();
        panel.spxState.activeIndex = Math.min(entries.length - 1, (panel.spxState.activeIndex || 0) + 1);
        panel.spxRender();
        var downInput = qs('.spx-command-search', panel);
        if (downInput) downInput.focus();
        return;
      }
      if (event.key === 'ArrowUp') {
        event.preventDefault();
        panel.spxState.activeIndex = Math.max(0, (panel.spxState.activeIndex || 0) - 1);
        panel.spxRender();
        var upInput = qs('.spx-command-search', panel);
        if (upInput) upInput.focus();
        return;
      }
      if (event.key === 'Enter') {
        event.preventDefault();
        executeCommandPaletteEntry(entries[panel.spxState.activeIndex || 0], settings, state, panel);
      }
    });
    document.body.appendChild(panel);
    return panel;
  }

  function bindCommandPaletteKeyboard(settings, state) {
    if (document.documentElement.dataset.spxCommandPaletteKeyboardReady === '1') return;
    document.documentElement.dataset.spxCommandPaletteKeyboardReady = '1';
    document.addEventListener('keydown', function handleCommandPaletteShortcut(event) {
      if (!event || (!event.ctrlKey && !event.metaKey) || event.shiftKey || event.altKey) return;
      if (String(event.key || '').toLowerCase() !== 'k') return;
      event.preventDefault();
      var panel = createCommandPalettePanel(settings, state);
      setCommandPaletteHidden(panel, !panel.hidden);
    }, true);
  }

  function createToolbar(settings, state) {
    if (qs('#spx-toolbar')) return;
    var toolbar = createEl('div', 'spx-toolbar');
    toolbar.id = 'spx-toolbar';
    var toolbox = null;
    var commandPalette = null;

    function getToolbox() {
      toolbox = toolbox || createToolboxPanel(settings, state);
      return toolbox;
    }

    function getCommandPalette() {
      commandPalette = commandPalette || createCommandPalettePanel(settings, state);
      return commandPalette;
    }

    function closeToolboxIfLoaded() {
      if (toolbox) setToolboxHidden(toolbox, true, TOOLBOX_BUTTON_SELECTOR);
    }

    function closeCommandPaletteIfLoaded() {
      if (commandPalette) setCommandPaletteHidden(commandPalette, true);
    }

    toolbar.appendChild(toolbarButton('顶部', '回到顶部', function top() {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    }));
    toolbar.appendChild(toolbarButton('底部', '滚到底部', function bottom() {
      window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' });
    }));
    var toolboxButton = toolbarButton('工具', '打开工具箱', function toggleToolbox() {
      var currentToolbox = getToolbox();
      if (currentToolbox.spxRender) currentToolbox.spxRender();
      var nextToolboxHidden = !currentToolbox.hidden;
      setToolboxHidden(currentToolbox, nextToolboxHidden, TOOLBOX_BUTTON_SELECTOR);
      if (!nextToolboxHidden) {
        var settingsPanel = qs('#spx-settings');
        if (settingsPanel) setSettingsPanelHidden(settingsPanel, true, SETTINGS_BUTTON_SELECTOR);
        closeCommandPaletteIfLoaded();
      }
    });
    toolboxButton.dataset.spxToolboxButton = '1';
    toolboxButton.setAttribute('aria-haspopup', 'dialog');
    toolboxButton.setAttribute('aria-controls', 'spx-toolbox');
    toolboxButton.setAttribute('aria-expanded', 'false');
    toolbar.appendChild(toolboxButton);

    var commandButton = toolbarButton('命令', '打开全局命令面板(Ctrl+K)', function toggleCommandPalette() {
      var currentCommandPalette = getCommandPalette();
      var nextHidden = !currentCommandPalette.hidden;
      setCommandPaletteHidden(currentCommandPalette, nextHidden);
      if (!nextHidden) {
        closeToolboxIfLoaded();
        var settingsPanel = qs('#spx-settings');
        if (settingsPanel) setSettingsPanelHidden(settingsPanel, true, SETTINGS_BUTTON_SELECTOR);
      }
    });
    commandButton.dataset.spxCommandPaletteButton = '1';
    commandButton.setAttribute('aria-haspopup', 'dialog');
    commandButton.setAttribute('aria-controls', 'spx-command-palette-overlay');
    commandButton.setAttribute('aria-expanded', 'false');
    toolbar.appendChild(commandButton);

    var settingsButton = toolbarButton('设置', '打开设置', function openSettings() {
      var panel = createSettingsPanel(settings, state);
      if (panel.spxSync) panel.spxSync();
      closeToolboxIfLoaded();
      closeCommandPaletteIfLoaded();
      setSettingsPanelHidden(panel, !panel.hidden, SETTINGS_BUTTON_SELECTOR);
    });
    settingsButton.dataset.spxSettingsButton = '1';
    settingsButton.setAttribute('aria-haspopup', 'dialog');
    settingsButton.setAttribute('aria-controls', 'spx-settings');
    settingsButton.setAttribute('aria-expanded', 'false');
    toolbar.appendChild(settingsButton);
    document.body.appendChild(toolbar);
  }

  function isEditableKeyboardTarget(target) {
    if (!target) return false;
    var tagName = String(target.tagName || '').toLowerCase();
    return (
      tagName === 'input' ||
      tagName === 'textarea' ||
      tagName === 'select' ||
      !!target.isContentEditable ||
      !!(target.closest && target.closest('[contenteditable="true"]'))
    );
  }

  function bindForumKeyboardPaging() {
    if (document.documentElement.dataset.spxForumKeyboardPagingReady === '1') return;
    document.documentElement.dataset.spxForumKeyboardPagingReady = '1';
    document.addEventListener('keydown', function handleForumKeyboardPaging(event) {
      if (!shouldUseForumKeyboardPaging(location.href)) return;
      if (!event || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
      if (isEditableKeyboardTarget(event.target)) return;

      var key = event.key || event.code;
      var page = currentPageNumber(location.href);
      if (key === 'ArrowLeft' || key === 'Left') {
        if (page <= 1) return;
        event.preventDefault();
        location.href = buildPageUrl(location.href, page - 1);
        return;
      }
      if (key === 'ArrowRight' || key === 'Right') {
        event.preventDefault();
        location.href = buildPageUrl(location.href, page + 1);
      }
    }, true);
  }

  function toolbarButton(text, title, onClick) {
    var button = createEl('button', '', text);
    button.type = 'button';
    button.title = title;
    button.addEventListener('click', onClick);
    return button;
  }

  function getCurrentForumDashboardPayload(settings, state) {
    return {
      settings: settings,
      read: state && state.read,
      watch: state && state.watch,
      progress: state && state.progress,
      resources: state && state.resources,
      autoBuyAttempts: loadAutoBuyAttempts(),
      favoriteSeen: loadMap(FAVORITE_NAV_SEEN_KEY),
      requestState: scriptRequestState,
    };
  }

  function formatForumDashboardDigest(report) {
    var data = report || collectForumDashboardReport({});
    var stats = data.stats || {};
    var lines = [
      'South Plus +++ 论坛仪表盘',
      '今日浏览:' + (stats.todayViewed || 0),
      '收藏新增:' + (stats.favoriteAdded || 0),
      '未读收藏:' + (stats.unreadFavorites || 0),
      '资源新增:' + (stats.resourceAdded || 0),
      '稍后看积压:' + (stats.watchBacklog || 0),
      '请求状态:' + (stats.requestStatus || '正常'),
      '',
      '值得回看:',
    ];
    (data.worthReviewing || []).forEach(function appendWorthTopic(topic, index) {
      lines.push((index + 1) + '. ' + topic.title + ' - ' + (topic.reason || topic.meta || '本地记录'));
    });
    return lines.join('\n');
  }

  function createForumDashboardStat(label, value, note, className) {
    var card = createEl('article', 'spx-forum-dashboard-stat' + (className ? ' ' + className : ''));
    card.appendChild(createEl('span', 'spx-forum-dashboard-stat-label', label));
    card.appendChild(createEl('strong', '', String(value == null ? 0 : value)));
    card.appendChild(createEl('small', '', note || ''));
    return card;
  }

  function createForumDashboardChip(text, className) {
    return createEl('span', 'spx-forum-dashboard-chip' + (className ? ' ' + className : ''), text);
  }

  function createForumDashboardTopicRow(topic) {
    var row = createEl('article', 'spx-forum-dashboard-topic');
    var body = createEl('div', 'spx-forum-dashboard-topic-main');
    var title = createEl(topic.url ? 'a' : 'span', 'spx-forum-dashboard-topic-title', topic.title || '未命名主题');
    if (topic.url) title.href = topic.url;
    body.appendChild(title);
    body.appendChild(createEl('div', 'spx-forum-dashboard-topic-meta', topic.reason || topic.meta || '本地记录'));
    row.appendChild(body);
    row.appendChild(createEl('div', 'spx-forum-dashboard-score', String(Math.max(0, Math.round(topic.score || 0)))));
    var chips = createEl('div', 'spx-forum-dashboard-chips');
    if (topic.resourceCount) chips.appendChild(createForumDashboardChip('资源 ' + topic.resourceCount, 'spx-green'));
    if (topic.hasWatch) chips.appendChild(createForumDashboardChip('稍后', 'spx-violet'));
    if (topic.hasSiteFavorite) chips.appendChild(createForumDashboardChip('收藏', 'spx-amber'));
    if (topic.progressPercent) chips.appendChild(createForumDashboardChip(topic.progressPercent + '%', ''));
    row.appendChild(chips);
    return row;
  }

  function createForumDashboardRankRows(items, emptyText) {
    var list = createEl('div', 'spx-forum-dashboard-ranks');
    var source = (items || []).filter(function keepRankItem(item) { return item && item.count > 0; }).slice(0, 4);
    var max = source.reduce(function maxRankValue(value, item) { return Math.max(value, Number(item.count) || 0); }, 1);
    if (!source.length) {
      list.appendChild(createEl('div', 'spx-forum-dashboard-empty', emptyText || '暂无数据'));
      return list;
    }
    source.forEach(function appendRank(item) {
      var row = createEl('div', 'spx-forum-dashboard-rank');
      row.appendChild(createEl('span', '', item.label));
      var bar = createEl('b', '');
      var fill = createEl('i', '');
      fill.style.width = Math.max(8, Math.round((Number(item.count) || 0) / max * 100)) + '%';
      bar.appendChild(fill);
      row.appendChild(bar);
      row.appendChild(createEl('strong', '', String(item.count)));
      list.appendChild(row);
    });
    return list;
  }

  function createForumDashboardResourceRow(entry) {
    var row = createEl('article', 'spx-forum-dashboard-resource');
    var body = createEl('div');
    var title = createEl(entry.sourceUrl ? 'a' : 'span', 'spx-forum-dashboard-resource-title', entry.sourceTitle || entry.label || '未命名资源');
    if (entry.sourceUrl) title.href = entry.sourceUrl;
    body.appendChild(title);
    body.appendChild(createEl('div', 'spx-forum-dashboard-topic-meta', [entry.label, entry.provider, entry.statusLabel].filter(Boolean).join(' · ') || '资源记录'));
    row.appendChild(body);
    row.appendChild(createForumDashboardChip(entry.type === 'cloud' ? '网盘' : entry.label, 'spx-green'));
    return row;
  }

  function createForumDashboardActivityRow(item) {
    var row = createEl('article', 'spx-forum-dashboard-activity spx-' + (item.type || 'progress'));
    row.appendChild(createEl('span', 'spx-forum-dashboard-activity-mark'));
    var body = createEl('div');
    body.appendChild(createEl('div', 'spx-forum-dashboard-topic-title', item.title || '本地动态'));
    body.appendChild(createEl('div', 'spx-forum-dashboard-topic-meta', item.meta || ''));
    row.appendChild(body);
    row.appendChild(createEl('time', '', item.time || ''));
    return row;
  }

  function renderForumDashboardPanel(panel, settings, state) {
    if (!panel) return;
    var report = collectForumDashboardReport(getCurrentForumDashboardPayload(settings, state));
    panel.textContent = '';
    var header = createEl('div', 'spx-forum-dashboard-head');
    var title = createEl('div');
    title.appendChild(createEl('h2', '', '论坛仪表盘'));
    title.appendChild(createEl('p', '', '浏览、收藏、资源和本地积压的首页总览。'));
    header.appendChild(title);
    var actions = createEl('div', 'spx-forum-dashboard-actions');
    [['refresh-dashboard', '刷新摘要'], ['open-watch-backlog', '只看未读'], ['copy-dashboard-digest', '复制日报']].forEach(function appendAction(item) {
      var button = createEl('button', item[0] === 'refresh-dashboard' ? 'spx-primary' : '', item[1]);
      button.type = 'button';
      button.dataset.action = item[0];
      actions.appendChild(button);
    });
    header.appendChild(actions);
    panel.appendChild(header);

    var stats = createEl('div', 'spx-forum-dashboard-stats');
    stats.appendChild(createForumDashboardStat('今日浏览', report.stats.todayViewed, '本地已读 / 阅读进度', 'spx-blue'));
    stats.appendChild(createForumDashboardStat('收藏新增', report.stats.favoriteAdded, '站内收藏 + 稍后看', 'spx-amber'));
    stats.appendChild(createForumDashboardStat('未读收藏', report.stats.unreadFavorites, '未完成的收藏主题', 'spx-rose'));
    stats.appendChild(createForumDashboardStat('资源新增', report.stats.resourceAdded, '今日保存资源', 'spx-green'));
    stats.appendChild(createForumDashboardStat('稍后看积压', report.stats.watchBacklog, '未读完稍后看', 'spx-violet'));
    stats.appendChild(createForumDashboardStat('请求状态', report.stats.requestStatus, report.request.detail, report.request.status === '正常' ? 'spx-green' : 'spx-amber'));
    panel.appendChild(stats);

    var grid = createEl('div', 'spx-forum-dashboard-grid');
    var left = createEl('div', 'spx-forum-dashboard-stack');
    var topicsPanel = createEl('section', 'spx-forum-dashboard-card spx-forum-dashboard-wide');
    topicsPanel.appendChild(createEl('h3', '', '值得回看的主题'));
    var topicList = createEl('div', 'spx-forum-dashboard-topic-list');
    if (report.worthReviewing.length) {
      report.worthReviewing.slice(0, 3).forEach(function appendTopic(topic) {
        topicList.appendChild(createForumDashboardTopicRow(topic));
      });
    } else {
      topicList.appendChild(createEl('div', 'spx-forum-dashboard-empty', '暂无需要回看的主题。'));
    }
    topicsPanel.appendChild(topicList);
    left.appendChild(topicsPanel);

    var split = createEl('div', 'spx-forum-dashboard-split');
    var tagPanel = createEl('section', 'spx-forum-dashboard-card');
    tagPanel.appendChild(createEl('h3', '', '高频标签'));
    tagPanel.appendChild(createForumDashboardRankRows(report.tagStats, '暂无标签统计'));
    split.appendChild(tagPanel);
    var backlogPanel = createEl('section', 'spx-forum-dashboard-card');
    backlogPanel.appendChild(createEl('h3', '', '本地积压'));
    backlogPanel.appendChild(createForumDashboardRankRows(report.backlogStats, '暂无积压'));
    split.appendChild(backlogPanel);
    left.appendChild(split);
    grid.appendChild(left);

    var right = createEl('div', 'spx-forum-dashboard-stack');
    var activityPanel = createEl('section', 'spx-forum-dashboard-card');
    activityPanel.appendChild(createEl('h3', '', '最近动态'));
    var activityList = createEl('div', 'spx-forum-dashboard-activity-list');
    if (report.activities.length) {
      report.activities.forEach(function appendActivity(item) {
        activityList.appendChild(createForumDashboardActivityRow(item));
      });
    } else {
      activityList.appendChild(createEl('div', 'spx-forum-dashboard-empty', '暂无本地动态。'));
    }
    activityPanel.appendChild(activityList);
    right.appendChild(activityPanel);

    var resourcePanel = createEl('section', 'spx-forum-dashboard-card');
    resourcePanel.appendChild(createEl('h3', '', '资源速览'));
    var resourceList = createEl('div', 'spx-forum-dashboard-resource-list');
    if (report.resources.length) {
      report.resources.slice(0, 3).forEach(function appendResource(entry) {
        resourceList.appendChild(createForumDashboardResourceRow(entry));
      });
    } else {
      resourceList.appendChild(createEl('div', 'spx-forum-dashboard-empty', '资源工作台暂无记录。'));
    }
    resourcePanel.appendChild(resourceList);
    right.appendChild(resourcePanel);
    grid.appendChild(right);
    panel.appendChild(grid);

    panel.spxDashboardReport = report;
  }

  function createForumDashboardPanel(settings, state) {
    var panel = createEl('section', 'spx-forum-dashboard-panel spx-home-module');
    panel.id = 'spx-forum-dashboard-panel';
    panel.dataset.spxLarge = '1';
    renderForumDashboardPanel(panel, settings, state);
    panel.addEventListener('click', function handleDashboardClick(event) {
      var target = event.target;
      var action = target && target.dataset && target.dataset.action;
      if (!action) return;
      if (action === 'refresh-dashboard') {
        renderForumDashboardPanel(panel, settings, state);
        setTemporaryText(target, '已刷新', '刷新摘要');
      }
      if (action === 'open-watch-backlog') {
        var watchPanel = createWatchCenterPanel(settings, state);
        setCenterPanelHidden(watchPanel, false);
      }
      if (action === 'copy-dashboard-digest') {
        copyTextToClipboard(formatForumDashboardDigest(panel.spxDashboardReport)).then(
          function showCopySuccess() { setTemporaryText(target, '已复制', '复制日报'); },
          function showCopyFailure() { setTemporaryText(target, '复制失败', '复制日报'); }
        );
      }
    });
    return panel;
  }

  function enhanceHome(settings, state) {
    if (detectPageType(location.href) !== 'home') return;
    enhanceHomeDashboard(settings, state);
    if (!settings.cleanMode) {
      qsa('[data-spx-home-hidden="1"]').forEach(function showTable(table) {
        table.style.display = '';
        delete table.dataset.spxHomeHidden;
      });
      return;
    }
    qsa('table').forEach(function markTable(table) {
      if (/友情链接|在线用户/.test(table.textContent || '')) {
        table.dataset.spxHomeHidden = '1';
        table.style.display = 'none';
      }
    });
  }

  function enhanceHomeDashboard(settings, state) {
    restoreHomeDashboard();
    if (!shouldUseHomeDashboard(settings, location.href)) return;

    var modules = qsa('#content .t[id^="t_"]');
    if (!modules.length) return;

    var grid = createEl('div');
    grid.id = 'spx-home-grid';
    modules[0].parentNode.insertBefore(grid, modules[0]);

    if (settings.forumDashboard !== false) {
      var dashboard = createForumDashboardPanel(settings, state);
      grid.appendChild(dashboard);
      queueModuleNavigationConfigs([{
        section: '站点导航',
        label: '论坛仪表盘',
        href: getHomeNavigationBaseUrl(location.origin) + '#spx-forum-dashboard-panel',
        title: '论坛仪表盘',
        target: dashboard,
        active: true,
        alwaysShow: true,
        navigationOnly: true,
      }]);
    }

    modules.forEach(function markModule(module) {
      var marker = createEl('span');
      marker.dataset.spxHomeMarker = module.id || 'module';
      marker.style.display = 'none';
      module.parentNode.insertBefore(marker, module);
      grid.appendChild(module);

      module.classList.add('spx-home-module');
      var rows = qsa('tr.tr3', module);
      if (rows.length > 6) module.dataset.spxLarge = '1';

      var header = qs('.h', module) || qs('h2', module);
      if (header && !qs('.spx-home-collapse', header)) {
        var collapse = createEl('button', 'spx-home-collapse', '折叠');
        collapse.type = 'button';
        collapse.addEventListener('click', function toggleModule() {
          var collapsed = module.dataset.spxCollapsed === '1';
          module.dataset.spxCollapsed = collapsed ? '0' : '1';
          setHomeModuleCollapsed(rows, !collapsed);
          collapse.textContent = collapsed ? '折叠' : '展开';
        });
        header.appendChild(collapse);
      }

      rows.forEach(function markForumRow(row) {
        var titleNode = qs('[id^="fn_"]', row);
        if (!titleNode || titleNode.dataset.spxHomeReady) return;
        titleNode.dataset.spxHomeReady = '1';
        var today = parseTodayCount(titleNode.textContent);
        if (today > 0) {
          row.classList.add('spx-home-hot');
          var badge = createEl('span', 'spx-home-badge', String(today));
          titleNode.appendChild(badge);
        }
      });
    });
    createHomeModuleNavigation(modules);
  }

  function restoreHomeDashboard() {
    restoreModuleNavigation();
    var quick = qs('#spx-home-quick');
    if (quick) quick.remove();
    qsa('.spx-home-module').forEach(function restoreModule(module) {
      var marker = qsa('[data-spx-home-marker]').filter(function findMarker(item) {
        return item.dataset.spxHomeMarker === (module.id || 'module');
      })[0];
      if (marker && marker.parentNode) {
        marker.parentNode.insertBefore(module, marker);
      }
      module.classList.remove('spx-home-module');
      delete module.dataset.spxLarge;
      delete module.dataset.spxCollapsed;
      qsa('.spx-home-collapse', module).forEach(function removeButton(button) {
        button.remove();
      });
      qsa('tr.tr3', module).forEach(function restoreRow(row) {
        setHomeModuleCollapsed([row], false);
        row.classList.remove('spx-home-hot');
      });
      qsa('[data-spx-home-ready="1"]', module).forEach(function restoreTitle(titleNode) {
        delete titleNode.dataset.spxHomeReady;
      });
      qsa('.spx-home-badge', module).forEach(function removeBadge(badge) {
        badge.remove();
      });
    });
    qsa('[data-spx-home-marker]').forEach(function removeMarker(marker) {
      marker.remove();
    });
    var grid = qs('#spx-home-grid');
    if (grid) grid.remove();
  }

  function unwrapTaskLayoutStacks(host) {
    qsa('.spx-task-side-stack,.spx-task-main-stack', host).forEach(function unwrapStack(stack) {
      while (stack.firstChild) {
        stack.parentNode.insertBefore(stack.firstChild, stack);
      }
      stack.remove();
    });
  }

  function isTaskBreadcrumbBlock(node) {
    if (!node || !node.classList) return false;
    return node.classList.contains('bdbA') || node.classList.contains('spx-task-breadcrumb-block') || !!qs('#breadcrumbs', node);
  }

  function markTaskBreadcrumbBlocks(host) {
    qsa('.spx-task-breadcrumb-block', host).forEach(function clearTaskBreadcrumbBlock(node) {
      node.classList.remove('spx-task-breadcrumb-block');
    });
    Array.prototype.slice.call(host.children || []).forEach(function markDirectTaskBreadcrumb(child) {
      if (isTaskBreadcrumbBlock(child)) child.classList.add('spx-task-breadcrumb-block');
    });
    qsa('#breadcrumbs', host).forEach(function markNestedTaskBreadcrumb(breadcrumbs) {
      var block = getTaskLayoutDirectChild(host, breadcrumbs);
      if (block && block !== host && block.classList) block.classList.add('spx-task-breadcrumb-block');
    });
  }

  function isTaskMainBlock(node) {
    var text = String((node && node.textContent) || '');
    if (!text.trim()) return false;
    if (/(任务时效|上次领取|奖励\s*[::])/.test(text)) return true;
    if (/\bfr\b/.test(String(node.className || '')) && !/社区论坛任务功能简介|版权/.test(text)) return true;
    return false;
  }

  function isTaskSideBlock(node) {
    var text = String((node && node.textContent) || '');
    if (!text.trim()) return false;
    if (/社区论坛任务功能简介|操作|版权|作者\s*[::]|网站\s*[::]/.test(text)) return true;
    return /\bfl\b/.test(String(node.className || ''));
  }

  function getTaskLayoutDirectChild(host, node) {
    var current = node;
    while (current && current.parentNode && current.parentNode !== host) {
      current = current.parentNode;
    }
    return current && current.parentNode === host ? current : node;
  }

  function hasTaskLayoutBlockClass(node) {
    return !!(node && /\b(?:fl|fr|t|t3|t5)\b/.test(String(node.className || '')));
  }

  function getTaskLayoutCandidateNodes(host) {
    var nodes = [];
    function appendNode(node) {
      if (!node || node === host || nodes.indexOf(node) !== -1) return;
      if (isTaskBreadcrumbBlock(node)) return;
      nodes.push(node);
    }
    Array.prototype.slice.call(host.children || []).forEach(appendNode);
    qsa('.fl,.fr,.t,.t3,.t5', host).forEach(appendNode);
    var matched = nodes.filter(function keepTaskLayoutBlock(node) {
      if (!isTaskMainBlock(node) && !isTaskSideBlock(node)) return false;
      if (
        node.parentNode === host &&
        !hasTaskLayoutBlockClass(node) &&
        qsa('.fl,.fr,.t,.t3,.t5', node).some(function hasInnerTaskBlock(inner) {
          return isTaskMainBlock(inner) || isTaskSideBlock(inner);
        })
      ) {
        return false;
      }
      return true;
    });
    return matched.filter(function keepInnermostTaskBlock(node) {
      return !matched.some(function hasSelectedDescendant(other) {
        return other !== node && node.contains(other);
      });
    });
  }

  function sortTaskLayoutNodes(nodes) {
    return nodes.slice().sort(function compareDomOrder(a, b) {
      if (a === b) return 0;
      if (!a.compareDocumentPosition) return 0;
      return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
    });
  }

  function enhanceTaskPageLayout(settings) {
    if (!shouldUseTaskPage(location.href)) return;
    var host = qs('.spx-module-body') || qs('#main');
    if (!host) return;
    unwrapTaskLayoutStacks(host);
    markTaskBreadcrumbBlocks(host);

    qsa('.spx-task-side-block,.spx-task-main-block', host).forEach(function resetTaskBlockClass(node) {
      node.classList.remove('spx-task-side-block', 'spx-task-main-block');
    });

    var sideItems = [];
    var mainItems = [];
    sortTaskLayoutNodes(getTaskLayoutCandidateNodes(host)).forEach(function collectTaskLayoutItem(node) {
      var isMain = isTaskMainBlock(node);
      var isSide = isTaskSideBlock(node);
      if (isMain && (!isSide || /(任务时效|上次领取|奖励\s*[::])/.test(String(node.textContent || '')))) {
        mainItems.push(node);
        return;
      }
      if (isSide) {
        sideItems.push(node);
      }
    });
    syncTaskClaimRecordsFromCurrentPage();
    if (!mainItems.length) {
      host.classList.remove('spx-task-layout-body');
      return;
    }

    host.classList.add('spx-task-layout-body');
    var anchorItem = sortTaskLayoutNodes(sideItems.concat(mainItems))[0] || null;
    var anchor = anchorItem ? getTaskLayoutDirectChild(host, anchorItem) : null;
    var sideStack = createEl('div', 'spx-task-side-stack');
    var mainStack = createEl('div', 'spx-task-main-stack');
    host.insertBefore(sideStack, anchor);
    host.insertBefore(mainStack, anchor);
    sideItems.forEach(function moveSideItem(item) {
      item.classList.add('spx-task-side-block');
      sideStack.appendChild(item);
    });
    mainItems.forEach(function moveMainItem(item) {
      item.classList.add('spx-task-main-block');
      mainStack.appendChild(item);
    });
    maybeRunAutoTaskClaim(settings, mainStack);
    ensureTaskClaimInlineSection(mainStack);
  }

  function enhanceAdBlock(settings) {
    restoreAdBlock();
    if (!settings.adBlock) return;

    qsa('a[href], area[href]').forEach(function hideAdLink(link) {
      if (!isAdUrl(link.href)) return;
      markAdHidden(link);

      var cell = link.closest('td');
      if (cell && isMostlyAdContainer(cell)) markAdHidden(cell);

      var row = link.closest('tr');
      if (row && isMostlyAdContainer(row)) markAdHidden(row);

      var box = link.closest('div');
      if (box && box.id !== 'header' && box.id !== 'mainNav' && isMostlyAdContainer(box)) {
        markAdHidden(box);
      }
    });

    qsa('img[src]').forEach(function hideAdImage(img) {
      if (!isAdUrl(img.src)) return;
      markAdHidden(img);
      var link = img.closest('a');
      if (link) markAdHidden(link);
    });
  }

  function markAdHidden(node) {
    if (!node || node.dataset.spxAdHidden === '1') return;
    node.dataset.spxAdPreviousDisplay = node.style.display || '';
    node.dataset.spxAdHidden = '1';
    node.classList.add('spx-ad-hidden');
    node.style.display = 'none';
  }

  function restoreAdBlock() {
    qsa('[data-spx-ad-hidden="1"]').forEach(function restoreAd(node) {
      node.classList.remove('spx-ad-hidden');
      node.style.display = node.dataset.spxAdPreviousDisplay || '';
      delete node.dataset.spxAdHidden;
      delete node.dataset.spxAdPreviousDisplay;
    });
  }

  function isMostlyAdContainer(node) {
    if (!node) return false;
    var text = (node.textContent || '').replace(/\s+/g, '');
    var links = qsa('a[href], area[href]', node);
    var images = qsa('img', node);
    if (!links.length && !images.length) return false;
    var adLinks = links.filter(function checkLink(link) {
      return isAdUrl(link.href);
    });
    if (adLinks.length && adLinks.length === links.length && text.length < 20) return true;
    if (images.length && text.length < 12 && node.getBoundingClientRect && node.getBoundingClientRect().height <= 180) return true;
    return false;
  }

  function enhanceAll(settings, state) {
    var pageType = detectPageType(location.href);
    enhanceCycle += 1;
    invalidateCommandPaletteCache();
    setBodyClasses(settings);
    pendingModuleNavigationConfigs = [];
    restoreModuleNavigation();
    if (shouldUseSiteShell(location.href)) applySiteShellLayout(document);
    enhanceSiteNavigation(document);
    enhanceFavoriteNavigation(settings, state, document);
    standardizeExistingInfobox();
    if (pageType === 'profile') enhanceAccountNavigation(document);
    if (pageType === 'profile') restoreProfileInfobox(settings);
    enhanceAdBlock(settings);
    if (pageType === 'home') enhanceHome(settings, state);
    if (pageType === 'forum') enhanceThreadList(settings, state);
    if (pageType === 'read') enhanceReadPage(settings, state);
    if (pageType === 'read' || pageType === 'post') enhanceQuickReply(settings, state);
    if (shouldUseModuleNavigation(settings, location.href, document)) createGlobalModuleNavigation(settings, state);
    if (pageType === 'task') enhanceTaskPageLayout(settings);
    maybeRunAutoTaskClaim(settings, null);
    var panel = qs('#spx-settings');
    if (panel && panel.spxSync) panel.spxSync();
  }

  function init() {
    if (testMode || typeof window === 'undefined' || !document.body) return;
    var settings = loadSettings();
    var state = {
      read: loadMap(READ_KEY),
      watch: loadMap(WATCH_KEY),
      progress: loadReadProgress(),
      threadUpdates: loadThreadUpdates(),
      resources: loadResourceLibrary(),
    };
    injectStyles();
    setBodyClasses(settings);
    createToolbar(settings, state);
    bindCommandPaletteKeyboard(settings, state);
    if (shouldUseForumKeyboardPaging(location.href)) bindForumKeyboardPaging();
    enhanceAll(settings, state);
  }

  return {
    init: init,
    getDefaultSettings: function getDefaultSettings() {
      return copySettings(DEFAULT_SETTINGS);
    },
    parseThreadId: parseThreadId,
    parseLineList: parseLineList,
    parseQuickReplyList: parseQuickReplyList,
    parseTagList: parseTagList,
    createBackupPayload: createBackupPayload,
    normalizeBackupPayload: normalizeBackupPayload,
    formatBackupFileName: formatBackupFileName,
    collectDataHealthReport: collectDataHealthReport,
    cleanupDataHealthPayload: cleanupDataHealthPayload,
    formatDataHealthSummary: formatDataHealthSummary,
    formatDataHealthWarnings: formatDataHealthWarnings,
    collectStorageUsageReport: collectStorageUsageReport,
    formatStorageBytes: formatStorageBytes,
    formatStorageUsageSummary: formatStorageUsageSummary,
    formatStorageUsageWarnings: formatStorageUsageWarnings,
    formatStorageUsageLimit: formatStorageUsageLimit,
    getStorageUsageLevel: getStorageUsageLevel,
    formatStorageUsageEntry: formatStorageUsageEntry,
    formatBackupImportPreview: formatBackupImportPreview,
    formatReadProgress: formatReadProgress,
    getReadProgressRestoreTarget: getReadProgressRestoreTarget,
    normalizeThreadUpdateRecord: normalizeThreadUpdateRecord,
    parseThreadReplyCount: parseThreadReplyCount,
    parseThreadReadReplyCountFromDocument: parseThreadReadReplyCountFromDocument,
    shouldCheckThreadUpdate: shouldCheckThreadUpdate,
    updateThreadReplyState: updateThreadReplyState,
    applyThreadUpdateEntries: applyThreadUpdateEntries,
    getThreadUpdateStatusForEntry: getThreadUpdateStatusForEntry,
    buildThreadLatestReadUrl: buildThreadLatestReadUrl,
    decorateFavoriteNavEntryWithUpdate: decorateFavoriteNavEntryWithUpdate,
    decorateFavoriteNavEntriesWithUpdates: decorateFavoriteNavEntriesWithUpdates,
    getFavoriteNavUpdateSummary: getFavoriteNavUpdateSummary,
    getFavoriteNavUnreadUrl: getFavoriteNavUnreadUrl,
    extractThreadReplyCountFromRow: extractThreadReplyCountFromRow,
    mergeReadProgressRecord: mergeReadProgressRecord,
    pruneReadProgress: pruneReadProgress,
    getWatchCenterEntries: getWatchCenterEntries,
    getHistoryCenterEntries: getHistoryCenterEntries,
    getAutoBuyCenterEntries: getAutoBuyCenterEntries,
    collectForumDashboardReport: collectForumDashboardReport,
    getForumDashboardTopicScore: getForumDashboardTopicScore,
    formatForumDashboardDigest: formatForumDashboardDigest,
    filterWatchCenterEntries: filterWatchCenterEntries,
    filterHistoryCenterEntries: filterHistoryCenterEntries,
    filterAutoBuyCenterEntries: filterAutoBuyCenterEntries,
    normalizeCommandPaletteFilter: normalizeCommandPaletteFilter,
    getCommandPaletteCategoryLabel: getCommandPaletteCategoryLabel,
    collectCommandPaletteEntries: collectCommandPaletteEntries,
    filterCommandPaletteEntries: filterCommandPaletteEntries,
    formatCommandPaletteResultSummary: formatCommandPaletteResultSummary,
    matchesBlockRules: matchesBlockRules,
    parseForumFilterQuery: parseForumFilterQuery,
    matchesForumFilter: matchesForumFilter,
    extractPreviewImageUrls: extractPreviewImageUrls,
    isLargePreviewImage: isLargePreviewImage,
    formatPreviewImageLinks: formatPreviewImageLinks,
    getPreviewImageMetaText: getPreviewImageMetaText,
    formatPreviewImageMarkdownLinks: formatPreviewImageMarkdownLinks,
    formatPreviewImageLinksByFloor: formatPreviewImageLinksByFloor,
    getPreviewGalleryRenderState: getPreviewGalleryRenderState,
    formatPreviewGallerySummary: formatPreviewGallerySummary,
    formatPreviewImageArchiveFileName: formatPreviewImageArchiveFileName,
    sanitizePreviewDownloadName: sanitizePreviewDownloadName,
    getPreviewImageDownloadExtension: getPreviewImageDownloadExtension,
    formatPreviewImageDownloadFileName: formatPreviewImageDownloadFileName,
    getPreviewDownloadStatusSummary: getPreviewDownloadStatusSummary,
    formatPreviewDownloadReport: formatPreviewDownloadReport,
    getZipCrc32: getZipCrc32,
    createPreviewZipBlob: createPreviewZipBlob,
    normalizeResourceUrl: normalizeResourceUrl,
    classifyResourceLink: classifyResourceLink,
    getCloudProviderLabel: getCloudProviderLabel,
    getResourceDisplayLabel: getResourceDisplayLabel,
    getResourceBadgeDefinition: getResourceBadgeDefinition,
    getResourceBadgeFromResourceItem: getResourceBadgeFromResourceItem,
    inferResourceBadgesFromText: inferResourceBadgesFromText,
    getThreadResourceBadgeIndex: getThreadResourceBadgeIndex,
    getThreadResourceBadges: getThreadResourceBadges,
    getResourceBadgeTypes: getResourceBadgeTypes,
    getResourceRailTypeKey: getResourceRailTypeKey,
    getResourceRailTypeLabel: getResourceRailTypeLabel,
    getResourceRailEntries: getResourceRailEntries,
    filterResourceRailEntries: filterResourceRailEntries,
    formatResourceRailSummary: formatResourceRailSummary,
    formatResourceRailSummaryText: formatResourceRailSummaryText,
    formatResourceRailCodes: formatResourceRailCodes,
    getAvailableResourceRailFilterTypes: getAvailableResourceRailFilterTypes,
    extractResourceLinksFromText: extractResourceLinksFromText,
    filterResourceLinks: filterResourceLinks,
    formatResourceLinks: formatResourceLinks,
    getJumpResourceLinks: getJumpResourceLinks,
    getResourceDownloadQueueEntries: getResourceDownloadQueueEntries,
    formatResourceDownloadList: formatResourceDownloadList,
    formatResourceMarkdownList: formatResourceMarkdownList,
    formatResourceDownloadFileName: formatResourceDownloadFileName,
    formatResourceJumpSummary: formatResourceJumpSummary,
    normalizeResourceTags: normalizeResourceTags,
    formatResourceTags: formatResourceTags,
    getSettingsPresetDefinitions: getSettingsPresetDefinitions,
    applySettingsPreset: applySettingsPreset,
    normalizeBatchConfirmItems: normalizeBatchConfirmItems,
    showBatchConfirmDialog: showBatchConfirmDialog,
    confirmBatchAction: confirmBatchAction,
    pruneResourceLibrary: pruneResourceLibrary,
    saveResourceLinksToLibrary: saveResourceLinksToLibrary,
    getResourceCenterEntries: getResourceCenterEntries,
    groupResourceCenterEntries: groupResourceCenterEntries,
    filterResourceCenterEntries: filterResourceCenterEntries,
    getSelectedResourceKeys: getSelectedResourceKeys,
    setResourceSelection: setResourceSelection,
    getResourceEntriesByKeys: getResourceEntriesByKeys,
    normalizeNavigationUsageMap: normalizeNavigationUsageMap,
    getModuleNavigationUsageKey: getModuleNavigationUsageKey,
    getModuleNavigationSmartScore: getModuleNavigationSmartScore,
    sortModuleNavigationTree: sortModuleNavigationTree,
    markThreadsRead: markThreadsRead,
    findThreadIdsByAuthor: findThreadIdsByAuthor,
    hasThreadRowHiddenClass: hasThreadRowHiddenClass,
    isVisibleThreadRow: isVisibleThreadRow,
    isStickyCell: isStickyCell,
    hideStickyThreads: hideStickyThreads,
    hideForumAnnouncementPanels: hideForumAnnouncementPanels,
    isPreviewImageCandidate: isPreviewImageCandidate,
    clampPreviewZoom: clampPreviewZoom,
    getPreviewLightboxKeyAction: getPreviewLightboxKeyAction,
    parsePostPrice: parsePostPrice,
    parseUserSpBalance: parseUserSpBalance,
    shouldAutoBuyPost: shouldAutoBuyPost,
    extractBuyTopicUrl: extractBuyTopicUrl,
    getAutoBuyAttemptKey: getAutoBuyAttemptKey,
    isAutoBuyAttemptBlocked: isAutoBuyAttemptBlocked,
    formatAutoBuyAttemptMessage: formatAutoBuyAttemptMessage,
    getAutoBuyDoneAttemptForThread: getAutoBuyDoneAttemptForThread,
    formatAutoBuyNavSuccessDetail: formatAutoBuyNavSuccessDetail,
    extractTaskAutoClaimUrl: extractTaskAutoClaimUrl,
    getTaskHomePageUrl: getTaskHomePageUrl,
    getTaskInProgressPageUrl: getTaskInProgressPageUrl,
    getTaskAutoClaimCooldownMs: getTaskAutoClaimCooldownMs,
    getLatestTaskClaimCompletedAt: getLatestTaskClaimCompletedAt,
    isTaskClaimCompletedToday: isTaskClaimCompletedToday,
    getTaskAutoClaimGate: getTaskAutoClaimGate,
    getTaskAutoClaimActionType: getTaskAutoClaimActionType,
    isTaskAutoClaimCandidate: isTaskAutoClaimCandidate,
    getTaskAutoClaimTargets: getTaskAutoClaimTargets,
    getTaskAutoClaimTargetsFromHtml: getTaskAutoClaimTargetsFromHtml,
    getTaskAutoClaimResponseResult: getTaskAutoClaimResponseResult,
    maybeRunAutoTaskClaim: maybeRunAutoTaskClaim,
    parseTaskClaimCompletedAt: parseTaskClaimCompletedAt,
    parseTaskClaimRecordsFromRows: parseTaskClaimRecordsFromRows,
    parseTaskClaimRecordsFromText: parseTaskClaimRecordsFromText,
    pruneTaskClaimRecords: pruneTaskClaimRecords,
    getTaskClaimCenterEntries: getTaskClaimCenterEntries,
    filterTaskClaimCenterEntries: filterTaskClaimCenterEntries,
    shouldSyncTaskClaimRecordsFromUrl: shouldSyncTaskClaimRecordsFromUrl,
    isAdUrl: isAdUrl,
    parseTodayCount: parseTodayCount,
    shouldUseSiteShell: shouldUseSiteShell,
    shouldUseForumDashboard: shouldUseForumDashboard,
    shouldUseForumKeyboardPaging: shouldUseForumKeyboardPaging,
    shouldUseSearchPage: shouldUseSearchPage,
    shouldUseProfilePage: shouldUseProfilePage,
    shouldRestoreProfileInfobox: shouldRestoreProfileInfobox,
    getProfileInfoboxSourceUrl: getProfileInfoboxSourceUrl,
    shouldUseTaskPage: shouldUseTaskPage,
    shouldUseReaderMode: shouldUseReaderMode,
    shouldUseImmersiveRead: shouldUseImmersiveRead,
    shouldUseHomeDashboard: shouldUseHomeDashboard,
    shouldUseModuleNavigation: shouldUseModuleNavigation,
    getCommonForumNavigationItems: getCommonForumNavigationItems,
    getCanonicalForumNavigationLabel: getCanonicalForumNavigationLabel,
    getCurrentForumId: getCurrentForumId,
    isForumGalleryModeUrl: isForumGalleryModeUrl,
    isForumGalleryModeLink: isForumGalleryModeLink,
    getForumPostLinkConfig: getForumPostLinkConfig,
    getForumListModeUrl: getForumListModeUrl,
    getForumGalleryModeUrl: getForumGalleryModeUrl,
    normalizeModuleNavDensity: normalizeModuleNavDensity,
    getModuleNavigationDensityConfig: getModuleNavigationDensityConfig,
    getModuleNavigationGroupKey: getModuleNavigationGroupKey,
    normalizeNavigationPinMap: normalizeNavigationPinMap,
    getModuleNavigationConfigPinKey: getModuleNavigationConfigPinKey,
    withPinnedModuleNavigationConfigs: withPinnedModuleNavigationConfigs,
    getFavoriteNavUrl: getFavoriteNavUrl,
    extractSiteVerifyHashFromText: extractSiteVerifyHashFromText,
    getSiteVerifyHash: getSiteVerifyHash,
    getThreadFavoriteUrl: getThreadFavoriteUrl,
    getThreadFavoriteResultText: getThreadFavoriteResultText,
    isNewThreadFavoriteResult: isNewThreadFavoriteResult,
    getSiteFavoriteDeleteResultText: getSiteFavoriteDeleteResultText,
    buildFavoriteDeleteRequest: buildFavoriteDeleteRequest,
    normalizeFavoriteDeleteFields: normalizeFavoriteDeleteFields,
    getFavoriteNavDeleteKey: getFavoriteNavDeleteKey,
    getSelectedSiteFavoriteEntries: getSelectedSiteFavoriteEntries,
    createFavoriteNavEntryFromThreadInfo: createFavoriteNavEntryFromThreadInfo,
    createReadPageFavoriteInfo: createReadPageFavoriteInfo,
    formatFavoriteNavCount: formatFavoriteNavCount,
    normalizeFavoriteNavCountCacheEntry: normalizeFavoriteNavCountCacheEntry,
    shouldRefreshFavoriteNavCountCache: shouldRefreshFavoriteNavCountCache,
    inferFavoriteNavTags: inferFavoriteNavTags,
    parseFavoriteSavedAt: parseFavoriteSavedAt,
    applyFavoriteNavSeenTimes: applyFavoriteNavSeenTimes,
    getFavoriteNavEntrySearchText: getFavoriteNavEntrySearchText,
    filterFavoriteNavEntries: filterFavoriteNavEntries,
    sortFavoriteNavEntries: sortFavoriteNavEntries,
    parseFavoriteReplyCount: parseFavoriteReplyCount,
    getFavoriteNavWatchEntries: getFavoriteNavWatchEntries,
    normalizeNavigationCollapseState: normalizeNavigationCollapseState,
    isModuleNavigationGroupCollapsed: isModuleNavigationGroupCollapsed,
    isModuleNavigationConfigActive: isModuleNavigationConfigActive,
    shouldShowToolbarFeature: shouldShowToolbarFeature,
    hasPreviewGalleryImages: hasPreviewGalleryImages,
    shouldShowToolbarAction: shouldShowToolbarAction,
    getSettingsPanelKeys: getSettingsPanelKeys,
    isNetworkFriendlyMode: isNetworkFriendlyMode,
    getScriptRequestPolicyConfig: getScriptRequestPolicyConfig,
    isScriptRateLimitStatus: isScriptRateLimitStatus,
    isScriptRateLimitHtml: isScriptRateLimitHtml,
    getScriptRequestDelay: getScriptRequestDelay,
    getNavigationRefreshKey: getNavigationRefreshKey,
    getThreadPreviewMetaText: getThreadPreviewMetaText,
    getThreadPreviewImageUrls: getThreadPreviewImageUrls,
    getThreadPreviewImageSummary: getThreadPreviewImageSummary,
    getThreadPreviewStatusChips: getThreadPreviewStatusChips,
    getThreadPreviewHoverDelay: getThreadPreviewHoverDelay,
    createQuickReplyRequest: createQuickReplyRequest,
    getQuickReplyAttachmentFiles: getQuickReplyAttachmentFiles,
    formatQuickReplyAttachmentSummary: formatQuickReplyAttachmentSummary,
    formatQuickReplyFileSize: formatQuickReplyFileSize,
    getQuickReplyEmotes: function getQuickReplyEmotes() { return QUICK_REPLY_EMOTES.slice(); },
    isQuickReplySubmitter: isQuickReplySubmitter,
    getQuickReplySubmitter: getQuickReplySubmitter,
    isQuickReplyEditorCandidate: isQuickReplyEditorCandidate,
    performQuickReplySubmit: performQuickReplySubmit,
    resolveQuickReplyRefreshUrl: resolveQuickReplyRefreshUrl,
    shouldUseQuickReplySubmitHtml: shouldUseQuickReplySubmitHtml,
    buildPageUrl: buildPageUrl,
    detectPageType: detectPageType,
    getInjectedStyleText: getInjectedStyleText,
    applySiteShellLayout: applySiteShellLayout,
    enhanceSiteNavigation: enhanceSiteNavigation,
    applyReadPostLayout: applyReadPostLayout,
    isEmptyReadSeparatorNode: isEmptyReadSeparatorNode,
    getPostToolsHost: getPostToolsHost,
    syncHiddenPostShell: syncHiddenPostShell,
    setHomeModuleCollapsed: setHomeModuleCollapsed,
  };
});