// ==UserScript== // @name MZ Tactics Manager // @namespace douglaskampl // @version 10.1.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 // @require https://cdnjs.cloudflare.com/ajax/libs/sweetalert2/11.10.2/sweetalert2.min.js // @license MIT // @downloadURL none // ==/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: opacity 0.3s ease, max-height 0.3s ease; max-height: 1000px; opacity: 1; color: #f8fafc; } .mz-group {background: linear-gradient(135deg, #334155 0%, #1e293b 100%); border-radius: 8px; padding: 16px; margin: 8px; border: 1px solid #334155; position: relative; } .mz-group-main-title {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); } .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: 22px; 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; } .swal2-popup.swal-mz-popup {font-family: "Space Grotesk", sans-serif; border-radius: 8px; background: #1e293b; color: #f8fafc; } .swal2-popup.swal-mz-popup .swal2-title {font-size: 18px; color: #f8fafc; } .swal2-popup.swal-mz-popup .swal2-html-container {font-size: 14px; color: #f8fafc; } .swal2-popup.swal-mz-popup .swal2-input {font-size: 14px; border: 1px solid #334155; border-radius: 6px; background: #0f172a; color: #f8fafc; } .swal2-popup.swal-mz-popup .swal2-textarea {background: #0f172a; color: #f8fafc; border: 1px solid #334155; } .swal2-popup.swal-mz-popup .swal2-confirm {background: #475569 !important; } .swal2-popup.swal-mz-popup .swal2-cancel {background: #64748b !important; } .swal2-container.swal2-backdrop-show {background: rgba(0, 0, 0, 0.6); } #hidden_trigger_button {position: absolute; visibility: hidden; pointer-events: none; }`); 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 = "10.1.0"; const VERSION_KEY = "mz_tactics_version"; const SWAL_CONSTANTS = { ICONS: { SUCCESS: 'success', ERROR: 'error', WARNING: 'warning' } }; const SWAL_CUSTOM_CLASS = { popup: 'swal-mz-popup', title: 'swal-mz-title', htmlContainer: 'swal-mz-html-container', input: 'swal-mz-input', validationMessage: 'swal-mz-validation', actions: 'swal-mz-actions', confirmButton: 'swal-mz-confirm', cancelButton: 'swal-mz-cancel', closeButton: 'swal-mz-close' }; 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! Enjoy using it!

If you got 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 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"); } function showAlert(options) { const defaultOptions = { customClass: SWAL_CUSTOM_CLASS, buttonsStyling: true, showClass: { popup: 'swal-mz-popup modalFadeIn' }, hideClass: { popup: 'modalFadeOut' }, allowOutsideClick: true, allowEscapeKey: true, width: options.html?.includes('swal-xml-input') ? '600px' : '300px', padding: '20px' }; if (options.customClass) { options.customClass = { ...SWAL_CUSTOM_CLASS, ...options.customClass }; } return Swal.fire({ ...defaultOptions, ...options }); } function showSuccessMessage(title, text) { return showAlert({ title, text, icon: SWAL_CONSTANTS.ICONS.SUCCESS }); } function showErrorMessage(title, text) { return showAlert({ title, text, icon: SWAL_CONSTANTS.ICONS.ERROR }); } 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() { const result = await showAlert({ title: USERSCRIPT_STRINGS.addWithXmlButton, html: `` + ``, focusConfirm: false, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton, preConfirm: () => { const xml = document.getElementById('swal-xml-input').value; const name = document.getElementById('swal-name-input').value; if (!xml) { Swal.showValidationMessage(USERSCRIPT_STRINGS.xmlValidationError); return false; } if (!name) { Swal.showValidationMessage(USERSCRIPT_STRINGS.noTacticNameProvidedError); return false; } if (name.length > MAX_TACTIC_NAME_LENGTH) { Swal.showValidationMessage(USERSCRIPT_STRINGS.tacticNameMaxLengthError); return false; } if (dropdownMenuTactics.some((t) => t.name === name)) { Swal.showValidationMessage(USERSCRIPT_STRINGS.alreadyExistingTacticNameError); return false; } return { xml, name }; } }); if (!result.value) { return; } try { const { xml, name } = result.value; 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 result = await showAlert({ text: USERSCRIPT_STRINGS.deleteConfirmation.replace("{}", selectedTactic.name), icon: SWAL_CONSTANTS.ICONS.WARNING, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.deleteTacticConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton }); if (!result.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), icon: SWAL_CONSTANTS.ICONS.WARNING, 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 result = await showAlert({ text: USERSCRIPT_STRINGS.clearConfirmation, icon: SWAL_CONSTANTS.ICONS.WARNING, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.clearTacticsConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton }); if (!result.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 result = await showAlert({ text: USERSCRIPT_STRINGS.resetConfirmation, icon: SWAL_CONSTANTS.ICONS.WARNING, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.resetTacticsConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton }); if (!result.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); } } async function showWelcomeMessage() { await showAlert({ html: `

${USERSCRIPT_STRINGS.welcomeMessage}

`, icon: 'info', confirmButtonText: USERSCRIPT_STRINGS.welcomeGotIt, customClass: { popup: 'swal-mz-popup', confirmButton: 'swal-mz-confirm' }, showClass: { popup: 'swal-mz-popup modalFadeIn' }, hideClass: { popup: 'modalFadeOut' } }); } 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 authorText = document.createElement("span"); authorText.textContent = "v10.1.0"; authorText.classList.add("mz-version-text"); mainTitle.appendChild(authorText); 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"); styleModalContent(modalContent); 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"); styleModalContent(modalContent); 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.textContent = ""; 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 styleModalContent(content) { content.classList.add('swal-mz-content'); } 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 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(); 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(); }); })();