RA Toolkit

Toolkit for RetroAchievements.org — ROMs, translations, dashboard, pagination and more. Based on Retro Enhanced by Miagui.

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

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

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

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

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

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

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

Advertisement:

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

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

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

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

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

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

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

Advertisement:

// ==UserScript==
// @name         RA Toolkit
// @namespace    https://github.com/WelingtonMonteiro
// @version      2.8.2
// @description  Toolkit for RetroAchievements.org — ROMs, translations, dashboard, pagination and more. Based on Retro Enhanced by Miagui.
// @author       Miagui / Updated by Welington
// @match        *://retroachievements.org/*
// @license      MIT
// @icon         https://retroachievements.org/assets/images/ra-logo.webp
// @homepageURL  https://github.com/WelingtonMonteiro/ra-toolkit
// @supportURL   https://github.com/WelingtonMonteiro/ra-toolkit/issues
// @require      https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js
// @grant        GM_xmlhttpRequest
// @grant        GM_log
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues
// @grant        GM_deleteValue
// @connect      archive.org
// @connect      the-eye.eu
// @connect      raw.githubusercontent.com
// @connect      sheets.googleapis.com
// @connect      emuparadise.me
// @connect      speedrun.com
// @connect      myrient.erista.me
// @connect      retroachievements.org
// @connect      api.mymemory.translated.net
// @connect      romsfun.com
// ==/UserScript==

