Dreadcast - PimpMyPion v0.5.2 [VERSION TEST]

Version TEST

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Dreadcast - PimpMyPion v0.5.2 [VERSION TEST]
// @namespace    http://tampermonkey.net/
// @version      0.5.2
// @description  Version TEST
// @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: false
  });

  // ============================================================
  // 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) {        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) {        return null;
      }
    },

    /**
     * Vide le cache des actions
     */
    clearCache() {
      state.actionCache.clear();    },

    /**
     * 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);        }

        // Mettre à jour le contenu de l'emoji si nécessaire
        if (emojiElement.textContent !== emoji) {
          emojiElement.textContent = emoji;        }

      } catch (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();        }
      } catch (error) {      }
    }
  };

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

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

    start() {
      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() {
      const currentSize = Storage.loadAvatarSize();
      this.savedSize = currentSize;
      this.isInCombat = true;

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

    onExitCombat() {
      this.isInCombat = false;

      if (this.savedSize !== null) {
        SizingSystem.applyAvatarSize(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;
      }    }
  };

  // ============================================================
  // 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));    },

    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));    },

    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));    },

    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));    },

    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));    },

    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));    },

    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) {      }
      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);
    }
  };

  // ============================================================
  // PIE CHART MANAGER - Gestion des pions multiples
  // ============================================================
  const PieChartManager = {
    debugMode: true, // ✅ Mode debug activé pour diagnostiquer

    /**
     * Vérifie si un pion est visible
     * @param {HTMLElement} pionElement - L'élément pion
     * @returns {boolean} - true si visible
     */
    isPionVisible(pionElement) {
      try {
        const style = window.getComputedStyle(pionElement);
        return style.display !== 'none' &&
               style.visibility !== 'hidden' &&
               style.opacity !== '0' &&
               pionElement.offsetParent !== null;
      } catch (error) {
        return false;
      }
    },

    /**
     * Parse les noms des joueurs depuis .info_a_afficher
     * @param {HTMLElement} pionContainer - Le conteneur .icon_perso
     * @returns {Array<string>} - Liste des noms de joueurs
     */
    parsePlayerNames(pionContainer) {
      try {
        const infoElement = pionContainer.querySelector('.info_a_afficher');
        if (!infoElement) return [];

        // Les noms sont séparés par <br /> ou <br> dans le HTML
        const innerHTML = infoElement.innerHTML;
        const names = innerHTML.split(/<br\s*\/?>/i)
          .map(name => name.trim())
          .filter(name => name.length > 0);

        return names;
      } catch (error) {
        console.error('[PMP DEBUG] Erreur parsePlayerNames:', error);
        return [];
      }
    },

    /**
     * Détecte les pions multiples (UN .icon_perso avec plusieurs .le_icon_perso dedans)
     * @returns {Array} - Tableau d'objets avec les pions multiples
     */
    detectMultiplePions() {
      const multiplePions = [];
      const allPions = DOMUtils.getAllPions();

      console.log('[PMP DEBUG] 🔍 Détection des pions multiples...');
      console.log('[PMP DEBUG] Total .icon_perso trouvés:', allPions.length);

      let visibleCount = 0;
      let multipleCount = 0;

      allPions.forEach(pionContainer => {
        // Vérifier si le pion est visible
        if (!this.isPionVisible(pionContainer)) {
          return;
        }
        visibleCount++;

        // ✅ CORRECTION : Compter les .le_icon_perso à l'intérieur
        const iconElements = pionContainer.querySelectorAll('.le_icon_perso');
        const playerCount = iconElements.length;

        if (playerCount >= 2) {
          // ✅ Ce pion contient plusieurs joueurs !
          multipleCount++;

          // Parser les noms
          const playerNames = this.parsePlayerNames(pionContainer);

          if (this.debugMode) {
            console.log(`[PMP DEBUG] 🥧 Pion multiple détecté: ${playerCount} joueurs`);
            console.log(`[PMP DEBUG]    Noms: ${playerNames.join(', ')}`);
            console.log(`[PMP DEBUG]    Container:`, pionContainer);
          }

          // Vérifier que le nombre de noms correspond au nombre de .le_icon_perso
          if (playerNames.length !== playerCount) {
            console.warn(`[PMP DEBUG] ⚠️ Mismatch: ${playerCount} .le_icon_perso mais ${playerNames.length} noms`);
          }

          multiplePions.push({
            container: pionContainer,
            iconElements: Array.from(iconElements),
            playerNames: playerNames,
            count: playerCount
          });
        }
      });

      console.log('[PMP DEBUG] Pions visibles:', visibleCount);
      console.log('[PMP DEBUG] Pions multiples détectés:', multipleCount);

      return multiplePions;
    },

    /**
     * Calcule les angles de début et fin pour chaque portion du pie chart
     * @param {number} count - Nombre de joueurs
     * @returns {Array} - Tableau d'objets {startAngle, endAngle}
     */
    calculateAngles(count) {
      const anglePerSlice = 360 / count;
      const angles = [];

      for (let i = 0; i < count; i++) {
        const startAngle = i * anglePerSlice - 90; // -90 pour commencer en haut
        const endAngle = (i + 1) * anglePerSlice - 90;
        angles.push({ startAngle, endAngle });
      }

      return angles;
    },

    /**
     * Convertit des degrés en radians
     * @param {number} degrees - Angle en degrés
     * @returns {number} - Angle en radians
     */
    degreesToRadians(degrees) {
      return degrees * (Math.PI / 180);
    },

    /**
     * Génère le path SVG pour une portion de pie chart
     * @param {number} startAngle - Angle de début (en degrés)
     * @param {number} endAngle - Angle de fin (en degrés)
     * @param {number} radius - Rayon du cercle
     * @param {number} cx - Centre X
     * @param {number} cy - Centre Y
     * @returns {string} - Path SVG
     */
    generatePiePath(startAngle, endAngle, radius = 50, cx = 50, cy = 50) {
      const startRad = this.degreesToRadians(startAngle);
      const endRad = this.degreesToRadians(endAngle);

      const x1 = cx + radius * Math.cos(startRad);
      const y1 = cy + radius * Math.sin(startRad);
      const x2 = cx + radius * Math.cos(endRad);
      const y2 = cy + radius * Math.sin(endRad);

      const largeArcFlag = endAngle - startAngle > 180 ? 1 : 0;

      return `M ${cx},${cy} L ${x1},${y1} A ${radius},${radius} 0 ${largeArcFlag},1 ${x2},${y2} Z`;
    },

    /**
     * Extrait les données de chaque joueur et les trie par priorité
     * @param {Object} multiplePionData - Objet {container, iconElements, playerNames, count}
     * @returns {Array} - Liste triée des joueurs avec leurs données
     */
    extractAndSortPlayers(multiplePionData) {
      const { iconElements, playerNames } = multiplePionData;

      const playersData = iconElements.map((iconElement, index) => {
        const playerName = playerNames[index] || `Joueur ${index + 1}`;

        // Détecter l'action depuis les classes de .le_icon_perso
        let action = null;
        const actionClasses = [
          'en_combat', 'encombat',
          'recherche', 'fouille',
          'repos',
          'cacher',
          'scruter',
          'soin',
          'travail',
          'destruction',
          'reparation',
          'aucune', 'noaction',
          'ko'
        ];

        for (const actionClass of actionClasses) {
          if (iconElement.classList.contains(actionClass)) {
            action = actionClass;
            break;
          }
        }

        // Vérifier si le joueur est connecté
        const isConnected = iconElement.classList.contains(CONFIG.CLASS_CONNECTED);

        // Emoji d'action
        const emoji = ActionDetector.getActionEmoji(action) || '';

        // URL de l'avatar
        const avatarUrl = Utils.buildAvatarUrl(playerName);

        // Calculer la priorité
        let priority = 0;
        if (action === 'en_combat' || action === 'encombat') {
          priority = 3; // Combat = priorité la plus haute
        } else if (isConnected) {
          priority = 2; // Connecté
        } else {
          priority = 1; // Déconnecté
        }

        return {
          iconElement,
          playerName,
          action,
          isConnected,
          emoji,
          avatarUrl,
          priority
        };
      });

      // Trier par priorité décroissante
      return playersData.sort((a, b) => b.priority - a.priority);
    },

    /**
     * Crée le SVG du pie chart avec les avatars
     * @param {Array} playersData - Données des joueurs triés
     * @param {HTMLElement} container - Conteneur où insérer le pie chart
     * @returns {SVGElement} - Élément SVG créé
     */
    createPieChart(playersData, container) {
      const count = playersData.length;
      const angles = this.calculateAngles(count);

      // Créer le SVG
      // 🎨 TAILLE : 50px choisie pour être proportionnée aux pions vanilla (environ 70% de leur taille)
      // Cette taille permet d'avoir un pie chart lisible sans être trop imposant
      const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      svg.setAttribute('viewBox', '0 0 100 100');
      svg.setAttribute('class', 'pie-chart-svg');
      svg.style.cssText = `
        width: 50px;
        height: 50px;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: ${CONFIG.Z_INDEX_AVATAR};
        pointer-events: none;
      `;

      // Créer le groupe defs pour les clipPaths
      const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
      svg.appendChild(defs);

      // Créer chaque portion avec son avatar
      playersData.forEach((player, index) => {
        const { startAngle, endAngle } = angles[index];
        const sliceId = `slice-${Date.now()}-${index}`;

        // Créer le clipPath pour cette portion
        const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
        clipPath.setAttribute('id', sliceId);

        const clipPathPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        clipPathPath.setAttribute('d', this.generatePiePath(startAngle, endAngle));
        clipPath.appendChild(clipPathPath);
        defs.appendChild(clipPath);

        // Créer l'image de l'avatar avec le clipPath
        const image = document.createElementNS('http://www.w3.org/2000/svg', 'image');
        image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', player.avatarUrl);
        image.setAttribute('x', '0');
        image.setAttribute('y', '0');
        image.setAttribute('width', '100');
        image.setAttribute('height', '100');
        image.setAttribute('clip-path', `url(#${sliceId})`);
        image.setAttribute('preserveAspectRatio', 'xMidYMid slice');
        svg.appendChild(image);

        // Créer la bordure blanche de la portion
        const borderPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        borderPath.setAttribute('d', this.generatePiePath(startAngle, endAngle));
        borderPath.setAttribute('fill', 'none');
        borderPath.setAttribute('stroke', 'white');
        borderPath.setAttribute('stroke-width', '2');
        svg.appendChild(borderPath);
      });

      // Créer le cercle central transparent pour la zone de survol
      const centerCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
      centerCircle.setAttribute('cx', '50');
      centerCircle.setAttribute('cy', '50');
      centerCircle.setAttribute('r', '15');
      centerCircle.setAttribute('fill', 'rgba(0, 0, 0, 0.7)');
      centerCircle.setAttribute('class', 'pie-chart-center');
      centerCircle.style.pointerEvents = 'auto';
      centerCircle.style.cursor = 'pointer';
      svg.appendChild(centerCircle);

      // Ajouter le texte au centre (nombre de joueurs)
      const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
      text.setAttribute('x', '50');
      text.setAttribute('y', '50');
      text.setAttribute('text-anchor', 'middle');
      text.setAttribute('dominant-baseline', 'middle');
      text.setAttribute('fill', 'white');
      text.setAttribute('font-size', '14');
      text.setAttribute('font-weight', 'bold');
      text.setAttribute('class', 'pie-chart-count');
      text.textContent = count;
      text.style.pointerEvents = 'none';
      svg.appendChild(text);

      // Attacher les événements de survol
      this.attachHoverEvents(centerCircle, playersData, container);

      return svg;
    },

    /**
     * Crée le popup avec la liste des joueurs
     * @param {Array} playersData - Données des joueurs
     * @returns {HTMLElement} - Élément popup
     */
    createPopup(playersData) {
      const popup = document.createElement('div');
      popup.className = 'pie-chart-popup';
      popup.style.cssText = `
        position: absolute;
        bottom: 100%;
        left: 50%;
        transform: translateX(-50%) translateY(-10px);
        background: rgba(26, 26, 26, 0.98);
        border: 1px solid #4a9eff;
        border-radius: 8px;
        padding: 12px;
        min-width: 200px;
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
        z-index: ${CONFIG.Z_INDEX_PANEL};
        opacity: 0;
        transition: opacity 0.2s ease, transform 0.2s ease;
        pointer-events: none;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      `;

      // Header
      const header = document.createElement('div');
      header.style.cssText = `
        color: #4a9eff;
        font-weight: 600;
        font-size: 13px;
        margin-bottom: 8px;
        padding-bottom: 8px;
        border-bottom: 1px solid rgba(74, 158, 255, 0.3);
      `;
      header.textContent = `👥 ${playersData.length} joueurs sur la case`;
      popup.appendChild(header);

      // Liste des joueurs
      playersData.forEach(player => {
        const playerRow = document.createElement('div');
        playerRow.style.cssText = `
          display: flex;
          align-items: center;
          gap: 8px;
          padding: 6px;
          margin: 4px 0;
          background: rgba(42, 42, 42, 0.5);
          border-radius: 4px;
        `;

        // Avatar
        const avatar = document.createElement('img');
        avatar.src = player.avatarUrl;
        avatar.style.cssText = `
          width: 32px;
          height: 32px;
          border-radius: 50%;
          border: 2px solid ${player.isConnected ? '#4ade80' : '#ffffff'};
          object-fit: cover;
        `;
        playerRow.appendChild(avatar);

        // Nom
        const name = document.createElement('span');
        name.textContent = player.playerName;
        name.style.cssText = `
          flex: 1;
          color: #ffffff;
          font-size: 13px;
          font-weight: 500;
        `;
        playerRow.appendChild(name);

        // Emoji d'action
        if (player.emoji) {
          const emoji = document.createElement('span');
          emoji.textContent = player.emoji;
          emoji.style.cssText = `
            font-size: 16px;
          `;
          playerRow.appendChild(emoji);
        }

        popup.appendChild(playerRow);
      });

      return popup;
    },

    /**
     * Attache les événements de survol au centre du pie chart
     * @param {SVGElement} centerCircle - Cercle central du pie chart
     * @param {Array} playersData - Données des joueurs
     * @param {HTMLElement} container - Conteneur du pie chart
     */
    attachHoverEvents(centerCircle, playersData, container) {
      let popup = null;
      let hoverTimeout = null;

      centerCircle.addEventListener('mouseenter', () => {
        hoverTimeout = setTimeout(() => {
          // Créer le popup
          popup = this.createPopup(playersData);
          container.appendChild(popup);

          // Animer l'apparition
          setTimeout(() => {
            popup.style.opacity = '1';
            popup.style.transform = 'translateX(-50%) translateY(-5px)';
          }, 10);
        }, 300); // Délai de 300ms
      });

      centerCircle.addEventListener('mouseleave', () => {
        if (hoverTimeout) {
          clearTimeout(hoverTimeout);
          hoverTimeout = null;
        }

        if (popup) {
          popup.style.opacity = '0';
          popup.style.transform = 'translateX(-50%) translateY(-10px)';
          setTimeout(() => {
            if (popup && popup.parentElement) {
              popup.remove();
            }
            popup = null;
          }, 200);
        }
      });
    },

    /**
     * Applique le pie chart aux pions multiples
     */
    applyPieCharts() {
      console.log('[PMP DEBUG] 🥧 Application des pie charts...');
      const multiplePions = this.detectMultiplePions();

      if (multiplePions.length === 0) {
        console.log('[PMP DEBUG] ✅ Aucun pion multiple détecté');
        return;
      }

      console.log(`[PMP DEBUG] 🎯 ${multiplePions.length} pion(s) multiple(s) à traiter`);

      let pieChartCreatedCount = 0;
      let pieChartFailedCount = 0;

      multiplePions.forEach((pionData, index) => {
        const { container, iconElements, playerNames, count } = pionData;

        console.log(`[PMP DEBUG] 📍 Traitement pion ${index + 1}/${multiplePions.length}`);
        console.log(`[PMP DEBUG]    ${count} joueurs: ${playerNames.join(', ')}`);

        // Extraire et trier les joueurs par priorité
        const sortedPlayers = this.extractAndSortPlayers(pionData);
        console.log('[PMP DEBUG] ✅ Joueurs triés par priorité:', sortedPlayers.map(p => p.playerName));

        // Supprimer tous les avatars existants sur tous les .le_icon_perso
        iconElements.forEach((iconEl, idx) => {
          const existingAvatar = iconEl.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);
          if (existingAvatar) {
            existingAvatar.remove();
            console.log(`[PMP DEBUG] 🗑️ Avatar existant supprimé sur .le_icon_perso ${idx + 1}`);
          }

          // Supprimer tout pie chart existant
          const existingPieChart = iconEl.querySelector('.pie-chart-svg');
          if (existingPieChart) {
            existingPieChart.remove();
            console.log(`[PMP DEBUG] 🗑️ Pie chart existant supprimé sur .le_icon_perso ${idx + 1}`);
          }
        });

        // IMPORTANT : Créer le pie chart sur le PREMIER .le_icon_perso seulement
        const mainIconElement = iconElements[0];

        // FIX v0.5.2 : S'assurer que le conteneur a position: relative pour le centrage
        const computedStyle = window.getComputedStyle(mainIconElement);
        console.log('[PMP DEBUG] 📍 Position du conteneur AVANT:', computedStyle.position);

        // Force position: relative si ce n'est pas déjà le cas
        if (computedStyle.position === 'static') {
          mainIconElement.style.position = 'relative';
          console.log('[PMP DEBUG] ✅ Position forcée à relative');
        }

        // Créer et insérer le pie chart
        try {
          const pieChart = this.createPieChart(sortedPlayers, mainIconElement);
          console.log('[PMP DEBUG] ✅ Pie chart créé:', pieChart);

          mainIconElement.appendChild(pieChart);
          console.log('[PMP DEBUG] ✅ Pie chart inséré dans le DOM');

          // FIX v0.5.2 : Logs de diagnostic du positionnement
          const pieChartStyle = window.getComputedStyle(pieChart);
          const containerRect = mainIconElement.getBoundingClientRect();
          const pieChartRect = pieChart.getBoundingClientRect();

          console.log('[PMP DEBUG] 📊 Diagnostic du positionnement:');
          console.log('[PMP DEBUG]    Conteneur (.le_icon_perso):', {
            position: computedStyle.position,
            width: `${containerRect.width}px`,
            height: `${containerRect.height}px`,
            top: `${containerRect.top}px`,
            left: `${containerRect.left}px`
          });
          console.log('[PMP DEBUG]    Pie chart (SVG):', {
            position: pieChartStyle.position,
            width: pieChartStyle.width,
            height: pieChartStyle.height,
            top: pieChartStyle.top,
            left: pieChartStyle.left,
            transform: pieChartStyle.transform,
            zIndex: pieChartStyle.zIndex
          });
          console.log('[PMP DEBUG]    Rect du pie chart:', {
            top: `${pieChartRect.top}px`,
            left: `${pieChartRect.left}px`,
            width: `${pieChartRect.width}px`,
            height: `${pieChartRect.height}px`
          });
          console.log('[PMP DEBUG]    ✅ Centrage théorique: top: 50%, left: 50%, transform: translate(-50%, -50%)');

          // ✅ CORRECTION : Cacher les pions vanilla (.le_icon_perso) de tous les autres iconElements
          // Le pie chart est sur le premier .le_icon_perso, on cache les autres
          let hiddenPionsCount = 0;
          iconElements.forEach((iconEl, idx) => {
            if (idx > 0) { // Garder le premier visible (celui avec le pie chart)
              iconEl.style.setProperty('display', 'none', 'important');
              hiddenPionsCount++;
              console.log(`[PMP DEBUG] 👻 Pion vanilla ${idx + 1} caché`);
            }
          });
          console.log(`[PMP DEBUG] ✅ ${hiddenPionsCount} pion(s) vanilla caché(s)`);

          // Vérifier que le pie chart est bien visible
          const insertedPieChart = mainIconElement.querySelector('.pie-chart-svg');
          if (insertedPieChart) {
            const style = window.getComputedStyle(insertedPieChart);
            console.log('[PMP DEBUG] 📊 Pie chart visible:', {
              display: style.display,
              visibility: style.visibility,
              opacity: style.opacity,
              width: style.width,
              height: style.height,
              position: style.position,
              zIndex: style.zIndex
            });
            pieChartCreatedCount++;
          } else {
            console.log('[PMP DEBUG] ❌ Pie chart non trouvé après insertion !');
            pieChartFailedCount++;
          }

          // Marquer le container comme traité
          container.setAttribute('data-pie-chart-applied', 'true');
          console.log('[PMP DEBUG] ✅ Container marqué comme traité');

        } catch (error) {
          console.error('[PMP DEBUG] ❌ Erreur lors de la création du pie chart:', error);
          pieChartFailedCount++;
        }
      });

      console.log(`[PMP DEBUG] 📊 Résumé: ${pieChartCreatedCount} pie chart(s) créé(s), ${pieChartFailedCount} échec(s)`);
    }
  };

  // ============================================================
  // 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);

      // ✅ APPROCHE 1 : Suppression des couleurs d'actions
      // Ne plus utiliser les couleurs basées sur les actions (combat, repos, fouille, etc.)
      // Conserver UNIQUEMENT les couleurs connecté/déconnecté

      // ❌ SUPPRIMÉ : Couleurs par action
      // const action = ActionDetector.getPlayerAction(pionElement);
      // if (action) {
      //   const actionColor = ActionDetector.getActionColor(action);
      //      //   return actionColor;
      // }

      // ✅ TOUJOURS utiliser les couleurs connecté/déconnecté
      const isConnected = iconElement.classList.contains(CONFIG.CLASS_CONNECTED);
      const colorStatus = Storage.getColorForStatus(isConnected);      return colorStatus;
    }
  };

  // ============================================================
  // 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 || '');        }

      } catch (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();    },

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

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

      // ✅ ÉTAPE 1 : Appliquer les avatars normaux à tous les pions
      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);
      }

      // ✅ ÉTAPE 2 : Détecter et appliquer les pie charts aux pions multiples
      PieChartManager.applyPieCharts();
    },

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

      // ✅ ÉTAPE 1 : Réappliquer les avatars normaux
      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);
      });

      // ✅ ÉTAPE 2 : Appliquer les pie charts aux pions multiples
      PieChartManager.applyPieCharts();
    },

    // ✅ CORRECTION BUG 1 : Fonction de rafraîchissement complet
    refreshAll() {
      // Forcer la réapplication de tous les avatars
      const pions = DOMUtils.getAllPions();
      let refreshedCount = 0;

      pions.forEach(pion => {
        const iconElement = pion.querySelector(CONFIG.SELECTOR_ICON);
        const avatarImg = iconElement?.querySelector(`.${CONFIG.CLASS_AVATAR_IMG}`);

        if (avatarImg) {
          // Récupérer la nouvelle couleur
          const borderColor = AvatarStyler.getBorderColor(pion);

          // Appliquer la couleur avec l'opacité actuelle
          avatarImg.style.setProperty('border', `3px solid ${borderColor}`, 'important');
          avatarImg.style.setProperty('box-shadow', `0 2px 8px ${borderColor}`, 'important');

          refreshedCount++;
        }
      });

      Utils.debugLog(`✅ ${refreshedCount} avatar(s) rafraîchi(s)`);
    }
  };

  // ============================================================
  // 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();
      state.reapplyIntervalId = setInterval(() => {
        if (Storage.loadAvatarEnabled()) {
          AvatarManager.reapplyAvatarsSync(false);
        }
      }, CONFIG.REAPPLY_INTERVAL);

      this.ultraFastReapplication();
    },

    stop() {
      state.stopReapplication();    }
  };

  // ============================================================
  // 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);    }
  };

  // ============================================================
  // Composants UI
  // ============================================================
  const UIComponents = {
    // ✅ CORRECTION BUG 3 : Amélioration du drag & drop
    createDraggableBehavior(element, handle) {      let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
      let isDragging = false;

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

        isDragging = true;

        // Changer le curseur
        handle.style.cursor = 'grabbing';
        document.body.style.userSelect = 'none';

        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) {
        if (!isDragging) return;

        e.preventDefault();
        pos1 = pos3 - e.clientX;
        pos2 = pos4 - e.clientY;
        pos3 = e.clientX;
        pos4 = e.clientY;

        // Calculer la nouvelle position
        const newTop = element.offsetTop - pos2;
        const newLeft = element.offsetLeft - pos1;

        // Limites de l'écran
        const maxX = window.innerWidth - element.offsetWidth;
        const maxY = window.innerHeight - element.offsetHeight;

        element.style.top = `${Math.max(0, Math.min(newTop, maxY))}px`;
        element.style.left = `${Math.max(0, Math.min(newLeft, maxX))}px`;
      }

      function closeDrag(e) {
        if (e) {
          e.stopPropagation();
        }
        isDragging = false;
        handle.style.cursor = 'move';
        document.body.style.userSelect = '';
        document.onmouseup = null;
        document.onmousemove = null;      }
    },

    // ✅ CORRECTION BUG 1 : Création du panel HTML
        createConfigPanel() {
      const overlay = document.createElement('div');
      overlay.id = 'dreadcast-avatar-config-panel';
      overlay.style.cssText = `
        display: none !important;
        position: fixed !important;
        top: 0 !important;
        left: 0 !important;
        width: 100% !important;
        height: 100% !important;
        background: rgba(0, 0, 0, 0.6) !important;
        z-index: ${CONFIG.Z_INDEX_OVERLAY} !important;
      `;

      const panel = document.createElement('div');
      panel.id = 'pmp-settings-menu';
      panel.style.cssText = `
        position: fixed !important;
        top: 50% !important;
        left: 50% !important;
        transform: translate(-50%, -50%) !important;
        z-index: ${CONFIG.Z_INDEX_PANEL} !important;
        width: 480px !important;
        max-height: 90vh !important;
        background: #1a1a1a !important;
        border: 1px solid #3a3a3a !important;
        border-radius: 12px !important;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5) !important;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
        color: #ffffff !important;
        display: flex !important;
        flex-direction: column !important;
        animation: fadeIn 0.2s ease !important;
      `;

      panel.innerHTML = `
        <style>
          @keyframes fadeIn {
            from { opacity: 0; transform: translate(-50%, -48%); }
            to { opacity: 1; transform: translate(-50%, -50%); }
          }

          #pmp-settings-menu .pmp-content::-webkit-scrollbar {
            width: 8px !important;
          }
          #pmp-settings-menu .pmp-content::-webkit-scrollbar-track {
            background: transparent !important;
          }
          #pmp-settings-menu .pmp-content::-webkit-scrollbar-thumb {
            background: #333333 !important;
            border-radius: 4px !important;
          }
          #pmp-settings-menu .pmp-content::-webkit-scrollbar-thumb:hover {
            background: #4a9eff !important;
          }

          #pmp-settings-menu .pmp-slider::-webkit-slider-thumb {
            -webkit-appearance: none !important;
            width: 18px !important;
            height: 18px !important;
            background: #4a9eff !important;
            border-radius: 50% !important;
            cursor: pointer !important;
            transition: all 0.2s ease !important;
          }
          #pmp-settings-menu .pmp-slider::-webkit-slider-thumb:hover {
            transform: scale(1.2) !important;
            box-shadow: 0 0 0 4px rgba(74, 158, 255, 0.2) !important;
          }
          #pmp-settings-menu .pmp-slider::-moz-range-thumb {
            width: 18px !important;
            height: 18px !important;
            background: #4a9eff !important;
            border: none !important;
            border-radius: 50% !important;
            cursor: pointer !important;
          }

          @media (max-width: 600px) {
            #pmp-settings-menu {
              width: 90vw !important;
              max-width: 400px !important;
            }
            #pmp-settings-menu .pmp-option {
              flex-direction: column !important;
              align-items: flex-start !important;
              gap: 12px !important;
            }
            #pmp-settings-menu .pmp-slider-container {
              width: 100% !important;
            }
          }
        </style>

        <!-- Header -->
        <div style="display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 20px 24px !important; border-bottom: 1px solid #3a3a3a !important; cursor: move !important; user-select: none !important;" class="pmp-header-drag">
          <div>
            <span style="margin: 0 !important; font-size: 20px !important; font-weight: 600 !important;">⚙️ PimpMyPion</span>
            <span style="margin-left: 8px !important; padding: 4px 8px !important; background: #2a2a2a !important; border-radius: 6px !important; font-size: 12px !important; font-weight: 500 !important; color: #a0a0a0 !important;">v0.5.2</span>
          </div>
          <button id="avatar-close-btn" style="width: 32px !important; height: 32px !important; padding: 0 !important; background: transparent !important; border: none !important; border-radius: 6px !important; font-size: 20px !important; color: #a0a0a0 !important; cursor: pointer !important; transition: all 0.2s ease !important;">✕</button>
        </div>

        <!-- Content -->
        <div class="pmp-content" style="flex: 1 !important; overflow-y: auto !important; padding: 8px !important;">

          <!-- Section Affichage -->
          <div style="margin-bottom: 8px !important; background: #2a2a2a !important; border-radius: 10px !important; overflow: hidden !important;">
            <h3 style="margin: 0 !important; padding: 16px 20px !important; font-size: 14px !important; font-weight: 600 !important; text-transform: uppercase !important; letter-spacing: 0.5px !important; color: #a0a0a0 !important; background: #1a1a1a !important;">Affichage</h3>
            <div style="padding: 8px !important;">

              <div style="display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 16px !important; margin-bottom: 4px !important; background: #1a1a1a !important; border-radius: 8px !important; transition: background 0.2s ease !important;">
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; flex: 1 !important;">
                  <span style="font-size: 24px !important;">🖼️</span>
                  <div style="display: flex !important; flex-direction: column !important; gap: 4px !important;">
                    <span style="font-size: 15px !important; font-weight: 500 !important;">Avatars</span>
                    <span style="font-size: 13px !important; color: #a0a0a0 !important;">Afficher les avatars des joueurs</span>
                  </div>
                </div>
                <label style="position: relative !important; display: inline-block !important; width: 48px !important; height: 28px !important; cursor: pointer !important;">
                  <input type="checkbox" id="avatar-enabled-checkbox" checked style="opacity: 0 !important; width: 0 !important; height: 0 !important;">
                  <span class="pmp-toggle-slider" style="position: absolute !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; background: #333333 !important; border-radius: 14px !important; transition: all 0.3s ease !important;"></span>
                </label>
              </div>

              <div style="display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 16px !important; background: #1a1a1a !important; border-radius: 8px !important;">
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; flex: 1 !important;">
                  <span style="font-size: 24px !important;">😀</span>
                  <div style="display: flex !important; flex-direction: column !important; gap: 4px !important;">
                    <span style="font-size: 15px !important; font-weight: 500 !important;">Emojis</span>
                    <span style="font-size: 13px !important; color: #a0a0a0 !important;">Afficher les icônes d'action</span>
                  </div>
                </div>
                <label style="position: relative !important; display: inline-block !important; width: 48px !important; height: 28px !important; cursor: pointer !important;">
                  <input type="checkbox" id="emoji-enabled-checkbox" checked style="opacity: 0 !important; width: 0 !important; height: 0 !important;">
                  <span class="pmp-toggle-slider" style="position: absolute !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; background: #333333 !important; border-radius: 14px !important; transition: all 0.3s ease !important;"></span>
                </label>
              </div>

            </div>
          </div>

          <!-- Section Couleurs -->
          <div style="margin-bottom: 8px !important; background: #2a2a2a !important; border-radius: 10px !important; overflow: hidden !important;">
            <h3 style="margin: 0 !important; padding: 16px 20px !important; font-size: 14px !important; font-weight: 600 !important; text-transform: uppercase !important; letter-spacing: 0.5px !important; color: #a0a0a0 !important; background: #1a1a1a !important;">Couleurs</h3>
            <div style="padding: 8px !important;">

              <div style="display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 16px !important; margin-bottom: 4px !important; background: #1a1a1a !important; border-radius: 8px !important;">
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; flex: 1 !important;">
                  <span style="font-size: 24px !important;">🟢</span>
                  <div style="display: flex !important; flex-direction: column !important; gap: 4px !important;">
                    <span style="font-size: 15px !important; font-weight: 500 !important;">Connecté</span>
                    <span style="font-size: 13px !important; color: #a0a0a0 !important;">Couleur des joueurs connectés</span>
                  </div>
                </div>
                <input type="color" id="color-connected" value="#4ade80" style="width: 48px !important; height: 48px !important; padding: 0 !important; border: 2px solid #3a3a3a !important; border-radius: 8px !important; cursor: pointer !important; transition: all 0.2s ease !important;">
              </div>

              <div style="display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 16px !important; margin-bottom: 4px !important; background: #1a1a1a !important; border-radius: 8px !important;">
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; flex: 1 !important;">
                  <span style="font-size: 24px !important;">⚪</span>
                  <div style="display: flex !important; flex-direction: column !important; gap: 4px !important;">
                    <span style="font-size: 15px !important; font-weight: 500 !important;">Déconnecté</span>
                    <span style="font-size: 13px !important; color: #a0a0a0 !important;">Couleur des joueurs déconnectés</span>
                  </div>
                </div>
                <input type="color" id="color-disconnected" value="#ffffff" style="width: 48px !important; height: 48px !important; padding: 0 !important; border: 2px solid #3a3a3a !important; border-radius: 8px !important; cursor: pointer !important; transition: all 0.2s ease !important;">
              </div>

              <div style="display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 16px !important; background: #1a1a1a !important; border-radius: 8px !important;">
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; flex: 1 !important;">
                  <span style="font-size: 24px !important;">👁️</span>
                  <div style="display: flex !important; flex-direction: column !important; gap: 4px !important;">
                    <span style="font-size: 15px !important; font-weight: 500 !important;">Opacité</span>
                    <span style="font-size: 13px !important; color: #a0a0a0 !important;">Transparence des couleurs</span>
                  </div>
                </div>
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; min-width: 180px !important;">
                  <input type="range" id="color-opacity-slider" min="0" max="100" value="100" class="pmp-slider" style="flex: 1 !important; height: 6px !important; background: #333333 !important; border-radius: 3px !important; outline: none !important; -webkit-appearance: none !important; cursor: pointer !important;">
                  <span id="color-opacity-value" style="min-width: 50px !important; text-align: right !important; font-size: 14px !important; font-weight: 500 !important;">100%</span>
                </div>
              </div>

            </div>
          </div>

          <!-- Section Tailles -->
          <div style="margin-bottom: 8px !important; background: #2a2a2a !important; border-radius: 10px !important; overflow: hidden !important;">
            <h3 style="margin: 0 !important; padding: 16px 20px !important; font-size: 14px !important; font-weight: 600 !important; text-transform: uppercase !important; letter-spacing: 0.5px !important; color: #a0a0a0 !important; background: #1a1a1a !important;">Tailles</h3>
            <div style="padding: 8px !important;">

              <div style="display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 16px !important; margin-bottom: 4px !important; background: #1a1a1a !important; border-radius: 8px !important;">
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; flex: 1 !important;">
                  <span style="font-size: 24px !important;">📏</span>
                  <div style="display: flex !important; flex-direction: column !important; gap: 4px !important;">
                    <span style="font-size: 15px !important; font-weight: 500 !important;">Pions</span>
                    <span style="font-size: 13px !important; color: #a0a0a0 !important;">Taille des avatars</span>
                  </div>
                </div>
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; min-width: 180px !important;">
                  <input type="range" id="avatar-size-slider" min="75" max="125" value="100" class="pmp-slider" style="flex: 1 !important; height: 6px !important; background: #333333 !important; border-radius: 3px !important; outline: none !important; -webkit-appearance: none !important; cursor: pointer !important;">
                  <span id="avatar-size-value" style="min-width: 50px !important; text-align: right !important; font-size: 14px !important; font-weight: 500 !important;">100%</span>
                </div>
              </div>

              <div style="display: flex !important; align-items: center !important; justify-content: space-between !important; padding: 16px !important; background: #1a1a1a !important; border-radius: 8px !important;">
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; flex: 1 !important;">
                  <span style="font-size: 24px !important;">🎯</span>
                  <div style="display: flex !important; flex-direction: column !important; gap: 4px !important;">
                    <span style="font-size: 15px !important; font-weight: 500 !important;">Icônes</span>
                    <span style="font-size: 13px !important; color: #a0a0a0 !important;">Taille des emojis d'action</span>
                  </div>
                </div>
                <div style="display: flex !important; align-items: center !important; gap: 12px !important; min-width: 180px !important;">
                  <input type="range" id="emoji-size-slider" min="12" max="28" value="18" class="pmp-slider" style="flex: 1 !important; height: 6px !important; background: #333333 !important; border-radius: 3px !important; outline: none !important; -webkit-appearance: none !important; cursor: pointer !important;">
                  <span id="emoji-size-value" style="min-width: 50px !important; text-align: right !important; font-size: 14px !important; font-weight: 500 !important;">18px</span>
                </div>
              </div>

            </div>
          </div>

        </div>

        <!-- Footer -->
        <div style="padding: 16px 24px !important; border-top: 1px solid #3a3a3a !important; display: flex !important; justify-content: center !important;">
          <button id="avatar-reset-btn" style="padding: 10px 20px !important; border: none !important; border-radius: 8px !important; font-size: 14px !important; font-weight: 500 !important; cursor: pointer !important; transition: all 0.2s ease !important; background: #2a2a2a !important; color: #ffffff !important;">
            🔄 Réinitialiser
          </button>
        </div>
      `;

      // Ajouter le CSS pour les toggles
      const style = document.createElement('style');
      style.textContent = `
        .pmp-toggle-slider:before {
          content: '' !important;
          position: absolute !important;
          height: 20px !important;
          width: 20px !important;
          left: 4px !important;
          bottom: 4px !important;
          background: white !important;
          border-radius: 50% !important;
          transition: all 0.3s ease !important;
        }
        input:checked + .pmp-toggle-slider {
          background: #4a9eff !important;
        }
        input:checked + .pmp-toggle-slider:before {
          transform: translateX(20px) !important;
        }
        #avatar-close-btn:hover {
          background: #333333 !important;
          color: #ffffff !important;
        }
        #avatar-reset-btn:hover {
          background: #333333 !important;
          transform: translateY(-1px) !important;
        }
      `;
      document.head.appendChild(style);

      overlay.appendChild(panel);

      // Fermer en cliquant sur l'overlay
      overlay.addEventListener('click', (e) => {
        if (e.target === overlay) {
          overlay.style.display = 'none';
        }
      });

      document.body.appendChild(overlay);

      // Attacher les événements
      setTimeout(() => {
        this.attachPanelEvents();
      }, CONFIG.EVENT_ATTACH_DELAY);

      return overlay;
    },

    // ✅ CORRECTION BUG 2 : Fonction pour attacher les event listeners
    attachPanelEvents() {
      const panel = document.getElementById('dreadcast-avatar-config-panel');
      if (!panel) {        return;
      }
      // Récupérer tous les éléments
      const menuPanel = document.getElementById('pmp-settings-menu');
      const headDiv = panel.querySelector('.pmp-header-drag');
      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 colorOpacitySlider = document.getElementById('color-opacity-slider');
      const colorOpacityValue = document.getElementById('color-opacity-value');
      const colorConnected = document.getElementById('color-connected');
      const colorDisconnected = document.getElementById('color-disconnected');
      // ===== DRAG & DROP =====
      if (menuPanel && headDiv) {
        this.createDraggableBehavior(menuPanel, headDiv);
      }

      // ===== BOUTON FERMER =====
      if (closeBtn) {
        closeBtn.addEventListener('click', () => {
          panel.style.display = 'none';
          Utils.debugLog('🔧 [DEBUG] Panel fermé (bouton X)');
        });      } else {      }

      // ===== CHECKBOX AVATARS =====
      if (avatarCheckbox) {
        avatarCheckbox.checked = Storage.loadAvatarEnabled();
        avatarCheckbox.addEventListener('change', () => {
          const enabled = avatarCheckbox.checked;
          Storage.saveAvatarEnabled(enabled);

          if (enabled) {
            AvatarManager.applyAvatarsToAllPions(true);
            ReapplicationSystem.start();
          } else {
            AvatarManager.removeCustomAvatars();
            ReapplicationSystem.stop();
          }        });      } else {      }

      // ===== CHECKBOX EMOJIS =====
      if (emojiCheckbox) {
        emojiCheckbox.checked = Storage.loadEmojiEnabled();
        emojiCheckbox.addEventListener('change', () => {
          const enabled = emojiCheckbox.checked;
          Storage.saveEmojiEnabled(enabled);

          AvatarManager.applyAvatarsToAllPions(true);        });      } else {      }

      // ===== SLIDER TAILLE PIONS =====
      if (slider && valueDisplay) {
        slider.value = Storage.loadAvatarSize();
        valueDisplay.textContent = `${slider.value}%`;

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

          if (!CombatDetector.isInCombat) {
            Storage.saveAvatarSize(size);
            SizingSystem.applyAvatarSize(size);
          }        });      } else {      }

      // ===== SLIDER TAILLE EMOJIS =====
      if (emojiSizeSlider && emojiSizeValue) {
        emojiSizeSlider.value = Storage.loadEmojiSize();
        emojiSizeValue.textContent = `${emojiSizeSlider.value}px`;

        emojiSizeSlider.addEventListener('input', () => {
          const size = parseInt(emojiSizeSlider.value, 10);
          emojiSizeValue.textContent = `${size}px`;
          Storage.saveEmojiSize(size);
          SizingSystem.applyEmojiSize(size);        });      } else {      }

      // ===== SLIDER OPACITÉ =====
      if (colorOpacitySlider && colorOpacityValue) {
        colorOpacitySlider.value = Storage.loadColorOpacity();
        colorOpacityValue.textContent = `${colorOpacitySlider.value}%`;

        colorOpacitySlider.addEventListener('input', () => {
          const opacity = parseInt(colorOpacitySlider.value, 10);
          colorOpacityValue.textContent = `${opacity}%`;
          Storage.saveColorOpacity(opacity);

          // Rafraîchir tous les avatars
          AvatarManager.refreshAll();        });      } else {      }

      // ===== COLOR PICKERS =====
      if (colorConnected) {
        const customColors = Storage.loadCustomColors();
        colorConnected.value = customColors['connected'] || CONFIG.COLOR_CONNECTED;

        colorConnected.addEventListener('change', () => {
          const customColors = Storage.loadCustomColors();
          customColors['connected'] = colorConnected.value;
          Storage.saveCustomColors(customColors);

          AvatarManager.refreshAll();        });      } else {      }

      if (colorDisconnected) {
        const customColors = Storage.loadCustomColors();
        colorDisconnected.value = customColors['disconnected'] || CONFIG.COLOR_DISCONNECTED;

        colorDisconnected.addEventListener('change', () => {
          const customColors = Storage.loadCustomColors();
          customColors['disconnected'] = colorDisconnected.value;
          Storage.saveCustomColors(customColors);

          AvatarManager.refreshAll();        });      } else {      }

      // ===== BOUTON RESET =====
      if (resetBtn) {
        resetBtn.addEventListener('click', () => {
          // Réinitialiser les valeurs
          const defaultSize = CONFIG.DEFAULT_SIZE;
          const defaultEmojiSize = CONFIG.DEFAULT_EMOJI_SIZE;
          const defaultOpacity = CONFIG.DEFAULT_COLOR_OPACITY;

          Storage.saveAvatarSize(defaultSize);
          Storage.saveEmojiSize(defaultEmojiSize);
          Storage.saveColorOpacity(defaultOpacity);
          Storage.saveCustomColors({});

          // Mettre à jour l'interface
          if (slider && valueDisplay) {
            slider.value = defaultSize;
            valueDisplay.textContent = `${defaultSize}%`;
          }

          if (emojiSizeSlider && emojiSizeValue) {
            emojiSizeSlider.value = defaultEmojiSize;
            emojiSizeValue.textContent = `${defaultEmojiSize}px`;
          }

          if (colorOpacitySlider && colorOpacityValue) {
            colorOpacitySlider.value = defaultOpacity;
            colorOpacityValue.textContent = `${defaultOpacity}%`;
          }

          if (colorConnected) {
            colorConnected.value = CONFIG.COLOR_CONNECTED;
          }

          if (colorDisconnected) {
            colorDisconnected.value = CONFIG.COLOR_DISCONNECTED;
          }

          // Appliquer les changements
          SizingSystem.applyAvatarSize(defaultSize);
          SizingSystem.applyEmojiSize(defaultEmojiSize);
          AvatarManager.refreshAll();        });      } else {      }    },

    openConfigPanel() {
      let panel = document.getElementById('dreadcast-avatar-config-panel');
      if (!panel) {        panel = this.createConfigPanel();      }

      if (panel) {
        panel.style.display = 'block';      } else {      }
    }
  };

  // ============================================================
  // 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.2';
          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);          }        }
      }, 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) => {
        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')) {              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)) {              AvatarManager.applyCustomAvatar(pionElement, true);
            }
          }
        });
      });

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

  // ============================================================
  // Initialisation
  // ============================================================
  function init() {
    const savedSize = Storage.loadAvatarSize();
    const savedEmojiSize = Storage.loadEmojiSize();

    SizingSystem.applyAvatarSize(savedSize);
    SizingSystem.applyEmojiSize(savedEmojiSize);
    MenuIntegration.addMenuOption();
    DOMObserver.observe();

    CombatDetector.start();
    setTimeout(() => {      AvatarManager.applyAvatarsToAllPions(true);

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

    setTimeout(() => {      AvatarManager.applyAvatarsToAllPions(true);
    }, CONFIG.SECONDARY_DELAY);
  }

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

})();