MZ Axis

Enhances the ManagerZone tactics interface: copy/paste formations, display rival formations with additional player data, and adjust player positions in real time (mouse + touchscreen), and now you can save your tactics.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         MZ Axis
// @namespace    http://tampermonkey.net/
// @version      0.4.1
// @description  Enhances the ManagerZone tactics interface: copy/paste formations, display rival formations with additional player data, and adjust player positions in real time (mouse + touchscreen), and now you can save your tactics.
// @author       jrcl
// @match        https://www.managerzone.com/?p=tactics*
// @grant        GM_xmlhttpRequest
// @grant        GM_log
// @grant        GM_addStyle
// @require      https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.4/pako.min.js
// @license      MIT
// ==/UserScript==

GM_addStyle(`
.fieldpos::after {
    content: attr(data-x) " " attr(data-y);
    position: absolute;
    bottom: -14px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 8px;
    color: #fff;
    pointer-events: none;
    white-space: nowrap;
    text-align: center;
    font-family: monospace;
}

/* error input */
.mz-error-input{
    background-color:#ffd6d6;
    transition: background-color 0.2s ease;
}
`);


(function() {
    'use strict';

    let _x = '--';
    let _y = '--';
    let inputX;
    let inputY;

    let formation = [];
    let rivalFormation = [];
    let rivalTeam = null;
    const conversionRatesToUSD = {USD: 1,R$: 2.827003,SEK: 7.4234,EUR: 0.80887,GBP: 0.555957,DKK: 6.00978,NOK: 6.921908,CHF: 1.265201,CAD: 1.3003,AUD: 1.309244,ILS: 4.378812,MXN: 10.82507,ARS: 2.807162,BOB: 7.905644,PYG: 5671.047,UYU: 28.88898,RUB: 28.21191,PLN: 3.801452,ISK: 71.15307,BGN: 1.576971,ZAR: 5.999531,THB: 43.46507,SIT: 190.539,SKK: 29.75788,JPY: 123.7233,INR: 43.66706,MZ: 7.4234,MM: 7.4234,点: 7.4234,};

    if(isSoccer()) {

        createRivalButton();
        createCoordinatesPanel();
        updateCoordinatesPanel();

        initPlayers();
        observePitchChanges();

        document.addEventListener("keydown", setKeys);
    }


    // Función para crear y configurar el botón de rival
    function createRivalButton() {
        // Obtener el primer span dentro de 'formation-container'
        const firstSpanOnFContainer = document.querySelector('#formation-container span');
        if (!firstSpanOnFContainer) return;

        // Crear y configurar el botón para la táctica del rival
        const toAddRivalBtn = document.createElement('button');
        toAddRivalBtn.id = "rivalBtn";
        toAddRivalBtn.textContent = "xmlRival";
        toAddRivalBtn.style.color = "blue";

        // Agregar el botón al contenedor
        firstSpanOnFContainer.appendChild(toAddRivalBtn);

        // Agregar eventos
        toAddRivalBtn.addEventListener('click', cfgRivalFormation);
        toAddRivalBtn.addEventListener('mouseover', showAdditionalInfoRivalPlayers);
        toAddRivalBtn.addEventListener('touchstart', e => { e.preventDefault(); showAdditionalInfoRivalPlayers(); });
    }

    function observePitchChanges() {

        const pitch = document.getElementById("pitch");
        if (!pitch) return;

        const observer = new MutationObserver((mutations) => {

            for (const m of mutations) {

                // Nuevos jugadores añadidos
                if (m.type === "childList") {

                    for (const node of m.addedNodes) {

                        if (!(node instanceof HTMLElement)) continue;

                        if (node.classList?.contains("fieldpos")) {

                            setupPlayers();
                            return;

                        }

                    }

                }

                // Cambios en style (movimiento)
                if (m.type === "attributes" && m.attributeName === "style") {

                    const el = m.target;

                    if (el.classList?.contains("fieldpos")) {

                        updatePlayerCoords(el);

                        const active = getActivePlayer();
                        if (active === el) {
                            setCoordsLabel({ currentTarget: active });
                        }

                    }

                }

                // Cambios en class (selección)
                if (m.type === "attributes" && m.attributeName === "class") {

                    const el = m.target;

                    if (el.classList?.contains("fieldpos")) {

                        const active = getActivePlayer();

                        if (!active) {
                            _x = '--';
                            _y = '--';
                        } else {
                            setCoordsLabel({ currentTarget: active });
                            attachArrowControlListeners();
                        }

                        updateCoordinatesPanel();

                    }

                }

            }

        });

        observer.observe(pitch, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ["style", "class"]
        });

    }

    function waitForElement(selector){

        return new Promise(resolve => {

            const el = document.querySelector(selector);
            if(el) return resolve(el);

            const observer = new MutationObserver(()=>{

                const el = document.querySelector(selector);

                if(el){
                    observer.disconnect();
                    resolve(el);
                }

            });

            observer.observe(document.body,{
                childList:true,
                subtree:true
            });

        });

    }

    function attachArrowControlListeners() {
        const arrowControls = document.querySelectorAll('.arrow-controls');

        arrowControls.forEach(control => {
            const buttons = control.querySelectorAll('.left-button, .right-button, .up-button, .down-button');
            buttons.forEach(button => {
                if (!button.dataset.listenerAttached) {
                    button.addEventListener('click', () => {
                        const activePlayer = getActivePlayer();
                        if (activePlayer) {
                            setCoordsLabel({ currentTarget: activePlayer });
                        }
                    });
                    button.dataset.listenerAttached = "true";
                }
            });
        });
    }

    function showAdditionalInfoRivalPlayers() {
        const addInfo = document.getElementsByClassName('additionalInfoRivalPlayer');
        for (let i = addInfo.length - 1; i >= 0; i--) {
            addInfo[i].classList.toggle('hidden');
        }
    }

    function initPlayers() {

        const container = document.getElementById('pitch');
        if (!container) return;

        if (container.dataset.mzAxisInit) return;
        container.dataset.mzAxisInit = "true";

        container.addEventListener('click', handlePlayerEvent);
        container.addEventListener('keydown', handlePlayerEvent);

        function handlePlayerEvent(e) {

            const player = e.target.closest('.fieldpos.fieldpos-ok.ui-draggable');

            if (!player) return;

            setCoordsLabel({ currentTarget: player });

            const validX = isInRange(inputX.value, 'x');
            const validY = isInRange(inputY.value, 'y');

            // manejar warnings
            validX ? removeWarning(inputX) : addWarning(inputX);
            validY ? removeWarning(inputY) : addWarning(inputY);
        }

        setupPlayers();
    }

    function getActivePlayer() {
        return document.querySelector('.fieldpos.ui-selected');
    }

    function getFieldPlayers() {
        return document.querySelectorAll('.fieldpos.fieldpos-ok.ui-draggable:not(.substitute):not(.goalkeeper)');
    }

    function setupPlayers() {

        const players = document.querySelectorAll('.fieldpos.ui-draggable');

        players.forEach(player => {

            // evitar reinicializar
            if (player.dataset.initialized) return;
            player.dataset.initialized = "true";
            updatePlayerCoords(player);



            $(player).draggable({
                drag: function(event, ui) {


                    const player = this;

                    const pos = pitchToCoords(ui.position.left, ui.position.top);

                    player.dataset.x = pos.x;
                    player.dataset.y = pos.y;

                    _x = pos.x;
                    _y = pos.y;

                    updateCoordinatesPanel();
                }
            });

            let originalStop = $(player).draggable("option", "stop");

            $(player).draggable("option", "stop", function(event, ui) {
                if (originalStop) originalStop.call(this, event, ui);
                updatePlayerCoords(player);
                _x = player.dataset.x;
                _y = player.dataset.y;
                updateCoordinatesPanel();
            });

        });
    }

    function setCoordsLabel(event) {

        const player = event.currentTarget;

        getOffset(player);

        updateCoordinatesPanel();
    }

    function getOffset( el ) {
        const pos = pitchToCoords(el.offsetLeft, el.offsetTop);
        _x = pos.x;
        _y = pos.y;
    }

    function updateCoordinatesPanel(){
        inputX.value = _x;
        inputY.value = _y;

        const disabled = (_x === '--');
        inputX.disabled = disabled;
        inputY.disabled = disabled;
    }

    function copyFormation() {

        const coords = getCurrentCoords();
        if(!coords) return;

        const txtCoord = coords.map(p => `${p.x},${p.y}`).join("\n");

        navigator.clipboard.writeText(txtCoord)
            .then(() => {
            console.log('Formation copied to the clipboard');
            alert('Formation copied to the clipboard');
        })
            .catch(err => {
            console.error('Error copying to the clipboard:', err);
        })
        //console.log(txtCoord);
    }

    function getCurrentCoords(){

        const players = [...getFieldPlayers()];

        if(players.length !== 10){
            alert("Not Valid Formation");
            return null;
        }

        const coords = players.map(p => ({
            x: Number(p.dataset.x),
            y: Number(p.dataset.y)
        }));

        // ordenar por Y (arriba→abajo) y luego por X (izquierda→derecha)
        coords.sort((a,b)=> a.y - b.y || a.x - b.x);

        return coords;

    }

    function saveTactic(){

        const coords = getCurrentCoords();
        if(!coords) return;

        const name = prompt("Tactic name:");
        if(!name) return;

        const text = name + "\n" + coords.map(p => `${p.x},${p.y}`).join("\n");

        showNote(2026,'01','01',1);

        setTimeout(()=>{

            const textarea = document.querySelector("textarea[name='cal_message']");
            if(!textarea) return;

            textarea.value = text;

            $("#calendar_add_not_btn").triggerHandler("click");

        },500);

    }

    async function loadTactics(){

        $("#calendar-new").datepicker("setDate", new Date(2026,0,1));

        setTimeout(async ()=>{

            [...document.querySelectorAll("#calendar-new td[data-handler='selectDay']")]
            .find(td => td.textContent.trim() === "1")
            .click();

            await waitForElement("textarea[id^='note']");

            const notes = [...document.querySelectorAll("textarea[id^='note']")];

            const tactics = [];

            notes.forEach(n => {

                const noteID = n.id.replace("note","");

                const lines = n.value
                    .split("\n")
                    .map(l=>l.trim())
                    .filter(Boolean);

                if(lines.length >= 11){

                    const name = lines[0];
                    const coords = lines.slice(1,11);

                    tactics.push({ name, coords, noteID });

                }

            });

            powerboxClose('calendar-administration');

            showTacticsModal(tactics);

        },300);

    }

    function showTacticsModal(tactics){

        let modal = document.getElementById("mzTacticModal");

        if(modal) modal.remove();

        modal = document.createElement("div");
        modal.id = "mzTacticModal";

        modal.style.position = "fixed";
        modal.style.top = "120px";
        modal.style.left = "50%";
        modal.style.transform = "translateX(-50%)";
        modal.style.background = "#f2f2f2";
        modal.style.border = "1px solid #666";
        modal.style.padding = "10px";
        modal.style.zIndex = "9999";
        modal.style.minWidth = "220px";

        modal.style.maxHeight = "70vh";
        modal.style.overflowY = "auto";

        let html = `<div style="font-weight:bold;margin-bottom:8px;">Tactics Library</div>`;

        tactics.forEach((t,i)=>{
            html += `
            <div style="display:flex;justify-content:space-between;
            padding:4px;border-bottom:1px solid #ddd;">

            <span class="mzTacticItem"
            data-index="${i}"
            style="cursor:pointer;">
            ${t.name}
            </span>

            <span class="mzDeleteTactic"
            data-index="${i}"
            style="cursor:pointer;color:red;">
            🗑
            </span>

            </div>`;
        });

        html += `
        <div style="text-align:center;margin-top:8px;">
            <button id="mzCloseTacticModal">Close</button>
        </div>
        `;

        modal.innerHTML = html;
        document.body.appendChild(modal);

        modal.querySelectorAll(".mzTacticItem").forEach(el=>{

            el.onclick = function(){

                const t = tactics[this.dataset.index];

                loadTactic(t);

                modal.remove();

            };

        });


        modal.querySelectorAll(".mzDeleteTactic").forEach(el=>{

            el.onclick = function(){

            const t = tactics[this.dataset.index];

            if(!confirm("Delete tactic: "+t.name+" ?")) return;

            confirm_delete_note(
                t.noteID,
                '2026-01-01 00:00:00',
                2026,
                '01',
                1
            );

            modal.remove();

            setTimeout(()=>{
                powerboxClose('calendar-administration');
                loadTactics(); // recargar lista
            },400);

            };

        });

        document.getElementById("mzCloseTacticModal")
        .onclick = ()=> modal.remove();
    }

    function loadTactic(tactic){

        formation = [];

        tactic.coords.forEach(c => {

            let [x,y] = c.split(",");

            const pos = coordsToPitch(Number(x), Number(y));
            formation.push([ pos.left, pos.top ]);
        });

        // Optional because it’s already saved in sorted order
        formation = sortFormation(formation);
        setFormation();
    }

    function sortFormation(arr){
        return arr.sort((a,b) => b[1] - a[1] || a[0] - b[0]);
    }

    function pasteFormation() {
        navigator.clipboard.readText()
          .then(text => {
            console.log('Clipboard text:', text)
            // is xml content?
            if (text.search("xml") != -1) {
                // parse clipboard to xml file
                console.log('Cartesian coordinate system from XML file');
                let parser = new DOMParser();
                let xmlDoc = parser.parseFromString(text,"text/xml");
                
                formation = [];
                let pos = xmlDoc.getElementsByTagName("Pos")
                for (let i = 1; i < pos.length; i++) {
                    formation.push([Number(pos[i].getAttribute("x")) - 7, Number(pos[i].getAttribute("y")) - 9]);
                }

                formation = sortFormation(formation); 
                // formation.sort(function(a,b) { if (b[1] == a[1]) return a[0] - b[0]; else return b[1] - a[1]; })
                setFormation();
            } else if (text.trim().split('\n').length == 10) { // is there 10 coord?
                console.log('Cartesian coordinate system from Clipboard');
                let coorXY = text.trim().split('\n')

                formation = [];
                for (let pair of coorXY) {
                    let [xAxis,yAxis] = pair.split(',');
                    const pos = coordsToPitch( Number(xAxis), Number(yAxis) );
                    formation.push([ pos.left, pos.top]);
                    // formation.push([Number(xAxis) + 97, 155 - Number(yAxis)]);
                }

                formation = sortFormation(formation); 
                //formation.sort(function(a,b) { if (b[1] == a[1]) return a[0] - b[0]; else return b[1] - a[1]; })
                setFormation();
            } else if (formation.length == 10) {
                console.log('Cartesian coordinate system from internal var');
                setFormation();
            } else {
                console.log("Nothing to do");
            }


          })
          .catch(err => {
            console.error('Error reading from the clipboard:', err)
          })
    }

    function setFormation() {
        let player = getFieldPlayers();

        if (formation.length == player.length) {
            // get an order
            let oldFormation = [];
            for (let i = 0; i < player.length; i++) {
                oldFormation.push([Number((player[i].style.left).slice(0,-2)), Number((player[i].style.top).slice(0,-2)), i]);
            }

            oldFormation = sortFormation(oldFormation); 
            //oldFormation.sort(function(a,b) { if (b[1] == a[1]) return a[0] - b[0]; else return b[1] - a[1]; })

            for (let i = 0; i < formation.length; i++) {
                player[oldFormation[i][2]].style.left = formation[i][0] + 'px';
                player[oldFormation[i][2]].style.top = formation[i][1] + 'px';
            }
            alert('Formation successfully copied');
            formation = []; // empty formation
        }
    }

    function cfgRivalFormation() {
        let elBoton = document.getElementById('rivalBtn');

        if(elBoton.innerHTML == "xmlRival") {
            pasteRivalFormation();
        } else {
            elBoton.style.color = "blue";
            elBoton.innerHTML = "xmlRival";

            let rivalPlayers = document.getElementById('rivalPlayers');
            if(rivalPlayers) {
                rivalPlayers.parentElement.removeChild(rivalPlayers);
            }

            navigator.clipboard.writeText('')
            .then(() => {
                console.log('Clipboard Clear');
            })
            .catch(err => {
                console.error('Error clearing the clipboard:', err);
            })
        }
    }

    function pasteRivalFormation(){
        navigator.clipboard.readText()
          .then(text => {
            // is xml content?
            if (text.search("xml") != -1) {
                console.log('Clipboard text:', text)
                // parse clipboard to xml file
                console.log('Cartesian coordinate system from XML file');
                let parser = new DOMParser();
                let xmlDoc = parser.parseFromString(text,"text/xml");
                
                rivalFormation = [];
                let pos = xmlDoc.getElementsByTagName("Pos")

                // x, y, shirtno, value, height, weight, age, countryShortname
                for (let i = 0; i < pos.length; i++) {
                    rivalFormation.push([Number(pos[i].getAttribute("x")) - 7, Number(pos[i].getAttribute("y")) - 9, '9', '', '', '', '', '' ]);
                }
                
                rivalFormation = sortFormation(rivalFormation); 
                // rivalFormation.sort(function(a,b) { if (b[1] == a[1]) return a[0] - b[0]; else return b[1] - a[1]; })

                rivalTeam = xmlDoc.querySelector("Team");
                rivalTeam.setAttribute("tactic", rivalTeam.getAttribute('tactics'));
                setRivalFormation();

            } else if (text != null && text != "") {
                // is valid id number
                
                let matchId = prompt("Please enter your Match ID [mid]:", (text.trim().length < 14 && !isNaN(text.trim()))? text.trim():"No IDea");

                if (matchId == null) {
                  console.log("User cancel this");  
                } else if(matchId == "" || matchId == "No IDea" || matchId.length < 5 || matchId.length > 14 || isNaN(matchId.trim()) ) {
                  console.log("User dont have any idea about this or not valid match id");
                } else {
                    matchId = matchId.trim();
                    console.log(`Your matchId is:${matchId}`);
                    getDataFromXML(matchId);
                }
            } else if (rivalFormation.length > 0) {
                console.log('Formation from system on internal var');
                setRivalFormation();
            } else {
                console.log("Nothing to do");
            }

          })
          .catch(err => {
            console.error('Error reading from the clipboard:', err)
          })
    }

    function getDataFromXML(matchId) {
        let statsUrl = `http://download06.managerzone.com/data/soccer/${matchId.slice(-1)}/${matchId.charAt(matchId.length-2)}/stats${matchId}.xml.gz`;
        GM_xmlhttpRequest({
            method: 'GET',
            // url: 'http://download06.managerzone.com/data/soccer/7/1/stats1486585817.xml.gz',
            url: statsUrl,
            responseType: 'arraybuffer',  // We want to handle the binary data (arraybuffer)
            onload: function(response) {
                if (response.status === 200) {
                    try {
                        // Decompress the .gz file using pako
                        const decompressed = pako.ungzip(new Uint8Array(response.response), { to: 'string' });

                        // Parse the XML data
                        const parser = new DOMParser();
                        const xmlDoc = parser.parseFromString(decompressed, 'text/xml');

                        let teams = xmlDoc.documentElement.getElementsByTagName("Team");

                        let ishome = 0;
                        if (confirm(`Press [ OK ] if your rival is:\t${teams[0].getAttribute('name')}\nPress [ Cancel ] if your rival is:\t${teams[1].getAttribute('name')}`)) {
                          ishome = 1;
                        } else {
                          ishome = 0;
                        }

                        rivalTeam = ishome ? teams[0] : teams[1];
                        console.log(rivalTeam.getAttribute('name'));

                        rivalFormation = [];
                        // x, y, shirtno, value, height, weight, age, countryShortname
                        let players = xmlDoc.querySelectorAll(`Player[teamId="${rivalTeam.getAttribute('id')}"][origin*=","]`);
                        for (let i = 0; i < players.length; i++) {
                            let origin = players[i].getAttribute('origin');
                            let xy = origin.split(",");
                            rivalFormation.push([StatsToPos_X(xy[0], ishome) - 7, StatsToPos_Y(xy[1], ishome) - 9, players[i].getAttribute('shirtno'), '', '', '', '', '' ]);
                        }

                        // Additional Data
                        let squadTeamUrl = `http://www.managerzone.com/xml/team_playerlist.php?sport_id=1&team_id=${rivalTeam.getAttribute('id')}`
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: squadTeamUrl,
                            onload: function(response) {
                                const parser = new DOMParser();
                                const xmlDoc = parser.parseFromString(response.responseText, "text/xml");
                                let teamPlayers = xmlDoc.querySelector('TeamPlayers');
                                for (let i = 0; i < players.length; i++) {
                                    let player = xmlDoc.querySelector(`Player[id="${players[i].getAttribute('id')}"]`);
                                    if (player) {
                                        let value = Number(player.getAttribute('value')) / (conversionRatesToUSD[teamPlayers.getAttribute('teamCurrency')] || 1)
                                        rivalFormation[i][3] = '' + value.toFixed(0);
                                        //player.getAttribute('value').replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
                                        rivalFormation[i][4] = player.getAttribute('height');
                                        rivalFormation[i][5] = player.getAttribute('weight');
                                        rivalFormation[i][6] = player.getAttribute('age');
                                        rivalFormation[i][7] = player.getAttribute('countryShortname');
                                    }
                                }
                                setRivalFormation();
                            },
                            onerror: function(error) {
                                console.log('Error:', error);
                            }
                        });

                        
                    } catch (error) {
                        console.error('Error decompressing or parsing XML:', error);
                    }
                } else {
                    console.error('Failed to fetch the file:', response.statusText);
                    alert('The match has probably not been viewed in 2D yet, therefore we cannot obtain the statistics, or try again later.');
                }
            },
            onerror: function(error) {
                console.error('Error with GM_xmlhttpRequest:', error);
            }
        });
    }

    function StatsToPos_X (i, isHome) {
        let ret = isHome ? Math.round(-.255800462 * i + 199.8228530689) : Math.round(.2555000556 * i + 8.3741302936);
        return ret;
    }

    function StatsToPos_Y (i, isHome) {
        let ret = isHome ? Math.round(-.3073207154 * i + 315.9278777381) : Math.round(.3070644902 * i + 9.2794889414);
        return ret;
    }


    function setRivalFormation () {
        let rivalPlayers = document.createElement('div');
        rivalPlayers.id = "rivalPlayers";
        let formationText = document.getElementById('formation_text');
        formationText.parentNode.insertBefore(rivalPlayers, formationText.nextSibling);
        console.log('players on the pitch');

        let contentRival = '';
        if (rivalTeam.hasAttribute("name")) {
            contentRival += `<div style="position:absolute; left: 1px; top: 3px; width: 215px;">`+
                            `<div style="display: flex; justify-content: space-between; font-size:0.7em; color:black"><div style="display: flex; flex-direction: column; justify-content: space-around; max-width: 90px; text-align: center;"><span>${rivalTeam.getAttribute('name')}</span><span><img src="https://www.managerzone.com/nocache-910/img/flags/64/${rivalTeam.getAttribute('country').toLowerCase()}.png" width="14" alt=""></span></div>` +
                            `<span><img src="https://www.managerzone.com/dynimg/badge.php?team_id=${rivalTeam.getAttribute('id')}" width="25" alt=""></span></div></div>`;
        }

        contentRival += `<div style="position:absolute; left: 1px; top: 44px; width: 215px">` +
                        `<div style="display: flex; justify-content: space-around; font-size:0.7em;"><span>${rivalTeam.getAttribute('tactic')}</span><span>${rivalTeam.getAttribute('playstyle')}</span><span>${rivalTeam.getAttribute('aggression')}</span></div></div>`;

        for (let i = 0; i < rivalFormation.length; i++) {
            contentRival += "<div style=\"position:absolute; left: " + (194 - rivalFormation[i][0]) + "px; top: " + (310 - rivalFormation[i][1]) + "px;\">" +
                    "<div style=\"border-radius: 50%; width: 18px; height: 18px; border: 2px solid #FFE42B; color: black; display: flex; align-items: center; justify-content: center; font-size:0.9em;  font-weight: bold;\">"+rivalFormation[i][2]+"</div>" +
                    "<div style=\"position:absolute; top: -6px; width:22px; display: flex; justify-content: space-between; font-size:0.45em; color:black\"><span>" + (rivalFormation[i][0] - 97) + "</span><span>" + (155 - rivalFormation[i][1]) + "</span></div>" +
                    `<div class="additionalInfoRivalPlayer hidden"><div style="position: absolute; white-space: nowrap; font-size:0.45em; writing-mode: vertical-rl; text-align: right; color:black; left: 22px; top: -6px; height:37px"><span>${rivalFormation[i][3]?rivalFormation[i][3].replace(/\B(?=(\d{3})+(?!\d))/g, ' ')+" USD":''}</span></div>` +
                    `<div style="display: flex; justify-content: space-between; font-size:0.3em; color:black"><span>${rivalFormation[i][4]?rivalFormation[i][4]+" cm":''}</span><span>${rivalFormation[i][6]?rivalFormation[i][6]+" y":''}</span></div>` +
                    `<div style="display: flex; justify-content: space-between; font-size:0.3em; color:black"><span>${rivalFormation[i][5]?rivalFormation[i][5]+" kg":''}</span><span>${rivalFormation[i][7]?`<img src="https://www.managerzone.com/nocache-910/img/flags/s_${rivalFormation[i][7]}.gif" width="7" alt="">`:''}</span></div></div></div>`;
        }

        rivalPlayers.innerHTML = contentRival;

        let elBoton = document.getElementById('rivalBtn');
        elBoton.style.color = '#FF4500';
        elBoton.innerHTML = "clearRival";
    }

    function createCoordinatesPanel(){

        if (document.getElementById("divCoords")) return;

        const coordsContainer = document.getElementById('formation-container');
        if (!coordsContainer) return;

        const div = document.createElement("div");
        div.id = "divCoords";

        div.innerHTML = `
            <span style="font-weight:600">
                Coords:
                <span style="color:green">X</span>
                <input id="inputX" type="text" style="width:24px">
                <span style="color:blue">Y</span>
                <input id="inputY" type="text" style="width:24px">
            </span>
            &nbsp;<button id="copyBtn">Copy</button>
            &nbsp;<button id="pasteBtn">Paste</button>
            &nbsp;&nbsp;<button id="saveBtn">Save</button>
            &nbsp;<button id="loadBtn">Load</button>
        `;

        coordsContainer.appendChild(div);

        const copyBtn = div.querySelector("#copyBtn");
        const pasteBtn = div.querySelector("#pasteBtn");
        const saveBtn = div.querySelector("#saveBtn");
        saveBtn.style.color = "white";
        saveBtn.style.background = "#4CAF50";
        const loadBtn = div.querySelector("#loadBtn");
        loadBtn.style.color = "white";
        loadBtn.style.background = "#007bff";

        inputX = div.querySelector("#inputX");
        inputY = div.querySelector("#inputY");

        inputX.inputMode = "numeric";
        inputY.inputMode = "numeric";
        inputX.addEventListener("input", setPlayerPosition);
        inputY.addEventListener("input", setPlayerPosition);
        const selectAll = e => e.target.select();
        inputX.addEventListener("focus", selectAll);
        inputY.addEventListener("focus", selectAll);

        copyBtn.addEventListener("click", copyFormation);
        pasteBtn.addEventListener("click", pasteFormation);
        saveBtn.addEventListener("click", saveTactic);
        loadBtn.addEventListener("click", loadTactics);

        [copyBtn, pasteBtn, saveBtn, loadBtn].forEach(btn => {
            btn.style.display = "inline-block";
            btn.style.margin = "0 0px";
            btn.style.padding = "2px 3px"; // más pequeño en móviles
            btn.style.boxSizing = "border-box";
        });

    }

    function setPlayerPosition() {

        const x = Number(inputX.value);
        const y = Number(inputY.value);

        const player = getActivePlayer();
        if(!player) return;

        const validX = isInRange(x, 'x');
        const validY = isInRange(y, 'y');

        validX ? removeWarning(inputX) : addWarning(inputX);
        validY ? removeWarning(inputY) : addWarning(inputY);

        if(validX && validY){
            const pos = coordsToPitch(x, y);
            player.style.left = pos.left + "px";
            player.style.top = pos.top + "px";
        }
    }

    function isInRange(number, coordinate) {

        const n = Number(number);

        if (Number.isNaN(n)) return false;

        if (coordinate === 'x') {
            return n >= -96 && n <= 96;
        }

        if (coordinate === 'y') {
            return n >= -157 && n <= 101;
        }

        return false;
    }

    function setKeys(key) {
        if((key.keyCode === 37 || key.keyCode === 38 || key.keyCode === 39 || key.keyCode === 40)
           && (key.currentTarget.activeElement.localName != 'input')) {

            const players = document.querySelectorAll('.fieldpos.fieldpos-ok.ui-draggable.ui-selected, .fieldpos.ui-selected.fieldpos-collision');

            if(players.length) {
                const pos = pitchToCoords(players[0].offsetLeft, players[0].offsetTop);
                _x = pos.x;
                _y = pos.y;
            } else {
                _y = '--';
                _x = '--';
            }
            updateCoordinatesPanel();
        }
    }

    function updatePlayerCoords(player){

        const pos = pitchToCoords(player.offsetLeft, player.offsetTop);

        player.dataset.x = pos.x;
        player.dataset.y = pos.y;

    }

    function coordsToPitch(x, y){
        return {
            left: x + 97,
            top: 155 - y
        };
    }

    function pitchToCoords(left, top){
        return {
            x: Math.round(left) - 97,
            y: 155 - Math.round(top)
        };
    }

    function addWarning(input) {
        input.classList.add("mz-error-input");
    }

    function removeWarning(input) {
        input.classList.remove("mz-error-input");
    }

    function isSoccer() {
        const sport = document.getElementById('tactics_box');
        return sport?.classList.contains('soccer') ?? false;
    }

})();