MWI Nex Leaderboard

Overhauls the leaderboard UI using data from li-mwi-leaderboard.ngrok.io

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MWI Nex Leaderboard
// @namespace    https://milkywayidle.com/
// @version      2.0
// @description  Overhauls the leaderboard UI using data from li-mwi-leaderboard.ngrok.io
// @match        https://*.milkywayidle.com/*
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// ==/UserScript==

(function () {
  "use strict";

  // --- Configuration ---
  const SORT_LIST_WIDTH = "140px";
  const COLLAPSED_STATE_KEY = "mwi_leaderboard_sort_collapsed";

  // --- Global State ---
  let leaderboardData = null;
  let globalPlayerRanks = {};
  let currentSortBy = "Total Level";
  let isSortListCollapsed = GM_getValue(COLLAPSED_STATE_KEY, false);
  let isProcessing = false;

  const style = document.createElement("style");
style.textContent = `
       /* Main Wrapper */
       .mwi-wrapper { display: flex; height: 1000px; border: 1px solid #333; background: rgba(5, 5, 10, 0.85); color: #f0f0f0; font-family: 'Segoe UI', sans-serif; overflow: hidden; width: 98%; max-width: 1600px; margin: 5px auto; }
       /* Left Panel */
       .mwi-left { width: 45%; min-width: 30px; border-right: 1px solid #222; display: flex; flex-direction: row; overflow: hidden; transition: width 0.3s ease; }
       /* Sort Options List */
       .mwi-sort-options-list { width: ${SORT_LIST_WIDTH}; height: 100%; display: flex; flex-direction: column; background: rgba(10, 10, 20, 0.4); flex-shrink: 0; transition: width 0.3s ease, padding 0.3s ease, border 0.3s ease; overflow: hidden; }
       .mwi-sort-options-scroll { flex-grow: 1; overflow-y: auto; overflow-x: hidden; padding-bottom: 5px; }
       .mwi-sort-option { padding: 7px 10px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px; color: #bbb; border-bottom: 1px solid #222; transition: background-color 0.2s, color 0.2s; }
       .mwi-sort-option:hover { background-color: rgba(255, 255, 255, 0.08); color: #fff; }
       .mwi-sort-option.active { background-color: rgba(60, 120, 180, 0.3); color: #7dd3fc; font-weight: bold; }
       /* Toggle Button */
       .mwi-sort-toggle-btn { display: flex; align-items: center; justify-content: center; width: 20px; height: 100%; background-color: #18181f; border-right: 1px solid #2b2b33; color: #aaa; cursor: pointer; flex-shrink: 0; transition: background-color 0.2s, color 0.2s; font-size: 16px; font-weight: bold; line-height: 1; }
       .mwi-sort-toggle-btn:hover { background-color: #2a2a33; color: #eee; }
       /* Player List Area */
       .mwi-player-area { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 150px; }
       .mwi-player-list-container { flex-grow: 1; overflow-y: auto; padding-right: 5px; }
       /* Player List Header */
       .mwi-list-header { display: flex; justify-content: space-between; padding: 5px 4px; border-bottom: 1px solid #555; margin: 0 4px 4px 4px; font-weight: bold; color: #ddd; font-size: 11px; text-transform: uppercase; flex-shrink: 0; }
       .mwi-list-header > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
       /* Player List Columns */
       .mwi-header-rank, .mwi-player-rank { flex-basis: 15%; color: #facc15; font-weight: bold; text-align: left; }
       .mwi-header-name, .mwi-player-name { flex-basis: 55%; padding: 0 5px; color: #f0f0f0; text-align: center; }
       .mwi-header-value, .mwi-player-value { flex-basis: 30%; text-align: center; color: #aaa; }
       .mwi-header-value { color: #ddd; } .mwi-header-rank { color: #ddd; } .mwi-header-name { color: #ddd; }
       /* Player Row */
       .mwi-player-row { cursor: pointer; padding: 7px 4px; margin: 0 4px; border-bottom: 1px solid #222; transition: background 0.2s; display: flex; justify-content: space-between; align-items: center; font-size: 13px; }
       .mwi-player-row:hover { background: rgba(255, 255, 255, 0.05); }
       .mwi-player-row.selected { background: rgba(80, 100, 120, 0.2); }
       .mwi-player-row > span { flex-shrink: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: inline-block; vertical-align: middle; }
       .mwi-player-name { line-height: normal; }

       /* --- Right Panel --- */
       .mwi-right { width: 55%; padding: 0; background: rgba(10, 10, 20, 0.6); display: flex; flex-direction: column; overflow-y: hidden; }
       .mwi-right-content {
           flex-grow: 1;
           overflow-y: auto;
           padding: 5px;
           display: flex;
           flex-direction: column;
        }
       .mwi-right-content > em { margin: auto; font-size: 14px; color: #aaa; }
       .mwi-header { /* Player name header */
           font-size: 20px;
           font-weight: bold;
           color: #ffe27a;
           margin-bottom: 7px;
           flex-shrink: 0;
           display: inline-block;
           line-height: normal;
        }
       .mwi-meta { /* Stats container */
           font-size: 13px;
           line-height: 1.4; /* <<< Increased line height */
           color: #bbb;
           margin-bottom: 8px; /* <<< Increased margin */
           flex-shrink: 0;
        }
       .mwi-meta b { color: #ddd; }
       .mwi-meta-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px 20px; font-size: 13px; margin-top: 6px; align-items: start; }
       .mwi-meta-grid > div { white-space: nowrap; }
       .mwi-ranks-container {
           flex-shrink: 0;
           margin-top: 15px; /* <<< Increased margin */
        }

       /* Right Panel Ranks Table Styling */
       .mwi-rank-header-row {
           display: flex; width: 100%; align-items: center;
           padding: 6px 4px; /* <<< Increased padding */
           border-bottom: 1px solid #666; font-weight: bold; color: #ddd;
           font-size: 11px; text-transform: uppercase;
           margin-bottom: 4px; /* <<< Increased margin */
       }
       .mwi-rank-row {
           display: flex; width: 100%; align-items: center;
           padding: 5px 4px; /* <<< Increased padding */
           border-bottom: 1px solid #2a2a33; font-size: 13px;
           line-height: 1.4; /* <<< Increased line height */
       }
       .mwi-rank-row:last-child { border-bottom: none; }
       /* Ranks Table Columns */
       .mwi-rh-skill, .mwi-rd-skill { flex-basis: 38%; text-align: left; padding-right: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
       .mwi-rh-level, .mwi-rd-level { flex-basis: 15%; text-align: center; }
       .mwi-rh-xphr, .mwi-rd-xphr { flex-basis: 22%; text-align: center; }
       .mwi-rh-rank, .mwi-rd-rank { flex-basis: 25%; text-align: right; padding-left: 10px; }
       /* Ranks Table Text Styles */
       .mwi-rh-skill, .mwi-rh-level, .mwi-rh-xphr, .mwi-rh-rank { color: #ddd; }
       .mwi-rd-skill { color: #ccc; } .mwi-rd-level { color: #bbb; } .mwi-rd-xphr { color: #38bdf8; font-size: 11px; } .mwi-rd-rank { color: #facc15; font-weight: bold; }

       /* Collapsed State */
       .mwi-left.collapsed .mwi-sort-options-list { width: 0; border-right: none; padding: 0; }
       .mwi-left.collapsed .mwi-sort-toggle-btn { border-right: 1px solid #333; }

       /* Media Query */
       @media (min-width: 1900px) {
           .mwi-left { width: 40%; } .mwi-right { width: 60%; }
           .mwi-header-rank, .mwi-player-rank { flex-basis: 12%; }
           .mwi-header-name, .mwi-player-name { flex-basis: 63%; text-align: center; }
           .mwi-header-value, .mwi-player-value { flex-basis: 25%; }
       }
     `;
  document.head.appendChild(style);

  // --- Helper Functions ---
  function getSkillData(player, skillKey) { if (!player || !Array.isArray(player.skills)) { return { level: (skillKey === 'total_level' && player?.totalLevel) ? player.totalLevel : 0, exp: 0 }; } const skill = player.skills.find( (s) => s.skill.toLowerCase() === skillKey.toLowerCase() ); const level = skill?.level ?? ((skillKey === 'total_level' && player?.totalLevel) ? player.totalLevel : 0); const exp = skill?.exp ?? 0; return { level: (typeof level === 'number' && !isNaN(level)) ? level : 0, exp: (typeof exp === 'number' && !isNaN(exp)) ? exp : 0 }; }
  function formatNumber(n) { if (n === null || n === undefined || isNaN(n)) { return "0"; } const num = parseFloat(n); if (num === 0) return "0"; if (num >= 1e9) return (num / 1e9).toFixed(2) + "B"; if (num >= 1e6) return (num / 1e6).toFixed(2) + "M"; if (num >= 10000) return (num / 1e3).toFixed(1) + "K"; if (num >= 1000) return (num / 1e3).toFixed(0) + "K"; return String(Math.floor(num)); }

  // --- Rank Calculation Function ---
  function calculateAllPlayerRanks(players) { const calculatedRanks = {}; if (!players || players.length === 0) return calculatedRanks; players.forEach(p => { if(p?.name) calculatedRanks[p.name] = {}; }); const categories = [ "Total Level", "XP", "Combat Level", "House Level", "Enhancing", "Magic", "Crafting", "Milking", "Stamina", "Cooking", "Tailoring", "Brewing", "Cheesesmithing", "Intelligence", "Power", "Ranged", "Attack", "Defense", "Foraging", "Alchemy", "Woodcutting" ]; for (const category of categories) { let key = category.toLowerCase().replace(/ /g, "_"); const sortable = players.map(p => { if (!p || !p.name) return null; let sortValue = 0; if (category === "XP") { sortValue = p.totalXP || 0; } else if (category === "Combat Level") { sortValue = p.combatLevel || 0; } else if (category === "House Level") { sortValue = p.totalHouseLevel || 0; } else { sortValue = getSkillData(p, key).exp; } sortValue = (typeof sortValue === 'number' && !isNaN(sortValue)) ? sortValue : 0; return { name: p.name, value: sortValue }; }).filter(p => p !== null); sortable.sort((a, b) => b.value - a.value); let denseRank = 0; let lastValue = -Infinity; for (let i = 0; i < sortable.length; i++) { if (sortable[i].value !== lastValue) { denseRank++; lastValue = sortable[i].value; } if (calculatedRanks[sortable[i].name]) { calculatedRanks[sortable[i].name][key] = denseRank; } } } return calculatedRanks; }

  // --- Data Fetching ---
  async function fetchLeaderboardDataAndCalculateRanks() { if (isProcessing) { return; } isProcessing = true; try { const res = await fetch("https://li-mwi-leaderboard.ngrok.io/api/leaderboard"); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } leaderboardData = await res.json(); if (!Array.isArray(leaderboardData)) { leaderboardData = []; } globalPlayerRanks = calculateAllPlayerRanks(leaderboardData); } catch (err) { console.error("[MWI] Failed to fetch or process leaderboard data:", err); leaderboardData = null; globalPlayerRanks = {}; } finally { isProcessing = false; } }

  // --- UI Injection and Rendering ---
  function injectCustomLeaderboard(container) { if (!leaderboardData) { container.innerHTML = "Error: Leaderboard data not available."; return; } container.innerHTML = ""; const wrapper = document.createElement("div"); wrapper.className = "mwi-wrapper"; const left = document.createElement("div"); left.className = "mwi-left"; if (isSortListCollapsed) { left.classList.add("collapsed"); } const sortOptionsList = document.createElement("div"); sortOptionsList.className = "mwi-sort-options-list"; const sortOptionsScroll = document.createElement("div"); sortOptionsScroll.className = "mwi-sort-options-scroll"; const sortOptions = [ "Total Level", "XP", "Combat Level", "House Level", "Enhancing", "Magic", "Crafting", "Milking", "Stamina", "Cooking", "Tailoring", "Brewing", "Cheesesmithing", "Intelligence", "Power", "Ranged", "Attack", "Defense", "Foraging", "Alchemy", "Woodcutting" ]; sortOptions.forEach((opt) => { const optionDiv = document.createElement("div"); optionDiv.className = "mwi-sort-option"; optionDiv.textContent = opt; optionDiv.dataset.sortBy = opt; if (opt === currentSortBy) { optionDiv.classList.add("active"); } sortOptionsScroll.appendChild(optionDiv); }); sortOptionsList.appendChild(sortOptionsScroll); const toggleBtn = document.createElement("div"); toggleBtn.className = "mwi-sort-toggle-btn"; toggleBtn.textContent = isSortListCollapsed ? "»" : "«"; toggleBtn.title = isSortListCollapsed ? "Expand Sort List" : "Collapse Sort List"; const playerArea = document.createElement("div"); playerArea.className = "mwi-player-area"; const listHeader = document.createElement("div"); listHeader.className = "mwi-list-header"; listHeader.innerHTML = `<span class="mwi-header-rank">RANK</span><span class="mwi-header-name">NAME</span><span class="mwi-header-value">TYPE</span>`; const headerValueSpan = listHeader.querySelector(".mwi-header-value"); const playerListContainer = document.createElement("div"); playerListContainer.className = "mwi-player-list-container"; playerArea.appendChild(listHeader); playerArea.appendChild(playerListContainer); left.appendChild(sortOptionsList); left.appendChild(toggleBtn); left.appendChild(playerArea); const right = document.createElement("div"); right.className = "mwi-right"; const rightContent = document.createElement("div"); rightContent.className = "mwi-right-content"; rightContent.innerHTML = "<em>Select a player to view details</em>"; right.appendChild(rightContent);
    function updateListHeader(sortByValue) { if (headerValueSpan) { headerValueSpan.textContent = sortByValue; headerValueSpan.title = `Sorted by: ${sortByValue}`; } }
    function renderPlayers(sortBy) { updateListHeader(sortBy); if (!leaderboardData) { playerListContainer.innerHTML = "<div style='padding:10px; color:#aaa;'>No leaderboard data available.</div>"; return; } if (leaderboardData.length === 0) { playerListContainer.innerHTML = "<div style='padding:10px; color:#aaa;'>Leaderboard data is empty.</div>"; return; } playerListContainer.innerHTML = ""; const sorted = [...leaderboardData].sort((a, b) => { try { if (!a || !b) return 0; let valA = 0; let valB = 0; let xpA = a.totalXP || 0; let xpB = b.totalXP || 0; let key = sortBy.toLowerCase().replace(/ /g, "_"); if (sortBy === "XP") { valA = xpA; valB = xpB; } else if (sortBy === "Combat Level") { valA = a.combatLevel || 0; valB = b.combatLevel || 0; } else if (sortBy === "House Level") { valA = a.totalHouseLevel || 0; valB = b.totalHouseLevel || 0; } else { const dataA = getSkillData(a, key); const dataB = getSkillData(b, key); valA = dataA.exp; valB = dataB.exp; } if (valB !== valA) { return valB - valA; } else { return xpB - xpA; } } catch (e) { console.error("Error during sort comparison:", e, a, b); return 0; } });
      sorted.forEach((player, idx) => { if (!player || !player.name) return; const p = document.createElement("div"); p.className = "mwi-player-row"; const playerName = player.name; p.innerHTML = ""; const rankSpan = document.createElement("span"); rankSpan.className = "mwi-player-rank"; rankSpan.textContent = `#${idx + 1}`; const nameSpan = document.createElement("span"); nameSpan.className = "mwi-player-name"; nameSpan.textContent = playerName; nameSpan.title = playerName; const valueSpan = document.createElement("span"); valueSpan.className = "mwi-player-value"; let displayValue = ""; let valueKey = sortBy.toLowerCase().replace(/ /g, "_"); if (sortBy === "XP") { displayValue = formatNumber(player.totalXP || 0); } else if (sortBy === "Combat Level") { displayValue = player.combatLevel || 0; } else if (sortBy === "House Level") { displayValue = player.totalHouseLevel || 0; } else { displayValue = getSkillData(player, valueKey).level; } valueSpan.textContent = String(displayValue);
        if (player.customStyle?.inlineStyles) { const styles = player.customStyle.inlineStyles; nameSpan.style.background = styles.background || ''; nameSpan.style.color = styles.color || ''; nameSpan.style.webkitTextFillColor = styles.WebkitTextFillColor || ''; nameSpan.style.webkitBackgroundClip = styles.WebkitBackgroundClip || ''; nameSpan.style.backgroundClip = styles.backgroundClip || ''; nameSpan.style.display = 'inline-block'; nameSpan.style.lineHeight = 'normal'; } else { nameSpan.style.background = ''; nameSpan.style.color = ''; nameSpan.style.webkitTextFillColor = ''; nameSpan.style.webkitBackgroundClip = ''; nameSpan.style.backgroundClip = ''; nameSpan.style.display = ''; nameSpan.style.lineHeight = ''; }
        p.appendChild(rankSpan); p.appendChild(nameSpan); p.appendChild(valueSpan);
        p.addEventListener("click", () => { const playerTotalLevel = getSkillData(player, "total_level").level; const totalXP = player.totalXP || 0; const createdAtTimestamp = player.createdAt ? new Date(player.createdAt).getTime() : Date.now(); const daysPlayed = Math.max( 0, Math.floor((Date.now() - createdAtTimestamp) / 86400000) ); rightContent.innerHTML = `<div class="mwi-header">${playerName}</div><div class="mwi-meta"><div class="mwi-meta-grid"><div><b>Guild:</b> ${player.guildName || "N/A"}</div><div><b>Level:</b> ${playerTotalLevel}</div><div><b>Recent XP/hr:</b> ${formatNumber( player.rollingXPPerHour )}</div><div><b>Lifetime XP/hr:</b> ${formatNumber( player.lifetimeXPPerHour )}</div><div><b>Total XP:</b> ${formatNumber(totalXP)}</div><div><b>Days Played:</b> ${daysPlayed}</div></div></div><div class="mwi-ranks-container">${buildRankList( player )}</div>`; const headerElement = rightContent.querySelector('.mwi-header'); if (headerElement) { if (player.customStyle?.inlineStyles) { const styles = player.customStyle.inlineStyles; headerElement.style.background = styles.background || ''; headerElement.style.color = styles.color || ''; headerElement.style.webkitTextFillColor = styles.WebkitTextFillColor || ''; headerElement.style.webkitBackgroundClip = styles.WebkitBackgroundClip || ''; headerElement.style.backgroundClip = styles.backgroundClip || ''; headerElement.style.display = 'inline-block'; headerElement.style.lineHeight = 'normal';} else { headerElement.style.background = ''; headerElement.style.color = ''; headerElement.style.webkitTextFillColor = ''; headerElement.style.webkitBackgroundClip = ''; headerElement.style.backgroundClip = ''; headerElement.style.display = ''; headerElement.style.lineHeight = ''; } } playerListContainer.querySelectorAll(".mwi-player-row.selected").forEach((row) => row.classList.remove("selected")); p.classList.add("selected"); }); playerListContainer.appendChild(p);
      });
    }
    sortOptionsList.addEventListener("click", (event) => { const targetOption = event.target.closest(".mwi-sort-option"); if (!targetOption || !targetOption.dataset.sortBy) return; const newSortBy = targetOption.dataset.sortBy; if (newSortBy === currentSortBy) return; currentSortBy = newSortBy; sortOptionsList.querySelector(".mwi-sort-option.active")?.classList.remove("active"); targetOption.classList.add("active"); renderPlayers(currentSortBy); });
    toggleBtn.addEventListener("click", () => { isSortListCollapsed = !isSortListCollapsed; left.classList.toggle("collapsed", isSortListCollapsed); toggleBtn.textContent = isSortListCollapsed ? "»" : "«"; toggleBtn.title = isSortListCollapsed ? "Expand Sort List" : "Collapse Sort List"; try { GM_setValue(COLLAPSED_STATE_KEY, isSortListCollapsed); } catch (e) { console.warn( "[MWI] Failed to save collapse state. Error:", e ); } });
    renderPlayers(currentSortBy);
    wrapper.appendChild(left); wrapper.appendChild(right); container.appendChild(wrapper);
  }

  // --- buildRankList Function (4-Column Table Format, uses calculated ranks) ---
  function buildRankList(player) { if (!player || !player.name) return ""; const categories = [ "Total Level", "XP", "Combat Level", "House Level", "Enhancing", "Magic", "Crafting", "Milking", "Stamina", "Cooking", "Tailoring", "Brewing", "Cheesesmithing", "Intelligence", "Power", "Ranged", "Attack", "Defense", "Foraging", "Alchemy", "Woodcutting" ]; const rolling = player.rollingXPPerHourBreakdown || {}; let tableHtml = ''; tableHtml += `<div class="mwi-rank-header-row"><span class="mwi-rh-skill">Skill</span><span class="mwi-rh-level">Level/Value</span><span class="mwi-rh-xphr">XP/hr</span><span class="mwi-rh-rank">Rank</span></div>`;
    for (const stat of categories) { let key = stat.toLowerCase().replace(/ /g, "_"); let displayValue = "—"; let xpHrText = '&nbsp;'; let rankValue = globalPlayerRanks[player.name]?.[key]; let rankDisplay = (rankValue !== null && rankValue !== undefined) ? `#${rankValue}` : "—"; if (stat === "XP") { displayValue = formatNumber(player.totalXP || 0); const overallXpHr = player.rollingXPPerHour; xpHrText = (overallXpHr !== null && overallXpHr !== undefined && overallXpHr > 0) ? `(${formatNumber(overallXpHr)} XP/hr)` : '&nbsp;'; } else if (stat === "Total Level") { displayValue = getSkillData(player, key).level; xpHrText = '&nbsp;'; } else if (stat === "Combat Level") { displayValue = player.combatLevel ?? "—"; xpHrText = '&nbsp;'; } else if (stat === "House Level") { displayValue = player.totalHouseLevel ?? "—"; xpHrText = '&nbsp;'; } else { displayValue = getSkillData(player, key).level; const skillXpHr = rolling[key]; xpHrText = (skillXpHr !== null && skillXpHr !== undefined && skillXpHr > 0) ? `(${formatNumber(skillXpHr)} XP/hr)` : '&nbsp;'; } displayValue = displayValue === "—" || displayValue === null || displayValue === undefined ? "—" : displayValue; tableHtml += `<div class="mwi-rank-row"><span class="mwi-rd-skill" title="${stat}">${stat}</span><span class="mwi-rd-level">${displayValue}</span><span class="mwi-rd-xphr">${xpHrText}</span><span class="mwi-rd-rank">${rankDisplay}</span></div>`; } return tableHtml;
  }

  // --- MutationObserver Logic ---
  function applyLeaderboardEnhancements(targetNode) { let leaderboardDiv = null; if (targetNode.nodeType === Node.ELEMENT_NODE) { if (targetNode.matches('[class^="LeaderboardPanel_leaderboardPanel"]')) { leaderboardDiv = targetNode; } else if (targetNode.querySelector) { leaderboardDiv = targetNode.querySelector('[class^="LeaderboardPanel_leaderboardPanel"]:not([data-custom-injected="true"])'); } } if (leaderboardDiv && !leaderboardDiv.dataset.customInjected) { leaderboardDiv.dataset.customInjected = "true"; if ((!leaderboardData || Object.keys(globalPlayerRanks).length === 0) && !isProcessing) { fetchLeaderboardDataAndCalculateRanks().then(() => { if (leaderboardData) { injectCustomLeaderboard(leaderboardDiv); } else { leaderboardDiv.innerHTML = "Failed to load or process leaderboard data."; } }); } else if (leaderboardData && Object.keys(globalPlayerRanks).length > 0) { injectCustomLeaderboard(leaderboardDiv); } else if (isProcessing) { leaderboardDiv.innerHTML = "<em>Loading leaderboard... (Try switching tabs if not loading)</em>"; } else { leaderboardDiv.innerHTML = "Error: Cannot display leaderboard."; } } }
  const observerCallback = (mutationsList, observer) => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(node => { applyLeaderboardEnhancements(node); }); } } }; const observer = new MutationObserver(observerCallback); observer.observe(document.body, { childList: true, subtree: true });

  // --- Initial Setup ---
  document.querySelectorAll('[class^="LeaderboardPanel_leaderboardPanel"]:not([data-custom-injected="true"])').forEach(panel => { applyLeaderboardEnhancements(panel); }); if (!document.querySelector('[class^="LeaderboardPanel_leaderboardPanel"]') && !isProcessing) { fetchLeaderboardDataAndCalculateRanks(); } console.log('[MWI] Leaderboard Enhancer initialized and observing.');

})();