MZ Tactics Manager

Userscript to manage tactics in ManagerZone

当前为 2025-03-30 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MZ Tactics Manager
// @namespace    douglaskampl
// @version      12.1.1
// @description  Userscript to manage 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_deleteValue
// @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';

    // ==============================
    // STYLES
    // ==============================
    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=Pacifico&display=swap");:root{--bg-color:#2a2d40;--text-color:#dcdde1;--highlight-color:#a37acc;--accent-color:#6c70d1;--shadow-color-dark:rgba(0,0,0,0.3);--shadow-color-light:rgba(255,255,255,0.05);--border-radius:12px;--shadow-base:3px 3px 6px var(--shadow-color-dark),-3px -3px 6px var(--shadow-color-light);--shadow-inset:inset 2px 2px 4px var(--shadow-color-dark),inset -2px -2px 4px var(--shadow-color-light);--shadow-concave:4px 4px 8px var(--shadow-color-dark),-4px -4px 8px var(--shadow-color-light),inset 1px 1px 2px var(--shadow-color-light),inset -1px -1px 2px var(--shadow-color-dark);--short-passing-color:#54a0ff;--wing-play-color:#5dd39e;--other-style-color:#ffcb77;--uncategorized-color:#8395a7;}#mz_tactics_panel{font-family:"Space Grotesk",-apple-system,sans-serif;background-color:var(--bg-color);border-radius:var(--border-radius);padding:20px;margin:12px;box-shadow:var(--shadow-base);border:1px solid rgba(255,255,255,0.05);transition:max-height 0.4s ease-out, padding 0.4s ease-out, margin 0.4s ease-out, opacity 0.3s ease-out;max-height:1000px;opacity:1;color:var(--text-color);overflow:visible;}#mz_tactics_panel.collapsed{max-height:0 !important; padding-top: 0 !important; padding-bottom: 0 !important; margin-top: 0 !important; margin-bottom: 0 !important; opacity:0 !important; border:none !important; overflow: hidden !important;}.mz-group{background-color:rgba(0,0,0,0.1);border-radius:var(--border-radius);padding:16px;margin:10px 0;box-shadow:none;border:1px solid rgba(255,255,255,0.05);position:relative;}.mz-group-main-title{display:flex;justify-content:space-between;align-items:center;color:var(--text-color);font-size:18px;font-weight:500;margin:-4px 0 12px 0;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.1);}.mz-main-title{color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:20px;font-weight:500;margin:0;padding:0;text-align:center;letter-spacing:0.2px;}.mz-version-text{color:var(--highlight-color);font-family:'Pacifico',cursive;font-size:1.1em;font-weight:400;margin-left:8px;transform:rotate(-5deg);}.mz-divider{width:50px;height:2px;background:var(--text-color);margin:10px auto 0;opacity:0.2;}#toggle_panel_btn{background:transparent;border:none;color:var(--text-color);cursor:pointer;padding:8px;width:32px;height:32px;border-radius:50%;margin-left:auto;font-size:18px;transition:all 0.3s ease;display:inline-flex;align-items:center;justify-content:center;}#toggle_panel_btn:hover{background:rgba(255,255,255,0.1);}#collapsed_icon{position:fixed;top:20px;right:20px;background:var(--bg-color);border-radius:50%;width:48px;height:48px;display:flex;align-items:center;justify-content:center;cursor:pointer;opacity:0;transition:all 0.3s ease;transform:scale(0);box-shadow:var(--shadow-base);z-index:1000;color:var(--text-color);font-size:16px; font-weight: bold; border: 1px solid rgba(255,255,255,0.1);}#collapsed_icon.visible{opacity:1;transform:scale(1);}#collapsed_icon:hover{transform:scale(1.05);box-shadow:0 0 15px rgba(163, 122, 204, 0.5);}#mz_tactics_panel .mzbtn{display:inline-flex;align-items:center;justify-content:center;padding:8px 14px;margin:4px;font-family:"Space Grotesk",sans-serif;font-size:13px;font-weight:500;color:var(--text-color);background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:8px;cursor:pointer;transition:all 0.2s ease;min-height:36px;box-shadow: 0 1px 2px rgba(0,0,0,0.1);}#mz_tactics_panel .mzbtn:hover{background:rgba(255,255,255,0.1);border-color:rgba(255,255,255,0.2);transform:translateY(-1px);box-shadow: 0 3px 6px rgba(0,0,0,0.15);}#mz_tactics_panel .mzbtn:active{background:rgba(0,0,0,0.1);transform:translateY(0);box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);}#mz_tactics_panel select{font-family:"Space Grotesk",sans-serif;font-size:14px;color:var(--text-color);padding:8px 14px;border:1px solid rgba(255,255,255,0.1);border-radius:8px;background-color:rgba(255,255,255,0.05);cursor:pointer;margin:0 4px;transition:all 0.2s ease;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml;utf8,<svg fill='%23dcdde1' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/></svg>");background-repeat:no-repeat;background-position:right 10px top 50%;padding-right:30px;height:36px;box-sizing:border-box;}#mz_tactics_panel select:hover{background:rgba(255,255,255,0.1);border-color:rgba(255,255,255,0.2);}#mz_tactics_panel select:focus{outline:none;border-color:var(--accent-color);box-shadow:0 0 0 2px rgba(108, 112, 209, 0.3);}#mz_tactics_panel select option{background-color:var(--bg-color);color:var(--text-color);padding:5px 10px;}.tactics-selector-section{margin-bottom:12px;}.tactics-selector-label{display:none;}#language_flag{height:12px;width:16px;margin:6px;border:none;border-radius:4px;}#info_modal,#useful_links_modal{background:var(--bg-color);padding:24px;border-radius:var(--border-radius);color:var(--text-color);width:90%;max-width:500px;box-shadow:var(--shadow-base); border: 1px solid rgba(255,255,255,0.1);}#info_modal a,#useful_links_modal a{color:var(--accent-color);text-decoration:none;transition:color 0.3s ease;}#info_modal a:hover,#useful_links_modal a:hover{color:var(--highlight-color); text-decoration: underline;}#info_modal ul,#useful_links_modal ul{list-style:none;padding:0;}#info_modal ul li,#useful_links_modal ul li{margin:12px 0;padding:8px 12px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);transition:all 0.3s ease;}#info_modal ul li:hover,#useful_links_modal ul li:hover{background:rgba(255,255,255,0.1);}#mz-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:rgba(42, 45, 64, 0.8);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:10000;opacity:0;transition:opacity 0.3s ease;}#mz-modal-container{background:var(--bg-color);border-radius:var(--border-radius);padding:24px;box-shadow:0 10px 25px rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.1);max-width:500px;width:90%;transform:scale(0.9);transition:transform 0.3s ease;color:var(--text-color);font-family:"Space Grotesk",-apple-system,sans-serif;}#mz-modal-overlay.active{opacity:1;}#mz-modal-overlay.active #mz-modal-container{transform:scale(1);}#mz-modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;border-bottom:1px solid rgba(255,255,255,0.1);padding-bottom:12px;}#mz-modal-title{font-size:20px;font-weight:500;margin:0;}#mz-modal-close{background:transparent;border:none;color:var(--text-color);font-size:22px;cursor:pointer;transition:all 0.3s ease;padding:0;width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:50%;}#mz-modal-close:hover{background:rgba(255,255,255,0.1);color:var(--highlight-color);}#mz-modal-content{margin-bottom:24px;white-space:pre-line;line-height:1.5;}#mz-modal-input{width:calc(100% - 32px);background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);color:var(--text-color);padding:14px 16px;border-radius:8px;font-family:"Space Grotesk",sans-serif;font-size:15px;margin-bottom:20px;transition:all 0.3s ease;box-sizing:border-box;}#mz-modal-input:focus{outline:none;border-color:var(--accent-color);box-shadow:0 0 0 2px rgba(108, 112, 209, 0.3);background:rgba(255,255,255,0.08);}#mz-modal-buttons{display:flex;justify-content:flex-start;gap:12px;}.mz-modal-btn{display:inline-flex;align-items:center;justify-content:center;padding:10px 18px;font-family:"Space Grotesk",sans-serif;font-size:15px;font-weight:500;color:var(--text-color);background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:8px;cursor:pointer;transition:all 0.2s ease;min-width:90px;box-shadow: 0 1px 2px rgba(0,0,0,0.1);}.mz-modal-btn:hover{background:rgba(255,255,255,0.1);border-color:rgba(255,255,255,0.2);transform:translateY(-1px);box-shadow: 0 3px 6px rgba(0,0,0,0.15);}.mz-modal-btn:active{background:rgba(0,0,0,0.1);transform:translateY(0);box-shadow: inset 0 1px 2px rgba(0,0,0,0.2);}.mz-modal-btn.primary{background:var(--accent-color);color:white;font-weight:600; border: none;}.mz-modal-btn.primary:hover{background:var(--highlight-color);}.mz-modal-btn.cancel{background:transparent;color:#aaa;border:1px solid rgba(255,255,255,0.1);}.mz-modal-btn.cancel:hover{background:rgba(255,255,255,0.05);color:var(--text-color); border-color:rgba(255,255,255,0.2);}.mz-modal-icon{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;margin-right:14px;background:rgba(0,0,0,0.1);}.mz-modal-icon.success{color:#5dd39e; background: rgba(93, 211, 158, 0.1);}.mz-modal-icon.error{color:#ff6b6b; background: rgba(255, 107, 107, 0.1);}.mz-modal-icon.info{color:#54a0ff; background: rgba(84, 160, 255, 0.1);}.mz-modal-title-with-icon{display:flex;align-items:center;}.tactics-selector-container{position:relative;width:100%; display: flex; align-items: center; gap: 8px;}.tactics-dropdown-container{display:flex;flex-wrap:nowrap;gap:8px;margin-top:0; flex-grow: 1; align-items: center;}.tactics-search-box{width:160px !important;padding:8px 12px;margin-bottom:0 !important;border:1px solid rgba(255,255,255,0.1);border-radius:8px;background-color:rgba(255,255,255,0.05);color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:14px;box-sizing:border-box;height:36px;transition:all 0.2s ease;position:relative; flex-shrink: 0;}.tactics-search-box:focus{outline:none;border-color:var(--accent-color);box-shadow:0 0 0 2px rgba(108, 112, 209, 0.3);background:rgba(255,255,255,0.08);}.tactics-search-box.filtering{border-bottom:2px solid var(--highlight-color);animation:pulse-border 1.5s infinite;}@keyframes pulse-border{0%{border-color:var(--highlight-color);}50%{border-color:transparent;}100%{border-color:var(--highlight-color);}}.tactics-filter-tabs{display:flex;margin:0;padding-bottom:0; overflow-x: auto; flex-shrink: 1; min-width: 100px; max-width: 300px; align-items: center; height: 36px; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.2) transparent;}.tactics-filter-tab{position:relative; display:inline-flex; align-items: center; padding:4px 10px;margin-right:6px;border:1px solid transparent;border-radius:12px; background-color:transparent;color:var(--text-color);opacity:0.7;font-family:"Space Grotesk",sans-serif;font-size:11px; cursor:pointer;white-space:nowrap;transition:all 0.2s ease; flex-shrink: 0; height: 24px; line-height: 16px;}.tactics-filter-tab:hover{background-color:rgba(255,255,255,0.08);opacity:1;}.tactics-filter-tab.active{background-color: var(--category-color, rgba(255,255,255,0.15)); border-color:transparent;font-weight:500;opacity:1; color: #fff; text-shadow: 0 0 3px rgba(0,0,0,0.5);}.remove-category-btn{margin-left:5px; font-weight: bold; font-size: 10px; color:rgba(255,255,255,0.5); background:rgba(0,0,0,0.2); border-radius: 50%; width: 14px; height: 14px; display: inline-flex; justify-content: center; align-items: center; line-height: 14px; cursor: pointer; transition: all 0.2s ease;}.tactics-filter-tab:hover .remove-category-btn{color:rgba(255,255,255,0.8); background:rgba(0,0,0,0.4);}.remove-category-btn:hover{color:#fff; background:rgba(255,50,50,0.7); transform: scale(1.1);}.tactics-dropdown-wrapper{flex:1;min-width:180px;position:relative; flex-grow: 1; flex-shrink: 1;}.tactics-style-indicator{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;}.tactics-style-indicator.short_passing{background-color:var(--short-passing-color);}.tactics-style-indicator.wing_play{background-color:var(--wing-play-color);}.tactics-style-indicator.other{background-color:var(--other-style-color);}.tactics-style-indicator.uncategorized{background-color:var(--uncategorized-color);}.tactics-category-header{color:rgba(220, 221, 225, 0.7);font-size:12px;font-weight:600;padding:4px 10px;background:rgba(0,0,0,0.2);margin-top:4px;border-radius:4px;}#category-selector{width:100%;margin-top:10px;padding:10px 12px;border:1px solid rgba(255,255,255,0.1);border-radius:8px;background-color:rgba(255,255,255,0.05);color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:14px;box-sizing:border-box;}#category-selector option{padding:8px; background-color: var(--bg-color);}.category-selection-container{margin-top:15px;margin-bottom:5px;}.category-selection-label{display:block;margin-bottom:5px;font-size:14px;color:var(--text-color);opacity:0.9;}.mz-language-container{display:flex;align-items:center;gap:10px;}.mz-language-label{font-size:14px;font-weight:500;}.mz-language-dropdown{flex:1;}.new-category-input-container{margin-top:10px;display:none;}.new-category-input-container.visible{display:block;}#new-category-input{width:100%;padding:10px 12px;border:1px solid rgba(255,255,255,0.1);border-radius:8px;background-color:rgba(255,255,255,0.05);color:var(--text-color);font-family:"Space Grotesk",sans-serif;font-size:14px;box-sizing:border-box;}#new-category-input:focus{outline:none;border-color:var(--accent-color);box-shadow:0 0 0 2px rgba(108, 112, 209, 0.3);background:rgba(255,255,255,0.08);}#tactics_selector{height:36px;box-sizing:border-box;max-height:300px;overflow-y:auto; width: 100%;}#tactics_selector option{animation:fadeIn 0.3s ease;background-color:var(--bg-color);padding:8px 12px;margin:2px 0; color: var(--text-color);}#tactics_selector optgroup{background-color:#580000;border-left:3px solid var(--accent-color);font-weight:600;padding:8px 10px;margin-top:5px;border-radius:6px;color:var(--text-color);}@keyframes fadeIn{from{opacity:0;transform:translateY(-5px);}to{opacity:1;transform:translateY(0);}}@keyframes shake{0%,100%{transform:translateX(0);}25%{transform:translateX(-2px);}50%{transform:translateX(0);}75%{transform:translateX(2px);}}.tactics-dropdown-wrapper.filtering:after{content:'';position:absolute;width:10px;height:10px;border-radius:50%;background-color:var(--highlight-color);right:40px;top:13px;animation:pulse 1.5s infinite;}@keyframes pulse{0%{transform:scale(0.8);opacity:0.5;}50%{transform:scale(1.2);opacity:1;}100%{transform:scale(0.8);opacity:0.5;}}.action-buttons-section{display: flex; flex-wrap: wrap; margin-top: 10px; justify-content: flex-start; gap: 4px;}.action-dropdown-menu{position:absolute;background-color:var(--bg-color);border-radius:8px;box-shadow:0 5px 15px rgba(0,0,0,0.3);padding:8px;z-index:100;display:none;min-width:150px; border: 1px solid rgba(255,255,255,0.1);}.action-dropdown-menu button{display:block;width:100%;margin:4px 0;text-align:left; background: transparent; border: none; box-shadow: none;}.action-dropdown-menu button:hover{background:rgba(255,255,255,0.1); color: var(--highlight-color); transform: none; box-shadow: none;}.footer-actions{display:flex;align-items:center;gap:10px;}#combined_info_modal_content > div { margin-bottom: 20px; } #combined_info_modal_content h3 { font-size: 18px; margin-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 5px; color: var(--highlight-color); }#manage_action_dropdown_menu{max-height:200px;overflow-y:auto;overflow-x:hidden;}`);

    // ==============================
    // CONSTANTS & CONFIG
    // ==============================
    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 中国地区 = ['CN', 'HK', 'MO', 'TW'];
    const CDN_URLS = {
        default: {
            tactics: 'https://u18mz.vercel.app/mz/userscript/tactics/json/defaultTactics.json',
            lang: 'https://u18mz.vercel.app/mz/userscript/tactics/json/lang/'
        },
        china: {
            tactics: 'https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/defaultTactics.json',
            lang: '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 SCRIPT_VERSION = '12.1.1';
    const DISPLAY_VERSION = '12';
    const VERSION_KEY = 'mz_tactics_version';
    const COLLAPSED_KEY = 'mz_tactics_collapsed';
    const CATEGORIES_STORAGE_KEY = 'mz_tactics_categories';
    const TACTICS_STORAGE_KEY = 'ls_tactics';
    const DEFAULT_CATEGORIES = {
        'short_passing': { id: 'short_passing', name: 'Short Passing', color: '#54a0ff' },
        'wing_play': { id: 'wing_play', name: 'Wing Play', color: '#5dd39e' }
    };
    const NEW_CATEGORY_ID = 'new_category';
    const OTHER_CATEGORY_ID = 'other';
    const USERSCRIPT_STRINGS = {
        addButton: 'Add', addCurrentTactic: 'Add Current', addWithXmlButton: 'Add via XML', manageButton: 'Manage', deleteButton: 'Delete', renameButton: 'Edit', updateButton: 'Save Positions', clearButton: 'Clear All', resetButton: 'Default', importButton: 'Import', exportButton: 'Export', infoButton: '❔', usefulLinksButton: 'Links', aboutButton: 'About', tacticNamePrompt: 'Please enter a name for the tactic', addAlert: 'Tactic {} added successfully.', deleteAlert: 'Tactic {} deleted successfully.', renameAlert: 'Tactic {} successfully edited.', updateAlert: 'Tactic {} updated successfully.', clearAlert: 'Tactics cleared successfully.', resetAlert: 'Default tactics loaded.', importAlert: 'Tactics imported successfully.', exportAlert: 'Tactics copied to clipboard.', deleteConfirmation: 'Do you really want to delete {}?', updateConfirmation: 'Do you really want to update {}?', clearConfirmation: 'Do you really want to clear all saved tactics?', resetConfirmation: 'Reset to default tactics? This will remove all your custom tactics.', invalidTacticError: 'Invalid tactic. Ensure 11 players are on the pitch.', noTacticNameProvidedError: 'No tactic name provided.', alreadyExistingTacticNameError: 'Tactic name already exists.', tacticNameMaxLengthError: 'Tactic name is too long (max 50 chars).', noTacticSelectedError: 'No tactic selected.', duplicateTacticError: 'This formation already exists.', noChangesMadeError: 'No changes detected in player positions.', invalidImportError: 'Invalid import data. Please provide valid JSON.', modalContentInfoText: 'Manage your tactics efficiently.', modalContentFeedbackText: 'For feedback or suggestions, contact <b>douglaskampl</b> via GB/Chat.', usefulContent: 'Community Resources:', tacticsDropdownMenuLabel: 'Select a tactic:', languageDropdownMenuLabel: 'Language:', errorTitle: 'Error', doneTitle: 'Success', confirmationTitle: 'Confirmation', deleteTacticConfirmButton: 'Delete', cancelConfirmButton: 'Cancel', updateConfirmButton: 'Update', clearTacticsConfirmButton: 'Clear All', resetTacticsConfirmButton: 'Reset', addConfirmButton: 'Add', xmlValidationError: 'Invalid XML format.', xmlParsingError: 'Error parsing XML.', xmlPlaceholder: 'Paste XML here', tacticNamePlaceholder: 'Tactic name', managerTitle: 'MZ Tactics Manager', tacticActionsTitle: 'Actions', otherActionsTitle: 'Other', searchPlaceholder: 'Search...', allTacticsFilter: 'All', selectTacticButton: 'Select', openTacticsSelector: 'Browse Tactics', noTacticsFound: 'No tactics found', welcomeMessage: `Welcome to MZ Tactics Manager v${SCRIPT_VERSION}!\n\nThis version includes:\n• New UI for better space usage.\n\nEnjoy managing your tactics!`, welcomeGotIt: 'Got it!', removeCategoryConfirmation: 'Remove category "{}"? All tactics in this category will be moved to "Other".', removeCategoryAlert: 'Category "{}" removed successfully.', removeCategoryButton: 'Remove'
    };
    const ELEMENT_STRING_KEYS = {
        delete_tactic_button: 'deleteButton', rename_tactic_button: 'renameButton', update_tactic_button: 'updateButton', tactics_dropdown_menu_label: 'tacticsDropdownMenuLabel', language_dropdown_menu_label: 'languageDropdownMenuLabel', info_modal_info_text: 'modalContentInfoText', info_modal_feedback_text: 'modalContentFeedbackText',
    };
    const DEFAULT_MODAL_STRINGS = {
        ok: 'OK', cancel: 'Cancel', error: 'Error', close: '×'
    };

    // ==============================
    // GLOBAL VARIABLES
    // ==============================
    const region = isLikelyFromChina() ? 'china' : 'default';
    const defaultTacticsDataUrl = CDN_URLS[region].tactics;
    const langDataBaseUrl = CDN_URLS[region].lang;
    let dropdownMenuTactics = [];
    let activeLanguage;
    let currentFilter = 'all';
    let searchTerm = '';
    let categories = {};
    let activeDropdownMenu = null;

    // ==============================
    // MODAL & ALERT FUNCTIONS
    // ==============================
    function createModalIcon(type) {
        if (!type) return null;
        const icon = document.createElement('div');
        icon.classList.add('mz-modal-icon');
        if (type === 'success') {
            icon.classList.add('success');
            icon.innerHTML = '✓';
        } else if (type === 'error') {
            icon.classList.add('error');
            icon.innerHTML = '✗';
        }
        return icon;
    }

    function validateModalInput(input, validator, errorContainerId) {
        if (!validator) return null;
        const validationError = validator(input.value);
        const errorContainer = document.getElementById(errorContainerId) || document.createElement('div');
        errorContainer.id = errorContainerId;
        errorContainer.style.color = '#ff6b6b';
        errorContainer.style.marginTop = '-10px';
        errorContainer.style.marginBottom = '10px';
        errorContainer.style.fontSize = '13px';
        const existingError = document.getElementById(errorContainerId);
        if (existingError) existingError.remove();
        if (!validationError) {
            return null;
        }
        errorContainer.textContent = validationError;
        if (!existingError) {
            input.parentNode.insertBefore(errorContainer, input.nextSibling);
        }
        return validationError;
    }

    function closeModal(overlay, callback) {
        overlay.classList.remove('active');
        setTimeout(() => {
            if (overlay.parentNode === document.body) {
                document.body.removeChild(overlay);
            }
            if (callback) callback();
        }, 300);
    }

    function handleAlertConfirm(options, input, categorySelector, newCategoryInput, overlay, resolve) {
        if (options.input === 'text' && options.inputValidator) {
            const hasError = validateModalInput(input, options.inputValidator, 'mz-modal-error');
            if (hasError) return;
        }
        let categoryValue = null;
        let newCategoryName = null;
        if (categorySelector) {
            categoryValue = categorySelector.value;
            if (categoryValue === NEW_CATEGORY_ID && newCategoryInput) {
                newCategoryName = newCategoryInput.value.trim();
                const categoryErrorContainer = document.getElementById('new-category-error');
                if (categoryErrorContainer) categoryErrorContainer.remove();
                if (!newCategoryName) {
                    const errorText = document.createElement('div');
                    errorText.style.color = '#ff6b6b';
                    errorText.style.marginTop = '5px';
                    errorText.style.fontSize = '13px';
                    errorText.textContent = 'Category name cannot be empty';
                    errorText.id = 'new-category-error';
                    newCategoryInput.parentNode.appendChild(errorText);
                    return;
                }
                const existingCategory = Object.values(categories).find(
                    cat => cat.name.toLowerCase() === newCategoryName.toLowerCase()
                );
                if (existingCategory) {
                    const errorText = document.createElement('div');
                    errorText.style.color = '#ff6b6b';
                    errorText.style.marginTop = '5px';
                    errorText.style.fontSize = '13px';
                    errorText.textContent = 'This category already exists';
                    errorText.id = 'new-category-error';
                    newCategoryInput.parentNode.appendChild(errorText);
                    return;
                }
            }
        }
        closeModal(overlay, () => {
            if (options.input === 'text') {
                const result = { value: input ? input.value : null, isConfirmed: true };
                if (categorySelector) {
                    if (categoryValue === NEW_CATEGORY_ID && newCategoryName) {
                        const newCategoryId = generateCategoryId(newCategoryName);
                        const newCategory = {
                            id: newCategoryId,
                            name: newCategoryName,
                            color: generateCategoryColor(newCategoryName)
                        };
                        result.category = newCategory;
                        addCategory(newCategory);
                    } else {
                        result.category = categories[categoryValue] || { id: OTHER_CATEGORY_ID, name: 'Other', color: '#ffcb77' };
                    }
                }
                resolve(result);
            } else {
                resolve({ isConfirmed: true });
            }
        });
    }

    function handleAlertCancel(overlay, resolve) {
        closeModal(overlay, () => {
            resolve({ isConfirmed: false, value: null });
        });
    }

    function setUpKeyboardHandler(handleConfirm, handleCancel, input) {
        return function (e) {
            if (e.key === 'Escape') {
                handleCancel();
            } else if (e.key === 'Enter' && !(input && document.activeElement !== input)) {
                if (input && input.tagName === 'TEXTAREA') {} else {
                     handleConfirm();
                }
            }
        };
    }

    function showAlert(options) {
        return new Promise((resolve) => {
            const overlay = document.createElement('div');
            overlay.id = 'mz-modal-overlay';
            const container = document.createElement('div');
            container.id = 'mz-modal-container';
            const header = document.createElement('div');
            header.id = 'mz-modal-header';
            const titleContainer = document.createElement('div');
            titleContainer.classList.add('mz-modal-title-with-icon');
            const icon = createModalIcon(options.type);
            if (icon) titleContainer.appendChild(icon);
            const title = document.createElement('h2');
            title.id = 'mz-modal-title';
            title.textContent = options.title || '';
            titleContainer.appendChild(title);
            header.appendChild(titleContainer);
            const closeBtn = document.createElement('button');
            closeBtn.id = 'mz-modal-close';
            closeBtn.innerHTML = DEFAULT_MODAL_STRINGS.close;
            header.appendChild(closeBtn);
            const content = document.createElement('div');
            content.id = 'mz-modal-content';
            if (options.htmlContent) {
                 content.appendChild(options.htmlContent);
            } else {
                 content.textContent = options.text || '';
            }
            let input;
            let categorySelector;
            let newCategoryInput;
            if (options.input === 'text') {
                input = document.createElement('input');
                input.id = 'mz-modal-input';
                input.type = 'text';
                input.value = options.inputValue || '';
                input.placeholder = options.placeholder || '';
            }
            if (options.showCategorySelector) {
                const categoryContainer = document.createElement('div');
                categoryContainer.className = 'category-selection-container';
                const categoryLabel = document.createElement('label');
                categoryLabel.className = 'category-selection-label';
                categoryLabel.textContent = 'Category:';
                categoryContainer.appendChild(categoryLabel);
                categorySelector = document.createElement('select');
                categorySelector.id = 'category-selector';

                const usedCategoryIds = new Set(dropdownMenuTactics.map(t => t.style).filter(Boolean));
                if (options.currentCategory) {
                    usedCategoryIds.add(options.currentCategory);
                }

                const availableCategories = Object.values(categories).filter(cat =>
                     cat.id === 'short_passing' ||
                     cat.id === 'wing_play' ||
                     cat.id === OTHER_CATEGORY_ID ||
                     usedCategoryIds.has(cat.id)
                );

                availableCategories.sort((a, b) => {
                    if (a.id === OTHER_CATEGORY_ID) return 1;
                    if (b.id === OTHER_CATEGORY_ID) return -1;
                    return a.name.localeCompare(b.name);
                });

                availableCategories.forEach(category => {
                    if (category.id !== OTHER_CATEGORY_ID) {
                        const option = document.createElement('option');
                        option.value = category.id;
                        option.textContent = category.name;
                        categorySelector.appendChild(option);
                    }
                });

                const otherOption = document.createElement('option');
                otherOption.value = OTHER_CATEGORY_ID;
                otherOption.textContent = getCategoryName(OTHER_CATEGORY_ID);
                categorySelector.appendChild(otherOption);

                const addNewOption = document.createElement('option');
                addNewOption.value = NEW_CATEGORY_ID;
                addNewOption.textContent = '+ New category';
                categorySelector.appendChild(addNewOption);

                if (options.currentCategory && categories[options.currentCategory]) {
                     categorySelector.value = options.currentCategory;
                } else {
                     categorySelector.value = OTHER_CATEGORY_ID;
                }

                categorySelector.addEventListener('change', function() {
                    const newCategoryContainer = document.querySelector('.new-category-input-container');
                    if (this.value === NEW_CATEGORY_ID) {
                        newCategoryContainer.classList.add('visible');
                        newCategoryInput.focus();
                    } else {
                        newCategoryContainer.classList.remove('visible');
                        const categoryErrorContainer = document.getElementById('new-category-error');
                        if (categoryErrorContainer) categoryErrorContainer.remove();
                    }
                });
                categoryContainer.appendChild(categorySelector);
                const newCategoryContainer = document.createElement('div');
                newCategoryContainer.className = 'new-category-input-container';
                newCategoryInput = document.createElement('input');
                newCategoryInput.id = 'new-category-input';
                newCategoryInput.type = 'text';
                newCategoryInput.placeholder = 'New category name';
                newCategoryContainer.appendChild(newCategoryInput);
                categoryContainer.appendChild(newCategoryContainer);
                if (content.textContent || content.hasChildNodes()) {
                     categoryContainer.style.marginTop = '15px';
                }
                content.appendChild(categoryContainer);
            }
            const buttons = document.createElement('div');
            buttons.id = 'mz-modal-buttons';
            const confirmHandler = () => {
                handleAlertConfirm(options, input, categorySelector, newCategoryInput, overlay, resolve);
            };
            const cancelHandler = () => handleAlertCancel(overlay, resolve);
            const confirmBtn = document.createElement('button');
            confirmBtn.classList.add('mz-modal-btn', 'primary');
            confirmBtn.textContent = options.confirmButtonText || DEFAULT_MODAL_STRINGS.ok;
            confirmBtn.addEventListener('click', confirmHandler);
            buttons.appendChild(confirmBtn);
            if (options.showCancelButton) {
                const cancelBtn = document.createElement('button');
                cancelBtn.classList.add('mz-modal-btn', 'cancel');
                cancelBtn.textContent = options.cancelButtonText || DEFAULT_MODAL_STRINGS.cancel;
                cancelBtn.addEventListener('click', cancelHandler);
                buttons.appendChild(cancelBtn);
            }
            closeBtn.addEventListener('click', cancelHandler);
            const keydownHandler = setUpKeyboardHandler(confirmHandler, cancelHandler, input);
            document.addEventListener('keydown', keydownHandler);
            container.appendChild(header);
            container.appendChild(content);
            if (input) container.appendChild(input);
            container.appendChild(buttons);
            overlay.appendChild(container);
            document.body.appendChild(overlay);
            setTimeout(() => {
                overlay.classList.add('active');
                if (input) input.focus();
                if (categorySelector && categorySelector.value === NEW_CATEGORY_ID) {
                    newCategoryInput.focus();
                }
            }, 10);
            overlay.addEventListener('transitionend', () => {
                if (!overlay.classList.contains('active')) {
                    document.removeEventListener('keydown', keydownHandler);
                }
            });
        });
    }

    function showSuccessMessage(title, text) {
        return showAlert({
            title: title || USERSCRIPT_STRINGS.doneTitle,
            text: text,
            type: 'success'
        });
    }

    function showErrorMessage(title, text) {
        return showAlert({
            title: title || USERSCRIPT_STRINGS.errorTitle,
            text: text,
            type: 'error'
        });
    }

    function showWelcomeMessage() {
        return showAlert({
            title: 'Welcome!',
            text: USERSCRIPT_STRINGS.welcomeMessage,
            confirmButtonText: USERSCRIPT_STRINGS.welcomeGotIt
        });
    }

    // ==============================
    // UTILITY FUNCTIONS
    // ==============================
    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 insertAfterElement(something, element) {
        element.parentNode.insertBefore(something, element.nextSibling);
    }

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

    function isLikelyFromChina() {
        const lang = navigator.language || navigator.userLanguage || ''; const ua = navigator.userAgent.toLowerCase(); const region = navigator.language?.split('-')[1]?.toUpperCase() || '';
        return lang.startsWith('zh-') || ua.includes('micromessenger') || ua.includes('qq') || ua.includes('ucbrowser') || 中国地区.includes(region);
    }

    // ==============================
    // STORAGE & DATA HANDLING
    // ==============================
    async function fetchTacticsFromGMStorage() {
        const storedTactics = GM_getValue(TACTICS_STORAGE_KEY);
        if (storedTactics) {
            return storedTactics;
        } else {
            const jsonTactics = await fetchTacticsFromJson();
            storeTacticsInGMStorage(jsonTactics);
            return jsonTactics;
        }
    }

    async function fetchTacticsFromJson() {
        try {
            const response = await fetch(defaultTacticsDataUrl);
            if (!response.ok) {
                throw new Error();
            }
            return await response.json();
        } catch (_e) {
            const fallbackURL = (defaultTacticsDataUrl === CDN_URLS.default.tactics) ? CDN_URLS.china.tactics : CDN_URLS.default.tactics;
            const fallbackResponse = await fetch(fallbackURL);
            return await fallbackResponse.json();
        }
    }

    function storeTacticsInGMStorage(data) {
        GM_setValue(TACTICS_STORAGE_KEY, data);
    }

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

    async function saveTacticToStorage(tactic) {
        const tacticsData = (await GM_getValue(TACTICS_STORAGE_KEY)) || { tactics: [] };
        tacticsData.tactics.push(tactic);
        await GM_setValue(TACTICS_STORAGE_KEY, 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';
        }
    }

    // ==============================
    // TACTIC/FORMATION LOGIC
    // ==============================
    function generateUniqueId(coordinates) {
        coordinates.sort((a, b) => { if (a[1] !== b[1]) { return a[1] - b[1]; } else { return a[0] - b[0]; } });
        const coordString = coordinates.map(coord => `${coord[0]},${coord[1]}`).join(';');
        return sha256Hash(coordString);
    }

    function handleTacticsSelection(tacticName) {
        if (!tacticName) return;
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const selectedTactic = dropdownMenuTactics.find((tacticData) => tacticData.name === tacticName);
        if (selectedTactic) {
            if (outfieldPlayers.length < MIN_OUTFIELD_PLAYERS) {
                const hiddenTriggerButton = document.getElementById('hidden_trigger_button');
                if(hiddenTriggerButton) 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) {
            if (coordinates[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);
        if (formationTextElement) {
            const defs = formationTextElement.querySelector('.defs');
            const mids = formationTextElement.querySelector('.mids');
            const atts = formationTextElement.querySelector('.atts');
            if (defs) defs.textContent = formation.defenders;
            if (mids) mids.textContent = formation.midfielders;
            if (atts) 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;
    }

    // ==============================
    // CATEGORY MANAGEMENT
    // ==============================
    function generateCategoryId(categoryName) {
        return sha256Hash(categoryName.toLowerCase()).substring(0, 10);
    }

    function generateCategoryColor(categoryName) {
        const hash = sha256Hash(categoryName);
        const hue = parseInt(hash.substring(0, 6), 16) % 360;
        const saturation = 50 + (parseInt(hash.substring(6, 8), 16) % 30);
        const lightness = 55 + (parseInt(hash.substring(8, 10), 16) % 15);
        return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
    }

    function addCategory(category) {
        categories[category.id] = category;
        saveCategories();
    }

    function saveCategories() {
        GM_setValue(CATEGORIES_STORAGE_KEY, categories);
    }

    function loadCategories() {
        const storedCategories = GM_getValue(CATEGORIES_STORAGE_KEY);
        if (storedCategories && typeof storedCategories === 'object') {
            categories = storedCategories;
            if (!categories['short_passing']) categories['short_passing'] = DEFAULT_CATEGORIES['short_passing'];
            if (!categories['wing_play']) categories['wing_play'] = DEFAULT_CATEGORIES['wing_play'];
        } else {
            categories = { ...DEFAULT_CATEGORIES };
            saveCategories();
        }
        if (!categories[OTHER_CATEGORY_ID]) {
            categories[OTHER_CATEGORY_ID] = { id: OTHER_CATEGORY_ID, name: 'Other', color: '#ffcb77' };
        }
    }

    function loadCategoryColor(categoryId) {
        if (categories[categoryId]) {
            return categories[categoryId].color;
        } else if (categoryId === 'short_passing') {
            return DEFAULT_CATEGORIES.short_passing.color;
        } else if (categoryId === 'wing_play') {
            return DEFAULT_CATEGORIES.wing_play.color;
        } else if (categoryId === 'other' || !categoryId) {
            return '#ffcb77';
        } else {
            return '#8395a7';
        }
    }

    function getCategoryName(categoryId) {
        if (categories[categoryId]) {
            return categories[categoryId].name;
        } else if (categoryId === 'short_passing') {
            return 'Short Passing';
        } else if (categoryId === 'wing_play') {
            return 'Wing Play';
        } else if (categoryId === OTHER_CATEGORY_ID || !categoryId) {
            return 'Other';
        } else {
            return categoryId || 'Uncategorized';
        }
    }

    async function removeCategory(categoryId) {
        if (!categoryId || categoryId === 'all' || categoryId === OTHER_CATEGORY_ID) {
            console.error("Attempted to remove non-removable category:", categoryId);
            return;
        }

        const categoryName = getCategoryName(categoryId);
        const confirmResult = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle,
            text: USERSCRIPT_STRINGS.removeCategoryConfirmation.replace('{}', categoryName),
            showCancelButton: true,
            confirmButtonText: USERSCRIPT_STRINGS.removeCategoryButton,
            cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton,
            type: 'error'
        });

        if (!confirmResult.isConfirmed) {
            return;
        }

        const tacticsData = await GM_getValue(TACTICS_STORAGE_KEY, { tactics: [] });
        let tacticsUpdated = false;

        tacticsData.tactics = tacticsData.tactics.map(tactic => {
            if (tactic.style === categoryId) {
                tactic.style = OTHER_CATEGORY_ID;
                tacticsUpdated = true;
            }
            return tactic;
        });

        dropdownMenuTactics = dropdownMenuTactics.map(tactic => {
            if (tactic.style === categoryId) {
                tactic.style = OTHER_CATEGORY_ID;
            }
            return tactic;
        });

        if (tacticsUpdated) {
            await GM_setValue(TACTICS_STORAGE_KEY, tacticsData);
        }

        if (categoryId !== 'short_passing' && categoryId !== 'wing_play') {
            delete categories[categoryId];
            saveCategories();
        }

        if (currentFilter === categoryId) {
            currentFilter = 'all';
        }

        updateTacticsDropdown();
        updateFilterTabs();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.removeCategoryAlert.replace('{}', categoryName));
    }

    // ==============================
    // TACTIC ACTIONS (ADD, DELETE, RENAME, UPDATE, etc.)
    // ==============================
    async function addNewTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        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: '', placeholder: USERSCRIPT_STRINGS.tacticNamePlaceholder,
            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;
                return null;
            },
            showCategorySelector: true, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!result.isConfirmed || !result.value) {
            return;
        }
        const tacticName = result.value;
        const tacticCategory = result.category.id;
        const tactic = { name: tacticName, coordinates: tacticCoordinates, id: tacticId, style: tacticCategory };
        await saveTacticToStorage(tactic);
        dropdownMenuTactics.push(tactic);
        dropdownMenuTactics.sort((a, b) => a.name.localeCompare(b.name));
        updateTacticsDropdown();
        updateFilterTabs();
        const tacticsSelector = document.getElementById('tactics_selector');
        tacticsSelector.value = tactic.name;
        handleTacticsSelection(tactic.name);
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.addAlert.replace('{}', tactic.name));
    }

    async function addNewTacticWithXml() {
        const xmlResult = await showAlert({
            title: USERSCRIPT_STRINGS.xmlPlaceholder, input: 'text', showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!xmlResult.isConfirmed || !xmlResult.value) {
            return;
        }
        const xml = xmlResult.value;
        const nameResult = await showAlert({
            title: USERSCRIPT_STRINGS.tacticNamePrompt, input: 'text', placeholder: USERSCRIPT_STRINGS.tacticNamePlaceholder,
            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;
                return null;
            },
            showCategorySelector: true, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.addConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!nameResult.isConfirmed || !nameResult.value) {
            return;
        }
        const name = nameResult.value;
        const category = nameResult.category.id;
        try {
            const newTactic = await convertXmlToTacticJson(xml, name);
            newTactic.style = category;
            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);
            dropdownMenuTactics.push(newTactic);
            dropdownMenuTactics.sort((a, b) => a.name.localeCompare(b.name));
            updateTacticsDropdown();
            updateFilterTabs();
            const tacticsSelector = document.getElementById('tactics_selector');
            tacticsSelector.value = newTactic.name;
            handleTacticsSelection(newTactic.name);
            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.addAlert.replace('{}', newTactic.name));
        } catch (e) {
            console.error('XMLError:', e);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.xmlParsingError + (e.message ? `: ${e.message}` : ''));
        }
    }

    async function deleteTactic() {
        const tacticsSelector = document.getElementById('tactics_selector');
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const confirmResult = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle, text: USERSCRIPT_STRINGS.deleteConfirmation.replace('{}', selectedTactic.name), showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.deleteTacticConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!confirmResult.isConfirmed) {
            return;
        }
        const deletedCategoryId = selectedTactic.style;
        const tacticsData = (await GM_getValue(TACTICS_STORAGE_KEY)) || { tactics: [] };
        tacticsData.tactics = tacticsData.tactics.filter((tactic) => tactic.id !== selectedTactic.id);
        await GM_setValue(TACTICS_STORAGE_KEY, tacticsData);
        dropdownMenuTactics = dropdownMenuTactics.filter((tactic) => tactic.id !== selectedTactic.id);
        const categoryStillHasTactics = dropdownMenuTactics.some(tactic => tactic.style === deletedCategoryId);
        if (!categoryStillHasTactics && deletedCategoryId !== 'short_passing' && deletedCategoryId !== 'wing_play' && deletedCategoryId !== OTHER_CATEGORY_ID) {
             delete categories[deletedCategoryId];
             saveCategories();
             if (currentFilter === deletedCategoryId) {
                  currentFilter = 'all';
             }
        }

        updateTacticsDropdown();
        updateFilterTabs();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.deleteAlert.replace('{}', selectedTactic.name));
    }

    async function renameTactic() {
        const tacticsSelector = document.getElementById('tactics_selector');
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        const oldName = selectedTactic.name;
        const oldCategory = selectedTactic.style;
        const result = await showAlert({
            title: 'Edit Tactic', input: 'text', inputValue: oldName, placeholder: USERSCRIPT_STRINGS.tacticNamePlaceholder,
            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;
                return null;
            },
            showCategorySelector: true, currentCategory: selectedTactic.style, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.updateConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
        });
        if (!result.isConfirmed || (!result.value && !result.category) ) {
            return;
        }
        const newName = result.value || oldName;
        const newCategory = result.category?.id || oldCategory;
        if (newName === oldName && newCategory === oldCategory) {
            return;
        }
        const categoryChanged = oldCategory !== newCategory;
        const tacticsData = (await GM_getValue(TACTICS_STORAGE_KEY)) || { tactics: [] };
        tacticsData.tactics = tacticsData.tactics.map((tactic) => {
            if (tactic.id === selectedTactic.id) {
                tactic.name = newName;
                tactic.style = newCategory;
            }
            return tactic;
        });
        await GM_setValue(TACTICS_STORAGE_KEY, tacticsData);

        dropdownMenuTactics = dropdownMenuTactics.map((tactic) => {
            if (tactic.id === selectedTactic.id) {
                tactic.name = newName;
                tactic.style = newCategory;
            }
            return tactic;
        });

        if (categoryChanged) {
            const oldCategoryStillHasTactics = dropdownMenuTactics.some(tactic => tactic.style === oldCategory);
            if (!oldCategoryStillHasTactics && oldCategory !== 'short_passing' && oldCategory !== 'wing_play' && oldCategory !== OTHER_CATEGORY_ID) {
                 delete categories[oldCategory];
                 saveCategories();
                 if (currentFilter === oldCategory) {
                     currentFilter = 'all';
                 }
            }
        }

        dropdownMenuTactics.sort((a, b) => a.name.localeCompare(b.name));
        updateTacticsDropdown();
        updateFilterTabs();
        tacticsSelector.value = newName;
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.renameAlert.replace('{}', newName));
    }

    async function updateTactic() {
        const outfieldPlayers = Array.from(document.querySelectorAll(OUTFIELD_PLAYERS_SELECTOR));
        const tacticsSelector = document.getElementById('tactics_selector');
        const selectedTactic = dropdownMenuTactics.find((tactic) => tactic.name === tacticsSelector.value);
        if (!selectedTactic) {
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.noTacticSelectedError);
            return;
        }
        if (!validateTacticPlayerCount(outfieldPlayers)) {
            return;
        }
        const updatedCoordinates = outfieldPlayers.map((player) => [parseInt(player.style.left), parseInt(player.style.top)]);
        const newId = generateUniqueId(updatedCoordinates);
        const tacticsData = (await GM_getValue(TACTICS_STORAGE_KEY)) || { 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({
            title: USERSCRIPT_STRINGS.confirmationTitle, 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;
            }
        }
        const memoryTactic = dropdownMenuTactics.find(t => t.id === selectedTactic.id);
        if (memoryTactic) {
             memoryTactic.coordinates = updatedCoordinates;
             memoryTactic.id = newId;
        }
        await GM_setValue(TACTICS_STORAGE_KEY, tacticsData);
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.updateAlert.replace('{}', selectedTactic.name));
    }

    async function clearTactics() {
        const confirmResult = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle, text: USERSCRIPT_STRINGS.clearConfirmation, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.clearTacticsConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton, type: 'error'
        });
        if (!confirmResult.isConfirmed) {
            return;
        }
        await GM_deleteValue(TACTICS_STORAGE_KEY);
        dropdownMenuTactics = [];
        currentFilter = 'all';
        categories = { ...DEFAULT_CATEGORIES };
        if (!categories[OTHER_CATEGORY_ID]) {
            categories[OTHER_CATEGORY_ID] = { id: OTHER_CATEGORY_ID, name: 'Other', color: '#ffcb77' };
        }
        saveCategories();
        updateTacticsDropdown();
        updateFilterTabs();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.clearAlert);
    }

    async function resetTactics() {
        const confirmResult = await showAlert({
            title: USERSCRIPT_STRINGS.confirmationTitle, text: USERSCRIPT_STRINGS.resetConfirmation, showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.resetTacticsConfirmButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton, type: 'error'
        });
        if (!confirmResult.isConfirmed) {
            return;
        }
        await GM_deleteValue(TACTICS_STORAGE_KEY);
        currentFilter = 'all';
        categories = { ...DEFAULT_CATEGORIES };
        if (!categories[OTHER_CATEGORY_ID]) {
            categories[OTHER_CATEGORY_ID] = { id: OTHER_CATEGORY_ID, name: 'Other', color: '#ffcb77' };
        }
        saveCategories();
        try {
            const response = await fetch(defaultTacticsDataUrl);
            if (!response.ok) {
                throw new Error();
            }
            const data = await response.json();
            const defaultTactics = data.tactics || [];
            defaultTactics.forEach(tactic => {
                if (!tactic.hasOwnProperty('style')) {
                    tactic.style = OTHER_CATEGORY_ID;
                }
            });
            await GM_setValue(TACTICS_STORAGE_KEY, { tactics: defaultTactics });
            dropdownMenuTactics = defaultTactics;
            dropdownMenuTactics.sort((a, b) => a.name.localeCompare(b.name));
        } catch (error) {
            console.log();
            try {
                 const fallbackURL = (defaultTacticsDataUrl === CDN_URLS.default.tactics) ? CDN_URLS.china.tactics : CDN_URLS.default.tactics;
                 const fallbackResponse = await fetch(fallbackURL);
                 if (!fallbackResponse.ok) {
                     throw new Error();
                 }
                 const fallbackData = await fallbackResponse.json();
                 const defaultTactics = fallbackData.tactics || [];
                 defaultTactics.forEach(tactic => {
                     if (!tactic.hasOwnProperty('style')) {
                         tactic.style = OTHER_CATEGORY_ID;
                     }
                 });
                 await GM_setValue(TACTICS_STORAGE_KEY, { tactics: defaultTactics });
                 dropdownMenuTactics = defaultTactics;
                 dropdownMenuTactics.sort((a, b) => a.name.localeCompare(b.name));
            } catch (e) {
                 console.error(e);
                 dropdownMenuTactics = [];
                 await GM_setValue(TACTICS_STORAGE_KEY, { tactics: [] });
                 await showErrorMessage('Error', 'Could not load default tactics.');
                 return;
            }
        }
        updateTacticsDropdown();
        updateFilterTabs();
        await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.resetAlert);
    }

    async function importTactics() {
        try {
            const result = await showAlert({
                title: 'Import Tactics', input: 'text', inputValue: '', placeholder: 'Paste Tactics JSON here', showCancelButton: true, confirmButtonText: USERSCRIPT_STRINGS.importButton, cancelButtonText: USERSCRIPT_STRINGS.cancelConfirmButton
            });
            if (!result.isConfirmed || !result.value) {
                return;
            }
            let importedData;
            try {
                importedData = JSON.parse(result.value);
            } 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;
            importedTactics.forEach(tactic => {
                if (!tactic.hasOwnProperty('style')) {
                    tactic.style = OTHER_CATEGORY_ID;
                }
                if (!tactic.name || !tactic.id || !Array.isArray(tactic.coordinates)) {
                     throw new Error('Invalid tactic structure in imported data.');
                }
                if (tactic.style && !categories[tactic.style] && tactic.style !== OTHER_CATEGORY_ID && tactic.style !== 'short_passing' && tactic.style !== 'wing_play') {
                    addCategory({ id: tactic.style, name: tactic.style, color: generateCategoryColor(tactic.style) });
                }
            });
            let existingTacticsData = await GM_getValue(TACTICS_STORAGE_KEY, { tactics: [] });
            let existingTactics = existingTacticsData.tactics || [];
            const mergedTactics = [...existingTactics];
            let addedCount = 0;
            for (const importedTactic of importedTactics) {
                if (!existingTactics.some((tactic) => tactic.id === importedTactic.id)) {
                    mergedTactics.push(importedTactic);
                    addedCount++;
                }
            }
            await GM_setValue(TACTICS_STORAGE_KEY, { tactics: mergedTactics });
            mergedTactics.sort((a, b) => a.name.localeCompare(b.name));
            dropdownMenuTactics = mergedTactics;
            updateTacticsDropdown();
            updateFilterTabs();
            await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.importAlert + (addedCount > 0 ? ` (${addedCount} new tactics added)`: ''));
        } catch (error) {
            console.error('ImportError:', error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, USERSCRIPT_STRINGS.invalidImportError + (error.message ? `: ${error.message}`: ''));
        }
    }

    async function exportTactics() {
        try {
            const tactics = GM_getValue(TACTICS_STORAGE_KEY, { tactics: [] });
            const tacticsJson = JSON.stringify(tactics, null, 2);
            if (navigator.clipboard?.writeText) {
                try {
                    await navigator.clipboard.writeText(tacticsJson);
                    await showSuccessMessage(USERSCRIPT_STRINGS.doneTitle, USERSCRIPT_STRINGS.exportAlert);
                    return;
                } catch (clipboardError) {
                    console.warn('Clipboard write failed, falling back to modal.', clipboardError);
                }
            }
            const textarea = document.createElement('textarea');
            textarea.value = tacticsJson;
            textarea.style.width = '100%';
            textarea.style.minHeight = '150px';
            textarea.style.marginTop = '10px';
            textarea.style.backgroundColor = 'rgba(0,0,0,0.2)';
            textarea.style.color = 'var(--text-color)';
            textarea.style.border = '1px solid rgba(255,255,255,0.1)';
            textarea.style.borderRadius = '4px';
            textarea.readOnly = true;
            const container = document.createElement('div');
            container.appendChild(document.createTextNode('Copy the JSON data below:'));
            container.appendChild(textarea);
            await showAlert({
                title: 'Export Tactics', htmlContent: container, confirmButtonText: 'Done'
            });
            textarea.select();
            textarea.setSelectionRange(0, 99999);
        } catch (error) {
            console.error('Export error:', error);
            await showErrorMessage(USERSCRIPT_STRINGS.errorTitle, 'Failed to export tactics.');
        }
    }

    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) {
            console.error('XMLError:', parserError[0].textContent);
            throw new Error(USERSCRIPT_STRINGS.xmlValidationError);
        }
        const posElements = Array.from(xmlDoc.getElementsByTagName('Pos'));
        const normalPosElements = posElements.filter(el => el.getAttribute('pos') === 'normal');
        if (normalPosElements.length !== MIN_OUTFIELD_PLAYERS) {
             throw new Error(`XML must contain exactly ${MIN_OUTFIELD_PLAYERS} outfield players ('Pos' elements with pos="normal"). Found ${normalPosElements.length}.`);
         }
        const coordinates = normalPosElements.map(el => {
            const x = parseInt(el.getAttribute('x'));
            const y = parseInt(el.getAttribute('y'));
            if (isNaN(x) || isNaN(y)) {
                 throw new Error('Invalid coordinates found in XML.');
             }
            const htmlLeft = x - 7;
            const htmlTop = y - 9;
            return [htmlLeft, htmlTop];
        });
        return {
            name: tacticName, coordinates: coordinates
        };
    }

    // ==============================
    // UI CREATION & UPDATES
    // ==============================
    function createTacticsSelector() {
        const container = document.createElement('div');
        container.className = 'tactics-selector-section';
        const dropdownContainer = document.createElement('div');
        dropdownContainer.className = 'tactics-dropdown-container';
        const dropdownWrapper = document.createElement('div');
        dropdownWrapper.className = 'tactics-dropdown-wrapper';
        const dropdown = document.createElement('select');
        dropdown.id = 'tactics_selector';
        dropdown.addEventListener('change', function() {
            handleTacticsSelection(this.value);
        });
        dropdownWrapper.appendChild(dropdown);
        dropdownContainer.appendChild(dropdownWrapper);
        const searchBox = document.createElement('input');
        searchBox.type = 'text';
        searchBox.className = 'tactics-search-box';
        searchBox.placeholder = USERSCRIPT_STRINGS.searchPlaceholder;
        searchBox.addEventListener('input', (e) => {
            searchTerm = e.target.value.toLowerCase();
            updateTacticsDropdown();
        });
        dropdownContainer.appendChild(searchBox);
        const filterTabs = document.createElement('div');
        filterTabs.className = 'tactics-filter-tabs';
        filterTabs.id = 'tactics-filter-tabs';
        dropdownContainer.appendChild(filterTabs);
        container.appendChild(dropdownContainer);
        return container;
    }

    function createFilterTab(categoryId, label, isActive = false, isRemovable = false) {
        const tab = document.createElement('button');
        tab.className = 'tactics-filter-tab';
        if (isActive) tab.classList.add('active');

        const labelSpan = document.createElement('span');
        labelSpan.textContent = label;
        tab.appendChild(labelSpan);

        tab.dataset.filter = categoryId;
        const categoryColor = loadCategoryColor(categoryId);
        tab.style.setProperty('--category-color', categoryColor);

        tab.addEventListener('click', (e) => {
            if (e.target.classList.contains('remove-category-btn')) {
                return;
            }
            document.querySelectorAll('.tactics-filter-tab').forEach(t => t.classList.remove('active'));
            tab.classList.add('active');
            currentFilter = categoryId;
            updateTacticsDropdown();
        });

        if (isRemovable) {
            const removeBtn = document.createElement('span');
            removeBtn.className = 'remove-category-btn';
            removeBtn.textContent = 'x';
            removeBtn.title = `Remove category "${label}"`;
            removeBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                removeCategory(categoryId).catch(console.error);
            });
            tab.appendChild(removeBtn);
            tab.style.paddingRight = '5px';
        }

        return tab;
    }


    function updateFilterTabs() {
        const filterTabsContainer = document.getElementById('tactics-filter-tabs');
        if (!filterTabsContainer) return;
        filterTabsContainer.innerHTML = '';

        const usedCategories = new Set(dropdownMenuTactics.map(tactic => tactic.style || OTHER_CATEGORY_ID));

        let categoriesToShow = [];
        categoriesToShow.push({ id: 'all', name: USERSCRIPT_STRINGS.allTacticsFilter, removable: false });

        if (usedCategories.has(OTHER_CATEGORY_ID) || currentFilter === OTHER_CATEGORY_ID) {
            categoriesToShow.push({ id: OTHER_CATEGORY_ID, name: getCategoryName(OTHER_CATEGORY_ID), removable: false });
        }

        const otherCategoryIds = Object.keys(categories)
            .filter(id => id !== 'all' && id !== OTHER_CATEGORY_ID && usedCategories.has(id))
            .map(id => ({
                id: id,
                name: getCategoryName(id),
                removable: (id !== 'short_passing' && id !== 'wing_play')
            }))
            .sort((a, b) => a.name.localeCompare(b.name));

        categoriesToShow = categoriesToShow.concat(otherCategoryIds);

        categoriesToShow.forEach(catInfo => {
            const isActive = currentFilter === catInfo.id;
            const tab = createFilterTab(catInfo.id, catInfo.name, isActive, catInfo.removable);
             if (catInfo.id === 'all' && isActive) {
                 tab.style.setProperty('--category-color', 'rgba(255,255,255,0.15)');
             }
            filterTabsContainer.appendChild(tab);
        });

        if (!categoriesToShow.some(cat => cat.id === currentFilter)) {
            currentFilter = 'all';
            const allTab = filterTabsContainer.querySelector('.tactics-filter-tab[data-filter="all"]');
            if (allTab) allTab.classList.add('active');
        }
    }


    function updateTacticsDropdown() {
        const dropdown = document.getElementById('tactics_selector');
        const dropdownWrapper = document.querySelector('.tactics-dropdown-wrapper');
        const searchBox = document.querySelector('.tactics-search-box');
        if (!dropdown) return;
        const previouslySelectedValue = dropdown.value;
        dropdown.innerHTML = '';
        if (searchTerm.length > 0) {
            dropdownWrapper?.classList.add('filtering');
            searchBox?.classList.add('filtering');
        } else {
            dropdownWrapper?.classList.remove('filtering');
            searchBox?.classList.remove('filtering');
        }
        const placeholderOption = document.createElement('option');
        placeholderOption.value = '';
        placeholderOption.textContent = 'Select a Tactic';
        placeholderOption.disabled = true;
        placeholderOption.selected = true;
        dropdown.appendChild(placeholderOption);
        const filteredTactics = dropdownMenuTactics.filter(tactic => {
             const nameMatch = searchTerm === '' || tactic.name.toLowerCase().includes(searchTerm);
             const categoryMatch = currentFilter === 'all' || (currentFilter === OTHER_CATEGORY_ID && (tactic.style === OTHER_CATEGORY_ID || !tactic.style)) || tactic.style === currentFilter;
             return nameMatch && categoryMatch;
        });
        const groupedTactics = {};
        Object.keys(categories).forEach(id => {
             groupedTactics[id] = [];
        });
        if (!groupedTactics[OTHER_CATEGORY_ID]) groupedTactics[OTHER_CATEGORY_ID] = [];
        filteredTactics.forEach(tactic => {
            const categoryId = tactic.style || OTHER_CATEGORY_ID;
            if (!groupedTactics[categoryId]) {
                 groupedTactics[OTHER_CATEGORY_ID].push(tactic);
            } else {
                 groupedTactics[categoryId].push(tactic);
            }
        });
        const categoryOrder = Object.keys(groupedTactics)
          .filter(id => groupedTactics[id].length > 0)
          .sort((a, b) => {
              if (a === currentFilter) return -1;
              if (b === currentFilter) return 1;
              if (a === OTHER_CATEGORY_ID) return 1;
              if (b === OTHER_CATEGORY_ID) return -1;
              return (getCategoryName(a) || '').localeCompare(getCategoryName(b) || '');
          });
        categoryOrder.forEach(categoryId => {
             if (groupedTactics[categoryId].length > 0) {
                 addTacticOptionsGroup(dropdown, groupedTactics[categoryId], getCategoryName(categoryId));
             }
        });
        if (filteredTactics.length === 0 && dropdownMenuTactics.length > 0) {
            const noTacticsOption = document.createElement('option');
            noTacticsOption.disabled = true;
            noTacticsOption.textContent = USERSCRIPT_STRINGS.noTacticsFound;
            dropdown.appendChild(noTacticsOption);
            placeholderOption.selected = false;
        } else if (filteredTactics.length === 0 && dropdownMenuTactics.length === 0) {
             placeholderOption.textContent = 'No tactics saved';
        }
        let foundPrevious = false;
        for (let i = 0; i < dropdown.options.length; i++) {
            if (dropdown.options[i].value === previouslySelectedValue) {
                dropdown.selectedIndex = i;
                foundPrevious = true;
                break;
            }
        }
        if (!foundPrevious) {
             if (filteredTactics.length === 1 && categoryOrder.length === 1 && groupedTactics[categoryOrder[0]].length === 1) {
                for (let i = 0; i < dropdown.options.length; i++) {
                    if (!dropdown.options[i].disabled) {
                        dropdown.selectedIndex = i;
                        break;
                    }
                }
             } else {
                 dropdown.selectedIndex = 0;
             }
        }
        dropdown.disabled = dropdownMenuTactics.length === 0;
    }

    function addTacticOptionsGroup(dropdown, tactics, groupLabel) {
        if (tactics.length === 0) return;
        const groupHeader = document.createElement('optgroup');
        groupHeader.label = groupLabel;
        dropdown.appendChild(groupHeader);
        tactics.sort((a, b) => a.name.localeCompare(b.name));
        tactics.forEach(tactic => {
            const option = document.createElement('option');
            option.value = tactic.name;
            option.dataset.style = tactic.style || OTHER_CATEGORY_ID;
            option.textContent = tactic.name;
            dropdown.appendChild(option);
        });
    }

    function createButton(id, text, clickHandler) {
        const button = document.createElement('button');
        setUpButton(button, id, text);
        if (clickHandler) {
            button.addEventListener('click', function (e) {
                e.stopPropagation();
                hideActiveDropdownMenu();
                clickHandler().catch((error) => {
                    console.error('Button click handler failed:', error);
                    showErrorMessage('Action Failed', `${error}`);
                 });
            });
        }
        return button;
    }

    function createActionDropdownButton(id, text, menuItems) {
        const wrapper = document.createElement('div');
        wrapper.style.position = 'relative';
        wrapper.style.display = 'inline-block';
        const button = createButton(id, text + ' ▼');
        const menu = document.createElement('div');
        menu.className = 'action-dropdown-menu';
        menu.id = id + '_menu';
        menuItems.forEach(item => {
             if (item && item.id && item.text && item.handler) {
                const menuItem = createButton(item.id, item.text, item.handler);
                menu.appendChild(menuItem);
             }
        });
        wrapper.appendChild(button);
        wrapper.appendChild(menu);
        button.addEventListener('click', (e) => {
            e.stopPropagation();
            toggleDropdownMenu(menu);
        });
        return wrapper;
    }

    function toggleDropdownMenu(menu) {
        if (activeDropdownMenu && activeDropdownMenu !== menu) {
            activeDropdownMenu.style.display = 'none';
        }
        if (menu.style.display === 'block') {
            menu.style.display = 'none';
            activeDropdownMenu = null;
        } else {
            menu.style.display = 'block';
            activeDropdownMenu = menu;
            menu.style.top = menu.previousElementSibling.offsetHeight + 4 + 'px';
            menu.style.left = '0';
        }
    }

    function hideActiveDropdownMenu() {
        if (activeDropdownMenu) {
            activeDropdownMenu.style.display = 'none';
            activeDropdownMenu = null;
        }
    }

    function createRenameTacticButton() {
        return createButton('rename_tactic_button', USERSCRIPT_STRINGS.renameButton, renameTactic);
    }

    function createUpdateTacticButton() {
        return createButton('update_tactic_button', USERSCRIPT_STRINGS.updateButton, updateTactic);
    }

    function createDeleteTacticButton() {
        return createButton('delete_tactic_button', USERSCRIPT_STRINGS.deleteButton, deleteTactic);
    }

    function createAddActionButton() {
        const menuItems = [
            { id: 'add_current_tactic_item', text: USERSCRIPT_STRINGS.addCurrentTactic, handler: addNewTactic },
            { id: 'add_xml_tactic_item', text: USERSCRIPT_STRINGS.addWithXmlButton, handler: addNewTacticWithXml }
        ];
        return createActionDropdownButton('add_action_dropdown', USERSCRIPT_STRINGS.addButton, menuItems);
    }

    function createManageActionButton() {
        const menuItems = [
            { id: 'import_tactics_item', text: USERSCRIPT_STRINGS.importButton, handler: importTactics },
            { id: 'export_tactics_item', text: USERSCRIPT_STRINGS.exportButton, handler: exportTactics },
            { id: 'reset_tactics_item', text: USERSCRIPT_STRINGS.resetButton, handler: resetTactics },
            { id: 'clear_tactics_item', text: USERSCRIPT_STRINGS.clearButton, handler: clearTactics }
        ];
        return createActionDropdownButton('manage_action_dropdown', USERSCRIPT_STRINGS.manageButton, menuItems);
    }

    async function checkVersion() {
        const storedVersion = GM_getValue(VERSION_KEY, null);
        if (!storedVersion || storedVersion !== SCRIPT_VERSION) {
            await showWelcomeMessage();
            GM_setValue(VERSION_KEY, SCRIPT_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().catch(e => console.error('Cannot play 猫 シ Corp.', e));
        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', '🔊');
        button.style.background = 'transparent'; button.style.border = 'none'; button.style.boxShadow = 'none';
        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%AF%20Plaza.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' ];
        let audios = []; let isPlaying = false; let currentAudio = null; let audioInitialized = false;
        const initializeAudio = () => { if (!audioInitialized) { audios = audioUrls.map(url => { const audio = new Audio(); audio.src = url; audio.preload = 'metadata'; return audio; }); audioInitialized = true; } };
        button.addEventListener('click', function () { initializeAudio(); if (!isPlaying) { const playableAudios = audios.map(a => a.cloneNode()); currentAudio = playRandomAudio(playableAudios); 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 = 'v' + DISPLAY_VERSION; vText.classList.add('mz-version-text'); mainTitle.appendChild(vText);
        const tacticsSelectorSection = createTacticsSelector();
        const buttonsSection = document.createElement('div');
        buttonsSection.className = 'action-buttons-section';
        const addActionBtn = createAddActionButton();
        const renameTacticBtn = createRenameTacticButton();
        const updateTacticBtn = createUpdateTacticButton();
        const deleteTacticBtn = createDeleteTacticButton();
        const manageActionBtn = createManageActionButton();
        appendChildren(buttonsSection, [ addActionBtn, renameTacticBtn, updateTacticBtn, deleteTacticBtn, manageActionBtn ]);
        appendChildren(tacticGroup, [ mainTitle, tacticsSelectorSection, buttonsSection, createHiddenTriggerButton() ]);
        const otherGroup = document.createElement('div');
        otherGroup.classList.add('mz-group'); otherGroup.style.padding = '10px 16px';
        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.className = 'footer-actions';
        const infoBtn = createCombinedInfoButton();
        const audioBtn = createAudioButton();
        appendChildren(otherLeftGroup, [infoBtn, audioBtn]);
        const otherRightGroup = document.createElement('div');
        otherRightGroup.className = 'mz-language-container';
        const languageLabel = document.createElement('div');
        languageLabel.id = 'language_dropdown_menu_label'; languageLabel.className = 'mz-language-label'; languageLabel.textContent = USERSCRIPT_STRINGS.languageDropdownMenuLabel;
        const languageDropdownWrapper = document.createElement('div');
        languageDropdownWrapper.className = 'mz-language-dropdown';
        const languageDropdownMenu = createLanguageDropdownMenu();
        languageDropdownWrapper.appendChild(languageDropdownMenu);
        const flagImage = createFlagImage();
        appendChildren(otherRightGroup, [languageLabel, languageDropdownWrapper, 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.position = 'absolute'; button.style.opacity = '0'; button.style.pointerEvents = 'none';
        button.style.width = '0'; button.style.height = '0'; button.style.padding = '0'; button.style.margin = '0'; button.style.border = '0';
        button.addEventListener('click', function () {
            const tacticsPresetInfo = { elem: document.getElementById('tactics_preset'), resetValue: '5-3-2' };
            if (tacticsPresetInfo.elem) { tacticsPresetInfo.elem.value = tacticsPresetInfo.resetValue; tacticsPresetInfo.elem.dispatchEvent(new Event('change')); }
        });
        return button;
    }

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

    function createLanguageDropdownMenu() {
        const dropdown = document.createElement('select');
        dropdown.id = '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, { defaultValue: USERSCRIPT_STRINGS[key] }); }
        const renameBtn = document.getElementById('rename_tactic_button'); if(renameBtn) renameBtn.textContent = 'Edit';
        const updateBtn = document.getElementById('update_tactic_button'); if(updateBtn) updateBtn.textContent = 'Update Coords';
        const deleteBtn = document.getElementById('delete_tactic_button'); if(deleteBtn) deleteBtn.textContent = USERSCRIPT_STRINGS.deleteButton;
        const addActionBtn = document.getElementById('add_action_dropdown'); if (addActionBtn) addActionBtn.textContent = 'Add' + ' ▼';
        const manageActionBtn = document.getElementById('manage_action_dropdown'); if (manageActionBtn) manageActionBtn.textContent = 'Other' + ' ▼';
        const addCurrentItem = document.getElementById('add_current_tactic_item'); if (addCurrentItem) addCurrentItem.textContent = USERSCRIPT_STRINGS.addCurrentTactic;
        const addXmlItem = document.getElementById('add_xml_tactic_item'); if (addXmlItem) addXmlItem.textContent = USERSCRIPT_STRINGS.addWithXmlButton;
        const importItem = document.getElementById('import_tactics_item'); if (importItem) importItem.textContent = USERSCRIPT_STRINGS.importButton;
        const exportItem = document.getElementById('export_tactics_item'); if (exportItem) exportItem.textContent = USERSCRIPT_STRINGS.exportButton;
        const resetItem = document.getElementById('reset_tactics_item'); if (resetItem) resetItem.textContent = USERSCRIPT_STRINGS.resetButton;
        const clearItem = document.getElementById('clear_tactics_item'); if (clearItem) clearItem.textContent = USERSCRIPT_STRINGS.clearButton;
        const infoBtn = document.getElementById('info_button'); if (infoBtn) infoBtn.textContent = USERSCRIPT_STRINGS.infoButton;
        const langLabel = document.getElementById('language_dropdown_menu_label'); if(langLabel) langLabel.textContent = USERSCRIPT_STRINGS.languageDropdownMenuLabel;
        const allFilterTab = document.querySelector('.tactics-filter-tab[data-filter="all"]'); if (allFilterTab) { const span = allFilterTab.querySelector('span:not(.remove-category-btn)'); if(span) span.textContent = i18next.t('allTacticsFilter', { defaultValue: 'All'}); }
        document.querySelectorAll('.tactics-filter-tab').forEach(tab => {
            const filterId = tab.dataset.filter;
            if (filterId && filterId !== 'all') {
                 const span = tab.querySelector('span:not(.remove-category-btn)');
                 if (span) {
                     span.textContent = getCategoryName(filterId);
                     const removeBtn = tab.querySelector('.remove-category-btn');
                     if (removeBtn) {
                         removeBtn.title = `Remove category "${span.textContent}"`;
                     }
                 }
            }
        });
        const searchBox = document.querySelector('.tactics-search-box'); if (searchBox) searchBox.placeholder = i18next.t('searchPlaceholder', { defaultValue: 'Search...'});
        updateTacticsDropdown();
        updateFilterTabs();
    }


    async function changeLanguage(languageCode) {
        try {
            const translationDataUrl = langDataBaseUrl + languageCode + '.json';
            let translations;
            try { const response = await fetch(translationDataUrl); if (!response.ok) { throw new Error('Primary language URL failed'); } translations = await response.json(); }
            catch (error) {
                console.log(`Primary language URL (${languageCode}) failed, trying fallback URL`);
                const fallbackBaseUrl = (langDataBaseUrl === CDN_URLS.default.lang) ? CDN_URLS.china.lang : CDN_URLS.default.lang;
                const fallbackUrl = fallbackBaseUrl + languageCode + '.json';
                try { const fallbackResponse = await fetch(fallbackUrl); if (!fallbackResponse.ok) throw new Error('Fallback language URL failed'); translations = await fallbackResponse.json(); }
                catch (fallbackError) { console.error(`Failed to load language ${languageCode} from primary and fallback sources. Using defaults.`); translations = {}; }
            }
            await i18next.changeLanguage(languageCode);
            i18next.addResourceBundle(languageCode, 'translation', translations, true, true);
            GM_setValue('language', languageCode); activeLanguage = 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) { console.error('Failed to change language:', e); }
    }

    function createCombinedInfoModalContent() {
        const wrapper = document.createElement('div'); wrapper.id = 'combined_info_modal_content';
        const aboutSection = document.createElement('div');
        const aboutTitle = document.createElement('h3'); aboutTitle.textContent = i18next.t('aboutButton');
        const aboutInfoText = document.createElement('p'); aboutInfoText.id = 'info_modal_info_text'; aboutInfoText.innerHTML = i18next.t('modalContentInfoText');
        const aboutFeedbackText = document.createElement('p'); aboutFeedbackText.id = 'info_modal_feedback_text'; aboutFeedbackText.innerHTML = i18next.t('modalContentFeedbackText');
        appendChildren(aboutSection, [aboutTitle, aboutInfoText, aboutFeedbackText]);
        const linksSection = document.createElement('div');
        const linksTitle = document.createElement('h3'); linksTitle.textContent = i18next.t('usefulLinksButton');
        const linksContent = 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);
        appendChildren(linksSection, [linksTitle, linksContent, usefulLinksList]);
        appendChildren(wrapper, [aboutSection, linksSection]);
        return wrapper;
    }

    function createUsefulContent() {
        const usefulContent = document.createElement('p'); usefulContent.id = 'useful_content'; usefulContent.textContent = i18next.t('usefulContent'); 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.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = title;
            listItem.appendChild(link); list.appendChild(listItem);
        });
        return list;
    }

    function createCombinedInfoButton() {
        const button = document.createElement('button'); setUpButton(button, 'info_button', USERSCRIPT_STRINGS.infoButton);
        button.style.background = 'transparent'; button.style.border = 'none'; button.style.boxShadow = 'none';
        button.addEventListener('click', function (event) {
            event.stopPropagation(); showAlert({ title: 'Info & Links', htmlContent: createCombinedInfoModalContent(), confirmButtonText: DEFAULT_MODAL_STRINGS.ok });
        });
        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 = 'TM'; icon.title = 'Show MZ Tactics Manager'; document.body.appendChild(icon); return icon;
    }

    // ==============================
    // INITIALIZATION & PANEL SETUP
    // ==============================
    function initializeLanguage() {
        return new Promise((resolve, reject) => {
            activeLanguage = getActiveLanguage();
            i18next.init({ lng: activeLanguage, fallbackLng: 'en', resources: { [activeLanguage]: { translation: {} } }, interpolation: { escapeValue: false } })
            .then(async () => {
                try {
                    let json = {};
                    try { const url = langDataBaseUrl + activeLanguage + '.json'; const res = await fetch(url); if (!res.ok) { throw new Error('Primary language URL failed during initialization'); } json = await res.json(); }
                    catch (error) {
                        console.log(`Primary language URL (${activeLanguage}) failed during initialization, trying fallback URL or using defaults.`);
                        const fallbackBaseURL = (langDataBaseUrl === CDN_URLS.default.lang) ? CDN_URLS.china.lang : CDN_URLS.default.lang;
                        const fallbackUrl = fallbackBaseURL + activeLanguage + '.json';
                         try { const fallbackRes = await fetch(fallbackUrl); if (!fallbackRes.ok) throw new Error('Fallback language URL failed too'); json = await fallbackRes.json(); }
                         catch (fallbackError) { console.error(`Failed to load language ${activeLanguage} from primary and fallback sources. Using defaults.`); }
                    }
                    i18next.addResourceBundle(activeLanguage, 'translation', json, true, true);
                    loadCategories(); await checkVersion(); resolve();
                } catch (error) { reject(error); }
            }).catch(reject);
        });
    }

    function setUpTacticsInterface(mainContainer) {
        const mainTitle = mainContainer.querySelector('.mz-group-main-title');
        const toggleBtn = createToggleButton();
        const collapsedIcon = createCollapsedIcon();
        mainTitle.appendChild(toggleBtn);

        let isCollapsed = GM_getValue(COLLAPSED_KEY, false);

        const applyCollapsedState = (instant = false) => {
             if (isCollapsed) {
                 if (instant) {
                     mainContainer.style.transition = 'none';
                     mainContainer.classList.add('collapsed');
                     collapsedIcon.classList.add('visible');
                     toggleBtn.innerHTML = '☰';
                     toggleBtn.title = 'Show panel';
                     void mainContainer.offsetHeight;
                     mainContainer.style.transition = '';
                 } else {
                     mainContainer.classList.add('collapsed');
                     collapsedIcon.classList.add('visible');
                     toggleBtn.innerHTML = '☰';
                     toggleBtn.title = 'Show panel';
                 }
             } else {
                 mainContainer.classList.remove('collapsed');
                 collapsedIcon.classList.remove('visible');
                 toggleBtn.innerHTML = '✕';
                 toggleBtn.title = 'Hide panel';
             }
        };

        applyCollapsedState(true);

        function togglePanel() {
            isCollapsed = !isCollapsed;
            GM_setValue(COLLAPSED_KEY, isCollapsed);
            applyCollapsedState();
        }
        toggleBtn.addEventListener('click', (e) => { e.stopPropagation(); togglePanel(); });
        collapsedIcon.addEventListener('click', () => { togglePanel(); });
    }

    async function loadTacticsData() {
        try {
            const data = await fetchTacticsFromGMStorage();
            dropdownMenuTactics = data.tactics || [];
            dropdownMenuTactics.forEach(tactic => { if (!tactic.hasOwnProperty('style')) { tactic.style = OTHER_CATEGORY_ID; } });
            dropdownMenuTactics.sort((a, b) => a.name.localeCompare(b.name));
            updateTacticsDropdown();
            updateFilterTabs();
        } catch (error) {
            console.error('ErrorLoadingTactics:', error);
            showErrorMessage('Load Error', 'Failed to load saved tactics.');
        }
    }

    function initialize() {
        const tacticsBox = document.getElementById('tactics_box');
        if (!tacticsBox) return;
        initializeLanguage()
            .then(() => {
                const mainContainer = createMainContainer();
                setUpTacticsInterface(mainContainer);
                if (isFootball()) { insertAfterElement(mainContainer, tacticsBox); }
                updateTranslation();
                return loadTacticsData();
            })
            .catch(error => {
                console.error('InitializationError:', error);
                const errorDiv = document.createElement('div');
                errorDiv.textContent = 'Error initializing MZ Tactics Manager.'; errorDiv.style.color = 'red'; errorDiv.style.padding = '10px'; errorDiv.style.border = '1px solid red'; errorDiv.style.margin = '10px';
                insertAfterElement(errorDiv, tacticsBox);
            });
    }

    // ==============================
    // EVENT LISTENERS & STARTUP
    // ==============================
    window.addEventListener('load', initialize);
    document.addEventListener('click', (e) => {
        if (activeDropdownMenu) {
            const triggerButton = activeDropdownMenu.previousElementSibling;
            if (!activeDropdownMenu.contains(e.target) && triggerButton !== e.target && !triggerButton.contains(e.target)) {
                hideActiveDropdownMenu();
            }
        }
    });
})();