South Plus +++

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

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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,
  };
});