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 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         RA Toolkit
// @namespace    https://github.com/WelingtonMonteiro
// @version      2.8.0
// @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.0 — ' + 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.0";

  var CHANGELOG = [
    { 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 + '-download';
        }

        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);
  }

  // =========================================
  //   User Wall — Linkify URLs + YouTube Embed
  // =========================================
  function initWallLinkify() {
    if (!/^\/user\/[^\/]+(\/(comments)?)?$/i.test(location.pathname)) 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('Wall linkify initialized');
  }

  // =========================================
  //   User Wall Comment Translation
  // =========================================
  async function initWallTranslation() {
    // Run on user profile pages and user comments pages
    if (!/^\/user\/[^\/]+(\/(comments)?)?$/i.test(location.pathname)) 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('Wall 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);
  });
})();