Scholarscope Lite

PubMed / Google Scholar 期刊 IF/分区徽章、筛选排序、Abstract 预览、iCite 引用数;参考 Scholarscope 扩展 UI。

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Scholarscope Lite
// @namespace    https://github.com/flashlab/JCR-IF-CF-API
// @version      0.2.0
// @description  PubMed / Google Scholar 期刊 IF/分区徽章、筛选排序、Abstract 预览、iCite 引用数;参考 Scholarscope 扩展 UI。
// @author       flashlab<[email protected]>
// @homepageURL  https://github.com/flashlab/JCR-IF-CF-API
// @license      GPL-3.0
// @match        https://pubmed.ncbi.nlm.nih.gov/*
// @match        https://scholar.google.com/scholar*
// @run-at       document-idle
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @connect      jcr-query-api.4cf.workers.dev
// @connect      icite.od.nih.gov
// @connect      eutils.ncbi.nlm.nih.gov
// @connect      scholar.google.com
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js
// ==/UserScript==

/* global DOMPurify */

(function () {
  "use strict";

  // =========================================================================
  // Config
  // =========================================================================
  const DEFAULTS = {
    quartileSource: "jcr",
    showCitation: true,
    autoFilter: false,
    autoSort: false,
    sortingMethod: 1,
    filter: { minIF: 0, maxIF: 2000, q1: 1, q2: 1, q3: 1, q4: 1 },
    pubmedApiKey: "",
  };
  const CFG = Object.assign({}, DEFAULTS, GM_getValue("cfg", {}));
  CFG.filter = Object.assign({}, DEFAULTS.filter, CFG.filter || {});
  const saveCfg = () => GM_setValue("cfg", CFG);

  function isScholarPage() {
    return (
      location.hostname === "scholar.google.com" &&
      location.pathname.startsWith("/scholar")
    );
  }

  const JCR_CACHE_TTL_MS = 7 * 24 * 3600 * 1000;
  const JCR_ENDPOINT = "https://jcr-query-api.4cf.workers.dev/api/jcr";
  const ICITE_ENDPOINT = "https://icite.od.nih.gov/api/pubs";
  // eutils endpoints accept api_key (CFG.pubmedApiKey); pubmed/scholar website bases do not.
  const EFETCH_ENDPOINT =
    "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi";
  const ESEARCH_ENDPOINT =
    "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi";
  const PUBMED_BASE = "https://pubmed.ncbi.nlm.nih.gov/";
  const SCHOLAR_BASE = "https://scholar.google.com/scholar";
  const JCR_DEFAULT_PARAMS = isScholarPage()
    ? null
    : { is_abbr: "1", is_med: "1" };

  // =========================================================================
  // Utilities
  // =========================================================================
  function el(tag, attrs, children) {
    const n = document.createElement(tag);
    if (attrs) {
      for (const k in attrs) {
        if (k === "class") n.className = attrs[k];
        else if (k === "style") n.setAttribute("style", attrs[k]);
        else if (k === "dataset") Object.assign(n.dataset, attrs[k]);
        else if (k.startsWith("on") && typeof attrs[k] === "function")
          n.addEventListener(k.slice(2), attrs[k]);
        else if (k === "checked" || k === "disabled") attrs[k] && (n[k] = true);
        else n.setAttribute(k, attrs[k]);
      }
    }
    if (children) {
      (Array.isArray(children) ? children : [children]).forEach((c) => {
        if (c == null) return;
        n.appendChild(typeof c === "string" ? document.createTextNode(c) : c);
      });
    }
    return n;
  }

  function factorColor(f) {
    if (f === "" || f == null || isNaN(parseFloat(f))) return "#616161";
    const v = parseFloat(f);
    if (v >= 20) return "#D50000";
    if (v >= 10) return "#F4511E";
    if (v >= 3) return "#F6BF26";
    if (v >= 0) return "#33B679";
    return "#616161";
  }

  function quartileColor(q) {
    if (!q) return "#616161";
    const s = String(q).replace(/\s+/g, "").toUpperCase();
    if (s === "Q1" || s === "1区") return "#D50000";
    if (s === "Q2" || s === "2区") return "#F4511E";
    if (s === "Q3" || s === "3区") return "#F6BF26";
    if (s === "Q4" || s === "4区") return "#33B679";
    return "#616161";
  }

  function normalizeFenqu(v) {
    if (!v) return "";
    return String(v).replace(/\s+/g, "");
  }

  function pickDisplayQuartile(row) {
    if (!row) return "";
    if (CFG.quartileSource === "cas") return normalizeFenqu(row.fenqu);
    return row.jif_quartile || "";
  }

  function gmFetch(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url,
        responseType: "json",
        timeout: 15000,
        onload: (r) => {
          if (r.status >= 200 && r.status < 300)
            resolve(r.response || JSON.parse(r.responseText || "null"));
          else reject(new Error("HTTP " + r.status));
        },
        onerror: (e) => reject(e),
        ontimeout: () => reject(new Error("timeout")),
      });
    });
  }

  function gmFetchText(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url,
        timeout: 20000,
        onload: (r) =>
          r.status >= 200 && r.status < 300
            ? resolve(r.responseText)
            : reject(new Error("HTTP " + r.status)),
        onerror: reject,
        ontimeout: () => reject(new Error("timeout")),
      });
    });
  }

  function makeQueue(concurrency) {
    let active = 0;
    const q = [];
    const drain = () => {
      while (active < concurrency && q.length) {
        const { task, resolve, reject } = q.shift();
        active++;
        task()
          .then(resolve, reject)
          .finally(() => {
            active--;
            drain();
          });
      }
    };
    return (task) =>
      new Promise((resolve, reject) => {
        q.push({ task, resolve, reject });
        drain();
      });
  }
  const jcrQueue = makeQueue(4);
  const efetchQueue = makeQueue(2);
  const scholarQueue = makeQueue(2);

  // =========================================================================
  // Cache
  // =========================================================================
  function readJcrCache() {
    return GM_getValue("jcrCache", {});
  }
  function writeJcrCache(c) {
    GM_setValue("jcrCache", c);
  }
  function jcrCacheGet(key) {
    const c = readJcrCache();
    const hit = c[key];
    if (!hit) return null;
    if (Date.now() - hit.ts > JCR_CACHE_TTL_MS) return null;
    return { data: hit.data || [], medHit: !!hit.medHit };
  }
  function jcrCacheSet(key, payload) {
    const c = readJcrCache();
    c[key] = {
      data: payload.data || [],
      medHit: !!payload.medHit,
      ts: Date.now(),
    };
    writeJcrCache(c);
  }

  // =========================================================================
  // APIs
  // =========================================================================
  function buildJcrUrl(q, extraParams) {
    const u = new URL(JCR_ENDPOINT);
    u.searchParams.set("q", q);
    const params = Object.assign({}, JCR_DEFAULT_PARAMS);
    if (extraParams) {
      for (const k in extraParams) {
        const v = extraParams[k];
        if (v == null || v === "") delete params[k];
        else params[k] = String(v);
      }
    }
    for (const k in params) u.searchParams.set(k, params[k]);
    return u.toString();
  }

  async function jcrLookup(keyword, opts) {
    opts = opts || {};
    const norm = String(keyword || "").toLowerCase();
    if (!norm) return { data: [], errored: false, medHit: false };
    const cacheKey = norm;
    if (!opts.skipCache) {
      const cached = jcrCacheGet(cacheKey);
      if (cached)
        return {
          data: cached.data,
          medHit: cached.medHit,
          errored: false,
          cached: true,
        };
    }
    return jcrQueue(async () => {
      try {
        const url = buildJcrUrl(keyword, opts.extraParams);
        const resp = await gmFetch(url);
        const data = resp && Array.isArray(resp.data) ? resp.data : [];
        const medHit = !!(resp && resp.med_hit);
        if (!opts.skipCache && (data.length > 0 || medHit)) {
          jcrCacheSet(cacheKey, { data, medHit });
        }
        return { data, errored: false, medHit };
      } catch (e) {
        return { data: [], errored: true, medHit: false, error: e.message };
      }
    });
  }

  async function iciteBatch(pmids) {
    if (!pmids || !pmids.length) return {};
    const url = `${ICITE_ENDPOINT}?pmids=${pmids.join(",")}`;
    try {
      const resp = await gmFetch(url);
      const out = {};
      ((resp && resp.data) || []).forEach((r) => {
        out[String(r.pmid)] = r;
      });
      return out;
    } catch (e) {
      return {};
    }
  }

  async function efetchAbstractXml(pmid) {
    const u = new URL(EFETCH_ENDPOINT);
    u.searchParams.set("db", "pubmed");
    u.searchParams.set("rettype", "xml");
    u.searchParams.set("id", pmid);
    if (CFG.pubmedApiKey) u.searchParams.set("api_key", CFG.pubmedApiKey);
    const xmlText = await efetchQueue(() => gmFetchText(u.toString()));
    const doc = new DOMParser().parseFromString(xmlText, "text/xml");
    const tags = doc.getElementsByTagName("AbstractText");
    let abstractHtml = "";
    for (let i = 0; i < tags.length; i++) {
      const label = tags[i].getAttribute("Label");
      const inner = tags[i].innerHTML;
      abstractHtml += label
        ? `<p><b>${label}: </b>${inner}</p>`
        : `<p>${inner}</p>`;
    }
    if (!abstractHtml) abstractHtml = "<b>No abstract.</b>";
    const titleNode = doc.getElementsByTagName("ArticleTitle")[0];
    const journalNode = doc.getElementsByTagName("Journal")[0];
    const journalTitleNode =
      journalNode && journalNode.getElementsByTagName("Title")[0];
    const idsNodes = doc.getElementsByTagName("ELocationID");
    const doiNode =
      idsNodes &&
      Array.from(idsNodes || []).find(
        (node) => node.getAttribute("EIdType") === "doi",
      );
    return {
      abstractHtml,
      articleTitleHtml: titleNode ? titleNode.innerHTML : "",
      journalTitleHtml: journalTitleNode ? journalTitleNode.innerHTML : "",
      doiHtml: doiNode ? doiNode.innerHTML : "",
    };
  }

  // =========================================================================
  // Modal
  // =========================================================================
  function ensureModalRoot() {
    let root = document.getElementById("Scholarscope_Modal");
    if (root) return root;
    root = el("div", { id: "Scholarscope_Modal", style: "display:none" });
    const mask = el("div", {
      class: "Scholarscope_Modal_Mask",
      onclick: hideModal,
    });
    const box = el("div", { class: "Scholarscope_Modal_Box" });
    root.appendChild(mask);
    root.appendChild(box);
    document.body.appendChild(root);
    document.addEventListener("keydown", (e) => {
      if (e.key === "Escape") hideModal();
    });
    return root;
  }
  function showModal(content) {
    const root = ensureModalRoot();
    const box = root.querySelector(".Scholarscope_Modal_Box");
    box.innerHTML = "";
    if (typeof content === "string")
      box.appendChild(
        el("div", { class: "Scholarscope_Modal_Content" }, content),
      );
    else box.appendChild(content);
    root.style.display = "block";
  }
  function hideModal() {
    const root = document.getElementById("Scholarscope_Modal");
    if (root) root.style.display = "none";
  }

  function jcrResultsModal(rows, highlightIdx, options) {
    const baseHeaders = [
      "name",
      "abbr",
      "jif_2024",
      "jif_quartile",
      "fenqu",
      "is_top",
    ];
    const headerSet = new Set(baseHeaders);
    (rows || []).forEach((row) => {
      if (!row) return;
      Object.keys(row).forEach((key) => {
        if (row[key] != null) headerSet.add(key);
      });
    });
    const headers = Array.from(headerSet);
    const tbl = el("table", { class: "Scholarscope_Modal_Table" });
    const thead = el(
      "thead",
      {},
      el(
        "tr",
        {},
        headers.map((h) => el("th", {}, h)),
      ),
    );
    tbl.appendChild(thead);
    const tbody = el("tbody");
    (rows || []).forEach((r, i) => {
      const tr = el(
        "tr",
        i === highlightIdx
          ? { class: "Scholarscope_Modal_RowHighlight" }
          : null,
      );
      headers.forEach((h) =>
        tr.appendChild(el("td", {}, r && r[h] != null ? String(r[h]) : "—")),
      );
      tbody.appendChild(tr);
    });
    tbl.appendChild(tbody);
    const wrap = el("div", { class: "Scholarscope_Modal_Content" });
    wrap.appendChild(
      el(
        "h3",
        {},
        `JCR lookup (${rows.length} result${rows.length === 1 ? "" : "s"})`,
      ),
    );
    wrap.appendChild(tbl);

    const buttonRow = el("div", { class: "Scholarscope_Modal_ButtonRow" });
    const factorEl = options && options.factorEl;
    const origKw = factorEl && factorEl.dataset && factorEl.dataset.origKw;
    if (factorEl && origKw) {
      const clearBtn = el(
        "div",
        { class: "Scholarscope_Modal_ClearCache" },
        "清除缓存",
      );
      clearBtn.addEventListener("click", () => {
        const cache = readJcrCache();
        delete cache[String(origKw).trim().toLowerCase()];
        writeJcrCache(cache);
        applyJcrToBadges(
          factorEl,
          getQuartileBadgeForFactor(factorEl),
          {},
          origKw,
        );
        hideModal();
      });
      buttonRow.appendChild(clearBtn);
    }
    buttonRow.appendChild(
      el(
        "div",
        { class: "Scholarscope_Modal_Close", onclick: hideModal },
        "关闭",
      ),
    );
    wrap.appendChild(buttonRow);
    return wrap;
  }

  function getQuartileBadgeForFactor(factorEl) {
    return (
      factorEl.parentElement &&
      factorEl.parentElement.querySelector(
        factorEl.classList.contains("Scholarscope_Appendix_Factor")
          ? ".Scholarscope_Appendix_Quartile"
          : ".Scholarscope_Quartile",
      )
    );
  }

  function getMetaContent(name) {
    const meta = document.querySelector(`meta[name="${name}"]`);
    return meta && meta.content ? meta.content.trim() : "";
  }

  function getDetailJournalTitle() {
    return getMetaContent("citation_journal_title");
  }

  function getManualLookupDefaultKeyword(factorEl) {
    if (factorEl && factorEl.classList.contains("Scholarscope_Factor")) {
      const title = getDetailJournalTitle();
      if (title) return title;
    }
    return factorEl && factorEl.dataset && factorEl.dataset.origKw
      ? factorEl.dataset.origKw.trim()
      : "";
  }

  function buildManualLookupSelect(id, options) {
    const select = el("select", {
      id,
      class: "Scholarscope_DropDownControl",
    });
    options.forEach((option) => {
      select.appendChild(el("option", { value: option.value }, option.label));
    });
    return select;
  }

  function serializeManualLookupForm(form) {
    const data = new FormData(form);
    return Object.fromEntries(
      Array.from(data.entries()), //.filter(([, value]) => String(value).trim() !== '')
    );
  }

  function buildManualLookupField(labelText, control) {
    return el("label", { class: "Scholarscope_DropDownField" }, [
      el("div", { class: "Scholarscope_DropDownLabel" }, labelText),
      control,
    ]);
  }

  function ensureManualLookupDropdown() {
    let dd = document.getElementById("Scholarscope_ManualLookupDropDown");
    if (dd) return dd;

    const keywordInput = el("input", {
      id: "Scholarscope_ManualLookupKeyword",
      name: "q",
      class: "Scholarscope_DropDownControl",
      type: "text",
      required: "required",
      minlength: "3",
      placeholder: "全称/缩写/ISSN",
    });
    const searchMode = buildManualLookupSelect(
      "Scholarscope_ManualLookupSearchMode",
      [
        { value: "", label: "精确" },
        { value: "1", label: "前缀" },
        { value: "2", label: "模糊" },
        { value: "3", label: "后缀" },
      ],
    );
    searchMode.name = "f";
    const isAbbr = buildManualLookupSelect("Scholarscope_ManualLookupIsAbbr", [
      { value: "", label: "不限" },
      { value: "1", label: "是" },
      { value: "0", label: "否" },
    ]);
    isAbbr.name = "is_abbr";
    const pageNo = buildManualLookupSelect("Scholarscope_ManualLookupPageNo", [
      { value: "", label: "1" },
      { value: "2", label: "2" },
      { value: "3", label: "3" },
      { value: "4", label: "4" },
      { value: "5", label: "5" },
    ]);
    pageNo.name = "page";
    const submit = el(
      "button",
      {
        type: "submit",
        class: "Scholarscope_DropDownButton Scholarscope_DropDownButtonPrimary",
      },
      "查询",
    );
    const reset = el(
      "button",
      {
        type: "reset",
        class: "Scholarscope_DropDownButton Scholarscope_DropDownButtonDanger",
      },
      "重置",
    );

    dd = el(
      "form",
      {
        id: "Scholarscope_ManualLookupDropDown",
        class: `Scholarscope_DropDown ${isScholarPage() ? "gs_query " : ""}notranslate`,
        style: "display:none",
        novalidate: "novalidate",
      },
      [
        el("div", { class: "Scholarscope_DropDownTitle" }, "JCR 手动查询"),
        keywordInput,
        el("div", { class: "Scholarscope_TermWrapper" }, [
          buildManualLookupField("搜索模式", searchMode),
          buildManualLookupField("缩写", isAbbr),
          buildManualLookupField("页数", pageNo),
        ]),
        el(
          "div",
          { class: "Scholarscope_TermWrapper Scholarscope_InlineCheck" },
          [
            el("div", {}, [
              el("input", {
                name: "is_med",
                id: "is_med",
                type: "checkbox",
                value: "0",
              }),
              el("label", { for: "is_med" }, "优先 JCR 数据库"),
            ]),
            el("div", {}, [
              el("input", {
                name: "show_all",
                id: "show_all",
                type: "checkbox",
                value: "1",
              }),
              el("label", { for: "show_all" }, "全部字段"),
            ]),
          ],
        ),
        el("div", { class: "Scholarscope_DropDownHint" }, [
          "参数详情查看",
          el(
            "a",
            {
              href: "https://github.com/flashlab/JCR-IF-CF-API",
              target: "_blank",
              rel: "noopener",
            },
            "flashlab/JCR-IF-CF-API",
          ),
        ]),
        el("div", { class: "Scholarscope_ButtonRow" }, [submit, reset]),
      ],
    );

    dd.addEventListener("click", (ev) => ev.stopPropagation());
    dd.addEventListener("submit", submitManualLookupForm);
    // close.addEventListener('click', closeManualLookupDropdown);

    document.body.appendChild(dd);
    document.addEventListener("click", (ev) => {
      const root = document.getElementById("Scholarscope_ManualLookupDropDown");
      if (root && root.style.display === "block" && !root.contains(ev.target))
        closeManualLookupDropdown();
    });
    document.addEventListener("keydown", (ev) => {
      if (ev.key === "Escape") closeManualLookupDropdown();
    });
    return dd;
  }

  function positionManualLookupDropdown(dd, factorEl) {
    const rect = factorEl.getBoundingClientRect();
    const width = 340;
    const gap = 8;
    const maxLeft = window.scrollX + window.innerWidth - width - gap;
    const left = Math.max(
      window.scrollX + gap,
      Math.min(window.scrollX + rect.left, maxLeft),
    );
    dd.style.top = `${window.scrollY + rect.bottom + gap}px`;
    dd.style.left = `${left}px`;
  }

  function openManualLookupDropdown(factorEl) {
    const dd = ensureManualLookupDropdown();
    dd._factorEl = factorEl;
    dd.querySelector(".Scholarscope_DropDownTitle").textContent =
      factorEl.classList.contains("Scholarscope_MedHit")
        ? "JCR 手动查询(MedLine 已收录)"
        : "JCR 手动查询";
    const keywordInput = dd.querySelector("#Scholarscope_ManualLookupKeyword");
    keywordInput.value = keywordInput.defaultValue =
      getManualLookupDefaultKeyword(factorEl);
    positionManualLookupDropdown(dd, factorEl);
    dd.style.display = "block";
    keywordInput.focus();
    keywordInput.select();
  }

  function closeManualLookupDropdown() {
    const dd = document.getElementById("Scholarscope_ManualLookupDropDown");
    if (!dd) return;
    // dd.reset();
    dd.style.display = "none";
    dd._factorEl = null;
    dd.dataset.busy = "0";
    const submit = dd.querySelector(".Scholarscope_DropDownButtonPrimary");
    if (submit) {
      submit.textContent = "查询";
      submit.style.pointerEvents = "";
    }
  }

  async function submitManualLookupForm(ev) {
    ev.preventDefault();
    const dd = ensureManualLookupDropdown();
    if (dd.dataset.busy === "1") return;
    const factorEl = dd._factorEl;
    if (!factorEl) return;

    if (!dd.reportValidity()) {
      return;
    }

    const { q: rawKeyword = "", ...extraParams } =
      serializeManualLookupForm(dd);
    const keyword = String(rawKeyword).trim();
    const submit = dd.querySelector(".Scholarscope_DropDownButtonPrimary");
    dd.dataset.busy = "1";
    submit.textContent = "查询中…";
    submit.style.pointerEvents = "none";

    const result = await jcrLookup(keyword, { skipCache: true, extraParams });
    const quartileEl = getQuartileBadgeForFactor(factorEl);
    const origKw = factorEl.dataset.origKw || "";
    const origNorm = String(origKw).trim().toLowerCase();
    closeManualLookupDropdown();

    if (!result.errored && result.data && result.data.length > 0) {
      if (origNorm)
        jcrCacheSet(origNorm, { data: result.data, medHit: !!result.medHit });
      applyJcrToBadges(factorEl, quartileEl, result, origKw || keyword);
      if (isSearchResultsPage()) {
        if (CFG.autoFilter) applyFilter(true);
        if (CFG.autoSort) applySorting();
      }
      return;
    }
    if (!result.errored && result.medHit) {
      if (origNorm) jcrCacheSet(origNorm, { data: [], medHit: true });
      applyJcrToBadges(factorEl, quartileEl, result, origKw || keyword);
      window.alert("已被 MedLine 收录,无 IF/分区数据。");
      return;
    }
    if (result.errored) {
      window.alert("查询失败:" + (result.error || ""));
      return;
    }
    window.alert("未找到,请重试并调整关键词或搜索参数。");
  }

  // =========================================================================
  // Badge factories
  // =========================================================================
  const STATE_CLASSES = [
    "Scholarscope_NotFound",
    "Scholarscope_MedHit",
    "Scholarscope_IF_Ambiguous",
  ];
  function setBadge(el, text, color) {
    if (!el) return;
    el.textContent = text;
    el.style.backgroundColor = color;
  }
  function applyJcrToBadges(factorEl, quartileEl, result, journalKw) {
    result = result || {};
    const rows = result.data || [];
    const primary = rows[0];
    factorEl.classList.remove(...STATE_CLASSES);
    if (result.errored) {
      setBadge(factorEl, "N/A", "#616161");
      factorEl.dataset.jcrAll = "[]";
      setBadge(quartileEl, "N/A", "#616161");
      return;
    }
    if (result.medHit && (!rows || rows.length === 0)) {
      setBadge(factorEl, "No IF", "#0094DF");
      factorEl.classList.add("Scholarscope_MedHit");
      factorEl.dataset.origKw = journalKw || "";
      factorEl.dataset.jcrAll = "[]";
      factorEl.title =
        "已被 NLM/MedLine 收录,但 JCR/中科院分区表无此刊;点击可手动重试关键词";
      setBadge(quartileEl, "—", "#616161");
      return;
    }
    if (!rows || rows.length === 0) {
      setBadge(factorEl, "Not Found", "#616161");
      factorEl.classList.add("Scholarscope_NotFound");
      factorEl.dataset.origKw = journalKw || "";
      factorEl.dataset.jcrAll = "[]";
      setBadge(quartileEl, "N/A", "#616161");
      return;
    }
    const ifVal = primary.jif_2024;
    setBadge(
      factorEl,
      ifVal == null || ifVal === "" ? "N/A" : String(ifVal),
      factorColor(ifVal),
    );
    factorEl.dataset.jcrAll = JSON.stringify(rows);
    if (journalKw && !factorEl.dataset.origKw)
      factorEl.dataset.origKw = journalKw;
    if (rows.length >= 2) {
      factorEl.classList.add("Scholarscope_IF_Ambiguous");
      factorEl.title = "多个候选,点击查看";
    } else {
      factorEl.title = "点击查看 JCR 详情";
    }

    const q = pickDisplayQuartile(primary);
    setBadge(quartileEl, q || "N/A", q ? quartileColor(q) : "#616161");
  }

  function attachFactorClickHandler(factorEl) {
    factorEl.addEventListener("click", (ev) => {
      ev.preventDefault();
      ev.stopPropagation();
      if (
        factorEl.classList.contains("Scholarscope_NotFound") ||
        factorEl.classList.contains("Scholarscope_MedHit")
      ) {
        openManualLookupDropdown(factorEl);
        return;
      }
      let rows = [];
      try {
        rows = JSON.parse(factorEl.dataset.jcrAll || "[]");
      } catch (e) {
        rows = [];
      }
      if (rows.length !== 0) showModal(jcrResultsModal(rows, 0, { factorEl }));
    });
  }

  function makeBadge(cls, clickable) {
    const attrs = clickable
      ? {
          class: `${cls} Scholarscope_Badge Scholarscope_BadgeClickable notranslate`,
          title: "点击查看 JCR 详情",
        }
      : { class: `${cls} Scholarscope_Badge notranslate` };
    const e = el("div", attrs);
    e.textContent = "…";
    if (clickable) attachFactorClickHandler(e);
    return e;
  }
  const makeFactorBadge = (cls) => makeBadge(cls, true);
  const makeQuartileBadge = (cls) => makeBadge(cls, false);

  // =========================================================================
  // Detail page
  // =========================================================================
  function getDetailJournalKeyword() {
    return getMetaContent("citation_publisher") || getDetailJournalTitle();
  }

  function getDetailPmid() {
    const m = location.pathname.match(/\/(\d+)\/?$/);
    return m ? m[1] : "";
  }

  async function renderDetailHeader() {
    const heading = document.getElementById("full-view-heading");
    if (!heading) return;
    const articleCitation = heading.querySelector(".article-citation");
    if (
      !articleCitation ||
      articleCitation.querySelector("#Scholarscope_JournalDetailFrame")
    )
      return;

    const frame = el("div", {
      id: "Scholarscope_JournalDetailFrame",
      class: "Scholarscope_JournalDetailFrame notranslate",
    });

    const pubTypeNode = heading.querySelector(".publication-type");
    if (pubTypeNode) {
      const at = el("div", {
        class:
          "Scholarscope_ArticleType Scholarscope_Badge Scholarscope_BadgeBlue",
      });
      at.textContent = pubTypeNode.textContent.trim();
      pubTypeNode.remove();
      frame.appendChild(at);
    }

    const factor = makeFactorBadge("Scholarscope_Factor");
    const quartile = makeQuartileBadge("Scholarscope_Quartile");
    frame.appendChild(factor);
    frame.appendChild(quartile);
    articleCitation.insertBefore(frame, articleCitation.firstChild);

    const source = articleCitation.querySelector(".article-source");
    if (source) {
      source.style.backgroundColor = "transparent";
      source.style.padding = "4px 0";
    }

    const kw = getDetailJournalKeyword();
    if (kw) {
      const result = await jcrLookup(kw);
      applyJcrToBadges(factor, quartile, result, kw);
    } else {
      factor.textContent = "N/A";
      quartile.textContent = "N/A";
    }
  }

  async function renderDetailTimesCited() {
    if (!CFG.showCitation) return;
    const ids = document.getElementById("full-view-identifiers");
    if (!ids || document.getElementById("Scholarscope_TimesCited")) return;
    const pmid = getDetailPmid();
    if (!pmid) return;
    const doi = getMetaContent("citation_doi");
    const title = (
      document.querySelector(".heading-title").textContent || ""
    ).trim();
    const res = await iciteBatch([pmid]);
    const info = res[pmid];
    const n =
      info && typeof info.citation_count === "number"
        ? info.citation_count
        : null;
    const wrap = el("ul", { class: "identifiers" });
    const href =
      n > 0
        ? `${PUBMED_BASE}?linkname=pubmed_pubmed_citedin&from_uid=${pmid}`
        : "#";
    const liCite = el(
      "li",
      { id: "Scholarscope_TimesCited" },
      el(
        "a",
        n > 0
          ? {
              href,
              target: "_blank",
              rel: "noopener",
              class: "Scholarscope_Action_Cited",
            }
          : { href: "#", class: "Scholarscope_Action_Cited" },
        `Cited: ${n}`,
      ),
    );
    const liGoogle = el(
      "li",
      null,
      el(
        "a",
        {
          href: `${SCHOLAR_BASE}?q=${encodeURIComponent(doi || title)}`,
          class: "Scholarscope_Action_GScholar",
          target: "_blank",
          rel: "noopener",
        },
        `Google Scholar`,
      ),
    );
    wrap.append(liCite, liGoogle);
    ids.parentNode.insertBefore(wrap, ids.nextSibling);
  }

  // =========================================================================
  // Appendix badge group (search page + detail page similar/cited-by lists)
  // =========================================================================
  function extractDocsumJournalYear(docsum) {
    const c = docsum.querySelector(
      ".docsum-journal-citation, .short-journal-citation",
    );
    if (!c) return { journal: "", year: "" };
    const raw = c.textContent || "";
    const firstDot = raw.indexOf(".");
    const journal = (firstDot > 0 ? raw.slice(0, firstDot) : raw).trim();
    const m = raw.match(/\b(19|20)\d{2}\b/);
    return { journal, year: m ? m[0] : "" };
  }
  function extractDocsumArticleType(docsum) {
    const c = docsum.querySelector(".publication-type");
    if (!c) return "";
    return (c.textContent || "").trim().replace(/\.+$/, "");
  }
  function extractDocsumPmid(docsum, pmidEl) {
    if (pmidEl && pmidEl.textContent) return pmidEl.textContent.trim();
    const dataId =
      docsum.getAttribute && docsum.getAttribute("data-article-id");
    if (dataId) return dataId;
    const link = docsum.querySelector("a.docsum-title");
    if (link) {
      const m = (link.getAttribute("href") || "").match(/\/(\d+)\/?/);
      if (m) return m[1];
    }
    return "";
  }

  function extractDocsumDoi(docsum) {
    const c = docsum.querySelector(".full-journal-citation");
    if (!c) return "";
    const text = c.textContent || "";
    const m = text.match(/doi:\s*(10\.\d{4,5}\/[^\s]+)/i);
    if (!m) return "";
    return m[1].replace(/[.;,]+$/, "");
  }

  function injectAppendixFrame(docsum) {
    if (docsum.querySelector(".Scholarscope_Appendix_JournalFrame"))
      return null;
    const content = docsum.querySelector(".docsum-content");
    const lcc = docsum.querySelector(".docsum-citation") || content;
    if (!lcc) return null;

    const { journal: journalKw, year } = extractDocsumJournalYear(docsum);
    const pmidEl = docsum.querySelector(".docsum-pmid");
    const pmid = extractDocsumPmid(docsum, pmidEl);
    const articleType = extractDocsumArticleType(docsum);
    const doi = extractDocsumDoi(docsum);
    const titleNode = docsum.querySelector("a.docsum-title");
    const articleTitle = ((titleNode && titleNode.textContent) || "").trim();

    const frame = el("div", {
      class: "Scholarscope_Appendix_JournalFrame notranslate",
    });
    frame.dataset.pmid = pmid;

    const journalDiv = el("div", {
      class: "Scholarscope_Appendix_Journal Scholarscope_Badge notranslate",
    });
    journalDiv.textContent = journalKw || "—";
    frame.appendChild(journalDiv);

    const factor = makeFactorBadge("Scholarscope_Appendix_Factor");
    frame.appendChild(factor);
    const quartile = makeQuartileBadge("Scholarscope_Appendix_Quartile");
    frame.appendChild(quartile);

    if (year) {
      const yd = el("div", {
        class:
          "Scholarscope_Appendix_Year Scholarscope_Badge Scholarscope_BadgeBlue",
      });
      yd.textContent = year;
      frame.appendChild(yd);
    }

    if (articleType) {
      const at = el("div", {
        class:
          "Scholarscope_Appendix_ArticleType Scholarscope_Badge Scholarscope_BadgeBlue notranslate",
      });
      at.textContent = articleType;
      frame.appendChild(at);
    }

    if (doi && pmidEl && pmidEl.parentNode) {
      const doiSpan = el("span", { class: "Scholarscope_DOI notranslate" });
      doiSpan.appendChild(document.createTextNode(" doi: "));
      doiSpan.appendChild(
        el(
          "a",
          {
            class: "Scholarscope_DOILink",
            href: "https://doi.org/" + doi,
            target: "_blank",
            rel: "noopener",
          },
          doi,
        ),
      );
      pmidEl.parentNode.insertBefore(doiSpan, pmidEl.nextSibling);
    }

    const pubType = docsum.querySelector(".publication-type");
    if (pubType) pubType.remove();
    const fullJournal = docsum.querySelector(".full-journal-citation");
    if (fullJournal) fullJournal.remove();

    lcc.insertBefore(frame, lcc.firstChild);

    if (content && pmid)
      injectActionRow(docsum, content, pmid, articleTitle, doi);

    return { docsum, frame, factor, quartile, journalKw, pmid };
  }

  function injectActionRow(docsum, content, pmid, articleTitle, doi) {
    if (docsum.querySelector(".Scholarscope_ActionRow")) return;
    const row = el("div", {
      class: "docsum-citation full-citation Scholarscope_ActionRow notranslate",
    });

    const citedHref = `${PUBMED_BASE}?linkname=pubmed_pubmed_citedin&from_uid=${pmid}`;
    const citedLink = el(
      "a",
      {
        class: "citation-part Scholarscope_Action_Cited",
        href: citedHref,
        target: "_blank",
        rel: "noopener",
      },
      CFG.showCitation ? "Cited: …" : "Cited: —",
    );
    row.appendChild(citedLink);

    const gsHref =
      `${SCHOLAR_BASE}?q=` + encodeURIComponent(doi || articleTitle || pmid);
    const gsLink = el(
      "a",
      {
        class:
          "spaced-citation-item citation-part Scholarscope_Action_GScholar",
        href: gsHref,
        target: "_blank",
        rel: "noopener",
      },
      "Google Scholar",
    );
    row.appendChild(gsLink);

    const absBtn = el(
      "div",
      {
        class:
          "spaced-citation-item citation-part Scholarscope_Action_Abstract",
        title: "点击展开/收起摘要",
      },
      "Full Abstract",
    );
    absBtn.addEventListener("click", (ev) => {
      ev.preventDefault();
      ev.stopPropagation();
      toggleAbstract(docsum, pmid);
    });
    row.appendChild(absBtn);

    content.appendChild(row);
  }

  async function fillAppendixJcr(entry) {
    if (!entry?.journalKw) {
      entry.factor.textContent = "N/A";
      entry.quartile.textContent = "N/A";
      return;
    }
    const opts = entry.truncated
      ? { extraParams: { f: entry.truncated } }
      : undefined;
    // Keep '…' in the keyword: it differentiates the cache key per truncation mode
    // and preserves the original Scholar string in dataset.origKw. The JCR API
    // strips chars outside [A-Z0-9 &'()+,\-./:] server-side anyway.
    const result = await jcrLookup(entry.journalKw, opts);
    applyJcrToBadges(entry.factor, entry.quartile, result, entry.journalKw);
  }

  async function fillAppendixCitationBatch(entries) {
    if (!CFG.showCitation) return;
    const pmids = entries.map((e) => e.pmid).filter(Boolean);
    if (!pmids.length) return;
    const map = await iciteBatch(pmids);
    entries.forEach((e) => {
      const cc = e.docsum.querySelector(".Scholarscope_Action_Cited");
      if (!cc) return;
      const info = map[e.pmid];
      if (info && typeof info.citation_count === "number")
        cc.textContent = `Cited: ${info.citation_count}`;
      else cc.textContent = "Cited: —";
    });
  }

  // =========================================================================
  // Abstract toggle
  // =========================================================================
  async function toggleAbstract(docsum, pmid) {
    const content = docsum.querySelector(".docsum-content");
    if (!content) return;
    let snippet = content.querySelector(
      `.full-view-snippet[data-fullview-pmid="${pmid}"]`,
    );
    if (snippet) {
      snippet.remove();
      return;
    }
    snippet = el("div", {
      class: "full-view-snippet",
      dataset: { fullviewPmid: pmid },
    });
    snippet.textContent = "Loading…";
    snippet.style.opacity = "0.6";
    content.appendChild(snippet);
    try {
      const { abstractHtml } = await efetchAbstractXml(pmid);
      snippet.innerHTML = DOMPurify.sanitize(abstractHtml);
      snippet.style.opacity = "1";
      snippet.style.borderLeft = "solid 0.2rem #0094DF";
      snippet.style.paddingLeft = "1rem";
    } catch (e) {
      snippet.textContent = "加载摘要失败:" + e.message;
      snippet.style.opacity = "1";
    }
  }

  // =========================================================================
  // Google Scholar
  // =========================================================================
  const SCHOLAR_RESULT_SELECTOR = ".gs_r.gs_or.gs_scl";
  const DOCSUM_SELECTOR =
    ".search-results-chunk .full-docsum, #gs_res_ccl_mid .full-docsum";

  function extractScholarTitle(gsR) {
    const a = gsR.querySelector(".gs_rt > a");
    return a ? (a.textContent || "").trim() : "";
  }

  function parseScholarMeta(gsR) {
    const a = gsR.querySelector(".gs_a");
    const raw = a ? (a.textContent || "").trim() : "";
    let firstAuthor = "";
    let journal = "";
    let year = "";
    if (!raw) return { firstAuthor, journal, year, truncated: false };
    // Scholar uses NBSP ( ) before the hyphen that separates the truncated
    // author list from the journal segment; normalize to regular spaces so a
    // single split rule covers both truncated and non-truncated rows.
    const text = raw.replace(/ /g, " ");
    const parts = text.split(" - ");
    if (parts.length) {
      const head = parts[0].split(",")[0] || "";
      firstAuthor = head.replace(/[…\s]+$/, "").trim();
    }
    if (parts.length >= 3) {
      const middle = parts.slice(1, -1).join(" - ");
      const m = middle.match(/^(.*?),\s*((?:19|20)\d{2})\s*$/);
      if (m) {
        journal = m[1].trim();
        year = m[2];
      } else if (/^(?:19|20)\d{2}$/.test(middle.trim())) {
        year = middle.trim();
      }
    } else if (parts.length === 2) {
      const m = parts[1].match(/^(.*?),\s*((?:19|20)\d{2})\s*$/);
      if (m) {
        journal = m[1].trim();
        year = m[2];
      }
    }
    // 'JOURNAL…' → f=1 (prefix); '…JOURNAL…' → f=2 (substring); '…JOURNAL' → f=3 (suffix)
    let truncated = "";
    if (journal) {
      const startsTrunc = journal.startsWith("…");
      const endsTrunc = /…\s*$/.test(journal);
      if (startsTrunc && endsTrunc) truncated = "2";
      else if (endsTrunc) truncated = "1";
      else if (startsTrunc) truncated = "3";
    }
    return { firstAuthor, journal, year, truncated };
  }

  function extractScholarCitedCount(gsR) {
    const links = gsR.querySelectorAll(".gs_fl a");
    for (const link of links) {
      const href = link.getAttribute("href") || "";
      if (href.indexOf("?cites=") >= 0 || href.indexOf("&cites=") >= 0) {
        const m = (link.textContent || "").match(/(\d[\d,]*)/);
        if (m) return parseInt(m[1].replace(/,/g, ""), 10);
      }
    }
    return null;
  }

  async function esearchPmid(title, journal) {
    const t = (title || "").trim();
    if (!t) throw new Error("no title");
    const j = (journal || "").trim();
    const isShortTitle = t.length < 12 || t.split(/\s+/).length < 8;
    const buildUrl = (term) => {
      const u = new URL(ESEARCH_ENDPOINT);
      u.searchParams.set("db", "pubmed");
      u.searchParams.set("retmax", "1");
      u.searchParams.set("retmode", "json");
      u.searchParams.set("term", term);
      if (CFG.pubmedApiKey) u.searchParams.set("api_key", CFG.pubmedApiKey);
      return u.toString();
    };
    const tryTerm = async (term) => {
      const r = await gmFetch(buildUrl(term));
      const ids = r && r.esearchresult && r.esearchresult.idlist;
      return Array.isArray(ids) && ids.length ? ids[0] : "";
    };
    return efetchQueue(async () => {
      if (isShortTitle) {
        if (j) {
          const id = await tryTerm(`(${t}[Title]) AND (${j}[Journal])`);
          if (id) return id;
        }
        const id2 = await tryTerm(`(${t}[Title])`);
        if (id2) return id2;
      }
      return await tryTerm(t);
    });
  }

  async function toggleScholarAbstract(gsR) {
    const ri = gsR.querySelector(".gs_ri") || gsR;
    const existing = ri.querySelector(
      '.full-view-snippet[data-fullview-scholar="1"]',
    );
    if (existing) {
      existing.remove();
      return;
    }
    const snippet = el("div", {
      class: "full-view-snippet",
      dataset: { fullviewScholar: "1" },
    });
    snippet.textContent = "Loading…";
    snippet.style.opacity = "0.6";
    const after = ri.querySelector(".gs_rs");
    if (after && after.parentNode === ri)
      ri.insertBefore(snippet, after.nextSibling);
    else ri.appendChild(snippet);
    try {
      const title = extractScholarTitle(gsR);
      const scholarMeta = parseScholarMeta(gsR);
      const pmid = await esearchPmid(
        title,
        scholarMeta.truncated ? "" : scholarMeta.journal,
      );
      if (!pmid) {
        snippet.textContent = "未找到 PubMed 记录";
        snippet.style.opacity = "1";
        return;
      }
      const { abstractHtml, articleTitleHtml, journalTitleHtml, doiHtml } =
        await efetchAbstractXml(pmid);
      const meta =
        `<div class="Scholarscope_AbstractMeta">` +
        `PMID: <a href="${PUBMED_BASE}${pmid}/" target="_blank" rel="noopener">${pmid}</a>` +
        (doiHtml
          ? `DOI: <a href="https://doi.org/${doiHtml}" target="_blank" rel="noopener">${doiHtml}</a>`
          : "") +
        (articleTitleHtml
          ? `<div class="Scholarscope_AbstractTitle">${articleTitleHtml}</div>`
          : "") +
        (journalTitleHtml
          ? `<div class="Scholarscope_AbstractJournal"><i>${journalTitleHtml}</i></div>`
          : "") +
        `</div>`;
      snippet.innerHTML = DOMPurify.sanitize(meta + abstractHtml);
      snippet.style.opacity = "1";
      snippet.style.borderLeft = "solid 0.2rem #0094DF";
      snippet.style.paddingLeft = "1rem";
    } catch (e) {
      snippet.textContent = "加载摘要失败:" + (e && e.message ? e.message : e);
      snippet.style.opacity = "1";
    }
  }

  async function fetchScholarCite(cid) {
    if (!cid) throw new Error("no cid");
    const url = `${SCHOLAR_BASE}?q=info:${encodeURIComponent(cid)}:scholar.google.com/&output=cite&hl=en`;
    return scholarQueue(() => gmFetchText(url));
  }

  function extractFromCite(html) {
    const doc = new DOMParser().parseFromString(html, "text/html");
    const citrs = doc.querySelectorAll(".gs_citr");
    let journal = "";
    let title = "";
    for (const citr of citrs) {
      const it = citr.querySelector("i");
      if (!journal) journal = it ? it.textContent.trim() : "";
      if (title) continue;
      let beforeText = "";
      for (const node of citr.childNodes) {
        if (node === it) break;
        beforeText += node.textContent || "";
      }
      // MLA / Chicago: "Title."
      const quoted = beforeText.match(/["“]([^"”]+)["”]/);
      if (quoted && quoted[1]) {
        title = quoted[1].trim().replace(/\.$/, "");
      } else {
        // APA / Harvard: ...). Title. <journal>
        const apa = beforeText.match(/\)\.\s+([^.]+?)\.\s*$/);
        if (apa && apa[1]) {
          title = apa[1].trim();
        } else {
          // Vancouver: "Smith J. Title. Journal."
          const vanc = beforeText.match(/[A-Z]\.\s+([^.]+?)\.\s*$/);
          if (vanc && vanc[1]) title = vanc[1].trim();
        }
      }
    }
    return { journal, title };
  }

  async function recoverScholarJournal(entry, journalDiv) {
    if (!entry || !entry.cid || !journalDiv) return false;
    try {
      const html = await fetchScholarCite(entry.cid);
      const { journal: fullName, title: fullTitle } = extractFromCite(html);
      let updated = false;

      // Recover truncated title
      if (fullTitle && entry.gsR) {
        const titleLink =
          entry.gsR.querySelector(".gs_rt > a") ||
          entry.gsR.querySelector(".gs_rt");
        if (titleLink) {
          const currentTitle = (titleLink.textContent || "").trim();
          if (currentTitle.includes("…") && !fullTitle.includes("…")) {
            titleLink.textContent = fullTitle;
            updated = true;
          }
        }
      }

      // Recover truncated journal
      if (fullName && fullName !== journalDiv.textContent) {
        journalDiv.textContent = entry.journalKw = fullName;
        entry.truncated = "";
        if (entry.factor) {
          entry.factor.textContent = "…";
          entry.factor.dataset.origKw = fullName;
          entry.factor.style.backgroundColor = "#616161";
          entry.factor.classList.remove(...STATE_CLASSES);
        }
        if (entry.quartile) {
          entry.quartile.textContent = "…";
          entry.quartile.style.backgroundColor = "#616161";
        }
        await fillAppendixJcr(entry);
        if (CFG.autoFilter) applyFilter(true);
        if (CFG.autoSort) applySorting();
        updated = true;
      }

      return updated;
    } catch (e) {
      return false;
    }
  }

  function injectScholarAppendix(gsR) {
    if (!gsR || gsR.dataset.scholarscopeSeen === "1") return null;
    gsR.dataset.scholarscopeSeen = "1";
    gsR.classList.add("full-docsum");

    const ri = gsR.querySelector(".gs_ri");
    if (!ri) return null;

    const meta = parseScholarMeta(gsR);
    const title = extractScholarTitle(gsR);
    const cited = extractScholarCitedCount(gsR);
    const titleTruncated = title.includes("…");

    const entry = {
      gsR,
      cid: gsR.getAttribute("data-cid") || gsR.getAttribute("data-aid") || "",
      factor: null,
      quartile: null,
      journalKw: meta.journal,
      truncated: meta.truncated,
    };

    const frame = el("div", {
      class: "Scholarscope_Appendix_JournalFrame notranslate",
    });
    frame.dataset.cid = entry.cid;

    const journalDiv = el("div", {
      class: "Scholarscope_Appendix_Journal Scholarscope_Badge notranslate",
    });
    journalDiv.textContent = meta.journal || "—";
    frame.appendChild(journalDiv);

    if ((entry.truncated || titleTruncated) && entry.cid) {
      journalDiv.classList.add("Scholarscope_BadgeClickable");
      journalDiv.title = "点击通过 Cite 弹窗补全完整杂志名或标题";
      journalDiv.addEventListener(
        "click",
        async (ev) => {
          ev.preventDefault();
          ev.stopPropagation();
          if (journalDiv.dataset.busy === "1") return;
          journalDiv.dataset.busy = "1";
          journalDiv.classList.remove("Scholarscope_BadgeClickable");
          journalDiv.removeAttribute("title");
          journalDiv.style.opacity = "0.6";
          const ok = await recoverScholarJournal(entry, journalDiv);
          journalDiv.style.opacity = "";
          journalDiv.dataset.busy = "0";
          if (!ok) window.alert("未能从 Cite 弹窗恢复完整内容");
        },
        { once: true },
      );
    }

    const factor = makeFactorBadge("Scholarscope_Appendix_Factor");
    frame.appendChild(factor);
    const quartile = makeQuartileBadge("Scholarscope_Appendix_Quartile");
    frame.appendChild(quartile);
    entry.factor = factor;
    entry.quartile = quartile;

    if (meta.year) {
      const yd = el("div", {
        class:
          "Scholarscope_Appendix_Year Scholarscope_Badge Scholarscope_BadgeBlue",
      });
      yd.textContent = meta.year;
      frame.appendChild(yd);
    }

    const rt = ri.querySelector(".gs_rt");
    if (rt && rt.nextSibling) ri.insertBefore(frame, rt.nextSibling);
    else ri.insertBefore(frame, ri.firstChild);

    if (typeof cited === "number") {
      const citedSpan = el("span", {
        class: "Scholarscope_Action_Cited",
        hidden: "hidden",
        style: "display:none",
      });
      citedSpan.textContent = `Cited: ${cited}`;
      frame.appendChild(citedSpan);
    }

    const fl =
      gsR.querySelector(".gs_fl.gs_flb") || gsR.querySelector(".gs_fl");
    const sav = fl && fl.querySelector(".gs_or_sav");
    if (fl && sav) {
      const trimmedTitle = (title || "").trim();
      const pubmedHref = trimmedTitle
        ? `${PUBMED_BASE}?term=${encodeURIComponent(trimmedTitle).replace(/%20/g, "+")}`
        : "";
      if (pubmedHref) {
        const pmLink = el(
          "a",
          {
            class: "Scholarscope_Action_PubMedSearch gs_or_btn",
            href: pubmedHref,
            target: "_blank",
            rel: "noopener",
            role: "button",
            title: "在 PubMed 中搜索(标题)",
          },
          "PubMed",
        );
        fl.insertBefore(pmLink, sav);
        fl.insertBefore(document.createTextNode(" "), sav);
      }
      const absBtn = el(
        "a",
        {
          class: "Scholarscope_Action_Abstract gs_or_btn",
          href: "javascript:void(0)",
          role: "button",
          title: "获取 PubMed 第一条结果的完整摘要",
        },
        "Full Abstract",
      );
      absBtn.addEventListener("click", (ev) => {
        ev.preventDefault();
        ev.stopPropagation();
        toggleScholarAbstract(gsR);
      });
      fl.insertBefore(absBtn, sav);
      fl.insertBefore(document.createTextNode(" "), sav);
    }

    return entry;
  }

  async function processBatch(nodes, injectFn, withCitation) {
    const entries = [];
    nodes.forEach((n) => {
      const e = injectFn(n);
      if (e) entries.push(e);
    });
    if (!entries.length) return;
    const tasks = [Promise.all(entries.map(fillAppendixJcr))];
    if (withCitation) tasks.push(fillAppendixCitationBatch(entries));
    await Promise.all(tasks);
    if (CFG.autoFilter) applyFilter(true);
    if (CFG.autoSort) applySorting();
  }

  function injectShowMoreButton() {
    const mid = document.getElementById("gs_res_ccl_mid");
    if (!mid) return;
    const existing = document.getElementById("Scholarscope_ShowMoreButton");
    if (existing) return;
    const btn = el(
      "a",
      {
        id: "Scholarscope_ShowMoreButton",
        class: "Scholarscope_ShowMoreButton notranslate",
        href: "javascript:void(0)",
        role: "button",
      },
      "显示更多结果",
    );
    const initialStart =
      parseInt(new URLSearchParams(location.search).get("start") || "0", 10) ||
      0;
    btn.dataset.nextStart = String(initialStart + 10);
    const errBox = el("div", {
      id: "Scholarscope_ShowMoreError",
      class: "Scholarscope_ShowMoreError",
      style: "display:none",
    });
    const parent = mid.parentNode;
    parent.insertBefore(btn, mid.nextSibling);
    parent.insertBefore(errBox, btn.nextSibling);
    btn.addEventListener("click", (ev) => {
      ev.preventDefault();
      handleShowMoreClick(btn, errBox);
    });
  }

  async function handleShowMoreClick(btn, errBox) {
    if (btn.getAttribute("aria-disabled") === "true") return;
    const nextStart = parseInt(btn.dataset.nextStart || "10", 10);
    btn.setAttribute("aria-disabled", "true");
    const origText = btn.textContent;
    btn.textContent = "加载中…";
    errBox.style.display = "none";
    errBox.textContent = "";
    try {
      const u = new URL(location.href);
      u.searchParams.set("start", String(nextStart));
      const html = await gmFetchText(u.toString());
      const doc = new DOMParser().parseFromString(html, "text/html");
      const newNodes = Array.from(
        doc.querySelectorAll(SCHOLAR_RESULT_SELECTOR),
      );
      const hasNext = !!doc.querySelector(".gs_ico_nav_next");
      if (!newNodes.length) {
        errBox.textContent =
          "Google Scholar 已限制频繁请求或无更多结果,请稍后再试或在新标签直接打开下一页。";
        errBox.style.display = "block";
        btn.textContent = origText;
        btn.removeAttribute("aria-disabled");
        return;
      }
      const mid = document.getElementById("gs_res_ccl_mid");
      const adopted = [];
      newNodes.forEach((n) => {
        const node = document.adoptNode(n);
        mid.appendChild(node);
        adopted.push(node);
      });
      await processBatch(adopted, injectScholarAppendix, false);
      btn.dataset.nextStart = String(nextStart + 10);
      if (!hasNext) {
        btn.textContent = "已无更多结果";
        btn.setAttribute("aria-disabled", "true");
      } else {
        btn.textContent = origText;
        btn.removeAttribute("aria-disabled");
      }
    } catch (e) {
      errBox.textContent = "加载失败:" + (e && e.message ? e.message : e);
      errBox.style.display = "block";
      btn.textContent = origText;
      btn.removeAttribute("aria-disabled");
    }
  }

  function initScholar() {
    buildToolbar();
    const selectBtn = document.getElementById("Scholarscope_SelectShownFrame");
    if (selectBtn) selectBtn.style.display = "none";
    const initial = Array.from(
      document.querySelectorAll(`#gs_res_ccl_mid > ${SCHOLAR_RESULT_SELECTOR}`),
    );
    processBatch(initial, injectScholarAppendix, false);
    injectShowMoreButton();
  }

  // =========================================================================
  // Search page toolbar: filter + sort + select-shown
  // =========================================================================
  function isSearchResultsPage() {
    if (document.querySelector(".search-results-chunk")) return true;
    if (
      document.querySelector(".full-docsum") &&
      !document.getElementById("full-view-heading")
    )
      return true;
    return false;
  }
  function isDetailPage() {
    return !!document.getElementById("full-view-heading");
  }

  function buildToolbar() {
    if (document.getElementById("Scholarscope_Toolbar")) return;
    const anchor =
      document.getElementById("gs_ab_md") ||
      document.querySelector(".top-wrapper") ||
      document.querySelector(".search-results-view-switch") ||
      document.querySelector(".results-amount-container");
    if (!anchor) return;

    const bar = el("div", {
      id: "Scholarscope_Toolbar",
      class: "Scholarscope_Toolbar notranslate",
    });
    const filterBtn = el(
      "div",
      {
        id: "Scholarscope_FilterButton",
        class:
          "Scholarscope_ToolbarButton Scholarscope_FilterButton notranslate",
      },
      "页内筛选",
    );
    const sortBtn = el(
      "div",
      {
        id: "Scholarscope_SortButton",
        class: "Scholarscope_ToolbarButton Scholarscope_SortButton notranslate",
      },
      sortButtonLabel(),
    );
    const selectBtn = el(
      "div",
      {
        id: "Scholarscope_SelectShownFrame",
        class:
          "Scholarscope_ToolbarButton Scholarscope_SelectShownFrame notranslate",
      },
      "全选",
    );
    const dropdown = buildFilterDropdown();

    bar.appendChild(filterBtn);
    bar.appendChild(sortBtn);
    bar.appendChild(selectBtn);
    bar.appendChild(dropdown);

    anchor.parentNode.insertBefore(bar, anchor.nextSibling);

    filterBtn.addEventListener("click", () => {
      dropdown.style.display =
        dropdown.style.display === "block" ? "none" : "block";
    });
    sortBtn.addEventListener("click", () => {
      CFG.sortingMethod = CFG.sortingMethod === 1 ? 2 : 1;
      sortBtn.textContent = sortButtonLabel();
      saveCfg();
      applySorting();
    });
    selectBtn.addEventListener("click", selectShownDocsums);
  }

  function sortButtonLabel() {
    return CFG.sortingMethod === 2 ? "按引用量排序" : "按分数排序";
  }

  function buildFilterDropdown() {
    const dd = el("div", {
      id: "Scholarscope_FilterDropDown",
      class: `Scholarscope_DropDown${isScholarPage() ? " gs_query" : ""} notranslate`,
      style: "display:none",
    });

    const container = el("div", { class: "Scholarscope_FilterContainerFrame" });

    const inputs = el("div", { class: "Scholarscope_FilterValueInputFrame" }, [
      el("div", { class: "Scholarscope_TermWrapper" }, [
        document.createTextNode("IF最小值:"),
        el("input", {
          id: "Scholarscope_FilterValueMinInput",
          class: "Scholarscope_FilterValueInput",
          type: "number",
          step: "0.1",
          min: "0",
          max: "2000",
          value: String(CFG.filter.minIF),
        }),
      ]),
      el("div", { class: "Scholarscope_TermWrapper" }, [
        document.createTextNode("IF最大值:"),
        el("input", {
          id: "Scholarscope_FilterValueMaxInput",
          class: "Scholarscope_FilterValueInput",
          type: "number",
          step: "0.1",
          min: "0",
          max: "2000",
          value: String(CFG.filter.maxIF),
        }),
      ]),
    ]);

    const qs = el("div", { class: "Scholarscope_FilterQuartileInputFrame" });
    ["q1", "q2", "q3", "q4"].forEach((k, i) => {
      const line = el("div", {}, [
        el("input", {
          id: `Scholarscope_FilterValueQuartile${i + 1}Input`,
          type: "checkbox",
          checked: !!CFG.filter[k] ? true : false,
        }),
        el(
          "label",
          {
            class: "Scholarscope_FilterValueQuartileTexts",
            for: `Scholarscope_FilterValueQuartile${i + 1}Input`,
          },
          CFG.quartileSource === "cas" ? `${i + 1}区` : `Q${i + 1}`,
        ),
      ]);
      qs.appendChild(line);
    });

    container.appendChild(inputs);
    container.appendChild(qs);

    const autoCheckbox = el("div", { class: "Scholarscope_InlineCheck" }, [
      el("div", {}, [
        el("input", {
          id: "Scholarscope_FilterCheckbox",
          type: "checkbox",
          checked: !!CFG.autoFilter ? true : false,
        }),
        el("label", { for: "Scholarscope_FilterCheckbox" }, "自动筛选"),
      ]),
      el("div", {}, [
        el("input", {
          id: "Scholarscope_SortCheckbox",
          type: "checkbox",
          checked: !!CFG.autoSort ? true : false,
        }),
        el("label", { for: "Scholarscope_SortCheckbox" }, "自动排序"),
      ]),
    ]);

    const apply = el(
      "div",
      {
        class: "Scholarscope_DropDownButton Scholarscope_DropDownButtonPrimary",
      },
      "应用",
    );
    const close = el(
      "div",
      {
        class: "Scholarscope_DropDownButton Scholarscope_DropDownButtonDanger",
      },
      "取消筛选",
    );
    const buttonRow = el("div", { class: "Scholarscope_ButtonRow" }, [
      apply,
      close,
    ]);

    dd.appendChild(container);
    dd.appendChild(autoCheckbox);
    dd.appendChild(buttonRow);

    apply.addEventListener("click", () => {
      CFG.filter.minIF =
        parseFloat(
          dd.querySelector("#Scholarscope_FilterValueMinInput").value,
        ) || 0;
      CFG.filter.maxIF =
        parseFloat(
          dd.querySelector("#Scholarscope_FilterValueMaxInput").value,
        ) || 2000;
      ["q1", "q2", "q3", "q4"].forEach((k, i) => {
        CFG.filter[k] = dd.querySelector(
          `#Scholarscope_FilterValueQuartile${i + 1}Input`,
        ).checked
          ? 1
          : 0;
      });
      CFG.autoFilter = dd.querySelector("#Scholarscope_FilterCheckbox").checked;
      CFG.autoSort = dd.querySelector("#Scholarscope_SortCheckbox").checked;
      saveCfg();
      applyFilter(true);
      dd.style.display = "none";
    });
    close.addEventListener("click", () => {
      applyFilter(false);
      dd.style.display = "none";
    });

    return dd;
  }

  function applyFilter(enabled) {
    const docsums = document.querySelectorAll(DOCSUM_SELECTOR);
    const selectBtn = document.getElementById("Scholarscope_SelectShownFrame");
    let anyHidden = false;
    docsums.forEach((ds) => {
      if (!enabled) {
        ds.style.height = "";
        ds.style.marginBottom = "";
        return;
      }
      const f = ds.querySelector(".Scholarscope_Appendix_Factor");
      const q = ds.querySelector(".Scholarscope_Appendix_Quartile");
      if (!f || !q) return;
      const fv = parseFloat(f.textContent);
      const qv = (q.textContent || "").replace(/\s+/g, "").toUpperCase();
      const anyQSelected =
        CFG.filter.q1 + CFG.filter.q2 + CFG.filter.q3 + CFG.filter.q4 > 0;
      let qMatch = !anyQSelected;
      if (!qMatch) {
        if (CFG.filter.q1 && (qv === "Q1" || qv === "1区")) qMatch = true;
        else if (CFG.filter.q2 && (qv === "Q2" || qv === "2区")) qMatch = true;
        else if (CFG.filter.q3 && (qv === "Q3" || qv === "3区")) qMatch = true;
        else if (CFG.filter.q4 && (qv === "Q4" || qv === "4区")) qMatch = true;
      }
      const ifHit =
        !isNaN(fv) && fv >= CFG.filter.minIF && fv <= CFG.filter.maxIF;
      const visible = qMatch && ifHit;
      if (!visible) {
        ds.style.height = "0px";
        ds.style.margin = "0";
        ds.style.padding = "0";
        ds.style.overflow = "hidden";
        anyHidden = true;
      } else {
        ds.style.height = "";
        ds.style.marginBottom = "";
        ds.style.overflow = "";
      }
    });
    if (selectBtn)
      selectBtn.style.visibility = enabled && anyHidden ? "visible" : "hidden";
  }

  function applySorting() {
    const docsums = Array.from(document.querySelectorAll(DOCSUM_SELECTOR));
    if (docsums.length < 2) return;
    const holders = docsums.map(
      (ds) => ds.closest("li, article.full-docsum") || ds,
    );
    const target = holders[0].parentElement;
    if (!target) return;
    holders.sort((a, b) => readSortValue(b) - readSortValue(a));
    holders.forEach((h) => target.appendChild(h));
  }
  function readSortValue(holder) {
    const ds =
      holder.classList && holder.classList.contains("full-docsum")
        ? holder
        : holder.querySelector(".full-docsum") || holder;
    if (CFG.sortingMethod === 2) {
      const cc = ds.querySelector(".Scholarscope_Action_Cited");
      if (cc) {
        const m = (cc.textContent || "").match(/(\d+(?:\.\d+)?)/);
        return m ? parseFloat(m[1]) : -1;
      }
      return -1;
    }
    const f = ds.querySelector(".Scholarscope_Appendix_Factor");
    if (f) {
      const v = parseFloat(f.textContent);
      return isNaN(v) ? -1 : v;
    }
    return -1;
  }

  function selectShownDocsums() {
    const docsums = document.querySelectorAll(DOCSUM_SELECTOR);
    docsums.forEach((ds) => {
      if (ds.style.height === "0px") return;
      const cb = ds.querySelector(
        '.search-result-selector input[type="checkbox"], input.search-result-selector',
      );
      if (cb && !cb.checked) cb.click();
    });
  }

  // =========================================================================
  // Bootstrap per page type
  // =========================================================================
  function watchDocsumList() {
    const processNew = () => {
      const fresh = Array.from(
        document.querySelectorAll(".full-docsum"),
      ).filter((ds) => !ds.dataset.scholarscopeSeen);
      fresh.forEach((ds) => (ds.dataset.scholarscopeSeen = "1"));
      if (fresh.length) processBatch(fresh, injectAppendixFrame, true);
    };
    processNew();
    const target =
      document.querySelector(".search-results-chunk") ||
      document.querySelector("main") ||
      document.body;
    const obs = new MutationObserver((muts) => {
      let need = false;
      for (const m of muts) {
        for (const n of m.addedNodes) {
          if (
            n.nodeType === 1 &&
            ((n.classList && n.classList.contains("full-docsum")) ||
              (n.querySelector && n.querySelector(".full-docsum")))
          ) {
            need = true;
            break;
          }
        }
        if (need) break;
      }
      if (need) processNew();
    });
    obs.observe(target, { childList: true, subtree: true });
  }

  function init() {
    if (isScholarPage()) {
      initScholar();
      return;
    }
    if (isDetailPage()) {
      renderDetailHeader();
      renderDetailTimesCited();
      watchDocsumList();
    } else if (isSearchResultsPage()) {
      buildToolbar();
      if (!document.getElementById("Scholarscope_Toolbar")) {
        const obs = new MutationObserver(() => {
          buildToolbar();
          if (document.getElementById("Scholarscope_Toolbar")) obs.disconnect();
        });
        obs.observe(document.body, { childList: true, subtree: true });
      }
      watchDocsumList();
    }
  }

  // =========================================================================
  // Menu commands
  // =========================================================================
  GM_registerMenuCommand(
    "切换分区来源 (当前:" +
      (CFG.quartileSource === "cas" ? "中科院" : "JCR") +
      ")",
    () => {
      CFG.quartileSource = CFG.quartileSource === "cas" ? "jcr" : "cas";
      saveCfg();
      alert("下次加载页面生效。");
    },
  );
  GM_registerMenuCommand("清空 JCR 缓存", () => {
    if (!window.confirm("确定清空 JCR 缓存吗?此操作不可撤销。")) return;
    GM_setValue("jcrCache", {});
    alert("已清空 JCR 缓存。");
  });
  GM_registerMenuCommand("设置 PubMed API Key", () => {
    const v = window.prompt(
      "输入 NCBI eutils API Key(留空则清除):",
      CFG.pubmedApiKey || "",
    );
    if (v == null) return;
    CFG.pubmedApiKey = v.trim();
    saveCfg();
    alert(CFG.pubmedApiKey ? "已保存。" : "已清除。");
  });

  // =========================================================================
  // Styles
  // =========================================================================
  GM_addStyle(`
/* Detail page */
.Scholarscope_JournalDetailFrame{display:inline-block;width:max-content;line-height:1.6;position:relative;top:10px;overflow:hidden}
.Scholarscope_Badge{
  color:#fff;padding:0 .5em;width:max-content;float:left;height:2em;line-height:2em;background:#616161;user-select:none}
.Scholarscope_BadgeClickable{cursor:pointer}
.Scholarscope_BadgeBlue{background:#225390}

/* Appendix (search + detail-list) badge group */
.Scholarscope_Appendix_JournalFrame{width:100%;min-height:2em;overflow:hidden;margin-top:.3em;margin-bottom:.3em}
.Scholarscope_Appendix_Journal{border-right:1px solid}

/* IF ambiguity warning */
.Scholarscope_IF_Ambiguous:before{content:"\u26A0\uFE0F";vertical-align:bottom;font-size:11px}

/* Not Found visual */
.Scholarscope_NotFound{background:#616161 !important}

/* MedLine 命中但无 IF/分区 */
.Scholarscope_MedHit{background-color:#0094DF !important;color:#fff}

/* DOI inline */
.Scholarscope_DOI{display:inline-block;margin-left:.5em;font-size:.92em;color:#5B616B}
.Scholarscope_DOILink{color:#0071BC;text-decoration:none}
.Scholarscope_DOILink:hover{text-decoration:underline}

/* Action row (Cited / Google Scholar / Full Abstract) */
.Scholarscope_ActionRow{margin-top:.4em;display:flex;column-gap:1em;flex-wrap:wrap}
.Scholarscope_Action_Cited:before,.Scholarscope_Action_Abstract:before{width:14px;height:14px}
.Scholarscope_Action_Cited:before{content:"\uD83D\uDD25"}
.Scholarscope_Action_Abstract{cursor:pointer}
.Scholarscope_Action_Abstract:before{content:"\uD83D\uDCD6"}
.full-view-snippet{overflow:auto;transition:opacity .3s;margin-top:.5rem}
#adjacent-navigation .full-view-snippet{max-height:200px;}

/* Toolbar */
.Scholarscope_Toolbar{position:relative;margin:.5rem 0;display:flex;gap:0;align-items:center}
.Scholarscope_ToolbarButton{
  display:inline-flex;align-items:center;
  padding:5px 15px 5px 12px;border:1px solid #aeb0b5;font-size:14px;color:#212121;cursor:pointer;
  background:#fff;margin-right:8px;transition:color .3s,border-color .3s,background-color .3s;user-select:none}
.Scholarscope_ToolbarButton:hover{border-color:#046B99}
.Scholarscope_ToolbarButton:active{background:#205493;color:#fff}
.Scholarscope_ToolbarButton:before{content:"";width:14px;height:14px;margin-right:3px;fill:#5B616B}
.Scholarscope_FilterButton:before{background:url("data:image/svg+xml,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M29.815 6.168A1.991 1.991 0 0 0 27.986 5H4.014c-.797 0-1.498.448-1.83 1.168a1.972 1.972 0 0 0 .297 2.128l.001.001L12 19.371V28a1 1 0 0 0 1.555.832l6-4c.278-.186.445-.498.445-.832v-4.629l9.519-11.074a1.972 1.972 0 0 0 .296-2.129z'/%3E%3C/svg%3E")}
.Scholarscope_SortButton:before{background:url("data:image/svg+xml,%3Csvg height='14' viewBox='0 0 28 28' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M24.244 18.373l-2.951 2.95.02-10.323a1 1 0 1 0-2 0l-.02 10.253-2.879-2.88A1 1 0 0 0 15 19.788l4.115 4.115a1.498 1.498 0 0 0 2.188.224c.039-.03.076-.062.111-.097l4.244-4.242a1 1 0 0 0-1.414-1.415zM2 20.7a.8.8 0 0 0 .8.8h7.9a.797.797 0 0 0 .8-.8v-.4a.8.8 0 0 0-.8-.8H2.8a.8.8 0 0 0-.8.8v.4zM2.506 13.443A.798.798 0 0 1 2 12.7v-.4a.8.8 0 0 1 .8-.8h12.4a.8.8 0 0 1 .8.8v.4a.8.8 0 0 1-.8.8H2.8a.805.805 0 0 1-.294-.057zM2.074 5.035A.797.797 0 0 1 2 4.7v-.4a.8.8 0 0 1 .8-.8h21.4a.8.8 0 0 1 .8.8v.4a.8.8 0 0 1-.8.8H2.8a.8.8 0 0 1-.726-.465z'/%3E%3C/svg%3E")}
.Scholarscope_SelectShownFrame{visibility:hidden}
.Scholarscope_SelectShownFrame:before{background:url("data:image/svg+xml,%3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M374.656 713.344a32 32 0 0 1 3.648 40.832l-3.648 4.48-128 128a32 32 0 0 1-40.832 3.648l-4.48-3.648-64-64a32 32 0 0 1 40.832-48.96l4.48 3.648L224 818.752l105.344-105.408a32 32 0 0 1 45.312 0zM864 768a32 32 0 1 1 0 64H480a32 32 0 1 1 0-64h384zM374.656 457.344a32 32 0 0 1 3.648 40.832l-3.648 4.48-128 128a32 32 0 0 1-40.832 3.648l-4.48-3.648-64-64a32 32 0 0 1 40.832-48.96l4.48 3.648L224 562.752l105.344-105.408a32 32 0 0 1 45.312 0zM864 512a32 32 0 1 1 0 64H480a32 32 0 0 1 0-64h384zM374.656 201.344a32 32 0 0 1 3.648 40.832l-3.648 4.48-128 128a32 32 0 0 1-40.832 3.648l-4.48-3.648-64-64a32 32 0 0 1 40.832-48.96l4.48 3.648L224 306.752l105.344-105.408a32 32 0 0 1 45.312 0zM864 256a32 32 0 1 1 0 64H480a32 32 0 0 1 0-64h384z'/%3E%3C/svg%3E")}
@media (max-width:600px){
  #gs_ab .Scholarscope_ToolbarButton{font-size:0 !important;width:20px;padding:5px !important}
  #gs_ab .Scholarscope_DropDown{transform:translateX(-50%)}
}

/* Dropdown */
.Scholarscope_DropDown{
  min-width:300px;background:#fff;border:1px solid #aeb0b5;box-shadow:0 3px 14px -4px #8E8E8E;
  position:absolute;top:44px;left:0;z-index:1000;padding:12px;box-sizing:border-box;font-size:14px;margin:0}
.Scholarscope_DropDown label{margin-top:0}
.Scholarscope_FilterContainerFrame{display:flex;border-bottom:1px dashed #DDD;padding-bottom:10px;margin-bottom:10px}
.Scholarscope_FilterValueInputFrame{width:62%}
.Scholarscope_FilterQuartileInputFrame{width:38%;border-left:1px dashed #DDD;padding-left:10px;margin-top:1em}
.Scholarscope_FilterValueQuartileTexts{font-weight:bold}
.Scholarscope_InlineCheck{display:flex;align-items:center;gap:12px!important;margin:10px 0}
.Scholarscope_ButtonRow{display:flex;gap:10px;margin-top:8px}
.Scholarscope_DropDownButton{
  flex:0 0 110px;min-width:110px;text-align:center;padding:8px 0;line-height:1;color:#fff;cursor:pointer;user-select:none;
  display:inline-block !important;visibility:visible !important;opacity:1 !important;pointer-events:auto !important;
  border:0;font:inherit}
.Scholarscope_DropDownButtonPrimary,.Scholarscope_Modal_Close{background:#0071BC}
.Scholarscope_DropDownButtonPrimary:hover,.Scholarscope_Modal_Close:hover{background:#20558A}
.Scholarscope_DropDownButtonDanger,.Scholarscope_Modal_ClearCache{background:#E66666}
.Scholarscope_DropDownButtonDanger:hover,.Scholarscope_Modal_ClearCache:hover{background:#D50000}
.Scholarscope_DropDown input[type="checkbox"]{display:none}
.Scholarscope_DropDownTitle{font-weight:bold;margin-bottom:10px}
.Scholarscope_DropDownField{display:block;flex:auto;margin:8px 0}
.Scholarscope_DropDownLabel{font-weight:bold;margin-bottom:4px}
.Scholarscope_FilterValueInput,.Scholarscope_DropDownControl{padding:4px;border:1px solid #bbb}
.Scholarscope_FilterValueInput{width:50px}
.Scholarscope_DropDownControl{width:100%;box-sizing:border-box}
.Scholarscope_DropDownHint{margin-top:4px;color:#5B616B;line-height:1.4}
.Scholarscope_TermWrapper{display:flex;gap:6px;margin-top:1em;align-items:center}

/* Modal */
#Scholarscope_Modal{position:fixed;inset:0;z-index:9999}
.Scholarscope_Modal_Mask{position:absolute;inset:0;background:rgba(0,0,0,.4)}
.Scholarscope_Modal_Box{position:relative;margin:10vh auto 0;max-width:720px;background:#fff;padding:20px;box-shadow:0 10px 40px rgba(0,0,0,.3);font-size:14px;max-height:80vh;overflow:auto}
.Scholarscope_Modal_Content h3{margin-top:0}
.Scholarscope_Modal_Table{width:100%;border-collapse:collapse;margin-top:10px}
.Scholarscope_Modal_Table th,.Scholarscope_Modal_Table td{border:1px solid #ddd;padding:6px 10px;text-align:left;font-size:13px}
.Scholarscope_Modal_Table th{background:#f4f6f8}
.Scholarscope_Modal_RowHighlight{background:#FFF8E1}
.Scholarscope_Modal_Close,.Scholarscope_Modal_ClearCache{display:inline-block;margin-top:12px;padding:8px 16px;color:#fff;cursor:pointer;user-select:none}
.Scholarscope_Modal_ButtonRow{display:flex;gap:10px;margin-top:12px}
.Scholarscope_Modal_ButtonRow .Scholarscope_Modal_Close,
.Scholarscope_Modal_ButtonRow .Scholarscope_Modal_ClearCache{margin-top:0}

/* Float clearfix */
.Scholarscope_Appendix_JournalFrame::after,.Scholarscope_JournalDetailFrame::after{content:"";display:block;clear:both}

/* Google Scholar */
.gs_ri .Scholarscope_Appendix_JournalFrame{margin:.4em 0;width:auto}
.gs_ri .Scholarscope_Appendix_Journal{max-width:24em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.Scholarscope_Action_PubMedSearch:before,.Scholarscope_Action_GScholar:before{vertical-align:middle;margin-right:2px}
.Scholarscope_Action_PubMedSearch:before{content:url("data:image/svg+xml,%3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='13' height='13'%3E%3Cpath d='M128 64h448l192 448-192 448H128A128 128 0 0 1 0 832V192A128 128 0 0 1 128 64z' fill='%23205992'/%3E%3Cpath d='M704 64h128l192 448-192 448H704l192-448z' fill='%23205992'/%3E%3C/svg%3E")}
.Scholarscope_Action_GScholar:before{content:url("data:image/svg+xml,%3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='13' height='13'%3E%3Cpath d='M512 822.24L0 405.334 512 0z' fill='%234285F4'/%3E%3Cpath d='M512 822.24l512-416.906L512 0z' fill='%23356AC3'/%3E%3Cpath d='M213.334 725.334a298.666 298.666 0 1 0 597.332 0 298.666 298.666 0 1 0-597.332 0z' fill='%23A0C3FF'/%3E%3Cpath d='M242.074 597.334C290.01 496.428 392.858 426.666 512 426.666s221.99 69.762 269.926 170.668H242.074z' fill='%2376A7FA'/%3E%3C/svg%3E")}
.Scholarscope_AbstractMeta{font-size:.92em;color:#5B616B;margin-bottom:.4em}
.Scholarscope_AbstractMeta a{color:#1a0dab;margin-right:5px;text-decoration:none}
.Scholarscope_AbstractMeta a:hover{text-decoration:underline}
.Scholarscope_AbstractTitle{font-weight:600;color:#212121;margin-top:.15em}
.Scholarscope_AbstractJournal{margin-top:.05em}
.Scholarscope_ShowMoreButton{
  display:block;width:max-content;margin:1em auto;padding:8px 24px;
  border:1px solid #aeb0b5;background:#fff;color:#1a0dab;font-size:14px;
  text-decoration:none;cursor:pointer;user-select:none}
.Scholarscope_ShowMoreButton:hover{border-color:#046B99;background:#f4f6f8;text-decoration:none}
.Scholarscope_ShowMoreButton[aria-disabled="true"]{opacity:.55;cursor:default;pointer-events:none}
.Scholarscope_ShowMoreError{margin:.5em auto;color:#D50000;text-align:center;font-size:13px}
.gs_query input[type="checkbox"]+label{display:inline-flex;cursor:pointer}
.gs_query input[type="checkbox"]+label:before{display:inline-block;content:"";width:14px;height:14px;margin-right:4px;border:1px solid #c6c6c6;border-radius:1px;color:#222}
.gs_query [type=checkbox]:checked+label::before{background:no-repeat url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAJCAQAAACssQXfAAAAiklEQVR4AT3PNUFGARAA4MO1AFRgQVrgTEgNXBZ0Z0aLQI0XANf594932LlLROjQHgmhM9mVwlhkMJO/4S7noMjghKEIPcmuUNNyGpa8uzUcYdA1qjjJ+ntwY8Q16jiIBCte0fSmUUr2Mio3W/BJqwQt2xHaZFhXhBnPqFr7D6eRKVMereeIv5++AKLreD06aLBkAAAAAElFTkSuQmCC") 50% #0071bc}

`);

  // =========================================================================
  // Entry
  // =========================================================================
  init();
  // Handle SPA navigations in PubMed (Scholar uses full reloads, no polling needed)
  if (!isScholarPage()) {
    let lastHref = location.href;
    setInterval(() => {
      if (location.href !== lastHref) {
        lastHref = location.href;
        setTimeout(init, 400);
      }
    }, 800);
  }
})();