FC2 标记下载状态

在 FC2 商品栏目标注状态(未下载/已下载/无资源),支持动态加载、过滤、导入/导出、美观UI、详情页与全局菜单,新增可伸缩面板与功能强大的收藏弹窗。

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         FC2 标记下载状态
// @namespace    http://tampermonkey.net/
// @license MIT
// @version      1.5
// @description  在 FC2 商品栏目标注状态(未下载/已下载/无资源),支持动态加载、过滤、导入/导出、美观UI、详情页与全局菜单,新增可伸缩面板与功能强大的收藏弹窗。
// @match        https://adult.contents.fc2.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// ==/UserScript==

(function () {
  'use strict';
  console.log('[状态脚本] 加载(v1.5)');

  const STATES = ['未下载', '已下载', '无资源'];
  const COLORS = {
      '未下载': '#007bff', // Blue
      '已下载': '#28a745', // Green
      '无资源': '#dc3545'  // Red
  };
  const STORE_KEY = 'fc2_status_map_v1';
  const PANEL_STATE_KEY = 'fc2_panel_state_v1';
  const ITEMS_PER_PAGE_KEY = 'fc2_items_per_page_v1';

  /* ===== 存储帮助 ===== */
  function getStore() {
    try {
      const raw = GM_getValue(STORE_KEY, null);
      if (!raw) return {};
      return JSON.parse(raw);
    } catch (e) {
      console.error('[状态脚本] 读取存储失败', e);
      return {};
    }
  }
  function saveStore(map) {
    try {
      GM_setValue(STORE_KEY, JSON.stringify(map));
    } catch (e) {
      console.error('[状态脚本] 保存存储失败', e);
    }
  }

  /* ===== CSS (v1.5) ===== */
  const css = `
  /* 通用项目容器样式 */
  .c-neoItem-1000_wrap, .c-cntCard-110-f {
      position: relative;
      transition: transform .18s ease, box-shadow .18s ease, opacity .18s ease;
  }

  /* 右上角控制面板 */
  #fc2-status-panel {
    position: fixed; top: 12px; right: 12px; z-index: 9999999;
    background: rgba(255,255,255,0.95); border-radius: 10px;
    box-shadow: 0 6px 18px rgba(20,20,30,0.12);
    display:flex; align-items:center;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, "PingFang SC";
    transition: all 0.2s ease-in-out;
  }
  #fc2-status-panel .fc2-panel-content {
      display: flex; gap:8px; align-items:center; padding: 8px;
      overflow: hidden;
      transition: all 0.2s ease-in-out;
  }
  #fc2-status-panel button, #fc2-status-panel select {
      font-size:13px;padding:7px 10px;border-radius:8px;cursor:pointer; border:1px solid rgba(0,0,0,0.08);
      background: linear-gradient(180deg, #fff, #f6f6f7); box-shadow: 0 2px 6px rgba(20,20,30,0.04);
      white-space: nowrap;
  }
  #fc2-panel-toggle {
      padding: 8px; border: none; background: transparent; cursor: pointer;
  }
  #fc2-panel-toggle svg { width: 24px; height: 24px; transition: transform 0.3s ease; }

  /* 面板折叠状态 */
  #fc2-status-panel.collapsed .fc2-panel-content { display: none; }
  #fc2-status-panel.collapsed #fc2-panel-toggle svg { transform: rotate(-90deg); }

  /* 状态标记按钮 */
  .fc2-status { display:inline-block; position:relative; margin-left:10px; vertical-align: middle; font-family: inherit; flex-shrink: 0; }
  .fc2-status-btn {
    display:inline-flex; gap:8px; align-items:center; padding: 8px 16px; border-radius:18px; border:none;
    color:#fff; font-weight:600; font-size:13px; cursor:pointer; box-shadow: 0 4px 12px rgba(16,24,40,0.12);
    transition: transform .12s ease, box-shadow .12s ease; user-select:none;
    position: relative; z-index: 10010;
  }
  .fc2-status-btn:active { transform: translateY(1px); }
  .fc2-status .caret { width:10px; height:10px; opacity:0.95; transform: translateY(1px); }

  /* (已下载)更明显的透明度 */
  .c-neoItem-1000_wrap.fc2-s-downloaded, .c-cntCard-110-f.fc2-s-downloaded { opacity: 0.32 !important; transform: scale(0.997); }

  /* 无资源:更明显的红色发光边框 */
  .c-neoItem-1000_wrap.fc2-s-noresource, .c-cntCard-110-f.fc2-s-noresource {
    box-shadow: 0 0 0 3px rgba(255,50,50,0.95), 0 10px 40px rgba(255,40,40,0.28), inset 0 0 40px rgba(255,0,0,0.06);
    background: rgba(255,230,230,0.18);
    transform: translateY(-2px) scale(1.01);
    z-index: 6;
  }
  .fc2-noresource-ribbon {
    position:absolute; top:8px; left:8px;
    background: linear-gradient(90deg,#ff6b6b,#ff2d2d); color:white; font-size:11px; font-weight:700;
    padding:4px 8px; border-radius:6px; transform: rotate(-10deg); box-shadow: 0 6px 18px rgba(255,50,50,0.2); pointer-events:none; z-index: 8;
  }

  /* 全局浮动菜单 */
  #fc2-global-status-menu {
    position: fixed; display:none; min-width:160px; border-radius:10px; background:#fff; padding:6px;
    box-shadow: 0 18px 48px rgba(10,10,20,0.2); z-index: 2147483646; font-weight:700;
  }
  #fc2-global-status-menu li { list-style:none; padding:8px 10px; border-radius:8px; cursor:pointer; display:flex; gap:8px; align-items:center; color:#222; }
  #fc2-global-status-menu li:hover { background: rgba(0,0,0,0.04); }
  .fc2-dot { width:10px; height:10px; border-radius:50%; flex-shrink: 0; box-shadow: inset 0 -2px 0 rgba(0,0,0,0.12); }

  /* 收藏夹弹窗 */
  #fc2-favorites-modal-overlay {
      position: fixed; top: 0; left: 0; width: 100%; height: 100%;
      background: rgba(0,0,0,0.6); z-index: 2147483640;
      display: flex; align-items: center; justify-content: center;
  }
  #fc2-favorites-modal {
      width: 90%; max-width: 1200px; height: 85vh; background: #f0f2f5;
      border-radius: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.3);
      display: flex; flex-direction: column; overflow: hidden;
  }
  .fc2-favorites-header {
      display: flex; justify-content: space-between; align-items: center;
      padding: 12px 20px; border-bottom: 1px solid #e0e0e0; background: #fff;
  }
  .fc2-favorites-header h2 { margin: 0; font-size: 18px; }
  .fc2-favorites-filters button { margin-left: 8px; }
  .fc2-favorites-filters button.active { background: #007bff; color: white; border-color: #007bff; }
  .fc2-favorites-close { cursor: pointer; font-size: 28px; line-height: 1; opacity: 0.5; border:none; background:none; padding:0; }
  .fc2-favorites-close:hover { opacity: 1; }
  .fc2-favorites-content { flex-grow: 1; overflow-y: auto; padding: 10px; }
  .fc2-favorites-list { list-style: none; margin: 0; padding: 0; }
  .fc2-favorite-item { display: flex; align-items: flex-start; background: #fff; border-radius: 8px; padding: 12px; margin-bottom: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
  .fc2-favorite-item img { width: 120px; height: auto; border-radius: 4px; margin-right: 15px; flex-shrink: 0; }
  .fc2-favorite-info { flex-grow: 1; display: flex; flex-direction: column; min-width: 0; }
  .fc2-favorite-info h3 { margin: 0 0 8px; font-size: 16px; line-height: 1.3; }
  .fc2-favorite-info h3 a { text-decoration: none; color: #1a1a1a; }
  .fc2-favorite-info h3 a:hover { color: #007bff; }
  .fc2-favorite-info .meta-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 5px 15px; font-size: 13px; color: #555; }
  .fc2-favorite-info .meta-grid p { margin: 0; }
  .fc2-favorites-pagination { display: flex; justify-content: center; align-items: center; gap: 10px; padding: 10px; border-top: 1px solid #e0e0e0; background: #fff; }
  .fc2-favorites-pagination button, .fc2-favorites-pagination input, .fc2-favorites-pagination select { padding: 5px 10px; }
  .fc2-favorites-pagination input { width: 60px; text-align: center; }
  .fc2-favorites-loading { text-align: center; padding: 50px; font-size: 16px; color: #888; }
  `;
  const style = document.createElement('style');
  style.textContent = css;
  document.head.appendChild(style);

  /* ===== 全局菜单(单例) ===== */
  let globalMenu = null;
  function ensureGlobalMenu() {
    if (globalMenu) return globalMenu;
    globalMenu = document.createElement('div');
    globalMenu.id = 'fc2-global-status-menu';
    globalMenu.innerHTML = '<ul style="margin:0;padding:6px;"></ul>';
    document.body.appendChild(globalMenu);
    document.addEventListener('click', (ev) => {
      if (globalMenu && globalMenu.style.display !== 'none' && !globalMenu.contains(ev.target)) hideGlobalMenu();
    }, true);
    window.addEventListener('keydown', (ev)=> { if (ev.key === 'Escape') hideGlobalMenu(); });
    window.addEventListener('scroll', ()=> hideGlobalMenu(), { passive: true });
    return globalMenu;
  }
  function showGlobalMenuFor(buttonEl, id) {
    const menu = ensureGlobalMenu();
    const ul = menu.querySelector('ul'); ul.innerHTML = '';
    STATES.forEach(s => {
      const li = document.createElement('li');
      li.dataset.val = s;
      li.innerHTML = `<span class="fc2-dot" style="background:${COLORS[s]}"></span><span style="flex:1;">${s}</span>`;
      li.onclick = (ev) => { ev.stopPropagation(); setItemStatus(id, s); hideGlobalMenu(); };
      ul.appendChild(li);
    });
    const rect = buttonEl.getBoundingClientRect();
    const margin = 8;
    menu.style.display = 'block';
    const menuW = menu.offsetWidth;
    const menuH = menu.offsetHeight;
    let left = rect.left + rect.width - menuW;
    let top = rect.bottom + margin;
    if (top + menuH > window.innerHeight - 8) top = rect.top - margin - menuH;
    menu.style.left = `${Math.max(8, left)}px`;
    menu.style.top = `${top}px`;
  }
  function hideGlobalMenu() {
    if (globalMenu) globalMenu.style.display = 'none';
  }

  /* ===== 核心逻辑 ===== */
  const ITEM_SELECTOR = '.c-neoItem-1000_wrap, .c-cntCard-110-f';

  function extractIdFromItem(item) {
    const link = item.querySelector('.c-cntCard-110-f_itemName a, .c-cntCard-110-f_thumb_link');
    if (!link) return null;
    const href = link.getAttribute('href') || '';
    const m = href.match(/article\/(\d+)/);
    return m ? m[1] : null;
  }

  function setItemStatus(id, state) {
    const store = getStore();
    store[id] = state;
    saveStore(store);
    document.querySelectorAll(ITEM_SELECTOR).forEach(item => {
      if (extractIdFromItem(item) === String(id)) applyVisualAndButton(item, id, state);
    });
    const detailRoot = document.querySelector('.items_article_headerTitleInArea');
    if (detailRoot && extractIdFromDetail() === String(id)) {
      applyVisualAndButton(detailRoot, id, state);
    }
  }

  function applyVisualAndButton(itemOrRoot, id, state) {
    let cardRoot = null;
    if (itemOrRoot.matches(ITEM_SELECTOR)) cardRoot = itemOrRoot;
    else cardRoot = document.querySelector('.items_article_headerTitleInArea');
    if (!cardRoot) return;

    const cont = cardRoot.querySelector(`.fc2-status[data-id="${id}"]`);
    if (cont) {
      const btn = cont.querySelector('.fc2-status-btn');
      const label = cont.querySelector('.fc2-status-label');
      if (label) label.textContent = state;
      if (btn) btn.style.background = `linear-gradient(180deg, ${COLORS[state]}, ${shadeColor(COLORS[state], -10)})`;
    }

    cardRoot.classList.remove('fc2-s-downloaded', 'fc2-s-noresource');
    cardRoot.querySelectorAll('.fc2-noresource-ribbon').forEach(el => el.remove());
    if (state === '已下载') {
      cardRoot.classList.add('fc2-s-downloaded');
    } else if (state === '无资源') {
      cardRoot.classList.add('fc2-s-noresource');
      const thumb = cardRoot.querySelector('.c-cntCard-110-f_thumb') || cardRoot.querySelector('.items_article_MainitemThumb > span');
      if (thumb) {
        const rb = document.createElement('div');
        rb.className = 'fc2-noresource-ribbon';
        rb.textContent = '无资源';
        if (getComputedStyle(thumb).position === 'static') thumb.style.position = 'relative';
        thumb.appendChild(rb);
      }
    }
  }

  function insertStatusControl(itemContainer, id) {
    if (itemContainer.querySelector(`.fc2-status[data-id="${id}"]`)) return;
    const store = getStore();
    const cur = store[id] || STATES[0];

    const cont = document.createElement('div');
    cont.className = 'fc2-status';
    cont.dataset.id = id;
    cont.innerHTML = `
      <button class="fc2-status-btn" type="button" style="background: linear-gradient(180deg, ${COLORS[cur]}, ${shadeColor(COLORS[cur], -10)})">
        <span class="fc2-status-label">${cur}</span>
        <svg class="caret" viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"><path d="M1 3l4 4 4-4" fill="none" stroke="rgba(255,255,255,0.9)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
      </button>`;

    const btn = cont.querySelector('.fc2-status-btn');
    btn.onclick = (ev) => {
      ev.stopPropagation();
      ev.preventDefault();
      showGlobalMenuFor(btn, id);
    };

    itemContainer.appendChild(cont);
    applyVisualAndButton(itemContainer.closest(ITEM_SELECTOR + ', .items_article_headerTitleInArea'), id, cur);
  }

  /* ===== 渲染与监听 ===== */
  function renderExistingItems() {
    document.querySelectorAll(ITEM_SELECTOR).forEach(item => {
      const id = extractIdFromItem(item);
      if (id) {
        const infoArea = item.querySelector('.c-cntCard-110-f_indetail') || item;
        insertStatusControl(infoArea, id);
      }
    });
  }

  const observer = new MutationObserver(muts => {
    muts.forEach(mut => mut.addedNodes.forEach(node => {
      if (node instanceof Element) {
        const items = node.matches(ITEM_SELECTOR) ? [node] : node.querySelectorAll(ITEM_SELECTOR);
        items.forEach(item => {
          const id = extractIdFromItem(item);
          if (id) {
            const infoArea = item.querySelector('.c-cntCard-110-f_indetail') || item;
            insertStatusControl(infoArea, id);
          }
        });
      }
    }));
    renderDetailPage();
  });
  observer.observe(document.body, { childList: true, subtree: true });

  /* ===== 详情页 (v1.5 修复布局) ===== */
  function extractIdFromDetail() {
    const urlMatch = location.href.match(/article\/(\d+)/);
    return urlMatch ? urlMatch[1] : null;
  }
  function renderDetailPage() {
    const id = extractIdFromDetail();
    if (!id) return;

    const titleElement = document.querySelector('.items_article_headerTitleInArea h3');
    if (!titleElement || titleElement.querySelector('.fc2-status')) return;

    titleElement.style.display = 'flex';
    titleElement.style.alignItems = 'center';
    titleElement.style.justifyContent = 'space-between';

    const nodesToWrap = Array.from(titleElement.childNodes).filter(node => !node.classList || !node.classList.contains('fc2-status'));
    const titleSpan = document.createElement('span');
    nodesToWrap.forEach(node => titleSpan.appendChild(node.cloneNode(true)));

    // 清空 h3 并重新组合
    titleElement.innerHTML = '';
    titleElement.appendChild(titleSpan);

    insertStatusControl(titleElement, id);

    const store = getStore();
    const cur = store[id] || STATES[0];
    const root = document.querySelector('.items_article_headerTitleInArea');
    if (root) applyVisualAndButton(root, id, cur);
  }


  /* ===== 右上角面板 (v1.3) ===== */
  function buildPanel() {
      if (document.querySelector('#fc2-status-panel')) return;
      const panel = document.createElement('div');
      panel.id = 'fc2-status-panel';

      const content = document.createElement('div');
      content.className = 'fc2-panel-content';

      const toggleBtn = document.createElement('button');
      toggleBtn.id = 'fc2-panel-toggle';
      toggleBtn.title = "展开/折叠面板";
      toggleBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 l-3.84,0c-0.24,0-0.44,0.17-0.48,0.41L9.22,5.15C8.63,5.39,8.1,5.71,7.6,6.09L5.21,5.13C5,5.06,4.75,5.13,4.63,5.34L2.71,8.66 c-0.12,0.22-0.07,0.47,0.12,0.61L4.86,11c-0.05,0.3-0.07,0.62-0.07,0.94s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.38,2.34 c0.04,0.24,0.24,0.41,0.48,0.41l3.84,0c0.24,0,0.44-0.17,0.48-0.41l0.38-2.34c0.59-0.24,1.12-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0.01,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"></path></svg>`;

      toggleBtn.onclick = () => {
          const isCollapsed = panel.classList.toggle('collapsed');
          GM_setValue(PANEL_STATE_KEY, isCollapsed ? 'collapsed' : 'expanded');
      };

      const filter = document.createElement('select'); filter.title = "按状态过滤页面内容";
      ['全部', ...STATES].forEach(s => { filter.innerHTML += `<option value="${s}">显示: ${s}</option>`; });
      filter.onchange = () => applyFilter(filter.value);

      const btnFavorites = document.createElement('button'); btnFavorites.textContent = '显示已收藏'; btnFavorites.title = "打开弹窗显示所有已下载或无资源的项目";
      btnFavorites.onclick = openFavoritesModal;

      const btnExport = document.createElement('button'); btnExport.textContent = '导出';
      btnExport.onclick = exportStore;

      const btnImport = document.createElement('button'); btnImport.textContent = '导入';
      btnImport.onclick = openImportModal;

      const btnClear = document.createElement('button'); btnClear.textContent = '清空';
      btnClear.onclick = () => {
          if (confirm('确认清空所有状态?不可恢复。')) {
              saveStore({});
              document.querySelectorAll(ITEM_SELECTOR).forEach(it => resetItemVisual(it));
          }
      };

      content.append(filter, btnFavorites, btnExport, btnImport, btnClear);
      panel.append(content, toggleBtn);
      document.body.appendChild(panel);

      if (GM_getValue(PANEL_STATE_KEY, 'collapsed') === 'collapsed') {
          panel.classList.add('collapsed');
      }
  }

  function applyFilter(value) {
    document.querySelectorAll(ITEM_SELECTOR).forEach(item => {
      const id = extractIdFromItem(item);
      if (!id) { item.style.display = ''; return; }
      const cur = getStore()[id] || STATES[0];
      item.style.display = (value === '全部' || cur === value) ? '' : 'none';
    });
  }

  /* ===== 收藏夹功能 (v1.5) ===== */
  async function openFavoritesModal() {
      const overlay = document.createElement('div');
      overlay.id = 'fc2-favorites-modal-overlay';
      overlay.innerHTML = `
          <div id="fc2-favorites-modal">
              <div class="fc2-favorites-header">
                  <h2>已收藏项目</h2>
                  <div class="fc2-favorites-filters">
                      <button data-filter="全部" class="active">全部</button>
                      <button data-filter="已下载">已下载</button>
                      <button data-filter="无资源">无资源</button>
                  </div>
                  <button class="fc2-favorites-close">×</button>
              </div>
              <div class="fc2-favorites-content">
                  <div class="fc2-favorites-loading">加载中...</div>
                  <ul class="fc2-favorites-list"></ul>
              </div>
              <div class="fc2-favorites-pagination"></div>
          </div>
      `;
      document.body.appendChild(overlay);

      const modal = overlay.querySelector('#fc2-favorites-modal');
      modal.onclick = e => e.stopPropagation();
      overlay.onclick = () => overlay.remove();
      overlay.querySelector('.fc2-favorites-close').onclick = () => overlay.remove();

      let itemsPerPage = GM_getValue(ITEMS_PER_PAGE_KEY, 15);
      let currentPage = 1;
      let currentFilter = '全部';

      const renderPage = async (page) => {
          currentPage = page;
          const store = getStore();
          const allFavoriteIds = Object.keys(store).filter(id => store[id] === '已下载' || store[id] === '无资源');
          const filteredIds = allFavoriteIds.filter(id => currentFilter === '全部' || store[id] === currentFilter);

          const listEl = overlay.querySelector('.fc2-favorites-list');
          const loadingEl = overlay.querySelector('.fc2-favorites-loading');
          listEl.innerHTML = '';
          loadingEl.style.display = 'block';

          const start = (page - 1) * itemsPerPage;
          const end = start + itemsPerPage;
          const pageIds = filteredIds.slice(start, end);

          const itemsData = await Promise.all(pageIds.map(id => fetchArticleDetails(id)));

          loadingEl.style.display = 'none';
          itemsData.forEach(data => {
              if (!data) return;
              const li = document.createElement('li');
              li.className = 'fc2-favorite-item';
              li.innerHTML = `
                  <img src="${data.thumb}" alt="${data.title}" />
                  <div class="fc2-favorite-info">
                      <h3><a href="https://adult.contents.fc2.com/article/${data.id}/" target="_blank" rel="noopener noreferrer">${data.title}</a></h3>
                      <div class="meta-grid">
                          <p><b>ID:</b> FC2-PPV-${data.id}</p>
                          <p><b>作者:</b> ${data.author || 'N/A'}</p>
                          <p><b>❤️ 登录数:</b> ${data.favoriteCount || 'N/A'}</p>
                          <p><b>📅 上架时间:</b> ${data.uploadDate || 'N/A'}</p>
                          <p><b>状态:</b> ${store[data.id]}</p>
                      </div>
                  </div>
              `;
              listEl.appendChild(li);
          });
          renderPagination(filteredIds.length);
      };

      const renderPagination = (totalItems) => {
          const paginationEl = overlay.querySelector('.fc2-favorites-pagination');
          const totalPages = Math.ceil(totalItems / itemsPerPage);
          paginationEl.innerHTML = '';

          const perPageLabel = document.createElement('span');
          perPageLabel.textContent = '每页显示:';
          const perPageInput = document.createElement('input');
          perPageInput.type = 'number';
          perPageInput.min = 5;
          perPageInput.value = itemsPerPage;
          perPageInput.onchange = () => {
              itemsPerPage = parseInt(perPageInput.value, 10) || 15;
              GM_setValue(ITEMS_PER_PAGE_KEY, itemsPerPage);
              renderPage(1);
          };

          if (totalPages > 1) {
              const prevBtn = document.createElement('button');
              prevBtn.textContent = '上一页';
              prevBtn.disabled = currentPage === 1;
              prevBtn.onclick = () => renderPage(currentPage - 1);

              const pageSelect = document.createElement('select');
              for(let i = 1; i <= totalPages; i++) {
                  const option = document.createElement('option');
                  option.value = i;
                  option.textContent = `第 ${i} 页`;
                  if(i === currentPage) option.selected = true;
                  pageSelect.appendChild(option);
              }
              pageSelect.onchange = () => renderPage(parseInt(pageSelect.value, 10));

              const pageInfo = document.createElement('span');
              pageInfo.textContent = `/ ${totalPages} 页`;

              const nextBtn = document.createElement('button');
              nextBtn.textContent = '下一页';
              nextBtn.disabled = currentPage === totalPages;
              nextBtn.onclick = () => renderPage(currentPage + 1);

              paginationEl.append(perPageLabel, perPageInput, prevBtn, pageSelect, pageInfo, nextBtn);
          } else {
              paginationEl.append(perPageLabel, perPageInput);
          }
      };

      overlay.querySelectorAll('.fc2-favorites-filters button').forEach(btn => {
          btn.onclick = () => {
              overlay.querySelector('.fc2-favorites-filters button.active').classList.remove('active');
              btn.classList.add('active');
              currentFilter = btn.dataset.filter;
              renderPage(1);
          };
      });

      renderPage(1);
  }

  const articleCache = new Map();
  async function fetchArticleDetails(id) {
      if (articleCache.has(id)) return articleCache.get(id);
      try {
          const response = await fetch(`https://adult.contents.fc2.com/article/${id}/`);
          if (!response.ok) return null;
          const htmlText = await response.text();
          const doc = new DOMParser().parseFromString(htmlText, 'text/html');

          // 优先从 ld+json 获取信息,更稳定
          let title = `项目 ${id}`, thumb = '', author = 'N/A';
          try {
              const scriptTag = doc.querySelector('script[type="application/ld+json"]');
              if(scriptTag) {
                  const jsonData = JSON.parse(scriptTag.textContent);
                  title = jsonData.name;
                  thumb = jsonData.image?.url;
                  author = jsonData.brand?.name;
              }
          } catch(e) { console.error('解析ld+json失败', e); }

          // 从页面元素中补充信息
          let favoriteCount = 'N/A';
          doc.querySelectorAll('.items_article_headerInfo ul li').forEach(li => {
              if (li.textContent.includes('登录数')) {
                  favoriteCount = li.querySelector('b')?.textContent || 'N/A';
              }
          });

          let uploadDate = 'N/A';
          doc.querySelectorAll('.items_article_softDevice p').forEach(p => {
              if (p.textContent.includes('上架时间')) {
                  uploadDate = p.textContent.split(':')[1]?.trim() || 'N/A';
              }
          });

          const details = { id, title, thumb, author, uploadDate, favoriteCount };
          articleCache.set(id, details);
          return details;
      } catch (error) {
          console.error(`[状态脚本] 获取项目详情失败: ${id}`, error);
          return {id, title: `项目 ${id} (加载失败)`, thumb: ''};
      }
  }

  /* ===== 导入/导出/清空/重置等 ===== */
  function openImportModal() {
      const overlay = document.createElement('div');
      overlay.style.cssText='position:fixed;left:0;top:0;right:0;bottom:0;background:rgba(0,0,0,0.45);z-index:2147483647;display:flex;align-items:center;justify-content:center';
      overlay.innerHTML = `
          <div style="width:520px;max-width:90vw;background:#fff;border-radius:12px;padding:16px;box-shadow:0 18px 60px rgba(0,0,0,0.28);">
              <h3 style="margin:0 0 8px 0;">导入状态 (JSON)</h3>
              <textarea placeholder='{"4745474":"已下载"}' style="width:100%;height:180px;padding:10px;border-radius:8px;border:1px solid #ddd;font-family:monospace;font-size:13px;"></textarea>
              <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:10px;">
                  <button type="button" class="cancel">取消</button>
                  <button type="button" class="ok">导入</button>
              </div>
          </div>
      `;
      document.body.appendChild(overlay);
      const ta = overlay.querySelector('textarea');
      overlay.querySelector('.cancel').onclick = () => overlay.remove();
      overlay.querySelector('.ok').onclick = () => {
          try {
              doImport(JSON.parse(ta.value.trim()));
              overlay.remove();
          } catch(e){ alert('无效 JSON'); }
      };
      overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
  }

  function doImport(jsonObj) {
      if (typeof jsonObj !== 'object' || jsonObj === null) return;
      const store = getStore();
      let changed = 0;
      for (const k in jsonObj) {
          if (STATES.includes(jsonObj[k])) {
              store[String(k)] = jsonObj[k];
              changed++;
          }
      }
      saveStore(store);
      renderExistingItems();
      renderDetailPage();
  }

  function exportStore() {
    const map = getStore();
    const text = JSON.stringify(map, null, 2);
    const blob = new Blob([text], { type: 'application/json' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `fc2_status_${Date.now()}.json`;
    a.click();
    URL.revokeObjectURL(a.href);
  }

  function resetItemVisual(item) {
    item.classList.remove('fc2-s-downloaded', 'fc2-s-noresource');
    item.querySelectorAll('.fc2-noresource-ribbon').forEach(el => el.remove());
    const id = extractIdFromItem(item);
    if (!id) return;
    const cont = item.querySelector(`.fc2-status[data-id="${id}"]`);
    if (cont) {
      const btn = cont.querySelector('.fc2-status-btn');
      const label = cont.querySelector('.fc2-status-label');
      if (label) label.textContent = STATES[0];
      if (btn) btn.style.background = `linear-gradient(180deg, ${COLORS[STATES[0]]}, ${shadeColor(COLORS[STATES[0]], -10)})`;
    }
  }

  function shadeColor(hex, percent) {
    hex = hex.replace('#','');
    const num = parseInt(hex,16);
    let r = (num >> 16) + Math.round(255 * (percent/100));
    let g = ((num >> 8) & 0x00FF) + Math.round(255 * (percent/100));
    let b = (num & 0x0000FF) + Math.round(255 * (percent/100));
    r=Math.min(255,Math.max(0,r)); g=Math.min(255,Math.max(0,g)); b=Math.min(255,Math.max(0,b));
    return '#'+(0x1000000 + (r<<16) + (g<<8) + b).toString(16).slice(1);
  }

  /* ===== 启动初始化 ===== */
  function init() {
    buildPanel();
    setTimeout(() => {
        renderExistingItems();
        renderDetailPage();
    }, 500);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

})();