// ==UserScript== // @name MZ - NT Player Search // @namespace douglaskampl // @version 4.02 // @description Searches for players who match min. requirements (NC/NCA only) // @author Douglas Vieira // @match https://www.managerzone.com/?p=national_teams&type=senior // @match https://www.managerzone.com/?p=national_teams&type=u21 // @icon https://yt3.googleusercontent.com/ytc/AIdro_mDHaJkwjCgyINFM7cdUV2dWPPnL9Q58vUsrhOmRqkatg=s160-c-k-c0x00ffffff-no-rj // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @connect mzlive.eu // @connect api.github.com // @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js // @run-at document-idle // @license MIT // @downloadURL none // ==/UserScript== (async function () { 'use strict'; const FONT_URL = 'https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap'; const STYLE = `.nt-search-fab{position:fixed;bottom:2rem;right:2rem;width:60px;height:60px;background:linear-gradient(135deg, #ff6e40, #ff5252, #448aff);border-radius:50%;box-shadow:0 4px 12px rgba(0,0,0,.3);z-index:9998;cursor:pointer;transition:all .3s cubic-bezier(0.4,0,0.2,1);display:flex;justify-content:center;align-items:center}.nt-search-fab:hover{transform:scale(1.1);box-shadow:0 6px 16px rgba(83,11,237,.4)}.nt-search-fab i{color:white;font-size:24px;transition:transform .3s}.nt-search-fab.loading i{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.nt-search-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(.95);background:linear-gradient(135deg,#0a0a0a 0%,#1a1a2e 100%);color:#f0f0f0;padding:2rem;border-radius:12px;box-shadow:0 8px 32px rgba(83,11,237,.3),0 4px 8px rgba(0,0,0,.2);z-index:9999;visibility:hidden;width:800px;max-width:99%;opacity:0;transition:all .3s cubic-bezier(0.4,0,0.2,1);border:1px solid rgba(138,43,226,.1)}.nt-search-container.visible{visibility:visible;opacity:1;transform:translate(-50%,-50%) scale(1)}.nt-search-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem;padding-bottom:1rem;border-bottom:1px solid rgba(138,43,226,.2)}.nt-search-header h2{font-family:'Space Mono',monospace;margin:0;color:violet;font-size:1.5rem;text-shadow:0 0 10px rgba(138,43,226,.5)}.nt-search-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1rem;margin-bottom:1.5rem}.nt-search-field{display:flex;flex-direction:column;gap:.5rem}.nt-search-field label{color:#ff9966;font-size:.875rem;text-transform:uppercase;letter-spacing:1px}.nt-search-field select{padding:.75rem;border:1px solid rgba(138,43,226,.3);border-radius:8px;background:#1a1a2e;color:#f0f0f0;font-size:1rem;transition:all .2s;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23ff9966' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right .75rem center;background-size:1rem}.nt-search-field select:focus{outline:none;border-color:#ff9966;box-shadow:0 0 0 2px rgba(138,43,226,.2)}.nt-search-field select:disabled{opacity:0.5;cursor:not-allowed;background:#333}.nt-search-buttons{display:flex;justify-content:center;align-items:center;gap:1rem;margin-top:1rem}.nt-search-button{width:auto;max-width:300px;padding:0.5rem 1rem;background:#009b3a;color:#ffdf00;border:none;border-radius:8px;font-weight:500;font-size:0.9rem;cursor:pointer;transition:all .2s;text-transform:uppercase;letter-spacing:2px;box-shadow:0 4px 6px rgba(0,0,0,.1)}.nt-search-button:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 6px 8px rgba(0,0,0,.2)}.nt-search-button:disabled{opacity:0.5;cursor:not-allowed;background:#666}.nt-search-log{margin-top:1rem;padding:1rem;background:rgba(26,26,46,.3);border-radius:8px;font-family:monospace;font-size:.875rem;max-height:150px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#6366f1 #1a1a2e}.nt-search-log::-webkit-scrollbar{width:8px;height:8px}.nt-search-log::-webkit-scrollbar-track{background:#1a1a2e;border-radius:4px}.nt-search-log::-webkit-scrollbar-thumb{background:#6366f1;border-radius:4px}.nt-search-log::-webkit-scrollbar-thumb:hover{background:#4834d4}.nt-search-log-entry{margin-bottom:.5rem;padding:.5rem;background:rgba(26,26,46,.5);border-radius:4px;color:#00ffff;animation:slideIn 0.3s ease-out forwards;opacity:0;transform:translateX(-20px)}@keyframes slideIn{from{opacity:0;transform:translateX(-20px)}to{opacity:1;transform:translateX(0)}}.nt-search-guestbook-link{position:fixed;bottom:1rem;right:1rem;color:#ff9966;transition:all .2s}.nt-search-guestbook-link:hover{color:#6366f1;transform:scale(1.1)}.nt-search-country-select{width:200px}.nt-search-country-select select{width:100%}.nt-search-results-button{width:auto;max-width:300px;padding:0.5rem 1rem;background:#009b3a;color:#ffdf00;border:none;border-radius:8px;font-weight:500;font-size:0.9rem;cursor:pointer;transition:all .2s;text-transform:uppercase;letter-spacing:2px;box-shadow:0 4px 6px rgba(0,0,0,.1);display:none}.nt-search-results-button:hover{transform:translateY(-2px);box-shadow:0 6px 8px rgba(0,0,0,.2)}.nt-search-results-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:linear-gradient(135deg,#0a0a0a 0%,#1a1a2e 100%);color:#f0f0f0;padding:0;border-radius:12px;z-index:10001;width:90%;height:90vh;overflow:hidden;box-shadow:0 8px 32px rgba(83,11,237,.3);animation:modalSlideIn 0.3s ease-out forwards}@keyframes modalSlideIn{from{opacity:0;transform:translate(-50%,-48%)}to{opacity:1;transform:translate(-50%,-50%)}}.nt-search-results-header{position:sticky;top:0;display:flex;justify-content:space-between;align-items:center;padding:1.5rem;background:inherit;border-bottom:1px solid rgba(138,43,226,.2);z-index:1}.nt-search-results-title{font-family:'Space Mono',monospace;margin:0;font-size:1.5rem;color:#fff;text-shadow:0 0 10px rgba(138,43,226,.5)}.nt-search-results-close{background:none;border:none;color:#ff9966;font-size:1.5rem;cursor:pointer;transition:all 0.2s;padding:0.5rem}.nt-search-results-close:hover{color:#6366f1;transform:scale(1.1)}.nt-search-results-content{padding:1.5rem;height:calc(90vh - 5rem);overflow-y:auto;scrollbar-width:thin;scrollbar-color:#6366f1 #1a1a2e}.nt-search-results-content::-webkit-scrollbar{width:8px}.nt-search-results-content::-webkit-scrollbar-track{background:#1a1a2e}.nt-search-results-content::-webkit-scrollbar-thumb{background:#6366f1;border-radius:4px}.nt-search-results-content::-webkit-scrollbar-thumb:hover{background:#4834d4}.nt-search-players-container{display:flex;flex-wrap:wrap;gap:1.5rem;margin:1rem 0}.nt-search-player-card{display:flex;flex-direction:row;gap:1.5rem;background:rgba(26,26,46,.5);border-radius:8px;padding:1.5rem;transition:all .2s;border:1px solid rgba(138,43,226,.1);flex:1 1 calc(50% - 1.5rem);box-sizing:border-box;min-width:500px}.nt-search-player-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(83,11,237,.2)}.nt-search-player-summary{display:flex;flex-direction:column;flex-basis:45%;gap:.75rem}.nt-search-player-name{font-size:1.2rem;font-weight:bold;color:#fff;margin:0}.nt-search-player-name a{color:inherit;text-decoration:none}.nt-search-player-name a:hover{color:violet}.nt-search-player-details{display:flex;flex-direction:column;gap:.5rem;color:#ccc;font-size:0.875rem;margin-top:auto}.nt-search-player-details a{color:#ff9966;text-decoration:none;transition:color .2s ease-in-out}.nt-search-player-details a:hover{color:#fff;text-decoration:underline}.nt-search-skills-list{display:flex;flex-direction:column;gap:4px;flex-basis:55%}.nt-search-skill-row{display:flex;align-items:center;background:transparent;padding:0;box-shadow:none;min-height:24px}.nt-search-skill-name{font-size:.8rem;color:#f0f0f0;flex-basis:80px;margin-right:8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.nt-search-skill-value{display:flex;align-items:center;gap:4px;color:#ff9966;flex-shrink:0}.nt-search-skill-value img{height:.9em;width:auto;vertical-align:middle}.nt-search-skill-value-text{font-size:.8rem;white-space:nowrap}.nt-search-player-total-balls{font-weight:bold;color:#ffdf00;font-size:1rem}.nt-search-results-pagination{display:flex;justify-content:center;align-items:center;gap:1rem;padding:1rem 0;border-top:1px solid rgba(138,43,226,.1);border-bottom:1px solid rgba(138,43,226,.1);margin:0 -1.5rem 1rem -1.5rem}.nt-search-results-pagination.bottom{border-top:1px solid rgba(138,43,226,.1);border-bottom:none;margin-top:1rem;margin-bottom:0}.nt-search-results-pagination.top{border-bottom:1px solid rgba(138,43,226,.1);border-top:none;margin-bottom:1rem;margin-top:0}.nt-search-pagination-button{background:#1a1a2e;color:#f0f0f0;border:1px solid rgba(138,43,226,.3);border-radius:4px;padding:0.5rem 1rem;cursor:pointer;transition:all 0.2s}.nt-search-pagination-button:not(:disabled):hover{background:#2a2a4e;transform:translateY(-1px)}.nt-search-pagination-button:disabled{opacity:0.5;cursor:not-allowed}.nt-search-pagination-info{color:#ff9966;font-size:0.875rem}.nt-search-header-controls{display:flex;align-items:center;gap:1rem;position:relative}.nt-search-export-button-group .nt-search-export-button{background:#1a1a2e;color:#f0f0f0;border:1px solid rgba(138,43,226,.3);border-radius:4px;padding:0.5rem 1rem;cursor:pointer;transition:all 0.2s}.nt-search-export-button-group .nt-search-export-button:hover{background:#2a2a4e;transform:translateY(-1px)}.nt-search-export-options{display:none;position:absolute;top:100%;right:0;background:#1a1a2e;border:1px solid rgba(138,43,226,.3);border-radius:4px;padding:0.5rem;z-index:10;box-shadow:0 4px 8px rgba(0,0,0,.2);flex-direction:column;gap:0.5rem;width:200px}.nt-search-export-options.show{display:flex}.nt-search-export-options button{width:100%;text-align:left;background:transparent;color:#f0f0f0;border:none;padding:0.5rem 0.75rem;border-radius:4px;cursor:pointer}.nt-search-export-options button:hover{background:rgba(138,43,226,.2)}.nt-search-history-controls{display:flex;gap:1rem;}.nt-search-history-button{padding:0.5rem 1rem;background-color:transparent;color:#ff9966;border:1px solid #ff9966;border-radius:8px;cursor:pointer;transition:all .2s}.nt-search-history-button:hover{background-color:rgba(255,153,102,0.1);color:white}.nt-search-history-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:linear-gradient(135deg,#0a0a0a 0%,#1a1a2e 100%);color:#f0f0f0;border-radius:12px;z-index:10001;width:90%;max-width:1000px;height:80vh;display:flex;flex-direction:column;box-shadow:0 8px 32px rgba(83,11,237,.3);animation:modalSlideIn .3s ease-out forwards;border:1px solid rgba(138,43,226,.1)}.nt-search-history-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.5rem;border-bottom:1px solid rgba(138,43,226,.2);flex-shrink:0}.nt-search-history-title{font-family:'Space Mono',monospace;margin:0;font-size:1.5rem;color:#fff;text-shadow:0 0 10px rgba(138,43,226,.5)}.nt-search-history-header-actions{display:flex;align-items:center;gap:1rem}.nt-search-history-content{padding:1.5rem;overflow-y:auto;flex-grow:1;scrollbar-width:thin;scrollbar-color:#6366f1 #1a1a2e}.nt-search-history-content::-webkit-scrollbar{width:8px}.nt-search-history-content::-webkit-scrollbar-track{background:#1a1a2e}.nt-search-history-content::-webkit-scrollbar-thumb{background:#6366f1}.nt-search-history-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:1rem}.nt-search-history-item{background:rgba(26,26,46,.5);border:1px solid rgba(138,43,226,.1);border-radius:8px;padding:1rem;display:flex;justify-content:space-between;align-items:center;transition:background-color .2s ease}.nt-search-history-item:hover{background:rgba(26,26,46,.8)}.nt-search-history-item-info{display:flex;flex-direction:column;gap:.5rem;max-width:70%}.nt-search-history-item-timestamp{font-family:'Space Mono',monospace;color:#ff9966;font-size:.875rem}.nt-search-history-item-filters{font-size:.8rem;color:#ccc;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.nt-search-history-item-actions{display:flex;gap:.5rem}.nt-search-history-item-actions .nt-search-button{padding:.4rem .8rem;font-size:.8rem;letter-spacing:1px;background:#1a1a2e;color:#f0f0f0;border:1px solid #6366f1}.nt-search-history-item-actions .nt-search-button.delete{border-color:#ff5252}.nt-search-history-empty{text-align:center;padding:4rem 0;color:#888;font-style:italic}`; const COUNTRIES_DATA = [{"code":"AL","name":"Albania","cid":45,"u21ntid":1043509,"ntid":855930},{"code":"DZ","name":"Algeria","cid":52,"u21ntid":1043510,"ntid":855937},{"code":"AD","name":"Andorra","cid":46,"u21ntid":1043511,"ntid":855931},{"code":"AO","name":"Angola","cid":64,"u21ntid":1043512,"ntid":855949},{"code":"AR","name":"Argentina","cid":240,"u21ntid":1043513,"ntid":100},{"code":"AU","name":"Australia","cid":246,"u21ntid":1043514,"ntid":101},{"code":"AT","name":"Austria","cid":242,"u21ntid":1043515,"ntid":102},{"code":"AZ","name":"Azerbaijan","cid":54,"u21ntid":1043516,"ntid":855939},{"code":"BD","name":"Bangladesh","cid":67,"u21ntid":1043517,"ntid":855952},{"code":"BY","name":"Belarus","cid":34,"u21ntid":1043518,"ntid":855919},{"code":"BE","name":"Belgium","cid":237,"u21ntid":1043519,"ntid":103},{"code":"BO","name":"Bolivia","cid":6,"u21ntid":1043520,"ntid":498680},{"code":"BA","name":"Bosnia and Herzegovina","cid":25,"u21ntid":1043521,"ntid":855910},{"code":"BR","name":"Brazil","cid":1,"u21ntid":1043522,"ntid":104},{"code":"BG","name":"Bulgaria","cid":11,"u21ntid":1043523,"ntid":498686},{"code":"CA","name":"Canada","cid":239,"u21ntid":1043524,"ntid":105},{"code":"CL","name":"Chile","cid":15,"u21ntid":1043525,"ntid":498690},{"code":"CN","name":"China","cid":21,"u21ntid":1043526,"ntid":768059},{"code":"CO","name":"Colombia","cid":17,"u21ntid":1043527,"ntid":498693},{"code":"CR","name":"Costa Rica","cid":37,"u21ntid":1043528,"ntid":855922},{"code":"HR","name":"Croatia","cid":24,"u21ntid":1043529,"ntid":61},{"code":"CY","name":"Cyprus","cid":32,"u21ntid":1043530,"ntid":855917},{"code":"CZ","name":"Czech Republic","cid":3,"u21ntid":1043531,"ntid":106},{"code":"DK","name":"Denmark","cid":231,"u21ntid":1043532,"ntid":107},{"code":"DO","name":"Dominican Republic","cid":49,"u21ntid":1043533,"ntid":855934},{"code":"EC","name":"Ecuador","cid":13,"u21ntid":1043534,"ntid":498688},{"code":"EG","name":"Egypt","cid":12,"u21ntid":1043535,"ntid":498687},{"code":"SV","name":"El Salvador","cid":55,"u21ntid":1043536,"ntid":855940},{"code":"EN","name":"England","cid":224,"u21ntid":1043537,"ntid":108},{"code":"EE","name":"Estonia","cid":252,"u21ntid":1043538,"ntid":109},{"code":"FO","name":"Faroe Islands","cid":31,"u21ntid":1043539,"ntid":855916},{"code":"FI","name":"Finland","cid":229,"u21ntid":1043540,"ntid":110},{"code":"FR","name":"France","cid":228,"u21ntid":1043541,"ntid":111},{"code":"GE","name":"Georgia","cid":58,"u21ntid":1043543,"ntid":855943},{"code":"DE","name":"Germany","cid":230,"u21ntid":1043544,"ntid":112},{"code":"GR","name":"Greece","cid":232,"u21ntid":1043545,"ntid":113},{"code":"GT","name":"Guatemala","cid":44,"u21ntid":1043546,"ntid":855929},{"code":"HN","name":"Honduras","cid":57,"u21ntid":1043547,"ntid":855942},{"code":"HU","name":"Hungary","cid":245,"u21ntid":1043548,"ntid":114},{"code":"IS","name":"Iceland","cid":10,"u21ntid":1043549,"ntid":498685},{"code":"IN","name":"India","cid":39,"u21ntid":1043550,"ntid":855924},{"code":"ID","name":"Indonesia","cid":251,"u21ntid":1043551,"ntid":117},{"code":"IR","name":"Iran","cid":41,"u21ntid":1043552,"ntid":855926},{"code":"IE","name":"Ireland","cid":249,"u21ntid":1043553,"ntid":115},{"code":"IL","name":"Israel","cid":2,"u21ntid":1043554,"ntid":116},{"code":"IT","name":"Italy","cid":226,"u21ntid":1043555,"ntid":118},{"code":"JO","name":"Jordan","cid":59,"u21ntid":1043556,"ntid":855944},{"code":"KZ","name":"Kazakhstan","cid":51,"u21ntid":1043557,"ntid":855936},{"code":"KE","name":"Kenya","cid":62,"u21ntid":1043558,"ntid":855947},{"code":"KW","name":"Kuwait","cid":61,"u21ntid":1043559,"ntid":855946},{"code":"KG","name":"Kyrgyzstan","cid":53,"u21ntid":1043560,"ntid":855938},{"code":"LV","name":"Latvia","cid":250,"u21ntid":1043561,"ntid":119},{"code":"LB","name":"Lebanon","cid":68,"u21ntid":1043562,"ntid":855953},{"code":"LI","name":"Liechtenstein","cid":42,"u21ntid":1043563,"ntid":855927},{"code":"LT","name":"Lithuania","cid":5,"u21ntid":1043564,"ntid":120},{"code":"LU","name":"Luxembourg","cid":38,"u21ntid":1043565,"ntid":855923},{"code":"DC","name":"MZ Country","cid":20,"u21ntid":1043572,"ntid":768061},{"code":"MK","name":"Macedonia","cid":33,"u21ntid":1043542,"ntid":855918},{"code":"MY","name":"Malaysia","cid":27,"u21ntid":1043566,"ntid":855912},{"code":"MT","name":"Malta","cid":30,"u21ntid":1043567,"ntid":855915},{"code":"MX","name":"Mexico","cid":4,"u21ntid":1043568,"ntid":121},{"code":"MD","name":"Moldova","cid":35,"u21ntid":1043569,"ntid":855920},{"code":"ME","name":"Montenegro","cid":70,"u21ntid":1043570,"ntid":855955},{"code":"MA","name":"Morocco","cid":43,"u21ntid":1043571,"ntid":855928},{"code":"NL","name":"Netherlands","cid":236,"u21ntid":1043573,"ntid":122},{"code":"NG","name":"Nigeria","cid":63,"u21ntid":1043574,"ntid":855948},{"code":"NI","name":"Northern Ireland","cid":69,"u21ntid":1043575,"ntid":855954},{"code":"NO","name":"Norway","cid":234,"u21ntid":1043576,"ntid":123},{"code":"PK","name":"Pakistan","cid":56,"u21ntid":1043577,"ntid":855941},{"code":"PA","name":"Panama","cid":29,"u21ntid":1043578,"ntid":855914},{"code":"PY","name":"Paraguay","cid":7,"u21ntid":1043579,"ntid":498681},{"code":"PE","name":"Peru","cid":16,"u21ntid":1043580,"ntid":498691},{"code":"PH","name":"Philippines","cid":40,"u21ntid":1043581,"ntid":855925},{"code":"PL","name":"Poland","cid":233,"u21ntid":1043582,"ntid":124},{"code":"PT","name":"Portugal","cid":243,"u21ntid":1043583,"ntid":125},{"code":"RO","name":"Romania","cid":247,"u21ntid":1043584,"ntid":126},{"code":"RU","name":"Russia","cid":9,"u21ntid":1043585,"ntid":498684},{"code":"SA","name":"Saudi Arabia","cid":60,"u21ntid":1043586,"ntid":855945},{"code":"SC","name":"Scotland","cid":254,"u21ntid":1043587,"ntid":127},{"code":"SN","name":"Senegal","cid":66,"u21ntid":1043588,"ntid":855951},{"code":"RS","name":"Serbia","cid":71,"u21ntid":1043589,"ntid":855956},{"code":"SG","name":"Singapore","cid":28,"u21ntid":1043590,"ntid":855913},{"code":"SK","name":"Slovakia","cid":23,"u21ntid":1043591,"ntid":855909},{"code":"SI","name":"Slovenia","cid":22,"u21ntid":1043592,"ntid":855807},{"code":"ZA","name":"South Africa","cid":18,"u21ntid":1043593,"ntid":498694},{"code":"KR","name":"South Korea","cid":48,"u21ntid":1043594,"ntid":855933},{"code":"ES","name":"Spain","cid":227,"u21ntid":1043595,"ntid":128},{"code":"SE","name":"Sweden","cid":205,"u21ntid":1043596,"ntid":129},{"code":"CH","name":"Switzerland","cid":235,"u21ntid":1043597,"ntid":130},{"code":"TH","name":"Thailand","cid":19,"u21ntid":1043598,"ntid":498695},{"code":"TT","name":"Trinidad & Tobago","cid":65,"u21ntid":1043599,"ntid":855950},{"code":"TN","name":"Tunisia","cid":50,"u21ntid":1043600,"ntid":855935},{"code":"TR","name":"Turkey","cid":253,"u21ntid":1043601,"ntid":131},{"code":"UA","name":"Ukraine","cid":26,"u21ntid":1043602,"ntid":855911},{"code":"AE","name":"United Arab Emirates","cid":47,"u21ntid":1043603,"ntid":855932},{"code":"US","name":"United States","cid":225,"u21ntid":1043604,"ntid":132},{"code":"UY","name":"Uruguay","cid":8,"u21ntid":1043605,"ntid":498683},{"code":"VE","name":"Venezuela","cid":14,"u21ntid":1043606,"ntid":498689},{"code":"VN","name":"Vietnam","cid":36,"u21ntid":1043607,"ntid":855921},{"code":"WL","name":"Wales","cid":248,"u21ntid":1043608,"ntid":133}]; const MASSIVE_COUNTRIES = ['BR', 'CN', 'AR', 'SE', 'PL', 'TR']; const PLAYERS_PER_PAGE = 10; const ORDERED_SKILL_KEYS = [ "speed", "stamina", "playIntelligence", "passing", "shooting", "heading", "keeping", "ballControl", "tackling", "aerialPassing", "setPlays", "experience" ]; try { const response = await fetch(FONT_URL); if (response.ok) { const fontCss = await response.text(); GM_addStyle(fontCss + STYLE); } else { throw new Error(`Failed to fetch font CSS, status: ${response.status}`); } } catch (error) { GM_addStyle(STYLE); } class Logger { constructor(container, flushInterval = 400) { this.container = container; this.flushInterval = flushInterval; this.queue = []; this.timeout = null; this.scheduled = false; } getTimestamp() { const now = new Date(); return `[${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}]`; } log(message, type = 'info') { this.queue.push({ message: `${this.getTimestamp()} ${message}`, type }); if (!this.scheduled) { this.scheduled = true; this.timeout = setTimeout(() => this.flush(), this.flushInterval); } } flush() { if (!this.queue.length || !this.container) { this.scheduled = false; return; } const fragment = document.createDocumentFragment(); this.queue.forEach(({ message, type }) => { const entry = document.createElement('div'); entry.className = `nt-search-log-entry ${type}`; entry.textContent = message; fragment.appendChild(entry); }); this.container.appendChild(fragment); this.container.scrollTop = this.container.scrollHeight; this.queue = []; if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; } this.scheduled = false; } } class HistoryManager { constructor(storageKey = 'MZ_NT_SEARCH_HISTORY') { this.storageKey = storageKey; } async getHistory() { const historyJson = await GM_getValue(this.storageKey, '[]'); return JSON.parse(historyJson); } async saveSearch(searchData) { let history = await this.getHistory(); history.unshift(searchData); await GM_setValue(this.storageKey, JSON.stringify(history)); } async deleteSearch(timestamp) { let history = await this.getHistory(); const newHistory = history.filter(entry => entry.timestamp !== timestamp); await GM_setValue(this.storageKey, JSON.stringify(newHistory)); } } class RequestQueue { constructor(maxConcurrent = 5, delay = 100) { this.queue = []; this.maxConcurrent = maxConcurrent; this.delay = delay; this.running = 0; this.processed = 0; } add(request) { return new Promise((resolve, reject) => { const wrappedRequest = async () => { try { await new Promise(res => setTimeout(res, this.delay)); const result = await request(); this.processed++; resolve(result); } catch (error) { reject(error); } finally { this.running--; this.processNext(); } }; this.queue.push(wrappedRequest); this.processNext(); }); } processNext() { while (this.running < this.maxConcurrent && this.queue.length > 0) { this.running++; const request = this.queue.shift(); request(); } } reset() { this.queue = []; this.running = 0; this.processed = 0; } } class ChunkProcessor { constructor(chunkSize = 25) { this.chunkSize = chunkSize; } async process(items, processFn, onChunkComplete) { const chunks = this.createChunks(items); let processed = 0; for (const chunk of chunks) { await Promise.all(chunk.map(processFn)); processed += chunk.length; if (onChunkComplete) { onChunkComplete(processed, items.length); } await new Promise(res => setTimeout(res, 50)); } } createChunks(items) { const chunks = []; for (let i = 0; i < items.length; i += this.chunkSize) { chunks.push(items.slice(i, i + this.chunkSize)); } return chunks; } } class NTPlayerParser { constructor(minRequirements) { this.minRequirements = minRequirements; this.logger = null; } parseSkills(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const rows = doc.querySelectorAll('.player_skills tr'); if (!rows.length) { return null; } const skills = {}; let totalBalls = 0; const totalBallsElement = doc.querySelector('td[title] span.bold'); if (totalBallsElement) { totalBalls = parseInt(totalBallsElement.textContent, 10) || 0; } let skillRows = Array.from(rows); if (skillRows.length > ORDERED_SKILL_KEYS.length) { skillRows = skillRows.slice(0, ORDERED_SKILL_KEYS.length); } skillRows.forEach((row, index) => { const valueCell = row.querySelector('.skillval'); if (!valueCell) { return; } const rawValue = valueCell.textContent.replace(/[()]/g, "").trim(); const value = parseInt(rawValue, 10); if (!isNaN(value)) { skills[ORDERED_SKILL_KEYS[index]] = value; } }); if (Object.keys(skills).length === 0) { return null; } ORDERED_SKILL_KEYS.forEach(key => { if (!(key in skills)) { skills[key] = 0; } }); if (!this.validateSkills(skills)) { return null; } return { skills, totalBalls }; } validateSkills(skills) { return Object.entries(this.minRequirements) .filter(([key]) => key in skills && typeof skills[key] === 'number') .every(([key, minValue]) => skills[key] >= minValue); } async fetchAndParsePlayer(playerId, ntid, cid) { const url = `https://www.managerzone.com/ajax.php?p=nationalTeams&sub=search&ntid=${ntid}&cid=${cid}&type=national_team&pid=${playerId}&sport=soccer`; try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const html = await response.text(); return this.parseSkills(html); } catch (error) { if (this.logger) { this.logger.log(`Error parsing player ${playerId}: ${error.message}`, 'error'); } return null; } } } class PlayerData { constructor(id, name, teamName, teamId, age, value, salary, totalBalls, skills) { this.id = id; this.name = name; this.teamName = teamName; this.teamId = teamId || null; this.age = age; this.value = value; this.salary = salary; this.totalBalls = totalBalls; this.skills = skills; } toJSON() { return { id: this.id, name: this.name, teamName: this.teamName, teamId: this.teamId, age: this.age, value: this.value, salary: this.salary, totalBalls: this.totalBalls, skills: this.skills, }; } toExcelRow() { const row = { 'ID': this.id, 'Name': this.name, 'Team': this.teamName, 'Age': this.age, 'Value': this.value, 'Salary': this.salary, 'Total Balls': this.totalBalls, }; ORDERED_SKILL_KEYS.forEach(key => { row[NTPlayerSearcher.prototype.formatSkillName(key)] = this.skills[key] || 0; }); return row; } } class NTPlayerSearcher { constructor() { this.requestQueue = new RequestQueue(5, 100); this.chunkProcessor = new ChunkProcessor(25); this.historyManager = new HistoryManager(); this.isInitialized = false; this.isInitializing = false; this.isSearching = false; this.searchValues = { speed: 0, stamina: 0, playIntelligence: 0, passing: 0, shooting: 0, heading: 0, keeping: 0, ballControl: 0, tackling: 0, aerialPassing: 0, setPlays: 0, experience: 0, minAge: 16, maxAge: 40, totalBalls: 9, country: '', countryData: null }; this.teamIds = new Set(); this.playerIds = new Map(); this.processedLeagues = 0; this.totalLeagues = 0; this.validPlayers = new Map(); this.floatingButton = null; this.logger = null; this.countries = []; this.userCountry = null; this.username = null; this.currentResultsPage = 1; this.resultsListeners = { prev: null, next: null, esc: null }; } async fetchTopPlayers(country, page = 0, isU21 = false) { try { const baseUrl = `https://mzlive.eu/mzlive.php?action=list&type=top100&mode=players&country=${country}&cy=EUR`; const url = isU21 ? `${baseUrl}&age=u21&page=${page}` : `${baseUrl}&page=${page}`; const response = await this.requestQueue.add(() => new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url, onload: res => resolve(res), onerror: err => reject(err), ontimeout: () => reject(new Error(`Timeout fetching Top100 page ${page}`)) }); }) ); const data = JSON.parse(response.responseText); const players = (data.players || []).filter(player => { return player.age >= this.searchValues.minAge && player.age <= this.searchValues.maxAge; }); const playerEntries = players.map(player => [ player.id.toString(), { id: player.id.toString(), name: player.name, teamName: player.team_name, teamId: player.team_id?.toString() || null, age: player.age, value: parseInt(player.value) || 0, salary: 0 } ]); this.playerIds = new Map([...this.playerIds, ...playerEntries]); return players.map(player => player.id.toString()); } catch (error) { if (this.logger) this.logger.log(`Error fetching Top100 (page ${page}): ${error.message}`, 'error'); return []; } } async fetchAllTop100Players(country) { const maxPages = MASSIVE_COUNTRIES.includes(country) ? 20 : 5; const isU21 = this.searchValues.maxAge <= 21; const pages = Array.from({ length: maxPages + 1 }, (_, i) => i); const chunkSize = 5; const results = []; if (this.logger) this.logger.log(`Fetching Top100 players...`); for (let i = 0; i < pages.length; i += chunkSize) { const chunk = pages.slice(i, i + chunkSize); const chunkResults = await Promise.all( chunk.map(page => this.fetchTopPlayers(country, page, isU21)) ); results.push(...chunkResults); await new Promise(res => setTimeout(res, 100)); } if (this.logger) this.logger.log(`Finished fetching Top100 players.`); return results.flat(); } async fetchCountriesList() { return Promise.resolve(COUNTRIES_DATA); } async fetchUserCountry() { const usernameElem = document.querySelector('#header-username'); if (!usernameElem) return { userCountry: null, username: null }; const username = usernameElem.textContent.trim(); try { const response = await fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&username=${username}`); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const text = await response.text(); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(text, "text/xml"); const parserError = xmlDoc.querySelector('parsererror'); if (parserError) throw new Error(`XML parsing error: ${parserError.textContent}`); const countryCode = xmlDoc.querySelector('UserData')?.getAttribute('countryShortname') || null; return { userCountry: countryCode, username: username }; } catch (error) { if (this.logger) this.logger.log(`Error fetching user data: ${error.message}`, 'error'); return { userCountry: null, username: username }; } } async checkUserRole(ntid, cid, username) { if (!ntid || !cid || !username) { if (this.logger) this.logger.log("Missing ntid, cid, or username for role check.", "warn"); return false; } const url = `https://www.managerzone.com/ajax.php?p=nationalTeams&sub=team&ntid=${ntid}&cid=${cid}&type=national_team&sport=soccer`; try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const profileLinks = doc.querySelectorAll('table.padding a[href*="/?p=profile&uid="]'); for (const link of profileLinks) { if (link.textContent.trim() === username) { if (this.logger) this.logger.log(`User confirmed as NC/NCA.`, 'info'); return true; } } if (this.logger) this.logger.log(`${username} is not NC/NCA.`, 'info'); return false; } catch (error) { if (this.logger) this.logger.log(`Error checking user role: ${error.message}`, 'error'); return false; } } async firstThingsFirst() { this.isInitializing = true; this.showLoading(); try { const [countries, { userCountry, username }] = await Promise.all([ this.fetchCountriesList(), this.fetchUserCountry() ]); this.countries = countries || []; this.userCountry = userCountry; this.username = username; let isAuthorized = false; let userCountryData = null; if (this.userCountry && this.username && this.countries.length > 0) { userCountryData = this.countries.find(c => c.code === this.userCountry); if (userCountryData) { const isU21Page = window.location.href.includes("type=u21"); const ntid = isU21Page ? userCountryData.u21ntid : userCountryData.ntid; isAuthorized = await this.checkUserRole(ntid, userCountryData.cid, this.username); } } const searchContainer = document.querySelector('.nt-search-container'); const countrySelect = searchContainer?.querySelector('select[name="country"]'); const searchButton = searchContainer?.querySelector('.nt-search-button'); const allSelects = searchContainer?.querySelectorAll('select'); if (isAuthorized && userCountryData) { this.searchValues.country = this.userCountry; this.searchValues.countryData = { ntid: userCountryData.ntid, u21ntid: userCountryData.u21ntid, cid: userCountryData.cid }; if (this.logger) this.logger.log(`Welcome, ${this.username}.`, 'info'); if (countrySelect) { countrySelect.innerHTML = this.generateCountryOptions(); countrySelect.disabled = false; } if (allSelects) allSelects.forEach(s => s.disabled = false); if (searchButton) searchButton.disabled = false; } else { if (this.logger) this.logger.log("Search tool disabled.", "warn"); if (allSelects) allSelects.forEach(s => s.disabled = true); if (searchButton) searchButton.disabled = true; if (countrySelect) { countrySelect.innerHTML = this.generateCountryOptions(); countrySelect.disabled = true; } } } catch (error) { if (this.logger) this.logger.log(`Initialization failed: ${error.message}`, 'error'); alert(`Initialization failed. Please check the browser console (F12) for more details.`); } finally { this.isInitialized = true; this.isInitializing = false; this.hideLoading(); if (this.logger) this.logger.flush(); } } initialize() { this.appendSearchTab(); this.setUpEvents(); } showLoading() { if (this.floatingButton) this.floatingButton.classList.add('loading'); } hideLoading() { if (this.floatingButton) this.floatingButton.classList.remove('loading'); } async getLeagueIds(countryCode) { try { const response = await this.requestQueue.add(() => new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `https://mzlive.eu/mzlive.php?action=list&type=leagues&country=${countryCode}`, onload: res => resolve(res), onerror: err => reject(err), ontimeout: () => reject(new Error('Timeout fetching leagues')) }); }) ); const leagues = JSON.parse(response.responseText); const maxDivision = MASSIVE_COUNTRIES.includes(countryCode) ? 6 : 3; return leagues.filter(league => { const name = league.name.toLowerCase(); if (name.startsWith('div')) { const divLevel = parseInt(name.split('.')[0].replace('div', '')); return isNaN(divLevel) || divLevel <= maxDivision; } return true; }).map(league => league.id); } catch (error) { if (this.logger) this.logger.log(`Error fetching leagues: ${error.message}`, 'error'); return []; } } async getTeamIds(leagueId) { try { const response = await this.requestQueue.add(() => fetch(`https://www.managerzone.com/xml/team_league.php?sport_id=1&league_id=${leagueId}`)); if (!response.ok) throw new Error(`HTTP error! status: ${response.status} for league ${leagueId}`); const text = await response.text(); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(text, "text/xml"); const parserError = xmlDoc.querySelector('parsererror'); if (parserError) throw new Error(`XML parsing error for league ${leagueId}: ${parserError.textContent}`); const teams = xmlDoc.getElementsByTagName('Team'); return Array.from(teams).map(team => team.getAttribute('teamId')); } catch (error) { if (this.logger) this.logger.log(`Error fetching teams for league ${leagueId}: ${error.message}`, 'error'); return []; } } async processLeagueBatch(leagueIds) { if (!leagueIds || leagueIds.length === 0) { if (this.logger) this.logger.log("No league IDs to process.", "warn"); return; } if (this.logger) this.logger.log(`Processing ${leagueIds.length} leagues...`); await this.chunkProcessor.process( leagueIds, async (leagueId) => { try { const teamIds = await this.getTeamIds(leagueId); if (teamIds && teamIds.length > 0) teamIds.forEach(id => this.teamIds.add(id)); this.processedLeagues++; } catch (error) { if (this.logger) this.logger.log(`Failed to process league ${leagueId}: ${error.message}`, 'error'); } } ); if (this.logger) this.logger.log(`Finished processing leagues. Found ${this.teamIds.size} unique teams.`); } async fetchPlayerList(teamId) { try { const response = await this.requestQueue.add(() => fetch(`https://www.managerzone.com/xml/team_playerlist.php?sport_id=1&team_id=${teamId}`)); if (!response.ok) throw new Error(`HTTP error! status: ${response.status} for team ${teamId}`); const text = await response.text(); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(text, "text/xml"); const parserError = xmlDoc.querySelector('parsererror'); if (parserError) throw new Error(`XML parsing error for team ${teamId}: ${parserError.textContent}`); const teamPlayers = xmlDoc.querySelector('TeamPlayers'); if (!teamPlayers) return; const teamName = teamPlayers.getAttribute('teamName') || `Team ${teamId}`; const actualTeamId = teamPlayers.getAttribute('teamId') || teamId; const players = xmlDoc.getElementsByTagName('Player'); const targetCountry = this.searchValues.country.toLowerCase(); Array.from(players).forEach(player => { const age = parseInt(player.getAttribute('age')); const countryCode = (player.getAttribute('countryShortname') || '').toLowerCase(); if (age >= this.searchValues.minAge && age <= this.searchValues.maxAge && countryCode === targetCountry) { const playerId = player.getAttribute('id'); const playerName = player.getAttribute('name'); const value = parseInt(player.getAttribute('value')) || 0; const salary = parseInt(player.getAttribute('salary')) || 0; if (playerId && playerName) { this.playerIds.set(playerId, { id: playerId, name: playerName, teamName, teamId: actualTeamId, age, value, salary }); } } }); } catch (error) { if (this.logger) this.logger.log(`Error fetching players for team ${teamId}: ${error.message}`, 'error'); } } async processTeamBatch(teamIds) { if (!teamIds || teamIds.length === 0) { if (this.logger) this.logger.log("No team IDs to process.", "warn"); return; } const totalTeams = teamIds.length; let processedTeams = 0; if (this.logger) this.logger.log(`Processing ${totalTeams} teams...`); await this.chunkProcessor.process( teamIds, async (teamId) => { await this.fetchPlayerList(teamId); processedTeams++; if (processedTeams % 100 === 0 || processedTeams === totalTeams) { if (this.logger) this.logger.log(`Team processing: ${processedTeams}/${totalTeams}`); } } ); if (this.logger) this.logger.log(`Finished processing teams. Found ${this.playerIds.size} potential players.`); } async searchForPlayers() { if (!this.searchValues.country || !this.searchValues.countryData) { if (this.logger) this.logger.log('Country not selected or country data missing.', 'error'); alert('Please ensure a country is selected.'); return; } this.teamIds = new Set(); this.playerIds = new Map(); this.processedLeagues = 0; this.totalLeagues = 0; this.validPlayers = new Map(); this.requestQueue.reset(); this.currentResultsPage = 1; const countryCode = this.searchValues.country; if (this.logger) this.logger.log(`Starting search for country: ${countryCode}`); try { if (this.searchValues.maxAge > 18) { await this.fetchAllTop100Players(countryCode); if (this.logger) this.logger.log(`Found ${this.playerIds.size} players from Top100.`); } const leagueIds = await this.getLeagueIds(countryCode); this.totalLeagues = leagueIds.length; if (this.totalLeagues === 0 && this.playerIds.size === 0) { if (this.logger) this.logger.log(`No leagues found and no top players matched. Stopping search.`, 'warn'); return; } await this.processLeagueBatch(leagueIds); await this.processTeamBatch(Array.from(this.teamIds)); const isU21Page = window.location.href.includes("type=u21"); const ntid = isU21Page ? this.searchValues.countryData.u21ntid : this.searchValues.countryData.ntid; const cid = this.searchValues.countryData.cid; const ntPlayerParser = new NTPlayerParser(this.searchValues); ntPlayerParser.logger = this.logger; const playerEntries = Array.from(this.playerIds.entries()); if (playerEntries.length === 0) { if (this.logger) this.logger.log('No potential players found after gathering IDs.', 'warn'); return; } if (this.logger) this.logger.log(`Processing skills for ${playerEntries.length} players...`); await this.chunkProcessor.process( playerEntries, async ([playerId, playerData]) => { try { const parsedData = await ntPlayerParser.fetchAndParsePlayer(playerId, ntid, cid); if (parsedData && parsedData.totalBalls >= this.searchValues.totalBalls) { this.validPlayers.set(playerId, new PlayerData(playerId, playerData.name, playerData.teamName, playerData.teamId, playerData.age, playerData.value, playerData.salary, parsedData.totalBalls, parsedData.skills)); } } catch (parseError) { if (this.logger) this.logger.log(`Error processing player ${playerId}: ${parseError.message}`, 'error'); } }, (processed, total) => { if (this.logger) { this.logger.log(`Processing player skills: ${processed}/${total}`); } } ); await new Promise(resolve => setTimeout(resolve, 200)); const finalCount = this.validPlayers.size; if (this.logger) this.logger.log(`Search complete: found ${finalCount} players matching all criteria.`); if (finalCount > 0) { const searchData = { timestamp: new Date().toISOString(), filters: JSON.parse(JSON.stringify(this.searchValues)), results: Array.from(this.validPlayers.values()).map(p => p.toJSON()) }; await this.historyManager.saveSearch(searchData); } const resultsButton = document.querySelector('.nt-search-results-button'); if (resultsButton) resultsButton.style.display = finalCount > 0 ? "inline-block" : "none"; return Array.from(this.validPlayers.keys()); } catch (error) { if (this.logger) this.logger.log(`Search error: ${error.message}`, 'error'); alert(`An error occurred during the search: ${error.message}. Check console for details.`); } finally { if (this.logger) this.logger.flush(); } } async performSearch() { if (this.isSearching) { if (this.logger) this.logger.log("Search already in progress.", "warn"); return; } if (!this.searchValues.country || !this.searchValues.countryData) { alert("Please select a country before searching."); return; } this.isSearching = true; const internalSearchButton = document.querySelector('.nt-search-container .nt-search-button'); if (internalSearchButton) internalSearchButton.disabled = true; const logContainer = document.querySelector('.nt-search-log'); const resultsButton = document.querySelector('.nt-search-results-button'); this.showLoading(); if (logContainer) logContainer.innerHTML = ''; if (resultsButton) resultsButton.style.display = 'none'; if (!this.logger || !this.logger.container) { const logCont = document.querySelector('.nt-search-log'); this.logger = logCont ? new Logger(logCont) : { log: () => {}, flush: () => {} }; } try { await this.searchForPlayers(); } catch (error) { if (this.logger) this.logger.log(`Unhandled search error: ${error.message}`, 'error'); alert(`An unexpected error occurred: ${error.message}. Check console for details.`); } finally { this.isSearching = false; if (internalSearchButton) internalSearchButton.disabled = false; this.hideLoading(); if (this.logger) this.logger.flush(); } } getFiltersAppliedText(filters = this.searchValues) { const applied = []; const countryName = this.countries.find(c => c.code === filters.country)?.name || filters.country; if (countryName) applied.push(`Country: ${countryName}`); applied.push(`Age: ${filters.minAge} - ${filters.maxAge}`); applied.push(`Min Total Balls: ${filters.totalBalls}`); ORDERED_SKILL_KEYS.forEach(skill => { if (filters[skill] > 0) { applied.push(`Min ${this.formatSkillName(skill)}: ${filters[skill]}`); } }); return applied.join('; '); } createPaginationControls(page, totalPages) { const container = document.createElement('div'); container.className = 'nt-search-results-pagination'; if (totalPages > 1) { const prevBtn = document.createElement('button'); prevBtn.className = 'nt-search-pagination-button'; prevBtn.textContent = 'Previous'; prevBtn.disabled = page === 1; prevBtn.dataset.action = "prev"; const pageInfo = document.createElement('span'); pageInfo.className = 'nt-search-pagination-info'; pageInfo.textContent = `Page ${page} of ${totalPages}`; const nextBtn = document.createElement('button'); nextBtn.className = 'nt-search-pagination-button'; nextBtn.textContent = 'Next'; nextBtn.disabled = page === totalPages; nextBtn.dataset.action = "next"; container.appendChild(prevBtn); container.appendChild(pageInfo); container.appendChild(nextBtn); } return container; } renderResultsPage(players, page) { const playersContainer = document.querySelector('.nt-search-players-container'); const paginationTopContainer = document.querySelector('.nt-search-results-pagination.top'); const paginationBottomContainer = document.querySelector('.nt-search-results-pagination.bottom'); const modalContent = document.querySelector('.nt-search-results-content'); if (!playersContainer || !paginationTopContainer || !paginationBottomContainer || !modalContent) return; this.currentResultsPage = page; playersContainer.textContent = ''; paginationTopContainer.textContent = ''; paginationBottomContainer.textContent = ''; const playersArray = players.sort((a, b) => b.totalBalls - a.totalBalls); const totalPages = Math.ceil(playersArray.length / PLAYERS_PER_PAGE); const startIndex = (page - 1) * PLAYERS_PER_PAGE; const pagePlayers = playersArray.slice(startIndex, startIndex + PLAYERS_PER_PAGE); this.removePaginationListeners(); this.resultsListeners.prev = () => { if (this.currentResultsPage > 1) { this.renderResultsPage(players, this.currentResultsPage - 1); if (modalContent) modalContent.scrollTop = 0; } }; this.resultsListeners.next = () => { if (this.currentResultsPage < totalPages) { this.renderResultsPage(players, this.currentResultsPage + 1); if (modalContent) modalContent.scrollTop = 0; } }; const topControls = this.createPaginationControls(page, totalPages); const bottomControls = this.createPaginationControls(page, totalPages); paginationTopContainer.appendChild(topControls); paginationBottomContainer.appendChild(bottomControls); this.addPaginationListeners(paginationTopContainer); this.addPaginationListeners(paginationBottomContainer); const fragment = document.createDocumentFragment(); pagePlayers.forEach(player => { let skillsHTML = ORDERED_SKILL_KEYS.map(skillKey => { const value = player.skills[skillKey] || 0; const skillName = this.formatSkillName(skillKey); return `
(${value})${filtersText}
| Name | Age | Total Balls | ${headerCells}
|---|