// ==UserScript==
// @name MZ - Unlucky
// @namespace douglaskampl
// @version 2.727
// @description Finds unlucky teams
// @author Douglas
// @match https://www.managerzone.com/?p=league&type*
// @grant GM_addStyle
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const DEBUG = false;
const BATCH_SIZE = 5;
const MAX_GK_LOOKUP_ATTEMPTS = 10;
GM_addStyle(`@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); @keyframes textGlitch { 0% { transform: translate(0); text-shadow: 0 0 8px rgba(255, 106, 193, 0.8); } 25% { text-shadow: -1px 1px 8px rgba(22, 242, 242, 0.8), 1px -1px 8px rgba(255, 106, 193, 0.8); } 50% { text-shadow: 1px -1px 8px rgba(22, 242, 242, 0.8), -1px 1px 8px rgba(255, 106, 193, 0.8); } 75% { text-shadow: -1px 0 8px rgba(22, 242, 242, 0.8), 1px 0 8px rgba(255, 106, 193, 0.8); } 100% { transform: translate(0); text-shadow: 0 0 8px rgba(255, 106, 193, 0.8); } } .pulse-dot { display: inline-block; width: 8px; height: 8px; background-color: #ff6ac1; border-radius: 50%; margin-right: 10px; animation: pulse 1.5s infinite ease-in-out; } @keyframes pulse { 0% { transform: scale(0.8); opacity: 0.5; } 50% { transform: scale(1.2); opacity: 1; } 100% { transform: scale(0.8); opacity: 0.5; } } @keyframes modalFadeIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } } @keyframes glow { 0% { box-shadow: 0 0 5px rgba(255, 0, 255, 0.5); } 50% { box-shadow: 0 0 20px rgba(0, 255, 255, 0.8); } 100% { box-shadow: 0 0 5px rgba(255, 0, 255, 0.5); } } @keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } #unluckyModal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px); display: flex; justify-content: center; align-items: center; z-index: 9999; opacity: 0; animation: modalFadeIn 0.3s ease-out forwards; font-size: 16px; font-family: 'Inter', sans-serif; } #unluckyModalContent { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); background-size: 200% 200%; animation: gradientShift 15s ease infinite; padding: 25px; border-radius: 10px; width: 700px; max-width: 90vw; max-height: 85vh; overflow-y: auto; box-shadow: 0 0 30px rgba(138, 43, 226, 0.6); color: #fff; border: 1px solid rgba(255, 255, 255, 0.1); transform: translateY(20px); transition: transform 0.3s ease-out; box-sizing: border-box; } #unluckyModal:hover #unluckyModalContent { transform: translateY(0); } #unluckyModalHeader { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; border-bottom: 1px solid rgba(255, 105, 180, 0.3); padding-bottom: 10px; } #unluckyModalTitle { font-size: 24px; font-weight: bold; color: #ff6ac1; text-shadow: 0 0 10px rgba(255, 105, 180, 0.7); font-family: 'Orbitron', sans-serif; letter-spacing: 2px; text-transform: uppercase; } #unluckyModalClose { cursor: pointer; font-size: 24px; color: #16f2f2; transition: all 0.2s; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; } #unluckyModalClose:hover { color: #ff6ac1; transform: rotate(90deg); background-color: rgba(255, 255, 255, 0.1); } .unluckyOption { margin: 15px 0; padding: 12px 20px; width: 100%; box-sizing: border-box; text-align: center; background: linear-gradient(to right, #614385, #516395); border: none; border-radius: 5px; cursor: pointer; color: white; font-weight: 500; font-size: 17px; transition: all 0.3s ease; position: relative; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); max-width: 100%; font-family: 'Inter', sans-serif; } .unluckyOption:before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: 0.5s; } .unluckyOption:hover { transform: translateY(-3px); box-shadow: 0 7px 14px rgba(0, 0, 0, 0.4); animation: glow 1.5s infinite; } .unluckyOption:hover:before { left: 100%; } .unluckyOption:active { transform: translateY(1px); } #unluckyResults { margin-top: 20px; max-height: 550px; overflow-y: auto; padding: 15px; border: 1px solid rgba(85, 213, 219, 0.3); display: none; background-color: rgba(0, 0, 0, 0.3); border-radius: 5px; color: #f0f0f0; font-family: 'Inter', sans-serif; transition: all 0.3s ease; } #unluckyResults p { margin: 5px 0; line-height: 1.5; } #unluckyResults::-webkit-scrollbar { width: 8px; } #unluckyResults::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.2); border-radius: 10px; } #unluckyResults::-webkit-scrollbar-thumb { background: linear-gradient(180deg, #ff6ac1, #7a4feb); border-radius: 10px; } #leftmenu_unlucky a { transition: all 0.3s ease; display: inline-block; font-family: 'Inter', sans-serif; } #leftmenu_unlucky a:hover { color: #ff6ac1 !important; text-shadow: 0 0 5px rgba(255, 105, 180, 0.7); transform: translateX(3px); } .team-stats { margin-bottom: 12px; padding: 10px; border-radius: 4px; border-left: 3px solid #ff6ac1; background-color: rgba(255, 255, 255, 0.05); font-size: 15px; font-family: 'Inter', sans-serif; } .stat-card { margin-bottom: 15px; padding: 15px; border-radius: 8px; background-color: rgba(0, 0, 0, 0.3); box-shadow: 0 0 10px rgba(138, 43, 226, 0.3); font-family: 'Inter', sans-serif; } .stat-card-title { margin-bottom: 10px; padding-bottom: 8px; font-size: 19px; font-weight: bold; color: #16f2f2; border-bottom: 1px solid rgba(255, 106, 193, 0.5); } .stat-item { margin: 10px 0; padding: 10px; border-radius: 4px; display: flex; align-items: center; justify-content: space-between; background-color: rgba(255, 255, 255, 0.05); font-size: 15px; flex-wrap: wrap; } .stat-item:hover { background-color: rgba(255, 255, 255, 0.1); } .stat-value { font-weight: bold; color: #ff6ac1; margin-left: auto; padding-left: 10px; flex-shrink: 0; } .stat-rank { display: inline-block; width: 24px; height: 24px; margin-right: 10px; background: linear-gradient(to right, #614385, #516395); border-radius: 50%; text-align: center; line-height: 24px; font-size: 13px; } .back-button { margin-top: 15px; padding: 8px 15px; background: linear-gradient(to right, #516395, #614385); border: none; border-radius: 4px; color: white; cursor: pointer; transition: all 0.3s ease; font-size: 15px; font-family: 'Inter', sans-serif; } .back-button:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } .pagination { display: flex; justify-content: center; margin-top: 15px; } .pagination-button { padding: 6px 12px; margin: 0 5px; border: none; border-radius: 4px; background: linear-gradient(to right, #516395, #614385); color: white; cursor: pointer; transition: all 0.3s ease; font-family: 'Inter', sans-serif; } .pagination-button:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } .pagination-button.active { background: linear-gradient(to right, #ff6ac1, #ff6ac1); } .league-selector { padding: 10px; margin-bottom: 10px; background-color: rgba(0, 0, 0, 0.2); border-radius: 5px; } .league-selector-title { margin-bottom: 10px; color: #16f2f2; font-size: 17px; } .league-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; } .league-item { padding: 5px 8px; text-align: center; background-color: rgba(255, 255, 255, 0.1); border-radius: 4px; cursor: pointer; transition: all 0.2s; font-size: 15px; display: flex; align-items: center; justify-content: center; } .league-item:hover { background-color: rgba(255, 106, 193, 0.3); } .league-item.active { background-color: rgba(255, 106, 193, 0.5); } .tab-container { display: flex; margin-bottom: 15px; } .tab { padding: 10px 20px; background: rgba(255, 255, 255, 0.1); border: none; color: white; cursor: pointer; transition: all 0.3s ease; font-size: 16px; font-family: 'Inter', sans-serif; } .tab:first-child { border-radius: 5px 0 0 5px; } .tab:last-child { border-radius: 0 5px 5px 0; } .tab.active { background: linear-gradient(to right, #ff6ac1, #7a4feb); } .tab-content { display: none; } .tab-content.active { display: block; } .toggle-matches { cursor: pointer; color: #16f2f2; text-decoration: underline; margin-left: 5px; font-size: 0.95em; } .toggle-matches:hover { color: #ff6ac1; } .match-list { display: none; margin-top: 10px; padding: 8px; background-color: rgba(0, 0, 0, 0.2); border-radius: 5px; } .match-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; margin-bottom: 5px; border-radius: 3px; background-color: rgba(255, 255, 255, 0.05); transition: all 0.2s; font-size: 14px; } .match-item:hover { background-color: rgba(255, 255, 255, 0.1); } .match-link { color: #fff; text-decoration: none; flex-grow: 1; } .match-link:hover { color: #ff6ac1; } .match-result { font-weight: bold; margin-left: 10px; } .match-result.win { color: #2ecc71; } .match-result.draw { color: #f39c12; } .match-result.loss { color: #e74c3c; } .match-stats { font-size: 0.9em; color: #bbb; } .info-icon { position: fixed; bottom: 20px; right: 20px; width: 40px; height: 40px; background-color: rgba(22, 242, 242, 0.8); color: #1a1a2e; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 24px; cursor: pointer; box-shadow: 0 0 15px rgba(22, 242, 242, 0.5); z-index: 10000; transition: all 0.3s ease; } .info-icon:hover { background-color: rgba(255, 106, 193, 0.8); box-shadow: 0 0 15px rgba(255, 106, 193, 0.5); transform: scale(1.1); } .info-tooltip { position: fixed; bottom: 70px; right: 20px; width: 350px; max-height: 70vh; overflow-y: auto; padding: 15px; background-color: rgba(26, 26, 46, 0.95); border: 1px solid rgba(22, 242, 242, 0.5); border-radius: 10px; color: white; font-size: 14px; line-height: 1.5; z-index: 10000; display: none; box-shadow: 0 0 20px rgba(22, 242, 242, 0.3); max-width: 80vw; font-family: 'Inter', sans-serif; } .info-tooltip h3 { color: #ff6ac1; margin-top: 0; margin-bottom: 10px; border-bottom: 1px solid rgba(255, 106, 193, 0.3); padding-bottom: 5px; font-family: 'Orbitron', sans-serif; } .info-tooltip p { margin: 10px 0; } .info-tooltip .formula { background-color: rgba(0, 0, 0, 0.2); padding: 8px; border-radius: 5px; margin: 10px 0; border-left: 3px solid #16f2f2; font-family: 'Courier New', monospace; } .info-tooltip .formula-explanation { margin-left: 15px; margin-bottom: 15px; color: #ddd; } .league-link { color: #16f2f2; text-decoration: none; transition: all 0.2s; display: inline-flex; align-items: center; } .league-link:hover { color: #ff6ac1; text-decoration: none; } .league-link:hover .league-label { background-color: rgba(255, 106, 193, 0.4); border-color: rgba(255, 106, 193, 0.7); } .loading-stage { margin-left: 10px; color: #ff6ac1; font-style: italic; font-size: 0.9em; } .loading-progress { margin-left: 10px; color: #16f2f2; } .league-range-input { display: flex; margin: 15px 0; gap: 10px; align-items: center; } .league-range-input input { flex: 1; padding: 8px; border-radius: 4px; border: none; background-color: rgba(255, 255, 255, 0.1); color: white; outline: none; } .league-input { width: 100%; padding: 10px; border-radius: 4px; margin-bottom: 15px; border: none; background-color: rgba(255, 255, 255, 0.1); color: white; outline: none; font-family: 'Inter', sans-serif; } .league-entry-container { margin-bottom: 20px; } .league-entry-form { display: flex; gap: 10px; margin-bottom: 10px; align-items: center; } .league-entry-form input, .league-entry-form select { flex: 1; padding: 8px 12px; border: none; border-radius: 4px; background-color: rgba(255, 255, 255, 0.1); color: lightgray; outline: none; } .league-entry-form select { cursor: pointer; } .league-entry-form .add-button { width: 36px; height: 36px; display: flex; justify-content: center; align-items: center; background: linear-gradient(to right, #614385, #516395); border: none; border-radius: 50%; color: white; font-size: 20px; cursor: pointer; transition: all 0.3s ease; flex-shrink: 0; } .league-entry-form .add-button:hover { transform: scale(1.1); box-shadow: 0 0 10px rgba(138, 43, 226, 0.5); } .league-list { max-height: 200px; overflow-y: auto; padding: 10px; background-color: rgba(0, 0, 0, 0.2); border-radius: 5px; margin-bottom: 15px; } .league-list-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; margin-bottom: 6px; background-color: rgba(255, 255, 255, 0.05); border-radius: 4px; } .league-list-item:last-child { margin-bottom: 0; } .league-list-item .remove-button { background: none; border: none; color: #ff6ac1; cursor: pointer; font-size: 16px; transition: all 0.2s; } .league-list-item .remove-button:hover { transform: scale(1.2); } .league-label { background-color: rgba(22, 242, 242, 0.15); border: 1px solid rgba(22, 242, 242, 0.4); color: #e0e0e0; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; margin-left: 6px; white-space: nowrap; transition: all 0.2s; display: inline-block; }`);
const CURRENCIES = {
"R$": 2.62589, EUR: 9.1775, USD: 7.4234, "点": 1,
SEK: 1, NOK: 1.07245, DKK: 1.23522, GBP: 13.35247,
CHF: 5.86737, RUB: 0.26313, CAD: 5.70899, AUD: 5.66999,
MZ: 1, MM: 1, PLN: 1.95278, ILS: 1.6953,
INR: 0.17, THB: 0.17079, ZAR: 1.23733, SKK: 0.24946,
BGN: 4.70738, MXN: 0.68576, ARS: 2.64445, BOB: 0.939,
UYU: 0.256963, PYG: 0.001309, ISK: 0.10433, SIT: 0.03896,
JPY: 0.06,
};
const LOG_COLORS = {
info: "#16f2f2",
warn: "#ff6ac1",
error: "#ff0000",
success: "#00ff00",
debug: "#f39c12",
gk: "#7a4feb"
};
const LEAGUE_TYPES = [
{ value: "senior", label: "Senior" },
{ value: "u23", label: "U23" },
{ value: "u23_world", label: "U23 World" },
{ value: "u21", label: "U21" },
{ value: "u21_world", label: "U21 World" },
{ value: "u18", label: "U18" },
{ value: "u18_world", label: "U18 World" }
];
const REGIONAL_LEAGUE_NAMES = {
727: "Americas", 1: "Argentina", 122: "Brazil", 848: "Central Europe",
969: "Iberia", 1090: "Mediterranean", 1211: "Northern Europe",
243: "Poland", 364: "Romania", 485: "Sweden", 606: "Turkey",
1332: "World"
};
const teamPlayersCache = {};
const teamGoalkeeperCache = {};
const xmlCache = {};
const matchCache = {};
const gkLogging = {
attempts: 0,
successes: 0,
failures: 0,
reasons: {},
teamDetails: {},
conversionDetails: {},
processedTeams: new Set(),
teamsWithGk: new Set(),
matchesUsed: new Set()
};
function log(msg, type = "info", obj = null) {
if (!DEBUG) return;
const style = `color: ${LOG_COLORS[type] || LOG_COLORS.info}; font-weight: bold;`;
if (obj !== null) {
console.log(`%c[MZ-UNLUCKY] ${msg}`, style, obj);
} else {
console.log(`%c[MZ-UNLUCKY] ${msg}`, style);
}
}
function displayGkDebugInfo() {
log("===== GOALKEEPER DEBUGGING INFORMATION =====", "gk");
log(`Total attempts: ${gkLogging.attempts}`, "gk");
log(`Successes: ${gkLogging.successes}`, "gk");
log(`Failures: ${gkLogging.failures}`, "gk");
log(`Success rate: ${((gkLogging.successes / Math.max(gkLogging.attempts, 1)) * 100).toFixed(2)}%`, "gk");
log(`Teams processed: ${gkLogging.processedTeams.size}`, "gk");
log(`Teams with goalkeeper data: ${gkLogging.teamsWithGk.size}`, "gk");
log(`Total matches used: ${gkLogging.matchesUsed.size}`, "gk");
log("Failure reasons:", "gk", gkLogging.reasons);
log("Team details:", "gk", gkLogging.teamDetails);
log("Currency conversion details:", "gk", gkLogging.conversionDetails);
const currencyCount = {};
for (const teamId in gkLogging.teamDetails) {
const details = gkLogging.teamDetails[teamId];
if (details.success && details.currency) {
currencyCount[details.currency] = (currencyCount[details.currency] || 0) + 1;
}
}
log("GKs by currency:", "gk", currencyCount);
const currencyAvg = {};
for (const teamId in gkLogging.teamDetails) {
const details = gkLogging.teamDetails[teamId];
if (details.success && details.currency) {
if (!currencyAvg[details.currency]) {
currencyAvg[details.currency] = { count: 0, total: 0, totalUsd: 0 };
}
currencyAvg[details.currency].count++;
currencyAvg[details.currency].total += details.originalValue;
currencyAvg[details.currency].totalUsd += details.valueInUsd;
}
}
for (const currency in currencyAvg) {
const data = currencyAvg[currency];
data.average = data.total / data.count;
data.averageUsd = data.totalUsd / data.count;
}
log("Average GK values by currency:", "gk", currencyAvg);
const topGks = Object.entries(gkLogging.teamDetails)
.filter(([_, details]) => details.success)
.sort((a, b) => b[1].valueInUsd - a[1].valueInUsd)
.slice(0, 5);
log("Top 5 most valuable goalkeepers:", "gk", topGks);
log("================================================", "gk");
}
function getCurrentLeagueId() {
const url = window.location.href;
const match = url.match(/sid=(\d+)/);
return match ? match[1] : null;
}
function getCurrentLeagueType() {
const url = window.location.href;
const match = url.match(/type=([^&]+)/);
return match ? match[1] : null;
}
function getDivisionDisplayName(sid, leagueType) {
const sidNum = parseInt(sid);
if (!sidNum) return `League ${sid}`;
if (leagueType && leagueType.includes('world')) {
if (sidNum === 1) return "Top Series";
let level = 0;
let levelStartSid = 1;
let leaguesInLevel = 1;
while (sidNum >= levelStartSid + leaguesInLevel) {
levelStartSid += leaguesInLevel;
level++;
leaguesInLevel *= 3;
}
if (level > 0) {
const indexInLevel = sidNum - levelStartSid + 1;
return `Div ${level}.${indexInLevel}`;
}
} else {
const regionalName = REGIONAL_LEAGUE_NAMES[sidNum];
if (regionalName) {
return regionalName;
}
}
return `League ${sid}`;
}
function makeRequest(url) {
log(`Making request to: ${url}`, "debug");
return fetch(url)
.then(response => {
if (!response.ok) {
log(`Request failed with status ${response.status}`, "error");
throw new Error(`Request failed with status ${response.status}`);
}
return response.text();
});
}
function convertToUsd(value, currency) {
gkLogging.conversionDetails[currency] = gkLogging.conversionDetails[currency] || [];
log(`Converting value from ${currency}: ${value}`, "debug");
if (currency === 'USD') {
log(`No conversion needed for USD value: ${value}`, "gk");
gkLogging.conversionDetails[currency].push({ original: value, converted: value, rate: 1, formula: 'No conversion (USD)' });
return value;
}
const conversionRate = CURRENCIES[currency] || 1;
if (!CURRENCIES[currency]) {
log(`Unknown currency: ${currency}, defaulting rate to 1`, "warn");
}
let valueInUsd;
if (currency === 'SEK') {
valueInUsd = (value / CURRENCIES.USD);
log(`Converting from SEK: ${value} SEK = ${valueInUsd} USD (${value} / ${CURRENCIES.USD})`, "gk");
} else {
const valueInSek = value * conversionRate;
valueInUsd = valueInSek / CURRENCIES.USD;
log(`Converting from ${currency}: ${value} ${currency} = ${valueInSek} SEK = ${valueInUsd} USD (${value} * ${conversionRate} / ${CURRENCIES.USD})`, "gk");
}
gkLogging.conversionDetails[currency].push({ original: value, converted: valueInUsd, rate: conversionRate, usdRate: CURRENCIES.USD, formula: `(${value} * ${conversionRate}) / ${CURRENCIES.USD} = ${valueInUsd}` });
return valueInUsd;
}
function createModal() {
const modal = document.createElement('div');
modal.id = 'unluckyModal';
modal.style.display = 'none';
const modalContent = document.createElement('div');
modalContent.id = 'unluckyModalContent';
const modalHeader = document.createElement('div');
modalHeader.id = 'unluckyModalHeader';
const modalTitle = document.createElement('div');
modalTitle.id = 'unluckyModalTitle';
modalTitle.innerHTML = '<span style="background: linear-gradient(to right, #ff6ac1, #16f2f2); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">アンラッキー 運命</span>';
const modalClose = document.createElement('div');
modalClose.id = 'unluckyModalClose';
modalClose.textContent = '×';
modalClose.addEventListener('click', closeModal);
modalHeader.appendChild(modalTitle);
modalHeader.appendChild(modalClose);
const currentOption = document.createElement('div');
currentOption.className = 'unluckyOption';
currentOption.innerHTML = '<span style="font-size:18px;margin-right:8px;">🔍</span> Current League';
currentOption.addEventListener('click', function() { findUnluckyTeams('current'); });
const specificOption = document.createElement('div');
specificOption.className = 'unluckyOption';
specificOption.innerHTML = '<span style="font-size:18px;margin-right:8px;">🎯</span> Specific League';
specificOption.addEventListener('click', function() { findUnluckyTeams('specific'); });
const allOption = document.createElement('div');
allOption.className = 'unluckyOption';
allOption.innerHTML = '<span style="font-size:18px;margin-right:8px;">🌐</span> All Leagues';
allOption.addEventListener('click', function() { findUnluckyTeams('all'); });
const resultsDiv = document.createElement('div');
resultsDiv.id = 'unluckyResults';
modalContent.appendChild(modalHeader);
modalContent.appendChild(currentOption);
modalContent.appendChild(specificOption);
modalContent.appendChild(allOption);
modalContent.appendChild(resultsDiv);
modal.appendChild(modalContent);
const infoIcon = document.createElement('div');
infoIcon.className = 'info-icon';
infoIcon.textContent = 'ℹ️';
infoIcon.title = 'Click for information about luck calculations';
const infoTooltip = document.createElement('div');
infoTooltip.className = 'info-tooltip';
infoTooltip.innerHTML = `
<h3>How Luck is Calculated</h3>
<p>The Luck Index combines multiple factors to accurately measure how lucky or unlucky a team has been based on their match performance data:</p>
<div class="formula">Luck Index = Offensive Conversion Rate - Defensive Conversion Rate - GK Value Adjustment</div>
<div class="formula-explanation">
<p><strong>Offensive Conversion Rate:</strong> (Goals Scored ÷ Shots on Target) × 100</p>
<p><strong>Defensive Conversion Rate:</strong> (Goals Conceded ÷ Shots on Target Against) × 100</p>
<p><strong>GK Value Adjustment:</strong> min(GK Value in USD ÷ 1,000,000, 5) × 0.5</p>
</div>
<p>A <strong>higher</strong> Luck Index indicates a luckier team (scoring from relatively few shots and/or conceding few goals despite facing many shots on target).</p>
<p>A <strong>lower</strong> Luck Index indicates an unlucky team (struggling to score despite creating chances and/or conceding frequently from relatively few shots).</p>
<p>The <strong>GK Value Adjustment</strong> factor accounts for goalkeeper quality. Teams with expensive goalkeepers are expected to have better defensive conversion rates, so this reduces their luck rating (as good defensive performance with a top goalkeeper is skill, not luck).</p>
<p><strong>Additional metrics tracked:</strong></p>
<ul>
<li><strong>Unlucky Matches %:</strong> (Matches where team had more shots on target but didn't win) ÷ (Total matches where team had more shots on target) × 100</li>
<li><strong>Weighted Unlucky Score:</strong> Accounts for both percentage and sample size to reduce statistical anomalies</li>
<li><strong>Lucky Wins %:</strong> (Matches won despite having fewer shots on target) ÷ (Total matches where team had fewer shots on target) × 100</li>
<li><strong>Weighted Lucky Score:</strong> Accounts for both percentage and sample size</li>
</ul>
<p><strong>Example calculation:</strong><br>
Team A has scored 15 goals from 50 shots on target (30% offensive conversion)<br>
They've conceded 10 goals from 40 shots on target against (25% defensive conversion)<br>
Their goalkeeper is worth $3M<br>
GK Value Adjustment: min(3, 5) × 0.5 = 1.5<br>
Luck Index = 30% - 25% - 1.5 = 3.5</p>
`;
infoIcon.addEventListener('click', function() {
infoTooltip.style.display = infoTooltip.style.display === 'block' ? 'none' : 'block';
});
document.addEventListener('click', function(e) {
if (e.target !== infoIcon && infoTooltip.style.display === 'block') {
infoTooltip.style.display = 'none';
}
});
modal.appendChild(infoIcon);
modal.appendChild(infoTooltip);
document.body.appendChild(modal);
log("Modal created successfully", "success");
}
function openUnluckyModal() {
document.getElementById('unluckyModal').style.display = 'flex';
document.getElementById('unluckyResults').style.display = 'none';
document.getElementById('unluckyResults').innerHTML = '';
log("Modal opened", "info");
}
function closeModal() {
document.getElementById('unluckyModal').style.display = 'none';
const resultsDiv = document.getElementById('unluckyResults');
resultsDiv.style.display = 'none';
resultsDiv.innerHTML = '';
document.querySelectorAll('.unluckyOption').forEach(option => {
option.style.display = 'block';
});
const infoTooltip = document.querySelector('.info-tooltip');
if (infoTooltip) {
infoTooltip.style.display = 'none';
}
log("Modal closed", "info");
}
function showBackButton(resultsDiv) {
const backButton = document.createElement('button');
backButton.className = 'back-button';
backButton.textContent = '← Back to Options';
backButton.addEventListener('click', function() {
resultsDiv.style.display = 'none';
resultsDiv.innerHTML = '';
document.querySelectorAll('.unluckyOption').forEach(option => {
option.style.display = 'block';
});
});
resultsDiv.appendChild(backButton);
}
function addUnluckyButton() {
const leftNav = document.querySelector('ul.leftnav');
if (leftNav) {
const li = document.createElement('li');
li.id = 'leftmenu_unlucky';
const button = document.createElement('a');
button.href = '#';
button.innerHTML = 'Unlucky <span style="color:#ff6ac1">☯</span>';
button.addEventListener('click', function(e) {
e.preventDefault();
openUnluckyModal();
});
li.appendChild(button);
leftNav.appendChild(li);
log("Added Unlucky button to left navigation", "success");
} else {
const expanderMenuList = document.querySelector('#nmenu');
if (expanderMenuList) {
const dt = document.createElement('dt');
dt.className = 'news';
dt.innerHTML = `
<table>
<tbody>
<tr>
<td align="right"><i class="fa" aria-hidden="true"></i></td>
<td><a href="#" style="text-decoration:none"><b>Unlucky <span style="color:#ff6ac1">☯</span></b></a></td>
</tr>
</tbody>
</table>
`;
dt.querySelector('a').addEventListener('click', function(e) {
e.preventDefault();
openUnluckyModal();
});
expanderMenuList.appendChild(dt);
log("Added Unlucky button to expander menu", "success");
} else {
log("Could not find any navigation to add button", "error");
}
}
}
async function fetchTeamPlayers(teamId) {
log(`Fetching team players for team ID: ${teamId}`, "info");
if (teamPlayersCache[teamId]) {
log(`Cache hit for team ${teamId}`, "success");
return teamPlayersCache[teamId];
}
const cacheKey = `team_${teamId}`;
if (xmlCache[cacheKey]) {
log(`XML cache hit for team ${teamId}`, "success");
return xmlCache[cacheKey];
}
try {
const url = `https://www.managerzone.com/xml/team_playerlist.php?sport_id=1&team_id=${teamId}`;
log(`API request to: ${url}`, "debug");
const response = await fetch(url);
if (!response.ok) {
log(`API response not OK: ${response.status} ${response.statusText}`, "error");
return null;
}
const text = await response.text();
log(`Received ${text.length} bytes of XML data`, "debug");
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(text, "text/xml");
const parserError = xmlDoc.querySelector("parsererror");
if (parserError) {
log(`XML parsing error: ${parserError.textContent}`, "error");
return null;
}
const teamPlayersElement = xmlDoc.querySelector('TeamPlayers');
if (!teamPlayersElement) {
log(`No TeamPlayers element found in XML for team ${teamId}`, "error");
return null;
}
const teamCurrency = teamPlayersElement.getAttribute('teamCurrency') || 'USD';
log(`Team ${teamId} currency: ${teamCurrency}`, "info");
const players = [];
let players18Count = 0;
const playerElements = xmlDoc.querySelectorAll('Player');
log(`Found ${playerElements.length} players for team ${teamId}`, "info");
playerElements.forEach(player => {
const id = player.getAttribute('id');
const name = player.getAttribute('name');
const value = parseInt(player.getAttribute('value')) || 0;
const junior = player.getAttribute('junior') === '1';
const shirtNo = player.getAttribute('shirtNo');
const age = parseInt(player.getAttribute('age')) || 0;
if (age === 18) {
players18Count++;
}
players.push({ id, name, value, junior, shirtNo, age });
if (shirtNo === "1") {
log(`Potential GK detected in XML: ${name} (#${shirtNo})`, "gk", { id, name, value, teamCurrency });
}
});
log(`Found ${players18Count} players aged 18 for team ${teamId}`, "info");
const result = { teamCurrency, players, players18Count };
teamPlayersCache[teamId] = result;
xmlCache[cacheKey] = result;
log(`Successfully processed team data for ${teamId}`, "success");
return result;
} catch (error) {
log(`Error fetching team players for ${teamId}: ${error.message}`, "error");
return null;
}
}
async function processMatchComplete(matchId, teamIdMap = null) {
log(`Processing match ${matchId} for complete data`, "info");
if (matchCache[matchId]) {
log(`Cache hit for match ${matchId}`, "success");
return matchCache[matchId];
}
try {
const matchUrl = `https://www.managerzone.com/?p=match&sub=result&mid=${matchId}`;
log(`Fetching match page: ${matchUrl}`, "debug");
const response = await fetch(matchUrl);
if (!response.ok) {
log(`Match page fetch failed: ${response.status} ${response.statusText}`, "error");
return null;
}
const html = await response.text();
log(`Received ${html.length} bytes of match HTML`, "debug");
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const matchInfoWrapper = doc.getElementById('match-info-wrapper');
if (!matchInfoWrapper) {
log(`No match info wrapper found for match ${matchId}`, "error");
return null;
}
const teamElements = matchInfoWrapper.querySelectorAll('a[href*="/?p=team&tid="]');
if (teamElements.length < 2) {
log(`Not enough team links found for match ${matchId}, found ${teamElements.length}`, "error");
return null;
}
const homeTeamHref = teamElements[0].getAttribute('href') || '';
const awayTeamHref = teamElements[1].getAttribute('href') || '';
const homeTeamMatch = homeTeamHref.match(/tid=(\d+)/);
const awayTeamMatch = awayTeamHref.match(/tid=(\d+)/);
if (!homeTeamMatch || !awayTeamMatch) {
log(`Failed to extract team IDs from match ${matchId}`, "error");
return null;
}
const homeTeam = { name: teamElements[0].textContent.trim(), id: homeTeamMatch[1] };
const awayTeam = { name: teamElements[1].textContent.trim(), id: awayTeamMatch[1] };
log(`Match: ${homeTeam.name} (${homeTeam.id}) vs ${awayTeam.name} (${awayTeam.id})`, "debug");
const teamTables = doc.querySelectorAll('.team-table.block');
log(`Found ${teamTables.length} team tables in match page`, "debug");
if (!teamTables || teamTables.length < 2) {
log(`Insufficient team tables (${teamTables?.length || 0}) found for match ${matchId}`, "error");
return null;
}
const teamsInMatch = [];
teamTables.forEach((table, index) => {
const teamLink = table.querySelector('a[href*="tid="]');
if (teamLink) {
const href = teamLink.getAttribute('href');
const tidMatch = href.match(/tid=(\d+)/);
if (tidMatch) {
const teamId = tidMatch[1];
const teamName = teamLink.textContent.trim();
teamsInMatch.push({ id: teamId, name: teamName, table: table, isHome: index === 0 });
}
}
});
const tacticBoard = matchInfoWrapper.querySelector('#tactic-board');
let matchFactsTable = null;
if (tacticBoard && tacticBoard.nextElementSibling) {
matchFactsTable = tacticBoard.nextElementSibling.querySelector('table.hitlist.statsLite.marker');
}
if (!matchFactsTable) {
log(`No match facts table found for match ${matchId}`, "error");
return null;
}
let homeGoals = 0, awayGoals = 0;
let homeSoT = 0, awaySoT = 0;
let homePossession = 0, awayPossession = 0;
const statsRows = matchFactsTable.querySelectorAll('tbody tr');
if (statsRows.length > 0) {
const homeTd = statsRows[0].querySelector('td:nth-child(2)');
const awayTd = statsRows[0].querySelector('td:nth-child(3)');
if (homeTd && awayTd) {
homeGoals = parseInt(homeTd.textContent.trim()) || 0;
awayGoals = parseInt(awayTd.textContent.trim()) || 0;
}
}
if (statsRows.length > 7) {
const homeTd = statsRows[7].querySelector('td:nth-child(2)');
const awayTd = statsRows[7].querySelector('td:nth-child(3)');
if (homeTd && awayTd) {
homeSoT = parseInt(homeTd.textContent.trim()) || 0;
awaySoT = parseInt(awayTd.textContent.trim()) || 0;
}
}
if (statsRows.length > 8) {
const homeTd = statsRows[8].querySelector('td:nth-child(2)');
const awayTd = statsRows[8].querySelector('td:nth-child(3)');
if (homeTd && awayTd) {
const homeMatch = homeTd.textContent.trim().match(/(\d+)%/);
const awayMatch = awayTd.textContent.trim().match(/(\d+)%/);
homePossession = homeMatch ? parseInt(homeMatch[1]) : 50;
awayPossession = awayMatch ? parseInt(awayMatch[1]) : 50;
}
}
let homeResult = homeGoals > awayGoals ? 'W' : (homeGoals < awayGoals ? 'L' : 'D');
let awayResult = awayGoals > homeGoals ? 'W' : (awayGoals < homeGoals ? 'L' : 'D');
const homeUnlucky = homeSoT > awaySoT && homeResult !== 'W';
const awayUnlucky = awaySoT > homeSoT && awayResult !== 'W';
const homeLucky = homeSoT < awaySoT && homeResult === 'W';
const awayLucky = awaySoT < homeSoT && awayResult === 'W';
const homeConversion = homeSoT > 0 ? (homeGoals / homeSoT * 100).toFixed(1) : 0;
const awayConversion = awaySoT > 0 ? (awayGoals / awaySoT * 100).toFixed(1) : 0;
const matchData = {
mid: matchId,
teams: teamsInMatch,
homeTeam: { ...homeTeam, goals: homeGoals, shotsOnTarget: homeSoT, possession: homePossession, conversionRate: homeConversion, result: homeResult, unlucky: homeUnlucky, lucky: homeLucky },
awayTeam: { ...awayTeam, goals: awayGoals, shotsOnTarget: awaySoT, possession: awayPossession, conversionRate: awayConversion, result: awayResult, unlucky: awayUnlucky, lucky: awayLucky },
doc: doc
};
matchCache[matchId] = matchData;
return matchData;
} catch (error) {
log(`Error processing match ${matchId}: ${error.message}`, "error");
return null;
}
}
async function extractGoalkeeperData(matchData, teamIdMap) {
if (!matchData) return null;
gkLogging.attempts++;
gkLogging.matchesUsed.add(matchData.mid);
try {
for (const team of matchData.teams) {
if (teamGoalkeeperCache[team.id]) {
log(`Team ${team.id} already has goalkeeper data, skipping`, "debug");
continue;
}
if (teamIdMap && !teamIdMap[team.id]) {
log(`Team ${team.id} not in current league's team map, skipping`, "debug");
continue;
}
gkLogging.processedTeams.add(team.id);
const teamData = await fetchTeamPlayers(team.id);
if (!teamData) {
log(`Failed to fetch team data for team ${team.id}`, "error");
gkLogging.teamDetails[team.id] = { success: false, reason: "no_team_data" };
continue;
}
const lineupTable = team.table.querySelector('table.hitlist.soccer.statsLite.marker');
if (!lineupTable) {
log(`Lineup table not found for team ${team.id}`, "error");
gkLogging.teamDetails[team.id] = { success: false, reason: "lineup_table_not_found" };
continue;
}
const rows = lineupTable.querySelectorAll('tbody tr');
if (!rows || rows.length === 0) {
log(`No player rows found in lineup table for team ${team.id}`, "error");
gkLogging.teamDetails[team.id] = { success: false, reason: "no_player_rows" };
continue;
}
const firstRow = rows[0];
const playerLink = firstRow.querySelector('td:nth-child(3) a');
if (!playerLink) {
log(`No player link found in first row for team ${team.id}`, "error");
gkLogging.teamDetails[team.id] = { success: false, reason: "no_player_link" };
continue;
}
const playerHref = playerLink.getAttribute('href');
const pidMatch = playerHref.match(/pid=(\d+)/);
if (!pidMatch) {
log(`No player ID found in player link for team ${team.id}`, "error");
gkLogging.teamDetails[team.id] = { success: false, reason: "no_player_id" };
continue;
}
const goalkeeperId = pidMatch[1];
log(`Found goalkeeper ID ${goalkeeperId} for team ${team.id}`, "gk");
const goalkeeper = teamData.players.find(p => p.id === goalkeeperId);
if (goalkeeper) {
const currencyValue = teamData.teamCurrency;
const originalValue = goalkeeper.value;
log(`Goalkeeper found: ${goalkeeper.name} (ID: ${goalkeeper.id})`, "gk", { team: team.id, teamName: team.name, currency: currencyValue, value: originalValue });
const valueInUsd = convertToUsd(originalValue, currencyValue);
log(`Goalkeeper value: ${originalValue} ${currencyValue} = ${valueInUsd.toFixed(2)} USD`, "gk");
const result = { ...goalkeeper, valueInUsd };
teamGoalkeeperCache[team.id] = result;
gkLogging.successes++;
gkLogging.teamsWithGk.add(team.id);
gkLogging.teamDetails[team.id] = { success: true, goalkeeper: goalkeeper.name, originalValue: originalValue, currency: currencyValue, valueInUsd: valueInUsd };
log(`Successfully cached goalkeeper data for team ${team.id}`, "success");
} else {
log(`Goalkeeper not found in team data for ID ${goalkeeperId}`, "error");
gkLogging.teamDetails[team.id] = { success: false, reason: "goalkeeper_not_in_team_data", goalkeeperId: goalkeeperId };
}
}
return true;
} catch (error) {
log(`Error extracting goalkeeper data from match: ${error.message}`, "error");
gkLogging.failures++;
gkLogging.reasons["exception"] = (gkLogging.reasons["exception"] || 0) + 1;
return false;
}
}
async function fetchGoalkeepersMultiMatches(teams, matches, loadingEl = null) {
log(`Starting goalkeeper data fetch using ${matches.length} matches for ${teams.length} teams`, "info");
gkLogging.processedTeams.clear();
gkLogging.teamsWithGk.clear();
gkLogging.matchesUsed.clear();
const teamIdMap = {};
teams.forEach(team => { teamIdMap[team.id] = team; });
log(`Team ID map created with ${Object.keys(teamIdMap).length} teams`, "debug");
const teamsNeedingGkData = teams.filter(team => !teamGoalkeeperCache[team.id]).map(team => team.id);
if (teamsNeedingGkData.length === 0) {
log("All teams already have goalkeeper data in cache", "success");
return;
}
log(`Teams needing goalkeeper data: ${teamsNeedingGkData.length}`, "info", teamsNeedingGkData);
let matchesAttempted = 0;
for (const match of matches) {
if (gkLogging.teamsWithGk.size >= teams.length) {
log(`Found goalkeeper data for all ${teams.length} teams, stopping match processing`, "success");
break;
}
if (matchesAttempted >= MAX_GK_LOOKUP_ATTEMPTS) {
log(`Hit maximum match lookup limit (${MAX_GK_LOOKUP_ATTEMPTS})`, "warn");
break;
}
matchesAttempted++;
if (loadingEl) {
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Fetching goalkeeper data</span>
<span class="loading-stage">Match ${matchesAttempted}/${Math.min(matches.length, MAX_GK_LOOKUP_ATTEMPTS)}</span>
<span class="loading-progress">(Found: ${gkLogging.teamsWithGk.size}/${teams.length} goalkeepers)</span>`;
}
const homeTeamId = match.homeTeam.id;
const awayTeamId = match.awayTeam.id;
if (teamGoalkeeperCache[homeTeamId] && teamGoalkeeperCache[awayTeamId]) {
log(`Skipping match ${match.mid}: both teams already have GK data`, "debug");
continue;
}
const matchData = await processMatchComplete(match.mid, teamIdMap);
if (matchData) {
await extractGoalkeeperData(matchData, teamIdMap);
}
log(`After processing match ${match.mid}: Found GK data for ${gkLogging.teamsWithGk.size}/${teams.length} teams`, "info");
}
if (loadingEl) {
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Goalkeeper data collection complete</span>
<span class="loading-progress">(Found: ${gkLogging.teamsWithGk.size}/${teams.length} goalkeepers)</span>`;
}
log(`Goalkeeper lookup complete. Processed ${matchesAttempted} matches.`, "success");
log(`Found goalkeeper data for ${gkLogging.teamsWithGk.size}/${teams.length} teams (${((gkLogging.teamsWithGk.size / teams.length) * 100).toFixed(1)}%)`, gkLogging.teamsWithGk.size > 0 ? "success" : "warn");
}
function extractTeamData(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const teams = [];
const rows = doc.querySelectorAll('table.nice_table tbody tr');
log(`Found ${rows.length} team rows in league table`, "debug");
rows.forEach(row => {
const teamLinkElement = row.querySelector('td:nth-child(2) a[href*="tid="]');
if (teamLinkElement) {
const teamName = teamLinkElement.textContent.trim();
const hrefMatch = teamLinkElement.getAttribute('href').match(/tid=(\d+)/);
const teamId = hrefMatch ? hrefMatch[1] : null;
if (teamId) {
const positionElement = row.querySelector('td:first-child');
const position = positionElement ? positionElement.textContent.trim() : '';
const matches = row.querySelector('td:nth-child(3)').textContent.trim();
const wins = row.querySelector('td:nth-child(4)').textContent.trim();
const draws = row.querySelector('td:nth-child(5)').textContent.trim();
const losses = row.querySelector('td:nth-child(6)').textContent.trim();
const goalsFor = row.querySelector('td:nth-child(7)').textContent.trim();
const goalsAgainst = row.querySelector('td:nth-child(8)').textContent.trim();
const goalDiff = row.querySelector('td:nth-child(9)').textContent.trim();
const points = row.querySelector('td:nth-child(10)').textContent.trim();
const last6Element = row.querySelector('td:nth-child(11)');
const last6Results = [];
if (last6Element) {
const matchLinks = last6Element.querySelectorAll('a');
matchLinks.forEach(link => {
const imgElement = link.querySelector('img');
if (imgElement) {
const resultType = imgElement.getAttribute('src').includes('green') ? 'W' : imgElement.getAttribute('src').includes('yellow') ? 'D' : 'L';
const matchTitle = link.getAttribute('title') || '';
last6Results.push({ result: resultType, match: matchTitle });
}
});
}
teams.push({
id: teamId, name: teamName, position,
matches: parseInt(matches) || 0, wins: parseInt(wins) || 0, draws: parseInt(draws) || 0, losses: parseInt(losses) || 0,
goalsFor: parseInt(goalsFor) || 0, goalsAgainst: parseInt(goalsAgainst) || 0, goalDiff: parseInt(goalDiff) || 0, points: parseInt(points) || 0,
last6: last6Results
});
log(`Extracted team: ${teamName} (ID: ${teamId})`, "debug");
}
}
});
log(`Total teams extracted: ${teams.length}`, "info");
return teams;
}
function extractScheduleData(html, teams) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const matches = [];
const teamNameToId = {};
teams.forEach(team => { teamNameToId[team.name] = team.id; });
const matchRows = doc.querySelectorAll('.hitlist tr');
log(`Found ${matchRows.length} match rows in schedule`, "debug");
matchRows.forEach(row => {
const homeTeamCell = row.querySelector('td:nth-child(1)');
const resultCell = row.querySelector('td:nth-child(2) a');
const awayTeamCell = row.querySelector('td:nth-child(3)');
if (homeTeamCell && resultCell && awayTeamCell) {
const homeTeamName = homeTeamCell.textContent.trim();
const awayTeamName = awayTeamCell.textContent.trim();
const result = resultCell.textContent.trim();
const matchHref = resultCell.getAttribute('href');
const midMatch = matchHref.match(/mid=(\d+)/);
const mid = midMatch ? midMatch[1] : null;
if (mid) {
const homeTeamId = teamNameToId[homeTeamName] || null;
const awayTeamId = teamNameToId[awayTeamName] || null;
matches.push({ mid: mid, homeTeam: { name: homeTeamName, id: homeTeamId }, awayTeam: { name: awayTeamName, id: awayTeamId }, result: result });
if (!homeTeamId || !awayTeamId) {
log(`Warning: Team ID missing for match ${mid}: ${homeTeamName} vs ${awayTeamName}`, "warn");
}
}
}
});
log(`Total matches extracted: ${matches.length}`, "info");
return matches;
}
async function processLeagueData(leagueId, resultsDiv, allLeaguesData = null, leagueType = null) {
if (!leagueType) {
leagueType = getCurrentLeagueType();
if (!leagueType) {
const errorMsg = "League type not found in URL and no type provided";
log(errorMsg, "error");
if (resultsDiv) {
resultsDiv.innerHTML = `<p><span style="color:#ff6ac1">Error:</span> ${errorMsg}</p>`;
showBackButton(resultsDiv);
}
return;
}
}
const tableUrl = `https://www.managerzone.com/ajax.php?p=league&type=${leagueType}&sid=${leagueId}&tid=1&sport=soccer&sub=table`;
const scheduleUrl = `https://www.managerzone.com/ajax.php?p=league&type=${leagueType}&sid=${leagueId}&tid=1&sport=soccer&sub=schedule`;
const loadingEl = document.createElement('div');
loadingEl.style.display = 'flex';
loadingEl.style.justifyContent = 'center';
loadingEl.style.alignItems = 'center';
loadingEl.style.margin = '30px 0';
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Loading league data...</span>`;
if (resultsDiv) {
resultsDiv.innerHTML = '';
resultsDiv.appendChild(loadingEl);
}
try {
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Loading league data</span>
<span class="loading-stage">Step 1/3: Fetching league information</span>`;
log(`Processing league ID: ${leagueId} (Type: ${leagueType})`, "info");
const [tableHTML, scheduleHTML] = await Promise.all([makeRequest(tableUrl), makeRequest(scheduleUrl)]);
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Processing league data</span>
<span class="loading-stage">Step 2/3: Processing teams and schedule</span>`;
const teams = extractTeamData(tableHTML);
const allMatches = extractScheduleData(scheduleHTML, teams);
const playedMatches = allMatches.filter(match => /^\d+\s*-\s*\d+$/.test(match.result));
log(`Found ${teams.length} teams and ${playedMatches.length} played matches in league ${leagueId}`, "info");
const teamStats = {};
teams.forEach(team => {
teamStats[team.id] = {
name: team.name, shotsOnTarget: 0, goalsScored: 0, shotsOnTargetReceived: 0, goalsConceded: 0, matchesPlayed: 0,
unluckyMatches: 0, dominatingMatches: 0, luckyWins: 0, outshot: 0, unluckyMatchDetails: [], luckyMatchDetails: [],
goalkeeper: null, players18Count: 0
};
});
if (playedMatches.length > 0) {
gkLogging.processedTeams.clear();
gkLogging.teamsWithGk.clear();
gkLogging.matchesUsed.clear();
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Analyzing match data</span>
<span class="loading-stage">Step 3/3: Processing matches</span>
<span class="loading-progress">(0/${playedMatches.length})</span>`;
const teamIdMap = {};
teams.forEach(team => { teamIdMap[team.id] = team; });
const batchSize = BATCH_SIZE;
const maxMatches = playedMatches.length;
for (let i = 0; i < maxMatches; i += batchSize) {
const batch = playedMatches.slice(i, i + batchSize);
const matchPromises = batch.map(match => processMatchComplete(match.mid, teamIdMap));
const matchResults = await Promise.all(matchPromises);
if (i < MAX_GK_LOOKUP_ATTEMPTS * batchSize) {
const gkPromises = matchResults.filter(Boolean).map(matchData => extractGoalkeeperData(matchData, teamIdMap));
await Promise.all(gkPromises);
}
matchResults.forEach(matchData => {
if (!matchData) return;
if (teamStats[matchData.homeTeam.id]) {
const homeStats = teamStats[matchData.homeTeam.id];
homeStats.shotsOnTarget += matchData.homeTeam.shotsOnTarget;
homeStats.goalsScored += matchData.homeTeam.goals;
homeStats.shotsOnTargetReceived += matchData.awayTeam.shotsOnTarget;
homeStats.goalsConceded += matchData.awayTeam.goals;
homeStats.matchesPlayed++;
if (matchData.homeTeam.shotsOnTarget > matchData.awayTeam.shotsOnTarget) {
homeStats.dominatingMatches++;
if (matchData.homeTeam.result !== 'W') {
homeStats.unluckyMatches++;
homeStats.unluckyMatchDetails.push({ mid: matchData.mid, opponent: matchData.awayTeam.name, homeTeam: true, result: matchData.homeTeam.result, score: `${matchData.homeTeam.goals}-${matchData.awayTeam.goals}`, shotsOnTarget: `${matchData.homeTeam.shotsOnTarget}-${matchData.awayTeam.shotsOnTarget}` });
}
}
if (matchData.homeTeam.shotsOnTarget < matchData.awayTeam.shotsOnTarget) {
homeStats.outshot++;
if (matchData.homeTeam.result === 'W') {
homeStats.luckyWins++;
homeStats.luckyMatchDetails.push({ mid: matchData.mid, opponent: matchData.awayTeam.name, homeTeam: true, result: matchData.homeTeam.result, score: `${matchData.homeTeam.goals}-${matchData.awayTeam.goals}`, shotsOnTarget: `${matchData.homeTeam.shotsOnTarget}-${matchData.awayTeam.shotsOnTarget}` });
}
}
}
if (teamStats[matchData.awayTeam.id]) {
const awayStats = teamStats[matchData.awayTeam.id];
awayStats.shotsOnTarget += matchData.awayTeam.shotsOnTarget;
awayStats.goalsScored += matchData.awayTeam.goals;
awayStats.shotsOnTargetReceived += matchData.homeTeam.shotsOnTarget;
awayStats.goalsConceded += matchData.homeTeam.goals;
awayStats.matchesPlayed++;
if (matchData.awayTeam.shotsOnTarget > matchData.homeTeam.shotsOnTarget) {
awayStats.dominatingMatches++;
if (matchData.awayTeam.result !== 'W') {
awayStats.unluckyMatches++;
awayStats.unluckyMatchDetails.push({ mid: matchData.mid, opponent: matchData.homeTeam.name, homeTeam: false, result: matchData.awayTeam.result, score: `${matchData.homeTeam.goals}-${matchData.awayTeam.goals}`, shotsOnTarget: `${matchData.homeTeam.shotsOnTarget}-${matchData.awayTeam.shotsOnTarget}` });
}
}
if (matchData.awayTeam.shotsOnTarget < matchData.homeTeam.shotsOnTarget) {
awayStats.outshot++;
if (matchData.awayTeam.result === 'W') {
awayStats.luckyWins++;
awayStats.luckyMatchDetails.push({ mid: matchData.mid, opponent: matchData.homeTeam.name, homeTeam: false, result: matchData.awayTeam.result, score: `${matchData.homeTeam.goals}-${matchData.awayTeam.goals}`, shotsOnTarget: `${matchData.homeTeam.shotsOnTarget}-${matchData.awayTeam.shotsOnTarget}` });
}
}
}
});
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Analyzing match data</span>
<span class="loading-stage">Step 3/3: Processing matches</span>
<span class="loading-progress">(${Math.min(i + batch.length, maxMatches)}/${maxMatches})</span>
<span class="loading-progress">(Found: ${gkLogging.teamsWithGk.size}/${teams.length} goalkeepers)</span>`;
}
teams.forEach(team => {
if (teamGoalkeeperCache[team.id] && teamStats[team.id]) {
teamStats[team.id].goalkeeper = teamGoalkeeperCache[team.id];
log(`Added goalkeeper data for team ${team.name} (${team.id})`, "debug");
} else {
log(`No goalkeeper data found for team ${team.name} (${team.id})`, "debug");
}
if (teamPlayersCache[team.id] && teamStats[team.id]) {
teamStats[team.id].players18Count = teamPlayersCache[team.id].players18Count || 0;
log(`Added 18-year-old player count for team ${team.name} (${team.id}): ${teamStats[team.id].players18Count}`, "debug");
}
});
log(`Goalkeeper data coverage: ${gkLogging.teamsWithGk.size}/${teams.length} teams (${((gkLogging.teamsWithGk.size / teams.length) * 100).toFixed(1)}%)`, gkLogging.teamsWithGk.size > 0 ? "success" : "warn");
}
const validTeams = [];
teams.forEach(team => {
const stats = teamStats[team.id] || {
shotsOnTarget: 0, goalsScored: 0, shotsOnTargetReceived: 0, goalsConceded: 0, matchesPlayed: 0,
unluckyMatches: 0, dominatingMatches: 0, luckyWins: 0, outshot: 0, unluckyMatchDetails: [], luckyMatchDetails: [],
goalkeeper: null, players18Count: 0
};
if (stats.shotsOnTarget === 0 || stats.shotsOnTargetReceived === 0) {
log(`Excluding team ${team.name} (${team.id}) - Has 0 shots on target (for: ${stats.shotsOnTarget}, against: ${stats.shotsOnTargetReceived})`, "warn");
return;
}
team.shotsOnTarget = stats.shotsOnTarget;
team.goalsScored = stats.goalsScored;
team.shotsOnTargetReceived = stats.shotsOnTargetReceived;
team.goalsConceded = stats.goalsConceded;
team.matchesPlayed = stats.matchesPlayed;
team.unluckyMatches = stats.unluckyMatches;
team.dominatingMatches = stats.dominatingMatches;
team.luckyWins = stats.luckyWins;
team.outshot = stats.outshot;
team.unluckyMatchDetails = stats.unluckyMatchDetails;
team.luckyMatchDetails = stats.luckyMatchDetails;
team.goalkeeper = stats.goalkeeper;
team.players18Count = stats.players18Count;
team.offensiveConversionRate = stats.shotsOnTarget > 0 ? (stats.goalsScored / stats.shotsOnTarget * 100).toFixed(1) : '0.0';
team.defensiveConversionRate = stats.shotsOnTargetReceived > 0 ? (stats.goalsConceded / stats.shotsOnTargetReceived * 100).toFixed(1) : '0.0';
let luckIndex = parseFloat(team.offensiveConversionRate) - parseFloat(team.defensiveConversionRate);
if (team.goalkeeper && team.goalkeeper.valueInUsd) {
const gkValueFactor = Math.min(team.goalkeeper.valueInUsd / 1000000, 5) * 0.5;
luckIndex -= gkValueFactor;
log(`Team ${team.name} GK adjustment: ${team.goalkeeper.valueInUsd.toFixed(2)} USD = -${gkValueFactor.toFixed(1)} luck points`, "gk");
} else {
log(`No goalkeeper data for team ${team.name}`, "warn");
}
team.luckIndex = luckIndex.toFixed(1);
team.unluckyIndex = stats.dominatingMatches > 0 ? ((stats.unluckyMatches / stats.dominatingMatches) * 100).toFixed(1) : '0.0';
team.luckyWinPct = stats.outshot > 0 ? ((stats.luckyWins / stats.outshot) * 100).toFixed(1) : '0.0';
if (stats.dominatingMatches > 0) {
const unluckyPct = stats.unluckyMatches / stats.dominatingMatches;
const sampleWeight = 1 + Math.log(Math.max(stats.dominatingMatches, 1)) / 10;
team.weightedUnluckyScore = (unluckyPct * sampleWeight * 100).toFixed(1);
} else {
team.weightedUnluckyScore = '0.0';
}
if (stats.outshot > 0) {
const luckyPct = stats.luckyWins / stats.outshot;
const sampleWeight = 1 + Math.log(Math.max(stats.outshot, 1)) / 10;
team.weightedLuckyScore = (luckyPct * sampleWeight * 100).toFixed(1);
} else {
team.weightedLuckyScore = '0.0';
}
validTeams.push(team);
});
log(`Filtered teams: ${validTeams.length} of ${teams.length} teams have valid shot data`, "info");
const statLeaders = {
bestOffensiveConversion: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.offensiveConversionRate) - parseFloat(a.offensiveConversionRate)).slice(0, 3),
worstOffensiveConversion: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.offensiveConversionRate) - parseFloat(b.offensiveConversionRate)).slice(0, 3),
bestDefensiveConversion: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.defensiveConversionRate) - parseFloat(b.defensiveConversionRate)).slice(0, 3),
worstDefensiveConversion: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.defensiveConversionRate) - parseFloat(a.defensiveConversionRate)).slice(0, 3),
luckiest: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.luckIndex) - parseFloat(a.luckIndex)).slice(0, 3),
unluckiest: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.luckIndex) - parseFloat(b.luckIndex)).slice(0, 3),
mostUnluckyMatches: validTeams.filter(team => team.dominatingMatches > 0).sort((a, b) => parseFloat(b.weightedUnluckyScore) - parseFloat(a.weightedUnluckyScore)).slice(0, 3),
mostLuckyWins: validTeams.filter(team => team.outshot > 0).sort((a, b) => parseFloat(b.weightedLuckyScore) - parseFloat(a.weightedLuckyScore)).slice(0, 3)
};
displayGkDebugInfo();
if (allLeaguesData !== null) {
allLeaguesData[leagueId] = { teams: validTeams, statLeaders, leagueType };
updateGlobalLeaders(allLeaguesData);
updateLeagueSelector(allLeaguesData, resultsDiv, leagueId);
} else {
displayLeagueData(validTeams, statLeaders, leagueId, leagueType, resultsDiv);
}
log(`League ${leagueId} processed successfully`, "success");
} catch (error) {
log(`Error processing league ${leagueId}: ${error.message}`, "error");
if (resultsDiv) {
resultsDiv.innerHTML = `<p><span style="color:#ff6ac1">Error:</span> Failed to process league data: ${error.message}</p>`;
showBackButton(resultsDiv);
}
}
}
function updateGlobalLeaders(allLeaguesData) {
const allTeams = [];
for (const leagueId in allLeaguesData) {
if (leagueId === 'globalLeaders') continue;
allLeaguesData[leagueId].teams.forEach(team => {
team.leagueId = leagueId;
team.leagueType = allLeaguesData[leagueId].leagueType;
allTeams.push(team);
});
}
const globalLeaders = {
bestOffensiveConversion: allTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.offensiveConversionRate) - parseFloat(a.offensiveConversionRate)).slice(0, 5),
worstOffensiveConversion: allTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.offensiveConversionRate) - parseFloat(b.offensiveConversionRate)).slice(0, 5),
bestDefensiveConversion: allTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.defensiveConversionRate) - parseFloat(b.defensiveConversionRate)).slice(0, 5),
worstDefensiveConversion: allTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.defensiveConversionRate) - parseFloat(a.defensiveConversionRate)).slice(0, 5),
luckiest: allTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.luckIndex) - parseFloat(a.luckIndex)).slice(0, 5),
unluckiest: allTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.luckIndex) - parseFloat(b.luckIndex)).slice(0, 5),
mostUnluckyMatches: allTeams.filter(team => team.dominatingMatches > 0).sort((a, b) => parseFloat(b.weightedUnluckyScore) - parseFloat(a.weightedUnluckyScore)).slice(0, 5),
mostLuckyWins: allTeams.filter(team => team.outshot > 0).sort((a, b) => parseFloat(b.weightedLuckyScore) - parseFloat(a.weightedLuckyScore)).slice(0, 5)
};
allLeaguesData.globalLeaders = globalLeaders;
log("Global leaders calculated", "success");
}
function displayLeagueData(teams, statLeaders, leagueId, leagueType, resultsDiv) {
const contentDiv = document.createElement('div');
const tabContainer = document.createElement('div');
tabContainer.className = 'tab-container';
const summaryTab = document.createElement('button');
summaryTab.className = 'tab active';
summaryTab.textContent = 'Statistics Summary';
summaryTab.addEventListener('click', () => activateTab('summary'));
const teamsTab = document.createElement('button');
teamsTab.className = 'tab';
teamsTab.textContent = 'All Teams';
teamsTab.addEventListener('click', () => activateTab('teams'));
tabContainer.appendChild(summaryTab);
tabContainer.appendChild(teamsTab);
contentDiv.appendChild(tabContainer);
const summaryContent = document.createElement('div');
summaryContent.id = 'summary-tab';
summaryContent.className = 'tab-content active';
const teamsContent = document.createElement('div');
teamsContent.id = 'teams-tab';
teamsContent.className = 'tab-content';
const leagueUrl = `https://www.managerzone.com/?p=league&type=${leagueType}&sid=${leagueId}`;
const divisionName = getDivisionDisplayName(leagueId, leagueType);
const headerInfo = `
<div style="margin-bottom: 20px; padding: 10px; border-radius: 5px; background: rgba(0,0,0,0.2);">
<p><span style="color:#16f2f2">League:</span> <a href="${leagueUrl}" target="_blank" class="league-link"><span class="league-label">${divisionName} (ID: ${leagueId})</span></a></p>
<p><span style="color:#16f2f2">League Type:</span> ${leagueType}</p>
<p><span style="color:#16f2f2">Teams Found:</span> ${teams.length}</p>
<p><span style="color:#ff6ac1">GK Data Coverage: </span>${teams.filter(t => t.goalkeeper).length}/${teams.length}</p>
</div>`;
summaryContent.innerHTML = headerInfo;
teamsContent.innerHTML = headerInfo;
const offensiveCard = createStatCard('Best Conversion Rate', statLeaders.bestOffensiveConversion,
team => `${team.name}: ${team.offensiveConversionRate}% (${team.goalsScored}/${team.shotsOnTarget} SoT)${team.players18Count ? ` | 18: ${team.players18Count}` : ''}`, true);
const worstOffensiveCard = createStatCard('Worst Conversion Rate', statLeaders.worstOffensiveConversion,
team => `${team.name}: ${team.offensiveConversionRate}% (${team.goalsScored}/${team.shotsOnTarget} SoT)${team.players18Count ? ` | 18: ${team.players18Count}` : ''}`, true);
const defensiveCard = createStatCard('Best GKs', statLeaders.bestDefensiveConversion, team => {
let gkInfo = '';
if (team.goalkeeper && team.goalkeeper.valueInUsd) {
let formattedValue;
const valueInUsd = team.goalkeeper.valueInUsd;
if (valueInUsd >= 1000000) {
formattedValue = `${(valueInUsd / 1000000).toFixed(2)}M`;
} else {
formattedValue = `${(valueInUsd / 1000).toFixed(0)}k`;
}
gkInfo = ` | GKValue: $${formattedValue}`;
}
return `${team.name}: ${team.defensiveConversionRate}% (${team.goalsConceded}/${team.shotsOnTargetReceived} SoT Against)${gkInfo}${team.players18Count ? ` | 18: ${team.players18Count}` : ''}`;
}, true);
const worstDefensiveCard = createStatCard('Worst GKs', statLeaders.worstDefensiveConversion, team => {
let gkInfo = '';
if (team.goalkeeper && team.goalkeeper.valueInUsd) {
let formattedValue;
const valueInUsd = team.goalkeeper.valueInUsd;
if (valueInUsd >= 1000000) {
formattedValue = `${(valueInUsd / 1000000).toFixed(2)}M`;
} else {
formattedValue = `${(valueInUsd / 1000).toFixed(0)}k`;
}
gkInfo = ` | GK: $${formattedValue}`;
}
return `${team.name}: ${team.defensiveConversionRate}% (${team.goalsConceded}/${team.shotsOnTargetReceived} SoT Against)${gkInfo}${team.players18Count ? ` | 18: ${team.players18Count}` : ''}`;
}, true);
const luckiestCard = createStatCard('Luckiest Teams (Overall)', statLeaders.luckiest,
team => `${team.name}: +${team.luckIndex}${team.players18Count ? ` | 18: ${team.players18Count}` : ''}`, true);
const unluckiestCard = createStatCard('Unluckiest Teams (Overall)', statLeaders.unluckiest,
team => `${team.name}: ${team.luckIndex}${team.players18Count ? ` | 18: ${team.players18Count}` : ''}`, true);
const unluckyMatchesCard = createStatCard('Teams with Most Unlucky Matches', statLeaders.mostUnluckyMatches,
team => `${team.name}: ${team.unluckyIndex}% (${team.unluckyMatches}/${team.dominatingMatches})${team.players18Count ? ` | 18: ${team.players18Count}` : ''}
<br><span style="font-size: 0.9em; color: #ff6ac1;">Weighted Score: ${team.weightedUnluckyScore}</span>
<span class="toggle-matches" data-team-id="${team.id}" data-match-type="unlucky">View matches</span>`, true);
const luckyWinsCard = createStatCard('Teams with Most Lucky Wins', statLeaders.mostLuckyWins,
team => `${team.name}: ${team.luckyWinPct}% (${team.luckyWins}/${team.outshot})${team.players18Count ? ` | 18: ${team.players18Count}` : ''}
<br><span style="font-size: 0.9em; color: #ff6ac1;">Weighted Score: ${team.weightedLuckyScore}</span>
<span class="toggle-matches" data-team-id="${team.id}" data-match-type="lucky">View matches</span>`, true);
summaryContent.appendChild(offensiveCard);
summaryContent.appendChild(worstOffensiveCard);
summaryContent.appendChild(defensiveCard);
summaryContent.appendChild(worstDefensiveCard);
summaryContent.appendChild(luckiestCard);
summaryContent.appendChild(unluckiestCard);
summaryContent.appendChild(unluckyMatchesCard);
summaryContent.appendChild(luckyWinsCard);
teams.forEach(team => {
const teamDiv = document.createElement('div');
teamDiv.className = 'team-stats';
let gkInfo = '';
if (team.goalkeeper) {
let formattedValue;
const valueInUsd = team.goalkeeper.valueInUsd;
if (valueInUsd >= 1000000) {
formattedValue = `${(valueInUsd / 1000000).toFixed(2)}M`;
} else {
formattedValue = `${(valueInUsd / 1000).toFixed(0)}k`;
}
gkInfo = `<div style="color: #16f2f2;">Goalkeeper: ${team.goalkeeper.name} (Value: USD ${formattedValue})</div>`;
}
let players18Info = '';
if (team.players18Count !== undefined) {
players18Info = `<div style="color: #16f2f2;">Players Aged 18: ${team.players18Count}</div>`;
}
teamDiv.innerHTML = `
<div class="team-stats-title">${team.name}</div>
${gkInfo}
${players18Info}
<div>Position: ${team.position} | Stats: ${team.wins}W-${team.draws}D-${team.losses}L | Points: ${team.points}</div>
<div>Goals: ${team.goalsFor}-${team.goalsAgainst} (${team.goalDiff})</div>
<div style="color: #ff6ac1;">
<div>Offensive: SoT ${team.shotsOnTarget || 0} | Goals ${team.goalsScored || 0} | Conversion ${team.offensiveConversionRate}%</div>
<div>Defensive: SoT Against ${team.shotsOnTargetReceived || 0} | Goals Against ${team.goalsConceded || 0} | Conversion Against ${team.defensiveConversionRate}%</div>
<div><strong>Luck Index: ${team.luckIndex}</strong> (higher is better)</div>
<div>Dominating Matches: ${team.dominatingMatches} (had more SoT than opponent)</div>
<div>Unlucky Matches: ${team.unluckyIndex}% (${team.unluckyMatches}/${team.dominatingMatches} matches where they dominated but didn't win)
<br>Weighted Score: ${team.weightedUnluckyScore}
${team.unluckyMatches > 0 ? `<span class="toggle-matches" data-team-id="${team.id}" data-match-type="unlucky">View matches</span>` : ''}
</div>
<div>Lucky Wins: ${team.luckyWinPct}% (${team.luckyWins}/${team.outshot} matches won despite being outshot)
<br>Weighted Score: ${team.weightedLuckyScore}
${team.luckyWins > 0 ? `<span class="toggle-matches" data-team-id="${team.id}" data-match-type="lucky">View matches</span>` : ''}
</div>
</div>
<div>Last 6 matches: ${(team.last6 || []).map(m => `<span class="match-result" title="${m.match}">${m.result}</span>`).join('')}</div>`;
const unluckyMatchesDiv = document.createElement('div');
unluckyMatchesDiv.className = 'match-list';
unluckyMatchesDiv.dataset.teamId = team.id;
unluckyMatchesDiv.dataset.matchType = 'unlucky';
if (team.unluckyMatchDetails.length > 0) {
team.unluckyMatchDetails.forEach(match => {
const matchItem = document.createElement('div');
matchItem.className = 'match-item';
const resultClass = match.result === 'W' ? 'win' : (match.result === 'D' ? 'draw' : 'loss');
const matchInfo = match.homeTeam ? `${team.name} vs ${match.opponent}` : `${match.opponent} vs ${team.name}`;
matchItem.innerHTML = `
<a href="https://www.managerzone.com/?p=match&sub=result&mid=${match.mid}" target="_blank" class="match-link">${matchInfo}</a>
<span class="match-stats">SoT: ${match.shotsOnTarget}</span>
<span class="match-result ${resultClass}">${match.score} (${match.result})</span>`;
unluckyMatchesDiv.appendChild(matchItem);
});
} else {
unluckyMatchesDiv.innerHTML = '<p>No unlucky matches found.</p>';
}
const luckyMatchesDiv = document.createElement('div');
luckyMatchesDiv.className = 'match-list';
luckyMatchesDiv.dataset.teamId = team.id;
luckyMatchesDiv.dataset.matchType = 'lucky';
if (team.luckyMatchDetails.length > 0) {
team.luckyMatchDetails.forEach(match => {
const matchItem = document.createElement('div');
matchItem.className = 'match-item';
const resultClass = match.result === 'W' ? 'win' : (match.result === 'D' ? 'draw' : 'loss');
const matchInfo = match.homeTeam ? `${team.name} vs ${match.opponent}` : `${match.opponent} vs ${team.name}`;
matchItem.innerHTML = `
<a href="https://www.managerzone.com/?p=match&sub=result&mid=${match.mid}" target="_blank" class="match-link">${matchInfo}</a>
<span class="match-stats">SoT: ${match.shotsOnTarget}</span>
<span class="match-result ${resultClass}">${match.score} (${match.result})</span>`;
luckyMatchesDiv.appendChild(matchItem);
});
} else {
luckyMatchesDiv.innerHTML = '<p>No lucky matches found.</p>';
}
teamDiv.appendChild(unluckyMatchesDiv);
teamDiv.appendChild(luckyMatchesDiv);
teamsContent.appendChild(teamDiv);
});
contentDiv.appendChild(summaryContent);
contentDiv.appendChild(teamsContent);
function activateTab(tabName) {
const tabs = contentDiv.querySelectorAll('.tab');
const contents = contentDiv.querySelectorAll('.tab-content');
tabs.forEach(tab => tab.classList.remove('active'));
contents.forEach(content => content.classList.remove('active'));
if (tabName === 'summary') {
summaryTab.classList.add('active');
summaryContent.classList.add('active');
} else {
teamsTab.classList.add('active');
teamsContent.classList.add('active');
}
}
resultsDiv.innerHTML = '';
resultsDiv.appendChild(contentDiv);
document.querySelectorAll('.toggle-matches').forEach(toggle => {
toggle.addEventListener('click', function(e) {
const teamId = this.dataset.teamId;
const matchType = this.dataset.matchType;
const parentStatItem = this.closest('.stat-item') || this.closest('.team-stats');
if (!parentStatItem) return;
const matchList = parentStatItem.querySelector(`.match-list[data-team-id="${teamId}"][data-match-type="${matchType}"]`);
if (matchList) {
if (matchList.style.display === 'block') {
matchList.style.display = 'none';
this.textContent = "View matches";
} else {
matchList.style.display = 'block';
this.textContent = "Hide matches";
}
}
});
});
}
function createStatCard(title, teams, formatter, showRank = false) {
const card = document.createElement('div');
card.className = 'stat-card';
const cardTitle = document.createElement('div');
cardTitle.className = 'stat-card-title';
cardTitle.textContent = title;
card.appendChild(cardTitle);
teams.forEach((team, index) => {
const item = document.createElement('div');
item.className = 'stat-item';
let content = '';
if (showRank) {
content += `<span class="stat-rank">${index + 1}</span> `;
}
content += formatter(team);
const itemContentDiv = document.createElement('div');
itemContentDiv.innerHTML = content;
item.appendChild(itemContentDiv);
if (team.leagueId && team.leagueType) {
const divisionName = getDivisionDisplayName(team.leagueId, team.leagueType);
const leagueUrl = `https://www.managerzone.com/?p=league&type=${team.leagueType}&sid=${team.leagueId}`;
const leagueInfoDiv = document.createElement('div');
leagueInfoDiv.className = 'stat-value';
leagueInfoDiv.innerHTML = `<a href="${leagueUrl}" target="_blank" class="league-link"><span class="league-label">${divisionName}</span></a>`;
item.appendChild(leagueInfoDiv);
}
card.appendChild(item);
if (itemContentDiv.querySelector('.toggle-matches')) {
const toggleElement = itemContentDiv.querySelector('.toggle-matches');
const matchType = toggleElement.dataset.matchType;
const matchDetails = matchType === 'unlucky' ? team.unluckyMatchDetails : team.luckyMatchDetails;
if (matchDetails && matchDetails.length > 0) {
const matchesDiv = document.createElement('div');
matchesDiv.className = 'match-list';
matchesDiv.dataset.teamId = team.id;
matchesDiv.dataset.matchType = matchType;
matchesDiv.style.display = 'none';
matchesDiv.style.width = '100%';
matchDetails.forEach(match => {
const matchItem = document.createElement('div');
matchItem.className = 'match-item';
const resultClass = match.result === 'W' ? 'win' : (match.result === 'D' ? 'draw' : 'loss');
const matchInfo = match.homeTeam ? `${team.name} vs ${match.opponent}` : `${match.opponent} vs ${team.name}`;
matchItem.innerHTML = `
<a href="https://www.managerzone.com/?p=match&sub=result&mid=${match.mid}" target="_blank" class="match-link">${matchInfo}</a>
<span class="match-stats">SoT: ${match.shotsOnTarget}</span>
<span class="match-result ${resultClass}">${match.score} (${match.result})</span>`;
matchesDiv.appendChild(matchItem);
});
item.appendChild(matchesDiv);
}
}
});
return card;
}
function updateLeagueSelector(allLeaguesData, resultsDiv, currentLeagueId) {
resultsDiv.innerHTML = '';
const tabContainer = document.createElement('div');
tabContainer.className = 'tab-container';
const globalTab = document.createElement('button');
globalTab.className = 'tab active';
globalTab.textContent = 'Global Leaders';
const leagueTab = document.createElement('button');
leagueTab.className = 'tab';
leagueTab.textContent = 'League View';
tabContainer.appendChild(globalTab);
tabContainer.appendChild(leagueTab);
const globalContent = document.createElement('div');
globalContent.id = 'global-content';
globalContent.className = 'tab-content active';
const leagueContent = document.createElement('div');
leagueContent.id = 'league-content';
leagueContent.className = 'tab-content';
const backButtonContainer = document.createElement('div');
const backButton = document.createElement('button');
backButton.className = 'back-button';
backButton.textContent = '← Back to Options';
backButton.style.marginBottom = '15px';
backButton.addEventListener('click', function() {
resultsDiv.style.display = 'none';
resultsDiv.innerHTML = '';
document.querySelectorAll('.unluckyOption').forEach(option => {
option.style.display = 'block';
});
});
backButtonContainer.appendChild(backButton);
const leagueIds = Object.keys(allLeaguesData)
.filter(key => key !== 'globalLeaders')
.sort((a, b) => parseInt(a) - parseInt(b));
let currentActiveLeagueIndex = leagueIds.indexOf(currentLeagueId);
if (currentActiveLeagueIndex === -1 && leagueIds.length > 0) {
currentActiveLeagueIndex = 0;
currentLeagueId = leagueIds[0];
}
if (allLeaguesData.globalLeaders) {
const globalLeaders = allLeaguesData.globalLeaders;
const globalSubTabContainer = document.createElement('div');
globalSubTabContainer.className = 'tab-container';
const globalSummaryTab = document.createElement('button');
globalSummaryTab.className = 'tab active';
globalSummaryTab.textContent = 'Statistics Summary';
const globalTeamsTab = document.createElement('button');
globalTeamsTab.className = 'tab';
globalTeamsTab.textContent = 'All Teams';
globalSubTabContainer.appendChild(globalSummaryTab);
globalSubTabContainer.appendChild(globalTeamsTab);
const globalSummaryContent = document.createElement('div');
globalSummaryContent.id = 'global-summary-tab';
globalSummaryContent.className = 'tab-content active';
const globalTeamsContent = document.createElement('div');
globalTeamsContent.id = 'global-teams-tab';
globalTeamsContent.className = 'tab-content';
const globalHeader = document.createElement('div');
globalHeader.innerHTML = `
<h2 style="color: #ff6ac1; text-align: center; margin-bottom: 20px;">Global Statistics Leaders</h2>
<p style="text-align: center; margin-bottom: 20px;">Showing the best and worst teams across all analyzed leagues</p>`;
globalSummaryContent.appendChild(globalHeader.cloneNode(true));
globalTeamsContent.appendChild(globalHeader.cloneNode(true));
const bestOffensive = createStatCard('Best Offensive Conversion (Global)', globalLeaders.bestOffensiveConversion,
team => `${team.name}: ${team.offensiveConversionRate}% (${team.goalsScored}/${team.shotsOnTarget} SoT)${team.players18Count ? ` | Players 18: ${team.players18Count}` : ''}`, true);
const worstOffensive = createStatCard('Worst Offensive Conversion (Global)', globalLeaders.worstOffensiveConversion,
team => `${team.name}: ${team.offensiveConversionRate}% (${team.goalsScored}/${team.shotsOnTarget} SoT)${team.players18Count ? ` | Players 18: ${team.players18Count}` : ''}`, true);
const bestDefensive = createStatCard('Best GKs (Global)', globalLeaders.bestDefensiveConversion, team => {
let gkInfo = '';
if (team.goalkeeper && team.goalkeeper.valueInUsd) {
let formattedValue;
const valueInUsd = team.goalkeeper.valueInUsd;
if (valueInUsd >= 1000000) {
formattedValue = `${(valueInUsd / 1000000).toFixed(2)}M`;
} else {
formattedValue = `${(valueInUsd / 1000).toFixed(0)}k`;
}
gkInfo = ` | GK: $${formattedValue}`;
}
return `${team.name}: ${team.defensiveConversionRate}% (${team.goalsConceded}/${team.shotsOnTargetReceived} SoT Against)${gkInfo}${team.players18Count ? ` | Players 18: ${team.players18Count}` : ''}`;
}, true);
const worstDefensive = createStatCard('Worst GKs (Global)', globalLeaders.worstDefensiveConversion, team => {
let gkInfo = '';
if (team.goalkeeper && team.goalkeeper.valueInUsd) {
let formattedValue;
const valueInUsd = team.goalkeeper.valueInUsd;
if (valueInUsd >= 1000000) {
formattedValue = `${(valueInUsd / 1000000).toFixed(2)}M`;
} else {
formattedValue = `${(valueInUsd / 1000).toFixed(0)}k`;
}
gkInfo = ` | GK: $${formattedValue}`;
}
return `${team.name}: ${team.defensiveConversionRate}% (${team.goalsConceded}/${team.shotsOnTargetReceived} SoT Against)${gkInfo}${team.players18Count ? ` | Players 18: ${team.players18Count}` : ''}`;
}, true);
const luckiest = createStatCard('Luckiest Teams (Global)', globalLeaders.luckiest,
team => `${team.name}: +${team.luckIndex}${team.players18Count ? ` | Players 18: ${team.players18Count}` : ''}`, true);
const unluckiest = createStatCard('Unluckiest Teams (Global)', globalLeaders.unluckiest,
team => `${team.name}: ${team.luckIndex}${team.players18Count ? ` | Players 18: ${team.players18Count}` : ''}`, true);
const unluckyMatches = createStatCard('Teams with Most Unlucky Matches (Global)', globalLeaders.mostUnluckyMatches,
team => `${team.name}: ${team.unluckyIndex}% (${team.unluckyMatches}/${team.dominatingMatches} matches)${team.players18Count ? ` | Players 18: ${team.players18Count}` : ''}
<br><span style="font-size: 0.9em; color: #ff6ac1;">Weighted Score: ${team.weightedUnluckyScore}</span>
<span class="toggle-matches" data-team-id="${team.id}" data-match-type="unlucky">View matches</span>`, true);
const luckyWins = createStatCard('Teams with Most Lucky Wins (Global)', globalLeaders.mostLuckyWins,
team => `${team.name}: ${team.luckyWinPct}% (${team.luckyWins}/${team.outshot} matches)${team.players18Count ? ` | Players 18: ${team.players18Count}` : ''}
<br><span style="font-size: 0.9em; color: #ff6ac1;">Weighted Score: ${team.weightedLuckyScore}</span>
<span class="toggle-matches" data-team-id="${team.id}" data-match-type="lucky">View matches</span>`, true);
globalSummaryContent.appendChild(bestOffensive);
globalSummaryContent.appendChild(worstOffensive);
globalSummaryContent.appendChild(bestDefensive);
globalSummaryContent.appendChild(worstDefensive);
globalSummaryContent.appendChild(luckiest);
globalSummaryContent.appendChild(unluckiest);
globalSummaryContent.appendChild(unluckyMatches);
globalSummaryContent.appendChild(luckyWins);
const globalTeamsListContainer = document.createElement('div');
const allTeams = [];
for (const leagueId in allLeaguesData) {
if (leagueId === 'globalLeaders') continue;
allLeaguesData[leagueId].teams.forEach(team => {
team.leagueId = leagueId;
team.leagueType = allLeaguesData[leagueId].leagueType;
allTeams.push(team);
});
}
const sortedTeams = allTeams.sort((a, b) => parseFloat(a.luckIndex) - parseFloat(b.luckIndex));
sortedTeams.forEach(team => {
const teamDiv = document.createElement('div');
teamDiv.className = 'team-stats';
let gkInfo = '';
if (team.goalkeeper) {
let formattedValue;
const valueInUsd = team.goalkeeper.valueInUsd;
if (valueInUsd >= 1000000) {
formattedValue = `${(valueInUsd / 1000000).toFixed(2)}M`;
} else {
formattedValue = `${(valueInUsd / 1000).toFixed(0)}k`;
}
gkInfo = `<div style="color: #16f2f2;">Goalkeeper: ${team.goalkeeper.name} (Value: USD ${formattedValue})</div>`;
}
const divisionName = getDivisionDisplayName(team.leagueId, team.leagueType);
const leagueUrl = `https://www.managerzone.com/?p=league&type=${team.leagueType}&sid=${team.leagueId}`;
teamDiv.innerHTML = `
<div class="team-stats-title">${team.name} <span style="float: right; font-size: 0.9em;">
<a href="${leagueUrl}" target="_blank" class="league-link"><span class="league-label">${divisionName}</span></a></span>
</div>
${gkInfo}
${team.players18Count ? `<div style="color: #16f2f2;">Players Aged 18: ${team.players18Count}</div>` : ''}
<div>Position: ${team.position} | Stats: ${team.wins}W-${team.draws}D-${team.losses}L | Points: ${team.points}</div>
<div>Goals: ${team.goalsFor}-${team.goalsAgainst} (${team.goalDiff})</div>
<div style="color: #ff6ac1;">
<div>Offensive: SoT ${team.shotsOnTarget || 0} | Goals ${team.goalsScored || 0} | Conversion ${team.offensiveConversionRate}%</div>
<div>Defensive: SoT Against ${team.shotsOnTargetReceived || 0} | Goals Against ${team.goalsConceded || 0} | Conversion Against ${team.defensiveConversionRate}%</div>
<div><strong>Luck Index: ${team.luckIndex}</strong> (higher is better)</div>
<div>Dominating Matches: ${team.dominatingMatches} (had more SoT than opponent)</div>
<div>Unlucky Matches: ${team.unluckyIndex}% (${team.unluckyMatches}/${team.dominatingMatches} matches where they dominated but didn't win)
<br>Weighted Score: ${team.weightedUnluckyScore}
${team.unluckyMatches > 0 ? `<span class="toggle-matches" data-team-id="${team.id}" data-match-type="unlucky">View matches</span>` : ''}
</div>
<div>Lucky Wins: ${team.luckyWinPct}% (${team.luckyWins}/${team.outshot} matches won despite being outshot)
<br>Weighted Score: ${team.weightedLuckyScore}
${team.luckyWins > 0 ? `<span class="toggle-matches" data-team-id="${team.id}" data-match-type="lucky">View matches</span>` : ''}
</div>
</div>`;
const unluckyMatchesDiv = document.createElement('div');
unluckyMatchesDiv.className = 'match-list';
unluckyMatchesDiv.dataset.teamId = team.id;
unluckyMatchesDiv.dataset.matchType = 'unlucky';
unluckyMatchesDiv.style.display = 'none';
if (team.unluckyMatchDetails && team.unluckyMatchDetails.length > 0) {
team.unluckyMatchDetails.forEach(match => {
const matchItem = document.createElement('div');
matchItem.className = 'match-item';
const resultClass = match.result === 'W' ? 'win' : (match.result === 'D' ? 'draw' : 'loss');
const matchInfo = match.homeTeam ? `${team.name} vs ${match.opponent}` : `${match.opponent} vs ${team.name}`;
matchItem.innerHTML = `
<a href="https://www.managerzone.com/?p=match&sub=result&mid=${match.mid}" target="_blank" class="match-link">${matchInfo}</a>
<span class="match-stats">SoT: ${match.shotsOnTarget}</span>
<span class="match-result ${resultClass}">${match.score} (${match.result})</span>`;
unluckyMatchesDiv.appendChild(matchItem);
});
} else {
unluckyMatchesDiv.innerHTML = '<p>No unlucky matches found.</p>';
}
const luckyMatchesDiv = document.createElement('div');
luckyMatchesDiv.className = 'match-list';
luckyMatchesDiv.dataset.teamId = team.id;
luckyMatchesDiv.dataset.matchType = 'lucky';
luckyMatchesDiv.style.display = 'none';
if (team.luckyMatchDetails && team.luckyMatchDetails.length > 0) {
team.luckyMatchDetails.forEach(match => {
const matchItem = document.createElement('div');
matchItem.className = 'match-item';
const resultClass = match.result === 'W' ? 'win' : (match.result === 'D' ? 'draw' : 'loss');
const matchInfo = match.homeTeam ? `${team.name} vs ${match.opponent}` : `${match.opponent} vs ${team.name}`;
matchItem.innerHTML = `
<a href="https://www.managerzone.com/?p=match&sub=result&mid=${match.mid}" target="_blank" class="match-link">${matchInfo}</a>
<span class="match-stats">SoT: ${match.shotsOnTarget}</span>
<span class="match-result ${resultClass}">${match.score} (${match.result})</span>`;
luckyMatchesDiv.appendChild(matchItem);
});
} else {
luckyMatchesDiv.innerHTML = '<p>No lucky matches found.</p>';
}
teamDiv.appendChild(unluckyMatchesDiv);
teamDiv.appendChild(luckyMatchesDiv);
globalTeamsListContainer.appendChild(teamDiv);
});
globalTeamsContent.appendChild(globalTeamsListContainer);
globalSummaryTab.addEventListener('click', () => {
globalSummaryTab.classList.add('active');
globalTeamsTab.classList.remove('active');
globalSummaryContent.classList.add('active');
globalTeamsContent.classList.remove('active');
});
globalTeamsTab.addEventListener('click', () => {
globalSummaryTab.classList.remove('active');
globalTeamsTab.classList.add('active');
globalSummaryContent.classList.remove('active');
globalTeamsContent.classList.add('active');
});
globalContent.appendChild(globalSubTabContainer);
globalContent.appendChild(globalSummaryContent);
globalContent.appendChild(globalTeamsContent);
}
const selectorContainer = document.createElement('div');
selectorContainer.className = 'league-selector';
const selectorTitle = document.createElement('div');
selectorTitle.className = 'league-selector-title';
selectorTitle.textContent = 'Select a League:';
selectorContainer.appendChild(selectorTitle);
const leagueGrid = document.createElement('div');
leagueGrid.className = 'league-grid';
leagueIds.forEach((leagueId, index) => {
const leagueButton = document.createElement('div');
leagueButton.className = 'league-item';
if (index === currentActiveLeagueIndex) {
leagueButton.classList.add('active');
}
const leagueType = allLeaguesData[leagueId].leagueType;
const divisionName = getDivisionDisplayName(leagueId, leagueType);
const leagueUrl = `https://www.managerzone.com/?p=league&type=${leagueType}&sid=${leagueId}`;
leagueButton.innerHTML = `<a href="${leagueUrl}" target="_blank" class="league-link" title="View ${divisionName}"><span class="league-label">${divisionName}</span></a>`;
leagueButton.dataset.leagueId = leagueId;
leagueButton.addEventListener('click', (e) => {
if (e.target.closest('.league-link')) {
return;
}
e.preventDefault();
const clickedLeagueId = e.currentTarget.dataset.leagueId;
displayLeagueContent(clickedLeagueId);
});
leagueGrid.appendChild(leagueButton);
});
selectorContainer.appendChild(leagueGrid);
const paginationContainer = document.createElement('div');
paginationContainer.className = 'pagination';
paginationContainer.style.marginTop = '15px';
if (leagueIds.length > 1) {
const prevButton = document.createElement('button');
prevButton.className = 'pagination-button prev';
prevButton.innerHTML = '« Previous League';
prevButton.disabled = currentActiveLeagueIndex <= 0;
prevButton.style.opacity = prevButton.disabled ? '0.5' : '1';
prevButton.addEventListener('click', () => {
if (currentActiveLeagueIndex > 0) {
displayLeagueContent(leagueIds[currentActiveLeagueIndex - 1]);
}
});
const nextButton = document.createElement('button');
nextButton.className = 'pagination-button next';
nextButton.innerHTML = 'Next League »';
nextButton.disabled = currentActiveLeagueIndex >= leagueIds.length - 1;
nextButton.style.opacity = nextButton.disabled ? '0.5' : '1';
nextButton.addEventListener('click', () => {
if (currentActiveLeagueIndex < leagueIds.length - 1) {
displayLeagueContent(leagueIds[currentActiveLeagueIndex + 1]);
}
});
paginationContainer.appendChild(prevButton);
paginationContainer.appendChild(nextButton);
selectorContainer.appendChild(paginationContainer);
}
leagueContent.appendChild(selectorContainer);
function displayLeagueContent(leagueId) {
const newLeagueIndex = leagueIds.indexOf(leagueId);
if (newLeagueIndex === -1) return;
const leagueData = allLeaguesData[leagueId];
currentActiveLeagueIndex = newLeagueIndex;
document.querySelectorAll('.league-item').forEach(item => {
item.classList.remove('active');
if (item.dataset.leagueId === leagueId) {
item.classList.add('active');
}
});
const prevButton = paginationContainer?.querySelector('.pagination-button.prev');
const nextButton = paginationContainer?.querySelector('.pagination-button.next');
if (prevButton) {
prevButton.disabled = newLeagueIndex <= 0;
prevButton.style.opacity = prevButton.disabled ? '0.5' : '1';
}
if (nextButton) {
nextButton.disabled = newLeagueIndex >= leagueIds.length - 1;
nextButton.style.opacity = nextButton.disabled ? '0.5' : '1';
}
const leagueDataContainer = document.createElement('div');
leagueDataContainer.id = 'league-data-container';
displayLeagueData(leagueData.teams, leagueData.statLeaders, leagueId, leagueData.leagueType, leagueDataContainer);
const existingContainer = leagueContent.querySelector('#league-data-container');
if (existingContainer) {
leagueContent.replaceChild(leagueDataContainer, existingContainer);
} else {
leagueContent.appendChild(leagueDataContainer);
}
leagueDataContainer.querySelectorAll('.toggle-matches').forEach(toggle => {
toggle.addEventListener('click', function(e) {
const teamId = this.dataset.teamId;
const matchType = this.dataset.matchType;
const parentStatItem = this.closest('.stat-item') || this.closest('.team-stats');
if (!parentStatItem) return;
const matchList = parentStatItem.querySelector(`.match-list[data-team-id="${teamId}"][data-match-type="${matchType}"]`);
if (matchList) {
if (matchList.style.display === 'block') {
matchList.style.display = 'none';
this.textContent = "View matches";
} else {
matchList.style.display = 'block';
this.textContent = "Hide matches";
}
}
});
});
globalTab.classList.remove('active');
leagueTab.classList.add('active');
globalContent.classList.remove('active');
leagueContent.classList.add('active');
resultsDiv.scrollTop = 0;
}
const initialLeagueDataContainer = document.createElement('div');
initialLeagueDataContainer.id = 'league-data-container';
leagueContent.appendChild(initialLeagueDataContainer);
if (currentActiveLeagueIndex !== -1) {
displayLeagueContent(leagueIds[currentActiveLeagueIndex]);
}
globalTab.addEventListener('click', () => {
globalTab.classList.add('active');
leagueTab.classList.remove('active');
globalContent.classList.add('active');
leagueContent.classList.remove('active');
});
leagueTab.addEventListener('click', () => {
globalTab.classList.remove('active');
leagueTab.classList.add('active');
globalContent.classList.remove('active');
leagueContent.classList.add('active');
});
const mainContent = document.createElement('div');
mainContent.appendChild(backButtonContainer);
mainContent.appendChild(tabContainer);
mainContent.appendChild(globalContent);
mainContent.appendChild(leagueContent);
resultsDiv.appendChild(mainContent);
globalContent.querySelectorAll('.toggle-matches').forEach(toggle => {
toggle.addEventListener('click', function(e) {
const teamId = this.dataset.teamId;
const matchType = this.dataset.matchType;
const parentStatItem = this.closest('.stat-item') || this.closest('.team-stats');
if (!parentStatItem) return;
const matchList = parentStatItem.querySelector(`.match-list[data-team-id="${teamId}"][data-match-type="${matchType}"]`);
if (matchList) {
if (matchList.style.display === 'block') {
matchList.style.display = 'none';
this.textContent = "View matches";
} else {
matchList.style.display = 'block';
this.textContent = "Hide matches";
}
}
});
});
}
async function processMultipleLeagues(leagueEntries, resultsDiv) {
const allLeaguesData = {};
const loadingEl = document.createElement('div');
loadingEl.style.display = 'flex';
loadingEl.style.justifyContent = 'center';
loadingEl.style.alignItems = 'center';
loadingEl.style.margin = '30px 0';
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Processing leagues (0/${leagueEntries.length})...</span>`;
resultsDiv.innerHTML = '';
resultsDiv.appendChild(loadingEl);
for (let i = 0; i < leagueEntries.length; i++) {
const entry = leagueEntries[i];
const leagueId = entry.id;
const leagueType = entry.type;
const divisionName = getDivisionDisplayName(leagueId, leagueType);
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Processing <span class="league-label" style="vertical-align: middle;">${divisionName}</span> (${i+1}/${leagueEntries.length}) - Type: ${leagueType}</span>
<span class="loading-stage">Step 1/3: Fetching info</span>`;
try {
const tableUrl = `https://www.managerzone.com/ajax.php?p=league&type=${leagueType}&sid=${leagueId}&tid=1&sport=soccer&sub=table`;
const scheduleUrl = `https://www.managerzone.com/ajax.php?p=league&type=${leagueType}&sid=${leagueId}&tid=1&sport=soccer&sub=schedule`;
log(`Processing league ID: ${leagueId} (Type: ${leagueType})`, "info");
const [tableHTML, scheduleHTML] = await Promise.all([makeRequest(tableUrl), makeRequest(scheduleUrl)]);
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Processing <span class="league-label" style="vertical-align: middle;">${divisionName}</span> (${i+1}/${leagueEntries.length}) - Type: ${leagueType}</span>
<span class="loading-stage">Step 2/3: Processing teams</span>`;
const teams = extractTeamData(tableHTML);
if (teams.length > 0) {
const allMatches = extractScheduleData(scheduleHTML, teams);
const playedMatches = allMatches.filter(match => /^\d+\s*-\s*\d+$/.test(match.result));
log(`Found ${teams.length} teams and ${playedMatches.length} played matches in league ${leagueId}`, "info");
const teamStats = {};
teams.forEach(team => {
teamStats[team.id] = {
name: team.name, shotsOnTarget: 0, goalsScored: 0, shotsOnTargetReceived: 0, goalsConceded: 0, matchesPlayed: 0,
unluckyMatches: 0, dominatingMatches: 0, luckyWins: 0, outshot: 0, unluckyMatchDetails: [], luckyMatchDetails: [],
goalkeeper: null, players18Count: 0
};
});
if (playedMatches.length > 0) {
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Processing <span class="league-label" style="vertical-align: middle;">${divisionName}</span> (${i+1}/${leagueEntries.length}) - Type: ${leagueType}</span>
<span class="loading-stage">Step 3/3: Analyzing matches</span>`;
const teamIdMap = {};
teams.forEach(team => { teamIdMap[team.id] = team; });
gkLogging.processedTeams.clear();
gkLogging.teamsWithGk.clear();
gkLogging.matchesUsed.clear();
const batchSize = BATCH_SIZE;
const maxMatches = playedMatches.length;
for (let j = 0; j < maxMatches; j += batchSize) {
const batch = playedMatches.slice(j, j + batchSize);
const matchPromises = batch.map(match => processMatchComplete(match.mid, teamIdMap));
const matchResults = await Promise.all(matchPromises);
if (j < MAX_GK_LOOKUP_ATTEMPTS * batchSize) {
const gkPromises = matchResults.filter(Boolean).map(matchData => extractGoalkeeperData(matchData, teamIdMap));
await Promise.all(gkPromises);
}
matchResults.forEach(matchData => {
if (!matchData) return;
if (teamStats[matchData.homeTeam.id]) {
const homeStats = teamStats[matchData.homeTeam.id];
homeStats.shotsOnTarget += matchData.homeTeam.shotsOnTarget;
homeStats.goalsScored += matchData.homeTeam.goals;
homeStats.shotsOnTargetReceived += matchData.awayTeam.shotsOnTarget;
homeStats.goalsConceded += matchData.awayTeam.goals;
homeStats.matchesPlayed++;
if (matchData.homeTeam.shotsOnTarget > matchData.awayTeam.shotsOnTarget) {
homeStats.dominatingMatches++;
if (matchData.homeTeam.result !== 'W') {
homeStats.unluckyMatches++;
homeStats.unluckyMatchDetails.push({ mid: matchData.mid, opponent: matchData.awayTeam.name, homeTeam: true, result: matchData.homeTeam.result, score: `${matchData.homeTeam.goals}-${matchData.awayTeam.goals}`, shotsOnTarget: `${matchData.homeTeam.shotsOnTarget}-${matchData.awayTeam.shotsOnTarget}` });
}
}
if (matchData.homeTeam.shotsOnTarget < matchData.awayTeam.shotsOnTarget) {
homeStats.outshot++;
if (matchData.homeTeam.result === 'W') {
homeStats.luckyWins++;
homeStats.luckyMatchDetails.push({ mid: matchData.mid, opponent: matchData.awayTeam.name, homeTeam: true, result: matchData.homeTeam.result, score: `${matchData.homeTeam.goals}-${matchData.awayTeam.goals}`, shotsOnTarget: `${matchData.homeTeam.shotsOnTarget}-${matchData.awayTeam.shotsOnTarget}` });
}
}
}
if (teamStats[matchData.awayTeam.id]) {
const awayStats = teamStats[matchData.awayTeam.id];
awayStats.shotsOnTarget += matchData.awayTeam.shotsOnTarget;
awayStats.goalsScored += matchData.awayTeam.goals;
awayStats.shotsOnTargetReceived += matchData.homeTeam.shotsOnTarget;
awayStats.goalsConceded += matchData.homeTeam.goals;
awayStats.matchesPlayed++;
if (matchData.awayTeam.shotsOnTarget > matchData.homeTeam.shotsOnTarget) {
awayStats.dominatingMatches++;
if (matchData.awayTeam.result !== 'W') {
awayStats.unluckyMatches++;
awayStats.unluckyMatchDetails.push({ mid: matchData.mid, opponent: matchData.homeTeam.name, homeTeam: false, result: matchData.awayTeam.result, score: `${matchData.homeTeam.goals}-${matchData.awayTeam.goals}`, shotsOnTarget: `${matchData.homeTeam.shotsOnTarget}-${matchData.awayTeam.shotsOnTarget}` });
}
}
if (matchData.awayTeam.shotsOnTarget < matchData.homeTeam.shotsOnTarget) {
awayStats.outshot++;
if (matchData.awayTeam.result === 'W') {
awayStats.luckyWins++;
awayStats.luckyMatchDetails.push({ mid: matchData.mid, opponent: matchData.homeTeam.name, homeTeam: false, result: matchData.awayTeam.result, score: `${matchData.homeTeam.goals}-${matchData.awayTeam.goals}`, shotsOnTarget: `${matchData.homeTeam.shotsOnTarget}-${matchData.awayTeam.shotsOnTarget}` });
}
}
}
});
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Processing <span class="league-label" style="vertical-align: middle;">${divisionName}</span> (${i+1}/${leagueEntries.length}) - Type: ${leagueType}</span>
<span class="loading-stage">Step 3/3: Analyzing matches</span>
<span class="loading-progress">(${Math.min(j + batch.length, maxMatches)}/${maxMatches} processed)</span>
<span class="loading-progress">(Found: ${gkLogging.teamsWithGk.size}/${teams.length} GKs)</span>`;
}
}
teams.forEach(team => {
if (teamGoalkeeperCache[team.id] && teamStats[team.id]) {
teamStats[team.id].goalkeeper = teamGoalkeeperCache[team.id];
}
if (teamPlayersCache[team.id] && teamStats[team.id]) {
teamStats[team.id].players18Count = teamPlayersCache[team.id].players18Count || 0;
}
});
const validTeams = [];
teams.forEach(team => {
const stats = teamStats[team.id] || {
shotsOnTarget: 0, goalsScored: 0, shotsOnTargetReceived: 0, goalsConceded: 0, matchesPlayed: 0,
unluckyMatches: 0, dominatingMatches: 0, luckyWins: 0, outshot: 0, unluckyMatchDetails: [], luckyMatchDetails: [],
goalkeeper: null, players18Count: 0
};
if (stats.shotsOnTarget === 0 || stats.shotsOnTargetReceived === 0) {
log(`Excluding team ${team.name} (${team.id}) - Has 0 shots on target (for: ${stats.shotsOnTarget}, against: ${stats.shotsOnTargetReceived})`, "warn");
return;
}
team.shotsOnTarget = stats.shotsOnTarget;
team.goalsScored = stats.goalsScored;
team.shotsOnTargetReceived = stats.shotsOnTargetReceived;
team.goalsConceded = stats.goalsConceded;
team.matchesPlayed = stats.matchesPlayed;
team.unluckyMatches = stats.unluckyMatches;
team.dominatingMatches = stats.dominatingMatches;
team.luckyWins = stats.luckyWins;
team.outshot = stats.outshot;
team.unluckyMatchDetails = stats.unluckyMatchDetails;
team.luckyMatchDetails = stats.luckyMatchDetails;
team.goalkeeper = stats.goalkeeper;
team.players18Count = stats.players18Count;
team.offensiveConversionRate = stats.shotsOnTarget > 0 ? (stats.goalsScored / stats.shotsOnTarget * 100).toFixed(1) : '0.0';
team.defensiveConversionRate = stats.shotsOnTargetReceived > 0 ? (stats.goalsConceded / stats.shotsOnTargetReceived * 100).toFixed(1) : '0.0';
let luckIndex = parseFloat(team.offensiveConversionRate) - parseFloat(team.defensiveConversionRate);
if (team.goalkeeper && team.goalkeeper.valueInUsd) {
const gkValueFactor = Math.min(team.goalkeeper.valueInUsd / 1000000, 5) * 0.5;
luckIndex -= gkValueFactor;
log(`Team ${team.name} GK adjustment: ${team.goalkeeper.valueInUsd.toFixed(2)} USD = -${gkValueFactor.toFixed(1)} luck points`, "gk");
}
team.luckIndex = luckIndex.toFixed(1);
team.unluckyIndex = stats.dominatingMatches > 0 ? ((stats.unluckyMatches / stats.dominatingMatches) * 100).toFixed(1) : '0.0';
team.luckyWinPct = stats.outshot > 0 ? ((stats.luckyWins / stats.outshot) * 100).toFixed(1) : '0.0';
if (stats.dominatingMatches > 0) {
const unluckyPct = stats.unluckyMatches / stats.dominatingMatches;
const sampleWeight = 1 + Math.log(Math.max(stats.dominatingMatches, 1)) / 10;
team.weightedUnluckyScore = (unluckyPct * sampleWeight * 100).toFixed(1);
} else {
team.weightedUnluckyScore = '0.0';
}
if (stats.outshot > 0) {
const luckyPct = stats.luckyWins / stats.outshot;
const sampleWeight = 1 + Math.log(Math.max(stats.outshot, 1)) / 10;
team.weightedLuckyScore = (luckyPct * sampleWeight * 100).toFixed(1);
} else {
team.weightedLuckyScore = '0.0';
}
validTeams.push(team);
});
log(`Filtered teams: ${validTeams.length} of ${teams.length} teams have valid shot data`, "info");
const statLeaders = {
bestOffensiveConversion: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.offensiveConversionRate) - parseFloat(a.offensiveConversionRate)).slice(0, 3),
worstOffensiveConversion: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.offensiveConversionRate) - parseFloat(b.offensiveConversionRate)).slice(0, 3),
bestDefensiveConversion: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.defensiveConversionRate) - parseFloat(b.defensiveConversionRate)).slice(0, 3),
worstDefensiveConversion: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.defensiveConversionRate) - parseFloat(a.defensiveConversionRate)).slice(0, 3),
luckiest: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(b.luckIndex) - parseFloat(a.luckIndex)).slice(0, 3),
unluckiest: validTeams.filter(team => team.matchesPlayed > 0).sort((a, b) => parseFloat(a.luckIndex) - parseFloat(b.luckIndex)).slice(0, 3),
mostUnluckyMatches: validTeams.filter(team => team.dominatingMatches > 0).sort((a, b) => parseFloat(b.weightedUnluckyScore) - parseFloat(a.weightedUnluckyScore)).slice(0, 3),
mostLuckyWins: validTeams.filter(team => team.outshot > 0).sort((a, b) => parseFloat(b.weightedLuckyScore) - parseFloat(a.weightedLuckyScore)).slice(0, 3)
};
if (playedMatches.length > 0 && validTeams.length > 0) {
allLeaguesData[leagueId] = { teams: validTeams, statLeaders, leagueType };
}
}
} catch (error) {
log(`Error processing league ${leagueId} (Type: ${leagueType}): ${error.message}`, "error");
continue;
}
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Processed <span class="league-label" style="vertical-align: middle;">${divisionName}</span> (${i+1}/${leagueEntries.length})</span>`;
}
loadingEl.innerHTML = `
<div class="pulse-dot" style="width: 20px; height: 20px;"></div>
<span style="color: #16f2f2; font-size: 18px;">Finalizing global statistics...</span>`;
displayGkDebugInfo();
updateGlobalLeaders(allLeaguesData);
const leagueIds = Object.keys(allLeaguesData)
.filter(key => key !== 'globalLeaders')
.sort((a, b) => parseInt(a) - parseInt(b));
if (leagueIds.length > 0) {
updateLeagueSelector(allLeaguesData, resultsDiv, leagueIds[0]);
} else {
resultsDiv.innerHTML = '<p>No leagues with valid match data found in the specified leagues.</p>';
showBackButton(resultsDiv);
}
}
function showLeagueInputForm(resultsDiv) {
const formDiv = document.createElement('div');
formDiv.style.marginBottom = '20px';
const header = document.createElement('p');
header.textContent = 'Add leagues (by ID & Type) to analyze:';
formDiv.appendChild(header);
const leagueEntryContainer = document.createElement('div');
leagueEntryContainer.className = 'league-entry-container';
const entryForm = document.createElement('div');
entryForm.className = 'league-entry-form';
const sidInput = document.createElement('input');
sidInput.type = 'text';
sidInput.placeholder = 'League IDs (e.g., 1, 2, 3)';
sidInput.autocomplete = 'off';
const typeSelect = document.createElement('select');
LEAGUE_TYPES.forEach(lt => {
const option = document.createElement('option');
option.value = lt.value;
option.textContent = lt.label;
typeSelect.appendChild(option);
});
const addButton = document.createElement('button');
addButton.className = 'add-button';
addButton.innerHTML = '+';
addButton.title = 'Add Leagues';
entryForm.appendChild(sidInput);
entryForm.appendChild(typeSelect);
entryForm.appendChild(addButton);
const leagueList = document.createElement('div');
leagueList.className = 'league-list';
leagueList.style.display = 'none';
const leagues = [];
const updateSubmitButton = () => {
submitButton.disabled = leagues.length === 0;
submitButton.style.opacity = submitButton.disabled ? 0.6 : 1;
submitButton.style.cursor = submitButton.disabled ? 'not-allowed' : 'pointer';
};
function updateLeagueList() {
if (leagues.length === 0) {
leagueList.style.display = 'none';
updateSubmitButton();
return;
}
leagueList.style.display = 'block';
leagueList.innerHTML = '';
leagues.forEach((league, index) => {
const leagueItem = document.createElement('div');
leagueItem.className = 'league-list-item';
const divisionName = getDivisionDisplayName(league.id, league.type);
const typeLabel = LEAGUE_TYPES.find(lt => lt.value === league.type)?.label || league.type;
leagueItem.innerHTML = `
<div><span class="league-label">${divisionName}</span> (${typeLabel})</div>
<button class="remove-button" data-index="${index}">×</button>`;
leagueList.appendChild(leagueItem);
});
leagueList.querySelectorAll('.remove-button').forEach(button => {
button.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
leagues.splice(index, 1);
updateLeagueList();
});
});
updateSubmitButton();
}
function addLeague() {
const rawIds = sidInput.value.trim();
const selectedType = typeSelect.value;
if (!rawIds) {
alert('Please enter at least one league ID.');
return;
}
const potentialIds = rawIds.split(/[\s,]+/).filter(id => id);
let addedCount = 0;
let invalidCount = 0;
let duplicateCount = 0;
potentialIds.forEach(idString => {
const potentialId = idString.trim();
if (potentialId && /^\d+$/.test(potentialId)) {
if (leagues.some(l => l.id === potentialId && l.type === selectedType)) {
duplicateCount++;
} else {
leagues.push({ id: potentialId, type: selectedType });
addedCount++;
}
} else if (potentialId) {
invalidCount++;
}
});
if (addedCount > 0) {
updateLeagueList();
}
sidInput.value = '';
sidInput.focus();
let feedback = [];
if (addedCount > 0) feedback.push(`Added ${addedCount} league(s).`);
if (invalidCount > 0) feedback.push(`Ignored ${invalidCount} invalid entries.`);
if (duplicateCount > 0) feedback.push(`Skipped ${duplicateCount} duplicate entries.`);
if (feedback.length > 0 && (invalidCount > 0 || duplicateCount > 0)) {
setTimeout(() => alert(feedback.join(' ')), 100);
} else if (addedCount === 0 && invalidCount === 0 && duplicateCount === 0) {
alert('No valid, new league IDs were entered.');
}
}
addButton.addEventListener('click', addLeague);
sidInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addLeague();
});
const submitButton = document.createElement('button');
submitButton.id = 'leagues-submit';
submitButton.className = 'unluckyOption';
submitButton.style.marginTop = '15px';
submitButton.textContent = 'Analyze Leagues';
submitButton.disabled = true;
updateSubmitButton();
submitButton.addEventListener('click', () => {
if (leagues.length > 0) {
const summaryText = leagues.map(league => {
const divName = getDivisionDisplayName(league.id, league.type);
const typeLabel = LEAGUE_TYPES.find(lt => lt.value === league.type)?.label || league.type;
return `${divName} (${typeLabel})`;
}).join(', ');
resultsDiv.innerHTML = `<p><span class="pulse-dot"></span> <span style="color:#16f2f2">Processing ${leagues.length} leagues...</span></p>`;
processMultipleLeagues(leagues, resultsDiv);
}
});
leagueEntryContainer.appendChild(entryForm);
leagueEntryContainer.appendChild(leagueList);
formDiv.appendChild(leagueEntryContainer);
formDiv.appendChild(submitButton);
resultsDiv.innerHTML = '';
resultsDiv.appendChild(formDiv);
showBackButton(resultsDiv);
setTimeout(() => sidInput.focus(), 100);
}
function showSidPrompt(resultsDiv, isSpecificOption = false) {
if (isSpecificOption) {
const currentLeagueId = getCurrentLeagueId();
const currentLeagueType = getCurrentLeagueType();
const formDiv = document.createElement('div');
formDiv.style.marginBottom = '20px';
formDiv.innerHTML = `
<p>Please enter a league ID and select type:</p>
<div class="league-entry-form" style="margin-top: 15px; margin-bottom: 15px;">
<input type="text" id="sid-input" placeholder="Enter league ID..." autocomplete="off" spellcheck="false" value="${currentLeagueId || ''}">
<select id="type-select">
${LEAGUE_TYPES.map(lt => `<option value="${lt.value}" ${currentLeagueType === lt.value ? 'selected' : ''}>${lt.label}</option>`).join('')}
</select>
<button id="sid-submit" class="unluckyOption" style="padding: 8px 15px; margin: 0; flex: 0 0 auto;">Submit</button>
</div>`;
resultsDiv.innerHTML = '';
resultsDiv.appendChild(formDiv);
const sidInput = document.getElementById('sid-input');
const typeSelect = document.getElementById('type-select');
const sidSubmit = document.getElementById('sid-submit');
const handleSubmit = () => {
const sid = sidInput.value.trim();
const type = typeSelect.value;
if (sid && /^\d+$/.test(sid)) {
const divisionName = getDivisionDisplayName(sid, type);
resultsDiv.innerHTML = `<p><span class="pulse-dot"></span> <span style="color:#16f2f2">Loading data for <span class="league-label">${divisionName}</span> (Type: ${type})...</span></p>`;
processLeagueData(sid, resultsDiv, null, type);
} else {
let errorMsg = 'Please enter a valid league ID (numbers only).';
formDiv.insertAdjacentHTML('afterbegin', `<p style="color:#ff6ac1; margin-bottom: 10px;">${errorMsg}</p>`);
}
};
sidSubmit.addEventListener('click', handleSubmit);
sidInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') handleSubmit();
});
showBackButton(resultsDiv);
setTimeout(() => sidInput.focus(), 100);
} else {
const leagueId = getCurrentLeagueId();
const leagueType = getCurrentLeagueType();
if (!leagueId || !leagueType) {
resultsDiv.innerHTML = `<p><span style="color:#ff6ac1">Error:</span> Could not detect league ID or type from URL.</p>`;
showBackButton(resultsDiv);
return;
}
const divisionName = getDivisionDisplayName(leagueId, leagueType);
resultsDiv.innerHTML = `<p><span class="pulse-dot"></span> <span style="color:#16f2f2">Loading data for <span class="league-label">${divisionName}</span> (Type: ${leagueType})...</span></p>`;
processLeagueData(leagueId, resultsDiv, null, leagueType);
}
}
function findUnluckyTeams(scope) {
document.querySelectorAll('.unluckyOption').forEach(option => {
option.style.display = 'none';
});
const resultsDiv = document.getElementById('unluckyResults');
resultsDiv.style.display = 'block';
resultsDiv.innerHTML = '';
if (scope === 'all') {
showLeagueInputForm(resultsDiv);
} else if (scope === 'current') {
const leagueId = getCurrentLeagueId();
const leagueType = getCurrentLeagueType();
if (!leagueId || !leagueType) {
resultsDiv.innerHTML = '<p style="color:#ff6ac1">Could not detect league ID or type from URL.</p>';
showBackButton(resultsDiv);
return;
}
showSidPrompt(resultsDiv, false);
} else if (scope === 'specific') {
showSidPrompt(resultsDiv, true);
}
}
function initializeScript() {
createModal();
addUnluckyButton();
log("MZ-Unlucky initialized - 私たちは最高の防御を持っています!", "success");
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(initializeScript, 500);
} else {
window.addEventListener('load', () => { setTimeout(initializeScript, 500); });
}
})();