(function () {
  "use strict";

  console.log('[RA Toolkit] ✅ Script loaded — v2.8.2 — ' + location.href);

  // =========================================
  //       Inertia Props Helper
  // =========================================
  // The new RAWeb uses Inertia.js + React. Page data is stored
  // as a JSON blob in the #app element's data-page attribute.

  function getInertiaProps() {
    const appEl = document.getElementById("app");
    if (!appEl) return null;
    try {
      const pageData = JSON.parse(appEl.getAttribute("data-page") || "{}");
      return pageData.props || null;
    } catch (e) {
      log.error("Failed to parse Inertia props: " + e);
      return null;
    }
  }

  // =========================================
  //     HTML/XML Parsing (jQuery-free)
  // =========================================
  const domParser = new DOMParser();

  function parseHtml(htmlString) {
    return domParser.parseFromString(htmlString, "text/html");
  }

  function parseXml(xmlString) {
    return domParser.parseFromString(xmlString, "text/xml");
  }

  function escapeHtml(str) {
    var div = document.createElement("div");
    div.appendChild(document.createTextNode(str));
    return div.innerHTML;
  }

  // =========================================
  //        Structured Logging
  // =========================================
  var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3, off: 4 };
  var currentLogLevel = LOG_LEVELS.info; // default until config loads

  var log = {
    _format: function (level, msg) {
      return "[RA Toolkit][" + level.toUpperCase() + "] " + msg;
    },
    debug: function (msg) {
      if (currentLogLevel <= LOG_LEVELS.debug) GM_log(log._format("debug", msg));
    },
    info: function (msg) {
      if (currentLogLevel <= LOG_LEVELS.info) GM_log(log._format("info", msg));
    },
    warn: function (msg) {
      if (currentLogLevel <= LOG_LEVELS.warn) {
        GM_log(log._format("warn", msg));
        console.warn(log._format("warn", msg));
      }
    },
    error: function (msg) {
      if (currentLogLevel <= LOG_LEVELS.error) {
        GM_log(log._format("error", msg));
        console.error(log._format("error", msg));
      }
    }
  };

  // =========================================
  //  GM_xmlhttpRequest wrapper with errors
  // =========================================
  function gmFetch(url, timeoutMs = 30000) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: url,
        timeout: timeoutMs,
        onload: function (response) {
          if (response.status >= 200 && response.status < 400) {
            resolve(response);
          } else {
            reject(new Error("HTTP " + response.status + " for " + url));
          }
        },
        onerror: function (err) {
          reject(new Error("Network error fetching " + url + ": " + (err.error || "unknown")));
        },
        ontimeout: function () {
          reject(new Error("Timeout fetching " + url));
        }
      });
    });
  }

  // =========================================
  //   MyMemory Translation with Rate Limiter
  // =========================================
  var TRANSLATE_DAILY_LIMIT = 5000; // MyMemory free tier: 5000 chars/day

  function getTodayKey() {
    var d = new Date();
    return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
  }

  function getTranslateUsage() {
    return Promise.resolve(GM_getValue('translateUsage', null)).then(function (val) {
      if (val && val.date === getTodayKey()) return val;
      return { date: getTodayKey(), chars: 0 };
    });
  }

  function addTranslateUsage(charCount) {
    return getTranslateUsage().then(function (usage) {
      usage.chars += charCount;
      GM_setValue('translateUsage', usage);
      return usage;
    });
  }

  function translateWithRateLimit(text, targetLang) {
    return getTranslateUsage().then(function (usage) {
      var remaining = TRANSLATE_DAILY_LIMIT - usage.chars;
      if (remaining <= 0) {
        return Promise.reject(new Error('RATE_LIMIT: Daily translation limit reached (' + TRANSLATE_DAILY_LIMIT + ' chars). Resets tomorrow.'));
      }
      if (text.length > remaining) {
        return Promise.reject(new Error('RATE_LIMIT: Text too long (' + text.length + ' chars). Only ' + remaining + ' chars remaining today.'));
      }
      var url = 'https://api.mymemory.translated.net/get?q=' + encodeURIComponent(text) + '&langpair=en|' + encodeURIComponent(targetLang.split('-')[0]);
      return gmFetch(url, 10000).then(function (resp) {
        var data = JSON.parse(resp.responseText);
        if (data.responseStatus === 200 && data.responseData && data.responseData.translatedText) {
          addTranslateUsage(text.length);
          return data.responseData.translatedText;
        }
        throw new Error(data.responseDetails || 'Translation failed');
      });
    });
  }

  // =========================================
  //     ROM Search Cache (GM_setValue + TTL)
  // =========================================
  var ROM_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours

  function getRomCacheKey(gameTitle, consoleName) {
    return 'romCache_' + consoleName + '_' + gameTitle.toLowerCase().replace(/[^a-z0-9]/g, '_');
  }

  function getCachedRomResults(gameTitle, consoleName) {
    var key = getRomCacheKey(gameTitle, consoleName);
    return Promise.resolve(GM_getValue(key, null)).then(function (cached) {
      if (!cached) return null;
      if (Date.now() - cached.ts > ROM_CACHE_TTL) {
        GM_deleteValue(key);
        return null;
      }
      log.info("[Cache] Hit for " + gameTitle + " (" + cached.results.length + " results, age " + Math.round((Date.now() - cached.ts) / 60000) + "m)");
      return cached;
    });
  }

  function setCachedRomResults(gameTitle, consoleName, results, resultsDlcs, collectionName, collectionUrl) {
    var key = getRomCacheKey(gameTitle, consoleName);
    GM_setValue(key, {
      ts: Date.now(),
      results: results,
      resultsDlcs: resultsDlcs,
      collection: { name: collectionName, url: collectionUrl }
    });
    log.info("[Cache] Stored " + results.length + " results for " + gameTitle);
  }

  // =========================================
  //   Changelog Popup (after version update)
  // =========================================
  var CURRENT_VERSION = "2.8.2";

  var CHANGELOG = [
    { version: "2.8.2", changes: [
      "Achievement pages: linkify URLs and embed YouTube/images in achievement comments",
      "Achievement pages: added Translate button for achievement comments"
    ]},
    { version: "2.8.1", changes: [
      "Docs: added complete User Guide wiki with 18 pages covering all features"
    ]},
    { version: "2.8.0", changes: [
      "Games Page: new 'Most Mastered' tab on /games — browse games ranked by most players",
      "Games Page: card grid with rank, players, beaten count, achievements, and system tag",
      "Games Page: paginated results with internal API integration"
    ]},
    { version: "2.7.2", changes: [
      "Header: restored Achievements dropdown menu (Easy Achievements, Hardest Achievements)"
    ]},
    { version: "2.7.0", changes: [
      "Game Awards: new Mastered/Beaten tabs in sidebar section",
      "Game Awards: Beaten tab shows all beaten games with trophy icon and count",
      "Game Awards: hardcore badges shown in gold, softcore slightly dimmed"
    ]},
    { version: "2.6.5", changes: [
      "Rarest Achievements: items are now clickable links to /achievement/{id}",
      "Last Games Played: Beaten/Mastered award labels shown on all paginated pages (via awards API)",
      "Last Games Played: page range info moved from heading to pagination bar"
    ]},
    { version: "2.6.4", changes: [
      "Progression Status: replaced native section with modern dark-theme dashboard",
      "Progression Status: KPI grid (total games, beaten, mastered, % completed)",
      "Progression Status: donut overview chart + completion % bar chart by console",
      "Progression Status: animated bubble / treemap canvas visualization with filter",
      "Progression Status: Mastered vs Beaten bar chart (Chart.js)",
      "Added Chart.js @require for chart rendering"
    ]},
    { version: "2.6.3", changes: [
      "User Stats: recent activity and softcore sections now use metric cards with icons (consistent with primary stats)",
      "User Stats: CSS refactored to generic class names (stats-grid-3/4, metric-card, card-top, etc.)",
      "Activity Timeline: all 3 modes (Achievements, Mastered, Beaten) active by default"
    ]},
    { version: "2.6.2", changes: [
      "Activity Timeline: multi-select now uses priority coloring per cell (Mastered > Beaten > Achievements) instead of single emerald color",
      "Activity Timeline: each day shows the color of the highest-priority event type present"
    ]},
    { version: "2.6.1", changes: [
      "User Stats: redesigned with clean 3-section layout (primary grid, recent activity, softcore)",
      "User Stats: new metric cards with icons, weighted/softcore sub-values",
      "Removed Console Breakdown section (redundant with native Progression Status)"
    ]},
    { version: "2.6.0", changes: [
      "Enhanced User Stats: replaces native User Stats with beautiful card-style layout",
      "User Stats: primary stats (Points, Rank, Achievements, RetroRatio, Games Beaten) with icons and colors",
      "User Stats: expandable secondary stats (7/30 day points, avg points/week, avg completion, softcore)"
    ]},
    { version: "2.5.5", changes: [
      "Activity Timeline: rich custom tooltip with date header and per-mode icon breakdown",
      "Activity Timeline: tooltip shows \ud83c\udfc6 \ud83d\udc51 \u2705 icons next to each line"
    ]},
    { version: "2.5.4", changes: [
      "Activity Timeline: multi-select toggle buttons — select multiple modes (Achievements + Mastered + Beaten) to see combined heatmap",
      "Activity Timeline: combined mode uses emerald green color scheme",
      "Activity Timeline: tooltip and footer show per-mode breakdown when multiple modes are active"
    ]},
    { version: "2.5.3", changes: [
      "Updated install/update URLs for Greasy Fork"
    ]},
    { version: "2.5.2", changes: [
      "Activity Timeline: tooltip now shows year (e.g. 'Mar 19, 2026: 5 achievements')"
    ]},
    { version: "2.5.1", changes: [
      "Translate: disable button for texts exceeding 500-char API query limit",
      "Translate: show 'Too long' label with character count tooltip on hover"
    ]},
    { version: "2.5.0", changes: [
      "Activity Timeline: total achievements count shown in title",
      "Activity Timeline: toggle buttons to switch between Achievements (blue), Mastered (gold), and Beaten (gray) heatmaps",
      "New API integration: GetUserAwards for mastered/beaten game dates"
    ]},
    { version: "2.4.4", changes: [
      "Fix: rarity indicators on game page now work with all languages (i18n-safe percentage parsing)"
    ]},
    { version: "2.4.3", changes: [
      "Fix: enableRarityIndicator variable scope — rarity indicators now work correctly in achievement badges pagination"
    ]},
    { version: "2.4.2", changes: [
      "Image preview in wall comments — image links (png, jpg, gif, webp, etc.) show inline preview, click to open"
    ]},
    { version: "2.4.1", changes: [
      "Activity Timeline moved above Player Insights stats for better visibility"
    ]},
    { version: "2.4.0", changes: [
      "User Wall linkify — plain text URLs in comments become clickable links (opens in new tab)",
      "YouTube embed — YouTube links in wall comments show an inline mini video player"
    ]},
    { version: "2.3.3", changes: [
      "Emuparadise fix — links to download page instead of direct file (avoids referer block)"
    ]},
    { version: "2.3.2", changes: [
      "Emuparadise download fix — correct game ID extraction and direct download link with workaround"
    ]},
    { version: "2.3.1", changes: [
      "Timeline layout fix — uniform cell sizes and month labels overflow like GitHub's contribution graph"
    ]},
    { version: "2.3.0", changes: [
      "1-year Activity Timeline — GitHub-style contribution heatmap (52 weeks × 7 days) replacing the 30-day grid",
      "Streak Tracker now uses 365-day data for more accurate streak and active-day counts",
      "Yearly data fetched via quarterly API chunks (API_GetAchievementsEarnedBetween) to bypass 500-record limit"
    ]},
    { version: "2.2.1", changes: [
      "Missing consoles — added ROM search support for Amstrad CPC, Apple II, Uzebox, and WASM-4"
    ]},
    { version: "2.2.0", changes: [
      "Achievement rarity indicator — color-coded badges (Common, Uncommon, Rare, Very Rare, Ultra Rare, Legendary) on game page achievements and profile badges",
      "Collapse/expand sidebar sections — click ROMs or World Records headers to collapse/expand, state persisted"
    ]},
    { version: "2.1.1", changes: [
      "Save button in settings panel — 'Atualizar' button to confirm and reload"
    ]},
    { version: "2.1.0", changes: [
      "ROM search cache (24h TTL) — no more re-searching the same game",
      "Changelog popup — shows what's new after updates",
      "Custom accent color — choose your highlight color in settings",
      "Light mode support — adapts to the RA site theme (dark/light/black)",
      "Mobile layout support — sidebar injections work on mobile (<1024px)",
      "Guide link detection — shows RA Guide link on game pages when available"
    ]},
    { version: "2.0.0", changes: [
      "Player Insights Dashboard (6 modules)",
      "RomsFun ROM source",
      "RA Trophy badge for hash-verified ROMs",
      "Pagination skeleton loaders",
      "Previous/Next pagination buttons",
      "MyMemory API rate limiter"
    ]}
  ];

  function showChangelogPopup() {
    return Promise.resolve(GM_getValue("lastSeenVersion", "0.0.0")).then(function (lastSeen) {
      if (lastSeen === CURRENT_VERSION) return;
      GM_setValue("lastSeenVersion", CURRENT_VERSION);

      // Collect changes since last seen version
      var newChanges = [];
      for (var i = 0; i < CHANGELOG.length; i++) {
        if (CHANGELOG[i].version === lastSeen) break;
        newChanges.push(CHANGELOG[i]);
      }
      if (newChanges.length === 0) return;

      var changesHtml = newChanges.map(function (entry) {
        var items = entry.changes.map(function (c) { return '<li style="margin:2px 0;">' + escapeHtml(c) + '</li>'; }).join('');
        return '<div style="margin-bottom:10px;"><strong style="color:var(--ra-accent,#3b82f6);">v' + escapeHtml(entry.version) + '</strong><ul style="margin:4px 0 0 16px;padding:0;list-style:disc;">' + items + '</ul></div>';
      }).join('');

      var overlay = document.createElement('div');
      overlay.id = 'enhanced-changelog-overlay';
      overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.6);';
      overlay.innerHTML =
        '<div style="background:var(--box-bg-color,#232323);border:1px solid rgba(255,255,255,0.15);border-radius:12px;padding:24px;max-width:480px;width:90%;max-height:80vh;overflow-y:auto;color:var(--text-color,#c8c8c8);font-size:0.9rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);">'
        + '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">'
        + '<h3 style="margin:0;font-size:1.2rem;color:var(--heading-color,#d2d2d2);">🎮 RA Toolkit Updated!</h3>'
        + '<button id="enhanced-changelog-close" style="background:none;border:none;color:var(--text-color,#c8c8c8);font-size:1.4rem;cursor:pointer;padding:0 4px;line-height:1;">&times;</button>'
        + '</div>'
        + '<div style="line-height:1.5;">' + changesHtml + '</div>'
        + '<div style="text-align:center;margin-top:16px;">'
        + '<button id="enhanced-changelog-ok" style="padding:8px 24px;border-radius:8px;border:none;background:var(--ra-accent,#3b82f6);color:#fff;font-size:0.9rem;cursor:pointer;font-weight:600;">Got it!</button>'
        + '</div>'
        + '</div>';

      document.body.appendChild(overlay);

      function closePopup() { overlay.remove(); }
      document.getElementById('enhanced-changelog-close').addEventListener('click', closePopup);
      document.getElementById('enhanced-changelog-ok').addEventListener('click', closePopup);
      overlay.addEventListener('click', function (e) { if (e.target === overlay) closePopup(); });
    });
  }

  // =========================================
  //    Theme Detection (light/dark/black)
  // =========================================
  function getScheme() {
    var html = document.documentElement;
    var scheme = html.getAttribute('data-scheme') || '';
    if (scheme === 'system') {
      return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
    }
    return scheme || 'dark';
  }

  function isLightMode() {
    return getScheme() === 'light';
  }

  // =========================================
  //       Wait for React to Render
  // =========================================
  function waitForElement(selector, timeout = 10000) {
    return new Promise((resolve, reject) => {
      const el = document.querySelector(selector);
      if (el) return resolve(el);

      const observer = new MutationObserver(() => {
        const el = document.querySelector(selector);
        if (el) {
          observer.disconnect();
          resolve(el);
        }
      });
      observer.observe(document.body, { childList: true, subtree: true });

      setTimeout(() => {
        observer.disconnect();
        reject(new Error("Timeout waiting for: " + selector));
      }, timeout);
    });
  }

  // =========================================
  //       Get Logged User
  // =========================================
  function getLoggedUser() {
    const props = getInertiaProps();
    if (props && props.auth && props.auth.user) {
      return props.auth.user.displayName || props.auth.user.display_name || "";
    }
    // Fallback: try dropdown-header text
    const header = document.querySelector(".dropdown-header");
    if (header) return header.textContent.trim();
    return "";
  }

  // =========================================
  //      Cleanup previous injections
  // =========================================
  // =========================================
  //     Rarity Tier Helper
  // =========================================
  function getRarityTier(percentage) {
    if (percentage >= 50) return { label: 'Common', color: '#a3a3a3', bg: 'rgba(163,163,163,0.12)' };
    if (percentage >= 25) return { label: 'Uncommon', color: '#22c55e', bg: 'rgba(34,197,94,0.12)' };
    if (percentage >= 10) return { label: 'Rare', color: '#3b82f6', bg: 'rgba(59,130,246,0.12)' };
    if (percentage >= 5)  return { label: 'Very Rare', color: '#a855f7', bg: 'rgba(168,85,247,0.12)' };
    if (percentage >= 2)  return { label: 'Ultra Rare', color: '#f59e0b', bg: 'rgba(245,158,11,0.12)' };
    return { label: 'Legendary', color: '#ef4444', bg: 'rgba(239,68,68,0.12)' };
  }

  function cleanup() {
    const ids = ["enhanced-settings", "enhanced-romsdl", "enhanced-speedruncom",
                 "enhanced-custom-bg-style", "enhanced-glass-style", "enhanced-dl-style",
                 "enhanced-translate-style", "enhanced-pagination", "enhanced-pagination-style",
                 "enhanced-guide-link", "enhanced-changelog-overlay", "enhanced-rarity-style",
                 "enhanced-collapse-style", "enhanced-wall-linkify-style",
                 "re-game-awards-style", "re-game-awards-tabs",
                 "re-achievements-dropdown",
                 "re-most-mastered-tab", "re-most-mastered-container", "re-most-mastered-style"];
    ids.forEach(id => {
      const el = document.getElementById(id);
      if (el) el.remove();
    });
    // Remove injected video iframes, translate buttons, rarity badges, and linkify embeds
    document.querySelectorAll("iframe.enhanced-video").forEach(el => el.remove());
    document.querySelectorAll(".enhanced-translate-btn").forEach(el => el.remove());
    document.querySelectorAll(".enhanced-rarity-badge").forEach(el => el.remove());
    document.querySelectorAll(".enhanced-yt-embed").forEach(el => el.remove());
  }

  // =========================================
  //        Main Init Function
  // =========================================
  async function init() {
    console.log('[RA Toolkit] ⚙️  init() starting on: ' + location.pathname);
    cleanup();

    var page = location.pathname;

    // Reload configs on each navigation
    var enableSpeedrun = await GM_getValue("enableSpeedrun", false);
  var enableRomSearch = await GM_getValue("enableRomSearch", true);
  var enableCustomBG = await GM_getValue("enableCustomBG", true);
  var enableGameplayVideo = await GM_getValue("enableGameplayVideo", true);
  var enableEmuparadise = await GM_getValue("enableEmuparadise", false);
  var prioritizeEmuparadise = await GM_getValue("prioritizeEmuparadise", false);
  var enableGlassEffect = await GM_getValue("enableGlassEffect", true);
  var enableHashCheck = await GM_getValue("enableHashCheck", true);
  var enableRomsFun = await GM_getValue("enableRomsFun", true);
  var enableDebugLog = await GM_getValue("enableDebugLog", false);
  var enableRarityIndicator = await GM_getValue("enableRarityIndicator", true);
  var translateLang = await GM_getValue("translateLang", "pt-BR");
  var accentColor = await GM_getValue("accentColor", "#3b82f6");

  // Apply log level from config
  currentLogLevel = enableDebugLog ? LOG_LEVELS.debug : LOG_LEVELS.info;

  // Inject accent color CSS variable
  var accentStyle = document.getElementById('enhanced-accent-style');
  if (!accentStyle) {
    accentStyle = document.createElement('style');
    accentStyle.id = 'enhanced-accent-style';
    document.head.appendChild(accentStyle);
  }
  accentStyle.textContent = ':root { --ra-accent: ' + accentColor + '; }'
    + ' .enhanced-switch[data-state="checked"] { background-color: ' + accentColor + ' !important; }'
    + ' .enhanced-translate-btn.translated { color: ' + accentColor + '; border-color: ' + accentColor + '40; }'
    + ' #enhanced-changelog-ok { background: ' + accentColor + ' !important; }';

  // Inject light mode adaptive CSS
  var lightStyle = document.getElementById('enhanced-light-style');
  if (!lightStyle) {
    lightStyle = document.createElement('style');
    lightStyle.id = 'enhanced-light-style';
    document.head.appendChild(lightStyle);
  }
  lightStyle.textContent = isLightMode() ? `
    .enhanced-translate-btn { color: #525252; border-color: rgba(0,0,0,0.15); }
    .enhanced-translate-btn:hover { background: rgba(0,0,0,0.06); color: #1a1a1a; border-color: rgba(0,0,0,0.25); }
    #enhanced-romsdl a { color: #2563eb !important; }
    #enhanced-romsdl a:hover { color: #1d4ed8 !important; }
    .enhanced-rom-noresults { background: rgba(0,0,0,0.03) !important; border-color: rgba(0,0,0,0.1) !important; }
    .enhanced-rom-noresults p { color: #525252 !important; }
    .enhanced-rom-noresults strong { color: #1a1a1a !important; }
  ` : '';

  // Show changelog popup on first run after update
  showChangelogPopup();

  // =========================================
  //          Console Mappings
  // =========================================
  const RAConsole = {
    ARCADE: "Arcade",
    SNES: "SNES/Super Famicom",
    NES: "NES/Famicom",
    GAMEBOY: "Game Boy",
    GAMEBOYCOLOR: "Game Boy Color",
    GAMEBOYADVANCE: "Game Boy Advance",
    NINTENDO64: "Nintendo 64",
    GAMECUBE: "GameCube",
    NINTENDODS: "Nintendo DS",
    NINTENDODSI: "Nintendo DSi",
    ATARI2600: "Atari 2600",
    ATARI7800: "Atari 7800",
    ATARIJAGUAR: "Atari Jaguar",
    ATARIJAGUARCD: "Atari Jaguar CD",
    ATARILYNX: "Atari Lynx",
    PCENGINE: "PC Engine/TurboGrafx-16",
    PCENGINECD: "PC Engine CD/TurboGrafx-CD",
    MASTERSYSTEM: "Master System",
    GAMEGEAR: "Game Gear",
    MEGADRIVE: "Genesis/Mega Drive",
    SEGA32X: "32X",
    SEGACD: "Sega CD",
    SATURN: "Saturn",
    DREAMCAST: "Dreamcast",
    PS1: "PlayStation",
    PS2: "PlayStation 2",
    PSP: "PlayStation Portable",
    P3DO: "3DO Interactive Multiplayer",
    NEOGEOCD: "Neo Geo CD",
    NEOGEOPOCKET: "Neo Geo Pocket",
    POKEMINI: "Pokemon Mini",
    VIRTUALBOY: "Virtual Boy",
    SG1000: "SG-1000",
    COLECO: "ColecoVision",
    MSX: "MSX",
    WII: "Wii",
    WONDERSWAN: "WonderSwan",
    VECTREX: "Vectrex",
    NEC8800: "PC-8000/8800",
    APPLEII: "Apple II",
    PCFX: "PC-FX",
    ARDUBOY: "Arduboy",
    ARCADIA: "Arcadia 2001",
    FAIRCHILD: "Fairchild Channel F",
    MAGNAVOXODYSSEY2: "Magnavox Odyssey 2",
    INTELLIVISION: "Intellivision",
    INTERTONVC4000: "Interton VC 4000",
    MEGADUCK: "Mega Duck",
    WATARA: "Watara Supervision",
    ZEEBO: "Zeebo",
    AMSTRADCPC: "Amstrad CPC",
    UZEBOX: "Uzebox",
    WASM4: "WASM-4"
  };

  const SRConsole = {
    PC: "8gej2n93",
    APPLEII: "w89ryw6l",
    ATARI2600: "o0644863",
    ARCADE: "vm9vn63k",
    NEC8800: "7g6mw8er",
    COLECOVISION: "wxeo8d6r",
    COMMODORE64: "gz9qox60",
    MSX: "jm950z6o",
    NES: "jm95z9ol",
    MSX2: "83exkk6l",
    MASTERSYSTEM: "83exwk6l",
    ATARI7800: "gde33gek",
    FAMICOMDISKSYSTEM: "mr6k409z",
    PCENGINE: "5negxk6y",
    MEGADRIVE: "mr6k0ezw",
    GAMEBOY: "n5683oev",
    NEOGEOAES: "mx6p4w63",
    GAMEGEAR: "w89r3w9l",
    SNES: "83exk6l5",
    PHILIPSCDI: "w89rjw6l",
    SEGACD: "31670d9q",
    PANASONIC3D0: "8gejmne3",
    NEOGEOCD: "kz9w7mep",
    PCFX: "p36n8568",
    PS1: "wxeod9rn",
    SEGA32X: "kz9wrn6p",
    SEGASATURN: "lq60l642",
    VIRTUALBOY: "7g6mk8er",
    NINTENDO64: "w89rwelk",
    GAMEBOYCOLOR: "gde3g9k1",
    NEOGEOPOCKETCOLOR: "7m6ydw6p",
    TURBOGRAFX16CD: "p36nlxe8",
    DREAMCAST: "v06d394z",
    WONDERSWAN: "vm9v8n63",
    PLAYSTATION2: "n5e17e27",
    WONDERSWANCOLOUR: "n568kz6v",
    GAMEBOYADVANCE: "3167d6q2",
    GAMECUBE: "4p9z06rn",
    POKÉMONMINI: "vm9vr1e3",
    NINTENDODS: "7g6m8erk",
    PLAYSTATIONPORTABLE: "5negk9y7",
    WII: "v06dk3e4",
    AMSTRADCPC: "5negykey"
  };

  // =========================================
  //        ROM Collection Dictionaries
  // =========================================

  const archiveCollectionDict = {
    [RAConsole.SATURN]: {
      name: "Redump Sega Saturn 2018",
      url: "https://archive.org/download/SegaSaturn2018July10"
    },
    [RAConsole.DREAMCAST]: {
      name: "CHD-ZSTD - Sega Dreamcast (Redump)",
      url: "https://archive.org/download/dc-chd-zstd-redump/dc-chd-zstd"
    },
    [RAConsole.SEGACD]: {
      name: "Redump Sega Mega CD & Sega CD",
      url: "https://archive.org/download/redump.sega_megacd-segacd"
    },
    [RAConsole.NEOGEOCD]: {
      name: "[REDUMP] Disc Image Collection: SNK - Neo Geo CD",
      url: "https://archive.org/download/redump.ngcd.revival"
    },
    [RAConsole.ATARI2600]: {
      name: "No-Intro Atari 2600",
      url: "https://archive.org/download/nointro2600atarii"
    },
    [RAConsole.NINTENDODS]: {
      name: "No-Intro Nintendo DS Decrypted",
      url: "https://archive.org/download/noIntroNintendoDsDecrypted2019Jun30"
    },
    [RAConsole.APPLEII]: {
      name: "Apple 2 TOSEC 2012",
      url: "https://archive.org/details/Apple_2_TOSEC_2012_04_23"
    }
  };

  const myrientCollectionDict = {
    // CDs and DVDs files
    [RAConsole.PS1]: {
      name: "chd_psx",
      urls: [
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_psx/CHD-PSX-USA/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_psx_eur/CHD-PSX-EUR/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_psx_jap/CHD-PSX-JAP/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_psx_jap_p2/CHD-PSX-JAP/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_psx_misc/CHD-PSX-Misc/"
      ]
    },
    [RAConsole.PSP]: {
      name: "psp-chd-zstd-redump",
      urls: [
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/psp-chd-zstd-redump-part1/psp-chd-zstd/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/psp-chd-zstd-redump-part2/psp-chd-zstd/",
      ]
    },
    [RAConsole.PS2]: {
      name: "Sony - PlayStation 2",
      urls: [
        "https://myrient.erista.me/files/Redump/Sony%20-%20PlayStation%202/"
      ]
    },
    [RAConsole.SATURN]: {
      name: "chd_saturn",
      urls: [
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_saturn/CHD-Saturn/USA/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_saturn/CHD-Saturn/Japan/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_saturn/CHD-Saturn/Europe/"
      ]
    },
    [RAConsole.DREAMCAST]: {
      name: "dc-chd-zstd",
      urls: ["https://myrient.erista.me/files/Internet%20Archive/chadmaster/dc-chd-zstd-redump/dc-chd-zstd/"]
    },
    [RAConsole.GAMECUBE]: {
      name: "Nintendo - GameCube - NKit RVZ [zstd-19-128k]",
      urls: ["https://myrient.erista.me/files/Redump/Nintendo%20-%20GameCube%20-%20NKit%20RVZ%20%5Bzstd-19-128k%5D/"]
    },
    [RAConsole.WII]: {
      name: "Nintendo - Wii - NKit RVZ [zstd-19-128k]",
      urls: ["https://myrient.erista.me/files/Redump/Nintendo%20-%20Wii%20-%20NKit%20RVZ%20%5Bzstd-19-128k%5D/"]
    },
    [RAConsole.SEGACD]: {
      name: "chd_segacd",
      urls: [
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_segacd/CHD-SegaCD-NTSC/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_segacd/CHD-MegaCD-NTSCJ/",
        "https://myrient.erista.me/files/Internet%20Archive/chadmaster/chd_segacd/CHD-MegaCD-PAL/",
      ]
    },
    [RAConsole.P3DO]: {
      name: "3do-chd-zstd",
      urls: ["https://myrient.erista.me/files/Internet%20Archive/chadmaster/3do-chd-zstd-redump/3do-chd-zstd/"]
    },
    [RAConsole.ATARIJAGUARCD]: {
      name: "jagcd-chd-zstd",
      urls: ["https://myrient.erista.me/files/Internet%20Archive/chadmaster/jagcd-chd-zstd/jagcd-chd-zstd/"]
    },
    [RAConsole.NEOGEOCD]: {
      name: "ngcd-chd-zstd",
      urls: ["https://myrient.erista.me/files/Internet%20Archive/chadmaster/ngcd-chd-zstd-redump/ngcd-chd-zstd/"]
    },
    [RAConsole.PCENGINECD]: {
      name: "pcecd-chd-zstd",
      urls: ["https://myrient.erista.me/files/Internet%20Archive/chadmaster/pcecd-chd-zstd-redump/pcecd-chd-zstd/"]
    },
    [RAConsole.PCFX]: {
      name: "PC-FX",
      urls: ["https://myrient.erista.me/files/Redump/NEC%20-%20PC-FX%20%26%20PC-FXGA/"]
    },
    // Cartridge roms
    [RAConsole.ARDUBOY]: {
      name: "Arduboy",
      urls: ["https://myrient.erista.me/files/No-Intro/Arduboy%20Inc%20-%20Arduboy/"]
    },
    [RAConsole.ATARI2600]: {
      name: "Atari - 2600",
      urls: ["https://myrient.erista.me/files/No-Intro/Atari%20-%202600/"]
    },
    [RAConsole.ATARI7800]: {
      name: "Atari - 7800",
      urls: ["https://myrient.erista.me/files/No-Intro/Atari%20-%207800/"]
    },
    [RAConsole.ATARIJAGUAR]: {
      name: "Atari - Jaguar (ROM)",
      urls: ["https://myrient.erista.me/files/No-Intro/Atari%20-%20Jaguar%20%28ROM%29/"]
    },
    [RAConsole.ATARILYNX]: {
      name: "Atari - Lynx (LYX)",
      urls: ["https://myrient.erista.me/files/No-Intro/Atari%20-%20Lynx%20%28LYX%29/"]
    },
    [RAConsole.WONDERSWAN]: {
      name: "Wonderswan",
      urls: [
        "https://myrient.erista.me/files/No-Intro/Bandai%20-%20WonderSwan/",
        "https://myrient.erista.me/files/No-Intro/Bandai%20-%20WonderSwan%20Color/"
      ]
    },
    [RAConsole.COLECO]: {
      name: "Coleco - ColecoVision",
      urls: ["https://myrient.erista.me/files/No-Intro/Coleco%20-%20ColecoVision/"]
    },
    [RAConsole.ARCADIA]: {
      name: "Emerson - Arcadia 2001",
      urls: ["https://myrient.erista.me/files/No-Intro/Emerson%20-%20Arcadia%202001/"]
    },
    [RAConsole.FAIRCHILD]: {
      name: "Fairchild - Channel F",
      urls: ["https://myrient.erista.me/files/No-Intro/Fairchild%20-%20Channel%20F/"]
    },
    [RAConsole.VECTREX]: {
      name: "GCE - Vectrex",
      urls: ["https://myrient.erista.me/files/No-Intro/GCE%20-%20Vectrex/"]
    },
    [RAConsole.MAGNAVOXODYSSEY2]: {
      name: "Magnavox - Odyssey 2",
      urls: ["https://myrient.erista.me/files/No-Intro/Magnavox%20-%20Odyssey%202/"]
    },
    [RAConsole.INTELLIVISION]: {
      name: "Mattel - Intellivision",
      urls: ["https://myrient.erista.me/files/No-Intro/Mattel%20-%20Intellivision/"]
    },
    [RAConsole.INTERTONVC4000]: {
      name: "Interton - VC 4000",
      urls: ["https://myrient.erista.me/files/No-Intro/Interton%20-%20VC%204000/"]
    },
    [RAConsole.MEGADUCK]: {
      name: "Welback - Mega Duck",
      urls: ["https://myrient.erista.me/files/No-Intro/Welback%20-%20Mega%20Duck/"]
    },
    [RAConsole.MSX]: {
      name: "Microsoft - MSX",
      urls: [
        "https://myrient.erista.me/files/No-Intro/Microsoft%20-%20MSX/",
        "https://myrient.erista.me/files/No-Intro/Microsoft%20-%20MSX2/"
      ]
    },
    [RAConsole.NEC8800]: {
      name: "Neo Kobe - NEC PC-8801 (2016-02-25)",
      urls: [
        "https://ia801307.us.archive.org/view_archive.php?archive=/35/items/Neo_Kobe_NEC_PC-8001_2016-02-25/Neo%20Kobe%20-%20NEC%20PC-8001%20%282016-02-25%29.zip",
        "https://ia801305.us.archive.org/view_archive.php?archive=/32/items/Neo_Kobe_NEC_PC-8801_2016-02-25/Neo%20Kobe%20-%20NEC%20PC-8801%20%282016-02-25%29.zip"
      ]
    },
    [RAConsole.PCENGINE]: {
      name: "NEC - PC Engine SuperGrafx",
      urls: [
        "https://myrient.erista.me/files/No-Intro/NEC%20-%20PC%20Engine%20-%20TurboGrafx-16/",
        "https://myrient.erista.me/files/No-Intro/NEC%20-%20PC%20Engine%20SuperGrafx/"
      ]
    },
    [RAConsole.NES]: {
      name: "Nintendo - Nintendo Entertainment System (Headered)",
      urls: [
        "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%20Entertainment%20System%20%28Headered%29/",
        "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Family%20Computer%20Disk%20System%20%28FDS%29/",
        "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%20Entertainment%20System%20%28Headered%29%20%28Private%29/"
      ]
    },
    [RAConsole.GAMEBOY]: {
      name: "Nintendo - Game Boy",
      urls: ["https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Game%20Boy/"]
    },
    [RAConsole.GAMEBOYADVANCE]: {
      name: "Nintendo - Game Boy Advance",
      urls: ["https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Game%20Boy%20Advance/"]
    },
    [RAConsole.GAMEBOYCOLOR]: {
      name: "Nintendo - Game Boy Color",
      urls: ["https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Game%20Boy%20Color/"]
    },
    [RAConsole.NINTENDO64]: {
      name: "Nintendo - Nintendo 64 (BigEndian)",
      urls: ["https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%2064%20%28BigEndian%29/"]
    },
    [RAConsole.NINTENDODS]: {
      name: "Nintendo - Nintendo DS (Decrypted)",
      urls: ["https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%20DS%20%28Decrypted%29/"]
    },
    [RAConsole.NINTENDODSI]: {
      name: "Nintendo - Nintendo DSi (Digital)",
      urls: ["https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Nintendo%20DSi%20%28Digital%29/"]
    },
    [RAConsole.POKEMINI]: {
      name: "Nintendo - Pokemon Mini",
      urls: ["https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Pokemon%20Mini/"]
    },
    [RAConsole.SNES]: {
      name: "Nintendo - Super Nintendo Entertainment System",
      urls: [
        "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Super%20Nintendo%20Entertainment%20System/",
        "https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Super%20Nintendo%20Entertainment%20System%20%28Private%29/"
      ]
    },
    [RAConsole.VIRTUALBOY]: {
      name: "Nintendo - Virtual Boy",
      urls: ["https://myrient.erista.me/files/No-Intro/Nintendo%20-%20Virtual%20Boy/"]
    },
    [RAConsole.NEOGEOPOCKET]: {
      name: "SNK - NeoGeo Pocket Color",
      urls: [
        "https://myrient.erista.me/files/No-Intro/SNK%20-%20NeoGeo%20Pocket%20Color/",
        "https://myrient.erista.me/files/No-Intro/SNK%20-%20NeoGeo%20Pocket/"
      ]
    },
    [RAConsole.SEGA32X]: {
      name: "Sega - 32X",
      urls: ["https://myrient.erista.me/files/No-Intro/Sega%20-%2032X/"]
    },
    [RAConsole.GAMEGEAR]: {
      name: "Sega - Game Gear",
      urls: ["https://myrient.erista.me/files/No-Intro/Sega%20-%20Game%20Gear/"]
    },
    [RAConsole.MASTERSYSTEM]: {
      name: "Sega - Master System - Mark III",
      urls: ["https://myrient.erista.me/files/No-Intro/Sega%20-%20Master%20System%20-%20Mark%20III/"]
    },
    [RAConsole.MEGADRIVE]: {
      name: "Sega - Mega Drive - Genesis",
      urls: [
        "https://myrient.erista.me/files/No-Intro/Sega%20-%20Mega%20Drive%20-%20Genesis/",
        "https://myrient.erista.me/files/No-Intro/Sega%20-%20Mega%20Drive%20-%20Genesis%20%28Private%29/"
      ]
    },
    [RAConsole.SG1000]: {
      name: "Sega - SG-1000",
      urls: ["https://myrient.erista.me/files/No-Intro/Sega%20-%20SG-1000/"]
    },
    [RAConsole.WATARA]: {
      name: "Watara - Supervision",
      urls: ["https://myrient.erista.me/files/No-Intro/Watara%20-%20Supervision/"]
    },
    [RAConsole.ZEEBO]: {
      name: "Zeebo - Zeebo",
      urls: ["https://myrient.erista.me/files/No-Intro/Zeebo%20-%20Zeebo/"]
    },
    [RAConsole.AMSTRADCPC]: {
      name: "Amstrad - CPC",
      urls: ["https://myrient.erista.me/files/No-Intro/Amstrad%20-%20CPC%20%28Misc%29/"]
    },
    [RAConsole.APPLEII]: {
      name: "Apple - II",
      urls: [
        "https://myrient.erista.me/files/No-Intro/Apple%20-%20II%20%28WOZ%29/",
        "https://myrient.erista.me/files/No-Intro/Apple%20-%20II%20%28A2R%29/"
      ]
    },
  };

  // =========================================
  //            Settings Page
  // =========================================
  if (page === "/settings") {
    try {
      // Wait for the React settings page to render
      const settingsContainer = await waitForElement("main.with-sidebar article");
      // Find the flex container that holds all settings section cards
      const flexContainer = settingsContainer.querySelector("div.flex.flex-col > div.flex.flex-col");

      if (flexContainer) {
        // Inject toggle switch styles (matching RAWeb BaseSwitch)
        const switchStyle = document.createElement("style");
        switchStyle.id = "enhanced-switch-style";
        switchStyle.textContent = `
          .enhanced-switch {
            position: relative;
            display: inline-flex;
            height: 1.5rem;
            width: 2.75rem;
            flex-shrink: 0;
            cursor: pointer;
            align-items: center;
            border-radius: 9999px;
            border: 2px solid transparent;
            transition: background-color 0.2s;
            background-color: #404040;
          }
          .enhanced-switch[data-state="checked"] {
            background-color: #3b82f6;
          }
          .enhanced-switch-thumb {
            pointer-events: none;
            display: block;
            height: 1.25rem;
            width: 1.25rem;
            border-radius: 9999px;
            background-color: #fafafa;
            box-shadow: 0 4px 6px -1px rgba(0,0,0,.1);
            transition: transform 0.2s;
            transform: translateX(0);
          }
          .enhanced-switch[data-state="checked"] .enhanced-switch-thumb {
            transform: translateX(1.25rem);
          }
          .enhanced-switch:focus-visible {
            outline: 2px solid #d4d4d4;
            outline-offset: 2px;
          }
        `;
        document.head.appendChild(switchStyle);

        function createSwitchHtml(id, checked) {
          var state = checked ? "checked" : "unchecked";
          return '<button id="' + id + '" role="switch" type="button" aria-checked="' + checked + '" data-state="' + state + '" class="enhanced-switch" tabindex="0"><span class="enhanced-switch-thumb"></span></button>';
        }

        function bindSwitch(id, gmKey) {
          var btn = document.getElementById(id);
          if (!btn) return;
          btn.addEventListener("click", function () {
            var isChecked = this.getAttribute("data-state") === "checked";
            var newState = !isChecked;
            this.setAttribute("data-state", newState ? "checked" : "unchecked");
            this.setAttribute("aria-checked", String(newState));
            GM_setValue(gmKey, newState);
          });
        }

        var settingsItems = [
          { id: "enhanced-romsearch", key: "enableRomSearch", val: enableRomSearch, label: "Enable ROMs search" },
          { id: "enhanced-hashcheck", key: "enableHashCheck", val: enableHashCheck, label: "Verify ROM hashes with RA API",
            hint: "Marks ROMs whose filename matches a known RA hash (requires RA API key in settings below)" },
          { id: "enhanced-epromsearch", key: "enableEmuparadise", val: enableEmuparadise, label: "Add Emuparadise to ROMs search",
            hint: "For Chrome users: enable mixed content; for all browsers: must click \"Add Exception\" the first time" },
          { id: "enhanced-prioritize_ep", key: "prioritizeEmuparadise", val: prioritizeEmuparadise, label: "Prioritize Emuparadise for ROMs search",
            hint: "Must have \"Add Emuparadise to ROMs search\" enabled" },
          { id: "enhanced-romsfun", key: "enableRomsFun", val: enableRomsFun, label: "Add RomsFun to ROMs search",
            hint: "Search romsfun.com for ROMs via their WordPress API" },
          { id: "enhanced-speedrun", key: "enableSpeedrun", val: enableSpeedrun, label: "Enable Speedrun.com stats" },
          { id: "enhanced-gameplayvideo", key: "enableGameplayVideo", val: enableGameplayVideo, label: "Enable gameplay video on the game page" },
          { id: "enhanced-custombg", key: "enableCustomBG", val: enableCustomBG, label: "Enable custom game page background" },
          { id: "enhanced-glassEffect", key: "enableGlassEffect", val: enableGlassEffect, label: "Enable glass background effect" },
          { id: "enhanced-debuglog", key: "enableDebugLog", val: enableDebugLog, label: "Enable debug logging",
            hint: "Outputs detailed debug-level logs to the Tampermonkey console" },
          { id: "enhanced-rarity", key: "enableRarityIndicator", val: enableRarityIndicator, label: "Achievement rarity indicator",
            hint: "Color-coded badges on achievements by unlock % (Common, Uncommon, Rare, Very Rare, Ultra Rare, Legendary)" },
        ];

        var rowsHtml = settingsItems.map(function (item) {
          var hintHtml = item.hint ? '<span style="display:block;font-size:0.8em;color:#b9b9b9;margin-top:2px;">' + item.hint + '</span>' : '';
          return '<div class="flex w-full items-center justify-between gap-3" style="min-height:2.5rem;">'
            + '<label for="' + item.id + '" class="text-menu-link cursor-pointer" style="flex:1;">' + item.label + hintHtml + '</label>'
            + createSwitchHtml(item.id, item.val)
            + '</div>';
        }).join('');

        // Translation language selector
        var langOptions = [
          { code: "pt-BR", label: "Português (BR)" },
          { code: "es-ES", label: "Español" },
          { code: "fr-FR", label: "Français" },
          { code: "de-DE", label: "Deutsch" },
          { code: "it-IT", label: "Italiano" },
          { code: "ja-JP", label: "日本語" },
          { code: "ko-KR", label: "한국어" },
          { code: "zh-CN", label: "中文 (简体)" },
          { code: "ru-RU", label: "Русский" },
          { code: "ar-SA", label: "العربية" },
        ];
        var langOptionsHtml = langOptions.map(function (opt) {
          return '<option value="' + opt.code + '"' + (translateLang === opt.code ? ' selected' : '') + '>' + escapeHtml(opt.label) + '</option>';
        }).join('');
        var langSelectorHtml = '<div class="flex w-full items-center justify-between gap-3" style="min-height:2.5rem;margin-top:0.5rem;padding-top:0.75rem;border-top:1px solid rgba(255,255,255,0.1);">'
          + '<label for="enhanced-translate-lang" class="text-menu-link" style="flex:1;">Translation language <span style="font-size:0.8em;color:#b9b9b9;">(for achievement card translate buttons)</span></label>'
          + '<select id="enhanced-translate-lang" style="width:200px;padding:4px 8px;border-radius:6px;border:1px solid #525252;background:#262626;color:#e5e5e5;font-size:0.875rem;cursor:pointer;">'
          + langOptionsHtml
          + '</select>'
          + '</div>';

        // API Key input
        var currentApiKey = await GM_getValue("raApiKey", "");
        var apiKeyHtml = '<div class="flex w-full items-center justify-between gap-3" style="min-height:2.5rem;margin-top:0.5rem;padding-top:0.75rem;border-top:1px solid rgba(255,255,255,0.1);">'
          + '<label for="enhanced-apikey" class="text-menu-link" style="flex:1;">RA API Key <span style="font-size:0.8em;color:#b9b9b9;">(for hash verification — find yours at Settings > Keys)</span></label>'
          + '<input id="enhanced-apikey" type="password" value="' + escapeHtml(currentApiKey) + '" placeholder="Your web API key" '
          + 'style="width:200px;padding:4px 8px;border-radius:6px;border:1px solid #525252;background:#262626;color:#e5e5e5;font-size:0.875rem;" />'
          + '</div>';

        // Accent color picker
        var accentColorHtml = '<div class="flex w-full items-center justify-between gap-3" style="min-height:2.5rem;margin-top:0.5rem;padding-top:0.75rem;border-top:1px solid rgba(255,255,255,0.1);">'
          + '<label for="enhanced-accent-color" class="text-menu-link" style="flex:1;">Accent color <span style="font-size:0.8em;color:#b9b9b9;">(custom highlight color for toggles, buttons, and UI elements)</span></label>'
          + '<div style="display:flex;align-items:center;gap:8px;">'
          + '<input id="enhanced-accent-color" type="color" value="' + escapeHtml(accentColor) + '" style="width:40px;height:32px;border:1px solid #525252;border-radius:6px;background:#262626;cursor:pointer;padding:2px;" />'
          + '<button id="enhanced-accent-reset" style="padding:4px 10px;border-radius:6px;border:1px solid #525252;background:#262626;color:#a3a3a3;font-size:0.8rem;cursor:pointer;" title="Reset to default blue">Reset</button>'
          + '</div>'
          + '</div>';

        const enhancedDiv = document.createElement("div");
        enhancedDiv.id = "enhanced-settings";
        enhancedDiv.className = "rounded-lg border border-embed-highlight bg-embed p-6 text-card-foreground shadow-sm w-full";
        enhancedDiv.innerHTML = '<h3 class="pb-2 border-b-0 text-2xl font-semibold leading-none tracking-tight">RA Toolkit</h3>'
          + '<div class="flex flex-col gap-4" style="margin-top:1rem;">'
          + rowsHtml
          + langSelectorHtml
          + apiKeyHtml
          + accentColorHtml
          + '</div>'
          + '<div class="flex w-full justify-end" style="margin-top:1rem;"><button id="enhanced-settings-save" class="btn-base btn-base--default btn-base--size-default" type="button">Atualizar</button></div>';

        // Insert after the second card in settings
        const cards = flexContainer.children;
        if (cards.length > 2) {
          cards[2].after(enhancedDiv);
        } else {
          flexContainer.appendChild(enhancedDiv);
        }

        // Bind all toggle switches
        settingsItems.forEach(function (item) {
          bindSwitch(item.id, item.key);
        });

        // Bind language selector
        var langSelect = document.getElementById("enhanced-translate-lang");
        if (langSelect) {
          langSelect.addEventListener("change", function () {
            GM_setValue("translateLang", this.value);
          });
        }

        // Bind API key input
        var apiKeyInput = document.getElementById("enhanced-apikey");
        if (apiKeyInput) {
          apiKeyInput.addEventListener("change", function () {
            GM_setValue("raApiKey", this.value);
          });
        }

        // Bind accent color picker
        var accentInput = document.getElementById("enhanced-accent-color");
        if (accentInput) {
          accentInput.addEventListener("input", function () {
            GM_setValue("accentColor", this.value);
            var s = document.getElementById('enhanced-accent-style');
            if (s) {
              s.textContent = ':root { --ra-accent: ' + this.value + '; }'
                + ' .enhanced-switch[data-state="checked"] { background-color: ' + this.value + ' !important; }'
                + ' .enhanced-translate-btn.translated { color: ' + this.value + '; border-color: ' + this.value + '40; }';
            }
            // Update all visible checked switches immediately
            document.querySelectorAll('.enhanced-switch[data-state="checked"]').forEach(function (sw) {
              sw.style.backgroundColor = accentInput.value;
            });
          });
        }
        var accentReset = document.getElementById("enhanced-accent-reset");
        if (accentReset) {
          accentReset.addEventListener("click", function () {
            var defaultColor = "#3b82f6";
            GM_setValue("accentColor", defaultColor);
            if (accentInput) accentInput.value = defaultColor;
            accentInput.dispatchEvent(new Event("input"));
          });
        }

        // Bind save/update button
        var saveBtn = document.getElementById("enhanced-settings-save");
        if (saveBtn) {
          saveBtn.addEventListener("click", function () {
            // All settings are already saved on change, just reload to apply
            var originalText = saveBtn.textContent;
            saveBtn.textContent = "✓ Salvo!";
            saveBtn.style.opacity = "0.7";
            saveBtn.disabled = true;
            setTimeout(function () {
              location.reload();
            }, 600);
          });
        }
      }
    } catch (e) {
      log.error("Settings page injection failed: " + e);
    }
  }

  // =========================================
  //            Game Page
  // =========================================
  // Match /game/{id} routes
  else if (page.match(/^\/game\/[0-9]+/) != null) {

    // Extract game data from Inertia props instead of scraping DOM
    let props = null;
    let consoleName = "";
    let gameTitle = "";
    let gameId = "";
    let gameImg = "";
    let tag = "";
    const rgxTag = /~(.*?)~/g;

    try {
      // Wait for the app to render and Inertia to hydrate
      await waitForElement('[data-testid="game-show"], [data-testid="sidebar"]');

      props = getInertiaProps();
      if (props && props.game) {
        const gameData = props.game || {};
        const backingGame = props.backingGame || {};
        const system = gameData.system || {};

        consoleName = system.name || "";
        gameTitle = backingGame.title || gameData.title || "";
        gameId = String(backingGame.id || gameData.id || "");
        gameImg = gameData.imageIngameUrl || "";

          log.info("[Inertia] Game = " + gameTitle + " | Console = " + consoleName + " | ID = " + gameId);
      } else {
        // Fallback: extract data from multiple DOM sources
        log.info("Inertia props unavailable, falling back to DOM scraping");

        // Game title: try h1, then og:title, then document title
        const h1 = document.querySelector('h1');
        const ogTitle = document.querySelector('meta[property="og:title"]');
        gameTitle = (h1 && h1.textContent.trim()) ||
                    (ogTitle && ogTitle.getAttribute("content")) ||
                    document.title.split(" - ")[0].trim() || "";

        // Game ID: try og:url, then canonical, then pathname
        const ogUrl = document.querySelector('meta[property="og:url"]');
        const canonical = document.querySelector('link[rel="canonical"]');
        const urlSource = (ogUrl && ogUrl.getAttribute("content")) ||
                          (canonical && canonical.getAttribute("href")) ||
                          location.href;
        const idMatch = /game\/(\d+)/.exec(urlSource);
        if (idMatch) gameId = idMatch[1];

        // Console name: try system chip (multiple selectors)
        const systemChip = document.querySelector('a[href*="/system/"] span.hidden.sm\\:inline') ||
                           document.querySelector('a[href*="/system/"] span:last-child') ||
                           document.querySelector('[data-testid="desktop-banner"] a[href*="/system/"]');
        consoleName = systemChip ? systemChip.textContent.trim() : "";

        // In-game screenshot: try multiple alt texts and selectors
        const ingameImg = document.querySelector('img[alt="ingame screenshot"]') ||
                          document.querySelector('img[alt="In-game screenshot"]') ||
                          document.querySelector('[data-testid="game-show"] img:nth-child(2)');
        gameImg = ingameImg ? ingameImg.getAttribute("src") : "";

        log.info("[DOM] Game = " + gameTitle + " | Console = " + consoleName + " | ID = " + gameId);
      }
    } catch (e) {
      log.error("Failed to get game data: " + e);
      return;
    }

    // Check for tags like ~Hack~, ~Homebrew~, etc.
    if (gameTitle.match(rgxTag) != undefined) {
      tag = rgxTag.exec(gameTitle)[1];
      gameTitle = gameTitle.replace(gameTitle.match(rgxTag) + " ", "");
    }

    // Avoid unwanted exceptions for hubs pages
    if (consoleName === "") return;

    var isAvailable = false;
    var collection = { name: "", url: "" };
    var results = [];
    var resultsDlcs = [];

    // Speedrun.com API resources
    var srRoot = "https://www.speedrun.com/api/v1/";
    var srLogo = "";
    var srVideoUrl = "";
    var srGamelink = "";
    var srGameId = "";
    var srRuns = [];

    // =========================================
    //           Custom Background
    // =========================================
    if (gameImg && !gameImg.includes("/Images/000002.png") && enableCustomBG) {
      const styleEl = document.createElement("style");
      styleEl.textContent = `
        body:before {
          content: "";
          position: fixed;
          width: 110%;
          height: 110%;
          background-image: url(${gameImg});
          background-size: cover;
          background-position: center;
          background-repeat: no-repeat;
          z-index: -1;
          overflow: hidden;
          filter: blur(8px);
          -moz-filter: blur(8px);
          -webkit-filter: blur(8px);
          -o-filter: blur(8px);
        }
      `;
      document.head.appendChild(styleEl);
    }

    // Glass background effect
    if (enableGlassEffect) {
      const glassStyle = document.createElement("style");
      glassStyle.textContent = `
        :root { --box-bg-color: rgba(35, 35, 35, 0.95); }
        main.with-sidebar > article { background: var(--box-bg-color); border-radius: 0.5rem; }
        main.with-sidebar > aside { background: var(--box-bg-color); border-radius: 0.5rem; }
      `;
      document.head.appendChild(glassStyle);
    }

    // =========================================
    //        Prepare Sidebar Injection
    // =========================================
    // Desktop: aside with data-testid="sidebar"
    // Mobile (<1024px): sidebar is rendered below main content in block layout
    var isMobile = window.innerWidth < 1024;
    const sidebar = document.querySelector('aside [data-testid="sidebar"]') ||
                    document.querySelector("aside");

    // On mobile, also try to find the article content area for injection
    const mobileContainer = isMobile ? (document.querySelector('main.with-sidebar > article') || document.querySelector('main > article')) : null;

    // Create ROMs and Speedrun containers in the sidebar
    const divRoms = document.createElement("div");
    divRoms.id = "enhanced-romsdl";
    divRoms.style.marginTop = "1em";

    const divSpeedruncom = document.createElement("div");
    divSpeedruncom.id = "enhanced-speedruncom";
    divSpeedruncom.style.margin = "1em 0em";

    var injectionTarget = sidebar;
    if (isMobile && !sidebar && mobileContainer) {
      // On mobile without visible sidebar, inject at the end of article
      injectionTarget = mobileContainer;
      log.info("[Mobile] Injecting into article container");
    }

    if (injectionTarget) {
      // Insert at the top of the sidebar, after boxart
      const boxart = injectionTarget.querySelector("div.overflow-hidden.text-center") ||
                     (sidebar ? sidebar.firstElementChild : null);
      if (boxart && boxart.nextSibling) {
        boxart.after(divSpeedruncom);
        boxart.after(divRoms);
      } else if (sidebar) {
        sidebar.prepend(divSpeedruncom);
        sidebar.prepend(divRoms);
      } else {
        // Mobile fallback: append at the end
        injectionTarget.appendChild(divRoms);
        injectionTarget.appendChild(divSpeedruncom);
      }
    }

    // =========================================
    //    Collapse/Expand Sidebar Sections
    // =========================================
    function injectCollapseStyle() {
      if (document.getElementById("enhanced-collapse-style")) return;
      var style = document.createElement("style");
      style.id = "enhanced-collapse-style";
      style.textContent = `
        .enhanced-collapse-header {
          display: flex;
          align-items: center;
          justify-content: space-between;
          cursor: pointer;
          user-select: none;
          padding: 2px 0;
          transition: opacity 0.15s;
        }
        .enhanced-collapse-header:hover {
          opacity: 0.8;
        }
        .enhanced-collapse-arrow {
          font-size: 0.75em;
          transition: transform 0.2s ease;
          color: #a3a3a3;
          margin-left: 6px;
        }
        .enhanced-collapse-arrow.collapsed {
          transform: rotate(-90deg);
        }
        .enhanced-collapse-content {
          overflow: hidden;
          transition: max-height 0.25s ease, opacity 0.2s ease;
          max-height: 2000px;
          opacity: 1;
        }
        .enhanced-collapse-content.collapsed {
          max-height: 0;
          opacity: 0;
        }
      `;
      document.head.appendChild(style);
    }

    function makeCollapsible(containerEl, sectionKey) {
      if (!containerEl) return;
      // Find the h3 header inside this container
      var h3 = containerEl.querySelector("h3");
      if (!h3 || h3.classList.contains("enhanced-collapse-header")) return;

      injectCollapseStyle();

      // Read persisted collapse state
      var storeKey = "sidebarCollapsed_" + sectionKey;
      var isCollapsed = false;

      // Wrap all siblings after h3 into a content div
      var contentDiv = document.createElement("div");
      contentDiv.className = "enhanced-collapse-content";

      // Collect all siblings after h3
      var siblings = [];
      var next = h3.nextSibling;
      while (next) {
        siblings.push(next);
        next = next.nextSibling;
      }
      siblings.forEach(function (node) {
        contentDiv.appendChild(node);
      });
      containerEl.appendChild(contentDiv);

      // Make h3 a clickable header
      h3.classList.add("enhanced-collapse-header");
      var arrow = document.createElement("span");
      arrow.className = "enhanced-collapse-arrow";
      arrow.textContent = "▼";
      h3.appendChild(arrow);

      function setCollapseState(collapsed) {
        isCollapsed = collapsed;
        if (collapsed) {
          contentDiv.classList.add("collapsed");
          arrow.classList.add("collapsed");
        } else {
          contentDiv.classList.remove("collapsed");
          arrow.classList.remove("collapsed");
        }
        GM_setValue(storeKey, collapsed);
      }

      h3.addEventListener("click", function () {
        setCollapseState(!isCollapsed);
      });

      // Apply persisted state
      Promise.resolve(GM_getValue(storeKey, false)).then(function (saved) {
        if (saved) setCollapseState(true);
      });
    }

    // Show loading indicator while searching
    var loadingEl = null;
    function showLoading(container, text) {
      loadingEl = document.createElement("div");
      loadingEl.id = "enhanced-loading";
      loadingEl.style.cssText = "display:flex;align-items:center;gap:8px;padding:8px 0;color:#a3a3a3;font-size:0.9em;";
      loadingEl.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" style="animation:enhanced-spin 1s linear infinite;"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" fill="none" stroke-dasharray="31.4 31.4" stroke-linecap="round"/></svg>'
        + '<span>' + escapeHtml(text) + '</span>';
      var spinStyle = document.getElementById("enhanced-spin-style");
      if (!spinStyle) {
        spinStyle = document.createElement("style");
        spinStyle.id = "enhanced-spin-style";
        spinStyle.textContent = "@keyframes enhanced-spin { to { transform: rotate(360deg); } }";
        document.head.appendChild(spinStyle);
      }
      container.appendChild(loadingEl);
    }
    function hideLoading() {
      if (loadingEl) { loadingEl.remove(); loadingEl = null; }
    }

    if (enableGameplayVideo || enableSpeedrun) getSpeedruns(gameTitle);

    // =========================================
    //        Guide Link Detection
    // =========================================
    (function injectGuideLink() {
      // Try Inertia props first, then DOM scraping
      var guideUrl = null;
      if (props && props.backingGame && props.backingGame.guideUrl) {
        guideUrl = props.backingGame.guideUrl;
      } else if (props && props.game && props.game.guideUrl) {
        guideUrl = props.game.guideUrl;
      }
      // Fallback: check if there's already a guide link in the DOM
      if (!guideUrl) {
        var existingGuide = document.querySelector('a[href*="github.com/RetroAchievements/guides"]');
        if (existingGuide) guideUrl = existingGuide.href;
      }
      if (!guideUrl) {
        log.debug("[Guide] No guide URL found for this game");
        return;
      }
      // Don't inject if there's already a visible guide button
      if (document.getElementById('enhanced-guide-link')) return;

      log.info("[Guide] Found guide: " + guideUrl);

      var guideDiv = document.createElement("div");
      guideDiv.id = "enhanced-guide-link";
      guideDiv.style.cssText = "margin:0.75em 0;";
      guideDiv.innerHTML =
        '<a href="' + escapeHtml(guideUrl) + '" target="_blank" rel="noopener" '
        + 'style="display:flex;align-items:center;gap:8px;padding:10px 14px;border-radius:8px;'
        + 'background:rgba(59,130,246,0.08);border:1px solid var(--ra-accent,#3b82f6);'
        + 'color:var(--ra-accent,#3b82f6);text-decoration:none;font-weight:600;font-size:0.9em;transition:all 0.2s;"'
        + ' onmouseover="this.style.background=\'rgba(59,130,246,0.15)\'"'
        + ' onmouseout="this.style.background=\'rgba(59,130,246,0.08)\'">'
        + '<span style="font-size:1.2em;">📖</span>'
        + '<span>RA Achievement Guide</span>'
        + '<span style="margin-left:auto;font-size:0.85em;opacity:0.7;">↗</span>'
        + '</a>';

      // Insert before ROM section in sidebar
      if (divRoms && divRoms.parentNode) {
        divRoms.parentNode.insertBefore(guideDiv, divRoms);
      } else if (injectionTarget) {
        injectionTarget.appendChild(guideDiv);
      }
    })();

    // =========================================
    //     Achievement Translation Feature
    // =========================================
    function translateText(text, targetLang) {
      return translateWithRateLimit(text, targetLang);
    }

    function injectTranslateButtons() {
      // Inject CSS once
      if (!document.getElementById("enhanced-translate-style")) {
        var style = document.createElement("style");
        style.id = "enhanced-translate-style";
        style.textContent = `
          .enhanced-translate-btn {
            display: inline-flex;
            align-items: center;
            gap: 3px;
            padding: 1px 6px;
            border: 1px solid rgba(255,255,255,0.15);
            border-radius: 4px;
            background: transparent;
            color: #a3a3a3;
            font-size: 0.7em;
            cursor: pointer;
            transition: all 0.2s;
            vertical-align: middle;
            margin-left: 6px;
          }
          .enhanced-translate-btn:hover {
            background: rgba(255,255,255,0.08);
            color: #e5e5e5;
            border-color: rgba(255,255,255,0.25);
          }
          .enhanced-translate-btn.translating {
            opacity: 0.6;
            pointer-events: none;
          }
          .enhanced-translate-btn.translated {
            color: #3b82f6;
            border-color: rgba(59,130,246,0.3);
          }
          .enhanced-translate-btn.disabled {
            opacity: 0.4;
            cursor: not-allowed;
            pointer-events: none;
          }
        `;
        document.head.appendChild(style);
      }

      // Find all achievement list items
      var items = document.querySelectorAll("li.game-set-item");
      items.forEach(function (li) {
        // Skip if already has a translate button
        if (li.querySelector(".enhanced-translate-btn")) return;

        // Find the description paragraph — it's the <p class="leading-4"> inside the title/description area
        var descPs = li.querySelectorAll("p.leading-4");
        var descP = null;
        for (var i = 0; i < descPs.length; i++) {
          // The description <p> is the one that doesn't contain progress bar info
          // It's typically the first p.leading-4 that has direct text content
          var txt = descPs[i].textContent.trim();
          if (txt && !txt.match(/^\d+\s*(of|de)\s*\d+/)) {
            descP = descPs[i];
            break;
          }
        }
        if (!descP) return;

        // Find the title link
        var titleLink = li.querySelector("a.font-medium");

        var btn = document.createElement("button");
        btn.className = "enhanced-translate-btn";

        var originalDesc = descP.textContent;
        var originalTitle = titleLink ? titleLink.textContent : "";
        var textToCheck = (originalTitle ? originalTitle + "\n" : "") + originalDesc;

        if (textToCheck.length > 500) {
          btn.classList.add("disabled");
          btn.title = "Text exceeds 500 character limit for translation (" + textToCheck.length + " chars)";
          btn.innerHTML = '&#x1F310; Too long';
          descP.appendChild(btn);
          return;
        }

        btn.title = "Translate to " + translateLang;
        btn.innerHTML = '&#x1F310; Translate';

        var isTranslated = false;
        var translatedDesc = null;
        var translatedTitle = null;

        btn.addEventListener("click", function () {
          if (btn.classList.contains("translating")) return;

          // Toggle back to original
          if (isTranslated) {
            descP.textContent = originalDesc;
            if (titleLink) titleLink.textContent = originalTitle;
            btn.innerHTML = '&#x1F310; Translate';
            btn.classList.remove("translated");
            isTranslated = false;
            return;
          }

          // Use cached translation if available
          if (translatedDesc) {
            descP.textContent = translatedDesc;
            if (titleLink && translatedTitle) titleLink.textContent = translatedTitle;
            btn.innerHTML = '&#x1F310; Original';
            btn.classList.add("translated");
            isTranslated = true;
            return;
          }

          // Fetch translation
          btn.classList.add("translating");
          btn.innerHTML = '&#x23F3; ...';

          var textToTranslate = originalDesc;
          if (titleLink && originalTitle) {
            textToTranslate = originalTitle + "\n" + originalDesc;
          }

          translateText(textToTranslate, translateLang)
            .then(function (result) {
              var parts = result.split("\n");
              if (titleLink && originalTitle && parts.length >= 2) {
                translatedTitle = parts[0];
                translatedDesc = parts.slice(1).join("\n");
                titleLink.textContent = translatedTitle;
              } else {
                translatedDesc = result;
              }
              descP.textContent = translatedDesc;
              btn.innerHTML = '&#x1F310; Original';
              btn.classList.remove("translating");
              btn.classList.add("translated");
              isTranslated = true;
            })
            .catch(function (err) {
              log.warn("Translation failed: " + err.message);
              var isRateLimit = err.message && err.message.indexOf('RATE_LIMIT') === 0;
              btn.innerHTML = isRateLimit ? '&#x26D4; Limit' : '&#x26A0; Error';
              btn.title = isRateLimit ? err.message.replace('RATE_LIMIT: ', '') : 'Translation failed';
              btn.classList.remove("translating");
              if (!isRateLimit) {
                setTimeout(function () {
                  btn.innerHTML = '&#x1F310; Translate';
                  btn.title = 'Translate to ' + translateLang;
                }, 2000);
              }
            });
        });

        // Insert the button after the description
        descP.appendChild(btn);
      });
    }

    // Inject translate buttons after the page has rendered achievements
    // Use a small delay + MutationObserver to catch dynamically loaded achievement lists
    setTimeout(injectTranslateButtons, 1500);
    var achObserver = new MutationObserver(function () {
      injectTranslateButtons();
      if (enableRarityIndicator) injectRarityIndicators();
    });
    var mainContent = document.querySelector("main") || document.body;
    achObserver.observe(mainContent, { childList: true, subtree: true });

    // =========================================
    //     Achievement Rarity Indicator
    // =========================================
    function injectRarityIndicators() {
      // Inject rarity CSS once
      if (!document.getElementById("enhanced-rarity-style")) {
        var rarityStyle = document.createElement("style");
        rarityStyle.id = "enhanced-rarity-style";
        rarityStyle.textContent = `
          .enhanced-rarity-badge {
            display: inline-flex;
            align-items: center;
            gap: 3px;
            padding: 1px 6px;
            border-radius: 4px;
            font-size: 0.65em;
            font-weight: 600;
            letter-spacing: 0.02em;
            white-space: nowrap;
            vertical-align: middle;
            margin-left: 6px;
            line-height: 1.6;
          }
          .enhanced-rarity-badge .enhanced-rarity-dot {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            flex-shrink: 0;
          }
          li.game-set-item.enhanced-rarity-bordered {
            border-left: 3px solid var(--enhanced-rarity-color, transparent);
            padding-left: 4px;
          }
        `;
        document.head.appendChild(rarityStyle);
      }

      var items = document.querySelectorAll("li.game-set-item");
      items.forEach(function (li) {
        if (li.querySelector(".enhanced-rarity-badge")) return;

        // Parse unlock percentage from the DOM (language-agnostic)
        // The dedicated unlock rate <p> has class "text-center text-2xs"
        // e.g. "45.25% unlock rate" (en) or "45,25% taxa de desbloqueio" (pt-BR)
        var percentage = null;

        // Primary: find the dedicated unlock rate paragraph by its text-center class
        var centerPs = li.querySelectorAll("p.text-center");
        for (var i = 0; i < centerPs.length; i++) {
          var txt = centerPs[i].textContent || '';
          var match = txt.match(/([\d,.]+)\s*%/);
          if (match) {
            percentage = parseFloat(match[1].replace(',', '.'));
            break;
          }
        }

        // Fallback: any <p> with a percentage followed by unlock-related text
        if (percentage === null) {
          var textEls = li.querySelectorAll("p");
          for (var j = 0; j < textEls.length; j++) {
            var txt2 = textEls[j].textContent || '';
            var match2 = txt2.match(/([\d,.]+)\s*%\s*(?:unlock\s*rate|taxa\s*de\s*desbloqueio)/i);
            if (match2) {
              percentage = parseFloat(match2[1].replace(',', '.'));
              break;
            }
          }
        }

        if (percentage === null || isNaN(percentage)) return;

        var tier = getRarityTier(percentage);

        // Add colored left border
        li.classList.add("enhanced-rarity-bordered");
        li.style.setProperty("--enhanced-rarity-color", tier.color);

        // Find the title area (after the title link) to inject the badge
        var titleSpan = li.querySelector("a.font-medium");
        if (!titleSpan) return;

        var badge = document.createElement("span");
        badge.className = "enhanced-rarity-badge";
        badge.style.cssText = "color:" + tier.color + ";background:" + tier.bg + ";border:1px solid " + tier.color + "30;";
        badge.title = percentage.toFixed(2) + "% unlock rate";
        badge.innerHTML = '<span class="enhanced-rarity-dot" style="background:' + tier.color + ';"></span>' + tier.label;

        // Insert after the title link's parent span
        var titleContainer = titleSpan.parentElement;
        if (titleContainer) {
          titleContainer.appendChild(badge);
        }
      });
    }

    if (enableRarityIndicator) {
      setTimeout(injectRarityIndicators, 1500);
    }
    // Stop observing after 30s to avoid performance overhead
    setTimeout(function () { achObserver.disconnect(); }, 30000);

    // =========================================
    //              Rom Search
    // =========================================
    // =========================================
    //       Hash Verification via RA API
    // =========================================
    var knownHashes = [];

    function fetchGameHashes(gId) {
      return Promise.resolve(GM_getValue("raApiKey", "")).then(function (apiKey) {
        log.info("[HashCheck] enableHashCheck=" + enableHashCheck + " apiKey=" + (apiKey ? "set (" + apiKey.length + " chars)" : "EMPTY"));
        if (!apiKey || !enableHashCheck) {
          log.warn("[HashCheck] Skipped: " + (!apiKey ? "no API key" : "hash check disabled"));
          return [];
        }
        var url = "https://retroachievements.org/API/API_GetGameHashes.php?i=" + encodeURIComponent(gId) + "&y=" + encodeURIComponent(apiKey);
        log.info("[HashCheck] Fetching hashes for game ID: " + gId);
        return gmFetch(url, 15000).then(function (resp) {
          var data = JSON.parse(resp.responseText);
          var results = (data.Results || []).map(function (h) {
            var labels = h.Labels;
            if (typeof labels === "string") labels = labels ? labels.split(",") : [];
            if (!Array.isArray(labels)) labels = [];
            return { name: (h.Name || "").toLowerCase(), md5: h.MD5, labels: labels };
          });
          log.info("[HashCheck] Found " + results.length + " known hashes");
          if (results.length > 0) {
            log.info("[HashCheck] Sample hash: " + results[0].name + " (MD5: " + results[0].md5 + ")");
          }
          return results;
        }).catch(function (err) {
          log.warn("[HashCheck] Hash fetch failed: " + err.message);
          return [];
        });
      }).catch(function (err) {
        log.warn("[HashCheck] GM_getValue failed: " + err.message);
        return [];
      });
    }

    // Normalize ROM filename: strip extension and brackets, keep region (parentheses)
    function normalizeRomName(name) {
      return name
        .toLowerCase()
        .replace(/\.(zip|7z|chd|bin|cue|iso|nds|gba|gbc|gb|nes|sfc|smc|md|gen|z64|n64|v64|a26|a78|lnx|pce|ngp|ngc|ws|wsc|min|col|rom|mx1|mx2|dsk|tap|fds)$/i, "")
        .replace(/\s*\[.*?\]/g, "")  // remove [!], [b], [h], etc.
        .replace(/\s+/g, " ")
        .trim();
    }

    // Title-only: strip extension, brackets AND region parentheses
    function titleOnlyRomName(name) {
      return normalizeRomName(name)
        .replace(/\s*\(.*?\)/g, "")
        .replace(/[^a-z0-9]/g, "");
    }

    // Extract region tags from parentheses, e.g. "(USA, Europe)" → ["usa","europe"]
    function extractRegions(name) {
      var regions = [];
      var re = /\(([^)]+)\)/g;
      var m;
      while ((m = re.exec(name.toLowerCase())) !== null) {
        m[1].split(/\s*,\s*/).forEach(function (r) {
          regions.push(r.trim());
        });
      }
      return regions;
    }

    function getHashBadge(romName) {
      if (knownHashes.length === 0) {
        log.debug("[HashBadge] No known hashes loaded, skipping badge for: " + romName);
        return "";
      }
      var normRom = normalizeRomName(romName);
      var romRegions = extractRegions(romName);

      // Level 1: exact normalized match (name + region)
      var match = knownHashes.find(function (h) {
        return normalizeRomName(h.name) === normRom;
      });

      // Level 2: same base title AND at least one region in common
      if (!match) {
        var titleRom = titleOnlyRomName(romName);
        match = knownHashes.find(function (h) {
          if (titleOnlyRomName(h.name) !== titleRom) return false;
          if (romRegions.length === 0) return true; // no region info — allow
          var hashRegions = extractRegions(h.name);
          if (hashRegions.length === 0) return true; // hash has no region — allow
          return romRegions.some(function (r) { return hashRegions.indexOf(r) !== -1; });
        });
      }

      log.debug("[HashBadge] ROM: " + romName + " | normalized: " + normRom + " | match: " + (match ? match.name : "NONE"));

      if (match) {
        var labelsArr = Array.isArray(match.labels) ? match.labels : [];
        var labelTxt = labelsArr.length > 0 ? labelsArr.join(", ") : "";
        var tooltipLines = [
          "\u2705 Compatible with RetroAchievements",
          "MD5: " + match.md5,
        ];
        if (labelTxt) tooltipLines.push("Labels: " + labelTxt);
        if (match.name) tooltipLines.push("Hash: " + match.name);
        var tooltip = escapeHtml(tooltipLines.join("\n"));

        return ' <span class="enhanced-trophy-badge" title="' + tooltip + '"'
          + ' style="display:inline-flex;align-items:center;gap:2px;font-size:0.75em;padding:1px 6px;border-radius:4px;'
          + 'background:linear-gradient(135deg,#fbbf24,#f59e0b);color:#78350f;vertical-align:middle;margin-left:4px;'
          + 'cursor:help;font-weight:600;box-shadow:0 1px 2px rgba(0,0,0,0.15);transition:transform 0.15s;">'
          + '\uD83C\uDFC6 RA</span>';
      }

      return '';
    }

    if (enableRomSearch) {
      for (const prop in RAConsole) {
        if (RAConsole.hasOwnProperty(prop)) {
          if (RAConsole[prop] === consoleName) isAvailable = true;
        }
      }

      if ((isAvailable && tag === "") || (consoleName === RAConsole.ARCADE && tag !== "")) {
        // Check cache first
        getCachedRomResults(gameTitle, consoleName).then(function (cached) {
          if (cached) {
            // Use cached results
            results = cached.results;
            resultsDlcs = cached.resultsDlcs || [];
            collection = cached.collection || collection;
            log.info("[Cache] Using cached ROM results (" + results.length + " ROMs)");
            // Still need hashes for badges
            return fetchGameHashes(gameId).then(function (hashes) {
              knownHashes = hashes;
            }).catch(function () { knownHashes = []; }).then(function () {
              if (results.length > 0) {
                createDownloads();
              } else {
                createNoRomsNotification();
              }
              if (resultsDlcs.length > 0) createDlcs();
            });
          }

          // No cache — run search chain
          showLoading(divRoms, "Searching ROMs...");
        log.info("Starting ROM search for: " + gameTitle + " [" + consoleName + "]");
        var promise;

        if (enableEmuparadise && prioritizeEmuparadise) {
          collection.name = "Emuparadise";
          collection.url = "https://www.emuparadise.me/roms-isos-games.php";
          promise = searchEmuparadise();
        } else {
          promise = Promise.resolve();
        }

        var searchTimedOut = false;
        var SEARCH_TIMEOUT_MS = 30000; // 30 seconds max for entire search

        var searchChain = promise.then(() => {
          if (results.length === 0) {
            if (consoleName === RAConsole.ARCADE) {
              collection.name = "FB Neo Nightly";
              collection.url = "https://archive.org/download/2020_01_06_fbn";
              return searchArcade();
            }

            if (myrientCollectionDict[consoleName]) {
              const entry = myrientCollectionDict[consoleName];
              collection.name = entry.name;
              collection.url = entry.urls[0];
              const urls = Array.isArray(entry.urls) ? entry.urls : [entry.urls];
              return chainSearchMyrient(urls);
            } else {
              collection.name = "No-Intro 2016";
              collection.url = "https://archive.org/download/No-Intro-Collection_2016-01-03_Fixed";
              return searchNoIntro2016();
            }
          }
        })
        .then(() => {
          if (enableEmuparadise && results.length === 0) {
            collection.name = "Emuparadise";
            collection.url = "https://www.emuparadise.me/roms-isos-games.php";
            return searchEmuparadise();
          }
        })
        .then(() => {
          if (enableRomsFun && results.length === 0) {
            collection.name = "RomsFun";
            collection.url = "https://romsfun.com/roms/";
            return searchRomsFun();
          }
        })
        .then(() => {
          if (consoleName === RAConsole.PSP)
            return searchArchiveDlc("https://archive.org/download/PSP-DLC/%5BNo-Intro%5D%20PSP%20DLC/");
        })
        .then(() => {
          // Fetch hashes before rendering so we can badge matching ROMs
          return fetchGameHashes(gameId).then(function (hashes) {
            knownHashes = hashes;
            log.info("[HashCheck] knownHashes loaded: " + knownHashes.length + " hashes for game " + gameId);
          }).catch(function (err) {
            log.warn("[HashCheck] fetchGameHashes promise failed: " + err.message);
            knownHashes = [];
          });
        });

        var timeoutPromise = new Promise(function (_, reject) {
          setTimeout(function () {
            searchTimedOut = true;
            reject(new Error("ROM search timed out after " + (SEARCH_TIMEOUT_MS / 1000) + "s"));
          }, SEARCH_TIMEOUT_MS);
        });

        Promise.race([searchChain, timeoutPromise])
        .then(() => {
          hideLoading();
          // Cache results for next time
          setCachedRomResults(gameTitle, consoleName, results, resultsDlcs, collection.name, collection.url);
          if (results.length > 0) {
            log.info("Found " + results.length + " ROM(s)");
            createDownloads();
          } else {
            log.info("No ROMs found");
            createNoRomsNotification();
          }
          if (resultsDlcs.length > 0) createDlcs();
        })
        .catch(function (err) {
          hideLoading();
          log.warn("ROM search failed: " + err.message);
          if (results.length > 0) {
            setCachedRomResults(gameTitle, consoleName, results, resultsDlcs, collection.name, collection.url);
            createDownloads();
          } else {
            createNoRomsNotification();
          }
        });
        }); // end getCachedRomResults.then
      } else {
        log.debug("Searching roms for this system not supported: " + consoleName);
      }
    }

    // =========================================
    //         Create Content Functions
    // =========================================
    function createNoRomsNotification() {
      const searchQuery = encodeURIComponent(gameTitle + " " + consoleName);
      const archiveUrl = "https://archive.org/search?query=" + searchQuery;
      const myrientUrl = "https://myrient.erista.me/files/" + encodeURIComponent(consoleName);

      const h3 = document.createElement("h3");
      h3.textContent = "ROMs";
      h3.style.cssText = "font-size: 1.17em; font-weight: bold; margin-bottom: 0.5em;";
      divRoms.appendChild(h3);

      const msgDiv = document.createElement("div");
      msgDiv.style.cssText = "padding: 10px 12px; border-radius: 8px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);";
      msgDiv.innerHTML =
        '<p style="margin:0 0 8px;color:#a3a3a3;font-size:0.9em;">No ROMs found for <strong style="color:#e5e5e5;">' + escapeHtml(gameTitle) + '</strong>.</p>' +
        '<p style="margin:0 0 4px;color:#a3a3a3;font-size:0.85em;">Try searching manually:</p>' +
        '<div style="display:flex;flex-direction:column;gap:4px;">' +
          '<a href="' + archiveUrl + '" target="_blank" rel="noopener" style="color:#5b9bd5;font-size:0.85em;text-decoration:none;">&#x1F50D; Archive.org</a>' +
          '<a href="' + myrientUrl + '" target="_blank" rel="noopener" style="color:#5b9bd5;font-size:0.85em;text-decoration:none;">&#x1F50D; Myrient</a>' +
          '<a href="https://romsfun.com/?s=' + searchQuery + '" target="_blank" rel="noopener" style="color:#5b9bd5;font-size:0.85em;text-decoration:none;">&#x1F50D; RomsFun</a>' +
        '</div>';
      divRoms.appendChild(msgDiv);
      makeCollapsible(divRoms, 'roms');
    }

    function createDownloads() {
      const style = document.createElement("style");
      style.textContent = `
        #enhanced-romsdl .dl-link {
          color: #5b9bd5;
          text-decoration: none;
        }
        #enhanced-romsdl .dl-link:hover {
          text-decoration: underline;
        }
        #enhanced-romsdl .rom-row {
          display: flex;
          align-items: center;
          padding: 2px 0;
        }
        .enhanced-trophy-badge:hover {
          transform: scale(1.15);
        }
      `;
      document.head.appendChild(style);

      const h3 = document.createElement("h3");
      h3.textContent = "ROMs";
      h3.style.cssText = "font-size: 1.17em; font-weight: bold; margin-bottom: 0.5em;";
      divRoms.appendChild(h3);

      for (var i = 0; i < results.length; i++) {
        let dlLink = results[i].url.replace(/ /g, "%20");
        const wrapper = document.createElement("div");
        wrapper.className = "rom-row";
        var badge = getHashBadge(results[i].name);
        wrapper.innerHTML = '<a class="dl-link" href="' + encodeURI(dlLink) + '" target="_blank" rel="noopener">' + escapeHtml(removeExt(results[i].name)) + '</a>' + badge;
        divRoms.appendChild(wrapper);
      }

      if (collection.url !== "") {
        const fromDiv = document.createElement("div");
        fromDiv.style.marginTop = "1em";
        fromDiv.innerHTML = `From <a href="${encodeURI(collection.url)}" style="color: #5b9bd5;">${escapeHtml(collection.name)}</a>`;
        divRoms.appendChild(fromDiv);
      }
      makeCollapsible(divRoms, 'roms');
    }

    function createDlcs() {
      const h3 = document.createElement("h3");
      h3.textContent = "DLCs";
      h3.style.cssText = "font-size: 1.17em; font-weight: bold; margin-top: 1em; margin-bottom: 0.5em;";
      divRoms.appendChild(h3);

      for (var i = 0; i < resultsDlcs.length; i++) {
        let dlLink = resultsDlcs[i].url.replace(/ /g, "%20");
        const a = document.createElement("a");
        a.className = "dl-link";
        a.href = dlLink;
        a.target = "_blank";
        a.rel = "noopener";
        a.textContent = removeExt(resultsDlcs[i].name);
        divRoms.appendChild(a);
      }

      const fromDiv = document.createElement("div");
      fromDiv.style.marginTop = "1em";
      fromDiv.innerHTML = `From <a href="https://archive.org/download/PSP-DLC/%5BNo-Intro%5D%20PSP%20DLC" style="color: #5b9bd5;">PSP-DLC (No-Intro)</a>`;
      divRoms.appendChild(fromDiv);
    }

    function createSpeedrun() {
      const h3 = document.createElement("h3");
      h3.textContent = "World Records";
      h3.style.cssText = "font-size: 1.17em; font-weight: bold; margin-bottom: 0.5em;";
      divSpeedruncom.appendChild(h3);

      if (srRuns.length > 0) {
        srRuns.forEach((runsData) => {
          const div = document.createElement("div");
          div.innerHTML = `<a href="${encodeURI(runsData.link)}" style="color: #5b9bd5;">${escapeHtml(runsData.category)}:</a> ${escapeHtml(runsData.time)} by ${escapeHtml(runsData.runner)}`;
          divSpeedruncom.appendChild(div);
        });
      } else {
        const div = document.createElement("div");
        div.textContent = "Couldn't find this game on Speedrun.com";
        divSpeedruncom.appendChild(div);
      }
      makeCollapsible(divSpeedruncom, 'speedrun');
    }

    function createVideo() {
      if (srVideoUrl === "") return;
      // Prevent duplicate video iframes
      if (document.querySelector("iframe.enhanced-video")) return;
      log.debug("Creating video with URL: " + srVideoUrl);

      // Insert after the screenshots container in the main article
      const gameShow = document.querySelector('[data-testid="game-show"]');
      if (gameShow && gameShow.firstElementChild) {
        const iframe = document.createElement("iframe");
        iframe.className = "enhanced-video";
        iframe.style.cssText = "display: block; width: 100%; height: 315px; padding-bottom: 1em; border: none; border-radius: 0.5rem;";
        iframe.src = srVideoUrl;
        iframe.allowFullscreen = true;
        iframe.setAttribute("autoplay", "false");
        // Insert after the screenshots section
        const screenshotsDiv = gameShow.firstElementChild;
        screenshotsDiv.after(iframe);
      }
    }

    // =========================================
    //         Speedrun.com Functions
    // =========================================
    function getSrConsoleId(cName) {
      const map = {
        [RAConsole.ATARI2600]: SRConsole.ATARI2600,
        [RAConsole.ATARI7800]: SRConsole.ATARI7800,
        [RAConsole.APPLEII]: SRConsole.APPLEII,
        [RAConsole.ARCADE]: SRConsole.ARCADE,
        [RAConsole.COLECO]: SRConsole.COLECOVISION,
        [RAConsole.DREAMCAST]: SRConsole.DREAMCAST,
        [RAConsole.GAMEBOY]: SRConsole.GAMEBOY,
        [RAConsole.GAMEBOYADVANCE]: SRConsole.GAMEBOYADVANCE,
        [RAConsole.GAMEBOYCOLOR]: SRConsole.GAMEBOYCOLOR,
        [RAConsole.MEGADRIVE]: SRConsole.MEGADRIVE,
        [RAConsole.GAMEGEAR]: SRConsole.GAMEGEAR,
        [RAConsole.NINTENDO64]: SRConsole.NINTENDO64,
        [RAConsole.SATURN]: SRConsole.SEGASATURN,
        [RAConsole.MASTERSYSTEM]: SRConsole.MASTERSYSTEM,
        [RAConsole.NINTENDODS]: SRConsole.NINTENDODS,
        [RAConsole.NEC8800]: SRConsole.NEC8800,
        [RAConsole.NEOGEOPOCKET]: SRConsole.NEOGEOPOCKETCOLOR,
        [RAConsole.NES]: SRConsole.NES,
        [RAConsole.P3DO]: SRConsole.PANASONIC3D0,
        [RAConsole.PCENGINE]: SRConsole.PCENGINE,
        [RAConsole.POKEMINI]: SRConsole.POKÉMONMINI,
        [RAConsole.PS1]: SRConsole.PS1,
        [RAConsole.PSP]: SRConsole.PLAYSTATIONPORTABLE,
        [RAConsole.SEGA32X]: SRConsole.SEGA32X,
        [RAConsole.SEGACD]: SRConsole.SEGACD,
        [RAConsole.SG1000]: SRConsole.MASTERSYSTEM,
        [RAConsole.SNES]: SRConsole.SNES,
        [RAConsole.VIRTUALBOY]: SRConsole.VIRTUALBOY,
        [RAConsole.AMSTRADCPC]: SRConsole.AMSTRADCPC,
      };
      return map[cName] || "";
    }

    function getSpeedruns(gameName) {
      var consoleId = getSrConsoleId(consoleName);
      var srSearchUrl = encodeURI(srRoot + "games?name=" + gameName + "&platform=" + consoleId);

      return gmFetch(srSearchUrl)
      .then(function (gamesResponse) {
        var gamesData = JSON.parse(gamesResponse.responseText).data;
        if (gamesData.length > 0) {
          srGamelink = gamesData[0].weblink;
          srGameId = gamesData[0].id;
          return gamesData[0].links[3].uri;
        } else {
          throw new Error("Couldn't find this game on Speedrun.com (" + srSearchUrl + ").");
        }
      })
      .then(function (link) {
        return gmFetch(link).then(function (response) {
          return JSON.parse(response.responseText).data;
        });
      })
      .then(function (categories) {
        return Promise.all(categories.map(function (category) {
          return gmFetch(srRoot + "runs?game=" + srGameId + "&category=" + category.id + "&status=verified")
          .then(function (runsResponse) {
            var runsData = JSON.parse(runsResponse.responseText).data[0];
            if (runsData != undefined && runsData.status.status !== "rejected") {
              if (srVideoUrl === "" && runsData.videos)
                srVideoUrl = toEmbedUrl(runsData.videos.links[0].uri);
            }
            return runsData;
          })
          .then(function (runsData) {
            if (runsData == undefined) return false;
            var isGuest = runsData.players[0].rel === "guest";

            return gmFetch(srRoot + "users/" + runsData.players[0].id)
            .then(function (userRes) {
              var userData = JSON.parse(userRes.responseText).data;
              srRuns.push({
                category: category.name,
                time: parseIso8601(runsData.times.primary),
                runner: isGuest ? runsData.players[0].name : userData.names.international,
                link: runsData.videos ? runsData.videos.links[0].uri : ""
              });
              return true;
            }).catch(function (err) {
              log.warn("Failed to fetch user data: " + err.message);
              srRuns.push({
                category: category.name,
                time: parseIso8601(runsData.times.primary),
                runner: isGuest ? runsData.players[0].name : "Unknown",
                link: runsData.videos ? runsData.videos.links[0].uri : ""
              });
              return true;
            });
          })
          .catch(function (err) {
            log.warn("Failed to fetch runs for category: " + err.message);
            return false;
          });
        }))
        .then(function () {
          if (enableSpeedrun) createSpeedrun();
          if (enableGameplayVideo) createVideo();
        });
      })
      .catch(function (err) {
        log.error("Speedrun fetch error: " + err.message);
        if (enableSpeedrun) {
          var div = document.createElement("div");
          div.textContent = "Couldn't find this game on Speedrun.com";
          divSpeedruncom.appendChild(div);
        }
      });
    }

    // =========================================
    //       Arcade Search Function
    // =========================================
    function searchArcade() {
      var mainDir = "//archive.org/download/2020_01_06_fbn/roms/arcade.zip/arcade%2F";
      var datDir = "https://raw.githubusercontent.com/libretro/FBNeo/master/dats/FinalBurn%20Neo%20(ClrMame%20Pro%20XML%2C%20Arcade%20only).dat";

      return gmFetch(datDir).then(function (response) {
        var xmlDoc = parseXml(response.responseText);

        xmlDoc.querySelectorAll("game").forEach(function (el) {
          var descEl = el.querySelector("description");
          var name = descEl ? descEl.textContent : "";
          if (tag === "" && name.toLowerCase().includes("hack")) return;
          if (tag === "Hack" && !name.toLowerCase().includes("hack")) return;
          if (refinedCompare(name, gameTitle)) {
            results.push({
              name: name,
              url: mainDir + el.getAttribute("name") + ".zip"
            });
          }
        });

        if (results.length === 0) {
          xmlDoc.querySelectorAll("game").forEach(function (el) {
            var descEl = el.querySelector("description");
            var name = descEl ? descEl.textContent : "";
            if (tag === "" && name.toLowerCase().includes("hack")) return;
            if (tag === "Hack" && !name.toLowerCase().includes("hack")) return;
            if (compare(name, gameTitle)) {
              results.push({
                name: name,
                url: mainDir + el.getAttribute("name") + ".zip"
              });
            }
          });
        }
        return true;
      }).catch(function (err) {
        log.warn("Arcade search failed: " + err.message);
        return true;
      });
    }

    // =========================================
    //          Myrient Search Function
    // =========================================
    function chainSearchMyrient(urls) {
      let promise = searchMyrient(urls[0]);
      for (let i = 1; i < urls.length; i++) {
        promise = promise.then(() => {
          if (results.length === 0) {
            return searchMyrient(urls[i]);
          }
        });
      }
      return promise;
    }

    function searchMyrient(mainDir) {
      return gmFetch(mainDir).then(function (response) {
        var doc = parseHtml(response.responseText);
        var cells = doc.querySelectorAll("td > :first-child");

        cells.forEach(function (el) {
          var textContent = el.textContent;
          var match = /([^\/]+)\/?$/g.exec(textContent);
          if (!match) return;
          var title = match[1];

          if (refinedCompare(title, gameTitle)) {
            var fullUrl = mainDir.endsWith("/") ?
              mainDir + el.getAttribute("href") : mainDir + "/" + el.getAttribute("href");
            results.push({ name: title, url: fullUrl });
          }
        });

        if (results.length === 0) {
          cells.forEach(function (el) {
            var textContent = el.textContent;
            var match = /([^\/]+)\/?$/g.exec(textContent);
            if (!match) return;
            var title = match[1];

            if (compare(title, gameTitle)) {
              var fullUrl = mainDir.endsWith("/") ?
                mainDir + el.getAttribute("href") : mainDir + "/" + el.getAttribute("href");
              results.push({ name: title, url: fullUrl });
            }
          });
        }
        return true;
      }).catch(function (err) {
        log.warn("Myrient search failed: " + err.message);
        return true;
      });
    }

    // =========================================
    //      No-Intro 2016 Search Function
    // =========================================
    function searchNoIntro2016() {
      var mainDir = "https://archive.org/download/No-Intro-Collection_2016-01-03_Fixed/";
      var consoleDir = "";
      var secondaryConsoleDir = "";

      const consoleDirMap = {
        [RAConsole.SNES]: "Nintendo - Super Nintendo Entertainment System",
        [RAConsole.NES]: "Nintendo - Nintendo Entertainment System",
        [RAConsole.GAMEBOY]: "Nintendo - Game Boy",
        [RAConsole.GAMEBOYCOLOR]: "Nintendo - Game Boy Color",
        [RAConsole.GAMEBOYADVANCE]: "Nintendo - Game Boy Advance",
        [RAConsole.NINTENDO64]: "Nintendo - Nintendo 64",
        [RAConsole.ATARI7800]: "Atari - 7800",
        [RAConsole.PCENGINE]: "NEC - PC Engine - TurboGrafx 16",
        [RAConsole.MEGADRIVE]: "Sega - Mega Drive - Genesis",
        [RAConsole.MASTERSYSTEM]: "Sega - Master System - Mark III",
        [RAConsole.GAMEGEAR]: "Sega - Game Gear",
        [RAConsole.POKEMINI]: "Nintendo - Pokemon Mini",
        [RAConsole.VIRTUALBOY]: "Nintendo - Virtual Boy",
        [RAConsole.SG1000]: "Sega - SG-1000",
        [RAConsole.COLECO]: "Coleco - ColecoVision",
        [RAConsole.VECTREX]: "GCE - Vectrex",
      };

      consoleDir = consoleDirMap[consoleName] || "";

      if (consoleName === RAConsole.NEOGEOPOCKET) {
        consoleDir = "SNK - Neo Geo Pocket";
        secondaryConsoleDir = "SNK - Neo Geo Pocket Color";
      }
      if (consoleName === RAConsole.MSX) {
        consoleDir = "Microsoft - MSX";
        secondaryConsoleDir = "Microsoft - MSX 2";
      }
      if (consoleName === RAConsole.WONDERSWAN) {
        consoleDir = "Bandai - WonderSwan Color";
        secondaryConsoleDir = "Bandai - WonderSwan Color";
      }

      consoleDir = consoleDir.replace(/ /g, "%20").concat(".zip/");
      secondaryConsoleDir = secondaryConsoleDir.replace(/ /g, "%20").concat(".zip/");

      function parseNoIntroResults(responseText) {
        var doc = parseHtml(responseText);
        doc.querySelectorAll("td > :first-child").forEach(function (el) {
          if (refinedCompare(el.textContent, gameTitle)) {
            results.push({ name: el.textContent, url: el.getAttribute("href") });
          }
        });
        if (results.length === 0) {
          doc.querySelectorAll("td > :first-child").forEach(function (el) {
            if (compare(el.textContent, gameTitle)) {
              results.push({ name: el.textContent, url: el.getAttribute("href") });
            }
          });
        }
      }

      return gmFetch(mainDir + consoleDir).then(function (response) {
        parseNoIntroResults(response.responseText);
        return true;
      }).catch(function (err) {
        log.warn("NoIntro2016 primary search failed: " + err.message);
        return true;
      })
      .then(function () {
        if (secondaryConsoleDir !== ".zip/") {
          return gmFetch(mainDir + secondaryConsoleDir).then(function (response) {
            parseNoIntroResults(response.responseText);
            return true;
          }).catch(function (err) {
            log.warn("NoIntro2016 secondary search failed: " + err.message);
            return true;
          });
        }
      });
    }

    // =========================================
    //      Archive.org Generic Search
    // =========================================
    function searchArchive(mainDir) {
      return gmFetch(mainDir).then(function (response) {
        var doc = parseHtml(response.responseText);
        var cells = doc.querySelectorAll("td > :first-child");

        cells.forEach(function (el) {
          var match = /([^\/]+)\/?$/g.exec(el.textContent);
          if (!match) return;
          var title = match[1];
          var href = el.getAttribute("href") || "";
          var fullUrl = href.startsWith("//archive.org/download/") ?
            href : mainDir + "/" + href;

          if (refinedCompare(title, gameTitle)) {
            results.push({ name: title, url: fullUrl });
          }
        });

        if (results.length === 0) {
          cells.forEach(function (el) {
            var match = /([^\/]+)\/?$/g.exec(el.textContent);
            if (!match) return;
            var title = match[1];
            var href = el.getAttribute("href") || "";
            var fullUrl = href.startsWith("//archive.org/download/") ?
              href : mainDir + "/" + href;

            if (compare(title, gameTitle)) {
              results.push({ name: title, url: fullUrl });
            }
          });
        }
        return true;
      }).catch(function (err) {
        log.warn("Archive search failed: " + err.message);
        return true;
      });
    }

    // =========================================
    //       Archive.org DLC Search
    // =========================================
    function searchArchiveDlc(mainDir) {
      return gmFetch(mainDir).then(function (response) {
        var doc = parseHtml(response.responseText);
        var cells = doc.querySelectorAll("td > :first-child");

        cells.forEach(function (el) {
          var match = /([^\/]+)\/?$/g.exec(el.textContent);
          if (!match) return;
          var title = match[1];
          var href = el.getAttribute("href") || "";
          var fullUrl = href.startsWith("//archive.org/download/") ?
            href : mainDir + "/" + href;

          if (refinedCompare(title, gameTitle)) {
            resultsDlcs.push({ name: title, url: fullUrl });
          }
        });

        if (resultsDlcs.length === 0) {
          cells.forEach(function (el) {
            var match = /([^\/]+)\/?$/g.exec(el.textContent);
            if (!match) return;
            var title = match[1];
            var href = el.getAttribute("href") || "";
            var fullUrl = href.startsWith("//archive.org/download/") ?
              href : mainDir + "/" + href;

            if (compare(title, gameTitle)) {
              resultsDlcs.push({ name: title, url: fullUrl });
            }
          });
        }
        return true;
      }).catch(function (err) {
        log.warn("Archive DLC search failed: " + err.message);
        return true;
      });
    }

    // =========================================
    //       Emuparadise Search Function
    // =========================================
    function searchEmuparadise() {
      var mainDir = "https://www.emuparadise.me";
      var consoleUrlMap = {
        [RAConsole.SNES]: "Super_Nintendo_Entertainment_System_(SNES)_ROMs/List-All-Titles/5",
        [RAConsole.NES]: "Nintendo_Entertainment_System_ROMs/List-All-Titles/13",
        [RAConsole.GAMEBOY]: "Nintendo_Game_Boy_ROMs/List-All-Titles/12",
        [RAConsole.GAMEBOYCOLOR]: "Nintendo_Game_Boy_Color_ROMs/List-All-Titles/11",
        [RAConsole.GAMEBOYADVANCE]: "Nintendo_Gameboy_Advance_ROMs/List-All-Titles/31",
        [RAConsole.NINTENDO64]: "Nintendo_64_ROMs/List-All-Titles/9",
        [RAConsole.GAMECUBE]: "Nintendo_Gamecube_ISOs/List-All-Titles/42",
        [RAConsole.NINTENDODS]: "Nintendo_DS_ROMs/List-All-Titles/32",
        [RAConsole.MEGADRIVE]: "Sega_Genesis_-_Sega_Megadrive_ROMs/List-All-Titles/6",
        [RAConsole.MASTERSYSTEM]: "Sega_Master_System_ROMs/List-All-Titles/15",
        [RAConsole.SEGA32X]: "Sega_32X_ROMs/61",
        [RAConsole.SATURN]: "Sega_Saturn_ISOs/List-All-Titles/3",
        [RAConsole.SEGACD]: "Sega_CD_ISOs/List-All-Titles/10",
        [RAConsole.GAMEGEAR]: "Sega_Game_Gear_ROMs/List-All-Titles/14",
        [RAConsole.NEOGEOPOCKET]: "Neo_Geo_Pocket_-_Neo_Geo_Pocket_Color_(NGPx)_ROMs/38",
        [RAConsole.ATARI2600]: "Atari_2600_ROMs/List-All-Titles/49",
        [RAConsole.ATARI7800]: "Atari_7800_ROMs/47",
        [RAConsole.PCENGINE]: "PC_Engine_-_TurboGrafx16_ROMs/List-All-Titles/16",
        [RAConsole.APPLEII]: "Apple_][_ROMs/List-All-Titles/24",
        [RAConsole.PS1]: "Sony_Playstation_ISOs/List-All-Titles/2",
        [RAConsole.PS2]: "Sony_Playstation_2_ISOs/List-All-Titles/41",
        [RAConsole.PSP]: "PSP_ISOs/List-All-Titles/44",
        [RAConsole.P3DO]: "Panasonic_3DO_(3DO_Interactive_Multiplayer)_ISOs/List-All-Titles/20",
      };

      var consoleUrl = consoleUrlMap[consoleName];
      if (!consoleUrl) return Promise.resolve();

      return gmFetch(mainDir + "/" + consoleUrl).then(function (response) {
        var doc = parseHtml(response.responseText);
        var items = doc.querySelectorAll(".index.gamelist");

        function buildDownloadPageUrl(href) {
          // href like /Console_ROMs/Game_Name/151200 — link to the download page
          var clean = href.replace(/\/+$/, '');
          return mainDir + clean;
        }

        items.forEach(function (el) {
          var href = el.getAttribute("href") || "";
          if (!href) return;
          if (refinedCompare(el.textContent, gameTitle)) {
            results.push({
              name: el.textContent,
              url: buildDownloadPageUrl(href)
            });
          }
        });

        if (results.length === 0) {
          items.forEach(function (el) {
            var href = el.getAttribute("href") || "";
            if (!href) return;
            if (compare(el.textContent, gameTitle)) {
              results.push({
                name: el.textContent,
                url: buildDownloadPageUrl(href)
              });
            }
          });
        }
        return true;
      }).catch(function (err) {
        log.warn("Emuparadise search failed: " + err.message);
        return true;
      });
    }

    // =========================================
    //       RomsFun Search Function
    // =========================================
    const romsfunConsoleSlug = {
      [RAConsole.SNES]: "super-nintendo",
      [RAConsole.NES]: "nintendo-nes",
      [RAConsole.GAMEBOY]: "game-boy",
      [RAConsole.GAMEBOYCOLOR]: "game-boy-color",
      [RAConsole.GAMEBOYADVANCE]: "game-boy-advance",
      [RAConsole.NINTENDO64]: "nintendo-64",
      [RAConsole.GAMECUBE]: "gamecube",
      [RAConsole.NINTENDODS]: "nintendo-ds",
      [RAConsole.NINTENDODSI]: "nintendo-dsi",
      [RAConsole.PS1]: "playstation",
      [RAConsole.PS2]: "playstation-2",
      [RAConsole.PSP]: "psp",
      [RAConsole.MEGADRIVE]: "sega-genesis",
      [RAConsole.MASTERSYSTEM]: "sega-master-system",
      [RAConsole.GAMEGEAR]: "game-gear",
      [RAConsole.SATURN]: "sega-saturn",
      [RAConsole.DREAMCAST]: "dreamcast",
      [RAConsole.SEGACD]: "sega-cd",
      [RAConsole.SEGA32X]: "sega-32x",
      [RAConsole.ATARI2600]: "atari-2600",
      [RAConsole.ATARI7800]: "atari-7800",
      [RAConsole.PCENGINE]: "pc-engine",
      [RAConsole.NEOGEOPOCKET]: "neo-geo-pocket",
      [RAConsole.VIRTUALBOY]: "virtual-boy",
      [RAConsole.WII]: "wii",
      [RAConsole.ARCADE]: "arcade",
      [RAConsole.MSX]: "msx",
      [RAConsole.P3DO]: "3do",
      [RAConsole.COLECO]: "colecovision",
      [RAConsole.ATARILYNX]: "atari-lynx",
      [RAConsole.WONDERSWAN]: "wonderswan",
      [RAConsole.POKEMINI]: "pokemon-mini",
    };

    function searchRomsFun() {
      var searchUrl = "https://romsfun.com/wp-json/wp/v2/rom?search=" + encodeURIComponent(gameTitle) + "&per_page=10";
      var expectedSlug = romsfunConsoleSlug[consoleName] || "";

      return gmFetch(searchUrl, 15000).then(function (resp) {
        var data = JSON.parse(resp.responseText);
        if (!Array.isArray(data) || data.length === 0) return;

        // First pass: refined match with console filter
        data.forEach(function (rom) {
          var romTitle = (rom.title && rom.title.rendered) || "";
          var romLink = rom.link || "";
          var romSlug = rom.slug || "";
          var romId = rom.id;

          // Filter by console slug in the URL if available
          if (expectedSlug && !romLink.includes("/roms/" + expectedSlug + "/")) return;

          if (refinedCompare(romTitle, gameTitle)) {
            results.push({
              name: romTitle + " (RomsFun)",
              url: "https://romsfun.com/download/" + romSlug + "-" + romId
            });
          }
        });

        // Second pass: loose match if nothing found
        if (results.length === 0) {
          data.forEach(function (rom) {
            var romTitle = (rom.title && rom.title.rendered) || "";
            var romLink = rom.link || "";
            var romSlug = rom.slug || "";
            var romId = rom.id;

            if (expectedSlug && !romLink.includes("/roms/" + expectedSlug + "/")) return;

            if (compare(romTitle, gameTitle)) {
              results.push({
                name: romTitle + " (RomsFun)",
                url: "https://romsfun.com/download/" + romSlug + "-" + romId
              });
            }
          });
        }
        return true;
      }).catch(function (err) {
        log.warn("RomsFun search failed: " + err.message);
        return true;
      });
    }

    // =========================================
    //           Utility Functions
    // =========================================
    function refinedCompare(a, b) {
      return simplify_title(a) === simplify_title(b);
    }

    function compare(a, b) {
      return simplify_title(a).includes(simplify_title(b));
    }

    function simplify_title(str) {
      if (consoleName === RAConsole.DREAMCAST)
        str = str.replace(/v[0-9].[0-9]{3}/gs, "");

      return str
        .replace(/\.(zip|7z)$/, "")
        .replace(/^The /g, '')
        .replace(", The", '')
        .replace(/'s/gs, '')
        .replace('&', 'and')
        .replace(/:|-| |\.|!|\?|\/|'/gs, '')
        .replace(/(\r\n|\n|\r)/gs, "")
        .split('|')[0]
        .replace(',', "")
        .replace(/\(.+\)/gs, "")
        .replace(/\[.+\]/gs, "")
        .toLowerCase();
    }

    function removeExt(str) {
      return str.replace(/\.(zip|7z|chd)$/, "");
    }

    function parseIso8601(time) {
      var parsed = "";
      let regex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
      let groups = regex.exec(time);
      if (groups[6] != undefined) parsed += groups[6] + "h ";
      if (groups[7] != undefined) parsed += groups[7] + "m ";
      if (groups[8] != undefined) parsed += groups[8] + "s ";
      return parsed;
    }

    function toEmbedUrl(url) {
      if (url.includes("twitch") || url.includes("youtu")) {
        var regexYoutube = /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)?([^\s&]+)/;
        var regexTwitch = /(?:https?:\/{2})?www\.twitch\.tv\/(?:[\S]+\/)?([\]?)?\/([\d]+)/;
        if (url.match(regexYoutube) != undefined) {
          return "https://www.youtube.com/embed/" + url.match(regexYoutube)[1];
        } else if (url.match(regexTwitch) != undefined) {
          return "https://player.twitch.tv/?video=" + url.match(regexTwitch)[2] + "&parent=retroachievements.org&autoplay=false";
        }
      }
      return "";
    }
  }
  } // end init()

  // =========================================
  //   User Profile Pagination (standalone)
  // =========================================
  // Runs outside init() to also work on legacy Blade pages
  async function initUserPagination() {
    var page = location.pathname;
    var userMatch = page.match(/^\/user\/([^\/?#]+)/);
    if (!userMatch) return;

    var targetUser = decodeURIComponent(userMatch[1]);
    var apiKey = await GM_getValue("raApiKey", "");
    var enableRarityIndicator = await GM_getValue("enableRarityIndicator", true);
    if (!apiKey) {
      log.debug("User pagination: no API key configured, skipping");
      return;
    }

    // Wait for the page to render
    await new Promise(function (resolve) {
      if (document.readyState === "complete") resolve();
      else window.addEventListener("load", resolve);
    });

    // Small extra delay for Blade components to render
    await new Promise(function (r) { setTimeout(r, 500); });

    // Find the "Last X Games Played" heading
    var headings = document.querySelectorAll("h2");
    var recentH2 = null;
    for (var i = 0; i < headings.length; i++) {
      if (/Last.*Games?\s*Played/i.test(headings[i].textContent)) {
        recentH2 = headings[i];
        break;
      }
    }
    if (!recentH2) {
      log.debug("User pagination: could not find 'Last Games Played' heading");
      return;
    }

    // The structure is: <div class="my-8"> > <div> > <h2> + <div class="flex flex-col gap-y-1">
    // We need the component root (h2's parent) and the game list inside it
    var componentRoot = recentH2.parentElement;
    var outerWrapper = componentRoot ? componentRoot.parentElement : null;
    var existingList = componentRoot ? componentRoot.querySelector("div.flex.flex-col") : null;

    if (!existingList) {
      log.debug("User pagination: could not find game list container");
      return;
    }

    // Already injected?
    if (document.getElementById("enhanced-pagination")) return;

    // Remove existing "more" link if present (it's a sibling of componentRoot inside outerWrapper)
    if (outerWrapper) {
      var moreLink = outerWrapper.querySelector('a[href*="?g="]');
      if (moreLink) {
        var moreLinkParent = moreLink.closest("div.text-right") || moreLink.parentElement;
        if (moreLinkParent && moreLinkParent !== outerWrapper) moreLinkParent.remove();
        else moreLink.remove();
      }
    }

    // Inject pagination styles
    if (!document.getElementById("enhanced-pagination-style")) {
      var style = document.createElement("style");
      style.id = "enhanced-pagination-style";
      style.textContent = `
        @keyframes enhanced-spin { to { transform: rotate(360deg); } }
        .enhanced-pagination {
          display: flex;
          align-items: center;
          justify-content: center;
          gap: 6px;
          margin-top: 12px;
          flex-wrap: wrap;
        }
        .enhanced-pagination button {
          padding: 4px 12px;
          border-radius: 6px;
          border: 1px solid rgba(255,255,255,0.15);
          background: transparent;
          color: #a3a3a3;
          font-size: 0.85em;
          cursor: pointer;
          transition: all 0.2s;
        }
        .enhanced-pagination button:hover:not(:disabled) {
          background: rgba(255,255,255,0.08);
          color: #e5e5e5;
          border-color: rgba(255,255,255,0.25);
        }
        .enhanced-pagination button.active {
          background: #3b82f6;
          color: #fff;
          border-color: #3b82f6;
        }
        .enhanced-pagination button:disabled {
          opacity: 0.4;
          cursor: default;
        }
        .enhanced-pagination .page-info {
          color: #a3a3a3;
          font-size: 0.8em;
        }
        .enhanced-games-list {
          display: flex;
          flex-direction: column;
          gap: 4px;
          width: 100%;
        }
        @keyframes enhanced-skeleton-pulse {
          0%, 100% { opacity: 0.4; }
          50% { opacity: 1; }
        }
        .enhanced-skeleton-card {
          display: flex;
          align-items: center;
          gap: 10px;
          padding: 8px;
          border-radius: 6px;
          background: rgba(255,255,255,0.03);
          animation: enhanced-skeleton-pulse 1.5s ease-in-out infinite;
        }
        .enhanced-skeleton-img {
          width: 58px;
          height: 58px;
          border-radius: 4px;
          background: rgba(255,255,255,0.08);
          flex-shrink: 0;
        }
        .enhanced-skeleton-content {
          flex: 1;
          display: flex;
          flex-direction: column;
          gap: 6px;
        }
        .enhanced-skeleton-line {
          height: 12px;
          border-radius: 4px;
          background: rgba(255,255,255,0.08);
        }
        .enhanced-skeleton-line.w-60 { width: 60%; }
        .enhanced-skeleton-line.w-40 { width: 40%; }
        .enhanced-skeleton-line.w-30 { width: 30%; }
        .enhanced-skeleton-bar {
          height: 8px;
          width: 100%;
          border-radius: 4px;
          background: rgba(255,255,255,0.06);
          margin-top: 2px;
        }
        /* Enhanced User Stats */
        .stats-root { padding: 0; }
        .stats-title { font-size: 11px; font-weight: 500; color: #9ca3af; letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: 14px; }
        .stats-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
        .stats-grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
        .metric-card { background: rgba(255,255,255,0.04); border-radius: 8px; padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; }
        .card-top { display: flex; align-items: center; justify-content: space-between; }
        .metric-label { font-size: 11px; color: #9ca3af; }
        .card-icon { font-size: 14px; line-height: 1; opacity: 0.7; }
        .metric-value { font-size: 20px; font-weight: 500; line-height: 1.1; }
        .metric-value-sm { font-size: 16px; font-weight: 500; line-height: 1.1; }
        .metric-sub { font-size: 11px; color: #9ca3af; }
        .stats-divider { border: none; border-top: 0.5px solid rgba(255,255,255,0.1); margin: 14px 0; }
        .section-label { font-size: 11px; font-weight: 500; color: #9ca3af; letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: 8px; }
        /* Player Insights Dashboard */
        .enhanced-dashboard {
          margin-bottom: 16px;
          display: flex;
          flex-direction: column;
          gap: 16px;
        }
        .enhanced-dashboard-title {
          font-size: 1.1rem;
          font-weight: 700;
          color: #e4e4e7;
          display: flex;
          align-items: center;
          gap: 8px;
        }
        .enhanced-stats-row {
          display: grid;
          grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
          gap: 10px;
        }
        .enhanced-stat-card {
          background: rgba(255,255,255,0.04);
          border: 1px solid rgba(255,255,255,0.08);
          border-radius: 8px;
          padding: 12px 14px;
          display: flex;
          flex-direction: column;
          gap: 2px;
          transition: border-color 0.2s;
        }
        .enhanced-stat-card:hover {
          border-color: rgba(255,255,255,0.15);
        }
        .enhanced-stat-value {
          font-size: 1.4rem;
          font-weight: 700;
          color: #e4e4e7;
          line-height: 1.2;
        }
        .enhanced-stat-label {
          font-size: 0.7rem;
          color: #737373;
          text-transform: uppercase;
          letter-spacing: 0.5px;
        }
        .enhanced-dashboard-section {
          background: rgba(255,255,255,0.02);
          border: 1px solid rgba(255,255,255,0.06);
          border-radius: 10px;
          padding: 14px 16px;
        }
        .enhanced-dashboard-section-title {
          font-size: 0.85rem;
          font-weight: 600;
          color: #a3a3a3;
          margin-bottom: 10px;
          display: flex;
          align-items: center;
          gap: 6px;
        }
        .enhanced-almost-item {
          display: flex;
          align-items: center;
          gap: 10px;
          padding: 8px 0;
          border-bottom: 1px solid rgba(255,255,255,0.04);
        }
        .enhanced-almost-item:last-child { border-bottom: none; }
        .enhanced-almost-img {
          width: 40px;
          height: 40px;
          border-radius: 4px;
          flex-shrink: 0;
        }
        .enhanced-almost-info {
          flex: 1;
          min-width: 0;
        }
        .enhanced-almost-name {
          font-size: 0.8rem;
          color: #e4e4e7;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
          text-decoration: none;
        }
        .enhanced-almost-name:hover { color: #60a5fa; }
        .enhanced-almost-meta {
          font-size: 0.7rem;
          color: #737373;
        }
        .enhanced-almost-bar-bg {
          width: 100%;
          height: 6px;
          border-radius: 3px;
          background: rgba(255,255,255,0.06);
          margin-top: 3px;
        }
        .enhanced-almost-bar-fill {
          height: 100%;
          border-radius: 3px;
          background: linear-gradient(90deg, #3b82f6, #60a5fa);
          transition: width 0.5s ease;
        }

        .enhanced-dashboard-skeleton {
          animation: enhanced-skeleton-pulse 1.5s ease-in-out infinite;
          background: rgba(255,255,255,0.06);
          border-radius: 6px;
        }
        /* Streak Tracker */
        .enhanced-streak-row {
          display: flex;
          align-items: center;
          gap: 14px;
        }
        .enhanced-streak-big {
          font-size: 2rem;
          font-weight: 800;
          line-height: 1;
          color: #f97316;
          min-width: 56px;
          text-align: center;
        }
        .enhanced-streak-info {
          font-size: 0.78rem;
          color: #a3a3a3;
          line-height: 1.4;
        }
        .enhanced-streak-detail {
          font-size: 0.7rem;
          color: #525252;
        }
        /* Rarest Achievements */
        .enhanced-rare-item {
          display: flex;
          align-items: center;
          gap: 10px;
          padding: 6px 0;
          border-bottom: 1px solid rgba(255,255,255,0.04);
          text-decoration: none;
          color: inherit;
          cursor: pointer;
          border-radius: 4px;
          transition: background 0.15s;
        }
        .enhanced-rare-item:hover {
          background: rgba(255,255,255,0.06);
        }
        .enhanced-rare-item:last-child { border-bottom: none; }
        .enhanced-rare-badge {
          width: 40px;
          height: 40px;
          border-radius: 4px;
          flex-shrink: 0;
        }
        .enhanced-rare-info {
          flex: 1;
          min-width: 0;
        }
        .enhanced-rare-title {
          font-size: 0.8rem;
          color: #e4e4e7;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }
        .enhanced-rare-meta {
          font-size: 0.7rem;
          color: #737373;
        }
        .enhanced-rare-ratio {
          font-size: 0.75rem;
          font-weight: 700;
          color: #a78bfa;
          flex-shrink: 0;
          text-align: right;
          min-width: 40px;
        }
        /* Activity Timeline (GitHub contributions style - yearly heatmap) */
        .enhanced-timeline-wrapper {
          overflow-x: auto;
          padding-bottom: 4px;
        }
        .enhanced-timeline-table {
          display: grid;
          gap: 2px;
          min-width: 0;
          width: 100%;
        }
        .enhanced-timeline-month-label {
          font-size: 0.55rem;
          color: #737373;
          text-align: left;
          white-space: nowrap;
          overflow: visible;
          line-height: 1;
          padding-bottom: 1px;
          position: relative;
        }
        .enhanced-timeline-day-label {
          font-size: 0.55rem;
          color: #737373;
          display: flex;
          align-items: center;
          justify-content: flex-end;
          padding-right: 4px;
          line-height: 1;
        }
        .enhanced-timeline-cell {
          border-radius: 2px;
          min-width: 0;
          cursor: default;
        }
        .enhanced-timeline-cell.level-0 { background: rgba(255,255,255,0.04); }
        .enhanced-timeline-cell.level-1 { background: rgba(59,130,246,0.25); }
        .enhanced-timeline-cell.level-2 { background: rgba(59,130,246,0.5); }
        .enhanced-timeline-cell.level-3 { background: rgba(59,130,246,0.75); }
        .enhanced-timeline-cell.level-4 { background: #3b82f6; }
        /* Mastered mode (gold) */
        .enhanced-timeline-cell.mastered-1 { background: rgba(251,191,36,0.25); }
        .enhanced-timeline-cell.mastered-2 { background: rgba(251,191,36,0.5); }
        .enhanced-timeline-cell.mastered-3 { background: rgba(251,191,36,0.75); }
        .enhanced-timeline-cell.mastered-4 { background: #fbbf24; }
        /* Beaten mode (gray) */
        .enhanced-timeline-cell.beaten-1 { background: rgba(163,163,163,0.25); }
        .enhanced-timeline-cell.beaten-2 { background: rgba(163,163,163,0.5); }
        .enhanced-timeline-cell.beaten-3 { background: rgba(163,163,163,0.75); }
        .enhanced-timeline-cell.beaten-4 { background: #a3a3a3; }

        .enhanced-timeline-tooltip {
          position: fixed;
          z-index: 99999;
          pointer-events: none;
          background: #1a1a2e;
          border: 1px solid rgba(255,255,255,0.15);
          border-radius: 6px;
          padding: 6px 10px;
          font-size: 0.7rem;
          color: #e5e5e5;
          line-height: 1.6;
          box-shadow: 0 4px 12px rgba(0,0,0,0.4);
          white-space: nowrap;
          opacity: 0;
          transition: opacity 0.12s;
        }
        .enhanced-timeline-tooltip.visible { opacity: 1; }
        .enhanced-timeline-tooltip .tooltip-date {
          font-weight: 700;
          margin-bottom: 2px;
          color: #fff;
        }
        .enhanced-timeline-tooltip .tooltip-line {
          display: flex;
          align-items: center;
          gap: 4px;
        }
        .enhanced-timeline-tooltip .tooltip-line .tooltip-icon { font-size: 0.75rem; }
        .enhanced-timeline-tooltip .tooltip-no-activity { color: #737373; font-style: italic; }
        .enhanced-timeline-footer {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-top: 6px;
          font-size: 0.6rem;
          color: #525252;
        }
        .enhanced-timeline-legend {
          display: flex;
          align-items: center;
          gap: 3px;
          font-size: 0.6rem;
          color: #525252;
        }
        .enhanced-timeline-legend-cell {
          width: 10px;
          height: 10px;
          border-radius: 2px;
        }
        .enhanced-timeline-toggle-bar {
          display: flex;
          gap: 4px;
          margin-bottom: 6px;
        }
        .enhanced-timeline-toggle-btn {
          padding: 2px 8px;
          border-radius: 4px;
          font-size: 0.65rem;
          font-weight: 600;
          cursor: pointer;
          border: 1px solid rgba(255,255,255,0.1);
          background: rgba(255,255,255,0.04);
          color: #a3a3a3;
          transition: all 0.15s;
        }
        .enhanced-timeline-toggle-btn:hover {
          background: rgba(255,255,255,0.08);
        }
        .enhanced-timeline-toggle-btn.active {
          border-color: var(--toggle-color, #3b82f6);
          color: var(--toggle-color, #3b82f6);
          background: var(--toggle-bg, rgba(59,130,246,0.12));
        }
        .enhanced-timeline-total {
          font-size: 0.75rem;
          font-weight: 600;
          color: #a3a3a3;
          margin-left: 8px;
        }
      `;
      document.head.appendChild(style);
    }

    function renderSkeletonCards(container, count) {
      container.innerHTML = '';
      for (var i = 0; i < count; i++) {
        var card = document.createElement('div');
        card.className = 'enhanced-skeleton-card';
        card.style.animationDelay = (i * 0.1) + 's';
        card.innerHTML =
          '<div class="enhanced-skeleton-img"></div>'
          + '<div class="enhanced-skeleton-content">'
            + '<div class="enhanced-skeleton-line w-60"></div>'
            + '<div class="enhanced-skeleton-line w-40"></div>'
            + '<div class="enhanced-skeleton-line w-30"></div>'
            + '<div class="enhanced-skeleton-bar"></div>'
          + '</div>';
        container.appendChild(card);
      }
    }

    var ITEMS_PER_PAGE = 5;
    var currentOffset = 0;
    var totalLoaded = -1; // -1 = unknown
    var highestKnownPage = 1; // track the furthest page we've confirmed exists
    var lastKnownHasMore = true;

    // Create games list container
    var gamesList = document.createElement("div");
    gamesList.className = "enhanced-games-list";

    // Create pagination wrapper (always below the list)
    var paginationDiv = document.createElement("div");
    paginationDiv.id = "enhanced-pagination";

    // Insert inside componentRoot so they inherit full width
    componentRoot.appendChild(gamesList);
    componentRoot.appendChild(paginationDiv);

    var originalHeadingText = recentH2.textContent.trim();

    // Items per page selector next to the heading
    var perPageWrapper = document.createElement('div');
    perPageWrapper.style.cssText = 'display:inline-flex;align-items:center;gap:6px;margin-left:12px;vertical-align:middle;';
    var perPageLabel = document.createElement('label');
    perPageLabel.textContent = 'Show:';
    perPageLabel.style.cssText = 'font-size:0.75rem;color:#a3a3a3;';
    var perPageSelect = document.createElement('select');
    perPageSelect.style.cssText = 'background:#18181b;color:#e4e4e7;border:1px solid rgba(255,255,255,0.2);border-radius:4px;padding:2px 6px;font-size:0.75rem;cursor:pointer;';
    [5, 10, 15, 20, 30, 50].forEach(function (n) {
      var opt = document.createElement('option');
      opt.value = n;
      opt.textContent = n;
      if (n === ITEMS_PER_PAGE) opt.selected = true;
      perPageSelect.appendChild(opt);
    });
    perPageSelect.addEventListener('change', function () {
      ITEMS_PER_PAGE = parseInt(perPageSelect.value, 10);
      highestKnownPage = 1;
      lastKnownHasMore = true;
      achievementCache = {};
      doLoadPage(0);
    });
    perPageWrapper.appendChild(perPageLabel);
    perPageWrapper.appendChild(perPageSelect);
    // Wrap heading + combo in their own flex row, don't touch componentRoot layout
    var headingRow = document.createElement('div');
    headingRow.style.cssText = 'display:flex;align-items:center;flex-wrap:wrap;gap:0;';
    recentH2.parentNode.insertBefore(headingRow, recentH2);
    headingRow.appendChild(recentH2);
    headingRow.appendChild(perPageWrapper);

    // =========================================
    //   Enhanced User Stats (replaces native)
    // =========================================
    (function enhanceUserStats() {
      var userStatsH2 = null;
      var allH2s = document.querySelectorAll('h2');
      for (var h = 0; h < allH2s.length; h++) {
        if (/User\s*Stats/i.test(allH2s[h].textContent)) {
          userStatsH2 = allH2s[h];
          break;
        }
      }
      if (!userStatsH2) return;

      var statsContainer = userStatsH2.closest('[x-data]');
      if (!statsContainer) statsContainer = userStatsH2.parentElement.parentElement;
      if (!statsContainer) return;

      // Scrape all stat elements: label → value pairs
      var statEls = statsContainer.querySelectorAll('.relative.flex.w-full.items-center.justify-between');
      var s = {};
      statEls.forEach(function (el) {
        var ps = el.querySelectorAll('p');
        if (ps.length >= 2) {
          var label = (ps[0].textContent || '').trim();
          var value = (ps[1].textContent || '').trim();
          if (label) s[label] = value;
        }
      });

      if (Object.keys(s).length === 0) return;

      // Parse helpers
      function val(key) { return s[key] || ''; }
      function extractWeighted(raw) {
        var m = raw.match(/^([\d,.\s]+)\s*\((.+)\)$/);
        return m ? { main: m[1].trim(), weighted: m[2].trim() } : { main: raw, weighted: '' };
      }
      function extractRankTotal(raw) {
        var m = raw.match(/#([\d,]+)\s*of\s*([\d,]+)/i);
        return m ? { rank: '#' + m[1], total: 'of ' + m[2] } : { rank: raw, total: '' };
      }
      function extractBeatenRetail(raw) {
        var m = raw.match(/^(\d+)\s*\((.+)\)$/);
        return m ? { count: m[1], retail: m[2].trim() } : { count: raw, retail: '' };
      }

      // Primary cards
      var pts = extractWeighted(val('Points'));
      var rank = extractRankTotal(val('Site rank'));
      var beaten = extractBeatenRetail(val('Total games beaten'));

      var primaryHtml = ''
        + '<div class="metric-card">'
          + '<div class="card-top"><span class="metric-label">Points</span><span class="card-icon">⭐</span></div>'
          + '<div class="metric-value" style="color:#a78bfa;">' + escapeHtml(pts.main) + '</div>'
          + (pts.weighted ? '<div class="metric-sub">' + escapeHtml(pts.weighted) + ' weighted</div>' : '')
        + '</div>'
        + '<div class="metric-card">'
          + '<div class="card-top"><span class="metric-label">Site rank</span><span class="card-icon">🏅</span></div>'
          + '<div class="metric-value-sm" style="color:#fbbf24;">' + escapeHtml(rank.rank) + '</div>'
          + (rank.total ? '<div class="metric-sub">' + escapeHtml(rank.total) + '</div>' : '')
        + '</div>'
        + '<div class="metric-card">'
          + '<div class="card-top"><span class="metric-label">Achievements</span><span class="card-icon">🏆</span></div>'
          + '<div class="metric-value" style="color:#3b82f6;">' + escapeHtml(val('Achievements unlocked')) + '</div>'
        + '</div>'
        + '<div class="metric-card">'
          + '<div class="card-top"><span class="metric-label">RetroRatio</span><span class="card-icon">📊</span></div>'
          + '<div class="metric-value" style="color:#10b981;">' + escapeHtml(val('RetroRatio')) + '</div>'
        + '</div>'
        + '<div class="metric-card">'
          + '<div class="card-top"><span class="metric-label">Games beaten</span><span class="card-icon">🎮</span></div>'
          + '<div class="metric-value" style="color:#f472b6;">' + escapeHtml(beaten.count) + '</div>'
          + (beaten.retail ? '<div class="metric-sub">' + escapeHtml(beaten.retail) + '</div>' : '')
        + '</div>'
        + '<div class="metric-card">'
          + '<div class="card-top"><span class="metric-label">Beaten rate</span><span class="card-icon">📈</span></div>'
          + '<div class="metric-value" style="color:#38bdf8;">' + escapeHtml(val('Started games beaten')) + '</div>'
        + '</div>';

      // Recent activity section
      var recentDefs = [
        { key: 'Points earned in the last 7 days', label: 'Points (7 days)', icon: '📅' },
        { key: 'Points earned in the last 30 days', label: 'Points (30 days)', icon: '📆' },
        { key: 'Average points per week', label: 'Avg pts / week', icon: '📉' },
        { key: 'Average completion', label: 'Avg completion', icon: '🎯' },
      ];
      var recentHtml = '';
      var hasRecent = false;
      recentDefs.forEach(function (def) {
        var v = val(def.key);
        if (!v) return;
        hasRecent = true;
        recentHtml += '<div class="metric-card">'
          + '<div class="card-top"><span class="metric-label">' + escapeHtml(def.label) + '</span><span class="card-icon">' + def.icon + '</span></div>'
          + '<div class="metric-value" style="color:#e4e4e7;">' + escapeHtml(v) + '</div>'
          + '</div>';
      });

      // Softcore section
      var softcoreDefs = [
        { key: 'Points (softcore)', label: 'Points', icon: '⚡' },
        { key: 'Softcore rank', label: 'Rank', icon: '🥈' },
        { key: 'Achievements unlocked (softcore)', label: 'Achievements', icon: '🔓' },
      ];
      var softcoreHtml = '';
      var hasSoftcore = false;
      softcoreDefs.forEach(function (def) {
        var v = val(def.key);
        if (!v) return;
        hasSoftcore = true;
        var parsed = extractRankTotal(v);
        if (def.key === 'Softcore rank' && parsed.total) {
          softcoreHtml += '<div class="metric-card">'
            + '<div class="card-top"><span class="metric-label">' + escapeHtml(def.label) + '</span><span class="card-icon">' + def.icon + '</span></div>'
            + '<div class="metric-value-sm" style="color:#737373;">' + escapeHtml(parsed.rank) + '</div>'
            + '<div class="metric-sub">' + escapeHtml(parsed.total) + '</div>'
            + '</div>';
        } else {
          softcoreHtml += '<div class="metric-card">'
            + '<div class="card-top"><span class="metric-label">' + escapeHtml(def.label) + '</span><span class="card-icon">' + def.icon + '</span></div>'
            + '<div class="metric-value" style="color:#737373;">' + escapeHtml(v) + '</div>'
            + '</div>';
        }
      });

      // Build full HTML
      var html = '<div class="stats-root">'
        + '<div class="stats-title">User Stats</div>'
        + '<div class="stats-grid-3">' + primaryHtml + '</div>';

      if (hasRecent) {
        html += '<hr class="stats-divider">'
          + '<div class="section-label">Recent activity</div>'
          + '<div class="stats-grid-4">' + recentHtml + '</div>';
      }

      if (hasSoftcore) {
        html += '<hr class="stats-divider">'
          + '<div class="section-label">Softcore</div>'
          + '<div class="stats-grid-3">' + softcoreHtml + '</div>';
      }

      html += '</div>';

      var enhancedDiv = document.createElement('div');
      enhancedDiv.innerHTML = html;

      statsContainer.parentNode.insertBefore(enhancedDiv, statsContainer);
      statsContainer.style.display = 'none';
    })();

    // =========================================
    //   Player Insights Dashboard
    // =========================================
    var dashboardDiv = document.createElement('div');
    dashboardDiv.className = 'enhanced-dashboard';
    componentRoot.insertBefore(dashboardDiv, headingRow);

    // Dashboard title
    var dashTitle = document.createElement('div');
    dashTitle.className = 'enhanced-dashboard-title';
    dashTitle.innerHTML = '📊 Player Insights';
    dashboardDiv.appendChild(dashTitle);

    // Activity Timeline section (above other modules)
    var timelineSection = document.createElement('div');
    timelineSection.className = 'enhanced-dashboard-section';
    timelineSection.innerHTML =
      '<div class="enhanced-dashboard-section-title">📅 Activity (Last 365 Days)<span class="enhanced-timeline-total" id="enhanced-timeline-total"></span></div>'
      + '<div class="enhanced-timeline-content">'
        + '<div class="enhanced-dashboard-skeleton" style="height:32px;"></div>'
      + '</div>';
    dashboardDiv.appendChild(timelineSection);

    // Stats row (skeleton while loading)
    var statsRow = document.createElement('div');
    statsRow.className = 'enhanced-stats-row';
    statsRow.innerHTML =
      '<div class="enhanced-dashboard-skeleton" style="height:60px;"></div>'
      + '<div class="enhanced-dashboard-skeleton" style="height:60px;animation-delay:0.1s;"></div>'
      + '<div class="enhanced-dashboard-skeleton" style="height:60px;animation-delay:0.2s;"></div>'
      + '<div class="enhanced-dashboard-skeleton" style="height:60px;animation-delay:0.3s;"></div>';
    dashboardDiv.appendChild(statsRow);

    // Almost There section
    var almostSection = document.createElement('div');
    almostSection.className = 'enhanced-dashboard-section';
    almostSection.innerHTML =
      '<div class="enhanced-dashboard-section-title">🎯 Almost There</div>'
      + '<div class="enhanced-almost-list">'
        + '<div class="enhanced-dashboard-skeleton" style="height:48px;margin-bottom:6px;"></div>'
        + '<div class="enhanced-dashboard-skeleton" style="height:48px;margin-bottom:6px;animation-delay:0.1s;"></div>'
        + '<div class="enhanced-dashboard-skeleton" style="height:48px;animation-delay:0.2s;"></div>'
      + '</div>';
    dashboardDiv.appendChild(almostSection);

    // Streak Tracker section
    var streakSection = document.createElement('div');
    streakSection.className = 'enhanced-dashboard-section';
    streakSection.innerHTML =
      '<div class="enhanced-dashboard-section-title">🔥 Streak Tracker</div>'
      + '<div class="enhanced-streak-content">'
        + '<div class="enhanced-dashboard-skeleton" style="height:48px;"></div>'
      + '</div>';
    dashboardDiv.appendChild(streakSection);

    // Rarest Achievements section
    var rarestSection = document.createElement('div');
    rarestSection.className = 'enhanced-dashboard-section';
    rarestSection.innerHTML =
      '<div class="enhanced-dashboard-section-title">💎 Rarest Achievements</div>'
      + '<div class="enhanced-rare-list">'
        + '<div class="enhanced-dashboard-skeleton" style="height:42px;margin-bottom:6px;"></div>'
        + '<div class="enhanced-dashboard-skeleton" style="height:42px;margin-bottom:6px;animation-delay:0.1s;"></div>'
        + '<div class="enhanced-dashboard-skeleton" style="height:42px;animation-delay:0.2s;"></div>'
      + '</div>';
    dashboardDiv.appendChild(rarestSection);

    // --- Render functions ---
    function renderStatsCards(data) {
      var totalGames = data.totalGames || 0;
      var mastered = data.mastered || 0;
      var completionPct = totalGames > 0 ? Math.round((mastered / totalGames) * 100) : 0;
      var points = data.points || 0;
      var rank = data.rank || '—';

      statsRow.innerHTML =
        '<div class="enhanced-stat-card">'
          + '<div class="enhanced-stat-value">' + totalGames + '</div>'
          + '<div class="enhanced-stat-label">Games Played</div>'
        + '</div>'
        + '<div class="enhanced-stat-card">'
          + '<div class="enhanced-stat-value" style="color:#fbbf24;">' + mastered + '</div>'
          + '<div class="enhanced-stat-label">Mastered</div>'
        + '</div>'
        + '<div class="enhanced-stat-card">'
          + '<div class="enhanced-stat-value" style="color:#3b82f6;">' + completionPct + '%</div>'
          + '<div class="enhanced-stat-label">Mastery Rate</div>'
        + '</div>'
        + '<div class="enhanced-stat-card">'
          + '<div class="enhanced-stat-value" style="color:#a78bfa;">' + points.toLocaleString() + '</div>'
          + '<div class="enhanced-stat-label">Points (Rank ' + escapeHtml(String(rank)) + ')</div>'
        + '</div>';
    }

    function renderAlmostThere(games) {
      var list = almostSection.querySelector('.enhanced-almost-list');
      if (!games || games.length === 0) {
        list.innerHTML = '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">No games close to mastery found.</div>';
        return;
      }
      list.innerHTML = '';
      games.forEach(function (g) {
        var pct = g.total > 0 ? Math.round((g.earned / g.total) * 100) : 0;
        var remaining = g.total - g.earned;
        var imgUrl = 'https://media.retroachievements.org' + g.imageIcon;

        var item = document.createElement('div');
        item.className = 'enhanced-almost-item';
        item.innerHTML =
          '<img class="enhanced-almost-img" src="' + escapeHtml(imgUrl) + '" alt="" loading="lazy">'
          + '<div class="enhanced-almost-info">'
            + '<a class="enhanced-almost-name" href="/game/' + g.gameId + '" title="' + escapeHtml(g.title) + '">' + escapeHtml(g.title) + '</a>'
            + '<div class="enhanced-almost-meta">' + remaining + ' achievement' + (remaining !== 1 ? 's' : '') + ' remaining (' + pct + '%)</div>'
            + '<div class="enhanced-almost-bar-bg"><div class="enhanced-almost-bar-fill" style="width:' + pct + '%;"></div></div>'
          + '</div>';
        list.appendChild(item);
      });
    }

    function renderStreakTracker(achievements) {
      var content = streakSection.querySelector('.enhanced-streak-content');
      if (!achievements || achievements.length === 0) {
        content.innerHTML = '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">No recent achievements found.</div>';
        return;
      }

      // Group achievements by date (YYYY-MM-DD)
      var daySet = {};
      achievements.forEach(function (a) {
        if (!a.Date) return;
        var day = a.Date.substring(0, 10); // "YYYY-MM-DD"
        daySet[day] = (daySet[day] || 0) + 1;
      });

      // Calculate current streak (consecutive days ending today or yesterday)
      var today = new Date();
      var streak = 0;
      var bestStreak = 0;
      var tempStreak = 0;

      // Get sorted unique days
      var days = Object.keys(daySet).sort().reverse();
      if (days.length === 0) {
        content.innerHTML = '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">No activity data available.</div>';
        return;
      }

      // Check from today backwards
      var checkDate = new Date(today);
      checkDate.setHours(0, 0, 0, 0);
      var todayStr = checkDate.toISOString().substring(0, 10);

      // If no activity today, check if yesterday had activity (streak might still be alive)
      if (!daySet[todayStr]) {
        checkDate.setDate(checkDate.getDate() - 1);
      }

      while (true) {
        var dStr = checkDate.toISOString().substring(0, 10);
        if (daySet[dStr]) {
          streak++;
          checkDate.setDate(checkDate.getDate() - 1);
        } else {
          break;
        }
      }

      // Calculate best streak in the data
      var sortedDays = Object.keys(daySet).sort();
      tempStreak = 1;
      bestStreak = 1;
      for (var i = 1; i < sortedDays.length; i++) {
        var prev = new Date(sortedDays[i - 1] + 'T00:00:00');
        var curr = new Date(sortedDays[i] + 'T00:00:00');
        var diff = (curr - prev) / (1000 * 60 * 60 * 24);
        if (diff === 1) {
          tempStreak++;
          if (tempStreak > bestStreak) bestStreak = tempStreak;
        } else {
          tempStreak = 1;
        }
      }
      if (streak > bestStreak) bestStreak = streak;

      var totalAch = achievements.length;
      var activeDays = Object.keys(daySet).length;

      content.innerHTML =
        '<div class="enhanced-streak-row">'
          + '<div class="enhanced-streak-big">' + streak + '</div>'
          + '<div>'
            + '<div class="enhanced-streak-info">' + (streak === 1 ? 'day streak' : 'days streak') + (streak > 0 ? ' 🔥' : '') + '</div>'
            + '<div class="enhanced-streak-detail">Best: ' + bestStreak + ' days · ' + activeDays + ' active days · ' + totalAch + ' achievements (365d)</div>'
          + '</div>'
        + '</div>';
    }

    function renderRarestAchievements(achievements) {
      var list = rarestSection.querySelector('.enhanced-rare-list');
      if (!achievements || achievements.length === 0) {
        list.innerHTML = '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">No achievement data available.</div>';
        return;
      }

      // Sort by TrueRatio descending (higher TrueRatio = rarer)
      var sorted = achievements.slice().filter(function (a) {
        return a.TrueRatio && parseInt(a.TrueRatio, 10) > 0;
      });
      sorted.sort(function (a, b) {
        return (parseInt(b.TrueRatio, 10) || 0) - (parseInt(a.TrueRatio, 10) || 0);
      });
      // Deduplicate by AchievementID (keep first = highest ratio)
      var seen = {};
      sorted = sorted.filter(function (a) {
        if (seen[a.AchievementID]) return false;
        seen[a.AchievementID] = true;
        return true;
      });
      sorted = sorted.slice(0, 5);

      if (sorted.length === 0) {
        list.innerHTML = '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">No rarity data available.</div>';
        return;
      }

      list.innerHTML = '';
      sorted.forEach(function (a) {
        var badgeUrl = a.BadgeURL || '';
        if (badgeUrl && !badgeUrl.startsWith('http')) {
          badgeUrl = 'https://media.retroachievements.org' + badgeUrl;
        }
        var trueRatio = parseInt(a.TrueRatio, 10) || 0;
        var points = parseInt(a.Points, 10) || 0;
        var ratio = trueRatio > 0 && points > 0 ? (trueRatio / points).toFixed(1) : '—';

        var item = document.createElement('a');
        item.className = 'enhanced-rare-item';
        item.href = '/achievement/' + a.AchievementID;
        item.innerHTML =
          '<img class="enhanced-rare-badge" src="' + escapeHtml(badgeUrl) + '" alt="" loading="lazy">'
          + '<div class="enhanced-rare-info">'
            + '<div class="enhanced-rare-title" title="' + escapeHtml(a.Title || '') + '">' + escapeHtml(a.Title || '') + '</div>'
            + '<div class="enhanced-rare-meta">' + escapeHtml(a.GameTitle || '') + ' · ' + points + ' pts</div>'
          + '</div>'
          + '<div class="enhanced-rare-ratio" title="TrueRatio: ' + trueRatio + ' (x' + ratio + ' rarity)">'
            + 'x' + ratio
          + '</div>';
        list.appendChild(item);
      });
    }

    function renderActivityTimeline(achievements, masteredDayMap, beatenDayMap) {
      var content = timelineSection.querySelector('.enhanced-timeline-content');
      if (!achievements || achievements.length === 0) {
        content.innerHTML = '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">No recent activity.</div>';
        return;
      }

      // Update total in title
      var totalEl = document.getElementById('enhanced-timeline-total');
      if (totalEl) totalEl.textContent = '— ' + achievements.length + ' achievements';

      // Group achievements by day
      var achDayMap = {};
      achievements.forEach(function (a) {
        if (!a.Date) return;
        var day = a.Date.substring(0, 10);
        achDayMap[day] = (achDayMap[day] || 0) + 1;
      });

      // Build 365-day calendar grid structure (shared across modes)
      var today = new Date();
      today.setHours(0, 0, 0, 0);
      var todayDow = today.getDay();
      var startDate = new Date(today);
      startDate.setDate(startDate.getDate() - 364 - todayDow);
      var totalDays = Math.floor((today - startDate) / (1000 * 60 * 60 * 24)) + 1;
      var numWeeks = Math.ceil(totalDays / 7);

      var baseCells = [];
      for (var w = 0; w < numWeeks; w++) {
        for (var dow = 0; dow < 7; dow++) {
          var d = new Date(startDate);
          d.setDate(d.getDate() + w * 7 + dow);
          if (d > today) continue;
          baseCells.push({ date: d.toISOString().substring(0, 10), day: d, week: w, dow: dow });
        }
      }

      var monthNames = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
      var dayLabels = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];

      // Month label positions
      var monthCols = {};
      baseCells.forEach(function (c) {
        if (c.dow === 0) {
          var key = c.day.getFullYear() + '-' + c.day.getMonth();
          if (!(key in monthCols)) monthCols[key] = { week: c.week, month: c.day.getMonth() };
        }
      });

      // Three data modes
      var modes = {
        achievements: {
          dayMap: achDayMap,
          prefix: 'level',
          icon: '🏆',
          label: '🏆 Achievements',
          totalLabel: function (dm) { var t = 0; for (var k in dm) t += dm[k]; return t + ' achievements'; },
          tooltipSingular: 'achievement',
          tooltipPlural: 'achievements',
          color: '#3b82f6',
          bg: 'rgba(59,130,246,0.12)',
          legendColors: ['rgba(255,255,255,0.04)','rgba(59,130,246,0.25)','rgba(59,130,246,0.5)','rgba(59,130,246,0.75)','#3b82f6']
        },
        mastered: {
          dayMap: masteredDayMap || {},
          prefix: 'mastered',
          icon: '👑',
          label: '👑 Mastered',
          totalLabel: function (dm) { var t = 0; for (var k in dm) t += dm[k]; return t + ' games mastered'; },
          tooltipSingular: 'game mastered',
          tooltipPlural: 'games mastered',
          color: '#fbbf24',
          bg: 'rgba(251,191,36,0.12)',
          legendColors: ['rgba(255,255,255,0.04)','rgba(251,191,36,0.25)','rgba(251,191,36,0.5)','rgba(251,191,36,0.75)','#fbbf24']
        },
        beaten: {
          dayMap: beatenDayMap || {},
          prefix: 'beaten',
          icon: '✅',
          label: '✅ Beaten',
          totalLabel: function (dm) { var t = 0; for (var k in dm) t += dm[k]; return t + ' games beaten'; },
          tooltipSingular: 'game beaten',
          tooltipPlural: 'games beaten',
          color: '#a3a3a3',
          bg: 'rgba(163,163,163,0.12)',
          legendColors: ['rgba(255,255,255,0.04)','rgba(163,163,163,0.25)','rgba(163,163,163,0.5)','rgba(163,163,163,0.75)','#a3a3a3']
        }
      };

      var activeModes = { achievements: true, mastered: true, beaten: true };

      function getActiveKeys() {
        var keys = [];
        for (var k in activeModes) { if (activeModes[k]) keys.push(k); }
        return keys;
      }

      function buildGrid() {
        var activeKeys = getActiveKeys();
        if (activeKeys.length === 0) return '';

        var isSingle = activeKeys.length === 1;
        var theme = isSingle ? modes[activeKeys[0]] : null;

        // Merge dayMaps from active modes
        var mergedDayMap = {};
        activeKeys.forEach(function (key) {
          var dm = modes[key].dayMap;
          for (var d in dm) mergedDayMap[d] = (mergedDayMap[d] || 0) + dm[d];
        });

        var maxCount = 0;
        var cellData = baseCells.map(function (c) {
          var count = mergedDayMap[c.date] || 0;
          if (count > maxCount) maxCount = count;
          return { date: c.date, count: count, day: c.day, week: c.week, dow: c.dow };
        });

        function getLevel(count) {
          if (count === 0) return 0;
          if (maxCount <= 4) return Math.min(count, 4);
          var pct = count / maxCount;
          if (pct <= 0.25) return 1;
          if (pct <= 0.5) return 2;
          if (pct <= 0.75) return 3;
          return 4;
        }

        var cellSize = Math.max(8, Math.floor((content.offsetWidth - 32) / (numWeeks + 1)));
        if (cellSize > 14) cellSize = 14;

        var levelPrefix = theme ? (theme.prefix === 'level' ? 'level' : theme.prefix) : null;

        // Priority order for multi-mode cell coloring: mastered > beaten > achievements
        var priorityOrder = ['mastered', 'beaten', 'achievements'];
        function getCellPrefix(date) {
          if (levelPrefix) return levelPrefix;
          for (var i = 0; i < priorityOrder.length; i++) {
            var k = priorityOrder[i];
            if (activeModes[k] && modes[k].dayMap[date]) return modes[k].prefix === 'level' ? 'level' : modes[k].prefix;
          }
          return 'level';
        }

        var html = '<div class="enhanced-timeline-wrapper">';
        html += '<div class="enhanced-timeline-table" style="grid-template-columns:28px repeat(' + numWeeks + ',' + cellSize + 'px);grid-template-rows:auto repeat(7,' + cellSize + 'px);">';

        // Month labels row
        html += '<div></div>';
        var monthLabelsArr = [];
        for (var wi = 0; wi < numWeeks; wi++) monthLabelsArr.push('');
        Object.keys(monthCols).forEach(function (key) {
          var info = monthCols[key];
          monthLabelsArr[info.week] = monthNames[info.month];
        });
        for (var wi = 0; wi < numWeeks; wi++) {
          html += '<div class="enhanced-timeline-month-label">' + monthLabelsArr[wi] + '</div>';
        }

        // Cell lookup
        var cellMap = {};
        cellData.forEach(function (c) {
          if (!cellMap[c.week]) cellMap[c.week] = {};
          cellMap[c.week][c.dow] = c;
        });

        for (var dow = 0; dow < 7; dow++) {
          if (dow === 1 || dow === 3 || dow === 5) {
            html += '<div class="enhanced-timeline-day-label">' + dayLabels[dow] + '</div>';
          } else {
            html += '<div class="enhanced-timeline-day-label"></div>';
          }
          for (var wi = 0; wi < numWeeks; wi++) {
            var cell = cellMap[wi] && cellMap[wi][dow];
            if (cell) {
              var level = getLevel(cell.count);
              // Build tooltip data attributes
              var tooltipLines = [];
              activeKeys.forEach(function (key) {
                var c = modes[key].dayMap[cell.date] || 0;
                if (c > 0) {
                  var unit = c === 1 ? modes[key].tooltipSingular : modes[key].tooltipPlural;
                  tooltipLines.push(modes[key].icon + '|' + c + ' ' + unit);
                }
              });
              var dateStr = monthNames[cell.day.getMonth()] + ' ' + cell.day.getDate() + ', ' + cell.day.getFullYear();
              var cellPfx = level === 0 ? 'level' : getCellPrefix(cell.date);
              var cls = level === 0 ? 'level-0' : (cellPfx + '-' + level);
              html += '<div class="enhanced-timeline-cell ' + cls + '" data-tip-date="' + escapeHtml(dateStr) + '" data-tip-lines="' + escapeHtml(tooltipLines.join(';;')) + '"></div>';
            } else {
              html += '<div></div>';
            }
          }
        }

        html += '</div></div>';

        // Footer with per-mode stats
        var footerParts = [];
        activeKeys.forEach(function (key) {
          footerParts.push(modes[key].totalLabel(modes[key].dayMap));
        });
        var activeDays = 0;
        for (var k in mergedDayMap) { if (mergedDayMap[k] > 0) activeDays++; }
        html += '<div class="enhanced-timeline-footer">';
        html += '<span>' + footerParts.join(', ') + ' in ' + activeDays + ' days</span>';
        html += '<div class="enhanced-timeline-legend">';
        if (isSingle) {
          html += '<span>Less</span>';
          for (var li = 0; li < theme.legendColors.length; li++) {
            html += '<div class="enhanced-timeline-legend-cell" style="background:' + theme.legendColors[li] + ';"></div>';
          }
          html += '<span>More</span>';
        } else {
          activeKeys.forEach(function (key) {
            html += '<span style="display:inline-flex;align-items:center;gap:3px;margin-right:8px;">' + modes[key].icon + '<div class="enhanced-timeline-legend-cell" style="background:' + modes[key].color + ';"></div></span>';
          });
        }
        html += '</div></div>';

        return html;
      }

      function renderModes() {
        var gridContainer = content.querySelector('.enhanced-timeline-grid-area');
        if (gridContainer) gridContainer.innerHTML = buildGrid();
        // Update toggle button active states
        var btns = content.querySelectorAll('.enhanced-timeline-toggle-btn');
        btns.forEach(function (btn) {
          var bm = btn.getAttribute('data-mode');
          if (activeModes[bm]) {
            btn.classList.add('active');
          } else {
            btn.classList.remove('active');
          }
        });
        // Update total in title
        var totalEl = document.getElementById('enhanced-timeline-total');
        if (totalEl) {
          var activeKeys = getActiveKeys();
          var totalParts = [];
          activeKeys.forEach(function (key) {
            var dm = modes[key].dayMap;
            var t = 0;
            for (var d in dm) t += dm[d];
            if (key === 'achievements') totalParts.push(t + ' achievements');
            else if (key === 'mastered') totalParts.push(t + ' mastered');
            else if (key === 'beaten') totalParts.push(t + ' beaten');
          });
          totalEl.textContent = '— ' + totalParts.join(', ');
        }
      }

      // Build toggle bar + grid container
      var outerHtml = '<div class="enhanced-timeline-toggle-bar">';
      ['achievements', 'mastered', 'beaten'].forEach(function (key) {
        var m = modes[key];
        var activeClass = ' active';
        outerHtml += '<button class="enhanced-timeline-toggle-btn' + activeClass + '" data-mode="' + key + '" '
          + 'style="--toggle-color:' + m.color + ';--toggle-bg:' + m.bg + ';">'
          + m.label + '</button>';
      });
      outerHtml += '</div>';
      outerHtml += '<div class="enhanced-timeline-grid-area">' + buildGrid() + '</div>';

      content.innerHTML = outerHtml;

      // Bind toggle clicks (multi-select: toggle on/off, at least 1 must stay active)
      content.querySelectorAll('.enhanced-timeline-toggle-btn').forEach(function (btn) {
        btn.addEventListener('click', function () {
          var mode = btn.getAttribute('data-mode');
          var activeKeys = getActiveKeys();
          if (activeModes[mode] && activeKeys.length <= 1) return; // prevent deselecting last one
          activeModes[mode] = !activeModes[mode];
          renderModes();
        });
      });

      // Custom tooltip
      var tooltip = document.createElement('div');
      tooltip.className = 'enhanced-timeline-tooltip';
      document.body.appendChild(tooltip);

      content.addEventListener('mouseover', function (e) {
        var cell = e.target.closest('.enhanced-timeline-cell');
        if (!cell || !cell.dataset.tipDate) return;
        var dateStr = cell.dataset.tipDate;
        var linesRaw = cell.dataset.tipLines;
        var html = '<div class="tooltip-date">' + escapeHtml(dateStr) + '</div>';
        if (linesRaw) {
          var lines = linesRaw.split(';;');
          lines.forEach(function (line) {
            if (!line) return;
            var parts = line.split('|');
            var icon = parts[0] || '';
            var text = parts[1] || '';
            html += '<div class="tooltip-line"><span class="tooltip-icon">' + icon + '</span> ' + escapeHtml(text) + '</div>';
          });
        } else {
          html += '<div class="tooltip-no-activity">No activity</div>';
        }
        tooltip.innerHTML = html;
        tooltip.classList.add('visible');
      });

      content.addEventListener('mousemove', function (e) {
        if (!tooltip.classList.contains('visible')) return;
        var x = e.clientX + 12;
        var y = e.clientY - tooltip.offsetHeight - 8;
        if (y < 4) y = e.clientY + 16;
        if (x + tooltip.offsetWidth > window.innerWidth - 4) x = e.clientX - tooltip.offsetWidth - 12;
        tooltip.style.left = x + 'px';
        tooltip.style.top = y + 'px';
      });

      content.addEventListener('mouseout', function (e) {
        var cell = e.target.closest('.enhanced-timeline-cell');
        if (!cell) return;
        tooltip.classList.remove('visible');
      });
    }

    // --- Fetch dashboard data ---
    // --- Scrape Console Breakdown from existing DOM ---
    function scrapeConsoleBreakdown() {
      var rows = document.querySelectorAll('li.progression-status-row');
      var consoles = [];
      var domTotalGames = 0;
      var domTotalMastered = 0;
      var domTotalBeaten = 0;
      var foundTotalRow = false;

      rows.forEach(function (row) {
        var link = row.querySelector('a');
        if (!link) return;

        var img = link.querySelector('img');
        var nameEl = link.querySelector('p');
        if (!nameEl) return;

        var shortName = nameEl.textContent.trim();
        var iconUrl = img ? img.src : '';
        var consoleName = img ? (img.alt || '').replace(' console icon', '') : shortName;

        // Get cell links: [console link, unfinished, beaten, mastered]
        var allLinks = row.querySelectorAll('a');

        // Parse numbers from a cell (handles .tally divs or plain text)
        function parseCellNumbers(cell) {
          var nums = [];
          var tallies = cell.querySelectorAll('.tally');
          if (tallies.length > 0) {
            tallies.forEach(function (t) {
              var n = parseInt(t.textContent.trim(), 10);
              if (!isNaN(n)) nums.push(n);
            });
          } else {
            var n = parseInt(cell.textContent.trim(), 10);
            if (!isNaN(n)) nums.push(n);
          }
          return nums;
        }

        // Cells: index 1=unfinished, 2=beaten, 3=mastered
        var unfinished = 0, beaten = 0, mastered = 0;
        if (allLinks.length >= 2) {
          var uNums = parseCellNumbers(allLinks[1]);
          unfinished = uNums.reduce(function (s, n) { return s + n; }, 0);
        }
        if (allLinks.length >= 3) {
          var bNums = parseCellNumbers(allLinks[2]);
          beaten = bNums.reduce(function (s, n) { return s + n; }, 0);
        }
        if (allLinks.length >= 4) {
          var mNums = parseCellNumbers(allLinks[3]);
          mastered = mNums.reduce(function (s, n) { return s + n; }, 0);
        }

        var totalCount = unfinished + beaten + mastered;

        // Skip "Total" row but extract its data for stats
        if (shortName === 'Total') {
          foundTotalRow = true;
          domTotalGames = totalCount;
          domTotalMastered = mastered;
          domTotalBeaten = beaten;
          return;
        }

        if (totalCount > 0) {
          consoles.push({
            shortName: shortName,
            consoleName: consoleName,
            iconUrl: iconUrl,
            count: totalCount,
            unfinished: unfinished,
            beaten: beaten,
            mastered: mastered
          });
        }
      });

      // If no Total row found, sum from individual consoles
      if (!foundTotalRow) {
        consoles.forEach(function (c) {
          domTotalGames += c.count;
          domTotalMastered += c.mastered;
          domTotalBeaten += c.beaten;
        });
      }

      return { consoles: consoles, totalGames: domTotalGames, totalMastered: domTotalMastered, totalBeaten: domTotalBeaten };
    }

    function renderProgressionDashboard() {
      if (document.getElementById('pd-root')) return;
      var firstRow = document.querySelector('li.progression-status-row');
      if (!firstRow) return;

      // Walk up to find section container
      var progSection = firstRow.parentElement;
      while (progSection && progSection !== document.body) {
        var cls = progSection.className || '';
        if (progSection.tagName === 'SECTION' || /\bmy-\d/.test(cls)) break;
        progSection = progSection.parentElement;
      }
      if (!progSection || progSection === document.body)
        progSection = firstRow.closest('section') || firstRow.parentElement.parentElement;

      var data = scrapeConsoleBreakdown();
      var consoles = data.consoles;
      if (!consoles.length) return;

      var totalGames = data.totalGames;
      var totalMastered = data.totalMastered;
      var totalBeaten = data.totalBeaten || consoles.reduce(function(s,c){ return s+(c.beaten||0); }, 0);
      var totalUnfinished = totalGames - totalBeaten - totalMastered;
      var pctDone = totalGames > 0 ? Math.round(((totalBeaten+totalMastered)/totalGames)*100) : 0;

      // Inject CSS once
      if (!document.getElementById('pd-style')) {
        var pdStyle = document.createElement('style');
        pdStyle.id = 'pd-style';
        pdStyle.textContent = [
          '.pd-kpi-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:14px}',
          '.pd-kpi{background:rgba(255,255,255,0.04);border-radius:8px;padding:12px 14px}',
          '.pd-kpi-val{font-size:22px;font-weight:500;color:#e4e4e7}',
          '.pd-kpi-lbl{font-size:12px;color:#737373;margin-top:2px}',
          '.pd-kpi-val.beaten{color:#7F77DD}',
          '.pd-kpi-val.mastered{color:#EF9F27}',
          '.pd-kpi-val.pct{color:#10b981}',
          '.pd-two-col{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);gap:16px;margin-bottom:14px;align-items:start}',
          '.pd-card{background:rgba(255,255,255,0.05);border:0.5px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem 1.25rem}',
          '.pd-card-title{font-size:13px;font-weight:500;color:#9ca3af;margin-bottom:12px}',
          '.pd-legend-row{display:flex;align-items:center;justify-content:space-between;font-size:12px;margin-bottom:6px}',
          '.pd-leg-dot{width:10px;height:10px;border-radius:2px;flex-shrink:0;margin-right:6px}',
          '.pd-leg-name{color:#9ca3af;display:flex;align-items:center;flex:1}',
          '.pd-leg-val{color:#e4e4e7;font-weight:500}',
          '.pd-bar-row{display:flex;align-items:center;gap:8px;margin-bottom:7px}',
          '.pd-bar-lbl{font-size:11px;color:#9ca3af;width:42px;flex-shrink:0;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
          '.pd-bar-track{flex:1;height:14px;background:rgba(255,255,255,0.06);border-radius:4px;overflow:hidden;display:flex}',
          '.pd-seg-u{background:#3f3f46;height:100%}',
          '.pd-seg-b{background:#7F77DD;height:100%}',
          '.pd-seg-m{background:#EF9F27;height:100%}',
          '.pd-bar-pct{font-size:11px;color:#737373;width:32px;text-align:right;flex-shrink:0}',
          '.pd-viz-card{background:rgba(255,255,255,0.05);border:0.5px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem 1.25rem;margin-bottom:14px}',
          '.pd-viz-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;flex-wrap:wrap;gap:8px}',
          '.pd-viz-title{font-size:13px;font-weight:500;color:#9ca3af}',
          '.pd-toggle-wrap{display:flex;background:rgba(255,255,255,0.04);border-radius:20px;padding:3px;gap:2px;border:0.5px solid rgba(255,255,255,0.1)}',
          '.pd-tog-btn{font-size:12px;padding:4px 14px;border-radius:18px;border:none;background:transparent;color:#9ca3af;cursor:pointer;transition:all .18s}',
          '.pd-tog-btn.active{background:rgba(255,255,255,0.1);color:#e4e4e7;font-weight:500}',
          '.pd-filter-row{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}',
          '.pd-filter-btn{font-size:12px;padding:4px 12px;border-radius:20px;border:0.5px solid rgba(255,255,255,0.15);background:transparent;color:#9ca3af;cursor:pointer}',
          '.pd-filter-btn.active{background:rgba(255,255,255,0.08);color:#e4e4e7;font-weight:500}',
          '.pd-viz-legend{display:flex;gap:14px;flex-wrap:wrap;margin-bottom:10px}',
          '.pd-vl-item{display:flex;align-items:center;gap:5px;font-size:12px;color:#9ca3af}',
          '.pd-vl-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}',
          '.pd-viz-wrap{position:relative;width:100%;height:380px}',
          '.pd-viz-tip{position:absolute;background:rgba(20,20,20,.95);border:0.5px solid rgba(255,255,255,.2);border-radius:8px;padding:8px 12px;font-size:12px;color:#e4e4e7;pointer-events:none;display:none;z-index:10;min-width:140px}',
          '.pd-mb-card{background:rgba(255,255,255,0.05);border:0.5px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem 1.25rem}',
          '.pd-donut-wrap{position:relative;height:180px}',
          '.pd-donut-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none}',
          '.pd-donut-pct{font-size:22px;font-weight:500;color:#e4e4e7}',
          '.pd-donut-sub{font-size:11px;color:#737373}',
          '@media(max-width:600px){.pd-two-col{grid-template-columns:1fr}.pd-kpi-grid{grid-template-columns:repeat(2,1fr)}}'
        ].join('');
        document.head.appendChild(pdStyle);
      }

      // Consoles with progress sorted by completion %
      var consolesWithProg = consoles.filter(function(c){ return (c.beaten||0)+(c.mastered||0)>0; });
      consolesWithProg.sort(function(a,b){
        return ((b.beaten||0)+(b.mastered||0))/b.count - ((a.beaten||0)+(a.mastered||0))/a.count;
      });

      var barsHtml = consolesWithProg.slice(0,12).map(function(d) {
        var bP = Math.round(((d.beaten||0)/d.count)*100);
        var mP = Math.round(((d.mastered||0)/d.count)*100);
        var done = bP+mP;
        return '<div class="pd-bar-row">'
          +'<span class="pd-bar-lbl" title="'+escapeHtml(d.consoleName)+'">'+escapeHtml(d.shortName)+'</span>'
          +'<div class="pd-bar-track">'
            +'<div class="pd-seg-u" style="width:'+(100-done)+'%"></div>'
            +'<div class="pd-seg-b" style="width:'+bP+'%"></div>'
            +'<div class="pd-seg-m" style="width:'+mP+'%"></div>'
          +'</div>'
          +'<span class="pd-bar-pct">'+done+'%</span>'
          +'</div>';
      }).join('');

      var html = '<div class="pd-wrap" id="pd-root">'
        +'<div class="enhanced-dashboard-section-title" style="margin-bottom:12px;">\uD83C\uDFAE Progression Status</div>'
        +'<div class="pd-kpi-grid">'
          +'<div class="pd-kpi"><div class="pd-kpi-val">'+totalGames+'</div><div class="pd-kpi-lbl">Total games</div></div>'
          +'<div class="pd-kpi"><div class="pd-kpi-val beaten">'+totalBeaten+'</div><div class="pd-kpi-lbl">Beaten</div></div>'
          +'<div class="pd-kpi"><div class="pd-kpi-val mastered">'+totalMastered+'</div><div class="pd-kpi-lbl">Mastered</div></div>'
          +'<div class="pd-kpi"><div class="pd-kpi-val pct">'+pctDone+'%</div><div class="pd-kpi-lbl">% completed</div></div>'
        +'</div>'
        +'<div class="pd-two-col">'
          +'<div class="pd-card">'
            +'<div class="pd-card-title">Overview</div>'
            +'<div class="pd-donut-wrap"><canvas id="pd-donut"></canvas>'
              +'<div class="pd-donut-center"><div class="pd-donut-pct">'+pctDone+'%</div><div class="pd-donut-sub">completed</div></div>'
            +'</div>'
            +'<div style="margin-top:14px">'
              +'<div class="pd-legend-row"><span class="pd-leg-name"><span class="pd-leg-dot" style="background:#3f3f46"></span>Unfinished</span><span class="pd-leg-val">'+totalUnfinished+' ('+Math.round((totalUnfinished/totalGames)*100)+'%)</span></div>'
              +'<div class="pd-legend-row"><span class="pd-leg-name"><span class="pd-leg-dot" style="background:#7F77DD"></span>Beaten</span><span class="pd-leg-val">'+totalBeaten+' ('+Math.round((totalBeaten/totalGames)*100)+'%)</span></div>'
              +'<div class="pd-legend-row"><span class="pd-leg-name"><span class="pd-leg-dot" style="background:#EF9F27"></span>Mastered</span><span class="pd-leg-val">'+totalMastered+' ('+Math.round((totalMastered/totalGames)*100)+'%)</span></div>'
            +'</div>'
          +'</div>'
          +'<div class="pd-card">'
            +'<div class="pd-card-title">Completion % by console</div>'
            +(barsHtml||'<div style="color:#737373;font-size:12px;">No completed games yet</div>')
          +'</div>'
        +'</div>'
        +'<div class="pd-viz-card">'
          +'<div class="pd-viz-header">'
            +'<span class="pd-viz-title" id="pd-viz-title">Games by console \xB7 animated bubbles</span>'
            +'<div class="pd-toggle-wrap">'
              +'<button class="pd-tog-btn active" data-v="bubble">Bubbles</button>'
              +'<button class="pd-tog-btn" data-v="treemap">Treemap</button>'
            +'</div>'
          +'</div>'
          +'<div class="pd-viz-legend">'
            +'<div class="pd-vl-item"><div class="pd-vl-dot" style="background:#52525b"></div>Unfinished</div>'
            +'<div class="pd-vl-item"><div class="pd-vl-dot" style="background:#7F77DD"></div>Beaten</div>'
            +'<div class="pd-vl-item"><div class="pd-vl-dot" style="background:#EF9F27"></div>Mastered</div>'
          +'</div>'
          +'<div class="pd-filter-row">'
            +'<button class="pd-filter-btn active" data-f="all">All</button>'
            +'<button class="pd-filter-btn" data-f="progress">With progress</button>'
            +'<button class="pd-filter-btn" data-f="mastered">Mastered</button>'
          +'</div>'
          +'<div class="pd-viz-wrap" id="pd-viz-wrap">'
            +'<canvas id="pd-viz-canvas"></canvas>'
            +'<div class="pd-viz-tip" id="pd-viz-tip"></div>'
          +'</div>'
        +'</div>'
        +'<div class="pd-mb-card">'
          +'<div class="pd-card-title">Mastered vs Beaten \xB7 consoles with progress</div>'
          +'<div style="position:relative;height:180px"><canvas id="pd-mb-chart"></canvas></div>'
        +'</div>'
      +'</div>';

      var pdWrapper = document.createElement('div');
      pdWrapper.innerHTML = html;
      var pdRoot = pdWrapper.firstChild;
      progSection.parentNode.insertBefore(pdRoot, progSection);
      progSection.style.display = 'none';

      // Chart.js charts
      if (typeof Chart !== 'undefined') {
        Chart.defaults.color = '#9ca3af';
        new Chart(document.getElementById('pd-donut').getContext('2d'), {
          type: 'doughnut',
          data: { labels: ['Unfinished','Beaten','Mastered'], datasets: [{ data: [totalUnfinished,totalBeaten,totalMastered], backgroundColor: ['#3f3f46','#7F77DD','#EF9F27'], borderWidth: 0, hoverOffset: 4 }] },
          options: { responsive: true, maintainAspectRatio: false, cutout: '72%', plugins: { legend: { display: false }, tooltip: { backgroundColor: 'rgba(20,20,20,.95)', borderColor: 'rgba(255,255,255,.15)', borderWidth: 1, callbacks: { label: function(ctx){ return ' '+ctx.label+': '+ctx.raw; } } } } }
        });
        if (consolesWithProg.length) {
          var mbData = consolesWithProg.slice(0,8);
          new Chart(document.getElementById('pd-mb-chart').getContext('2d'), {
            type: 'bar',
            data: { labels: mbData.map(function(c){return c.shortName;}), datasets: [ { label: 'Beaten', data: mbData.map(function(c){return c.beaten||0;}), backgroundColor: '#7F77DD', borderRadius: 4 }, { label: 'Mastered', data: mbData.map(function(c){return c.mastered||0;}), backgroundColor: '#EF9F27', borderRadius: 4 } ] },
            options: { responsive: true, maintainAspectRatio: false, scales: { x: { grid: { display: false }, ticks: { color: '#737373', font: { size: 12 } } }, y: { grid: { color: 'rgba(255,255,255,.06)' }, ticks: { color: '#737373', font: { size: 11 }, stepSize: 1 } } }, plugins: { legend: { display: false }, tooltip: { backgroundColor: 'rgba(20,20,20,.95)', borderColor: 'rgba(255,255,255,.15)', borderWidth: 1, callbacks: { label: function(ctx){ return ' '+ctx.dataset.label+': '+ctx.raw; } } } } }
          });
        }
      }

      // Bubble / Treemap canvas animation
      var vizWrap = document.getElementById('pd-viz-wrap');
      var vizCanvas = document.getElementById('pd-viz-canvas');
      var vizTip = document.getElementById('pd-viz-tip');
      var vizTitleEl = document.getElementById('pd-viz-title');
      var vCtx = vizCanvas.getContext('2d');
      var vizMode = 'bubble';
      var pdCurrentFilter = 'all';
      var vBubbles = [];
      var vAnimFrame = null;
      var vTmRects = [];

      function pdColorFor(d) {
        if ((d.mastered||0) > 0) return { fill: '#4a3600', stroke: '#EF9F27', text: '#fbbf24' };
        if ((d.beaten||0) > 0) return { fill: '#2d2b6e', stroke: '#7F77DD', text: '#a5b4fc' };
        return { fill: '#27272a', stroke: '#52525b', text: '#9ca3af' };
      }

      function pdFiltered(f) {
        if (f === 'progress') return consoles.filter(function(d){ return (d.beaten||0)+(d.mastered||0)>0; });
        if (f === 'mastered') return consoles.filter(function(d){ return (d.mastered||0)>0; });
        return consoles;
      }

      function pdInitBubbles(items) {
        var W = vizWrap.clientWidth, H = 380;
        vizCanvas.width = W; vizCanvas.height = H;
        var maxT = Math.max.apply(null, items.map(function(d){ return d.count; }));
        var minR = 18, maxR = Math.min(W/4, 90);
        vBubbles = items.map(function(d) {
          var r = minR + Math.sqrt(d.count/maxT) * (maxR-minR);
          return { shortName: d.shortName, consoleName: d.consoleName, count: d.count, beaten: d.beaten||0, mastered: d.mastered||0, r: r, x: r+Math.random()*(W-r*2), y: r+Math.random()*(H-r*2), vx: (Math.random()-.5)*.8, vy: (Math.random()-.5)*.8 };
        });
      }

      function pdSimulate() {
        var W = vizCanvas.width, H = vizCanvas.height;
        for (var i = 0; i < vBubbles.length; i++) {
          var a = vBubbles[i];
          a.x += a.vx; a.y += a.vy;
          if (a.x-a.r < 0) { a.x=a.r; a.vx=Math.abs(a.vx); }
          if (a.x+a.r > W) { a.x=W-a.r; a.vx=-Math.abs(a.vx); }
          if (a.y-a.r < 0) { a.y=a.r; a.vy=Math.abs(a.vy); }
          if (a.y+a.r > H) { a.y=H-a.r; a.vy=-Math.abs(a.vy); }
          for (var j = i+1; j < vBubbles.length; j++) {
            var b = vBubbles[j];
            var dx = b.x-a.x, dy = b.y-a.y, dist = Math.sqrt(dx*dx+dy*dy), mn = a.r+b.r+2;
            if (dist < mn && dist > 0) {
              var nx = dx/dist, ny = dy/dist, ov = (mn-dist)/2;
              a.x -= nx*ov; a.y -= ny*ov; b.x += nx*ov; b.y += ny*ov;
              var rv = (a.vx-b.vx)*nx+(a.vy-b.vy)*ny;
              if (rv > 0) { a.vx -= rv*nx*.5; a.vy -= rv*ny*.5; b.vx += rv*nx*.5; b.vy += rv*ny*.5; }
            }
          }
          var spd = Math.sqrt(a.vx*a.vx+a.vy*a.vy);
          if (spd > 1.2) { a.vx = a.vx/spd*1.2; a.vy = a.vy/spd*1.2; }
        }
      }

      function pdDrawBubbles() {
        vCtx.clearRect(0, 0, vizCanvas.width, vizCanvas.height);
        for (var i = 0; i < vBubbles.length; i++) {
          var b = vBubbles[i];
          var c = pdColorFor(b);
          var pct = Math.round(((b.beaten+b.mastered)/b.count)*100);
          vCtx.beginPath(); vCtx.arc(b.x, b.y, b.r, 0, Math.PI*2);
          vCtx.fillStyle = c.fill; vCtx.fill();
          vCtx.strokeStyle = c.stroke; vCtx.lineWidth = 1.5; vCtx.stroke();
          if (b.r > 22) {
            vCtx.fillStyle = c.text; vCtx.textAlign = 'center'; vCtx.textBaseline = 'middle';
            var fs = Math.min(13, Math.max(10, b.r/3.2));
            vCtx.font = '500 '+fs+'px sans-serif';
            vCtx.fillText(b.shortName, b.x, b.r > 34 ? b.y-7 : b.y);
            if (b.r > 32) {
              vCtx.font = '400 '+Math.max(9,fs-2)+'px sans-serif';
              vCtx.fillStyle = c.text+'aa';
              vCtx.fillText(b.count+(pct>0?' \xB7 '+pct+'%':''), b.x, b.y+9);
            }
          }
        }
      }

      function pdBubbleLoop() { pdSimulate(); pdDrawBubbles(); vAnimFrame = requestAnimationFrame(pdBubbleLoop); }

      function pdSquarify(items, x, y, w, h) {
        var rects = [];
        function lay(nodes, lx, ly, lw, lh) {
          if (!nodes.length) return;
          if (nodes.length === 1) { rects.push(Object.assign({}, nodes[0], {x:lx,y:ly,w:lw,h:lh})); return; }
          var acc = 0, split = 0, tot = nodes.reduce(function(s,d){return s+d.value;},0);
          for (var i = 0; i < nodes.length; i++) { acc += nodes[i].value; if (acc >= tot/2) { split=i+1; break; } }
          var aN = nodes.slice(0,split), bN = nodes.slice(split);
          var aS = aN.reduce(function(s,d){return s+d.value;},0);
          if (lw >= lh) { var wa = lw*(aS/tot); lay(aN,lx,ly,wa,lh); lay(bN,lx+wa,ly,lw-wa,lh); }
          else { var ha = lh*(aS/tot); lay(aN,lx,ly,lw,ha); lay(bN,lx,ly+ha,lw,lh-ha); }
        }
        var tot = items.reduce(function(s,d){return s+d.value;},0);
        lay(items.map(function(d){return Object.assign({},d,{value:d.value/tot});}), x, y, w, h);
        return rects;
      }

      function pdDrawTreemap(items) {
        var W = vizWrap.clientWidth, H = 380;
        vizCanvas.width = W; vizCanvas.height = H;
        vCtx.clearRect(0, 0, W, H);
        var sorted = items.slice().map(function(d){return Object.assign({},d,{value:d.count});}).sort(function(a,b){return b.value-a.value;});
        vTmRects = pdSquarify(sorted, 0, 0, W, H);
        vTmRects.forEach(function(r) {
          var pad = 2, c = pdColorFor(r);
          vCtx.fillStyle = c.fill;
          vCtx.beginPath();
          if (vCtx.roundRect) vCtx.roundRect(r.x+pad, r.y+pad, r.w-pad*2, r.h-pad*2, 4);
          else vCtx.rect(r.x+pad, r.y+pad, r.w-pad*2, r.h-pad*2);
          vCtx.fill();
          var fw = r.w-pad*2, fh = r.h-pad*2;
          if (fw > 28 && fh > 18) {
            vCtx.fillStyle = c.text;
            var fs = Math.min(13, Math.max(10, fw/5));
            vCtx.font = '500 '+fs+'px sans-serif'; vCtx.textAlign = 'center'; vCtx.textBaseline = 'middle';
            var tx = r.x+r.w/2, ty = r.y+r.h/2;
            vCtx.fillText(r.shortName, tx, fh>32?ty-6:ty);
            if (fh > 30 && fw > 36) { vCtx.font = '400 '+Math.max(9,fs-2)+'px sans-serif'; vCtx.fillStyle = c.text+'bb'; vCtx.fillText(r.count, tx, ty+9); }
          }
        });
      }

      function pdStartViz() {
        cancelAnimationFrame(vAnimFrame); vAnimFrame = null;
        vizTip.style.display = 'none';
        var items = pdFiltered(pdCurrentFilter);
        if (vizMode === 'bubble') {
          vizTitleEl.textContent = 'Games by console \xB7 animated bubbles';
          pdInitBubbles(items); pdBubbleLoop();
        } else {
          vizTitleEl.textContent = 'Games by console \xB7 proportional size';
          pdDrawTreemap(items);
        }
      }

      pdRoot.querySelectorAll('.pd-tog-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
          pdRoot.querySelectorAll('.pd-tog-btn').forEach(function(b){b.classList.remove('active');});
          btn.classList.add('active'); vizMode = btn.getAttribute('data-v'); pdStartViz();
        });
      });

      pdRoot.querySelectorAll('.pd-filter-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
          pdRoot.querySelectorAll('.pd-filter-btn').forEach(function(b){b.classList.remove('active');});
          btn.classList.add('active'); pdCurrentFilter = btn.getAttribute('data-f'); pdStartViz();
        });
      });

      vizWrap.addEventListener('mousemove', function(e) {
        var rect = vizCanvas.getBoundingClientRect();
        var mx = e.clientX-rect.left, my = e.clientY-rect.top;
        var hit = null;
        if (vizMode === 'bubble') {
          for (var i = 0; i < vBubbles.length; i++) { var bb = vBubbles[i]; if (Math.sqrt((mx-bb.x)*(mx-bb.x)+(my-bb.y)*(my-bb.y)) <= bb.r) { hit=bb; break; } }
        } else {
          for (var i = 0; i < vTmRects.length; i++) { var rr = vTmRects[i]; if (mx>=rr.x && mx<=rr.x+rr.w && my>=rr.y && my<=rr.y+rr.h) { hit=rr; break; } }
        }
        if (hit) {
          var pct = Math.round(((hit.beaten+hit.mastered)/hit.count)*100);
          vizTip.innerHTML = '<b>'+escapeHtml(hit.consoleName||hit.shortName)+'</b>'+hit.count+' games \xB7 '+pct+'% completed<br>Beaten: '+hit.beaten+' \xA0\xB7\xA0 Mastered: '+hit.mastered;
          vizTip.style.display = 'block';
          vizTip.style.left = Math.min(e.offsetX+14, vizWrap.clientWidth-160)+'px';
          vizTip.style.top = Math.max(e.offsetY-68, 4)+'px';
        } else { vizTip.style.display = 'none'; }
      });
      vizWrap.addEventListener('mouseleave', function(){ vizTip.style.display='none'; });
      window.addEventListener('resize', pdStartViz);
      pdStartViz();
    }

    function fetchDashboardData() {
      // Scrape console data from DOM for totalGames/totalMastered stats
      var domData = scrapeConsoleBreakdown();

      var summaryUrl = 'https://retroachievements.org/API/API_GetUserSummary.php'
        + '?u=' + encodeURIComponent(targetUser)
        + '&y=' + encodeURIComponent(apiKey)
        + '&g=0&a=0';

      var recentAllUrl = 'https://retroachievements.org/API/API_GetUserRecentlyPlayedGames.php'
        + '?u=' + encodeURIComponent(targetUser)
        + '&y=' + encodeURIComponent(apiKey)
        + '&c=50&o=0';

      var recentAchUrl = 'https://retroachievements.org/API/API_GetUserRecentAchievements.php'
        + '?u=' + encodeURIComponent(targetUser)
        + '&y=' + encodeURIComponent(apiKey)
        + '&m=43200'; // 30 days in minutes

      var awardsUrl = 'https://retroachievements.org/API/API_GetUserAwards.php'
        + '?u=' + encodeURIComponent(targetUser)
        + '&y=' + encodeURIComponent(apiKey);

      // Build quarterly URLs for 1-year timeline (4 chunks of ~91 days)
      var now = Math.floor(Date.now() / 1000);
      var oneYearAgo = now - 365 * 24 * 60 * 60;
      var quarterSec = Math.ceil((now - oneYearAgo) / 4);
      var yearlyChunkUrls = [];
      for (var q = 0; q < 4; q++) {
        var f = oneYearAgo + q * quarterSec;
        var t = (q < 3) ? (oneYearAgo + (q + 1) * quarterSec) : now;
        yearlyChunkUrls.push(
          'https://retroachievements.org/API/API_GetAchievementsEarnedBetween.php'
          + '?u=' + encodeURIComponent(targetUser)
          + '&y=' + encodeURIComponent(apiKey)
          + '&f=' + f + '&t=' + t
        );
      }

      // Fetch summary + recent games + recent achievements (30d) + awards + yearly chunks in parallel
      var corePromises = [
        gmFetch(summaryUrl, 15000).then(function (r) { return JSON.parse(r.responseText); }).catch(function () { return null; }),
        gmFetch(recentAllUrl, 15000).then(function (r) { return JSON.parse(r.responseText); }).catch(function () { return null; }),
        gmFetch(recentAchUrl, 15000).then(function (r) { return JSON.parse(r.responseText); }).catch(function () { return null; }),
        gmFetch(awardsUrl, 15000).then(function (r) { return JSON.parse(r.responseText); }).catch(function () { return null; })
      ];
      var yearlyPromises = yearlyChunkUrls.map(function (url) {
        return gmFetch(url, 20000).then(function (r) { return JSON.parse(r.responseText); }).catch(function () { return []; });
      });

      Promise.all(corePromises.concat(yearlyPromises)).then(function (results) {
        var summary = results[0];
        var recentGames = results[1];
        var recentAchievements = results[2]; // 30-day data for rarest
        var awardsData = results[3]; // user awards (mastered/beaten dates)

        // Merge 4 quarterly chunks into yearlyAchievements
        var yearlyAchievements = [];
        for (var q = 0; q < 4; q++) {
          var chunk = results[4 + q];
          if (Array.isArray(chunk)) {
            yearlyAchievements = yearlyAchievements.concat(chunk);
          }
        }
        // Deduplicate by AchievementID + Date (in case of overlapping boundaries)
        var seen = {};
        yearlyAchievements = yearlyAchievements.filter(function (a) {
          var key = a.AchievementID + '|' + a.Date + '|' + a.HardcoreMode;
          if (seen[key]) return false;
          seen[key] = true;
          return true;
        });

        // --- Stats Cards ---
        var points = 0;
        var rank = '—';
        if (summary) {
          points = parseInt(summary.TotalPoints, 10) || 0;
          rank = summary.Rank || '—';
        }

        renderStatsCards({
          totalGames: domData.totalGames,
          mastered: domData.totalMastered,
          points: points,
          rank: rank
        });

        // --- Almost There ---
        var almostGames = [];
        if (recentGames && Array.isArray(recentGames)) {
          recentGames.forEach(function (g) {
            var earned = parseInt(g.NumAchieved, 10) || 0;
            var total = parseInt(g.NumPossibleAchievements, 10) || 0;
            if (total > 0 && earned < total) {
              var pct = earned / total;
              if (pct >= 0.5) {
                almostGames.push({
                  gameId: g.GameID,
                  title: g.Title || '',
                  imageIcon: g.ImageIcon || '',
                  earned: earned,
                  total: total,
                  pct: pct
                });
              }
            }
          });
          // Sort by pct descending (closest to 100% first)
          almostGames.sort(function (a, b) { return b.pct - a.pct; });
          almostGames = almostGames.slice(0, 5);
        }
        renderAlmostThere(almostGames);

        // --- Streak Tracker (uses yearly data for better accuracy) ---
        if (yearlyAchievements && yearlyAchievements.length > 0) {
          renderStreakTracker(yearlyAchievements);
        } else {
          streakSection.querySelector('.enhanced-streak-content').innerHTML =
            '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">Could not load streak data.</div>';
        }

        // --- Rarest Achievements (30-day data) ---
        if (recentAchievements && Array.isArray(recentAchievements)) {
          renderRarestAchievements(recentAchievements);
        } else {
          rarestSection.querySelector('.enhanced-rare-list').innerHTML =
            '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">Could not load rarity data.</div>';
        }

        // --- Build gameAwardsMap from awards data (for pagination Beaten/Mastered labels) ---
        var awardPriority = { 'mastered': 4, 'completed': 3, 'beaten-hardcore': 2, 'beaten-softcore': 1 };
        if (awardsData && Array.isArray(awardsData.VisibleUserAwards)) {
          awardsData.VisibleUserAwards.forEach(function (award) {
            if (!award.AwardedAt || !award.AwardData) return;
            var gameId = String(award.AwardData);
            var aType = (award.AwardType || '').toLowerCase();
            var extra = parseInt(award.AwardDataExtra, 10) || 0;
            var kind = '';
            if (aType === 'mastery/completion' || aType === 'mastery') {
              kind = extra === 1 ? 'mastered' : 'completed';
            } else if (aType === 'game beaten') {
              kind = extra === 1 ? 'beaten-hardcore' : 'beaten-softcore';
            }
            if (!kind) return;
            var existing = gameAwardsMap[gameId];
            if (!existing || (awardPriority[kind] || 0) > (awardPriority[existing.awardKind] || 0)) {
              gameAwardsMap[gameId] = { awardKind: kind, awardedAt: award.AwardedAt };
            }
          });
        }

        // --- Activity Timeline (yearly data + awards) ---
        // Process awards for mastered/beaten heatmap modes
        var masteredDayMap = {};
        var beatenDayMap = {};
        var oneYearAgoDate = new Date();
        oneYearAgoDate.setDate(oneYearAgoDate.getDate() - 365);
        oneYearAgoDate.setHours(0, 0, 0, 0);

        if (awardsData && Array.isArray(awardsData.VisibleUserAwards)) {
          awardsData.VisibleUserAwards.forEach(function (award) {
            if (!award.AwardedAt) return;
            var awardDate = new Date(award.AwardedAt);
            if (awardDate < oneYearAgoDate) return;
            var dayStr = awardDate.toISOString().substring(0, 10);

            // AwardType + AwardDataExtra: "Game Beaten" = beaten, "Mastery/Completion" with Extra=1 = mastered, Extra=0 = completed (softcore mastery)
            var aType = (award.AwardType || '').toLowerCase();
            if (aType === 'mastery/completion' || aType === 'mastery') {
              masteredDayMap[dayStr] = (masteredDayMap[dayStr] || 0) + 1;
            } else if (aType === 'game beaten') {
              beatenDayMap[dayStr] = (beatenDayMap[dayStr] || 0) + 1;
            }
          });
        }

        if (yearlyAchievements && yearlyAchievements.length > 0) {
          renderActivityTimeline(yearlyAchievements, masteredDayMap, beatenDayMap);
        } else {
          timelineSection.querySelector('.enhanced-timeline-content').innerHTML =
            '<div style="font-size:0.78rem;color:#525252;padding:4px 0;">Could not load activity data.</div>';
        }

        log.info('Dashboard loaded for ' + targetUser);
      }).catch(function (err) {
        log.warn('Dashboard failed: ' + err.message);
        statsRow.innerHTML = '<div style="color:#ef4444;font-size:0.8rem;grid-column:1/-1;">Failed to load dashboard</div>';
      });
    }

    renderProgressionDashboard();
    fetchDashboardData();

    function renderPaginator(container, offset, hasMore) {
      var currentPage = Math.floor(offset / ITEMS_PER_PAGE) + 1;
      lastKnownHasMore = hasMore;

      // Update highest known page
      if (currentPage > highestKnownPage) highestKnownPage = currentPage;
      if (hasMore && currentPage >= highestKnownPage) highestKnownPage = currentPage + 1;

      container.innerHTML = '';
      container.className = 'enhanced-pagination';

      function addBtn(label, page, disabled, isActive) {
        var btn = document.createElement('button');
        btn.textContent = label;
        btn.disabled = !!disabled;
        if (isActive) btn.className = 'active';
        if (!disabled) {
          btn.addEventListener('click', function () {
            doLoadPage((page - 1) * ITEMS_PER_PAGE);
          });
        }
        container.appendChild(btn);
      }

      // First button
      addBtn('First', 1, currentPage === 1, false);

      // Previous button (<) — goes back one page, disabled on page 1
      addBtn('\u276E', currentPage - 1, currentPage === 1, false);

      // Calculate visible page range (show up to 5 numbered buttons)
      var lastPage = highestKnownPage;
      var startP = Math.max(1, currentPage - 2);
      var endP = Math.min(lastPage, startP + 4);
      if (endP - startP < 4) startP = Math.max(1, endP - 4);

      // Ellipsis before
      if (startP > 1) {
        var dots = document.createElement('span');
        dots.className = 'page-info';
        dots.textContent = '...';
        container.appendChild(dots);
      }

      // Numbered page buttons
      for (var p = startP; p <= endP; p++) {
        addBtn(String(p), p, false, p === currentPage);
      }

      // Ellipsis after (if we know there are more pages beyond what we show)
      if (endP < lastPage || hasMore) {
        var dotsAfter = document.createElement('span');
        dotsAfter.className = 'page-info';
        dotsAfter.textContent = '...';
        container.appendChild(dotsAfter);
      }

      // Next button (>) — goes forward one page
      var nextTarget = currentPage + 1;
      var nextDisabled = !hasMore && currentPage >= lastPage;
      addBtn('\u276F', nextTarget, nextDisabled, false);

      // Page range info (e.g. "6–10")
      var rangeStart = offset + 1;
      var rangeEnd = offset + ITEMS_PER_PAGE;
      var rangeSpan = document.createElement('span');
      rangeSpan.className = 'page-info';
      rangeSpan.style.cssText = 'margin-left:8px;font-size:0.75rem;color:#a3a3a3;';
      rangeSpan.textContent = '(' + rangeStart + '–' + rangeEnd + ')';
      container.appendChild(rangeSpan);
    }

    // ConsoleID → { short name, icon filename } mapping (from RAWeb config/systems.php)
    var consoleIdMap = {
      1:{s:'MD',i:'md'},2:{s:'N64',i:'n64'},3:{s:'SNES',i:'snes'},4:{s:'GB',i:'gb'},
      5:{s:'GBA',i:'gba'},6:{s:'GBC',i:'gbc'},7:{s:'NES',i:'nes'},8:{s:'PCE',i:'pce'},
      9:{s:'SCD',i:'scd'},10:{s:'32X',i:'32-x'},11:{s:'SMS',i:'sms'},12:{s:'PS1',i:'ps1'},
      13:{s:'Lynx',i:'lynx'},14:{s:'NGP',i:'ngp'},15:{s:'GG',i:'gg'},16:{s:'GC',i:'gc'},
      17:{s:'JAG',i:'jag'},18:{s:'DS',i:'ds'},19:{s:'Wii',i:'wii'},20:{s:'WiiU',i:'wii-u'},
      21:{s:'PS2',i:'ps2'},22:{s:'Xbox',i:'xbox'},23:{s:'MO2',i:'mo-2'},24:{s:'MINI',i:'mini'},
      25:{s:'2600',i:'2600'},27:{s:'ARC',i:'arc'},28:{s:'VB',i:'vb'},29:{s:'MSX',i:'msx'},
      33:{s:'SG1K',i:'sg-1-k'},37:{s:'CPC',i:'cpc'},38:{s:'A2',i:'a2'},39:{s:'SAT',i:'sat'},
      40:{s:'DC',i:'dc'},41:{s:'PSP',i:'psp'},43:{s:'3DO',i:'3-do'},44:{s:'CV',i:'cv'},
      45:{s:'INTV',i:'intv'},46:{s:'VECT',i:'vect'},47:{s:'80/88',i:'8088'},49:{s:'PC-FX',i:'pc-fx'},
      51:{s:'7800',i:'7800'},53:{s:'WS',i:'ws'},56:{s:'NGCD',i:'ngcd'},57:{s:'CHF',i:'chf'},
      63:{s:'WSV',i:'wsv'},69:{s:'DUCK',i:'duck'},71:{s:'ARD',i:'ard'},72:{s:'WASM4',i:'wasm-4'},
      73:{s:'A2001',i:'a2001'},74:{s:'VC4000',i:'vc-4000'},75:{s:'ELEK',i:'elek'},
      76:{s:'PCCD',i:'pccd'},77:{s:'JCD',i:'jcd'},78:{s:'DSi',i:'dsi'},80:{s:'UZE',i:'uze'},
      81:{s:'FDS',i:'fds'},102:{s:'EXE',i:'exe'}
    };

    function getConsoleInfo(consoleId) {
      var entry = consoleIdMap[consoleId];
      if (entry) {
        return {
          shortName: entry.s,
          iconUrl: 'https://static.retroachievements.org/assets/images/system/' + entry.i + '.png'
        };
      }
      return { shortName: '', iconUrl: 'https://static.retroachievements.org/assets/images/system/unknown.png' };
    }

    // Cache for fetched achievement data per game
    var achievementCache = {};
    var playerCountCache = {};

    // Map GameID → { awardKind, awardedAt } from API_GetUserAwards
    var gameAwardsMap = {};

    function renderSkeletonBadges(container, count) {
      container.innerHTML = '';
      for (var i = 0; i < Math.min(count, 20); i++) {
        var span = document.createElement('span');
        span.className = 'inline';
        span.innerHTML = '<div style="width:48px;height:48px;border-radius:6px;background:rgba(255,255,255,0.08);animation:enhanced-skeleton-pulse 1.5s ease-in-out infinite;animation-delay:' + (i * 0.05) + 's;"></div>';
        container.appendChild(span);
      }
    }

    function fetchAndRenderAchievements(gameId, gridContainer, gameName) {
      if (achievementCache[gameId]) {
        renderAchievementBadges(achievementCache[gameId], gridContainer, gameName, playerCountCache[gameId] || 0);
        return;
      }

      renderSkeletonBadges(gridContainer, 12);

      var url = 'https://retroachievements.org/API/API_GetGameInfoAndUserProgress.php'
        + '?g=' + gameId
        + '&u=' + encodeURIComponent(targetUser)
        + '&y=' + encodeURIComponent(apiKey);

      gmFetch(url, 15000).then(function (resp) {
        var data = JSON.parse(resp.responseText);
        var achievements = data.Achievements || {};
        var numPlayers = parseInt(data.NumDistinctPlayers, 10) || 0;
        achievementCache[gameId] = achievements;
        playerCountCache[gameId] = numPlayers;
        renderAchievementBadges(achievements, gridContainer, gameName, numPlayers);
      }).catch(function () {
        gridContainer.innerHTML = '<div style="color:#ef4444;grid-column:1/-1;">Failed to load achievements</div>';
      });
    }

    function renderAchievementBadges(achievements, gridContainer, gameName, numPlayers) {
      gridContainer.innerHTML = '';
      var achList = Object.values(achievements);

      // Separate unlocked and locked, sort each by DisplayOrder
      var unlocked = achList.filter(function (a) { return a.DateEarned || a.DateEarnedHardcore; });
      var locked = achList.filter(function (a) { return !a.DateEarned && !a.DateEarnedHardcore; });

      // Unlocked: most recently earned first
      unlocked.sort(function (a, b) {
        var da = a.DateEarnedHardcore || a.DateEarned || '';
        var db = b.DateEarnedHardcore || b.DateEarned || '';
        return da > db ? -1 : da < db ? 1 : 0;
      });
      // Locked: by display order
      locked.sort(function (a, b) { return (a.DisplayOrder || 0) - (b.DisplayOrder || 0); });

      var sorted = unlocked.concat(locked);

      sorted.forEach(function (ach) {
        var isUnlocked = !!(ach.DateEarned || ach.DateEarnedHardcore);
        var badgeName = ach.BadgeName || '';
        var badgeUrl = isUnlocked
          ? 'https://media.retroachievements.org/Badge/' + badgeName + '.png'
          : 'https://media.retroachievements.org/Badge/' + badgeName + '_lock.png';
        var imgClass = isUnlocked ? 'goldimage' : 'badgeimglarge';

        var unlockText = '';
        if (ach.DateEarnedHardcore) {
          unlockText = '\nUnlocked ' + ach.DateEarnedHardcore + ' (hardcore)';
        } else if (ach.DateEarned) {
          unlockText = '\nUnlocked ' + ach.DateEarned;
        }

        // Calculate rarity from NumAwarded
        var rarityText = '';
        var borderStyle = '';
        var numAwarded = parseInt(ach.NumAwarded, 10) || 0;
        if (enableRarityIndicator && numPlayers > 0 && numAwarded > 0) {
          var pct = (numAwarded / numPlayers) * 100;
          var tier = getRarityTier(pct);
          rarityText = '\n' + tier.label + ' (' + pct.toFixed(1) + '% unlock rate)';
          borderStyle = 'border: 2px solid ' + tier.color + '; border-radius: 8px;';
        }

        var titleText = ach.Title + '\n' + (ach.Description || '') + '\n' + (ach.Points || 0) + ' points'
          + '\n' + (gameName || '') + unlockText + rarityText;

        var span = document.createElement('span');
        span.className = 'inline';
        span.innerHTML = '<a class="inline-block" href="https://retroachievements.org/achievement/' + ach.ID + '" title="' + escapeHtml(titleText) + '">'
          + '<img loading="lazy" decoding="async" width="48" height="48" src="' + badgeUrl + '" alt="' + escapeHtml(ach.Title || '') + '" class="' + imgClass + '"'
          + (borderStyle ? ' style="' + borderStyle + '"' : '') + '>'
          + '</a>';

        gridContainer.appendChild(span);
      });

      if (sorted.length === 0) {
        gridContainer.innerHTML = '<div style="color:#a3a3a3;grid-column:1/-1;">No achievements</div>';
      }
    }

    function renderGames(games) {
      gamesList.innerHTML = '';
      if (games.length === 0) {
        gamesList.innerHTML = '<div style="color:#a3a3a3;padding:12px;">No more games found.</div>';
        return;
      }
      games.forEach(function (game) {
        var imgSrc = game.ImageIcon
          ? "https://retroachievements.org" + game.ImageIcon
          : "";

        var numAchieved = game.NumAchieved || 0;
        var numHC = game.NumAchievedHardcore || 0;
        var numTotal = game.NumPossibleAchievements || 0;
        var totalScore = game.PossibleScore || 0;
        var hcScore = game.ScoreAchievedHardcore || 0;
        var scScore = game.ScoreAchieved || 0;
        var exclusiveSoftcore = Math.max(scScore - hcScore, 0);
        var leftPoints = hcScore >= exclusiveSoftcore ? hcScore : exclusiveSoftcore;

        // Progress percentages
        var hcPct = numTotal > 0 ? Math.floor((numHC / numTotal) * 100) : 0;
        var totalPct = numTotal > 0 ? Math.floor((numAchieved / numTotal) * 100) : 0;
        var softcoreBarWidth = Math.max(totalPct - hcPct, 0);

        // Achievement count text
        var achHtml = '';
        if (numTotal > 0) {
          if (numAchieved === numTotal) {
            achHtml = 'All <span class="font-bold">' + numAchieved + '</span> achievements';
          } else {
            achHtml = '<span class="font-bold">' + numAchieved + '</span> of <span class="font-bold">' + numTotal + '</span> achievements';
          }
        }

        // Points text
        var pointsHtml = '';
        if (totalScore > 0) {
          pointsHtml = '<span class="font-bold">' + leftPoints + '</span> of <span class="font-bold">' + totalScore + '</span> points';
          if (exclusiveSoftcore > 0 && exclusiveSoftcore < hcScore) {
            pointsHtml += ' (+<span class="font-bold">' + exclusiveSoftcore + '</span> softcore)';
          } else if (hcScore > 0 && exclusiveSoftcore > hcScore) {
            pointsHtml += ' (+<span class="font-bold">' + hcScore + '</span> hardcore)';
          }
        }

        // Last played date
        var lastPlayedLabel = '';
        if (game.LastPlayed) {
          var d = new Date(game.LastPlayed);
          var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
          lastPlayedLabel = months[d.getMonth()] + ' ' + d.getDate() + ' ' + d.getFullYear();
        }

        // Console info (short name + icon URL)
        var consoleInfo = getConsoleInfo(game.ConsoleID);

        // Determine award state — prefer gameAwardsMap (accurate), fallback to progress inference
        var awardKind = '';
        var awardInfo = gameAwardsMap[String(game.GameID)];
        if (awardInfo) {
          awardKind = awardInfo.awardKind;
        } else if (numTotal > 0 && numHC === numTotal) {
          awardKind = 'mastered';
        } else if (numTotal > 0 && numAchieved === numTotal) {
          awardKind = 'completed';
        }

        // Award title labels
        var awardTitles = { 'mastered':'Mastered', 'completed':'Completed', 'beaten-hardcore':'Beaten', 'beaten-softcore':'Beaten (softcore)' };
        var awardTitle = awardTitles[awardKind] || 'Unfinished';

        // Progress bar HTML (reusing site's existing CSS classes)
        var progressBarHtml = '';
        if (numTotal > 0) {
          progressBarHtml = '<div class="cprogress-pbar__root">'
            + '<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + totalPct + '">'
            + '<div style="width:' + hcPct + '%"' + (hcPct === 100 ? ' class="rounded-r"' : '') + '></div>'
            + '<div style="width:' + softcoreBarWidth + '%"' + (hcPct === 0 ? ' class="rounded-l"' : '') + (totalPct === 100 ? ' class="rounded-r"' : '') + '></div>'
            + '</div>'
            + '<p class="text-2xs flex justify-between w-full">' + totalPct + '%</p>'
            + '</div>';
        } else {
          progressBarHtml = '<div class="cprogress-pbar__root">'
            + '<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"></div>'
            + '<p class="text-2xs flex justify-between w-full">No achievements yet</p>'
            + '</div>';
        }

        // Award indicator HTML (reusing site's CSS classes)
        var awardIndicatorHtml = '<div class="cprogress-ind__root" data-award="' + awardKind + '" title="' + awardTitle + '">'
          + '<div><div></div><div></div></div>'
          + '</div>';

        // Console badge as <a> with icon image (matching original site structure)
        var consoleBadgeHtml = '<a href="https://retroachievements.org/user/' + encodeURIComponent(targetUser) + '/progress?filter%5Bsystem%5D=' + game.ConsoleID + '"'
          + ' class="hidden sm:flex gap-x-1 items-center rounded bg-zinc-950 light:bg-zinc-300 py-0.5 px-2">'
          + '<img src="' + consoleInfo.iconUrl + '" width="18" height="18" alt="' + escapeHtml(game.ConsoleName) + ' console icon">'
          + '<p>' + escapeHtml(consoleInfo.shortName || game.ConsoleName) + '</p>'
          + '</a>';

        // Build the card matching the site's original structure
        var item = document.createElement("div");
        item.className = 'relative flex flex-col w-full px-2 py-2 transition-all rounded-sm'
          + (awardKind ? ' bg-zinc-950/60 light:bg-stone-200' : ' bg-embed');

        item.innerHTML =
          '<div class="flex flex-col sm:flex-row w-full sm:justify-between sm:items-center gap-x-2">'
            + '<div class="flex sm:items-center gap-x-2.5">'
              // Game image
              + '<a href="/game/' + game.GameID + '">'
              + '<img src="' + imgSrc + '" width="58" height="58" class="rounded-sm w-[58px] h-[58px]" loading="lazy" decoding="async" />'
              + '</a>'
              // Primary meta
              + '<div class="cprogress-pmeta__root">'
                + '<a href="/game/' + game.GameID + '">' + escapeHtml(game.Title) + '</a>'
                + (achHtml ? '<div class="flex flex-col"><p>' + achHtml + '</p>' + (pointsHtml ? '<p>' + pointsHtml + '</p>' : '') + '</div>' : '')
                + (lastPlayedLabel ? '<div class="flex !flex-col-reverse"><p><span>Last played</span> ' + lastPlayedLabel + '</p>' + (awardKind && awardTitles[awardKind] ? '<p><span class="hidden md:inline lg:hidden">&bull;</span> ' + awardTitles[awardKind] + (awardInfo && awardInfo.awardedAt ? ' <span style="color:#a3a3a3;font-size:0.75rem;">' + escapeHtml(new Date(awardInfo.awardedAt).toLocaleDateString('en-US', { month:'short', day:'numeric', year:'numeric' })) + '</span>' : '') + '</p>' : '') + '</div>' : '')
              + '</div>'
            + '</div>'
            // Right side: console badge + progress bar + award + toggle
            + '<div class="mt-1 sm:mt-0">'
              + '<div class="flex gap-x-2 items-center sm:gap-x-4 sm:divide-x divide-neutral-700 ml-[68px] sm:ml-0">'
                + consoleBadgeHtml
                + progressBarHtml
                + awardIndicatorHtml
                + '<div class="absolute sm:static top-0 right-0 sm:pl-4">'
                  + '<button class="btn transition-transform lg:active:scale-95 duration-75 re-toggle-btn"'
                  + (numTotal <= 0 ? ' disabled' : '') + '>'
                  + '<div class="transition-transform duration-300 re-chevron-icon">'
                  + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16" fill="currentColor"><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>'
                  + '</div></button></div>'
              + '</div>'
            + '</div>'
          + '</div>'
          // Expandable achievements section
          + '<div class="re-expand-section" style="max-height:0;opacity:0;overflow:hidden;transition:all 300ms ease-in-out;">'
            + '<hr class="mt-2 border-embed-highlight">'
            + '<div class="py-4 place-content-center grid grid-cols-[repeat(auto-fill,minmax(52px,52px))] px-0.5 sm:px-4 re-badges-grid"></div>'
          + '</div>';

        // Toggle button click handler
        var toggleBtn = item.querySelector('.re-toggle-btn');
        var expandSection = item.querySelector('.re-expand-section');
        var chevronIcon = item.querySelector('.re-chevron-icon');
        var badgesGrid = item.querySelector('.re-badges-grid');
        var isExpanded = false;
        var hasFetched = false;

        if (toggleBtn && numTotal > 0) {
          toggleBtn.addEventListener('click', function () {
            isExpanded = !isExpanded;
            if (isExpanded) {
              expandSection.style.maxHeight = '2000px';
              expandSection.style.opacity = '1';
              chevronIcon.style.transform = 'rotate(180deg)';
              if (!hasFetched) {
                hasFetched = true;
                fetchAndRenderAchievements(game.GameID, badgesGrid, game.Title);
              }
            } else {
              expandSection.style.maxHeight = '0';
              expandSection.style.opacity = '0';
              chevronIcon.style.transform = 'rotate(0deg)';
            }
          });
        }

        gamesList.appendChild(item);
      });
    }

    function doLoadPage(offset) {
      currentOffset = offset;

      // Page 1: show original server-rendered content (only if default 5 items)
      if (offset === 0 && ITEMS_PER_PAGE === 5) {
        existingList.style.display = "";
        gamesList.innerHTML = '';
        recentH2.textContent = originalHeadingText;
        renderPaginator(paginationDiv, 0, true);
        return;
      }

      // Other pages: hide original, load from API
      existingList.style.display = "none";
      renderSkeletonCards(gamesList, ITEMS_PER_PAGE);

      var url = "https://retroachievements.org/API/API_GetUserRecentlyPlayedGames.php"
        + "?u=" + encodeURIComponent(targetUser)
        + "&y=" + encodeURIComponent(apiKey)
        + "&c=" + ITEMS_PER_PAGE
        + "&o=" + offset;

      gmFetch(url, 15000)
        .then(function (resp) {
          var games = JSON.parse(resp.responseText);
          var hasMore = games.length === ITEMS_PER_PAGE;
          renderGames(games);
          renderPaginator(paginationDiv, offset, hasMore);

          // Update heading (keep clean, range info is in paginator)
          if (games.length > 0) {
            recentH2.textContent = 'Recently Played Games';
          }

          recentH2.scrollIntoView({ behavior: "smooth", block: "start" });
        })
        .catch(function (err) {
          gamesList.innerHTML = '<div style="color:#ef4444;padding:12px;">Failed to load games: ' + escapeHtml(err.message) + '</div>';
        });
    }

    // Initial paginator (page 1 already visible from server render)
    renderPaginator(paginationDiv, 0, true);

    log.info("User pagination initialized for: " + targetUser);
  }

  // =========================================
  //   Game Awards — Beaten Tab
  // =========================================
  async function initGameAwardsBeaten() {
    var page = location.pathname;
    var userMatch = page.match(/^\/user\/([^\/?#]+)/);
    if (!userMatch) return;

    var targetUser = decodeURIComponent(userMatch[1]);
    var apiKey = await GM_getValue("raApiKey", "");
    if (!apiKey) return;

    // Find the native Game Awards section in the sidebar
    var gameAwardsDiv = document.getElementById('gameawards');
    if (!gameAwardsDiv) return;

    // Already injected?
    if (document.getElementById('re-game-awards-tabs')) return;

    var heading = gameAwardsDiv.querySelector('h3');
    var nativeGrid = gameAwardsDiv.querySelector('.component');
    if (!heading || !nativeGrid) return;

    // Parse native counters from heading
    var nativeCounters = heading.querySelector('.grow');
    var nativeCounterSpans = heading.querySelectorAll('.cursor-help');

    // Save reference to native heading counters for tab switching
    var headingCountersContainer = heading;
    var originalCountersHtml = '';
    // Capture all counter divs (mastered + completed)
    var counterDivs = heading.querySelectorAll('.cursor-help');
    counterDivs.forEach(function (el) { originalCountersHtml += el.outerHTML; });

    // Inject styles
    if (!document.getElementById('re-game-awards-style')) {
      var style = document.createElement('style');
      style.id = 're-game-awards-style';
      style.textContent = `
        .re-awards-tabs {
          display: flex;
          gap: 0;
          margin-bottom: 8px;
          border-radius: 6px;
          overflow: hidden;
          border: 1px solid rgba(255,255,255,0.1);
        }
        .re-awards-tab {
          flex: 1;
          padding: 6px 10px;
          font-size: 0.8rem;
          font-weight: 600;
          text-align: center;
          cursor: pointer;
          background: transparent;
          color: #a3a3a3;
          border: none;
          transition: all 0.2s;
          display: flex;
          align-items: center;
          justify-content: center;
          gap: 6px;
        }
        .re-awards-tab:hover { background: rgba(255,255,255,0.05); color: #e4e4e7; }
        .re-awards-tab.active {
          background: rgba(255,255,255,0.1);
          color: #e4e4e7;
        }
        .re-awards-tab .re-tab-count {
          font-size: 0.7rem;
          background: rgba(255,255,255,0.1);
          padding: 1px 6px;
          border-radius: 10px;
          min-width: 20px;
          text-align: center;
        }
        .re-awards-tab.active .re-tab-count {
          background: rgba(255,255,255,0.2);
        }
        .re-beaten-empty {
          grid-column: 1 / -1;
          text-align: center;
          color: #525252;
          font-size: 0.8rem;
          padding: 12px 0;
        }
      `;
      document.head.appendChild(style);
    }

    // Build tabs UI
    var tabsDiv = document.createElement('div');
    tabsDiv.id = 're-game-awards-tabs';
    tabsDiv.className = 're-awards-tabs';

    var masteredTab = document.createElement('button');
    masteredTab.className = 're-awards-tab active';
    masteredTab.innerHTML = '👑 Mastered <span class="re-tab-count" id="re-mastered-count">-</span>';

    var beatenTab = document.createElement('button');
    beatenTab.className = 're-awards-tab';
    beatenTab.innerHTML = '🏆 Beaten <span class="re-tab-count" id="re-beaten-count">-</span>';

    tabsDiv.appendChild(masteredTab);
    tabsDiv.appendChild(beatenTab);

    // Insert tabs before the grid
    nativeGrid.parentNode.insertBefore(tabsDiv, nativeGrid);

    // Create beaten grid (hidden by default) — same classes as native grid
    var beatenGrid = document.createElement('div');
    beatenGrid.className = 'component w-full place-content-center bg-embed gap-2 grid grid-cols-[repeat(auto-fill,minmax(52px,52px))] xl:rounded xl:py-2';
    beatenGrid.style.display = 'none';
    beatenGrid.innerHTML = '<div class="re-beaten-empty">Loading...</div>';
    nativeGrid.parentNode.insertBefore(beatenGrid, nativeGrid.nextSibling);

    // Count mastered from native section
    var masteredBadges = nativeGrid.querySelectorAll('.goldimage');
    var completedBadges = nativeGrid.querySelectorAll('.badgeimg.siteawards');
    var masteredCount = masteredBadges.length + completedBadges.length;
    var masteredCountEl = document.getElementById('re-mastered-count');
    if (masteredCountEl) masteredCountEl.textContent = String(masteredCount);

    // Variables to hold counts for heading update
    var beatenTotalCount = 0;
    var masteredTotalCount = masteredCount;
    var beatenHcCount = 0;
    var beatenScCount = 0;

    function updateHeadingCounters(mode) {
      // Remove existing counter divs from heading
      var existing = headingCountersContainer.querySelectorAll('.cursor-help');
      existing.forEach(function (el) { el.remove(); });

      if (mode === 'mastered') {
        // Restore original mastered/completed counters
        var temp = document.createElement('div');
        temp.innerHTML = originalCountersHtml;
        while (temp.firstChild) {
          headingCountersContainer.appendChild(temp.firstChild);
        }
      } else {
        // Show beaten counters
        if (beatenHcCount > 0) {
          var hcDiv = document.createElement('div');
          hcDiv.className = 'cursor-help flex gap-x-1 text-sm';
          hcDiv.title = beatenHcCount + (beatenHcCount === 1 ? ' game' : ' games') + ' beaten';
          hcDiv.innerHTML = '<div class="text-2xs">🏆</div><div class="numitems">' + beatenHcCount + '</div>';
          headingCountersContainer.appendChild(hcDiv);
        }
        if (beatenScCount > 0) {
          var scDiv = document.createElement('div');
          scDiv.className = 'cursor-help flex gap-x-1 text-sm';
          scDiv.title = beatenScCount + (beatenScCount === 1 ? ' game' : ' games') + ' beaten (softcore)';
          scDiv.innerHTML = '<div class="text-2xs">🎖️</div><div class="numitems">' + beatenScCount + '</div>';
          headingCountersContainer.appendChild(scDiv);
        }
        if (beatenHcCount === 0 && beatenScCount === 0) {
          var emptyDiv = document.createElement('div');
          emptyDiv.className = 'cursor-help flex gap-x-1 text-sm';
          emptyDiv.title = '0 games beaten';
          emptyDiv.innerHTML = '<div class="text-2xs">🏆</div><div class="numitems">0</div>';
          headingCountersContainer.appendChild(emptyDiv);
        }
      }
    }

    // Tab switching
    masteredTab.addEventListener('click', function () {
      masteredTab.classList.add('active');
      beatenTab.classList.remove('active');
      nativeGrid.style.display = '';
      beatenGrid.style.display = 'none';
      updateHeadingCounters('mastered');
    });

    beatenTab.addEventListener('click', function () {
      beatenTab.classList.add('active');
      masteredTab.classList.remove('active');
      nativeGrid.style.display = 'none';
      beatenGrid.style.display = '';
      updateHeadingCounters('beaten');
    });

    // Fetch beaten games from API
    var awardsUrl = 'https://retroachievements.org/API/API_GetUserAwards.php'
      + '?u=' + encodeURIComponent(targetUser)
      + '&y=' + encodeURIComponent(apiKey);

    gmFetch(awardsUrl, 15000).then(function (resp) {
      var data = JSON.parse(resp.responseText);
      var awards = data.VisibleUserAwards || [];

      // Filter beaten awards (exclude events)
      var beatenAwards = awards.filter(function (a) {
        return (a.AwardType || '').toLowerCase() === 'game beaten'
          && a.ConsoleName !== 'Events';
      });

      // Also update mastered count from API for accuracy
      var masteredAwards = awards.filter(function (a) {
        var aType = (a.AwardType || '').toLowerCase();
        return (aType === 'mastery/completion' || aType === 'mastery')
          && a.ConsoleName !== 'Events';
      });
      masteredTotalCount = masteredAwards.length;
      if (masteredCountEl) masteredCountEl.textContent = String(masteredAwards.length);

      // Count beaten by mode
      beatenHcCount = beatenAwards.filter(function (a) { return parseInt(a.AwardDataExtra, 10) === 1; }).length;
      beatenScCount = beatenAwards.filter(function (a) { return parseInt(a.AwardDataExtra, 10) !== 1; }).length;
      beatenTotalCount = beatenAwards.length;

      var beatenCountEl = document.getElementById('re-beaten-count');
      if (beatenCountEl) beatenCountEl.textContent = String(beatenAwards.length);

      // Render beaten badges
      beatenGrid.innerHTML = '';
      if (beatenAwards.length === 0) {
        beatenGrid.innerHTML = '<div class="re-beaten-empty">No beaten games yet.</div>';
        return;
      }

      beatenAwards.forEach(function (award) {
        var gameId = award.AwardData;
        var imageIcon = award.ImageIcon || '';
        var isHardcore = parseInt(award.AwardDataExtra, 10) === 1;
        var imgSrc = imageIcon ? 'https://media.retroachievements.org' + imageIcon : '';
        var imgClass = isHardcore ? 'goldimage' : 'badgeimg siteawards';

        // Use same structure as native mastered badges with Alpine.js tooltip
        var wrapper = document.createElement('span');
        wrapper.className = 'inline';
        wrapper.setAttribute('x-data', "tooltipComponent($el, { dynamicType: 'game', dynamicId: '" + gameId + "', dynamicContext: '" + escapeHtml(targetUser) + "' })");
        wrapper.setAttribute('@mouseover', 'showTooltip($event)');
        wrapper.setAttribute('@mouseleave', 'hideTooltip');
        wrapper.setAttribute('@mousemove', 'trackMouseMovement($event)');
        wrapper.innerHTML = '<a class="inline-block" href="/game/' + gameId + '">'
          + '<img loading="lazy" decoding="async" width="48" height="48"'
          + ' src="' + escapeHtml(imgSrc) + '"'
          + ' alt="" class="' + imgClass + '">'
          + '</a>';

        beatenGrid.appendChild(wrapper);
      });

      // Initialize Alpine.js on the dynamically created tooltip elements
      if (window.Alpine && Alpine.initTree) {
        Alpine.initTree(beatenGrid);
      }

    }).catch(function (err) {
      beatenGrid.innerHTML = '<div class="re-beaten-empty">Failed to load beaten games.</div>';
      log.warn('Game Awards Beaten fetch failed: ' + err.message);
    });
  }

  // =========================================
  //   Games Page — Most Mastered Filter Tab
  // =========================================
  async function initGamesMostMastered() {
    if (!/^\/games\/?$/i.test(location.pathname)) return;

    // Already injected?
    if (document.getElementById('re-most-mastered-tab')) return;

    // Wait for the React toolbar to render (the bg-embed filter row)
    var toolbar = null;
    for (var attempt = 0; attempt < 20; attempt++) {
      toolbar = document.querySelector('div.flex.w-full.flex-col.justify-between.gap-2');
      if (toolbar) break;
      await new Promise(function (r) { setTimeout(r, 300); });
    }
    if (!toolbar) {
      log.warn('Most Mastered: toolbar not found');
      return;
    }

    // Inject styles
    if (!document.getElementById('re-most-mastered-style')) {
      var style = document.createElement('style');
      style.id = 're-most-mastered-style';
      style.textContent = [
        '.re-mm-tabs{display:flex;gap:0;border-radius:6px;overflow:hidden;border:1px solid rgba(255,255,255,0.1);margin-bottom:4px}',
        '.re-mm-tab{flex:none;padding:6px 14px;font-size:0.8rem;font-weight:600;text-align:center;cursor:pointer;background:transparent;color:#a3a3a3;border:none;transition:all 0.2s;display:flex;align-items:center;gap:6px}',
        '.re-mm-tab:hover{background:rgba(255,255,255,0.05);color:#e4e4e7}',
        '.re-mm-tab.active{background:rgba(255,255,255,0.1);color:#e4e4e7}',
        '.re-mm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;padding:8px 0}',
        '.re-mm-card{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);transition:all 0.2s}',
        '.re-mm-card:hover{background:rgba(255,255,255,0.07);border-color:rgba(255,255,255,0.15)}',
        '.re-mm-card img{width:48px;height:48px;border-radius:6px;object-fit:cover}',
        '.re-mm-card-info{flex:1;min-width:0}',
        '.re-mm-card-title{font-size:0.85rem;font-weight:600;color:#e4e4e7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-decoration:none;display:block}',
        '.re-mm-card-title:hover{color:#60a5fa;text-decoration:underline}',
        '.re-mm-card-meta{font-size:0.72rem;color:#737373;margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}',
        '.re-mm-card-badge{font-size:0.65rem;padding:1px 6px;border-radius:10px;display:inline-flex;align-items:center;gap:3px}',
        '.re-mm-badge-players{background:rgba(59,130,246,0.15);color:#60a5fa}',
        '.re-mm-badge-beaten{background:rgba(34,197,94,0.15);color:#4ade80}',
        '.re-mm-badge-achievements{background:rgba(234,179,8,0.15);color:#facc15}',
        '.re-mm-rank{font-size:0.75rem;font-weight:700;color:#525252;min-width:24px;text-align:center}',
        '.re-mm-loading{text-align:center;padding:24px;color:#737373;font-size:0.85rem}',
        '.re-mm-pagination{display:flex;justify-content:center;gap:6px;padding:12px 0}',
        '.re-mm-page-btn{padding:4px 10px;border-radius:6px;border:1px solid rgba(255,255,255,0.1);background:transparent;color:#a3a3a3;cursor:pointer;font-size:0.8rem;transition:all 0.2s}',
        '.re-mm-page-btn:hover{background:rgba(255,255,255,0.05);color:#e4e4e7}',
        '.re-mm-page-btn.active{background:rgba(255,255,255,0.1);color:#e4e4e7;font-weight:600}',
        '.re-mm-page-btn:disabled{opacity:0.4;cursor:default}',
        '.re-mm-system-tag{font-size:0.65rem;padding:1px 5px;border-radius:4px;background:rgba(139,92,246,0.15);color:#a78bfa}',
        '@media (prefers-color-scheme:light){',
        '  .re-mm-tab{color:#525252}',
        '  .re-mm-tab:hover,.re-mm-tab.active{background:rgba(0,0,0,0.06);color:#1a1a1a}',
        '  .re-mm-card{background:rgba(0,0,0,0.03);border-color:rgba(0,0,0,0.08)}',
        '  .re-mm-card:hover{background:rgba(0,0,0,0.06);border-color:rgba(0,0,0,0.15)}',
        '  .re-mm-card-title{color:#1a1a1a}',
        '  .re-mm-card-title:hover{color:#2563eb}',
        '  .re-mm-card-meta{color:#737373}',
        '  .re-mm-rank{color:#a3a3a3}',
        '  .re-mm-page-btn{color:#525252;border-color:rgba(0,0,0,0.1)}',
        '  .re-mm-page-btn.active{background:rgba(0,0,0,0.06);color:#1a1a1a}',
        '}'
      ].join('\n');
      document.head.appendChild(style);
    }

    // Build tabs bar
    var tabsBar = document.createElement('div');
    tabsBar.id = 're-most-mastered-tab';
    tabsBar.className = 're-mm-tabs';

    var defaultTab = document.createElement('button');
    defaultTab.className = 're-mm-tab active';
    defaultTab.textContent = '📋 All Games';

    var masteredTab = document.createElement('button');
    masteredTab.className = 're-mm-tab';
    masteredTab.innerHTML = '👑 Most Mastered';

    tabsBar.appendChild(defaultTab);
    tabsBar.appendChild(masteredTab);

    // Insert tabs above the toolbar
    toolbar.parentNode.insertBefore(tabsBar, toolbar);

    // Create container for most mastered results (hidden by default)
    var mmContainer = document.createElement('div');
    mmContainer.id = 're-most-mastered-container';
    mmContainer.style.display = 'none';

    // Find the existing table container (next sibling of toolbar)
    var existingTableWrapper = toolbar.parentNode;

    // Insert after the toolbar's parent
    existingTableWrapper.parentNode.insertBefore(mmContainer, existingTableWrapper.nextSibling);

    // State
    var mmLoaded = false;
    var mmCurrentPage = 1;
    var mmPageSize = 25;
    var mmTotalPages = 1;
    var mmData = [];

    function renderMasteredGrid(items, page, totalPages, total) {
      var startRank = (page - 1) * mmPageSize + 1;

      var html = '<div class="re-mm-loading" style="padding:4px 0;font-size:0.8rem;color:#737373;text-align:right">'
        + '👑 ' + total.toLocaleString() + ' games with achievements — sorted by most players'
        + '</div>'
        + '<div class="re-mm-grid">';

      items.forEach(function (entry, i) {
        var game = entry.game || entry;
        var rank = startRank + i;
        var title = game.title || 'Unknown';
        var imgUrl = game.badgeUrl || '';
        var playersTotal = game.playersTotal || 0;
        var timesBeatenHc = game.timesBeatenHardcore || 0;
        var achievements = game.achievementsPublished || 0;
        var systemName = (game.system && game.system.name) ? game.system.name : '';
        var gameId = game.id || 0;

        html += '<div class="re-mm-card">'
          + '<div class="re-mm-rank">#' + rank + '</div>'
          + '<a href="/game/' + gameId + '">'
          + '<img loading="lazy" decoding="async" src="' + escapeHtml(imgUrl) + '" alt="' + escapeHtml(title) + '">'
          + '</a>'
          + '<div class="re-mm-card-info">'
          + '<a class="re-mm-card-title" href="/game/' + gameId + '" title="' + escapeHtml(title) + '">' + escapeHtml(title) + '</a>'
          + '<div class="re-mm-card-meta">'
          + '<span class="re-mm-card-badge re-mm-badge-players">👥 ' + playersTotal.toLocaleString() + ' players</span>'
          + (timesBeatenHc > 0 ? '<span class="re-mm-card-badge re-mm-badge-beaten">🏆 ' + timesBeatenHc.toLocaleString() + ' beaten</span>' : '')
          + '<span class="re-mm-card-badge re-mm-badge-achievements">⭐ ' + achievements + ' achievements</span>'
          + (systemName ? '<span class="re-mm-system-tag">' + escapeHtml(systemName) + '</span>' : '')
          + '</div>'
          + '</div>'
          + '</div>';
      });

      html += '</div>';

      // Pagination
      if (totalPages > 1) {
        html += '<div class="re-mm-pagination">';
        html += '<button class="re-mm-page-btn" data-page="1"' + (page <= 1 ? ' disabled' : '') + '>First</button>';
        html += '<button class="re-mm-page-btn" data-page="' + (page - 1) + '"' + (page <= 1 ? ' disabled' : '') + '>‹ Prev</button>';

        var startPage = Math.max(1, page - 2);
        var endPage = Math.min(totalPages, page + 2);
        for (var p = startPage; p <= endPage; p++) {
          html += '<button class="re-mm-page-btn' + (p === page ? ' active' : '') + '" data-page="' + p + '">' + p + '</button>';
        }

        html += '<button class="re-mm-page-btn" data-page="' + (page + 1) + '"' + (page >= totalPages ? ' disabled' : '') + '>Next ›</button>';
        html += '<button class="re-mm-page-btn" data-page="' + totalPages + '"' + (page >= totalPages ? ' disabled' : '') + '>Last</button>';
        html += '</div>';
      }

      mmContainer.innerHTML = html;

      // Bind pagination click events
      mmContainer.querySelectorAll('.re-mm-page-btn').forEach(function (btn) {
        btn.addEventListener('click', function () {
          var targetPage = parseInt(btn.getAttribute('data-page'), 10);
          if (targetPage && targetPage !== mmCurrentPage && targetPage >= 1 && targetPage <= mmTotalPages) {
            mmCurrentPage = targetPage;
            fetchMasteredPage(targetPage);
          }
        });
      });
    }

    function fetchMasteredPage(page) {
      mmContainer.innerHTML = '<div class="re-mm-loading">Loading most mastered games...</div>';

      var apiUrl = '/internal-api/games'
        + '?page%5Bnumber%5D=' + page
        + '&page%5Bsize%5D=' + mmPageSize
        + '&sort=-playersTotal'
        + '&filter%5BachievementsPublished%5D=has';

      fetch(apiUrl, {
        credentials: 'same-origin',
        headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
      })
        .then(function (resp) {
          if (!resp.ok) throw new Error('HTTP ' + resp.status);
          return resp.json();
        })
        .then(function (data) {
          var items = data.items || [];
          var total = data.total || 0;
          mmTotalPages = data.lastPage || 1;
          mmCurrentPage = page;
          mmLoaded = true;

          if (items.length === 0) {
            mmContainer.innerHTML = '<div class="re-mm-loading">No games found.</div>';
            return;
          }

          renderMasteredGrid(items, page, mmTotalPages, total);
        })
        .catch(function (err) {
          log.warn('Most Mastered fetch failed: ' + err.message);
          mmContainer.innerHTML = '<div class="re-mm-loading">Failed to load games. ' + escapeHtml(err.message) + '</div>';
        });
    }

    // Tab switching
    defaultTab.addEventListener('click', function () {
      defaultTab.classList.add('active');
      masteredTab.classList.remove('active');
      existingTableWrapper.style.display = '';
      mmContainer.style.display = 'none';
    });

    masteredTab.addEventListener('click', function () {
      masteredTab.classList.add('active');
      defaultTab.classList.remove('active');
      existingTableWrapper.style.display = 'none';
      mmContainer.style.display = '';

      if (!mmLoaded) {
        fetchMasteredPage(1);
      }
    });

    log.info('Most Mastered tab initialized on /games');
  }

  // =========================================
  //   Achievements Nav Links (restored from removed header menu)
  // =========================================
  function initAchievementNavLinks() {
    // Already injected?
    if (document.getElementById('re-achievements-dropdown')) return;

    // Find all nav-item dropdowns in the navbar
    var navItems = document.querySelectorAll('.dropdown.nav-item');
    if (!navItems.length) return;

    // Find the "Games" dropdown by checking trigger text
    var gamesDropdown = null;
    for (var i = 0; i < navItems.length; i++) {
      var trigger = navItems[i].querySelector('.nav-link');
      if (trigger && /\bgames?\b/i.test(trigger.textContent)) {
        gamesDropdown = navItems[i];
        break;
      }
    }
    if (!gamesDropdown) return;

    // Build the Achievements dropdown with same structure as native dropdowns
    var achDropdown = document.createElement('div');
    achDropdown.id = 're-achievements-dropdown';
    achDropdown.className = 'dropdown nav-item';

    var btn = document.createElement('button');
    btn.className = 'nav-link';
    btn.setAttribute('role', 'button');
    btn.setAttribute('aria-haspopup', 'true');
    btn.setAttribute('aria-expanded', 'false');
    btn.title = 'Achievements';
    btn.innerHTML = '<span style="font-size:0.85em;">🏆</span> <span class="ml-1 hidden sm:inline-block">Achievements</span>';

    var menu = document.createElement('div');
    menu.className = 'dropdown-menu';

    var links = [
      { href: '/achievementList.php', text: 'All Achievements' },
      { href: '/achievementList.php?s=4&p=2', text: '🟢 Easy Achievements' },
      { href: '/achievementList.php?s=14&p=2', text: '🔴 Hardest Achievements' }
    ];

    links.forEach(function (item, idx) {
      if (idx === 1) {
        var div = document.createElement('div');
        div.className = 'dropdown-divider';
        menu.appendChild(div);
      }
      var a = document.createElement('a');
      a.className = 'dropdown-item';
      a.href = item.href;
      a.textContent = item.text;
      menu.appendChild(a);
    });

    achDropdown.appendChild(btn);
    achDropdown.appendChild(menu);

    // Insert after the Games dropdown
    gamesDropdown.parentNode.insertBefore(achDropdown, gamesDropdown.nextSibling);
  }

  // =========================================
  //   Comments (User Wall + Achievement Pages) — Linkify + Media Embed
  // =========================================
  function initWallLinkify() {
    var isUserCommentsPage = /^\/user\/[^\/]+(\/(comments)?)?$/i.test(location.pathname);
    var isAchievementPage = /^\/achievement\/\d+(\/.*)?$/i.test(location.pathname);
    if (!isUserCommentsPage && !isAchievementPage) return;

    // Inject CSS once
    if (!document.getElementById('enhanced-wall-linkify-style')) {
      var style = document.createElement('style');
      style.id = 'enhanced-wall-linkify-style';
      style.textContent = `
        .enhanced-wall-link {
          color: var(--ra-accent, #3b82f6);
          text-decoration: underline;
          word-break: break-all;
        }
        .enhanced-wall-link:hover {
          opacity: 0.8;
        }
        .enhanced-yt-embed {
          display: block;
          margin-top: 6px;
          border-radius: 6px;
          overflow: hidden;
          max-width: 360px;
          aspect-ratio: 16/9;
        }
        .enhanced-yt-embed iframe {
          width: 100%;
          height: 100%;
          border: 0;
        }
        .enhanced-img-preview {
          display: block;
          margin-top: 6px;
          max-width: 360px;
          max-height: 300px;
          border-radius: 6px;
          object-fit: contain;
          cursor: pointer;
        }
        .enhanced-img-preview:hover {
          opacity: 0.85;
        }
      `;
      document.head.appendChild(style);
    }

    // Check if URL points to an image
    var imageExtRegex = /\.(png|jpe?g|gif|webp|bmp|svg|ico|avif)(\?[^#]*)?$/i;
    function isImageUrl(url) {
      return imageExtRegex.test(url);
    }

    // Extract YouTube video ID from various URL formats
    function extractYouTubeId(url) {
      var m = url.match(/(?:youtube\.com\/watch\?.*v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/);
      return m ? m[1] : null;
    }

    function linkifyCommentBody(bodyEl) {
      if (bodyEl.getAttribute('data-enhanced-linkified')) return;
      bodyEl.setAttribute('data-enhanced-linkified', '1');

      // Process text nodes only (preserve existing HTML structure)
      var walker = document.createTreeWalker(bodyEl, NodeFilter.SHOW_TEXT, null, false);
      var textNodes = [];
      while (walker.nextNode()) textNodes.push(walker.currentNode);

      // URL regex — match http(s) and www. URLs
      var urlRegex = /(https?:\/\/[^\s<>"']+|www\.[^\s<>"']+)/gi;

      var youtubeIds = [];
      var imageUrls = [];

      textNodes.forEach(function (node) {
        var text = node.textContent;
        if (!urlRegex.test(text)) return;
        urlRegex.lastIndex = 0;

        var frag = document.createDocumentFragment();
        var lastIdx = 0;
        var match;

        while ((match = urlRegex.exec(text)) !== null) {
          // Add text before the match
          if (match.index > lastIdx) {
            frag.appendChild(document.createTextNode(text.slice(lastIdx, match.index)));
          }

          var rawUrl = match[0].replace(/[.,;:!?)]+$/, ''); // trim trailing punctuation
          var href = rawUrl.startsWith('http') ? rawUrl : 'https://' + rawUrl;

          var a = document.createElement('a');
          a.href = href;
          a.target = '_blank';
          a.rel = 'noopener noreferrer';
          a.className = 'enhanced-wall-link';
          a.textContent = rawUrl;
          frag.appendChild(a);

          // Check for YouTube
          var ytId = extractYouTubeId(href);
          if (ytId && youtubeIds.indexOf(ytId) === -1) {
            youtubeIds.push(ytId);
          }

          // Check for image
          if (isImageUrl(href) && imageUrls.indexOf(href) === -1) {
            imageUrls.push(href);
          }

          lastIdx = match.index + match[0].length;
          // Adjust if we trimmed trailing punctuation
          var trimmed = match[0].length - rawUrl.length;
          if (trimmed > 0) {
            frag.appendChild(document.createTextNode(match[0].slice(match[0].length - trimmed)));
          }
        }

        // Remaining text after last match
        if (lastIdx < text.length) {
          frag.appendChild(document.createTextNode(text.slice(lastIdx)));
        }

        node.parentNode.replaceChild(frag, node);
      });

      // Append YouTube embeds after the comment body
      youtubeIds.forEach(function (ytId) {
        var wrapper = document.createElement('div');
        wrapper.className = 'enhanced-yt-embed';
        var iframe = document.createElement('iframe');
        iframe.src = 'https://www.youtube-nocookie.com/embed/' + encodeURIComponent(ytId);
        iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
        iframe.allowFullscreen = true;
        iframe.loading = 'lazy';
        wrapper.appendChild(iframe);
        bodyEl.appendChild(wrapper);
      });

      // Append image previews
      imageUrls.forEach(function (imgUrl) {
        var img = document.createElement('img');
        img.className = 'enhanced-img-preview';
        img.src = imgUrl;
        img.alt = 'Image preview';
        img.loading = 'lazy';
        img.title = 'Click to open in new tab';
        img.addEventListener('click', function () {
          window.open(imgUrl, '_blank', 'noopener,noreferrer');
        });
        bodyEl.appendChild(img);
      });
    }

    function processAllComments() {
      var commentItems = document.querySelectorAll(
        'tr.comment.group, .commentscomponent tr.comment, ul.highlighted-list > li'
      );

      commentItems.forEach(function (el) {
        var bodyEl = null;

        // Strategy 1: element with word-break style
        var candidates = el.querySelectorAll('[style*="word-break"]');
        for (var i = 0; i < candidates.length; i++) {
          if (candidates[i].textContent.trim()) {
            bodyEl = candidates[i];
            break;
          }
        }

        // Strategy 2: legacy Blade — td with colspan
        if (!bodyEl) {
          var td = el.querySelector('td[colspan]') || el.querySelector('td.w-full');
          if (td) {
            var divs = td.querySelectorAll(':scope > div');
            for (var j = divs.length - 1; j >= 0; j--) {
              var txt = divs[j].textContent.trim();
              if (txt && !divs[j].querySelector('.smalldate') && txt.length > 2) {
                bodyEl = divs[j];
                break;
              }
            }
          }
        }

        // Strategy 3: React — p inside div.w-full
        if (!bodyEl) {
          var contentDiv = el.querySelector('div.w-full');
          if (contentDiv) {
            var ps = contentDiv.querySelectorAll(':scope > p');
            for (var k = ps.length - 1; k >= 0; k--) {
              var t = ps[k].textContent.trim();
              if (t && !ps[k].querySelector('.smalldate') && t.length > 2) {
                bodyEl = ps[k];
                break;
              }
            }
          }
        }

        if (!bodyEl) return;
        linkifyCommentBody(bodyEl);
      });
    }

    setTimeout(processAllComments, 800);

    var linkifyObserver = new MutationObserver(function () {
      processAllComments();
    });
    var container = document.querySelector('.commentscomponent')
      || document.querySelector('ul.highlighted-list')
      || document.querySelector('main')
      || document.body;
    linkifyObserver.observe(container, { childList: true, subtree: true });

    log.info('Comments linkify initialized');
  }

  // =========================================
  //   Comment Translation (User Wall + Achievement Pages)
  // =========================================
  async function initWallTranslation() {
    var isUserCommentsPage = /^\/user\/[^\/]+(\/(comments)?)?$/i.test(location.pathname);
    var isAchievementPage = /^\/achievement\/\d+(\/.*)?$/i.test(location.pathname);
    if (!isUserCommentsPage && !isAchievementPage) return;

    var wallLang = await GM_getValue("translateLang", "pt-BR");

    function wallTranslateText(text, targetLang) {
      return translateWithRateLimit(text, targetLang);
    }

    // Inject CSS once (reuses same class names as achievement translate)
    if (!document.getElementById("enhanced-wall-translate-style")) {
      var style = document.createElement("style");
      style.id = "enhanced-wall-translate-style";
      style.textContent = `
        .enhanced-wall-translate-btn {
          display: inline-flex;
          align-items: center;
          gap: 3px;
          padding: 1px 6px;
          border: 1px solid rgba(255,255,255,0.15);
          border-radius: 4px;
          background: transparent;
          color: #a3a3a3;
          font-size: 0.7em;
          cursor: pointer;
          transition: all 0.2s;
          vertical-align: middle;
          margin-top: 4px;
        }
        .enhanced-wall-translate-btn:hover {
          background: rgba(255,255,255,0.08);
          color: #e5e5e5;
          border-color: rgba(255,255,255,0.25);
        }
        .enhanced-wall-translate-btn.translating {
          opacity: 0.6;
          pointer-events: none;
        }
        .enhanced-wall-translate-btn.translated {
          color: #3b82f6;
          border-color: rgba(59,130,246,0.3);
        }
        .enhanced-wall-translate-btn.disabled {
          opacity: 0.4;
          cursor: not-allowed;
          pointer-events: none;
        }
      `;
      document.head.appendChild(style);
    }

    function injectWallTranslateButtons() {
      // Legacy profile page: <tr class="comment group"> inside <table id="feed">
      //   Comment body: <div style="word-break: break-word;"> inside <td>
      // React /comments page: <li class="group ..."> inside <ul class="highlighted-list">
      //   Comment body: <p style="word-break: break-word">
      var commentItems = document.querySelectorAll(
        'tr.comment.group, .commentscomponent tr.comment, ul.highlighted-list > li'
      );

      commentItems.forEach(function (el) {
        if (el.querySelector('.enhanced-wall-translate-btn')) return;

        // Find the comment body element (<div> or <p> with word-break style)
        var bodyEl = null;

        // Strategy 1: any element with word-break in style (works for both legacy <div> and React <p>)
        var candidates = el.querySelectorAll('[style*="word-break"]');
        for (var i = 0; i < candidates.length; i++) {
          if (candidates[i].textContent.trim()) {
            bodyEl = candidates[i];
            break;
          }
        }

        // Strategy 2: For legacy Blade — <td> with colspan, last <div> child
        if (!bodyEl) {
          var td = el.querySelector('td[colspan]') || el.querySelector('td.w-full');
          if (td) {
            var divs = td.querySelectorAll(':scope > div');
            for (var j = divs.length - 1; j >= 0; j--) {
              var txt = divs[j].textContent.trim();
              if (txt && !divs[j].querySelector('.smalldate') && txt.length > 2) {
                bodyEl = divs[j];
                break;
              }
            }
          }
        }

        // Strategy 3: For React — <p> inside div.w-full
        if (!bodyEl) {
          var contentDiv = el.querySelector('div.w-full');
          if (contentDiv) {
            var ps = contentDiv.querySelectorAll(':scope > p');
            for (var k = ps.length - 1; k >= 0; k--) {
              var t = ps[k].textContent.trim();
              if (t && !ps[k].querySelector('.smalldate') && t.length > 2) {
                bodyEl = ps[k];
                break;
              }
            }
          }
        }

        if (!bodyEl || !bodyEl.textContent.trim()) return;

        var btn = document.createElement('button');
        btn.className = 'enhanced-wall-translate-btn';

        var commentText = bodyEl.textContent.trim();
        if (commentText.length > 500) {
          btn.classList.add('disabled');
          btn.title = 'Text exceeds 500 character limit for translation (' + commentText.length + ' chars)';
          btn.innerHTML = '&#x1F310; Too long';
          bodyEl.after(btn);
          return;
        }

        btn.title = 'Translate to ' + wallLang;
        btn.innerHTML = '&#x1F310; Translate';

        var isTranslated = false;
        var originalText = bodyEl.innerHTML;
        var translatedText = null;

        btn.addEventListener('click', function () {
          if (btn.classList.contains('translating')) return;

          if (isTranslated) {
            bodyEl.innerHTML = originalText;
            btn.innerHTML = '&#x1F310; Translate';
            btn.classList.remove('translated');
            isTranslated = false;
            return;
          }

          if (translatedText) {
            bodyEl.innerHTML = translatedText;
            btn.innerHTML = '&#x1F310; Original';
            btn.classList.add('translated');
            isTranslated = true;
            return;
          }

          btn.classList.add('translating');
          btn.innerHTML = '&#x23F3; ...';

          wallTranslateText(bodyEl.textContent.trim(), wallLang)
            .then(function (result) {
              // Preserve line breaks
              translatedText = escapeHtml(result).replace(/\n/g, '<br>');
              bodyEl.innerHTML = translatedText;
              btn.innerHTML = '&#x1F310; Original';
              btn.classList.remove('translating');
              btn.classList.add('translated');
              isTranslated = true;
            })
            .catch(function (err) {
              log.warn('Wall translation failed: ' + err.message);
              var isRateLimit = err.message && err.message.indexOf('RATE_LIMIT') === 0;
              btn.innerHTML = isRateLimit ? '&#x26D4; Limit' : '&#x26A0; Error';
              btn.title = isRateLimit ? err.message.replace('RATE_LIMIT: ', '') : 'Translation failed';
              btn.classList.remove('translating');
              if (!isRateLimit) {
                setTimeout(function () {
                  btn.innerHTML = '&#x1F310; Translate';
                  btn.title = 'Translate to ' + wallLang;
                }, 2000);
              }
            });
        });

        // Insert button after the comment body element
        bodyEl.after(btn);
      });
    }

    // Run with delay to let page render, then observe for dynamic changes
    await new Promise(function (r) { setTimeout(r, 1000); });
    injectWallTranslateButtons();

    var wallObserver = new MutationObserver(function () {
      injectWallTranslateButtons();
    });
    var wallContainer = document.querySelector('.commentscomponent')
      || document.querySelector('ul.highlighted-list')
      || document.querySelector('main')
      || document.body;
    wallObserver.observe(wallContainer, { childList: true, subtree: true });

    log.info('Comment translation initialized');
  }

  // =========================================
  //   Hydration-aware Startup
  // =========================================
  // RAWeb uses hydrateRoot in production (SSR).
  // During hydration, the DOM is replaced/reconciled by React.
  // We must wait for hydration to complete before injecting.

  function waitForHydration(timeout) {
    return new Promise(function (resolve) {
      var el = document.getElementById("app");
      // No app element = not an Inertia page, run immediately
      if (!el) return resolve();

      // If the app already has React's internal fiber key, it's hydrated
      var hasReactFiber = Object.keys(el).some(function (k) {
        return k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$");
      });
      if (hasReactFiber) return resolve();

      // Otherwise, observe for React to attach
      var observer = new MutationObserver(function () {
        var hydrated = Object.keys(el).some(function (k) {
          return k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$");
        });
        if (hydrated) {
          observer.disconnect();
          resolve();
        }
      });
      observer.observe(el, { childList: true, subtree: true, attributes: true });

      setTimeout(function () {
        observer.disconnect();
        resolve(); // proceed anyway after timeout
      }, timeout || 5000);
    });
  }

  // =========================================
  //     SPA Navigation Support
  // =========================================
  var _lastInitUrl = null;
  var _initTimer = null;

  function runAll() {
    var url = location.pathname + location.search;
    if (_lastInitUrl === url) {
      console.log('[RA Toolkit] ⏩ Skipping duplicate init for: ' + url);
      return;
    }
    console.log('[RA Toolkit] 🚀 runAll() → ' + url);
    _lastInitUrl = url;
    init().catch(function (err) { console.error('[RA Toolkit] ❌ init() threw:', err); });
    initAchievementNavLinks();
    initUserPagination().catch(function (err) { console.error('[RA Toolkit] ❌ initUserPagination() threw:', err); });
    initGameAwardsBeaten().catch(function (err) { console.error('[RA Toolkit] ❌ initGameAwardsBeaten() threw:', err); });
    initGamesMostMastered().catch(function (err) { console.error('[RA Toolkit] ❌ initGamesMostMastered() threw:', err); });
    initWallLinkify();
    initWallTranslation();
  }

  function scheduleInit(delay) {
    if (_initTimer) clearTimeout(_initTimer);
    _initTimer = setTimeout(function () {
      _initTimer = null;
      runAll();
    }, delay);
  }

  // Run on initial page load (after hydration)
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", function () {
      console.log('[RA Toolkit] 🟡 DOMContentLoaded — waiting for hydration...');
      waitForHydration(5000).then(function () { scheduleInit(0); });
    });
  } else {
    console.log('[RA Toolkit] 🟢 Document already ready — waiting for hydration...');
    waitForHydration(5000).then(function () { scheduleInit(0); });
  }

  // Re-run on Inertia SPA navigations
  document.addEventListener("inertia:navigate", function () {
    _lastInitUrl = null; // allow re-init on actual navigation
    scheduleInit(300);
  });
})();