Dreadcast - PimpMyPion [[VERSION TEST]]

Ajoute un slider pour contrôler la taille des pions + affiche les avatars personnalisés des joueurs + détection automatique du combat + couleurs par action + emojis d'action + contrôle taille emojis + personnalisation des couleurs + slider d'opacité

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Dreadcast - PimpMyPion [[VERSION TEST]]
// @namespace    http://tampermonkey.net/
// @version      0.5.1
// @description  Ajoute un slider pour contrôler la taille des pions + affiche les avatars personnalisés des joueurs + détection automatique du combat + couleurs par action + emojis d'action + contrôle taille emojis + personnalisation des couleurs + slider d'opacité
// @author       Darlene
// @match        https://www.dreadcast.net/*
// @match        http://www.dreadcast.net/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  // ============================================================
  // CONFIGURATION
  // ============================================================
  const CONFIG = Object.freeze({
    // Clefs stockage
    STORAGE_KEY_SIZE: 'dreadcast_avatar_size',
    STORAGE_KEY_ENABLED: 'dreadcast_avatar_enabled',
    STORAGE_KEY_EMOJI_ENABLED: 'dreadcast_emoji_enabled',
    STORAGE_KEY_EMOJI_SIZE: 'dreadcast_emoji_size',
    STORAGE_KEY_CUSTOM_COLORS: 'dreadcast_custom_colors',
    STORAGE_KEY_COLOR_OPACITY: 'dreadcast_color_opacity',

    // Paramètres de taille(en %)
    DEFAULT_SIZE: 100,
    MIN_SIZE: 75,
    MAX_SIZE: 125,

    // Paramètres de taille emoji (en px)
    DEFAULT_EMOJI_SIZE: 12,
    MIN_EMOJI_SIZE: 12,
    MAX_EMOJI_SIZE: 28,

    // Paramètres d'opacité des couleurs (en %)
    DEFAULT_COLOR_OPACITY: 100,
    MIN_COLOR_OPACITY: 0,
    MAX_COLOR_OPACITY: 100,

    // URLs & chemins
    AVATAR_BASE_URL: 'https://www.dreadcast.net/images/avatars/',

    // Timing (millisecondes)
    REAPPLY_INTERVAL: 50,
    RAF_THROTTLE: 0,
    INIT_DELAY: 2000,
    SECONDARY_DELAY: 5000,
    MENU_CHECK_INTERVAL: 500,
    MENU_CHECK_TIMEOUT: 10000,
    EVENT_ATTACH_DELAY: 100,
    COMBAT_CHECK_INTERVAL: 200,

    // Z-indices
    Z_INDEX_AVATAR: 999,
    Z_INDEX_EMOJI: 1001,
    Z_INDEX_OVERLAY: 999999,
    Z_INDEX_PANEL: 1000000,

    // Selecteurs CSS
    SELECTOR_PIONS: '.personnages .icon_perso',
    SELECTOR_ICON: '.le_icon_perso',
    SELECTOR_INFO: '.info_a_afficher',
    SELECTOR_SETTINGS_MENU: '.parametres ul',
    SELECTOR_COMBAT: '#combat_carte',
    SELECTOR_PLAYER_ACTION: '#icon_action',

    // Classes CSS
    CLASS_AVATAR_IMG: 'custom-avatar-img',
    CLASS_CONNECTED: 'connecte',
    CLASS_ACTION_EMOJI: 'action-emoji',

    // Data attributes
    ATTR_AVATAR_STATUS: 'data-avatar-applied',
    ATTR_PLAYER_NAME: 'data-player-name',
    ATTR_CURRENT_ACTION: 'data-current-action',

    // Status avatars
    STATUS_SUCCESS: 'success',
    STATUS_FAILED: 'failed',

    // Couleurs par action
    ACTION_COLORS: {
      'en_combat': '#ef4444',        // Rouge vif - Combat
      'encombat': '#ef4444',         // Rouge vif - Combat (variante)
      'aucune': '#9ca3af',           // Gris - Aucune action
      'noaction': '#9ca3af',         // Gris - Aucune action (variante)
      'repos': '#06b6d4',            // Cyan - Repos
      'recherche': '#f59e0b',        // Jaune/Or - Fouille

      'cacher': '#8b5cf6',           // Violet - Cacher
      'scruter': '#f97316',          // Orange - Scruter
      'soin': '#10b981',          // Vert - Soigner
      'travail': '#92400e',        // Marron - Travailler
      'ko': '#1f2937',             // Gris foncé - Mort
      'destruction': '#ef4444',       // Rouge vif
      'reparation': '#ef4444'         // Rouge vif
    },

    // Emojis par action
    ACTION_EMOJIS: {
      'en_combat': '⚔️',
      'encombat': '⚔️',
      'aucune': '⏸️',
      'noaction': '⏸️',
      'repos': '😴',
      'recherche': '⛏️',

      'cacher': '🫣',
      'scruter': '👀',
      'soin': '💊',
      'travail': '⚙️',
      'ko': '💀',
      'destruction': '💥',
      'reparation': '🔧'
    },

    // Couleurs de base (fallback)
    COLOR_CONNECTED: '#4ade80',
    COLOR_DISCONNECTED: '#ffffff',

    // Divers
    DEBUG_MODE: true
  });

  // ============================================================
  // Gestion des état
  // ============================================================
  class AvatarState {
    constructor() {
      this.avatarCache = new Map();
      this.avatarUrlCache = new Map();
      this.actionCache = new Map();
      this.reapplyIntervalId = null;
      this.reapplyAnimationFrameId = null;
      this.lastReapplyTime = 0;
    }

    clearCaches() {
      this.avatarCache.clear();
      this.avatarUrlCache.clear();
      this.actionCache.clear();
    }

    stopReapplication() {
      if (this.reapplyIntervalId) {
        clearInterval(this.reapplyIntervalId);
        this.reapplyIntervalId = null;
      }
      if (this.reapplyAnimationFrameId) {
        cancelAnimationFrame(this.reapplyAnimationFrameId);
        this.reapplyAnimationFrameId = null;
      }
    }
  }

  const state = new AvatarState();

  // ============================================================
  // Détection des actions
  // ============================================================
  const ActionDetector = {
    /**
     * Détecte l'action d'un joueur à partir de son pion
     * @param {HTMLElement} pionElement - L'élément .icon_perso
     * @returns {string|null} - Le nom de l'action détectée ou null
     */
    getPlayerAction(pionElement) {
      try {
        // Vérifier le cache
        if (state.actionCache.has(pionElement)) {
          const cached = state.actionCache.get(pionElement);
          // Vérifier si le cache est encore valide (< 500ms)
          if (Date.now() - cached.timestamp < 500) {
            return cached.action;
          }
        }

        // Chercher l'action dans .le_icon_perso
        const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
        if (!iconElement) return null;

        // Les classes possibles pour les actions
        const actionClasses = [
          'en_combat', 'encombat',
          'recherche', 'fouille',
          'repos',
          'cacher',
          'scruter',
          'soin',
          'travail',
          'destruction',
          'reparation',
          'aucune', 'noaction',
          'ko'
        ];

        // Chercher quelle action est active
        for (const actionClass of actionClasses) {
          if (iconElement.classList.contains(actionClass)) {
            // Mettre en cache
            state.actionCache.set(pionElement, {
              action: actionClass,
              timestamp: Date.now()
            });
            return actionClass;
          }
        }

        // Si aucune action trouvée, retourner null
        return null;

      } catch (error) {
        Utils.debugLog('❌ Erreur détection action:', error);
        return null;
      }
    },

    /**
     * Récupère la couleur correspondant à une action
     * @param {string} action - Le nom de l'action
     * @returns {string} - La couleur hexadécimale
     */
    getActionColor(action) {
      if (!action) return Storage.getColorForStatus(false);
      return Storage.getColorForAction(action.toLowerCase());
    },

    /**
     * Détecte l'action du joueur principal via #icon_action
     * @returns {string|null} - Le nom de l'action détectée ou null
     */
    getMainPlayerAction() {
      try {
        const iconAction = document.querySelector(CONFIG.SELECTOR_PLAYER_ACTION);
        if (!iconAction) return null;

        // Les classes possibles sur #icon_action
        const actionClasses = [
          'actionEncombat',
          'actionAucune',
          'actionRepos',
          'actionFouille',
          'actionCacher',
          'actionScruter',
          'actionSoin',
          'actionTravailler',
          'actionDetruire',
          'actionReparer',
          'actionKo',
        ];

        for (const actionClass of actionClasses) {
          if (iconAction.classList.contains(actionClass)) {
            // Convertir le nom de classe en nom d'action
            const actionName = actionClass.replace('action', '').toLowerCase();
            return actionName;
          }
        }

        return null;

      } catch (error) {
        Utils.debugLog('❌ Erreur détection action principale:', error);
        return null;
      }
    },

    /**
     * Vide le cache des actions
     */
    clearCache() {
      state.actionCache.clear();
      Utils.debugLog('✅ Cache des actions vidé');
    },

    /**
     * Récupère l'emoji correspondant à une action
     * @param {string} action - Le nom de l'action
     * @returns {string|null} - L'emoji ou null
     */
    getActionEmoji(action) {
      if (!action) return null;
      const emoji = CONFIG.ACTION_EMOJIS[action.toLowerCase()];
      return emoji || null;
    },

    /**
     * Crée ou met à jour l'élément emoji sur un avatar
     * @param {HTMLElement} iconElement - L'élément .le_icon_perso
     * @param {string} action - Le nom de l'action
     */
    updateActionEmoji(iconElement, action) {
      try {
        if (!Storage.loadEmojiEnabled()) {
          // Supprimer l'emoji s'il existe
          const existingEmoji = iconElement.querySelector(`.${CONFIG.CLASS_ACTION_EMOJI}`);
          if (existingEmoji) {
            existingEmoji.remove();
            Utils.debugLog('🗑️ Emoji supprimé (fonction désactivée)');
          }
          return;
        }

        const emoji = this.getActionEmoji(action);

        if (!emoji) {
          // Si pas d'emoji pour cette action, supprimer l'emoji existant
          const existingEmoji = iconElement.querySelector(`.${CONFIG.CLASS_ACTION_EMOJI}`);
          if (existingEmoji) {
            existingEmoji.remove();
            Utils.debugLog('🗑️ Emoji supprimé (aucun emoji pour cette action)');
          }
          return;
        }

        let emojiElement = iconElement.querySelector(`.${CONFIG.CLASS_ACTION_EMOJI}`);

        if (!emojiElement) {
          // Créer un nouveau span pour l'emoji
          emojiElement = document.createElement('span');
          emojiElement.className = CONFIG.CLASS_ACTION_EMOJI;
          iconElement.appendChild(emojiElement);
          Utils.debugLog('✨ Nouvel emoji créé:', emoji);
        }

        // Mettre à jour le contenu de l'emoji si nécessaire
        if (emojiElement.textContent !== emoji) {
          emojiElement.textContent = emoji;
          Utils.debugLog('🔄 Emoji mis à jour:', emoji);
        }

      } catch (error) {
        Utils.debugLog('❌ Erreur mise à jour emoji:', error);
      }
    },

    /**
     * Supprime l'emoji d'action d'un avatar
     * @param {HTMLElement} iconElement - L'élément .le_icon_perso
     */
    removeActionEmoji(iconElement) {
      try {
        const emojiElement = iconElement.querySelector(`.${CONFIG.CLASS_ACTION_EMOJI}`);
        if (emojiElement) {
          emojiElement.remove();
          Utils.debugLog('🗑️ Emoji supprimé');
        }
      } catch (error) {
        Utils.debugLog('❌ Erreur suppression emoji:', error);
      }
    }
  };

  // ============================================================
  // COMBAT DETECTOR
  // ============================================================
  const CombatDetector = {
    isInCombat: false,
    savedSize: null,
    checkInterval: null,

    detectCombat() {
      return document.querySelector(CONFIG.SELECTOR_COMBAT) !== null;
    },

    start() {
      Utils.debugLog('⚔️ Démarrage du détecteur de combat');

      this.checkInterval = setInterval(() => {
        const inCombat = this.detectCombat();

        if (inCombat && !this.isInCombat) {
          this.onEnterCombat();
        } else if (!inCombat && this.isInCombat) {
          this.onExitCombat();
        }
      }, CONFIG.COMBAT_CHECK_INTERVAL);
    },

    onEnterCombat() {
      Utils.debugLog('⚔️ 🔴 ENTRÉE EN COMBAT - Forçage taille 100%');

      const currentSize = Storage.loadAvatarSize();
      this.savedSize = currentSize;
      this.isInCombat = true;

      SizingSystem.applyAvatarSize(100);
      this.disableSlider();
    },

    onExitCombat() {
      Utils.debugLog('⚔️ 🟢 SORTIE DE COMBAT - Restauration taille sauvegardée');

      this.isInCombat = false;

      if (this.savedSize !== null) {
        SizingSystem.applyAvatarSize(this.savedSize);
        Utils.debugLog(`✅ Taille restaurée à ${this.savedSize}%`);
      }

      this.enableSlider();
      this.savedSize = null;
    },

    disableSlider() {
      const slider = document.getElementById('avatar-size-slider');
      const valueDisplay = document.getElementById('avatar-size-value');

      if (slider) {
        slider.disabled = true;
        slider.style.opacity = '0.5';
        slider.style.cursor = 'not-allowed';
      }

      if (valueDisplay) {
        valueDisplay.textContent = '100% ⚔️';
        valueDisplay.style.color = '#ef4444';
      }
    },

    enableSlider() {
      const slider = document.getElementById('avatar-size-slider');
      const valueDisplay = document.getElementById('avatar-size-value');

      if (slider) {
        slider.disabled = false;
        slider.style.opacity = '1';
        slider.style.cursor = 'pointer';

        const currentSize = Storage.loadAvatarSize();
        slider.value = currentSize;

        if (valueDisplay) {
          valueDisplay.textContent = `${currentSize}%`;
          valueDisplay.style.color = '#667eea';
        }
      }
    },

    stop() {
      if (this.checkInterval) {
        clearInterval(this.checkInterval);
        this.checkInterval = null;
      }
      Utils.debugLog('⚔️ Arrêt du détecteur de combat');
    }
  };

  // ============================================================
  // Fonctions utilitaires
  // ============================================================
  const Utils = {
    debugLog(message, ...args) {
      if (CONFIG.DEBUG_MODE) {
        console.log(`[Dreadcast PimpMyPion] ${message}`, ...args);
      }
    },

    encodePlayerName(name) {
      return encodeURIComponent(name);
    },

    buildAvatarUrl(playerName) {
      return `${CONFIG.AVATAR_BASE_URL}${this.encodePlayerName(playerName)}.png`;
    },

    throttle(fn, delay) {
      let lastCall = 0;
      return function (...args) {
        const now = Date.now();
        if (now - lastCall < delay) return;
        lastCall = now;
        fn.apply(this, args);
      };
    },

    /**
     * Convertit une couleur hexadécimale en rgba avec opacité
     * @param {string} hex - Couleur hex (#rrggbb)
     * @param {number} opacity - Opacité de 0 à 1
     * @returns {string} - Couleur rgba
     */
    hexToRgba(hex, opacity) {
      // Supprimer le # si présent
      hex = hex.replace('#', '');

      // Convertir en RGB
      const r = parseInt(hex.substring(0, 2), 16);
      const g = parseInt(hex.substring(2, 4), 16);
      const b = parseInt(hex.substring(4, 6), 16);

      return `rgba(${r}, ${g}, ${b}, ${opacity})`;
    }
  };

  // ============================================================
  // Fonctions de stockage
  // ============================================================
  const Storage = {
    loadAvatarSize() {
      const savedSize = localStorage.getItem(CONFIG.STORAGE_KEY_SIZE);
      return savedSize ? parseInt(savedSize, 10) : CONFIG.DEFAULT_SIZE;
    },

    saveAvatarSize(size) {
      localStorage.setItem(CONFIG.STORAGE_KEY_SIZE, String(size));
      Utils.debugLog('✅ Taille sauvegardée:', `${size}%`);
    },

    loadAvatarEnabled() {
      const savedEnabled = localStorage.getItem(CONFIG.STORAGE_KEY_ENABLED);
      return savedEnabled === null ? true : savedEnabled === 'true';
    },

    saveAvatarEnabled(enabled) {
      localStorage.setItem(CONFIG.STORAGE_KEY_ENABLED, String(enabled));
      Utils.debugLog('✅ Affichage des avatars:', enabled ? 'activé' : 'désactivé');
    },

    loadEmojiEnabled() {
      const savedEnabled = localStorage.getItem(CONFIG.STORAGE_KEY_EMOJI_ENABLED);
      return savedEnabled === null ? true : savedEnabled === 'true';
    },

    saveEmojiEnabled(enabled) {
      localStorage.setItem(CONFIG.STORAGE_KEY_EMOJI_ENABLED, String(enabled));
      Utils.debugLog('✅ Affichage des emojis:', enabled ? 'activé' : 'désactivé');
    },

    loadEmojiSize() {
      const savedSize = localStorage.getItem(CONFIG.STORAGE_KEY_EMOJI_SIZE);
      return savedSize ? parseInt(savedSize, 10) : CONFIG.DEFAULT_EMOJI_SIZE;
    },

    saveEmojiSize(size) {
      localStorage.setItem(CONFIG.STORAGE_KEY_EMOJI_SIZE, String(size));
      Utils.debugLog('✅ Taille emoji sauvegardée:', `${size}px`);
    },

    loadCustomColors() {
      const saved = localStorage.getItem(CONFIG.STORAGE_KEY_CUSTOM_COLORS);
      return saved ? JSON.parse(saved) : {};
    },

    saveCustomColors(colors) {
      localStorage.setItem(CONFIG.STORAGE_KEY_CUSTOM_COLORS, JSON.stringify(colors));
      Utils.debugLog('✅ Couleurs personnalisées sauvegardées');
    },

    loadColorOpacity() {
      const saved = localStorage.getItem(CONFIG.STORAGE_KEY_COLOR_OPACITY);
      return saved ? parseInt(saved, 10) : CONFIG.DEFAULT_COLOR_OPACITY;
    },

    saveColorOpacity(opacity) {
      localStorage.setItem(CONFIG.STORAGE_KEY_COLOR_OPACITY, String(opacity));
      Utils.debugLog('✅ Opacité des couleurs sauvegardée:', `${opacity}%`);
    },

    getColorForAction(action) {
      const customColors = this.loadCustomColors();
      const opacity = this.loadColorOpacity() / 100; // Convertir % en 0-1

      let hexColor;
      if (customColors[action]) {
        hexColor = customColors[action];
      } else {
        hexColor = CONFIG.ACTION_COLORS[action] || CONFIG.COLOR_CONNECTED;
      }

      return Utils.hexToRgba(hexColor, opacity);
    },

    getColorForStatus(isConnected) {
      const customColors = this.loadCustomColors();
      const opacity = this.loadColorOpacity() / 100; // Convertir % en 0-1

      let hexColor;
      if (isConnected && customColors['connected']) {
        hexColor = customColors['connected'];
      } else if (!isConnected && customColors['disconnected']) {
        hexColor = customColors['disconnected'];
      } else {
        hexColor = isConnected ? CONFIG.COLOR_CONNECTED : CONFIG.COLOR_DISCONNECTED;
      }

      return Utils.hexToRgba(hexColor, opacity);
    }
  };

  // ============================================================
  // DOM UTILITIES
  // ============================================================
  const DOMUtils = {
    getPlayerNameFromPion(pionElement) {
      try {
        const cachedName = pionElement.getAttribute(CONFIG.ATTR_PLAYER_NAME);
        if (cachedName) return cachedName;

        const infoElement = pionElement.querySelector(CONFIG.SELECTOR_INFO);
        if (infoElement?.textContent) {
          const playerName = infoElement.textContent.trim();
          pionElement.setAttribute(CONFIG.ATTR_PLAYER_NAME, playerName);
          return playerName;
        }
      } catch (error) {
        Utils.debugLog('❌ Erreur extraction nom joueur:', error);
      }
      return null;
    },

    isAvatarValid(pionElement) {
      const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
      if (!iconElement) return false;

      const avatarImg = iconElement.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);
      if (!avatarImg) return false;

      const isAttached = avatarImg.parentElement !== null;
      const isVisible = avatarImg.style.display !== 'none' &&
                        avatarImg.style.visibility !== 'hidden' &&
                        avatarImg.style.opacity !== '0';

      return isAttached && isVisible;
    },

    getAllPions() {
      return document.querySelectorAll(CONFIG.SELECTOR_PIONS);
    }
  };

  // ============================================================
  // Chargement des images
  // ============================================================
  const ImageLoader = {
    async checkImageExists(url, playerName) {
      if (state.avatarUrlCache.has(playerName)) {
        return state.avatarUrlCache.get(playerName).exists;
      }

      return new Promise((resolve) => {
        const img = new Image();
        img.onload = () => {
          state.avatarUrlCache.set(playerName, { url, exists: true });
          resolve(true);
        };
        img.onerror = () => {
          state.avatarUrlCache.set(playerName, { url, exists: false });
          resolve(false);
        };
        img.src = url;
      });
    }
  };

  // ============================================================
  // Style des avatars
  // ============================================================
  const AvatarStyler = {
    createAvatarImage(avatarUrl, playerName) {
      const img = document.createElement('img');
      img.className = CONFIG.CLASS_AVATAR_IMG;
      img.src = avatarUrl;
      img.alt = playerName;
      img.setAttribute('loading', 'eager');
      img.setAttribute('decoding', 'sync');
      return img;
    },

    applyAvatarStyles(avatarImg, borderColor) {
      const styles = {
        width: '20px',
        height: '20px',
        'object-fit': 'cover',
        'border-radius': '50%',
        border: `3px solid ${borderColor}`,
        'box-shadow': '0 2px 8px rgba(0, 0, 0, 0.3)',
        position: 'absolute',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        'z-index': String(CONFIG.Z_INDEX_AVATAR),
        'pointer-events': 'none',
        display: 'block',
        visibility: 'visible',
        opacity: '1',
        transition: 'border-color 0.3s ease',
        animation: 'none'
      };

      Object.entries(styles).forEach(([property, value]) => {
        avatarImg.style.setProperty(property, value, 'important');
      });
    },

    getBorderColor(pionElement) {
      const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
      if (!iconElement) return Storage.getColorForStatus(false);

      // Détecter l'action du joueur
      const action = ActionDetector.getPlayerAction(pionElement);

      if (action) {
        // Utiliser la couleur de l'action
        const actionColor = ActionDetector.getActionColor(action);
        Utils.debugLog(`🎨 Action détectée: ${action} -> ${actionColor}`);
        return actionColor;
      }

      // Fallback: utiliser le système connecté/déconnecté avec couleurs personnalisées
      const isConnected = iconElement.classList.contains(CONFIG.CLASS_CONNECTED);
      return Storage.getColorForStatus(isConnected);
    }
  };

  // ============================================================
  // Application des avatars
  // ============================================================
  const AvatarManager = {
    async applyCustomAvatar(pionElement, force = false) {
      try {
        if (!Storage.loadAvatarEnabled()) return;

        const cachedStatus = state.avatarCache.get(pionElement);

        if (!force && cachedStatus === CONFIG.STATUS_SUCCESS && DOMUtils.isAvatarValid(pionElement)) {
          // Même si l'avatar est valide, on doit mettre à jour la bordure
          const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
          const avatarImg = iconElement?.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);

          if (avatarImg) {
            const borderColor = AvatarStyler.getBorderColor(pionElement);
            avatarImg.style.setProperty('border', `3px solid ${borderColor}`, 'important');
          }
          return;
        }

        if (!force && cachedStatus === CONFIG.STATUS_FAILED) return;

        const playerName = DOMUtils.getPlayerNameFromPion(pionElement);
        if (!playerName) {
          state.avatarCache.set(pionElement, CONFIG.STATUS_FAILED);
          return;
        }

        const avatarUrl = Utils.buildAvatarUrl(playerName);

        if (!state.avatarUrlCache.has(playerName) || force) {
          const exists = await ImageLoader.checkImageExists(avatarUrl, playerName);
          if (!exists) {
            state.avatarCache.set(pionElement, CONFIG.STATUS_FAILED);
            pionElement.setAttribute(CONFIG.ATTR_AVATAR_STATUS, CONFIG.STATUS_FAILED);
            return;
          }
        } else if (!state.avatarUrlCache.get(playerName).exists) {
          return;
        }

        const iconElement = pionElement.querySelector(CONFIG.SELECTOR_ICON);
        if (!iconElement) return;

        let avatarImg = iconElement.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);

        if (!avatarImg) {
          avatarImg = AvatarStyler.createAvatarImage(avatarUrl, playerName);
          if (iconElement.firstChild) {
            iconElement.insertBefore(avatarImg, iconElement.firstChild);
          } else {
            iconElement.appendChild(avatarImg);
          }
        } else if (!DOMUtils.isAvatarValid(pionElement)) {
          avatarImg.src = avatarUrl;
          avatarImg.alt = playerName;
        }

        const borderColor = AvatarStyler.getBorderColor(pionElement);
        AvatarStyler.applyAvatarStyles(avatarImg, borderColor);

        if (!avatarImg.complete || avatarImg.naturalHeight === 0) {
          avatarImg.src = avatarImg.src;
        }

        pionElement.setAttribute(CONFIG.ATTR_AVATAR_STATUS, CONFIG.STATUS_SUCCESS);
        pionElement.setAttribute(CONFIG.ATTR_PLAYER_NAME, playerName);
        state.avatarCache.set(pionElement, CONFIG.STATUS_SUCCESS);

        // Gérer l'emoji d'action
        const action = ActionDetector.getPlayerAction(pionElement);
        const currentAction = pionElement.getAttribute(CONFIG.ATTR_CURRENT_ACTION);

        // Mettre à jour l'emoji si l'action a changé ou si force est activé
        if (force || action !== currentAction) {
          ActionDetector.updateActionEmoji(iconElement, action);
          pionElement.setAttribute(CONFIG.ATTR_CURRENT_ACTION, action || '');
          Utils.debugLog('🎯 Action mise à jour:', action);
        }

      } catch (error) {
        Utils.debugLog('❌ Erreur application avatar:', error);
        state.avatarCache.set(pionElement, CONFIG.STATUS_FAILED);
      }
    },

    removeCustomAvatars() {
      document.querySelectorAll(`.${CONFIG.CLASS_AVATAR_IMG}`).forEach(img => img.remove());
      document.querySelectorAll(`.${CONFIG.CLASS_ACTION_EMOJI}`).forEach(emoji => emoji.remove());

      DOMUtils.getAllPions().forEach(pion => {
        pion.removeAttribute(CONFIG.ATTR_AVATAR_STATUS);
        pion.removeAttribute(CONFIG.ATTR_PLAYER_NAME);
        pion.removeAttribute(CONFIG.ATTR_CURRENT_ACTION);
      });

      state.avatarCache.clear();
      Utils.debugLog('✅ Avatars et emojis supprimés');
    },

    async applyAvatarsToAllPions(force = false) {
      if (!Storage.loadAvatarEnabled()) {
        this.removeCustomAvatars();
        return;
      }

      const pions = DOMUtils.getAllPions();
      Utils.debugLog(`🔍 ${pions.length} pion(s) trouvé(s)`);

      for (const pion of pions) {
        if (!force && DOMUtils.isAvatarValid(pion)) {
          // Mettre à jour uniquement la bordure
          const iconElement = pion.querySelector(CONFIG.SELECTOR_ICON);
          const avatarImg = iconElement?.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);

          if (avatarImg) {
            const borderColor = AvatarStyler.getBorderColor(pion);
            avatarImg.style.setProperty('border', `3px solid ${borderColor}`, 'important');
          }
          continue;
        }
        await this.applyCustomAvatar(pion, force);
      }
    },

    reapplyAvatarsSync(force = false) {
      if (!Storage.loadAvatarEnabled()) return;

      DOMUtils.getAllPions().forEach(pion => {
        if (!force && DOMUtils.isAvatarValid(pion)) {
          // Mettre à jour uniquement la bordure
          const iconElement = pion.querySelector(CONFIG.SELECTOR_ICON);
          const avatarImg = iconElement?.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);

          if (avatarImg) {
            const borderColor = AvatarStyler.getBorderColor(pion);
            avatarImg.style.setProperty('border', `3px solid ${borderColor}`, 'important');
          }
          return;
        }
        this.applyCustomAvatar(pion, force);
      });
    }
  };

  // ============================================================
  // Système de Réapplication rapide
  // ============================================================
  const ReapplicationSystem = {
    ultraFastReapplication() {
      const now = Date.now();

      if (now - state.lastReapplyTime < CONFIG.RAF_THROTTLE) {
        state.reapplyAnimationFrameId = requestAnimationFrame(() => this.ultraFastReapplication());
        return;
      }

      state.lastReapplyTime = now;

      if (Storage.loadAvatarEnabled()) {
        AvatarManager.reapplyAvatarsSync(false);
      }

      state.reapplyAnimationFrameId = requestAnimationFrame(() => this.ultraFastReapplication());
    },

    start() {
      state.stopReapplication();
      Utils.debugLog('🔄 Démarrage système de réapplication');

      state.reapplyIntervalId = setInterval(() => {
        if (Storage.loadAvatarEnabled()) {
          AvatarManager.reapplyAvatarsSync(false);
        }
      }, CONFIG.REAPPLY_INTERVAL);

      this.ultraFastReapplication();
    },

    stop() {
      state.stopReapplication();
      Utils.debugLog('⏹️ Arrêt système de réapplication');
    }
  };

  // ============================================================
  // Systeme de taille & styles
  // ============================================================
  const SizingSystem = {
    applyAvatarSize(size) {
      const scale = size / 100;

      let styleElement = document.getElementById('dreadcast-avatar-resize-style');
      if (!styleElement) {
        styleElement = document.createElement('style');
        styleElement.id = 'dreadcast-avatar-resize-style';
        document.head.appendChild(styleElement);
      }

      const emojiSize = Storage.loadEmojiSize();

      styleElement.textContent = `
        .personnages .icon_perso {
          transform: scale(${scale}) !important;
          transform-origin: center center !important;
        }

        .personnages .icon_perso .le_icon_perso {
          transform: scale(1) !important;
          position: relative !important;
        }

        .personnages .icon_perso {
          z-index: auto !important;
        }

        .${CONFIG.CLASS_AVATAR_IMG} {
          pointer-events: none !important;
          width: 70px !important;
          height: 70px !important;
          object-fit: cover !important;
          border-radius: 50% !important;
          border: 2px solid rgba(255, 255, 255, 0.8) !important;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
          position: absolute !important;
          top: 50% !important;
          left: 50% !important;
          transform: translate(-50%, -50%) !important;
          z-index: ${CONFIG.Z_INDEX_AVATAR} !important;
          display: block !important;
          visibility: visible !important;
          opacity: 1 !important;
          transition: border-color 0.3s ease !important;
          animation: none !important;
        }

        .personnages .icon_perso .le_icon_perso > * {
          position: relative !important;
        }

        .personnages .icon_perso .le_icon_perso > .${CONFIG.CLASS_AVATAR_IMG} {
          z-index: ${CONFIG.Z_INDEX_AVATAR} !important;
        }

        .personnages .icon_perso .le_icon_perso > svg,
        .personnages .icon_perso .le_icon_perso > use {
          z-index: 1 !important;
        }

        .${CONFIG.CLASS_ACTION_EMOJI} {
          position: absolute !important;
          top: -5px !important;
          right: -15px !important;
          font-size: ${emojiSize}px !important;
          border-radius: 50% !important;
          width: ${emojiSize * 0.56}px !important;
          height: ${emojiSize * 0.56}px !important;
          display: flex !important;
          align-items: center !important;
          justify-content: center !important;
          box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important;
          z-index: ${CONFIG.Z_INDEX_EMOJI} !important;
          pointer-events: none !important;
          border: 2px solid rgba(0, 0, 0, 0.1) !important;
          transition: opacity 0.2s ease !important;
        }
      `;
    },

    applyEmojiSize(size) {
      // Cette fonction met à jour uniquement la taille des emojis
      // Elle réutilise applyAvatarSize qui utilise déjà Storage.loadEmojiSize()
      const currentAvatarSize = Storage.loadAvatarSize();
      this.applyAvatarSize(currentAvatarSize);
      Utils.debugLog('✅ Taille emoji appliquée:', `${size}px`);
    }
  };

  // ============================================================
  // Mise à jour de la légende des couleurs
  // ============================================================
  function updateColorLegend() {
    const legendContainer = document.getElementById('color-legend-container');
    if (!legendContainer) return;

    const customColors = Storage.loadCustomColors();
    const opacity = Storage.loadColorOpacity() / 100;

    const legendHTML = Object.entries(CONFIG.ACTION_COLORS)
      .filter(([action]) => !['encombat', 'noaction'].includes(action))
      .map(([action, defaultColor]) => {
        const hexColor = customColors[action] || defaultColor;
        const rgbaColor = Utils.hexToRgba(hexColor, opacity);

        const labels = {
          'en_combat': '⚔️ Combat',
          'aucune': '⏸️ Aucune',
          'repos': '😴 Repos',
          'recherche': '⛏️ Fouille',
          'cacher': '🫣 Cacher',
          'scruter': '👀 Scruter',
          'soin': '💊 Soigner',
          'travail': '⚙️ Travailler',
          'ko': '💀 Mort',
          'destruction': '💥 Destruction',
          'reparation': '🔧 Reparation'
        };

        return `
          <div style="display: inline-flex !important; align-items: center !important; margin: 4px 8px !important;">
            <div style="width: 16px !important; height: 16px !important; border-radius: 50% !important; background: ${rgbaColor} !important; margin-right: 6px !important; border: 2px solid rgba(0, 0, 0, 0.1) !important;"></div>
            <span style="font-size: 12px !important; color: #4a5568 !important;">${labels[action]}</span>
          </div>
        `;
      }).join('');

    legendContainer.innerHTML = legendHTML;
  }

  // ============================================================
  // Composants UI
  // ============================================================
  const UIComponents = {
    createDraggableBehavior(element, handle) {
      let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
      let isDragging = false;

      handle.onmousedown = (e) => {
        e.preventDefault();
        e.stopPropagation();

        isDragging = true;

        const rect = element.getBoundingClientRect();
        element.style.transform = 'none';
        element.style.top = `${rect.top}px`;
        element.style.left = `${rect.left}px`;

        pos3 = e.clientX;
        pos4 = e.clientY;
        document.onmouseup = closeDrag;
        document.onmousemove = elementDrag;
      };

      function elementDrag(e) {
        e.preventDefault();
        pos1 = pos3 - e.clientX;
        pos2 = pos4 - e.clientY;
        pos3 = e.clientX;
        pos4 = e.clientY;
        element.style.top = `${element.offsetTop - pos2}px`;
        element.style.left = `${element.offsetLeft - pos1}px`;
      }

      function closeDrag(e) {
        if (e) {
          e.stopPropagation();
        }
        isDragging = false;
        document.onmouseup = null;
        document.onmousemove = null;
      }
    },

    attachPanelEvents() {
      const closeBtn = document.getElementById('avatar-close-btn');
      const slider = document.getElementById('avatar-size-slider');
      const valueDisplay = document.getElementById('avatar-size-value');
      const resetBtn = document.getElementById('avatar-reset-btn');
      const avatarCheckbox = document.getElementById('avatar-enabled-checkbox');
      const emojiCheckbox = document.getElementById('emoji-enabled-checkbox');
      const emojiSizeSlider = document.getElementById('emoji-size-slider');
      const emojiSizeValue = document.getElementById('emoji-size-value');
      const resetColorsBtn = document.getElementById('reset-colors-btn');
      const panel = document.getElementById('dreadcast-avatar-config-panel');

      if (!closeBtn || !slider || !valueDisplay || !resetBtn || !avatarCheckbox || !emojiCheckbox || !emojiSizeSlider || !emojiSizeValue) {
        Utils.debugLog('❌ Éléments du panneau introuvables');
        return;
      }

      const currentSize = Storage.loadAvatarSize();
      const avatarsEnabled = Storage.loadAvatarEnabled();
      const emojisEnabled = Storage.loadEmojiEnabled();
      const currentEmojiSize = Storage.loadEmojiSize();

      slider.value = currentSize;
      valueDisplay.textContent = `${currentSize}%`;
      avatarCheckbox.checked = avatarsEnabled;
      emojiCheckbox.checked = emojisEnabled;
      emojiSizeSlider.value = currentEmojiSize;
      emojiSizeValue.textContent = `${currentEmojiSize}px`;

      closeBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        panel.style.display = 'none';
      });

      slider.addEventListener('input', function () {
        const size = parseInt(this.value, 10);
        valueDisplay.textContent = `${size}%`;

        if (!CombatDetector.isInCombat) {
          SizingSystem.applyAvatarSize(size);
          Storage.saveAvatarSize(size);
        } else {
          CombatDetector.savedSize = size;
          Storage.saveAvatarSize(size);
          Utils.debugLog('⚔️ En combat: taille sauvegardée mais non appliquée');
        }
      });

      avatarCheckbox.addEventListener('change', function () {
        const enabled = this.checked;
        Storage.saveAvatarEnabled(enabled);

        if (enabled) {
          Utils.debugLog('🖼️ Activation des avatars...');
          ReapplicationSystem.start();
          AvatarManager.applyAvatarsToAllPions(true);
        } else {
          Utils.debugLog('🚫 Désactivation des avatars...');
          ReapplicationSystem.stop();
          AvatarManager.removeCustomAvatars();
        }
      });

      emojiCheckbox.addEventListener('change', function () {
        const enabled = this.checked;
        Storage.saveEmojiEnabled(enabled);

        if (enabled) {
          Utils.debugLog('😀 Activation des emojis...');
          // Réappliquer tous les avatars pour ajouter les emojis
          AvatarManager.applyAvatarsToAllPions(true);
        } else {
          Utils.debugLog('🚫 Désactivation des emojis...');
          // Supprimer tous les emojis
          document.querySelectorAll(`.${CONFIG.CLASS_ACTION_EMOJI}`).forEach(emoji => emoji.remove());
        }
      });

      emojiSizeSlider.addEventListener('input', function () {
        const size = parseInt(this.value, 10);
        emojiSizeValue.textContent = `${size}px`;
        SizingSystem.applyEmojiSize(size);
        Storage.saveEmojiSize(size);
        // Réappliquer les avatars pour mettre à jour les emojis
        AvatarManager.applyAvatarsToAllPions(true);
      });

      resetBtn.addEventListener('click', () => {
        slider.value = CONFIG.DEFAULT_SIZE;
        valueDisplay.textContent = `${CONFIG.DEFAULT_SIZE}%`;
        avatarCheckbox.checked = true;
        emojiCheckbox.checked = true;
        emojiSizeSlider.value = CONFIG.DEFAULT_EMOJI_SIZE;
        emojiSizeValue.textContent = `${CONFIG.DEFAULT_EMOJI_SIZE}px`;

        // Réinitialiser l'opacité
        const opacitySlider = document.getElementById('color-opacity-slider');
        const opacityValue = document.getElementById('color-opacity-value');
        if (opacitySlider && opacityValue) {
          opacitySlider.value = CONFIG.DEFAULT_COLOR_OPACITY;
          opacityValue.textContent = `${CONFIG.DEFAULT_COLOR_OPACITY}%`;
          Storage.saveColorOpacity(CONFIG.DEFAULT_COLOR_OPACITY);
        }

        // Réinitialiser les couleurs aussi
        Storage.saveCustomColors({});

        SizingSystem.applyAvatarSize(CONFIG.DEFAULT_SIZE);
        SizingSystem.applyEmojiSize(CONFIG.DEFAULT_EMOJI_SIZE);
        Storage.saveAvatarSize(CONFIG.DEFAULT_SIZE);
        Storage.saveAvatarEnabled(true);
        Storage.saveEmojiEnabled(true);
        Storage.saveEmojiSize(CONFIG.DEFAULT_EMOJI_SIZE);

        // Réinitialiser les color pickers
        const colorActions = ['en_combat', 'aucune', 'repos', 'recherche', 'cacher', 'scruter', 'soin', 'travail', 'ko', 'destruction', 'reparation'];
        const colorStatuses = ['connected', 'disconnected'];

        colorActions.forEach(action => {
          const colorPicker = document.getElementById(`color-${action}`);
          if (colorPicker) {
            colorPicker.value = CONFIG.ACTION_COLORS[action];
          }
        });

        colorStatuses.forEach(status => {
          const colorPicker = document.getElementById(`color-${status}`);
          if (colorPicker) {
            colorPicker.value = status === 'connected' ? CONFIG.COLOR_CONNECTED : CONFIG.COLOR_DISCONNECTED;
          }
        });

        // Mettre à jour la légende
        updateColorLegend();

        state.clearCaches();
        ReapplicationSystem.start();
        AvatarManager.applyAvatarsToAllPions(true);
      });

      // Color pickers pour les actions
      const colorActions = ['en_combat', 'aucune', 'repos', 'recherche', 'fouille', 'cacher', 'scruter', 'soin', 'travail', 'ko', 'destruction', 'reparation'];
      const colorStatuses = ['connected', 'disconnected'];

      // Charger les couleurs personnalisées
      const customColors = Storage.loadCustomColors();

      // Initialiser les color pickers avec les couleurs personnalisées ou par défaut
      colorActions.forEach(action => {
        const colorPicker = document.getElementById(`color-${action}`);
        if (colorPicker) {
          // Charger la couleur personnalisée ou la couleur par défaut
          const savedColor = customColors[action] || CONFIG.ACTION_COLORS[action];
          colorPicker.value = savedColor;

          // Event listener pour sauvegarder la couleur
          colorPicker.addEventListener('input', function() {
            const newColor = this.value;
            const allCustomColors = Storage.loadCustomColors();
            allCustomColors[action] = newColor;
            Storage.saveCustomColors(allCustomColors);

            // Mettre à jour la légende
            updateColorLegend();

            // Réappliquer les avatars pour mettre à jour les couleurs
            AvatarManager.applyAvatarsToAllPions(true);

            Utils.debugLog(`🎨 Couleur ${action} mise à jour: ${newColor}`);
          });
        }
      });

      // Color pickers pour les statuts
      colorStatuses.forEach(status => {
        const colorPicker = document.getElementById(`color-${status}`);
        if (colorPicker) {
          const savedColor = customColors[status] || (status === 'connected' ? CONFIG.COLOR_CONNECTED : CONFIG.COLOR_DISCONNECTED);
          colorPicker.value = savedColor;

          colorPicker.addEventListener('input', function() {
            const newColor = this.value;
            const allCustomColors = Storage.loadCustomColors();
            allCustomColors[status] = newColor;
            Storage.saveCustomColors(allCustomColors);

            AvatarManager.applyAvatarsToAllPions(true);

            Utils.debugLog(`🎨 Couleur ${status} mise à jour: ${newColor}`);
          });
        }
      });

      // Slider d'opacité des couleurs
      const opacitySlider = document.getElementById('color-opacity-slider');
      const opacityValue = document.getElementById('color-opacity-value');

      if (opacitySlider && opacityValue) {
        // Charger l'opacité sauvegardée
        const currentOpacity = Storage.loadColorOpacity();
        opacitySlider.value = currentOpacity;
        opacityValue.textContent = `${currentOpacity}%`;

        // Event listener pour le slider
        opacitySlider.addEventListener('input', function() {
          const opacity = parseInt(this.value, 10);
          opacityValue.textContent = `${opacity}%`;

          // Sauvegarder l'opacité
          Storage.saveColorOpacity(opacity);

          // Réappliquer tous les avatars pour mettre à jour les couleurs
          AvatarManager.applyAvatarsToAllPions(true);

          // Mettre à jour la légende
          if (typeof updateColorLegend === 'function') {
            updateColorLegend();
          }

          Utils.debugLog(`👁️ Opacité des couleurs mise à jour: ${opacity}%`);
        });
      }

      // Bouton reset couleurs
      if (resetColorsBtn) {
        resetColorsBtn.addEventListener('click', () => {
          // Réinitialiser toutes les couleurs
          Storage.saveCustomColors({});

          // Réinitialiser l'opacité
          const opacitySlider = document.getElementById('color-opacity-slider');
          const opacityValue = document.getElementById('color-opacity-value');
          if (opacitySlider && opacityValue) {
            opacitySlider.value = CONFIG.DEFAULT_COLOR_OPACITY;
            opacityValue.textContent = `${CONFIG.DEFAULT_COLOR_OPACITY}%`;
            Storage.saveColorOpacity(CONFIG.DEFAULT_COLOR_OPACITY);
          }

          // Réinitialiser les color pickers
          colorActions.forEach(action => {
            const colorPicker = document.getElementById(`color-${action}`);
            if (colorPicker) {
              colorPicker.value = CONFIG.ACTION_COLORS[action];
            }
          });

          colorStatuses.forEach(status => {
            const colorPicker = document.getElementById(`color-${status}`);
            if (colorPicker) {
              colorPicker.value = status === 'connected' ? CONFIG.COLOR_CONNECTED : CONFIG.COLOR_DISCONNECTED;
            }
          });

          // Mettre à jour la légende
          updateColorLegend();

          // Réappliquer les avatars
          AvatarManager.applyAvatarsToAllPions(true);

          Utils.debugLog('✅ Couleurs réinitialisées');
        });
      }

      const dataBox = panel.querySelector('.dataBox');
      const head = panel.querySelector('.head');
      if (dataBox && head) {
        this.createDraggableBehavior(dataBox, head);
      }
    },

    createConfigPanel() {
      let panel = document.getElementById('dreadcast-avatar-config-panel');
      if (panel) return panel;

      panel = document.createElement('div');
      panel.id = 'dreadcast-avatar-config-panel';
      panel.style.cssText = `
        position: fixed !important;
        top: 0 !important;
        left: 0 !important;
        width: 100% !important;
        height: 100% !important;
        background-color: rgba(0, 0, 0, 0.7) !important;
        z-index: ${CONFIG.Z_INDEX_OVERLAY} !important;
        display: none !important;
      `;

      // Créer la légende des couleurs
      const customColors = Storage.loadCustomColors();
      const legendHTML = Object.entries(CONFIG.ACTION_COLORS)
        .filter(([action]) => !['encombat', 'noaction'].includes(action))
        .map(([action, defaultColor]) => {
          const color = customColors[action] || defaultColor;
          const labels = {
            'en_combat': '⚔️ Combat',
            'aucune': '⏸️ Aucune',
            'repos': '😴 Repos',
            'recherche': '⛏️ Fouille',

            'cacher': '🫣 Cacher',
            'scruter': '👀 Scruter',
            'soin': '💊 Soigner',
            'travail': '⚙️ Travailler',
            'ko': '💀 Mort/KO',
            'destruction': '💥 Destruction',
            'reparation': '🔧 Réparation'
          };

          return `
            <div style="display: inline-flex !important; align-items: center !important; margin: 4px 8px !important;">
              <div style="width: 16px !important; height: 16px !important; border-radius: 50% !important; background: ${color} !important; margin-right: 6px !important; border: 2px solid rgba(0, 0, 0, 0.1) !important;"></div>
              <span style="font-size: 12px !important; color: #4a5568 !important;">${labels[action]}</span>
            </div>
          `;
        }).join('');

      panel.innerHTML = `
        <div class="dataBox" style="position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; z-index: ${CONFIG.Z_INDEX_PANEL} !important; width: 600px !important; max-height: 90vh !important; overflow-y: auto !important; background: #ffffff !important; border-radius: 12px !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;">
          <relative>
            <div class="head" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; padding: 20px !important; cursor: move !important; position: relative !important;">
              <div class="title" style="color: #ffffff !important; font-size: 18px !important; font-weight: 600 !important; text-align: center !important;">🎀 PmP</div>
              <div title="Fermer" class="close" id="avatar-close-btn" style="position: absolute !important; top: 15px !important; right: 15px !important; color: #ffffff !important; cursor: pointer !important; font-size: 24px !important; background: rgba(255, 255, 255, 0.2) !important; border-radius: 6px !important; width: 32px !important; height: 32px !important; display: flex !important; align-items: center !important; justify-content: center !important;">✕</div>
            </div>
            <div class="content" style="padding: 30px !important; background: #ffffff !important;">
              <div style="margin-bottom: 25px !important; padding: 18px !important; background: linear-gradient(135deg, #f6f8fb 0%, #eef2f7 100%) !important; border-left: 4px solid #667eea !important; border-radius: 8px !important;">
                <label style="display: flex !important; align-items: center !important; cursor: pointer !important;">
                  <input type="checkbox" id="avatar-enabled-checkbox" style="width: 20px !important; height: 20px !important; margin-right: 12px !important; cursor: pointer !important; accent-color: #667eea !important;">
                  <span style="font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">🖼️ Afficher les avatars des joueurs</span>
                </label>
                <div style="margin-top: 10px !important; font-size: 12px !important; color: #718096 !important; padding-left: 32px !important;">Remplace l'icône par défaut par l'avatar réel de chaque joueur</div>
              </div>

              <div style="margin-bottom: 25px !important; padding: 18px !important; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important; border-left: 4px solid #f59e0b !important; border-radius: 8px !important;">
                <label style="display: flex !important; align-items: center !important; cursor: pointer !important;">
                  <input type="checkbox" id="emoji-enabled-checkbox" style="width: 20px !important; height: 20px !important; margin-right: 12px !important; cursor: pointer !important; accent-color: #f59e0b !important;">
                  <span style="font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">😀 Afficher les emojis d'action</span>
                </label>
                <div style="margin-top: 10px !important; font-size: 12px !important; color: #92400e !important; padding-left: 32px !important;">Ajoute un emoji dans le coin de l'avatar pour indiquer l'action en cours</div>
              </div>

              <div style="margin-bottom: 20px !important; padding: 18px !important; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important; border-left: 4px solid #f59e0b !important; border-radius: 8px !important;">
                <div style="font-size: 14px !important; color: #78350f !important; font-weight: 600 !important; margin-bottom: 12px !important;">🎨 Couleurs par action</div>
                <div id="color-legend-container" style="display: flex !important; flex-wrap: wrap !important; gap: 4px !important;">
                  ${legendHTML}
                </div>
                <div style="margin-top: 10px !important; font-size: 11px !important; color: #92400e !important;">La bordure de l'avatar change de couleur selon l'action du joueur</div>
              </div>

              <div style="margin-bottom: 20px !important; padding: 18px !important; background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%) !important; border-left: 4px solid #6366f1 !important; border-radius: 8px !important;">
                <div style="font-size: 14px !important; color: #312e81 !important; font-weight: 600 !important; margin-bottom: 12px !important;">🎨 Personnaliser les couleurs</div>

                <div style="display: grid !important; grid-template-columns: 1fr 1fr !important; gap: 10px !important; margin-top: 12px !important;">

                  <!-- Combat -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">⚔️ Combat</label>
                    <input type="color" id="color-en_combat" value="#ef4444" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Aucune -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">⏸️ Aucune</label>
                    <input type="color" id="color-aucune" value="#9ca3af" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Repos -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">😴 Repos</label>
                    <input type="color" id="color-repos" value="#06b6d4" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Recherche -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">⛏️ Fouille</label>
                    <input type="color" id="color-recherche" value="#f59e0b" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>


                  <!-- Cacher -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">🫣 Cacher</label>
                    <input type="color" id="color-cacher" value="#8b5cf6" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Scruter -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">👀 Scruter</label>
                    <input type="color" id="color-scruter" value="#f97316" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Soigner -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">💊 Soigner</label>
                    <input type="color" id="color-soin" value="#10b981" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Travailler -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">⚙️ Travailler</label>
                    <input type="color" id="color-travail" value="#92400e" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- KO/Mort -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">💀 KO/Mort</label>
                    <input type="color" id="color-ko" value="#1f2937" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Destruction -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">💥 Destruction</label>
                    <input type="color" id="color-destruction" value="#ef4444" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Réparation -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">🔧 Réparation</label>
                    <input type="color" id="color-reparation" value="#ef4444" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Connecté -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">🟢 Connecté</label>
                    <input type="color" id="color-connected" value="#4ade80" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                  <!-- Déconnecté -->
                  <div style="display: flex !important; align-items: center !important; justify-content: space-between !important;">
                    <label style="font-size: 12px !important; color: #4a5568 !important;">⚪ Déconnecté</label>
                    <input type="color" id="color-disconnected" value="#ffffff" style="width: 40px !important; height: 30px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important;">
                  </div>

                </div>

                <div style="margin-top: 12px !important; text-align: center !important;">
                  <button id="reset-colors-btn" style="padding: 8px 20px !important; background: #6366f1 !important; color: #ffffff !important; border: none !important; border-radius: 6px !important; cursor: pointer !important; font-size: 12px !important; font-weight: 600 !important;">🔄 Réinitialiser les couleurs</button>
                </div>

                <div style="margin-top: 10px !important; font-size: 11px !important; color: #4338ca !important;">Cliquez sur les carrés de couleur pour personnaliser chaque action</div>
              </div>

              <div style="margin-bottom: 25px !important; padding: 18px !important; background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important; border-left: 4px solid #f59e0b !important; border-radius: 8px !important;">
                <label style="display: block !important; margin-bottom: 15px !important; font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">
                  👁️ Opacité des couleurs : <span id="color-opacity-value" style="color: #f59e0b !important; font-weight: 700 !important;">100%</span>
                </label>
                <input type="range" id="color-opacity-slider" min="${CONFIG.MIN_COLOR_OPACITY}" max="${CONFIG.MAX_COLOR_OPACITY}" value="${CONFIG.DEFAULT_COLOR_OPACITY}" style="width: 100% !important; height: 6px !important; background: linear-gradient(to right, #fde68a, #f59e0b) !important; border-radius: 10px !important; cursor: pointer !important;">
                <div style="margin-top: 8px !important; font-size: 12px !important; color: #92400e !important;">💡 0% = invisible, 100% = couleurs vives</div>
                <style>
                  #color-opacity-slider::-webkit-slider-thumb {
                    -webkit-appearance: none !important;
                    width: 20px !important;
                    height: 20px !important;
                    background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                    box-shadow: 0 2px 6px rgba(245, 158, 11, 0.4) !important;
                  }
                  #color-opacity-slider::-webkit-slider-thumb:hover {
                    transform: scale(1.2) !important;
                  }
                  #color-opacity-slider::-moz-range-thumb {
                    width: 20px !important;
                    height: 20px !important;
                    background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
                    border: none !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                  }
                </style>
              </div>

              <div style="margin-bottom: 25px !important;">
                <label style="display: block !important; margin-bottom: 15px !important; font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">
                  📏 Taille des pions : <span id="avatar-size-value" style="color: #667eea !important; font-weight: 700 !important;">100%</span>
                </label>
                <input type="range" id="avatar-size-slider" min="${CONFIG.MIN_SIZE}" max="${CONFIG.MAX_SIZE}" value="100" style="width: 100% !important; height: 6px !important; background: linear-gradient(to right, #e2e8f0, #667eea) !important; border-radius: 10px !important; cursor: pointer !important;">
                <div style="margin-top: 8px !important; font-size: 12px !important; color: #718096 !important;">⚔️ La taille passe automatiquement à 100% en combat</div>
              </div>

              <div style="margin-bottom: 25px !important;">
                <label style="display: block !important; margin-bottom: 15px !important; font-size: 15px !important; color: #2d3748 !important; font-weight: 500 !important;">
                  😀 Taille des emojis : <span id="emoji-size-value" style="color: #f59e0b !important; font-weight: 700 !important;">18px</span>
                </label>
                <input type="range" id="emoji-size-slider" min="${CONFIG.MIN_EMOJI_SIZE}" max="${CONFIG.MAX_EMOJI_SIZE}" value="${CONFIG.DEFAULT_EMOJI_SIZE}" style="width: 100% !important; height: 6px !important; background: linear-gradient(to right, #fde68a, #f59e0b) !important; border-radius: 10px !important; cursor: pointer !important;">
                <div style="margin-top: 8px !important; font-size: 12px !important; color: #718096 !important;">📐 Ajustez la taille des emojis d'action</div>
                <style>
                  #avatar-size-slider::-webkit-slider-thumb {
                    -webkit-appearance: none !important;
                    width: 20px !important;
                    height: 20px !important;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                    box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4) !important;
                  }
                  #avatar-size-slider::-webkit-slider-thumb:hover {
                    transform: scale(1.2) !important;
                  }
                  #avatar-size-slider::-moz-range-thumb {
                    width: 20px !important;
                    height: 20px !important;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
                    border: none !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                  }
                  #emoji-size-slider::-webkit-slider-thumb {
                    -webkit-appearance: none !important;
                    width: 20px !important;
                    height: 20px !important;
                    background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                    box-shadow: 0 2px 6px rgba(245, 158, 11, 0.4) !important;
                  }
                  #emoji-size-slider::-webkit-slider-thumb:hover {
                    transform: scale(1.2) !important;
                  }
                  #emoji-size-slider::-moz-range-thumb {
                    width: 20px !important;
                    height: 20px !important;
                    background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%) !important;
                    border: none !important;
                    border-radius: 50% !important;
                    cursor: pointer !important;
                  }
                  #avatar-size-slider:disabled::-webkit-slider-thumb {
                    background: #9ca3af !important;
                  }
                  #avatar-size-slider:disabled::-moz-range-thumb {
                    background: #9ca3af !important;
                  }
                  #avatar-close-btn:hover {
                    background: rgba(255, 255, 255, 0.3) !important;
                    transform: rotate(90deg) !important;
                  }
                  #avatar-reset-btn:hover {
                    transform: translateY(-2px) !important;
                    box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;
                  }
                  #reset-colors-btn:hover {
                    background: #4f46e5 !important;
                    transform: translateY(-1px) !important;
                  }
                </style>
              </div>

              <div style="text-align: center !important;">
                <button id="avatar-reset-btn" style="padding: 12px 30px !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; cursor: pointer !important; font-size: 14px !important; font-weight: 600 !important;">🔄 Réinitialiser</button>
              </div>
            </div>
          </relative>
        </div>
      `;

      panel.addEventListener('click', (e) => {
        if (e.target === panel) {
          panel.style.display = 'none';
        }
      });

      const appendPanel = () => {
        if (document.body) {
          document.body.appendChild(panel);
          setTimeout(() => this.attachPanelEvents(), CONFIG.EVENT_ATTACH_DELAY);
        } else {
          setTimeout(appendPanel, CONFIG.MENU_CHECK_INTERVAL);
        }
      };

      appendPanel();
      return panel;
    },

    openConfigPanel() {
      let panel = document.getElementById('dreadcast-avatar-config-panel');
      if (!panel) {
        panel = this.createConfigPanel();
      }
      if (panel) {
        panel.style.display = 'block';
        Utils.debugLog('✅ Panneau ouvert');
      }
    }
  };

  // ============================================================
  // Intégration du menu
  // ============================================================
  const MenuIntegration = {
    addMenuOption() {
      const checkMenu = setInterval(() => {
        const parametresMenu = document.querySelector(CONFIG.SELECTOR_SETTINGS_MENU);

        if (parametresMenu) {
          clearInterval(checkMenu);

          if (document.getElementById('avatar-resize-menu-option')) return;

          const menuOption = document.createElement('li');
          menuOption.id = 'avatar-resize-menu-option';
          menuOption.className = 'link couleur2';
          menuOption.textContent = '🎀 PmP v0.5.1';
          menuOption.style.cursor = 'pointer';

          menuOption.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            UIComponents.openConfigPanel();
          }, true);

          const lastSeparator = parametresMenu.querySelector('.separator:last-of-type');
          if (lastSeparator) {
            parametresMenu.insertBefore(menuOption, lastSeparator);
          } else {
            parametresMenu.appendChild(menuOption);
          }

          Utils.debugLog('✅ Option menu ajoutée');
        }
      }, CONFIG.MENU_CHECK_INTERVAL);

      setTimeout(() => clearInterval(checkMenu), CONFIG.MENU_CHECK_TIMEOUT);
    }
  };

  // ============================================================
  // DOM OBSERVER
  // ============================================================
  const DOMObserver = {
    observe() {
      const targetNode = document.querySelector('.personnages');
      if (!targetNode) return;

      const observer = new MutationObserver((mutations) => {
        Utils.debugLog('🔍 Changement DOM détecté:', mutations.length);

        const currentSize = Storage.loadAvatarSize();

        if (CombatDetector.isInCombat) {
          SizingSystem.applyAvatarSize(100);
        } else {
          SizingSystem.applyAvatarSize(currentSize);
        }

        mutations.forEach(mutation => {
          mutation.addedNodes.forEach(node => {
            if (node.nodeType === 1 && node.classList?.contains('icon_perso')) {
              Utils.debugLog('🆕 Nouveau pion détecté');
              AvatarManager.applyCustomAvatar(node, true);
            }
          });

          if (mutation.type === 'childList' || mutation.type === 'attributes') {
            const target = mutation.target;
            const pionElement = target.classList?.contains('icon_perso')
              ? target
              : target.closest('.icon_perso');

            if (pionElement && !DOMUtils.isAvatarValid(pionElement)) {
              Utils.debugLog('⚠️ Pion modifié, réapplication...');
              AvatarManager.applyCustomAvatar(pionElement, true);
            }
          }
        });
      });

      observer.observe(targetNode, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['class'],
        attributeOldValue: true,
        characterData: true,
        characterDataOldValue: true
      });

      Utils.debugLog('✅ MutationObserver activé');
    }
  };

  // ============================================================
  // Initialisation
  // ============================================================
  function init() {
    Utils.debugLog('⚡ PimpMyPion - Initialisation');

    const savedSize = Storage.loadAvatarSize();
    const savedEmojiSize = Storage.loadEmojiSize();

    SizingSystem.applyAvatarSize(savedSize);
    SizingSystem.applyEmojiSize(savedEmojiSize);

    Utils.debugLog('✅ Taille pions appliquée:', `${savedSize}%`);
    Utils.debugLog('✅ Taille emojis appliquée:', `${savedEmojiSize}px`);

    MenuIntegration.addMenuOption();
    DOMObserver.observe();

    CombatDetector.start();
    Utils.debugLog('✅ Détecteur de combat initialisé');

    setTimeout(() => {
      Utils.debugLog('🖼️ Application initiale des avatars');
      AvatarManager.applyAvatarsToAllPions(true);

      if (Storage.loadAvatarEnabled()) {
        ReapplicationSystem.start();
      }
    }, CONFIG.INIT_DELAY);

    setTimeout(() => {
      Utils.debugLog('🔄 Réapplication de sécurité');
      AvatarManager.applyAvatarsToAllPions(true);
    }, CONFIG.SECONDARY_DELAY);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

})();