Google Scholar Author Highlighter Script

Highlights first-author, second-author, co-first, corresponding and last-author papers on Google Scholar profile pages and provides simple filters and metrics.

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 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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         Google Scholar Author Highlighter Script
// @namespace    https://controlnet.space/
// @version      2.1.1
// @description  Highlights first-author, second-author, co-first, corresponding and last-author papers on Google Scholar profile pages and provides simple filters and metrics.
// @author       ControlNet
// @include      https://scholar.google.*/citations?*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// @license      AGPL-3.0
// ==/UserScript==

/*
 * This userscript is adapted from the original Chrome extension
 * "Google Scholar Author Highlighter".
 *
 * It refactors the extension into a Tampermonkey userscript while preserving
 * the main highlighting, filtering, and metrics features.
 *
 * Credit for the original plugin and core implementation belongs to the
 * original author.
 */

(function () {
  'use strict';

  // Prevent double injection on dynamic navigations
  if (window.googleScholarAuthorHighlighterLoaded) {
    return;
  }
  window.googleScholarAuthorHighlighterLoaded = true;

  /**
   * ---------------------------------------------------------------------------
   *  Inject styles
   *
   *  The original extension ships a standalone CSS file that defines the
   *  colour scheme for each author position as well as styles for the control
   *  panel, histogram and modal.  We inline the contents here via GM_addStyle.
   */
  GM_addStyle(`
    .first-author { background-color: #ffffd9 !important; border-left: 4px solid #ffd700 !important; }
    .second-author { background-color: #fff0e6 !important; border-left: 4px solid #ff8c00 !important; }
    .co-first-author { background-color: #e6ffe6 !important; border-left: 4px solid #00cc00 !important; }
    .corresponding-author { background-color: #f3e5f5 !important; border-left: 4px solid #9c27b0 !important; }
    .last-author { background-color: #e6f3ff !important; border-right: 4px solid #4285f4 !important; }
    .first-author.last-author { background: linear-gradient(to right, #ffffd9 50%, #e6f3ff 50%) !important; border-left: 4px solid #ffd700 !important; border-right: 4px solid #4285f4 !important; }
    .second-author.last-author { background: linear-gradient(to right, #fff0e6 50%, #e6f3ff 50%) !important; border-left: 4px solid #ff8c00 !important; border-right: 4px solid #4285f4 !important; }
    .co-first-author.last-author { background: linear-gradient(to right, #e6ffe6 50%, #e6f3ff 50%) !important; border-left: 4px solid #00cc00 !important; border-right: 4px solid #4285f4 !important; }
    .corresponding-author.last-author { background: linear-gradient(to right, #f3e5f5 50%, #e6f3ff 50%) !important; border-left: 4px solid #9c27b0 !important; border-right: 4px solid #4285f4 !important; }
    .second-author.corresponding-author { background: linear-gradient(to right, #fff0e6 50%, #f3e5f5 50%) !important; border-left: 4px solid #9c27b0 !important; }
    .first-author.corresponding-author { background: linear-gradient(to right, #ffffd9 50%, #f3e5f5 50%) !important; border-left: 4px solid #9c27b0 !important; }
    .author-highlighter-controls { background-color: #f8f9fa; border: 1px solid #dadce0; border-radius: 8px; padding: 10px; margin-bottom: 16px; position: relative; z-index: 1000; font-size: 14px; line-height: 1.3; }
    .author-highlighter-controls h3 { margin: 0 0 6px 0; color: #1a73e8; font-size: 16px; display: flex; align-items: center; justify-content: space-between; }
    .title-container { display: flex; align-items: center; }
    .toggle-container, .filter-container { margin-top: 6px; }
    .toggle-container label, .filter-container>label { display: inline-block; margin-right: 6px; font-weight: bold; font-size: 13px; }
    .filter-container { display: flex; align-items: center; justify-content: space-between; }
    .filter-options { display: grid; grid-template-columns: max-content max-content max-content; gap: 8px 12px; align-items: center; margin-left: 6px; margin-right: 8px; }
    .filter-options div { display: flex; align-items: center; }
    .filter-options label { margin-left: 3px; font-size: 13px; white-space: nowrap; }
    #visibility-toggle, .filter-options input[type="checkbox"] { transform: scale(1.1); margin-right: 3px; }
    #gsc_prf_w { position: relative; }
    #gsc_prf_w>div:first-child { margin-bottom: 16px; }
    .highlighted-cited-by { background-color: #f8f9fa; border: 1px solid #dadce0; border-radius: 8px; padding: 10px; margin-top: 10px; font-size: 14px; }
    .highlighted-cited-by h4 { margin: 0 0 6px 0; color: #1a73e8; font-size: 16px; }
    .highlighted-cited-by p { margin: 4px 0; }
    .highlighted-metrics { margin-top: 10px; font-size: 14px; }
    .highlighted-metrics h4 { margin: 10px 0 6px 0; color: #1a73e8; font-size: 16px; }
    .metrics-grid { display: flex; flex-direction: column; gap: 4px; }
    .metric-item { display: flex; justify-content: space-between; align-items: center; }
    .metric-label { font-weight: bold; color: #5f6368; }
    .metric-value { font-weight: bold; color: #202124; margin-right: 8px; }
    .metrics-divider { border: none; border-top: 1px solid #dadce0; margin: 10px 0; }
    .highlighted-author-name { background-color: #ffff00 !important; font-weight: bold !important; }
    .info-icon { display: inline-block; margin-left: 5px; font-size: 13px; color: #4285f4; cursor: help; position: relative; vertical-align: text-bottom; font-style: normal; font-weight: normal; }
    .info-icon:hover { color: #1a73e8; }
    .info-icon::before { content: attr(data-tooltip); position: absolute; bottom: 150%; left: 50%; transform: translateX(-50%); background-color: #333; color: #fff; padding: 8px 14px; border-radius: 6px; white-space: pre; font-size: 13px; font-weight: normal; line-height: 1.5; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; z-index: 10000; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); text-align: left; min-width: 300px; }
    .info-icon:hover::before { opacity: 1; }
    .time-filter-container { margin-top: 6px; display: flex; align-items: center; }
    .time-filter-container label { margin-right: 6px; font-weight: bold; font-size: 13px; }
    .year-input { padding: 2px 4px; border: 1px solid #dadce0; border-radius: 4px; font-size: 13px; margin: 0 4px; height: 24px; background-color: white; }
    .year-input:focus { outline: none; border-color: #1a73e8; }
    .metric-total { font-size: 0.9em; color: #5f6368; font-weight: normal; margin-left: 4px; }
    /* Histogram styles */
    .metrics-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
    .metrics-header h4 { margin: 0; display: flex; align-items: center; flex: 1; }
    .histogram-emoji-btn { cursor: pointer; margin-left: auto; margin-right: 8px; color: #1a73e8; opacity: 0.7; transition: color 0.2s, opacity 0.2s; user-select: none; display: flex; align-items: center; }
    .histogram-emoji-btn:hover { color: #1558b0; opacity: 1; }
    .histogram-wrapper { margin-top: 10px; padding-top: 10px; padding-left: 15px; padding-right: 8px; border-top: 1px dotted #dadce0; }
    .histogram { display: flex; align-items: flex-end; height: 60px; gap: 2px; padding-bottom: 5px; border-bottom: 1px solid #dadce0; position: relative; }
    .histogram-bar-container { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; position: relative; }
    .histogram-bar { display: flex !important; flex-direction: column; background-color: transparent; border-radius: 1px 1px 0 0; min-width: 2px; width: 100%; overflow: hidden; }
    .histogram-segment { width: 100%; transition: background-color 0.1s; }
    .histogram-segment.normal { background-color: #dadce0; }
    .histogram-segment.highlighted { background-color: #bdc1c6; }
    .histogram-bar-container:hover .histogram-segment.normal { background-color: #bdc1c6; }
    .histogram-bar-container:hover .histogram-segment.highlighted { background-color: #9aa0a6; }
    .histogram-bar-container::after { content: attr(data-label); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background-color: #333; color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.1s ease; z-index: 10; margin-bottom: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
    .histogram-bar-container:hover::after { opacity: 1; }
    .histogram-axis { position: relative; height: 14px; width: 100%; margin-top: 4px; }
    .histogram-axis span { position: absolute; transform: translateX(-50%); font-size: 10px; color: #5f6368; white-space: nowrap; }
    .histogram-y-axis-line { position: absolute; left: 0; right: 0; border-top: 1px dashed #dadce0; pointer-events: none; z-index: 1; }
    .histogram-y-axis-label { position: absolute; left: -12px; transform: translateY(50%); font-size: 10px; color: #5f6368; line-height: 1; }
    .histogram-year-label { position: absolute; left: 50%; transform: translateX(-50%); font-size: 10px; color: #5f6368; white-space: nowrap; }
  `);

  /**
   * ---------------------------------------------------------------------------
   *  Storage Helper
   *
   *  The original extension uses chrome.storage.sync to persist settings.  In
   *  Tampermonkey we use GM_getValue/GM_setValue instead.  These functions are
   *  synchronous so we wrap them in the same asynchronous signature that
   *  chrome.storage exposed (callback with (result, error)).  Keys may be
   *  provided as a single string or an array of strings.
   */
  window.storageHelper = (function () {
    function get(keys, callback) {
      try {
        const result = {};
        if (Array.isArray(keys)) {
          keys.forEach((k) => {
            result[k] = GM_getValue(k);
          });
        } else if (typeof keys === 'object' && keys !== null) {
          // Tampermonkey does not support default objects the way chrome.storage does
          Object.keys(keys).forEach((k) => {
            result[k] = GM_getValue(k, keys[k]);
          });
        } else {
          result[keys] = GM_getValue(keys);
        }
        if (callback) callback(result, null);
      } catch (e) {
        console.warn('storageHelper.get error:', e);
        if (callback) callback(null, e);
      }
    }

    function set(items, callback) {
      try {
        Object.keys(items).forEach((k) => {
          GM_setValue(k, items[k]);
        });
        if (callback) callback(null);
      } catch (e) {
        console.warn('storageHelper.set error:', e);
        if (callback) callback(e);
      }
    }

    return { get, set };
  })();

  /**
   * ---------------------------------------------------------------------------
   *  Author Matcher
   *
   *  Utilities to normalise names, compute Levenshtein distance and find the
   *  best matching author position within a paper’s author list.  Copied
   *  directly from the original extension.
   */
  (function () {
    function normalizeProfileName(profileName) {
      const temp = profileName
        .toLowerCase()
        .normalize('NFD')
        .replace(/[\u0300-\u036f]/g, '')
        .replace(/[\-\.,()()]/g, ' ')
        .replace(/[\'’]/g, '')
        .replace(/[\*†‡§#✉]/g, '');
      return temp.replace(/\s+/g, '');
    }

    function levenshteinDistance(s1, s2) {
      if (s1.length < s2.length) return levenshteinDistance(s2, s1);
      if (s2.length === 0) return s1.length;
      const previousRow = Array.from({ length: s2.length + 1 }, (_, i) => i);
      for (let i = 0; i < s1.length; i++) {
        const currentRow = [i + 1];
        for (let j = 0; j < s2.length; j++) {
          const insertions = previousRow[j + 1] + 1;
          const deletions = currentRow[j] + 1;
          const substitutions = previousRow[j] + (s1[i] !== s2[j] ? 1 : 0);
          currentRow.push(Math.min(insertions, deletions, substitutions));
        }
        previousRow.splice(0, previousRow.length, ...currentRow);
      }
      return previousRow[s2.length];
    }

    function getAuthorSymbol(author) {
      const symbols = '*†‡§#✉';
      for (const symbol of symbols) {
        if (author.includes(symbol)) return symbol;
      }
      return '';
    }

    const isCoFirstAuthor = getAuthorSymbol;

    function isCharSubset(candidate, profile) {
      if (!candidate) return true;
      if (!profile && candidate) return false;
      if (!profile && !candidate) return true;
      const counts = {};
      for (const ch of profile) counts[ch] = (counts[ch] || 0) + 1;
      for (const ch of candidate) {
        if (!counts[ch] || counts[ch] === 0) return false;
        counts[ch]--;
      }
      return true;
    }

    function extractFamilyNamePart(authorString) {
      const cleaned = authorString.toLowerCase()
        .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
        .replace(/[\-\.,()()]/g, ' ')
        .replace(/[\'’]/g, '')
        .replace(/[\*†‡§#✉]/g, '')
        .trim();
      const parts = cleaned.split(/\s+/).filter(p => p.length > 0);
      if (parts.length === 0) return '';
      return parts[parts.length - 1];
    }

    function extractProfileNameParts(profileName) {
      return profileName
        .toLowerCase()
        .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
        .replace(/[\-\.,()()]/g, ' ')
        .replace(/[\'’]/g, '')
        .replace(/[\*†‡§#✉]/g, '')
        .split(/\s+/)
        .filter(p => p.length > 0);
    }

    function getMatchScore(authorStringFromList, localFullyCleanedProfileName, profileName) {
      const tempAuthor = authorStringFromList.toLowerCase()
        .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
        .replace(/[\-\.,()()]/g, ' ')
        .replace(/[\'’]/g, '')
        .replace(/[\*†‡§#✉]/g, '');
      const candidate = tempAuthor.replace(/\s+/g, '');
      if (candidate === '') return Infinity;
      if (!isCharSubset(candidate, localFullyCleanedProfileName)) return Infinity;
      const familyNamePart = extractFamilyNamePart(authorStringFromList);
      let hasContiguousFamilyName = false;
      if (familyNamePart.length > 1) {
        if (profileName) {
          const profileParts = extractProfileNameParts(profileName);
          if (!profileParts.includes(familyNamePart)) return Infinity;
          hasContiguousFamilyName = true;
        } else {
          hasContiguousFamilyName = localFullyCleanedProfileName.includes(familyNamePart);
          if (!hasContiguousFamilyName) return Infinity;
        }
      }
      let score = levenshteinDistance(candidate, localFullyCleanedProfileName);
      if (candidate.length > 0 && localFullyCleanedProfileName.length > 0) {
        if (candidate[0] === localFullyCleanedProfileName[0]) score -= 0.5;
      }
      if (hasContiguousFamilyName && candidate.length > familyNamePart.length) {
        const index = localFullyCleanedProfileName.indexOf(familyNamePart);
        const positionAfterFamilyName = index + familyNamePart.length;
        const initialsInCandidate = candidate.substring(0, candidate.length - familyNamePart.length);
        if (initialsInCandidate.length > 0 && positionAfterFamilyName < localFullyCleanedProfileName.length && initialsInCandidate[0] === localFullyCleanedProfileName[positionAfterFamilyName]) {
          score -= 1.0;
        }
      }
      return score;
    }

    function findBestMatch(authorList, fullyCleanedProfileName, profileName, otherNames = []) {
      let bestMatchIndex = -1;
      let minScore = Infinity;
      let actualMatchedAuthorString = '';
      authorList.forEach((currentAuthorName, index) => {
        let score = getMatchScore(currentAuthorName, fullyCleanedProfileName, profileName);
        if (otherNames && otherNames.length > 0) {
          otherNames.forEach((alias) => {
            const aliasCleaned = normalizeProfileName(alias);
            const aliasScore = getMatchScore(currentAuthorName, aliasCleaned, alias);
            if (aliasScore < score) score = aliasScore;
          });
        }
        if (score < minScore) {
          minScore = score;
          bestMatchIndex = index;
          actualMatchedAuthorString = currentAuthorName;
        }
      });
      return {
        matchIndex: bestMatchIndex,
        matchedAuthor: actualMatchedAuthorString,
        score: minScore
      };
    }

    window.authorMatcher = {
      normalizeProfileName,
      levenshteinDistance,
      isCoFirstAuthor,
      getAuthorSymbol,
      isCharSubset,
      getMatchScore,
      findBestMatch
    };
  })();

  /**
   * ---------------------------------------------------------------------------
   *  Highlighter Module
   *
   *  This module tags each paper row with data attributes describing whether the
   *  logged‑in user is first author, second author, co‑first author, corresponding
   *  author or last author.  It then invokes a callback so filters can be
   *  applied.  Taken directly from the original extension.
   */
  (function () {
    function highlightAuthors(papers, onComplete) {
      const fullNameElement = document.querySelector('#gsc_prf_in');
      if (!fullNameElement) {
        console.error('Google Scholar Author Highlighter: Could not find author name element');
        return;
      }
      const profileNameOriginal = fullNameElement.textContent;
      const fullyCleanedProfileName = window.authorMatcher.normalizeProfileName(profileNameOriginal);
      // helper to extract alternative names
      function extractOtherNames() {
        const aliases = [];
        const infoRows = document.querySelectorAll('.gsc_prf_il');
        infoRows.forEach((row) => {
          const text = row.textContent.trim();
          const labelMatch = text.match(/^(also published as|also known as|alternative names|别名|曾用名)[:\s]+(.*)/i);
          if (labelMatch && labelMatch[2]) {
            const names = labelMatch[2].split(',');
            names.forEach((name) => {
              const cleanName = name.trim();
              if (cleanName) aliases.push(cleanName);
            });
          }
        });
        const otherNamesElement = document.querySelector('#gs_prf_ion_txt');
        if (otherNamesElement) {
          const text = otherNamesElement.textContent.trim();
          if (text) {
            const names = text.split(',');
            names.forEach((name) => {
              const cleanName = name.trim();
              if (cleanName) aliases.push(cleanName);
            });
          }
        }
        return aliases;
      }
      const otherNames = extractOtherNames();
      papers.forEach((paper) => {
        if (paper.dataset.processed) return;
        const authorsTextNode = paper.querySelector('.gs_gray');
        const authorsText = authorsTextNode ? authorsTextNode.textContent : '';
        const authorList = authorsText.split(',').map((a) => a.trim()).filter((a) => a.length > 0 && a !== '...');
        let isFirst = false;
        let isSecond = false;
        let isCoFirst = false;
        let isLast = false;
        let isCorresponding = false;
        if (authorList.length > 0) {
          const matchResult = window.authorMatcher.findBestMatch(authorList, fullyCleanedProfileName, profileNameOriginal, otherNames);
          const bestIndex = matchResult.matchIndex;
          const actualMatchedAuthorString = matchResult.matchedAuthor;
          const minScore = matchResult.score;
          const includesEllipsis = authorsText.includes('...');
          if (minScore !== Infinity) {
            const authorSymbol = window.authorMatcher.getAuthorSymbol(actualMatchedAuthorString);
            let qualifiesCoFirst = false;
            if (authorSymbol) {
              let allPrecedingHaveSameSymbol = true;
              if (bestIndex > 0) {
                for (let j = 0; j < bestIndex; j++) {
                  if (!authorList[j].includes(authorSymbol)) {
                    allPrecedingHaveSameSymbol = false;
                    break;
                  }
                }
              }
              if (allPrecedingHaveSameSymbol) {
                qualifiesCoFirst = true;
              } else {
                isCorresponding = true;
              }
            }
            if (qualifiesCoFirst) {
              isCoFirst = true;
            } else {
              if (bestIndex === 0) isFirst = true;
              if (bestIndex === 1) isSecond = true;
              if (!includesEllipsis && bestIndex === authorList.length - 1) isLast = true;
            }
          }
        }
        paper.dataset.isFirstAuthor = isFirst;
        paper.dataset.isSecondAuthor = isSecond;
        paper.dataset.isCoFirstAuthor = isCoFirst;
        paper.dataset.isLastAuthor = isLast;
        paper.dataset.isCorrespondingAuthor = isCorresponding;
        paper.dataset.processed = 'true';
      });
      if (onComplete) onComplete();
    }
    window.highlighterModule = { highlightAuthors };
  })();

  /**
   * ---------------------------------------------------------------------------
   *  Filter Manager
   *
   *  Applies filters to the paper list based on which author positions should be
   *  highlighted and whether to show only highlighted papers.  It also handles
   *  year filtering.  Adapted from the original extension.
   */
  (function () {
    function getYearFromPaper(paper) {
      const yearEl = paper.querySelector('.gsc_a_y');
      if (!yearEl) return null;
      const text = yearEl.textContent.trim();
      if (!text) return null;
      const year = parseInt(text, 10);
      return isNaN(year) ? null : year;
    }

    function applyFilters(filters, yearFilter) {
      if (!yearFilter) {
        const startEl = document.getElementById('start-year-input');
        const endEl = document.getElementById('end-year-input');
        yearFilter = {
          start: startEl ? startEl.value : '',
          end: endEl ? endEl.value : ''
        };
      }
      const papers = document.querySelectorAll('.gsc_a_tr');
      papers.forEach((paper) => {
        paper.classList.remove('highlighted-author', 'first-author', 'second-author', 'co-first-author', 'corresponding-author', 'last-author');
        if (filters.first && paper.dataset.isFirstAuthor === 'true') {
          paper.classList.add('first-author', 'highlighted-author');
        }
        if (filters.second && paper.dataset.isSecondAuthor === 'true') {
          paper.classList.add('second-author', 'highlighted-author');
        }
        if (filters.coFirst && paper.dataset.isCoFirstAuthor === 'true') {
          paper.classList.add('co-first-author', 'highlighted-author');
        }
        if (filters.corresponding && paper.dataset.isCorrespondingAuthor === 'true') {
          paper.classList.add('corresponding-author', 'highlighted-author');
        }
        if (filters.last && paper.dataset.isLastAuthor === 'true') {
          paper.classList.add('last-author', 'highlighted-author');
        }
      });
      const visibilityToggle = document.getElementById('visibility-toggle');
      if (visibilityToggle) {
        togglePaperVisibility(visibilityToggle.checked, yearFilter);
      } else {
        window.storageHelper.get(['showOnlyHighlighted'], function (result, error) {
          const showOnlyHighlighted = (error || !result.showOnlyHighlighted) ? false : result.showOnlyHighlighted;
          togglePaperVisibility(showOnlyHighlighted, yearFilter);
        });
      }
      window.metricsCalculator.displayHighlightedMetrics();
    }

    function togglePaperVisibility(showOnlyHighlighted, yearFilter) {
      if (!yearFilter) {
        const startEl = document.getElementById('start-year-input');
        const endEl = document.getElementById('end-year-input');
        yearFilter = {
          start: startEl ? startEl.value : '',
          end: endEl ? endEl.value : ''
        };
      }
      const startYear = yearFilter.start ? parseInt(yearFilter.start, 10) : null;
      const endYear = yearFilter.end ? parseInt(yearFilter.end, 10) : null;
      const papers = document.querySelectorAll('.gsc_a_tr');
      papers.forEach((paper) => {
        let visible = true;
        if (showOnlyHighlighted && !paper.classList.contains('highlighted-author')) visible = false;
        if (visible) {
          const paperYear = getYearFromPaper(paper);
          if (startYear !== null || endYear !== null) {
            if (paperYear === null) {
              visible = false;
            } else {
              if (startYear !== null && paperYear < startYear) visible = false;
              if (endYear !== null && paperYear > endYear) visible = false;
            }
          }
        }
        paper.style.display = visible ? '' : 'none';
      });
      window.metricsCalculator.displayHighlightedMetrics();
    }

    function updateFilters() {
      const filters = {
        first: document.getElementById('first-author-filter').checked,
        second: document.getElementById('second-author-filter').checked,
        coFirst: document.getElementById('co-first-author-filter').checked,
        corresponding: document.getElementById('corresponding-author-filter').checked,
        last: document.getElementById('last-author-filter').checked
      };
      const yearFilter = {
        start: document.getElementById('start-year-input').value,
        end: document.getElementById('end-year-input').value
      };
      try {
        sessionStorage.setItem('authorHighlighterYearFilter', JSON.stringify(yearFilter));
      } catch (e) {
        console.warn('Error saving year filter to sessionStorage:', e);
      }
      window.storageHelper.set({ filters: filters }, function (error) {
        applyFilters(filters, yearFilter);
      });
    }

    window.filterManager = { applyFilters, togglePaperVisibility, updateFilters };
  })();

  /**
   * ---------------------------------------------------------------------------
   *  Name Highlighter
   *
   *  Adds an additional highlight to the user’s name within the author list of
   *  each paper.  The state of this toggle is stored via storageHelper.
   */
  (function () {
    function removeHighlighting(authorsTextNode) {
      const existingHighlight = authorsTextNode.querySelector('.highlighted-author-name');
      if (existingHighlight) authorsTextNode.innerHTML = authorsTextNode.textContent;
    }
    function escapeRegex(str) {
      return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }
    function applyHighlighting(authorsTextNode, fullyCleanedProfileName, profileNameOriginal, otherNames) {
      const authorsText = authorsTextNode.textContent;
      const authorList = authorsText.split(',').map((a) => a.trim()).filter((a) => a.length > 0 && a !== '...');
      const matchResult = window.authorMatcher.findBestMatch(authorList, fullyCleanedProfileName, profileNameOriginal, otherNames);
      if (matchResult.score !== Infinity && matchResult.matchedAuthor) {
        const matchedAuthor = matchResult.matchedAuthor;
        const escapedAuthor = escapeRegex(matchedAuthor);
        const regex = new RegExp(`(${escapedAuthor})`, 'gi');
        const highlightedHTML = authorsText.replace(regex, '<span class="highlighted-author-name">$1</span>');
        authorsTextNode.innerHTML = highlightedHTML;
      }
    }
    function highlightAuthorNames(enabled) {
      const fullNameElement = document.querySelector('#gsc_prf_in');
      if (!fullNameElement) {
        console.error('Google Scholar Author Highlighter: Could not find author name element');
        return;
      }
      const profileNameOriginal = fullNameElement.textContent;
      const fullyCleanedProfileName = window.authorMatcher.normalizeProfileName(profileNameOriginal);
      function extractOtherNames() {
        const aliases = [];
        const infoRows = document.querySelectorAll('.gsc_prf_il');
        infoRows.forEach((row) => {
          const text = row.textContent.trim();
          const labelMatch = text.match(/^(also published as|also known as|alternative names|别名|曾用名)[:\s]+(.*)/i);
          if (labelMatch && labelMatch[2]) {
            const names = labelMatch[2].split(',');
            names.forEach((name) => {
              const cleanName = name.trim();
              if (cleanName) aliases.push(cleanName);
            });
          }
        });
        const otherNamesElement = document.querySelector('#gs_prf_ion_txt');
        if (otherNamesElement) {
          const text = otherNamesElement.textContent.trim();
          if (text) {
            const names = text.split(',');
            names.forEach((name) => {
              const cleanName = name.trim();
              if (cleanName) aliases.push(cleanName);
            });
          }
        }
        return aliases;
      }
      const otherNames = extractOtherNames();
      const papers = document.querySelectorAll('.gsc_a_tr');
      papers.forEach((paper) => {
        const authorsTextNode = paper.querySelector('.gs_gray');
        if (!authorsTextNode) return;
        removeHighlighting(authorsTextNode);
        if (!enabled) return;
        applyHighlighting(authorsTextNode, fullyCleanedProfileName, profileNameOriginal, otherNames);
      });
    }
    window.nameHighlighter = { highlightAuthorNames };
  })();

  /**
   * ---------------------------------------------------------------------------
   *  DOM Observer
   *
   *  Watches the page for the profile container and newly added papers.  When
   *  additional papers load (for example when clicking “Show more”), it invokes
   *  the highlighter again.  Derived from the original extension.
   */
  (function () {
    function debounce(func, wait) {
      let timeout;
      return function executedFunction(...args) {
        const later = () => {
          clearTimeout(timeout);
          func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
      };
    }
    function observeInitialLoad(onProfileFound) {
      const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
          if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
            const profileContainer = document.querySelector('#gsc_prf_w');
            if (profileContainer && !document.getElementById('author-highlighter-controls')) {
              onProfileFound();
              observer.disconnect();
              break;
            }
          }
        }
      });
      observer.observe(document.body, { childList: true, subtree: true });
      return observer;
    }
    function observePaperChanges(onPapersChanged) {
      const paperContainer = document.querySelector('#gsc_a_b');
      if (paperContainer) {
        const debouncedHandler = debounce(onPapersChanged, 150);
        const paperObserver = new MutationObserver((mutations) => {
          const addedNodes = [];
          for (const mutation of mutations) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
              mutation.addedNodes.forEach((node) => {
                if (node.nodeType === Node.ELEMENT_NODE && node.matches('.gsc_a_tr')) {
                  addedNodes.push(node);
                }
              });
            }
          }
          if (addedNodes.length > 0) debouncedHandler(addedNodes);
        });
        paperObserver.observe(paperContainer, { childList: true });
        return paperObserver;
      }
      return null;
    }
    window.domObserver = { observeInitialLoad, observePaperChanges };
  })();

  /**
   * ---------------------------------------------------------------------------
   *  Metrics Calculator
   *
   *  Computes summary metrics (count, citations, h‑index and i10‑index) for
   *  highlighted papers versus the total set and optionally draws a histogram
   *  of highlighted vs non‑highlighted papers per year.  Adapted from the
   *  original extension.
   */
  (function () {
    let isHistogramVisible = false;
    function calculateMetrics() {
      const papers = document.querySelectorAll('.gsc_a_tr');
      const startEl = document.getElementById('start-year-input');
      const endEl = document.getElementById('end-year-input');
      const startYear = startEl && startEl.value ? parseInt(startEl.value, 10) : null;
      const endYear = endEl && endEl.value ? parseInt(endEl.value, 10) : null;
      const stats = {
        total: { count: 0, citations: 0, citationCounts: [], years: {} },
        highlighted: { count: 0, citations: 0, citationCounts: [], years: {} }
      };
      papers.forEach((paper) => {
        let paperYear = null;
        const yearEl = paper.querySelector('.gsc_a_y');
        if (yearEl) {
          const yearText = yearEl.textContent.trim();
          if (yearText) {
            const parsed = parseInt(yearText, 10);
            if (!isNaN(parsed)) paperYear = parsed;
          }
        }
        let inYearRange = true;
        if (startYear !== null || endYear !== null) {
          if (paperYear === null) {
            inYearRange = false;
          } else {
            if (startYear !== null && paperYear < startYear) inYearRange = false;
            if (endYear !== null && paperYear > endYear) inYearRange = false;
          }
        }
        if (!inYearRange) return;
        let citationCount = 0;
        const citationElement = paper.querySelector('.gsc_a_ac');
        if (citationElement) {
          const text = citationElement.textContent.trim();
          const parsed = parseInt(text, 10);
          if (!isNaN(parsed)) citationCount = parsed;
        }
        stats.total.count++;
        stats.total.citations += citationCount;
        stats.total.citationCounts.push(citationCount);
        if (paperYear !== null) {
          stats.total.years[paperYear] = (stats.total.years[paperYear] || 0) + 1;
        }
        if (paper.classList.contains('highlighted-author')) {
          stats.highlighted.count++;
          stats.highlighted.citations += citationCount;
          stats.highlighted.citationCounts.push(citationCount);
          if (paperYear !== null) {
            stats.highlighted.years[paperYear] = (stats.highlighted.years[paperYear] || 0) + 1;
          }
        }
      });
      const calcIndices = (citations) => {
        citations.sort((a, b) => b - a);
        const hIndex = citations.filter((c, i) => c > i).length;
        const i10index = citations.filter((c) => c >= 10).length;
        return { hIndex, i10index };
      };
      const totalIndices = calcIndices(stats.total.citationCounts);
      const highlightedIndices = calcIndices(stats.highlighted.citationCounts);
      return {
        total: {
          count: stats.total.count,
          citations: stats.total.citations,
          hIndex: totalIndices.hIndex,
          i10index: totalIndices.i10index,
          years: stats.total.years
        },
        highlighted: {
          count: stats.highlighted.count,
          citations: stats.highlighted.citations,
          hIndex: highlightedIndices.hIndex,
          i10index: highlightedIndices.i10index,
          years: stats.highlighted.years
        }
      };
    }
    function displayHighlightedMetrics() {
      const metrics = calculateMetrics();
      let metricsComponent = document.getElementById('highlighted-metrics');
      if (!metricsComponent) {
        metricsComponent = document.createElement('div');
        metricsComponent.id = 'highlighted-metrics';
        metricsComponent.className = 'highlighted-metrics';
        const controlsContainer = document.getElementById('author-highlighter-controls');
        if (controlsContainer) controlsContainer.appendChild(metricsComponent);
      }
      // prepare histogram
      const totalYearCounts = metrics.total.years;
      const highlightedYearCounts = metrics.highlighted.years;
      const years = Object.keys(totalYearCounts).map((y) => parseInt(y, 10)).filter((y) => y >= 1980).sort((a, b) => a - b);
      let histogramHTML = '';
      if (years.length > 0) {
        const minYear = years[0];
        const maxYear = years[years.length - 1];
        const maxCount = Math.max(...years.map((y) => totalYearCounts[y] || 0));
        const safeMaxCount = maxCount > 0 ? maxCount : 1;
        let barsHTML = '';
        for (let y = minYear; y <= maxYear; y++) {
          const totalCount = totalYearCounts[y] || 0;
          const highlightedCount = highlightedYearCounts[y] || 0;
          const normalCount = totalCount - highlightedCount;
          const totalHeightPercent = (totalCount / safeMaxCount) * 100;
          const highlightedHeightPercent = totalCount > 0 ? (highlightedCount / totalCount) * 100 : 0;
          const normalHeightPercent = totalCount > 0 ? (normalCount / totalCount) * 100 : 0;
          barsHTML += `
              <div class="histogram-bar-container" data-label="${highlightedCount} of ${totalCount} (${y})">
                  <div class="histogram-bar" style="height: ${totalHeightPercent}%">
                      <div class="histogram-segment normal" style="height: ${normalHeightPercent}%"></div>
                      <div class="histogram-segment highlighted" style="height: ${highlightedHeightPercent}%"></div>
                  </div>
              </div>`;
        }
        const displayStyle = isHistogramVisible ? 'block' : 'none';
        const totalYears = maxYear - minYear + 1;
        const getPosPercent = (year) => {
          const index = year - minYear;
          return (((index + 0.5) / totalYears) * 100).toFixed(2);
        };
        let axisHTML = '';
        const range = maxYear - minYear;
        let numLabels = 5;
        if (range <= 8) numLabels = 3; else if (range <= 16) numLabels = 4;
        const labelYears = new Set();
        labelYears.add(minYear);
        labelYears.add(maxYear);
        if (maxYear - minYear > 1) {
          const step = (maxYear - minYear) / (numLabels - 1);
          for (let i = 1; i < numLabels - 1; i++) {
            labelYears.add(Math.round(minYear + step * i));
          }
        }
        const sortedLabelYears = Array.from(labelYears).sort((a, b) => a - b);
        sortedLabelYears.forEach((year) => {
          axisHTML += `<span style="left: ${getPosPercent(year)}%">${year}</span>`;
        });
        histogramHTML = `
            <div class="histogram-wrapper" id="histogram-wrapper" style="display: ${displayStyle}">
                <div class="histogram">
                    ${barsHTML}
                    ${maxCount >= 1 ? (() => { const midValue = Math.max(1, Math.round(maxCount / 2)); return `
                        <div class="histogram-y-axis-line" style="bottom: ${(midValue / safeMaxCount) * 100}%"></div>
                        <div class="histogram-y-axis-label" style="bottom: ${(midValue / safeMaxCount) * 100}%">${midValue}</div>
                    `; })() : ''}
                </div>
                <div class="histogram-axis">
                    ${axisHTML}
                </div>
            </div>`;
      }
      metricsComponent.innerHTML = `
        <hr class="metrics-divider">
        <div class="metrics-header">
            <h4>Highlighted Papers Metrics <span id="histogram-toggle-icon" class="histogram-emoji-btn" title="Show papers by year"><svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 24 24" width="20" fill="currentColor"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM7 10h2v7H7zm4-3h2v10h-2zm4 6h2v4h-2z"/></svg></span></h4>
        </div>
        <div class="metrics-grid">
          <div class="metric-item">
            <span class="metric-label">Papers:</span>
            <span class="metric-value">${metrics.highlighted.count} <span class="metric-total">of ${metrics.total.count}</span></span>
          </div>
          <div class="metric-item">
            <span class="metric-label">Citations:</span>
            <span class="metric-value">${metrics.highlighted.citations} <span class="metric-total">of ${metrics.total.citations}</span></span>
          </div>
          <div class="metric-item">
            <span class="metric-label">h-index:</span>
            <span class="metric-value">${metrics.highlighted.hIndex} <span class="metric-total">of ${metrics.total.hIndex}</span></span>
          </div>
          <div class="metric-item">
            <span class="metric-label">i10-index:</span>
            <span class="metric-value">${metrics.highlighted.i10index} <span class="metric-total">of ${metrics.total.i10index}</span></span>
          </div>
        </div>
        ${histogramHTML}
      `;
      const toggleBtn = document.getElementById('histogram-toggle-icon');
      if (toggleBtn) {
        toggleBtn.addEventListener('click', function () {
          const wrapper = document.getElementById('histogram-wrapper');
          isHistogramVisible = !isHistogramVisible;
          if (wrapper) wrapper.style.display = isHistogramVisible ? 'block' : 'none';
        });
      }
    }
    window.metricsCalculator = { displayHighlightedMetrics };
  })();

  /**
   * ---------------------------------------------------------------------------
   *  UI Manager
   *
   *  Injects the control panel into the profile page and wires up event
   *  listeners for all toggles and filters.  It also populates the year
   *  dropdowns based on the available papers.  Adapted from the original
   *  extension with minor adjustments for Tampermonkey storage.
   */
  (function () {
    function populateYearDropdowns() {
      const startSelect = document.getElementById('start-year-input');
      const endSelect = document.getElementById('end-year-input');
      if (!startSelect || !endSelect) return;
      const currentStart = startSelect.value;
      const currentEnd = endSelect.value;
      while (startSelect.options.length > 1) startSelect.remove(1);
      while (endSelect.options.length > 1) endSelect.remove(1);
      const papers = document.querySelectorAll('.gsc_a_tr');
      const years = new Set();
      if (currentStart) years.add(parseInt(currentStart, 10));
      if (currentEnd) years.add(parseInt(currentEnd, 10));
      papers.forEach((paper) => {
        const yearEl = paper.querySelector('.gsc_a_y');
        if (yearEl) {
          const yearText = yearEl.textContent.trim();
          const year = parseInt(yearText, 10);
          if (!isNaN(year)) years.add(year);
        }
      });
      const sortedYears = Array.from(years).sort((a, b) => b - a);
      const createOption = (val) => {
        const opt = document.createElement('option');
        opt.value = val;
        opt.textContent = val;
        return opt;
      };
      sortedYears.forEach((year) => {
        startSelect.appendChild(createOption(year));
        endSelect.appendChild(createOption(year));
      });
      if (currentStart) startSelect.value = currentStart;
      if (currentEnd) endSelect.value = currentEnd;
    }
    function ensureOption(selectElement, value) {
      if (!value) return;
      let exists = false;
      for (let i = 0; i < selectElement.options.length; i++) {
        if (selectElement.options[i].value === value.toString()) {
          exists = true;
          break;
        }
      }
      if (!exists) {
        const opt = document.createElement('option');
        opt.value = value;
        opt.textContent = value;
        selectElement.appendChild(opt);
      }
    }
    function setupEventListeners(initialHighlightNameState, highlightNameCallback) {
      const visibilityToggle = document.getElementById('visibility-toggle');
      const highlightNameToggle = document.getElementById('highlight-name-toggle');
      const firstAuthorFilter = document.getElementById('first-author-filter');
      const secondAuthorFilter = document.getElementById('second-author-filter');
      const coFirstAuthorFilter = document.getElementById('co-first-author-filter');
      const correspondingAuthorFilter = document.getElementById('corresponding-author-filter');
      const lastAuthorFilter = document.getElementById('last-author-filter');
      const startYearInput = document.getElementById('start-year-input');
      const endYearInput = document.getElementById('end-year-input');
      visibilityToggle.addEventListener('change', function () {
        const showOnlyHighlighted = this.checked;
        window.storageHelper.set({ showOnlyHighlighted: showOnlyHighlighted });
        window.filterManager.togglePaperVisibility(showOnlyHighlighted);
      });
      startYearInput.addEventListener('change', window.filterManager.updateFilters);
      endYearInput.addEventListener('change', window.filterManager.updateFilters);
      highlightNameToggle.addEventListener('change', function () {
        const highlightName = this.checked;
        if (highlightNameCallback) highlightNameCallback(highlightName);
      });
      [firstAuthorFilter, secondAuthorFilter, coFirstAuthorFilter, correspondingAuthorFilter, lastAuthorFilter].forEach((filter) => {
        filter.addEventListener('change', window.filterManager.updateFilters);
      });
      window.storageHelper.get(['showOnlyHighlighted', 'filters'], function (result, error) {
        if (error) {
          visibilityToggle.checked = false;
          const filters = { first: true, second: true, coFirst: true, corresponding: true, last: true };
          firstAuthorFilter.checked = filters.first;
          secondAuthorFilter.checked = filters.second;
          coFirstAuthorFilter.checked = filters.coFirst;
          correspondingAuthorFilter.checked = filters.corresponding;
          lastAuthorFilter.checked = filters.last;
          window.filterManager.applyFilters(filters);
        } else {
          visibilityToggle.checked = result.showOnlyHighlighted || false;
          const filters = result.filters || { first: true, second: true, coFirst: true, corresponding: true, last: true };
          firstAuthorFilter.checked = filters.first;
          secondAuthorFilter.checked = filters.second;
          coFirstAuthorFilter.checked = filters.coFirst;
          correspondingAuthorFilter.checked = filters.corresponding !== undefined ? filters.corresponding : true;
          lastAuthorFilter.checked = filters.last;
          window.filterManager.applyFilters(filters);
          try {
            const storedYearFilter = sessionStorage.getItem('authorHighlighterYearFilter');
            if (storedYearFilter) {
              const yearFilter = JSON.parse(storedYearFilter);
              if (yearFilter.start) ensureOption(startYearInput, yearFilter.start);
              if (yearFilter.end) ensureOption(endYearInput, yearFilter.end);
              startYearInput.value = yearFilter.start || '';
              endYearInput.value = yearFilter.end || '';
              window.filterManager.applyFilters(filters, yearFilter);
            }
          } catch (e) {
            console.warn('Error loading year filter from sessionStorage:', e);
          }
        }
        highlightNameToggle.checked = initialHighlightNameState;
        window.nameHighlighter.highlightAuthorNames(initialHighlightNameState);
      });
    }
    function injectFilterUI(initialHighlightNameState, highlightNameCallback) {
      if (document.getElementById('author-highlighter-controls')) return;
      const filterHTML = `
        <div id="author-highlighter-controls" class="author-highlighter-controls">
          <h3><span class="title-container">Author Highlighter<span class="info-icon" data-tooltip="Co-first: Beginning authors marked with symbols (* † ‡ § # ✉).
Corresponding: Non-beginning authors marked with symbols (* † ‡ § # ✉)."><svg focusable="false" width="16" height="16" viewBox="0 0 24 24" style="fill: currentColor"><path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"></path></svg></span></span></h3>
          <div class="filter-container">
            <label>Highlight:</label>
            <div class="filter-options">
              <div>
                <input type="checkbox" id="first-author-filter" checked>
                <label for="first-author-filter">First</label>
              </div>
              <div>
                <input type="checkbox" id="co-first-author-filter" checked>
                <label for="co-first-author-filter">Co-First</label>
              </div>
              <div>
                <input type="checkbox" id="second-author-filter" checked>
                <label for="second-author-filter">Second</label>
              </div>
              <div>
                <input type="checkbox" id="corresponding-author-filter" checked>
                <label for="corresponding-author-filter">Corr.</label>
              </div>
              <div>
                <input type="checkbox" id="last-author-filter" checked>
                <label for="last-author-filter">Last</label>
              </div>
            </div>
          </div>
          <div class="toggle-container">
            <label for="visibility-toggle">Show only highlighted:</label>
            <input type="checkbox" id="visibility-toggle">
          </div>
          <div class="toggle-container">
            <label for="highlight-name-toggle">Highlight author name:</label>
            <input type="checkbox" id="highlight-name-toggle">
          </div>
          <div class="time-filter-container">
            <label>Year:</label>
            <select id="start-year-input" class="year-input">
              <option value="">Start</option>
            </select>
            <span>-</span>
            <select id="end-year-input" class="year-input">
              <option value="">End</option>
            </select>
          </div>
        </div>`;
      const citedBySection = document.querySelector('#gsc_rsb_cit');
      if (citedBySection) {
        const controlsContainer = document.createElement('div');
        controlsContainer.innerHTML = filterHTML;
        citedBySection.parentNode.insertBefore(controlsContainer, citedBySection);
        populateYearDropdowns();
        setupEventListeners(initialHighlightNameState, highlightNameCallback);
      }
    }
    window.uiManager = { injectFilterUI, populateYearDropdowns };
  })();

  /**
   * ---------------------------------------------------------------------------
   *  Show More Clicker
   *
   *  Google Scholar paginates long author lists behind a “Show more” button.  To
   *  ensure all papers are processed we simulate clicking this button until
   *  everything is loaded.  Adapted from the original extension but simplified
   *  slightly.  Debug logging has been removed for clarity.
   */
  (function () {
    let retryCount = 0;
    const MAX_RETRIES = 10;
    function clickShowMoreUntilAllLoaded() {
      const possibleSelectors = [
        '#gsc_bpf_more', '.gsc_pgn_pnx', 'button[onclick*="showMore"]', 'button[onclick*="bpf_more"]', '.gs_btnPR[onclick*="showMore"]', '[data-i18n="show_more"]', '.gsc_pgn button', '#gsc_pgn_pnx'
      ];
      let showMoreButton = null;
      let foundSelector = null;
      for (const selector of possibleSelectors) {
        if (selector.includes(':contains(')) {
          const text = selector.match(/:contains\("(.+)"\)/)?.[1];
          if (text) {
            const buttons = document.querySelectorAll('button');
            for (const btn of buttons) {
              if (btn.textContent.trim().toLowerCase().includes(text.toLowerCase())) {
                showMoreButton = btn;
                foundSelector = `button containing "${text}"`;
                break;
              }
            }
          }
        } else {
          showMoreButton = document.querySelector(selector);
          if (showMoreButton) {
            foundSelector = selector;
            break;
          }
        }
      }
      if (showMoreButton) {
        const style = window.getComputedStyle(showMoreButton);
        const isClickable = !showMoreButton.disabled && style.display !== 'none' && style.visibility !== 'hidden';
        if (isClickable) {
          try { showMoreButton.click(); } catch (e) {
            try { showMoreButton.dispatchEvent(new MouseEvent('click', { bubbles: true })); } catch (e2) {
              if (showMoreButton.onclick) showMoreButton.onclick.call(showMoreButton);
            }
          }
          retryCount++;
          setTimeout(() => { clickShowMoreUntilAllLoaded(); }, 2000);
        } else {
          finishLoading();
        }
      } else {
        const paperCount = document.querySelectorAll('.gsc_a_tr').length;
        if (retryCount < MAX_RETRIES && paperCount < 20) {
          retryCount++;
          setTimeout(() => { clickShowMoreUntilAllLoaded(); }, 1000);
        } else {
          finishLoading();
        }
      }
    }
    function finishLoading() {
      retryCount = 0;
      if (typeof window.highlightAuthors === 'function') {
        window.highlightAuthors();
      }
    }
    function waitForPageReady() {
      const paperContainer = document.querySelector('#gsc_a_b, .gsc_a_tr');
      if (paperContainer) {
        setTimeout(() => { clickShowMoreUntilAllLoaded(); }, 1000);
      } else {
        setTimeout(waitForPageReady, 500);
      }
    }
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', waitForPageReady);
    } else {
      waitForPageReady();
    }
    window.addEventListener('load', () => {
      setTimeout(waitForPageReady, 1000);
    });
  })();

  /**
   * ---------------------------------------------------------------------------
   *  Main Orchestrator
   *
   *  Coordinates all modules: injects the UI, applies highlights and filters
   *  when the page is ready, observes newly loaded papers and keeps state in
   *  sync with storage.  Derived from the original extension.
   */
  (function () {
    let highlightNameStateForThisTab = false;
    function setHighlightNameState(isEnabled) {
      highlightNameStateForThisTab = isEnabled;
      window.storageHelper.set({ highlightName: isEnabled });
      window.nameHighlighter.highlightAuthorNames(isEnabled);
    }
    function highlightAuthors(papers) {
      const papersToProcess = papers || document.querySelectorAll('.gsc_a_tr');
      window.highlighterModule.highlightAuthors(papersToProcess, function () {
        window.storageHelper.get(['filters'], function (result, error) {
          const filters = (error || !result.filters) ? { first: true, second: true, coFirst: true, corresponding: true, last: true } : result.filters;
          window.uiManager.populateYearDropdowns();
          window.filterManager.applyFilters(filters);
          window.nameHighlighter.highlightAuthorNames(highlightNameStateForThisTab);
        });
      });
    }
    window.highlightAuthors = highlightAuthors; // expose for showMoreClicker
    function initialize() {
      const start = () => {
        window.uiManager.injectFilterUI(highlightNameStateForThisTab, setHighlightNameState);
        highlightAuthors();
        window.domObserver.observePaperChanges(highlightAuthors);
      };
      const profileContainer = document.querySelector('#gsc_prf_w');
      window.storageHelper.get('highlightName', function (result, error) {
        highlightNameStateForThisTab = (error || typeof result.highlightName === 'undefined') ? false : result.highlightName;
        if (profileContainer) {
          start();
        } else {
          window.domObserver.observeInitialLoad(start);
        }
      });
    }
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initialize);
    } else {
      initialize();
    }
  })();
})();