您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Finds unlucky teams
当前为
// ==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); }); } })();