MZ Tactics Manager

Lets you manage your tactics in ManagerZone

Install this script?
Author's suggested script

You may also like ylOppTactsPreview (Modified).

Install this script
// ==UserScript==
// @name         MZ Tactics Manager
// @namespace    douglaskampl
// @version      11.0.0
// @description  Lets you manage your tactics in ManagerZone
// @author       Douglas Vieira
// @match        https://www.managerzone.com/?p=tactics
// @match        https://www.managerzone.com/?p=national_teams&sub=tactics&type=*
// @icon         https://yt3.googleusercontent.com/ytc/AIdro_mDHaJkwjCgyINFM7cdUV2dWPPnL9Q58vUsrhOmRqkatg=s160-c-k-c0x00ffffff-no-rj
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @require      https://cdnjs.cloudflare.com/ajax/libs/jsSHA/3.3.1/sha256.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/i18next/23.7.16/i18next.min.js
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    GM_addStyle(`@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Dancing+Script:wght@500&display=swap"); #mz_tactics_panel {font-family: "Space Grotesk", -apple-system, sans-serif; background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); border-radius: 12px; padding: 20px; margin: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); border: 1px solid #1e293b; transition: all 0.3s ease-in-out; max-height: 1000px; opacity: 1; color: #f8fafc; overflow: hidden; } #mz_tactics_panel.collapsed {max-height: 0; padding: 0; margin: 0; opacity: 0; border: none; } .mz-group-main-title {display: flex; justify-content: space-between; align-items: center; color: #f8fafc; font-size: 16px; font-weight: 500; margin: -4px 0 12px 0; padding-bottom: 8px; border-bottom: 1px solid rgba(248, 250, 252, 0.1); } #toggle_panel_btn {background: none; border: none; color: #94a3b8; cursor: pointer; padding: 4px; margin-left: auto; font-size: 18px; transition: all 0.3s ease; display: inline-flex; align-items: center; justify-content: center; opacity: 0.7; } #toggle_panel_btn:hover {opacity: 1; transform: translateY(-1px); } #toggle_panel_btn.collapsed {transform: rotate(180deg); } #toggle_panel_btn.collapsed:hover {transform: rotate(180deg) translateY(-1px); } #collapsed_icon {position: fixed; top: 20px; right: 20px; background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0; transition: all 0.3s ease; transform: scale(0); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); z-index: 1000; color: #94a3b8; font-size: 18px; } #collapsed_icon.visible {opacity: 1; transform: scale(1); } #collapsed_icon:hover {transform: scale(1.1); opacity: 1; } .mz-group {background: linear-gradient(135deg, #334155 0%, #1e293b 100%); border-radius: 8px; padding: 16px; margin: 8px; border: 1px solid #334155; position: relative; } .mz-main-title {color: #f8fafc; font-family: "Space Grotesk", sans-serif; font-size: 18px; font-weight: 500; margin: 0; padding: 0; text-align: center; letter-spacing: 0.2px; } .mz-version-text {color: #ff9933; font-family: "Dancing Script", cursive; font-size: 0.9em; font-weight: 500; margin-left: 4px; } .mz-divider {width: 40px; height: 2px; background: #64748b; margin: 8px auto 0; opacity: 0.3; } #mz_tactics_panel .mzbtn {display: inline-flex; align-items: center; padding: 8px 14px; margin: 4px; font-family: "Space Grotesk", sans-serif; font-size: 13px; font-weight: 500; color: #f8fafc; background: #334155; border: none; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; } #mz_tactics_panel .mzbtn:hover {background: #475569; transform: translateY(-1px); } #mz_tactics_panel .mzbtn:focus {outline: 2px solid #94a3b8; outline-offset: 1px; } #mz_tactics_panel select {font-family: "Space Grotesk", sans-serif; font-size: 13px; color: #f8fafc; padding: 8px 14px; border: 1px solid #334155; border-radius: 6px; background-color: #1e293b; cursor: pointer; margin: 4px; transition: all 0.2s ease; } #mz_tactics_panel select:focus {outline: none; border-color: #64748b; box-shadow: 0 0 0 2px rgba(100, 116, 139, 0.1); } #language_flag {height: 15px; width: 25px; margin: 6px 0 6px 6px; border: 1px solid #e2e8f0; border-radius: 2px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); } #info_modal, #useful_links_modal {background: #1e293b; padding: 20px; border-radius: 12px; color: #f8fafc; width: 90%; max-width: 500px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); } #info_modal a, #useful_links_modal a {color: #60a5fa; } #info_modal ul, #useful_links_modal ul {list-style: none; padding: 0; } #info_modal ul li, #useful_links_modal ul li {margin: 10px 0; }`);

    const OUTFIELD_PLAYERS_SELECTOR = ".fieldpos.fieldpos-ok.ui-draggable:not(.substitute):not(.goalkeeper):not(.substitute.goalkeeper), .fieldpos.fieldpos-collision.ui-draggable:not(.substitute):not(.goalkeeper):not(.substitute.goalkeeper)";
    const GOALKEEPER_SELECTOR = ".fieldpos.fieldpos-ok.goalkeeper.ui-draggable";
    const FORMATION_TEXT_SELECTOR = "#formation_text";
    const TACTIC_SLOT_SELECTOR = ".ui-state-default.ui-corner-top.ui-tabs-selected.ui-state-active.invalid";
    const MIN_OUTFIELD_PLAYERS = 10;
    const MAX_TACTIC_NAME_LENGTH = 50;
    const DEFAULT_TACTICS_DATA_URL = "https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/defaultTactics.json";
    const LANG_DATA_BASE_URL = "https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/lang/";
    const BASE_FLAG_URL = "https://flagcdn.com/w320/";
    const LANGUAGES = [
        { code: "en", name: "English", flag: BASE_FLAG_URL + "gb.png" },
        { code: "pt", name: "Português", flag: BASE_FLAG_URL + "br.png" },
        { code: "zh", name: "中文", flag: BASE_FLAG_URL + "cn.png" },
        { code: "sv", name: "Svenska", flag: BASE_FLAG_URL + "se.png" },
        { code: "no", name: "Norsk", flag: BASE_FLAG_URL + "no.png" },
        { code: "da", name: "Dansk", flag: BASE_FLAG_URL + "dk.png" },
        { code: "es", name: "Español", flag: BASE_FLAG_URL + "ar.png" },
        { code: "pl", name: "Polski", flag: BASE_FLAG_URL + "pl.png" },
        { code: "nl", name: "Nederlands", flag: BASE_FLAG_URL + "nl.png" },
        { code: "id", name: "Bahasa Indonesia", flag: BASE_FLAG_URL + "id.png" },
        { code: "de", name: "Deutsch", flag: BASE_FLAG_URL + "de.png" },
        { code: "it", name: "Italiano", flag: BASE_FLAG_URL + "it.png" },
        { code: "fr", name: "Français", flag: BASE_FLAG_URL + "fr.png" },
        { code: "ro", name: "Română", flag: BASE_FLAG_URL + "ro.png" },
        { code: "tr", name: "Türkçe", flag: BASE_FLAG_URL + "tr.png" },
        { code: "ko", name: "한국어", flag: BASE_FLAG_URL + "kr.png" },
        { code: "ru", name: "Русский", flag: BASE_FLAG_URL + "ru.png" },
        { code: "ar", name: "العربية", flag: BASE_FLAG_URL + "sa.png" },
        { code: "jp", name: "日本語", flag: BASE_FLAG_URL + "jp.png" }
    ];
    const VERSION = "11.0.0";
    const VERSION_KEY = "mz_tactics_version";
    const USERSCRIPT_STRINGS = {
        addButton: "Add Tactic",
        addWithXmlButton: "Add Tactic via XML",
        deleteButton: "Delete Tactic",
        renameButton: "Rename Tactic",
        updateButton: "Update Tactic",
        clearButton: "Clear Tactics",
        resetButton: "Reset Tactics",
        importButton: "Import Tactics",
        exportButton: "Export Tactics",
        usefulLinksButton: "Useful Links",
        aboutButton: "About",
        tacticNamePrompt: "Tactic Name:",
        addAlert: "Tactic {} added successfully.",
        deleteAlert: "Tactic {} deleted successfully.",
        renameAlert: "Tactic {} renamed to {} successfully.",
        updateAlert: "Tactic {} updated successfully.",
        clearAlert: "Tactics cleared successfully.",
        resetAlert: "Tactics reset successfully.",
        importAlert: "Tactics imported successfully.",
        exportAlert: "Tactics exported successfully.",
        deleteConfirmation: "Do you really want to delete {}?",
        updateConfirmation: "Do you really want to update {}?",
        clearConfirmation: "Do you really want to clear tactics?",
        resetConfirmation: "Do you really want to reset tactics?",
        invalidTacticError: "Invalid tactic.",
        noTacticNameProvidedError: "No tactic name provided.",
        alreadyExistingTacticNameError: "Tactic name already exists.",
        tacticNameMaxLengthError: "Tactic name is too long.",
        noTacticSelectedError: "No tactic selected.",
        duplicateTacticError: "Duplicate tactic.",
        noChangesMadeError: "No changes made.",
        invalidImportError: "Invalid import file.",
        modalContentInfoText: "This is the tactic selector.",
        modalContentFeedbackText: "Send your feedback.",
        usefulContent: "Some useful resources:",
        tacticsDropdownMenuLabel: "Tactics:",
        languageDropdownMenuLabel: "Language:",
        errorTitle: "Error",
        doneTitle: "Done",
        confirmationTitle: "Confirmation",
        deleteTacticConfirmButton: "Delete",
        cancelConfirmButton: "Cancel",
        updateConfirmButton: "Update",
        clearTacticsConfirmButton: "Clear",
        resetTacticsConfirmButton: "Reset",
        addConfirmButton: "Add",
        xmlValidationError: "Invalid XML.",
        xmlParsingError: "Error parsing XML.",
        xmlPlaceholder: "Paste XML here",
        tacticNamePlaceholder: "Name",
        managerTitle: "MZ Tactics Manager",
        tacticActionsTitle: "Actions",
        otherActionsTitle: "Other",
        welcomeMessage: "Welcome to MZ Tactics Manager v11!<br><br>What's new:<br>• Fixed compatibility issues for Chinese users<br>• UI improvements<br><br>If you have any questions or suggestions, feel free to message douglaskampl via chat or guestbook.",
        welcomeGotIt: "Got it!"
    };
    const ELEMENT_STRING_KEYS = {
        add_tactic_button: "addButton",
        add_tactic_with_xml_button: "addWithXmlButton",
        delete_tactic_button: "deleteButton",
        rename_tactic_button: "renameButton",
        update_tactic_button: "updateButton",
        clear_tactics_button: "clearButton",
        reset_tactics_button: "resetButton",
        import_tactics_button: "importButton",
        export_tactics_button: "exportButton",
        about_button: "aboutButton",
        tactics_dropdown_menu_label: "tacticsDropdownMenuLabel",
        language_dropdown_menu_label: "languageDropdownMenuLabel",
        info_modal_info_text: "modalContentInfoText",
        info_modal_feedback_text: "modalContentFeedbackText",
        useful_links_button: "usefulLinksButton"
    };

    let dropdownMenuTactics = [];
    let activeLanguage;
    let infoModal;
    let usefulLinksModal;

    function showAlert(options) {
        return new Promise((resolve) => {
            if (options.input === 'text') {
                let userInput = prompt(options.title || '', options.inputValue || '');
                if (userInput === null) {
                    return resolve({ value: null });
                }
                if (options.inputValidator) {
                    const validationError = options.inputValidator(userInput);
                    if (validationError) {
                        alert(validationError);
                        return resolve({ value: null });
                    }
                }
                return resolve({ value: userInput });
            }
            if (options.showCancelButton) {
                const c = confirm(options.text || options.title || '');
                return resolve({ isConfirmed: c });
            }
            alert(options.text || options.title || '');
            return resolve({});
        });
    }

    function showSuccessMessage(title, text) {
        alert(`${title}\n\n${text}`);
        return Promise.resolve();
    }

    function showErrorMessage(title, text) {
        alert(`${title}\n\n${text}`);
        return Promise.resolve();
    }

    function showWelcomeMessage() {
        const msg = USERSCRIPT_STRINGS.welcomeMessage.replace(/<br\s*\/?>/gi, '\n');
        alert(msg);
        return Promise.resolve();
    }

    function isFootball() {
        const element = document.querySelector("div#tactics_box.soccer.clearfix");
        return !!element;
    }

    function sha256Hash(str) {
        const shaObj = new jsSHA("SHA-256", "TEXT");
        shaObj.update(str);
        return shaObj.getHash("HEX");
    }

    async function fetchTacticsFromGMStorage() {
        const storedTactics = GM_getValue("ls_tactics");
        if (storedTactics) {
            return storedTactics;
        } else {
            const jsonTactics = await fetchTacticsFromJson();
            storeTacticsInGMStorage(jsonTactics);
            return jsonTactics;
        }
    }

    async function fetchTacticsFromJson() {
        const response = await fetch(DEFAULT_TACTICS_DATA_URL);
        return await response.json();
    }

    function storeTacticsInGMStorage(data) {
        GM_setValue("ls_tactics", data);
    }

    async function validateDuplicateTactic(id) {
        const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
        return tacticsData.tactics.some((tactic) => tactic.id === id);
    }

    async function saveTacticToStorage(tactic) {
        const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
        tacticsData.tactics.push(tactic);
        await GM_setValue("ls_tactics", tacticsData);
    }

    async function validateDuplicateTacticWithUpdatedCoord(newId, selectedTac, tacticsData) {
        if (newId === selectedTac.id) {
            return "unchanged";
        } else if (tacticsData.tactics.some((tac) => tac.id === newId)) {
            return "duplicate";
        } else {
            return "unique";
        }
    }

    function handleTacticsSelection(tactic) {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const selectedTactic = dropdownMenuTactics.find((tacticData) => tacticData.name === tactic);
        if (selectedTactic) {
            if (outfieldPlayers.length < MIN_OUTFIELD_PLAYERS) {
                const hiddenTriggerButton = document.getElementById("hidden_trigger_button");
                hiddenTriggerButton.click();
                setTimeout(() => rearrangePlayers(selectedTactic.coordinates), 1);
            } else {
                rearrangePlayers(selectedTactic.coordinates);
            }
        }
    }

    function rearrangePlayers(coordinates) {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        findBestPositions(outfieldPlayers, coordinates);
        for (let i = 0; i < outfieldPlayers.length; ++i) {
            outfieldPlayers[i].style.left = coordinates[i][0] + "px";
            outfieldPlayers[i].style.top = coordinates[i][1] + "px";
            removeCollision(outfieldPlayers[i]);
        }
        removeTacticSlotInvalidStatus();
        updateFormationText(getFormation(coordinates));
    }

    function findBestPositions(players, coordinates) {
        players.sort((a, b) => parseInt(a.style.top) - parseInt(b.style.top));
        coordinates.sort((a, b) => a[1] - b[1]);
    }

    function removeCollision(player) {
        if (player.classList.contains("fieldpos-collision")) {
            player.classList.remove("fieldpos-collision");
            player.classList.add("fieldpos-ok");
        }
    }

    function removeTacticSlotInvalidStatus() {
        const slot = document.querySelector(TACTIC_SLOT_SELECTOR);
        if (slot) {
            slot.classList.remove("invalid");
        }
    }

    function updateFormationText(formation) {
        const formationTextElement = document.querySelector(FORMATION_TEXT_SELECTOR);
        formationTextElement.querySelector(".defs").textContent = formation.defenders;
        formationTextElement.querySelector(".mids").textContent = formation.midfielders;
        formationTextElement.querySelector(".atts").textContent = formation.strikers;
    }

    function getFormation(coordinates) {
        let strikers = 0;
        let midfielders = 0;
        let defenders = 0;
        for (const coo of coordinates) {
            const y = coo[1];
            if (y < 103) {
                strikers++;
            } else if (y <= 204) {
                midfielders++;
            } else {
                defenders++;
            }
        }
        return { strikers, midfielders, defenders };
    }

    function validateTacticPlayerCount(outfieldPlayers) {
        const isGoalkeeper = document.querySelector(GOALKEEPER_SELECTOR);
        outfieldPlayers = outfieldPlayers.filter((player) => !player.classList.contains("fieldpos-collision"));
        if (outfieldPlayers.length < MIN_OUTFIELD_PLAYERS || !isGoalkeeper) {
            showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidTacticError);
            return false;
        }
        return true;
    }

    async function addNewTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const tacticsDropdownMenu = document.getElementById("tactics_dropdown_menu");
        const tacticCoordinates = outfieldPlayers.map((player) => [parseInt(player.style.left), parseInt(player.style.top)]);
        if (!validateTacticPlayerCount(outfieldPlayers)) {
            return;
        }
        const tacticId = generateUniqueId(tacticCoordinates);
        const isDuplicate = await validateDuplicateTactic(tacticId);
        if (isDuplicate) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.duplicateTacticError);
            return;
        }
        const result = await showAlert({
            title: USERSCRIPT_STRINGS.tacticNamePrompt,
            input: 'text',
            inputValue: '',
            inputValidator: (value) => {
                if (!value) {
                    return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                }
                if (value.length > MAX_TACTIC_NAME_LENGTH) {
                    return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                }
                if (dropdownMenuTactics.some((t) => t.name === value)) {
                    return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                }
            },
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        const tacticName = result.value;
        if (!tacticName) {
            return;
        }
        const tactic = {
            name: tacticName,
            coordinates: tacticCoordinates,
            id: tacticId
        };
        await saveTacticToStorage(tactic);
        addTacticsToDropdownMenu(tacticsDropdownMenu, [tactic]);
        dropdownMenuTactics.push(tactic);
        const placeholderOption = tacticsDropdownMenu.querySelector('option[value=""]');
        if (placeholderOption) {
            placeholderOption.remove();
        }
        if (tacticsDropdownMenu.disabled) {
            tacticsDropdownMenu.disabled = false;
        }
        tacticsDropdownMenu.value = tactic.name;
        const changeEvent = new Event('change', { bubbles: true });
        tacticsDropdownMenu.dispatchEvent(changeEvent);
        handleTacticsSelection(tactic.name);
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.addAlert.replace("{}", tactic.name));
    }

    async function addNewTacticWithXml() {
        let xml = prompt(USERSCRIPT_STRINGS.xmlPlaceholder, "");
        if (!xml) {
            alert(USERSCRIPT_STRINGS.xmlValidationError);
            return;
        }
        let name = prompt(USERSCRIPT_STRINGS.tacticNamePrompt, "");
        if (!name) {
            alert(USERSCRIPT_STRINGS.noTacticNameProvidedError);
            return;
        }
        if (name.length > MAX_TACTIC_NAME_LENGTH) {
            alert(USERSCRIPT_STRINGS.tacticNameMaxLengthError);
            return;
        }
        if (dropdownMenuTactics.some((t) => t.name === name)) {
            alert(USERSCRIPT_STRINGS.alreadyExistingTacticNameError);
            return;
        }
        try {
            const newTactic = await convertXmlToTacticJson(xml, name);
            const tacticId = generateUniqueId(newTactic.coordinates);
            const isDuplicate = await validateDuplicateTactic(tacticId);
            if (isDuplicate) {
                await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.duplicateTacticError);
                return;
            }
            newTactic.id = tacticId;
            await saveTacticToStorage(newTactic);
            const tacticsDropdownMenu = document.getElementById('tactics_dropdown_menu');
            addTacticsToDropdownMenu(tacticsDropdownMenu, [newTactic]);
            dropdownMenuTactics.push(newTactic);
            tacticsDropdownMenu.value = newTactic.name;
            handleTacticsSelection(newTactic.name);
            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.addAlert.replace('{}', newTactic.name));
        } catch (e) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.xmlParsingError);
        }
    }

    async function deleteTactic() {
        const tacticsDropdownMenu = document.getElementById("tactics_dropdown_menu");
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsDropdownMenu.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const confirmResult = await showAlert({
            text: USERSCRIPT_STRINGS.deleteConfirmation.replace("{}", selectedTactic.name),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.deleteTacticConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmResult.isConfirmed) {
            return;
        }
        const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
        tacticsData.tactics = tacticsData.tactics.filter((tactic) => tactic.id !== selectedTactic.id);
        await GM_setValue("ls_tactics", tacticsData);
        dropdownMenuTactics = dropdownMenuTactics.filter((tactic) => tactic.id !== selectedTactic.id);
        const selectedOption = Array.from(tacticsDropdownMenu.options).find((option) => option.value === selectedTactic.name);
        tacticsDropdownMenu.remove(selectedOption.index);
        if (tacticsDropdownMenu.options[0]?.disabled) {
            tacticsDropdownMenu.selectedIndex = 0;
        }
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.deleteAlert.replace("{}", selectedTactic.name));
    }

    async function renameTactic() {
        const tacticsDropdownMenu = document.getElementById("tactics_dropdown_menu");
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsDropdownMenu.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const oldName = selectedTactic.name;
        const result = await showAlert({
            title: USERSCRIPT_STRINGS.tacticNamePrompt,
            input: 'text',
            inputValue: oldName,
            inputValidator: (value) => {
                if (!value) {
                    return USERSCRIPT_STRINGS.noTacticNameProvidedError;
                }
                if (value.length > MAX_TACTIC_NAME_LENGTH) {
                    return USERSCRIPT_STRINGS.tacticNameMaxLengthError;
                }
                if (value !== oldName && dropdownMenuTactics.some((t) => t.name === value)) {
                    return USERSCRIPT_STRINGS.alreadyExistingTacticNameError;
                }
            },
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.updateConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        const newName = result.value;
        if (!newName) {
            return;
        }
        const selectedOption = Array.from(tacticsDropdownMenu.options).find((option) => option.value === selectedTactic.name);
        const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
        tacticsData.tactics = tacticsData.tactics.map((tactic) => {
            if (tactic.id === selectedTactic.id) {
                tactic.name = newName;
            }
            return tactic;
        });
        await GM_setValue("ls_tactics", tacticsData);
        dropdownMenuTactics = dropdownMenuTactics.map((tactic) => {
            if (tactic.id === selectedTactic.id) {
                tactic.name = newName;
            }
            return tactic;
        });
        selectedOption.value = newName;
        selectedOption.textContent = newName;
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.renameAlert.replace("{}", oldName).replace("{}", newName));
    }

    async function updateTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const tacticsDropdownMenu = document.getElementById("tactics_dropdown_menu");
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsDropdownMenu.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const updatedCoordinates = outfieldPlayers.map((player) => [parseInt(player.style.left), parseInt(player.style.top)]);
        const newId = generateUniqueId(updatedCoordinates);
        const tacticsData = (await GM_getValue("ls_tactics")) || { tactics: [] };
        const validationOutcome = await validateDuplicateTacticWithUpdatedCoord(newId, selectedTactic, tacticsData);
        if (validationOutcome === "unchanged") {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noChangesMadeError);
            return;
        } else if (validationOutcome === "duplicate") {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.duplicateTacticError);
            return;
        }
        const result = await showAlert({
            text: USERSCRIPT_STRINGS.updateConfirmation.replace("{}", selectedTactic.name),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.updateConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!result.isConfirmed) {
            return;
        }
        for (const tactic of tacticsData.tactics) {
            if (tactic.id === selectedTactic.id) {
                tactic.coordinates = updatedCoordinates;
                tactic.id = newId;
            }
        }
        for (const tactic of dropdownMenuTactics) {
            if (tactic.id === selectedTactic.id) {
                tactic.coordinates = updatedCoordinates;
                tactic.id = newId;
            }
        }
        await GM_setValue("ls_tactics", tacticsData);
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.updateAlert.replace("{}", selectedTactic.name));
    }

    async function clearTactics() {
        const confirmResult = await showAlert({
            text: USERSCRIPT_STRINGS.clearConfirmation,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.clearTacticsConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmResult.isConfirmed) {
            return;
        }
        await GM_setValue("ls_tactics", { tactics: [] });
        dropdownMenuTactics = [];
        const tacticsDropdownMenu = document.getElementById("tactics_dropdown_menu");
        tacticsDropdownMenu.innerHTML = "";
        tacticsDropdownMenu.disabled = true;
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.clearAlert);
    }

    async function resetTactics() {
        const confirmResult = await showAlert({
            text: USERSCRIPT_STRINGS.resetConfirmation,
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.resetTacticsConfirmButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmResult.isConfirmed) {
            return;
        }
        const response = await fetch(DEFAULT_TACTICS_DATA_URL);
        const data = await response.json();
        const defaultTactics = data.tactics;
        await GM_setValue("ls_tactics", { tactics: defaultTactics });
        dropdownMenuTactics = defaultTactics;
        const tacticsDropdownMenu = document.getElementById("tactics_dropdown_menu");
        tacticsDropdownMenu.innerHTML = "";
        tacticsDropdownMenu.appendChild(createPlaceholderOption());
        addTacticsToDropdownMenu(tacticsDropdownMenu, dropdownMenuTactics);
        tacticsDropdownMenu.disabled = false;
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.resetAlert);
    }

    async function importTactics() {
        const input = document.createElement("input");
        input.type = "file";
        input.accept = ".json";
        input.onchange = async function (event) {
            const file = event.target.files[0];
            const reader = new FileReader();
            reader.onload = async function (event) {
                let importedData;
                try {
                    importedData = JSON.parse(event.target.result);
                } catch (e) {
                    await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidImportError);
                    return;
                }
                if (!importedData || !Array.isArray(importedData.tactics)) {
                    await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidImportError);
                    return;
                }
                const importedTactics = importedData.tactics;
                let existingTactics = await GM_getValue("ls_tactics", { tactics: [] });
                existingTactics = existingTactics.tactics;
                const mergedTactics = [...existingTactics];
                for (const importedTactic of importedTactics) {
                    if (!existingTactics.some((tactic) => tactic.id === importedTactic.id)) {
                        mergedTactics.push(importedTactic);
                    }
                }
                await GM_setValue("ls_tactics", { tactics: mergedTactics });
                mergedTactics.sort((a, b) => a.name.localeCompare(b.name));
                dropdownMenuTactics = mergedTactics;
                const tacticsDropdownMenu = document.getElementById("tactics_dropdown_menu");
                tacticsDropdownMenu.innerHTML = "";
                tacticsDropdownMenu.append(createPlaceholderOption());
                addTacticsToDropdownMenu(tacticsDropdownMenu, dropdownMenuTactics);
                tacticsDropdownMenu.disabled = false;
                await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.importAlert);
            };
            reader.readAsText(file);
        };
        input.click();
    }

    function exportTactics() {
        const tactics = GM_getValue("ls_tactics", { tactics: [] });
        const tacticsJson = JSON.stringify(tactics);
        const blob = new Blob([tacticsJson], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.href = url;
        link.download = "tactics.json";
        const onFocus = () => {
            window.removeEventListener('focus', onFocus);
            URL.revokeObjectURL(url);
        };
        window.addEventListener('focus', onFocus, { once: true });
        link.click();
    }

    async function convertXmlToTacticJson(xmlString, tacticName) {
        const parser = new DOMParser();
        const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
        const parserError = xmlDoc.getElementsByTagName('parsererror');
        if (parserError.length > 0) {
            throw new Error('Invalid XML');
        }
        const posElements = Array.from(xmlDoc.getElementsByTagName('Pos'));
        const normalPosElements = posElements.filter(el => el.getAttribute('pos') === 'normal');
        const coordinates = normalPosElements.map(el => {
            const x = parseInt(el.getAttribute('x'));
            const y = parseInt(el.getAttribute('y'));
            const htmlLeft = x - 7;
            const htmlTop = y - 9;
            return [htmlLeft, htmlTop];
        });
        return {
            name: tacticName,
            coordinates: coordinates
        };
    }

    function createAddNewTacticButton() {
        const button = document.createElement("button");
        setUpButton(button, "add_tactic_button", USERSCRIPT_STRINGS.addButton);
        button.addEventListener("click", function () {
            addNewTactic().catch((_) => { });
        });
        return button;
    }

    function createAddNewTacticWithXmlButton() {
        const button = document.createElement("button");
        setUpButton(button, "add_tactic_with_xml_button", USERSCRIPT_STRINGS.addWithXmlButton);
        button.addEventListener("click", function () {
            addNewTacticWithXml().catch((_) => { });
        });
        return button;
    }

    function createDeleteTacticButton() {
        const button = document.createElement("button");
        setUpButton(button, "delete_tactic_button", USERSCRIPT_STRINGS.deleteButton);
        button.addEventListener("click", function () {
            deleteTactic().catch((_) => { });
        });
        return button;
    }

    function createRenameTacticButton() {
        const button = document.createElement("button");
        setUpButton(button, "rename_tactic_button", USERSCRIPT_STRINGS.renameButton);
        button.addEventListener("click", function () {
            renameTactic().catch((_) => { });
        });
        return button;
    }

    function createUpdateTacticButton() {
        const button = document.createElement("button");
        setUpButton(button, "update_tactic_button", USERSCRIPT_STRINGS.updateButton);
        button.addEventListener("click", function () {
            updateTactic().catch((_) => { });
        });
        return button;
    }

    function createClearTacticsButton() {
        const button = document.createElement("button");
        setUpButton(button, "clear_tactics_button", USERSCRIPT_STRINGS.clearButton);
        button.addEventListener("click", function () {
            clearTactics().catch((_) => { });
        });
        return button;
    }

    function createResetTacticsButton() {
        const button = document.createElement("button");
        setUpButton(button, "reset_tactics_button", USERSCRIPT_STRINGS.resetButton);
        button.addEventListener("click", function () {
            resetTactics().catch((_) => { });
        });
        return button;
    }

    function createImportTacticsButton() {
        const button = document.createElement("button");
        setUpButton(button, "import_tactics_button", USERSCRIPT_STRINGS.importButton);
        button.addEventListener("click", function () {
            importTactics().catch((_) => { });
        });
        return button;
    }

    function createExportTacticsButton() {
        const button = document.createElement("button");
        setUpButton(button, "export_tactics_button", USERSCRIPT_STRINGS.exportButton);
        button.addEventListener("click", function () {
            exportTactics();
        });
        return button;
    }

    async function checkVersion() {
        const storedVersion = GM_getValue(VERSION_KEY, null);
        if (!storedVersion || storedVersion !== VERSION) {
            await showWelcomeMessage();
            GM_setValue(VERSION_KEY, VERSION);
        }
    }

    function playRandomAudio(audios) {
        if (audios.length === 0) {
            return;
        }
        const randomIdx = Math.floor(Math.random() * audios.length);
        const activeAudio = audios.splice(randomIdx, 1)[0];
        playAudio(activeAudio, audios);
        return activeAudio;
    }

    function playAudio(currAudio, audios) {
        currAudio.play();
        currAudio.onended = function () {
            playRandomAudio(audios);
        };
    }

    function pauseAudio(audio) {
        if (audio) {
            audio.pause();
            audio.currentTime = 0;
        }
    }

    function updateAudioIcon(button, isPlaying) {
        button.textContent = isPlaying ? "⏸️" : "🔊";
    }

    function createAudioButton() {
        const button = document.createElement("button");
        setUpButton(button, "audio_button", "🔊");
        const audioUrls = [
            "https://ia801901.us.archive.org/31/items/corp.-palm-mall-01-palm-mall/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20-%2003%20Special%20Discount.mp3",
            "https://ia801901.us.archive.org/31/items/corp.-palm-mall-01-palm-mall/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20-%2004%20First%20Floor.mp3",
            "https://ia801901.us.archive.org/31/items/corp.-palm-mall-01-palm-mall/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20-%2006%20Second%20Floor.mp3",
            "https://ia801901.us.archive.org/7/items/palm-mall-mars-remastered/%E7%8C%AB%20%E3%82%B7%20Corp.%20%26%20SEPHORA%E8%84%B3%E3%83%90%E3%82%A4%E3%83%96%E3%82%B9%20-%20Palm%20Mall%20Mars%20%28remastered%29%20-%2006%20Second%20floor-%20%ED%99%98%EB%8C%80%20%26%20%EC%9D%8C%EC%95%85.mp3",
            "https://ia801901.us.archive.org/7/items/palm-mall-mars-remastered/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20Mars%20%28remastered%29%20-%2001%20%E3%82%B9%E3%82%AD%E3%83%9D%E3%83%BC%E3%83%AB%E7%A9%BA%E6%B8%AFPlaza.mp3",
            "https://ia801901.us.archive.org/7/items/palm-mall-mars-remastered/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20Palm%20Mall%20Mars%20%28remastered%29%20-%2009%20Sembikiya%20Restaurant.mp3",
            "https://ia804504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2001%20FORUM%20%E6%B6%88%E8%B2%BB%E8%80%85-kuluttaja-.mp3",
            "https://ia904504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2002%20Pelican%20Self%20Storage%20-Tilaa%20Kaikelle-.mp3",
            "https://ia904504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2003%20%E8%B2%B7%E3%81%86%40JUMBO%20-Kauppakeskus-.mp3",
            "https://ia904504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2005%20Hesburger%20%E6%98%A0%E7%94%BB%E9%A4%A8%20-hampurilainen-.mp3",
            "https://ia804504.us.archive.org/20/items/5-wn9896/%E7%8C%AB%20%E3%82%B7%20Corp.%20-%20%E3%82%B7%E3%83%A7%E3%83%83%E3%83%97%20%40%20%E3%83%98%E3%83%AB%E3%82%B7%E3%83%B3%E3%82%AD%20-%2006%20%E9%83%BD%E5%B8%82%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A9%E3%83%A0%20Consumer%20-kahvi-.mp3"
        ];
        const audios = audioUrls.map(url => new Audio(url));
        let isPlaying = false;
        let currentAudio = null;
        button.addEventListener("click", function () {
            if (!isPlaying) {
                currentAudio = playRandomAudio(audios);
                isPlaying = true;
            } else {
                pauseAudio(currentAudio);
                isPlaying = false;
            }
            updateAudioIcon(button, isPlaying);
        });
        return button;
    }

    function createMainContainer() {
        const container = document.createElement("div");
        container.id = "mz_tactics_panel";
        container.classList.add("mz-panel");
        const tacticGroup = document.createElement("div");
        tacticGroup.classList.add("mz-group");
        const mainTitle = document.createElement("h2");
        mainTitle.classList.add("mz-group-main-title");
        const titleText = document.createElement("span");
        titleText.textContent = "MZ Tactics Manager";
        mainTitle.appendChild(titleText);
        const vText = document.createElement("span");
        vText.textContent = "v11";
        vText.classList.add("mz-version-text");
        mainTitle.appendChild(vText);
        const dropdownSection = document.createElement("div");
        const tacticsDropdownMenuLabel = createDropdownMenuLabel("tactics_dropdown_menu_label");
        const tacticsDropdownMenu = createTacticsDropdownMenu();
        const tacticsDropdownGroup = createLabelDropdownMenuGroup(tacticsDropdownMenuLabel, tacticsDropdownMenu);
        dropdownSection.appendChild(tacticsDropdownGroup);
        const buttonsSection = document.createElement("div");
        buttonsSection.style.marginTop = "10px";
        const addNewTacticBtn = createAddNewTacticButton();
        const addNewTacticWithXmlBtn = createAddNewTacticWithXmlButton();
        const deleteTacticBtn = createDeleteTacticButton();
        const renameTacticBtn = createRenameTacticButton();
        const updateTacticBtn = createUpdateTacticButton();
        const clearTacticsBtn = createClearTacticsButton();
        const resetTacticsBtn = createResetTacticsButton();
        const importTacticsBtn = createImportTacticsButton();
        const exportTacticsBtn = createExportTacticsButton();
        appendChildren(buttonsSection, [
            addNewTacticBtn,
            addNewTacticWithXmlBtn,
            deleteTacticBtn,
            renameTacticBtn,
            updateTacticBtn,
            clearTacticsBtn,
            resetTacticsBtn,
            importTacticsBtn,
            exportTacticsBtn
        ]);
        appendChildren(tacticGroup, [
            mainTitle,
            dropdownSection,
            buttonsSection,
            createHiddenTriggerButton()
        ]);
        const otherGroup = document.createElement("div");
        otherGroup.classList.add("mz-group");
        const otherContainer = document.createElement("div");
        otherContainer.style.display = "flex";
        otherContainer.style.justifyContent = "space-between";
        otherContainer.style.alignItems = "center";
        otherContainer.style.width = "100%";
        const otherLeftGroup = document.createElement("div");
        otherLeftGroup.style.display = "flex";
        otherLeftGroup.style.alignItems = "center";
        const usefulLinksBtn = createUsefulLinksButton();
        const aboutBtn = createAboutButton();
        const audioBtn = createAudioButton();
        appendChildren(otherLeftGroup, [usefulLinksBtn, aboutBtn, audioBtn]);
        const otherRightGroup = document.createElement("div");
        otherRightGroup.style.display = "flex";
        otherRightGroup.style.alignItems = "center";
        const languageDropdownMenuLabel = createDropdownMenuLabel("language_dropdown_menu_label");
        const languageDropdownMenu = createLanguageDropdownMenu();
        const languageDropdownGroup = createLabelDropdownMenuGroup(languageDropdownMenuLabel, languageDropdownMenu);
        const flagImage = createFlagImage();
        appendChildren(otherRightGroup, [languageDropdownGroup, flagImage]);
        appendChildren(otherContainer, [otherLeftGroup, otherRightGroup]);
        appendChildren(otherGroup, [otherContainer]);
        appendChildren(container, [tacticGroup, otherGroup]);
        return container;
    }

    function createHiddenTriggerButton() {
        const button = document.createElement("button");
        button.id = "hidden_trigger_button";
        button.textContent = "";
        button.style.visibility = "hidden";
        button.addEventListener("click", function () {
            const tacticsPresetInfo = {
                elem: document.getElementById("tactics_preset"),
                resetValue: "5-3-2"
            };
            tacticsPresetInfo.elem.value = tacticsPresetInfo.resetValue;
            tacticsPresetInfo.elem.dispatchEvent(new Event("change"));
        });
        return button;
    }

    function insertAfterElement(something, element) {
        element.parentNode.insertBefore(something, element.nextSibling);
    }

    function appendChildren(parent, children) {
        children.forEach((ch) => {
            parent.appendChild(ch);
        });
    }

    function setUpButton(button, id, textContent) {
        button.id = id;
        button.classList.add('mzbtn');
        button.textContent = textContent;
    }

    function createTacticsDropdownMenu() {
        const dropdown = document.createElement("select");
        setUpDropdownMenu(dropdown, "tactics_dropdown_menu");
        appendChildren(dropdown, [createPlaceholderOption()]);
        return dropdown;
    }

    function createDropdownMenuLabel(labelId) {
        const label = document.createElement("span");
        setUpDropdownMenuLabel(label, labelId, USERSCRIPT_STRINGS.languageDropdownMenuLabel);
        return label;
    }

    function createLabelDropdownMenuGroup(label, dropdown) {
        const group = document.createElement("div");
        group.classList.add('dropdown-group');
        group.appendChild(label);
        group.appendChild(dropdown);
        return group;
    }

    function setUpDropdownMenu(dropdown, id) {
        dropdown.id = id;
    }

    function createPlaceholderOption() {
        const placeholderOption = document.createElement("option");
        placeholderOption.value = "";
        placeholderOption.text = "";
        placeholderOption.disabled = true;
        placeholderOption.selected = true;
        return placeholderOption;
    }

    function addTacticsToDropdownMenu(dropdown, tactics) {
        for (const tactic of tactics) {
            const option = document.createElement("option");
            option.value = tactic.name;
            option.text = tactic.name;
            dropdown.appendChild(option);
        }
    }

    function setUpDropdownMenuLabel(description, id, textContent) {
        description.id = id;
        description.textContent = textContent;
    }

    function createLanguageDropdownMenu() {
        const dropdown = document.createElement("select");
        setUpDropdownMenu(dropdown, "language_dropdown_menu");
        for (const lang of LANGUAGES) {
            const option = document.createElement("option");
            option.value = lang.code;
            option.textContent = lang.name;
            if (lang.code === activeLanguage) {
                option.selected = true;
            }
            dropdown.appendChild(option);
        }
        dropdown.addEventListener("change", function () {
            changeLanguage(this.value).catch((_) => { });
        });
        return dropdown;
    }

    function createFlagImage() {
        const img = document.createElement("img");
        img.id = "language_flag";
        const activeLang = LANGUAGES.find((lang) => lang.code === activeLanguage);
        if (activeLang) {
            img.src = activeLang.flag;
        }
        return img;
    }

    function getActiveLanguage() {
        let language = GM_getValue("language");
        if (!language) {
            let browserLanguage = navigator.language || "en";
            browserLanguage = browserLanguage.split("-")[0];
            const languageExists = LANGUAGES.some((lang) => lang.code === browserLanguage);
            language = languageExists ? browserLanguage : "en";
        }
        return language;
    }

    function updateTranslation() {
        for (const key in USERSCRIPT_STRINGS) {
            USERSCRIPT_STRINGS[key] = i18next.t(key);
        }
        for (const id in ELEMENT_STRING_KEYS) {
            const element = document.getElementById(id);
            if (id === "info_modal_info_text" || id === "info_modal_feedback_text") {
                if (element) element.innerHTML = USERSCRIPT_STRINGS[ELEMENT_STRING_KEYS[id]];
            } else if (element) {
                element.textContent = USERSCRIPT_STRINGS[ELEMENT_STRING_KEYS[id]];
            }
        }
    }

    async function changeLanguage(languageCode) {
        try {
            const translationDataUrl = LANG_DATA_BASE_URL + languageCode + ".json";
            const translations = await (await fetch(translationDataUrl)).json();
            i18next.changeLanguage(languageCode);
            i18next.addResourceBundle(languageCode, "translation", translations);
            GM_setValue("language", languageCode);
            updateTranslation();
            const language = LANGUAGES.find((lang) => lang.code === languageCode);
            if (language) {
                const flagImage = document.getElementById("language_flag");
                if (flagImage) flagImage.src = language.flag;
            }
        } catch (_e) { }
    }

    function generateUniqueId(coordinates) {
        const sortedCoordinates = coordinates.sort((a, b) => a[1] - b[1] || a[0] - b[0]);
        const coordString = sortedCoordinates.map((coord) => coord[1] + "_" + coord[0]).join("_");
        return sha256Hash(coordString);
    }

    function createUsefulLinksModal() {
        const modal = document.createElement("div");
        setUpModal(modal, "useful_links_modal");
        const modalContent = createUsefulLinksModalContent();
        modal.appendChild(modalContent);
        return modal;
    }

    function createUsefulLinksModalContent() {
        const modalContent = document.createElement("div");
        const usefulContent = createUsefulContent();
        const resources = new Map([
            ["gewlaht - BoooM", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=11415137&forum_id=49&sport=soccer"],
            ["taktikskola by honken91", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=12653892&forum_id=4&sport=soccer"],
            ["peto - mix de dibujos", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=12196312&forum_id=255&sport=soccer"],
            ["The Zone Chile", "https://www.managerzone.com/thezone/paper.php?paper_id=18036&page=9&sport=soccer"],
            ["Tactics guide by lukasz87o/filipek4", "https://www.managerzone.com/?p=forum&sub=topic&topic_id=12766444&forum_id=12&sport=soccer&share_sport=soccer"],
            ["MZExtension/van.mz.playerAdvanced by vanjoge", "https://greasyfork.org/pt-BR/scripts/373382-van-mz-playeradvanced"],
            ["Mazyar Userscript", "https://greasyfork.org/pt-BR/scripts/476290-mazyar"],
            ["Stats Xente Userscript", "https://greasyfork.org/pt-BR/scripts/491442-stats-xente-script"],
            ["More userscripts", "https://greasyfork.org/pt-BR/users/1088808-douglasdotv"]
        ]);
        const usefulLinksList = createLinksList(resources);
        modalContent.appendChild(usefulContent);
        modalContent.appendChild(usefulLinksList);
        return modalContent;
    }

    function createUsefulContent() {
        const usefulContent = document.createElement("p");
        usefulContent.id = "useful_content";
        usefulContent.textContent = "";
        return usefulContent;
    }

    function createLinksList(hrefs) {
        const list = document.createElement("ul");
        hrefs.forEach((href, title) => {
            const listItem = document.createElement("li");
            const link = document.createElement("a");
            link.href = href;
            link.textContent = title;
            listItem.appendChild(link);
            list.appendChild(listItem);
        });
        return list;
    }

    function setUsefulLinksModal() {
        usefulLinksModal = createUsefulLinksModal();
        document.body.appendChild(usefulLinksModal);
    }

    function createInfoModal() {
        const modal = document.createElement("div");
        setUpModal(modal, "info_modal");
        const modalContent = createModalContent();
        modal.appendChild(modalContent);
        return modal;
    }

    function createModalContent() {
        const modalContent = document.createElement("div");
        const title = createTitle();
        const infoText = createInfoText();
        const feedbackText = createFeedbackText();
        modalContent.appendChild(title);
        modalContent.appendChild(infoText);
        modalContent.appendChild(feedbackText);
        return modalContent;
    }

    function createTitle() {
        const title = document.createElement("h2");
        title.id = "info_modal_title";
        title.style.fontSize = "24px";
        title.style.fontWeight = "bold";
        title.style.marginBottom = "20px";
        return title;
    }

    function createInfoText() {
        const infoText = document.createElement("p");
        infoText.id = "info_modal_info_text";
        infoText.innerHTML = USERSCRIPT_STRINGS.modalContentInfoText;
        return infoText;
    }

    function createFeedbackText() {
        const feedbackText = document.createElement("p");
        feedbackText.id = "info_modal_feedback_text";
        feedbackText.innerHTML = USERSCRIPT_STRINGS.modalContentFeedbackText;
        return feedbackText;
    }

    function setInfoModal() {
        infoModal = createInfoModal();
        document.body.appendChild(infoModal);
    }

    function setUpModal(modal, id) {
        modal.id = id;
        modal.style.display = "none";
        modal.style.position = "fixed";
        modal.style.zIndex = "1";
        modal.style.left = "50%";
        modal.style.top = "50%";
        modal.style.transform = "translate(-50%, -50%)";
        modal.style.opacity = "0";
        modal.style.transition = "opacity 0.5s ease-in-out";
    }

    function toggleModal(modal) {
        if (modal.style.display === "none" || modal.style.opacity === "0") {
            showModal(modal);
        } else {
            hideModal(modal);
        }
    }

    function showModal(modal) {
        modal.style.display = "block";
        setTimeout(function () {
            modal.style.opacity = "1";
        }, 0);
    }

    function hideModal(modal) {
        modal.style.opacity = "0";
        setTimeout(function () {
            modal.style.display = "none";
        }, 500);
    }

    function setUpModalsWindowClickListener() {
        window.addEventListener("click", function (event) {
            if (usefulLinksModal.style.display === "block" && !usefulLinksModal.contains(event.target)) {
                hideModal(usefulLinksModal);
            }
            if (infoModal.style.display === "block" && !infoModal.contains(event.target)) {
                hideModal(infoModal);
            }
        });
    }

    function createUsefulLinksButton() {
        const button = document.createElement("button");
        setUpButton(button, "useful_links_button", USERSCRIPT_STRINGS.usefulLinksButton);
        button.addEventListener("click", function (event) {
            event.stopPropagation();
            toggleModal(usefulLinksModal);
        });
        return button;
    }

    function createAboutButton() {
        const button = document.createElement("button");
        setUpButton(button, "about_button", USERSCRIPT_STRINGS.aboutButton);
        button.addEventListener("click", function (event) {
            event.stopPropagation();
            toggleModal(infoModal);
        });
        return button;
    }

    function createToggleButton() {
        const button = document.createElement('button');
        button.id = 'toggle_panel_btn';
        button.innerHTML = '⌃';
        button.title = 'Hide panel';
        return button;
    }

    function createCollapsedIcon() {
        const icon = document.createElement('div');
        icon.id = 'collapsed_icon';
        icon.innerHTML = '👇';
        icon.title = 'Show MZ Tactics Manager';
        document.body.appendChild(icon);
        return icon;
    }

    function initialize() {
        const tacticsBox = document.getElementById("tactics_box");
        if (tacticsBox) {
            activeLanguage = getActiveLanguage();
            i18next.init({
                lng: activeLanguage,
                resources: {
                    [activeLanguage]: {
                        translation: {}
                    }
                }
            }).then(async () => {
                const res = await fetch(LANG_DATA_BASE_URL + activeLanguage + ".json");
                const json = await res.json();
                i18next.addResourceBundle(activeLanguage, "translation", json);
                await checkVersion();
                const mainContainer = createMainContainer();
                const mainTitle = mainContainer.querySelector('.mz-group-main-title');
                const toggleBtn = createToggleButton();
                const collapsedIcon = createCollapsedIcon();
                mainTitle.appendChild(toggleBtn);

                let isCollapsed = false;
                function togglePanel() {
                    isCollapsed = !isCollapsed;
                    mainContainer.classList.toggle('collapsed');
                    toggleBtn.classList.toggle('collapsed');
                    collapsedIcon.classList.toggle('visible');
                }

                toggleBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    togglePanel();
                });

                collapsedIcon.addEventListener('click', () => {
                    togglePanel();
                });

                if (isFootball()) {
                    insertAfterElement(mainContainer, tacticsBox);
                }

                fetchTacticsFromGMStorage().then((data) => {
                    const tacticsDropdownMenu = document.getElementById("tactics_dropdown_menu");
                    tacticsDropdownMenu.addEventListener('click', function () {
                        if (this.value) {
                            handleTacticsSelection(this.value);
                        }
                    });
                    dropdownMenuTactics = data.tactics;
                    dropdownMenuTactics.sort((a, b) => a.name.localeCompare(b.name));
                    addTacticsToDropdownMenu(tacticsDropdownMenu, dropdownMenuTactics);
                    tacticsDropdownMenu.addEventListener("change", function () {
                        handleTacticsSelection(this.value);
                    });
                }).catch((_) => { });

                setInfoModal();
                setUsefulLinksModal();
                setUpModalsWindowClickListener();
                updateTranslation();
            });
        }
    }

    window.addEventListener("load", function () {
        initialize();
    });
})();