Mudanças entre as edições de "Widget:C.Skills"

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
 
(44 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
Linha 1: Linha 1:
<!-- MAIN SKILLS SYSTEM -->
<!-- MAIN SKILLS SYSTEM -->
<script>
<script>
    (function () {
  (function () {
        const $ = (s, root = document) => root.querySelector(s);
    const $ = (s, root = document) => root.querySelector(s);
        const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
    const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
        const ensureRemoved = sel => {
    const ensureRemoved = (sel) => {
            Array.from(document.querySelectorAll(sel)).forEach(n => n.remove());
      Array.from(document.querySelectorAll(sel)).forEach((n) => n.remove());
        };
    };
        const onceFlag = (el, key) => {
    const onceFlag = (el, key) => {
            if (!el) return false;
      if (!el) return false;
            if (el.dataset[key]) return false;
      if (el.dataset[key]) return false;
            el.dataset[key] = '1';
      el.dataset[key] = "1";
            return true;
      return true;
        };
    };
        const addOnce = (el, ev, fn, options = {}) => {
    const addOnce = (el, ev, fn, options = {}) => {
            if (!el) return;
      if (!el) return;
            const attr = `data-wired-${ev}`;
      const attr = `data-wired-${ev}`;
            if (el.hasAttribute(attr)) return;
      if (el.hasAttribute(attr)) return;
            el.addEventListener(ev, fn, options);
      el.addEventListener(ev, fn, options);
            el.setAttribute(attr, '1');
      el.setAttribute(attr, "1");
        };
    };
        const FLAG_ICON_FILES = {
    const FLAG_ICON_FILES = {
            aggro: 'Enemyaggro-icon.png', bridge: 'Bridgemaker-icon.png', wall: 'Destroywall-icon.png', quickcast: 'Quickcast-icon.png', wallpass: 'Passthroughwall-icon.png'
      aggro: "Enemyaggro-icon.png",
        };
      bridge: "Bridgemaker-icon.png",
        const subBarTemplateCache = window.__skillSubBarTemplateCache || (window.__skillSubBarTemplateCache = new Map());
      wall: "Destroywall-icon.png",
        const imagePreloadCache = window.__skillImagePreloadCache || (window.__skillImagePreloadCache = new Map());
      quickcast: "Quickcast-icon.png",
        const videoPreloadCache = window.__skillVideoPreloadCache || (window.__skillVideoPreloadCache = new Set());
      wallpass: "Passthroughwall-icon.png",
        const flagRowCache = window.__skillFlagRowCache || (window.__skillFlagRowCache = new Map());
    };
        const flagIconURLCache = window.__skillFlagIconURLCache || (window.__skillFlagIconURLCache = new Map());
    const subBarTemplateCache =
      window.__skillSubBarTemplateCache ||
      (window.__skillSubBarTemplateCache = new Map());
    const imagePreloadCache =
      window.__skillImagePreloadCache ||
      (window.__skillImagePreloadCache = new Map());
    const videoPreloadCache =
      window.__skillVideoPreloadCache ||
      (window.__skillVideoPreloadCache = new Set());
    const flagRowCache =
      window.__skillFlagRowCache || (window.__skillFlagRowCache = new Map());
    const flagIconURLCache =
      window.__skillFlagIconURLCache ||
      (window.__skillFlagIconURLCache = new Map());


        // Sistema de múltiplas formas (genérico)
    // Sistema de múltiplas formas (genérico)
        let currentForm = null; // null = primeira forma, depois nome da forma atual
    let currentForm = null; // null = primeira forma, depois nome da forma atual
        let formsData = {};
    let formsData = {};
        let fixedSkills = []; // Skills que sempre aparecem (Change Form, Guard Point, etc.)
    let fixedSkills = []; // Skills que sempre aparecem (Change Form, Guard Point, etc.)


        function showFormTransitionVideo(changeFormIconEl) {
    // Sistema genérico de swap de personagens (ex: Buchi & Sham, futuros personagens)
            const skillsRoot = document.getElementById('skills');
    let activeCharacter = null; // null = personagem padrão, depois nome do personagem ativo (ex: "Buchi", "Sham")
            if (!skillsRoot) return;


            // Busca videoBox
    function showFormTransitionVideo(changeFormIconEl) {
            let videoBox = skillsRoot.querySelector('.video-container');
      const skillsRoot = document.getElementById("skills");
            if (!videoBox) {
      if (!skillsRoot) return;
                const skillsContainer = skillsRoot.querySelector('.skills-container');
                if (skillsContainer) {
                    videoBox = skillsContainer.querySelector('.video-container');
                }
            }
            if (!videoBox) return;


            try {
      // Busca videoBox
                // Lê dados de forms para determinar forma atual e próxima
      let videoBox = skillsRoot.querySelector(".video-container");
                const formsJSON = skillsRoot.dataset.forms || '{}';
      if (!videoBox) {
                if (formsJSON && formsJSON !== '{}') {
        const skillsContainer = skillsRoot.querySelector(".skills-container");
                    const tempFormsData = JSON.parse(formsJSON);
        if (skillsContainer) {
                    const formNames = Object.keys(tempFormsData);
          videoBox = skillsContainer.querySelector(".video-container");
        }
      }
      if (!videoBox) return;


                    // Determina forma atual e próxima
      try {
                    const currentIdx = currentForm ? formNames.indexOf(currentForm) : -1;
        // Lê dados de forms para determinar forma atual e próxima
                    const nextIdx = (currentIdx + 1) % formNames.length;
        const formsJSON = skillsRoot.dataset.forms || "{}";
                    const nextForm = formNames[nextIdx];
        if (formsJSON && formsJSON !== "{}") {
          const tempFormsData = JSON.parse(formsJSON);
          const formNames = Object.keys(tempFormsData);


                    // Busca vídeo de transição na skill Change Form
          // Determina forma atual e próxima
                    // form_videos[forma_atual] = "video.mp4" (vídeo da transição atual próxima)
          const currentIdx = currentForm ? formNames.indexOf(currentForm) : -1;
                    const formVideosRaw = changeFormIconEl.dataset.formVideos || changeFormIconEl.getAttribute('data-form-videos');
          const nextIdx = (currentIdx + 1) % formNames.length;
                    if (formVideosRaw) {
          const nextForm = formNames[nextIdx];
                        try {
                            const videos = JSON.parse(formVideosRaw);
                            const transitionVideo = videos[currentForm] || '';


                            if (transitionVideo && transitionVideo.trim() !== '') {
          // Busca vídeo de transição na skill Change Form
                                const videoURL = filePathURL(transitionVideo);
          // form_videos[forma_atual] = "video.mp4" (vídeo da transição atual → próxima)
                                if (videoURL) {
          const formVideosRaw =
                                    // Busca ou cria elemento de vídeo para esta transição
            changeFormIconEl.dataset.formVideos ||
                                    const videoKey = `form_transition:${currentForm}:${nextForm}`;
            changeFormIconEl.getAttribute("data-form-videos");
                                    let v = videosCache.get(videoKey);
          if (formVideosRaw) {
            try {
              const videos = JSON.parse(formVideosRaw);
              const transitionVideo = videos[currentForm] || "";


                                    if (!v) {
              if (transitionVideo && transitionVideo.trim() !== "") {
                                        // Cria novo elemento de vídeo
                const videoURL = filePathURL(transitionVideo);
                                        v = document.createElement('video');
                if (videoURL) {
                                        v.className = 'skill-video';
                  // Busca ou cria elemento de vídeo para esta transição
                                        v.src = videoURL;
                  const videoKey = `form_transition:${currentForm}:${nextForm}`;
                                        v.preload = 'auto';
                  let v = videosCache.get(videoKey);
                                        v.controls = false;
                                        v.muted = false;
                                        v.loop = false;
                                        v.playsInline = true;
                                        videoBox.appendChild(v);
                                        videosCache.set(videoKey, v);
                                    }


                                    // Mostra e reproduz o vídeo
                  if (!v) {
                                    Array.from(videoBox.querySelectorAll('video.skill-video')).forEach(vid => {
                    // Cria novo elemento de vídeo
                                        try {
                    v = document.createElement("video");
                                            vid.pause();
                    v.className = "skill-video";
                                        } catch (e) {
                    v.src = videoURL;
                                        }
                    v.preload = "auto";
                                        vid.style.display = 'none';
                    v.controls = false;
                                    });
                    v.muted = false;
                    v.loop = false;
                    v.playsInline = true;
                    videoBox.appendChild(v);
                    videosCache.set(videoKey, v);
                  }


                                    videoBox.style.display = 'block';
                  // Mostra e reproduz o vídeo
                                    v.style.display = 'block';
                  Array.from(
                                    try {
                    videoBox.querySelectorAll("video.skill-video")
                                        v.currentTime = 0;
                  ).forEach((vid) => {
                                        v.play().catch(() => { });
                    try {
                                    } catch (e) {
                      vid.pause();
                                    }
                    } catch (e) { }
                                }
                    vid.style.display = "none";
                            }
                  });
                        } catch (e) {
 
                            console.error('[Forms] Erro ao parsear form_videos:', e);
                  videoBox.style.display = "block";
                        }
                  v.style.display = "block";
                    }
                  try {
                    v.currentTime = 0;
                    v.play().catch(() => { });
                  } catch (e) { }
                 }
                 }
              }
             } catch (e) {
             } catch (e) {
                console.error('[Forms] Erro ao processar vídeo de transição:', e);
              console.error("[Forms] Erro ao parsear form_videos:", e);
             }
             }
          }
         }
         }
      } catch (e) {
        console.error("[Forms] Erro ao processar vídeo de transição:", e);
      }
    }


        // Detecta qual forma está atualmente visível no DOM
    // Sistema genérico de swap de personagens (ex: Buchi & Sham, futuros personagens)
        function detectCurrentForm() {
    // Detecta quais personagens estão disponíveis baseado nas skills
            const iconBar = document.querySelector('.icon-bar');
    function detectAvailableCharacters() {
            if (!iconBar || !formsData || Object.keys(formsData).length === 0) return null;
      const iconBar = document.querySelector(".icon-bar");
      if (!iconBar) return [];
 
      const characters = new Set();
      Array.from(
        iconBar.querySelectorAll(".skill-icon[data-only-character]")
      ).forEach((icon) => {
        const character = (icon.dataset.onlyCharacter || "").trim();
        if (character) {
          characters.add(character);
        }
      });


            // Coleta todas as skills de form que estão visíveis no DOM
      return Array.from(characters);
            const allFormSkillNames = new Set();
    }
            Object.values(formsData).forEach(form => {
                if (form.order && Array.isArray(form.order)) {
                    form.order.forEach(skillName => allFormSkillNames.add(skillName));
                }
            });


            const visibleFormSkillNames = new Set();
    // Inicializa o personagem padrão (deve ser chamado quando a página carrega)
            Array.from(iconBar.querySelectorAll('.skill-icon[data-index]')).forEach(icon => {
    function initializeActiveCharacter() {
                const name = (icon.dataset.nome || '').trim();
      const availableCharacters = detectAvailableCharacters();
                if (name && allFormSkillNames.has(name)) {
      if (availableCharacters.length > 0 && activeCharacter === null) {
                    visibleFormSkillNames.add(name);
        // Inicializa com o primeiro personagem disponível (padrão)
                }
        activeCharacter = availableCharacters[0];
            });
        // Aplica o estado inicial (habilita/desabilita skills)
        applyCharacterSwapState();
      }
    }


            // Compara com cada forma para ver qual corresponde
    // Aplica o estado de swap nas skills (habilita/desabilita e atualiza vídeos)
            for (const [formName, formData] of Object.entries(formsData)) {
    function applyCharacterSwapState() {
                if (formData.order && Array.isArray(formData.order)) {
      const iconBar = document.querySelector(".icon-bar");
                    const formSkillSet = new Set(formData.order);
      if (!iconBar) return;
                    // Verifica se todas as skills desta forma estão visíveis
                    let allMatch = true;
                    for (const skillName of formData.order) {
                        if (!visibleFormSkillNames.has(skillName)) {
                            allMatch = false;
                            break;
                        }
                    }
                    if (allMatch && formData.order.length === visibleFormSkillNames.size) {
                        return formName;
                    }
                }
            }


             return null;
      // Atualiza todas as skills na barra
        }
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
        (icon) => {
          const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
          const hasCharacterVideos =
             icon.dataset.characterVideos &&
            icon.dataset.characterVideos.trim() !== "";
          const baseVideoFile = icon.dataset.videoFile || "";
          const baseVideoURL = icon.dataset.video || "";


        function switchForm() {
          // Salva o vídeo original se ainda não foi salvo (apenas para skills compartilhadas com character_videos)
             const skillsRoot = document.getElementById('skills');
          if (
             if (!skillsRoot) return;
            hasCharacterVideos &&
            !onlyCharacter &&
             !icon.dataset.originalVideoFile
          ) {
             icon.dataset.originalVideoFile =
              baseVideoFile || baseVideoURL || "";
          }


             // Lê dados de forms do atributo data-forms
          // Desabilita/habilita skills baseado no personagem ativo
             try {
          if (onlyCharacter) {
                const formsJSON = skillsRoot.dataset.forms || '{}';
             // Skill específica de um personagem
                if (formsJSON && formsJSON !== '{}') {
             if (onlyCharacter === activeCharacter) {
                    formsData = JSON.parse(formsJSON);
              // Skill do personagem ativo → habilitar
                }
              icon.style.opacity = "1";
            } catch (e) {
              icon.style.filter = "";
                console.error('[Forms] Erro ao parsear forms:', e);
              icon.style.pointerEvents = "";
                return;
              icon.classList.remove("disabled-skill");
            } else {
              // Skill de outro personagem → desabilitar (escuras)
              icon.style.opacity = "0.3";
              icon.style.filter = "grayscale(100%)";
              icon.style.pointerEvents = "none";
              icon.classList.add("disabled-skill");
             }
             }
          } else {
            // Skill compartilhada → sempre habilitada
            icon.style.opacity = "1";
            icon.style.filter = "";
            icon.style.pointerEvents = "";
            icon.classList.remove("disabled-skill");


             if (!formsData || Object.keys(formsData).length === 0) {
            // Atualiza vídeo se houver character_videos e personagem ativo
                 return; // Não tem forms, não faz nada
             if (hasCharacterVideos && activeCharacter) {
              try {
                const characterVideos = JSON.parse(
                  icon.dataset.characterVideos
                );
                const characterVideo = characterVideos[activeCharacter];
                if (characterVideo && characterVideo.trim() !== "") {
                  icon.dataset.videoFile = characterVideo;
                  icon.dataset.video =
                    filePathURL(characterVideo) || characterVideo;
                }
              } catch (e) {
                console.error("[Swap] Erro ao processar character_videos:", e);
              }
            } else if (hasCharacterVideos && activeCharacter === null) {
              // Restaura vídeo original quando volta ao personagem padrão (null)
              const originalVideo = icon.dataset.originalVideoFile || "";
              if (originalVideo) {
                 icon.dataset.videoFile = originalVideo;
                icon.dataset.video =
                  filePathURL(originalVideo) || originalVideo;
              }
             }
             }
          }
        }
      );
    }


            // Identifica skills fixas (sempre presentes)
    // Troca entre personagens (genérico)
            const iconBar = document.querySelector('.icon-bar');
    function handleSwapCharacter(swapIconEl) {
            if (!iconBar) return;
      if (!swapIconEl) return;


            // Busca a skill com form_switch dinamicamente (genérico)
      const iconBar = document.querySelector(".icon-bar");
            const changeFormIcon = Array.from(iconBar.querySelectorAll('.skill-icon[data-index]'))
      if (!iconBar) return;
                .find(icon => icon.dataset.formSwitch === 'true' || icon.getAttribute('data-form-switch') === 'true');
            if (changeFormIcon) {
                changeFormIcon.classList.add('active');
            }


            // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
      const availableCharacters = detectAvailableCharacters();
            const allFormSkillNames = new Set();
      if (availableCharacters.length === 0) {
            Object.values(formsData).forEach(form => {
        // Se não há personagens específicos, não faz nada (não há swap)
                if (form.order && Array.isArray(form.order)) {
        return;
                    form.order.forEach(skillName => allFormSkillNames.add(skillName));
      }
                }
            });


            // Skills fixas = todas as skills na barra que não estão em nenhuma forms
      // Se activeCharacter é null, inicializa com o primeiro personagem disponível
            fixedSkills = Array.from(iconBar.querySelectorAll('.skill-icon[data-index]'))
      if (activeCharacter === null) {
                .filter(icon => {
        activeCharacter = availableCharacters[0];
                    const name = (icon.dataset.nome || '').trim();
      } else {
                    return name && !allFormSkillNames.has(name);
        // Encontra o índice do personagem atual e avança para o próximo
                })
        const currentIndex = availableCharacters.indexOf(activeCharacter);
                .map(icon => snapshotIconData(icon));
        const nextIndex = (currentIndex + 1) % availableCharacters.length;
        activeCharacter = availableCharacters[nextIndex];
      }


            // Obtém lista de formas disponíveis
      // Aplica o novo estado
            const formNames = Object.keys(formsData);
      applyCharacterSwapState();
            if (formNames.length === 0) return;


            // Determina próxima forma
      // Atualiza todas as skills na barra
            // Se currentForm é null, detecta qual forma está atualmente visível no DOM
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
            if (currentForm === null) {
        (icon) => {
                currentForm = detectCurrentForm();
          const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
            }
          const hasCharacterVideos =
            icon.dataset.characterVideos &&
            icon.dataset.characterVideos.trim() !== "";
          const baseVideoFile = icon.dataset.videoFile || "";
          const baseVideoURL = icon.dataset.video || "";


            // Se ainda não conseguiu detectar, usa a primeira forma como fallback
          // Salva o vídeo original se ainda não foi salvo
            if (!currentForm && formNames.length > 0) {
          if (hasCharacterVideos && !icon.dataset.originalVideoFile) {
                currentForm = formNames[0];
            icon.dataset.originalVideoFile =
            }
              baseVideoFile || baseVideoURL || "";
          }


            // Cria ordem circular fixa baseada na forma atual detectada
          // Desabilita/habilita skills baseado no personagem ativo
            // Se detectamos "Brain Point", ordem é: Brain → Kung Fu → Heavy → Brain
          if (onlyCharacter) {
            // Se detectamos "Kung Fu Point", ordem é: Kung Fu → Heavy → Brain → Kung Fu
             // Skill específica de um personagem
             // Se detectamos "Heavy Point", ordem é: Heavy → Brain → Kung Fu → Heavy
             if (onlyCharacter === activeCharacter) {
            let orderedFormNames = [];
              // Skill do personagem ativo habilitar
             if (currentForm === "Brain Point" && formNames.length === 3) {
              icon.style.opacity = "1";
                // Ordem conhecida: Brain Kung Fu → Heavy
              icon.style.filter = "";
                if (formNames.includes("Kung Fu Point") && formNames.includes("Heavy Point")) {
              icon.style.pointerEvents = "";
                    orderedFormNames = ["Brain Point", "Kung Fu Point", "Heavy Point"];
              icon.classList.remove("disabled-skill");
                }
             } else {
             } else if (currentForm === "Kung Fu Point" && formNames.length === 3) {
              // Skill de outro personagem desabilitar (escuras)
                // Ordem conhecida: Kung Fu Heavy → Brain
              icon.style.opacity = "0.3";
                if (formNames.includes("Heavy Point") && formNames.includes("Brain Point")) {
              icon.style.filter = "grayscale(100%)";
                    orderedFormNames = ["Kung Fu Point", "Heavy Point", "Brain Point"];
              icon.style.pointerEvents = "none";
                }
              icon.classList.add("disabled-skill");
            } else if (currentForm === "Heavy Point" && formNames.length === 3) {
                // Ordem conhecida: Heavy → Brain → Kung Fu
                if (formNames.includes("Brain Point") && formNames.includes("Kung Fu Point")) {
                    orderedFormNames = ["Heavy Point", "Brain Point", "Kung Fu Point"];
                }
             }
             }
          } else {
            // Skill compartilhada → sempre habilitada
            icon.style.opacity = "1";
            icon.style.filter = "";
            icon.style.pointerEvents = "";
            icon.classList.remove("disabled-skill");


             // Se não conseguiu criar ordem conhecida, usa ordem alfabética como fallback
             // Atualiza vídeo se houver character_videos e personagem ativo
             if (orderedFormNames.length === 0) {
             if (hasCharacterVideos && activeCharacter) {
                 orderedFormNames = [...formNames].sort();
              try {
                 // Se sabemos a forma atual, reorganiza para começar por ela
                 const characterVideos = JSON.parse(
                 if (currentForm) {
                  icon.dataset.characterVideos
                    const currentIdx = orderedFormNames.indexOf(currentForm);
                );
                    if (currentIdx !== -1) {
                 const characterVideo = characterVideos[activeCharacter];
                        orderedFormNames = [
                 if (characterVideo && characterVideo.trim() !== "") {
                            ...orderedFormNames.slice(currentIdx),
                  icon.dataset.videoFile = characterVideo;
                            ...orderedFormNames.slice(0, currentIdx)
                  icon.dataset.video =
                        ];
                    filePathURL(characterVideo) || characterVideo;
                    }
                 }
                 }
              } catch (e) {
                console.error("[Swap] Erro ao processar character_videos:", e);
              }
            } else if (hasCharacterVideos && activeCharacter === null) {
              // Restaura vídeo original quando volta ao personagem padrão (null)
              const originalVideo = icon.dataset.originalVideoFile || "";
              if (originalVideo) {
                icon.dataset.videoFile = originalVideo;
                icon.dataset.video =
                  filePathURL(originalVideo) || originalVideo;
              }
             }
             }
          }
        }
      );
    }


            const currentIdx = orderedFormNames.indexOf(currentForm);
    // Detecta qual forma está atualmente visível no DOM
            if (currentIdx === -1) return; // Forma não encontrada
    function detectCurrentForm() {
      const iconBar = document.querySelector(".icon-bar");
      if (!iconBar || !formsData || Object.keys(formsData).length === 0)
        return null;


            const nextIdx = (currentIdx + 1) % orderedFormNames.length;
      // Coleta todas as skills de form que estão visíveis no DOM
            const nextForm = orderedFormNames[nextIdx];
      const allFormSkillNames = new Set();
 
      Object.values(formsData).forEach((form) => {
            currentForm = nextForm;
        if (form.order && Array.isArray(form.order)) {
          form.order.forEach((skillName) => allFormSkillNames.add(skillName));
        }
      });


            // Atualiza barra de skills (que vai remover o active depois da animação)
      const visibleFormSkillNames = new Set();
            updateSkillsBarForForm(nextForm, formsData[nextForm], changeFormIcon);
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
        (icon) => {
          const name = (icon.dataset.nome || "").trim();
          if (name && allFormSkillNames.has(name)) {
            visibleFormSkillNames.add(name);
          }
         }
         }
      );


        function snapshotIconData(icon) {
      // Compara com cada forma para ver qual corresponde
            const img = icon.querySelector('img');
      for (const [formName, formData] of Object.entries(formsData)) {
            const iconURL = img ? img.src : '';
        if (formData.order && Array.isArray(formData.order)) {
            const subsRaw = icon.dataset.subs || icon.getAttribute('data-subs') || '';
          const formSkillSet = new Set(formData.order);
            let subs = null;
          // Verifica se todas as skills desta forma estão visíveis
            try {
          let allMatch = true;
                subs = subsRaw ? JSON.parse(subsRaw) : null;
          for (const skillName of formData.order) {
            } catch {
            if (!visibleFormSkillNames.has(skillName)) {
                subs = null;
              allMatch = false;
              break;
             }
             }
            let flags = null;
          }
            if (icon.dataset.flags) {
          if (
                try {
             allMatch &&
                    flags = JSON.parse(icon.dataset.flags);
             formData.order.length === visibleFormSkillNames.size
                } catch (e) {
          ) {
                }
             return formName;
             }
          }
             let weapon = null;
            if (icon.dataset.weapon) {
                try {
                    weapon = JSON.parse(icon.dataset.weapon);
                } catch (e) {
                }
            }
             return {
                name: icon.dataset.nome || icon.dataset.name || '',
                index: icon.dataset.index || '',
                level: icon.dataset.level || '',
                desc: icon.dataset.desc || '',
                descPt: icon.dataset.descPt || '',
                descEn: icon.dataset.descEn || '',
                descEs: icon.dataset.descEs || '',
                descPl: icon.dataset.descPl || '',
                attrs: icon.dataset.atr || icon.dataset.attrs || '',
                video: icon.dataset.video || '',
                iconURL,
                iconFile: icon.dataset.iconFile || '',
                subs,
                flags,
                weapon,
                formSwitch: icon.dataset.formSwitch || ''
            };
         }
         }
      }


        function updateSkillsBarForForm(formName, formData, changeFormIconEl) {
      return null;
            const iconBar = document.querySelector('.icon-bar');
    }
            if (!iconBar || !formData || !formData.skills) return;


            // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
    function switchForm() {
            const allFormSkillNames = new Set();
      const skillsRoot = document.getElementById("skills");
            Object.values(formsData).forEach(form => {
      if (!skillsRoot) return;
                if (form.order && Array.isArray(form.order)) {
                    form.order.forEach(skillName => allFormSkillNames.add(skillName));
                }
            });


            // Remove skills de forma antigas (que não são fixas) com animação de saída
      // Lê dados de forms do atributo data-forms
      try {
        const formsJSON = skillsRoot.dataset.forms || "{}";
        if (formsJSON && formsJSON !== "{}") {
          formsData = JSON.parse(formsJSON);
        }
      } catch (e) {
        console.error("[Forms] Erro ao parsear forms:", e);
        return;
      }


            const existingIcons = Array.from(iconBar.querySelectorAll('.skill-icon[data-index]'));
      if (!formsData || Object.keys(formsData).length === 0) {
            const iconsToRemove = [];
        return; // Não tem forms, não faz nada
            existingIcons.forEach(icon => {
      }
                const name = (icon.dataset.nome || '').trim();
                if (name && allFormSkillNames.has(name)) {
                    iconsToRemove.push(icon);
                }
            });


            // Anima saída das skills antigas
      // Identifica skills fixas (sempre presentes)
            iconsToRemove.forEach(icon => {
      const iconBar = document.querySelector(".icon-bar");
                icon.style.transition = 'opacity .15s ease, transform .15s ease';
      if (!iconBar) return;
                icon.style.opacity = '0';
                icon.style.transform = 'translateY(-6px)';
            });


            // Encontra a skill com form_switch dinamicamente (genérico)
      // Busca a skill com form_switch dinamicamente (genérico)
            const changeFormIcon = Array.from(iconBar.querySelectorAll('.skill-icon[data-index]'))
      const changeFormIcon = Array.from(
                .find(icon => icon.dataset.formSwitch === 'true' || icon.getAttribute('data-form-switch') === 'true');
        iconBar.querySelectorAll(".skill-icon[data-index]")
      ).find(
        (icon) =>
          icon.dataset.formSwitch === "true" ||
          icon.getAttribute("data-form-switch") === "true"
      );
      if (changeFormIcon) {
        changeFormIcon.classList.add("active");
      }


            // Encontra a próxima skill fixa depois do form_switch dinamicamente
      // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
            let nextFixedSkillIcon = null;
      const allFormSkillNames = new Set();
            if (changeFormIcon) {
      Object.values(formsData).forEach((form) => {
                const allIcons = Array.from(iconBar.querySelectorAll('.skill-icon[data-index]')).sort((a, b) => {
        if (form.order && Array.isArray(form.order)) {
                    return parseInt(a.dataset.index || '0', 10) - parseInt(b.dataset.index || '0', 10);
          form.order.forEach((skillName) => allFormSkillNames.add(skillName));
                });
        }
                const changeFormIndex = allIcons.indexOf(changeFormIcon);
      });
                const allFormSkillNames = new Set();
                Object.values(formsData).forEach(form => {
                    if (form.order && Array.isArray(form.order)) {
                        form.order.forEach(skillName => allFormSkillNames.add(skillName));
                    }
                });


                // Procura a próxima skill fixa (que não está em forms)
      // Skills fixas = todas as skills na barra que não estão em nenhuma forms
                for (let i = changeFormIndex + 1; i < allIcons.length; i++) {
      fixedSkills = Array.from(
                    const icon = allIcons[i];
        iconBar.querySelectorAll(".skill-icon[data-index]")
                    const name = (icon.dataset.nome || '').trim();
      )
                    if (name && !allFormSkillNames.has(name)) {
        .filter((icon) => {
                        nextFixedSkillIcon = icon;
          const name = (icon.dataset.nome || "").trim();
                        break;
          return name && !allFormSkillNames.has(name);
                    }
        })
                }
        .map((icon) => snapshotIconData(icon));
            }


            if (!changeFormIcon || !nextFixedSkillIcon) {
      // Obtém lista de formas disponíveis
                console.warn('[Forms] Skill com form_switch ou próxima skill fixa não encontrada');
      const formNames = Object.keys(formsData);
                return;
      if (formNames.length === 0) return;
            }


            const formSkills = formData.skills || [];
      // Determina próxima forma
            const formOrder = formData.order || [];
      // Se currentForm é null, detecta qual forma está atualmente visível no DOM
      if (currentForm === null) {
        currentForm = detectCurrentForm();
      }


            // Primeira skill da forma (vai ANTES do Change Form)
      // Se ainda não conseguiu detectar, usa a primeira forma como fallback
            const firstSkillName = formOrder[0];
      if (!currentForm && formNames.length > 0) {
            const firstSkillData = formSkills.find(s => s.name === firstSkillName);
        currentForm = formNames[0];
      }


            // Terceira skill da forma (vai DEPOIS do Change Form, ANTES do Guard Point)
      // Cria ordem circular fixa baseada na forma atual detectada
            const thirdSkillName = formOrder[1]; // Segunda na ordem = terceira skill (índice 1)
      // Se detectamos "Brain Point", ordem é: Brain → Kung Fu → Heavy → Brain
            const thirdSkillData = formSkills.find(s => s.name === thirdSkillName);
      // Se detectamos "Kung Fu Point", ordem é: Kung Fu → Heavy → Brain → Kung Fu
      // Se detectamos "Heavy Point", ordem é: Heavy → Brain → Kung Fu → Heavy
      let orderedFormNames = [];
      if (currentForm === "Brain Point" && formNames.length === 3) {
        // Ordem conhecida: Brain → Kung Fu → Heavy
        if (
          formNames.includes("Kung Fu Point") &&
          formNames.includes("Heavy Point")
        ) {
          orderedFormNames = ["Brain Point", "Kung Fu Point", "Heavy Point"];
        }
      } else if (currentForm === "Kung Fu Point" && formNames.length === 3) {
        // Ordem conhecida: Kung Fu → Heavy → Brain
        if (
          formNames.includes("Heavy Point") &&
          formNames.includes("Brain Point")
        ) {
          orderedFormNames = ["Kung Fu Point", "Heavy Point", "Brain Point"];
        }
      } else if (currentForm === "Heavy Point" && formNames.length === 3) {
        // Ordem conhecida: Heavy → Brain → Kung Fu
        if (
          formNames.includes("Brain Point") &&
          formNames.includes("Kung Fu Point")
        ) {
          orderedFormNames = ["Heavy Point", "Brain Point", "Kung Fu Point"];
        }
      }


            // Quinta skill da forma (vai DEPOIS do Guard Point)
      // Se não conseguiu criar ordem conhecida, usa ordem alfabética como fallback
            const fifthSkillName = formOrder[2]; // Terceira na ordem = quinta skill (índice 2)
      if (orderedFormNames.length === 0) {
            const fifthSkillData = formSkills.find(s => s.name === fifthSkillName);
        orderedFormNames = [...formNames].sort();
        // Se sabemos a forma atual, reorganiza para começar por ela
        if (currentForm) {
          const currentIdx = orderedFormNames.indexOf(currentForm);
          if (currentIdx !== -1) {
            orderedFormNames = [
              ...orderedFormNames.slice(currentIdx),
              ...orderedFormNames.slice(0, currentIdx),
            ];
          }
        }
      }


            // Cria fragments para inserir
      const currentIdx = orderedFormNames.indexOf(currentForm);
            const firstFragment = document.createDocumentFragment();
      if (currentIdx === -1) return; // Forma não encontrada
            const thirdFragment = document.createDocumentFragment();
            const fifthFragment = document.createDocumentFragment();


            if (firstSkillData) {
      const nextIdx = (currentIdx + 1) % orderedFormNames.length;
                const iconElement = createSkillIconElement(firstSkillData, 1);
      const nextForm = orderedFormNames[nextIdx];
                firstFragment.appendChild(iconElement);
            }
            if (thirdSkillData) {
                const changeFormIndex = parseInt(changeFormIcon.dataset.index || '2', 10);
                const iconElement = createSkillIconElement(thirdSkillData, changeFormIndex + 1);
                thirdFragment.appendChild(iconElement);
            }
            if (fifthSkillData) {
                const nextFixedSkillIndex = parseInt(nextFixedSkillIcon.dataset.index || '4', 10);
                const iconElement = createSkillIconElement(fifthSkillData, nextFixedSkillIndex + 1);
                fifthFragment.appendChild(iconElement);
            }


            // Remove os ícones antigos do DOM após animação
      currentForm = nextForm;
            setTimeout(() => {
                iconsToRemove.forEach(icon => {
                    if (icon.parentNode) {
                        icon.remove();
                    }
                });


                // Insere a primeira skill ANTES da skill com form_switch
      // Atualiza barra de skills (que vai remover o active depois da animação)
                if (firstFragment.hasChildNodes()) {
      updateSkillsBarForForm(nextForm, formsData[nextForm], changeFormIcon);
                    iconBar.insertBefore(firstFragment, changeFormIcon);
    }
                }


                // Insere a terceira skill DEPOIS da skill com form_switch, ANTES da próxima skill fixa
    function snapshotIconData(icon) {
                if (thirdFragment.hasChildNodes()) {
      const img = icon.querySelector("img");
                    iconBar.insertBefore(thirdFragment, nextFixedSkillIcon);
      const iconURL = img ? img.src : "";
                }
      const subsRaw = icon.dataset.subs || icon.getAttribute("data-subs") || "";
      let subs = null;
      try {
        subs = subsRaw ? JSON.parse(subsRaw) : null;
      } catch {
        subs = null;
      }
      let flags = null;
      if (icon.dataset.flags) {
        try {
          flags = JSON.parse(icon.dataset.flags);
        } catch (e) { }
      }
      let weapon = null;
      if (icon.dataset.weapon) {
        try {
          weapon = JSON.parse(icon.dataset.weapon);
        } catch (e) { }
      }
      return {
        name: icon.dataset.nome || icon.dataset.name || "",
        index: icon.dataset.index || "",
        level: icon.dataset.level || "",
        desc: icon.dataset.desc || "",
        descPt: icon.dataset.descPt || "",
        descEn: icon.dataset.descEn || "",
        descEs: icon.dataset.descEs || "",
        descPl: icon.dataset.descPl || "",
        attrs: icon.dataset.atr || icon.dataset.attrs || "",
        video: icon.dataset.video || "",
        iconURL,
        iconFile: icon.dataset.iconFile || "",
        subs,
        flags,
        weapon,
        formSwitch: icon.dataset.formSwitch || "",
      };
    }


                // Insere a quinta skill DEPOIS da próxima skill fixa
    function updateSkillsBarForForm(formName, formData, changeFormIconEl) {
                if (fifthFragment.hasChildNodes()) {
      const iconBar = document.querySelector(".icon-bar");
                    if (nextFixedSkillIcon.nextSibling) {
      if (!iconBar || !formData || !formData.skills) return;
                        iconBar.insertBefore(fifthFragment, nextFixedSkillIcon.nextSibling);
                    } else {
                        iconBar.appendChild(fifthFragment);
                    }
                }


                // Anima entrada das novas skills (similar a animateIconsBarEntrance)
      // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
                const newIconsInBar = Array.from(iconBar.querySelectorAll('.skill-icon[data-index]'))
      const allFormSkillNames = new Set();
                    .filter(icon => {
      Object.values(formsData).forEach((form) => {
                        const name = (icon.dataset.nome || '').trim();
        if (form.order && Array.isArray(form.order)) {
                        return name && allFormSkillNames.has(name);
          form.order.forEach((skillName) => allFormSkillNames.add(skillName));
                    });
        }
      });


                newIconsInBar.forEach((icon, idx) => {
      // Remove skills de forma antigas (que não são fixas) com animação de saída
                    icon.style.opacity = '0';
                    icon.style.transform = 'translateY(6px)';
                    requestAnimationFrame(() => {
                        setTimeout(() => {
                            icon.style.transition = 'opacity .18s ease, transform .18s ease';
                            icon.style.opacity = '1';
                            icon.style.transform = 'translateY(0)';
                        }, idx * 24);
                    });
                });


                // Remove slots de descrição das skills de forma antigas e cria novos
      const existingIcons = Array.from(
                const descBox = document.querySelector('.skills-details .desc-box');
        iconBar.querySelectorAll(".skill-icon[data-index]")
                if (descBox) {
      );
                    // Encontra os slots de descrição dos elementos fixos
      const iconsToRemove = [];
                    const changeFormIndex = parseInt(changeFormIcon.dataset.index || '1', 10);
      existingIcons.forEach((icon) => {
                    const nextFixedSkillIndex = parseInt(nextFixedSkillIcon.dataset.index || '1', 10);
        const name = (icon.dataset.nome || "").trim();
                    const changeFormDescSlot = descBox.querySelector(`.skill-desc[data-index="${changeFormIndex}"]`);
        if (name && allFormSkillNames.has(name)) {
                    const nextFixedSkillDescSlot = descBox.querySelector(`.skill-desc[data-index="${nextFixedSkillIndex}"]`);
          iconsToRemove.push(icon);
        }
      });


                    // Cria slot para primeira skill (antes do Change Form)
      // Anima saída das skills antigas
                    if (firstSkillData && changeFormDescSlot) {
      iconsToRemove.forEach((icon) => {
                        const descSlot = document.createElement('div');
        icon.style.transition = "opacity .15s ease, transform .15s ease";
                        descSlot.className = 'skill-desc';
        icon.style.opacity = "0";
                        descSlot.setAttribute('data-index', changeFormIndex);
        icon.style.transform = "translateY(-6px)";
                        descBox.insertBefore(descSlot, changeFormDescSlot);
      });
                    }


                    // Cria slot para terceira skill (depois da skill com form_switch, antes da próxima skill fixa)
      // Encontra a skill com form_switch dinamicamente (genérico)
                    if (thirdSkillData && nextFixedSkillDescSlot) {
      const changeFormIcon = Array.from(
                        const descSlot = document.createElement('div');
        iconBar.querySelectorAll(".skill-icon[data-index]")
                        descSlot.className = 'skill-desc';
      ).find(
                        descSlot.setAttribute('data-index', nextFixedSkillIndex);
        (icon) =>
                        descBox.insertBefore(descSlot, nextFixedSkillDescSlot);
          icon.dataset.formSwitch === "true" ||
                    }
          icon.getAttribute("data-form-switch") === "true"
      );


                    // Cria slot para quinta skill (depois da próxima skill fixa)
      // Encontra a próxima skill fixa depois do form_switch dinamicamente
                    if (fifthSkillData && nextFixedSkillDescSlot) {
      let nextFixedSkillIcon = null;
                        const descSlot = document.createElement('div');
      if (changeFormIcon) {
                        descSlot.className = 'skill-desc';
        const allIcons = Array.from(
                        descSlot.setAttribute('data-index', nextFixedSkillIndex + 1);
          iconBar.querySelectorAll(".skill-icon[data-index]")
                        if (nextFixedSkillDescSlot.nextSibling) {
        ).sort((a, b) => {
                            descBox.insertBefore(descSlot, nextFixedSkillDescSlot.nextSibling);
          return (
                        } else {
            parseInt(a.dataset.index || "0", 10) -
                            descBox.appendChild(descSlot);
            parseInt(b.dataset.index || "0", 10)
                        }
          );
                    }
        });
                }
        const changeFormIndex = allIcons.indexOf(changeFormIcon);
        const allFormSkillNames = new Set();
        Object.values(formsData).forEach((form) => {
          if (form.order && Array.isArray(form.order)) {
            form.order.forEach((skillName) => allFormSkillNames.add(skillName));
          }
        });


                // Re-numera todas as skills na ordem do DOM
        // Procura a próxima skill fixa (que não está em forms)
                const allIcons = Array.from(iconBar.querySelectorAll('.skill-icon[data-index]'));
        for (let i = changeFormIndex + 1; i < allIcons.length; i++) {
                let currentIndex = 1;
          const icon = allIcons[i];
                allIcons.forEach(icon => {
          const name = (icon.dataset.nome || "").trim();
                    const oldIndex = icon.dataset.index;
          if (name && !allFormSkillNames.has(name)) {
                    icon.setAttribute('data-index', currentIndex);
            nextFixedSkillIcon = icon;
            break;
          }
        }
      }


                    // Atualiza slot de descrição
      if (!changeFormIcon || !nextFixedSkillIcon) {
                    if (descBox && oldIndex) {
        console.warn(
                        const descSlot = descBox.querySelector(`.skill-desc[data-index="${oldIndex}"]`);
          "[Forms] Skill com form_switch ou próxima skill fixa não encontrada"
                        if (descSlot) {
        );
                            descSlot.setAttribute('data-index', currentIndex);
        return;
                        }
      }
                    }
                    currentIndex++;
                });


                // Re-wire eventos
      const formSkills = formData.skills || [];
                wireClicksForCurrentBar();
      const formOrder = formData.order || [];
                wireTooltipsForNewIcons();


                // Remove active do Change Form após animação completar
      // Primeira skill da forma (vai ANTES do Change Form)
                setTimeout(() => {
      const firstSkillName = formOrder[0];
                    if (changeFormIconEl) {
      const firstSkillData = formSkills.find((s) => s.name === firstSkillName);
                        changeFormIconEl.classList.remove('active');
                    }
                }, (newIconsInBar.length * 24) + 180);
            }, 150);
        }


        function createSkillIconElement(skill, index) {
      // Terceira skill da forma (vai DEPOIS do Change Form, ANTES do Guard Point)
            const iconWrap = document.createElement('div');
      const thirdSkillName = formOrder[1]; // Segunda na ordem = terceira skill (índice 1)
            iconWrap.className = 'skill-icon';
      const thirdSkillData = formSkills.find((s) => s.name === thirdSkillName);
            iconWrap.setAttribute('data-index', index);
            iconWrap.setAttribute('data-nome', skill.name || '');
            iconWrap.setAttribute('data-desc', '');
            iconWrap.setAttribute('data-atr', makeAttrString(skill.powerpve, skill.powerpvp, skill.energy, skill.cooldown));
            iconWrap.setAttribute('data-video', skill.video ? filePathURL(skill.video) : '');
            iconWrap.setAttribute('data-video-preload', 'auto');
            iconWrap.setAttribute('data-icon-file', skill.icon || 'Nada.png');
            iconWrap.setAttribute('data-video-file', skill.video || '');


            if (skill.level && skill.level !== '' && skill.level.toUpperCase() !== 'NIVEL') {
      // Quinta skill da forma (vai DEPOIS do Guard Point)
                iconWrap.setAttribute('data-level', skill.level);
      const fifthSkillName = formOrder[2]; // Terceira na ordem = quinta skill (índice 2)
            }
      const fifthSkillData = formSkills.find((s) => s.name === fifthSkillName);


            if (skill.desc_i18n) {
      // Cria fragments para inserir
                if (skill.desc_i18n.pt) iconWrap.setAttribute('data-desc-pt', skill.desc_i18n.pt);
      const firstFragment = document.createDocumentFragment();
                if (skill.desc_i18n.en) iconWrap.setAttribute('data-desc-en', skill.desc_i18n.en);
      const thirdFragment = document.createDocumentFragment();
                if (skill.desc_i18n.es) iconWrap.setAttribute('data-desc-es', skill.desc_i18n.es);
      const fifthFragment = document.createDocumentFragment();
                if (skill.desc_i18n.pl) iconWrap.setAttribute('data-desc-pl', skill.desc_i18n.pl);
            }


            if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
      if (firstSkillData) {
                iconWrap.setAttribute('data-subs', JSON.stringify(skill.subs));
        const iconElement = createSkillIconElement(firstSkillData, 1);
            }
        firstFragment.appendChild(iconElement);
            if (skill.suborder && Array.isArray(skill.suborder) && skill.suborder.length > 0) {
      }
                iconWrap.setAttribute('data-suborder', JSON.stringify(skill.suborder));
      if (thirdSkillData) {
            }
        const changeFormIndex = parseInt(
          changeFormIcon.dataset.index || "2",
          10
        );
        const iconElement = createSkillIconElement(
          thirdSkillData,
          changeFormIndex + 1
        );
        thirdFragment.appendChild(iconElement);
      }
      if (fifthSkillData) {
        const nextFixedSkillIndex = parseInt(
          nextFixedSkillIcon.dataset.index || "4",
          10
        );
        const iconElement = createSkillIconElement(
          fifthSkillData,
          nextFixedSkillIndex + 1
        );
        fifthFragment.appendChild(iconElement);
      }


            if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
      // Remove os ícones antigos do DOM após animação
                iconWrap.setAttribute('data-flags', JSON.stringify(skill.flags));
      setTimeout(() => {
            }
        iconsToRemove.forEach((icon) => {
          if (icon.parentNode) {
            icon.remove();
          }
        });


            if (skill.weapon && typeof skill.weapon === 'object' && Object.keys(skill.weapon).length > 0) {
        // Insere a primeira skill ANTES da skill com form_switch
                iconWrap.setAttribute('data-weapon', JSON.stringify(skill.weapon));
        if (firstFragment.hasChildNodes()) {
            }
          iconBar.insertBefore(firstFragment, changeFormIcon);
        }


            const img = document.createElement('img');
        // Insere a terceira skill DEPOIS da skill com form_switch, ANTES da próxima skill fixa
            img.className = 'skill-icon-img';
        if (thirdFragment.hasChildNodes()) {
            img.src = filePathURL(skill.icon || 'Nada.png');
          iconBar.insertBefore(thirdFragment, nextFixedSkillIcon);
            img.alt = '';
        }
            iconWrap.appendChild(img);


             return iconWrap;
        // Insere a quinta skill DEPOIS da próxima skill fixa
        if (fifthFragment.hasChildNodes()) {
          if (nextFixedSkillIcon.nextSibling) {
             iconBar.insertBefore(fifthFragment, nextFixedSkillIcon.nextSibling);
          } else {
            iconBar.appendChild(fifthFragment);
          }
         }
         }


         function createSkillIcon(skill, index, container) {
         // Anima entrada das novas skills (similar a animateIconsBarEntrance)
            // Cria ícone similar ao que é gerado pelo servidor
        const newIconsInBar = Array.from(
            const iconWrap = document.createElement('div');
          iconBar.querySelectorAll(".skill-icon[data-index]")
            iconWrap.className = 'skill-icon';
        ).filter((icon) => {
            iconWrap.setAttribute('data-index', index);
          const name = (icon.dataset.nome || "").trim();
            iconWrap.setAttribute('data-nome', skill.name || '');
          return name && allFormSkillNames.has(name);
            iconWrap.setAttribute('data-desc', '');
        });
            iconWrap.setAttribute('data-atr', makeAttrString(skill.powerpve, skill.powerpvp, skill.energy, skill.cooldown));
 
             iconWrap.setAttribute('data-video', skill.video ? filePathURL(skill.video) : '');
        newIconsInBar.forEach((icon, idx) => {
            iconWrap.setAttribute('data-video-preload', 'auto');
          icon.style.opacity = "0";
             iconWrap.setAttribute('data-icon-file', skill.icon || 'Nada.png');
          icon.style.transform = "translateY(6px)";
             iconWrap.setAttribute('data-video-file', skill.video || '');
          requestAnimationFrame(() => {
             setTimeout(() => {
              icon.style.transition = "opacity .18s ease, transform .18s ease";
              icon.style.opacity = "1";
              icon.style.transform = "translateY(0)";
            }, idx * 24);
          });
        });
 
        // Remove slots de descrição das skills de forma antigas e cria novos
        const descBox = document.querySelector(".skills-details .desc-box");
        if (descBox) {
          // Encontra os slots de descrição dos elementos fixos
          const changeFormIndex = parseInt(
            changeFormIcon.dataset.index || "1",
            10
          );
          const nextFixedSkillIndex = parseInt(
             nextFixedSkillIcon.dataset.index || "1",
            10
          );
          const changeFormDescSlot = descBox.querySelector(
            `.skill-desc[data-index="${changeFormIndex}"]`
          );
          const nextFixedSkillDescSlot = descBox.querySelector(
             `.skill-desc[data-index="${nextFixedSkillIndex}"]`
          );


            if (skill.level && skill.level !== '' && skill.level.toUpperCase() !== 'NIVEL') {
          // Cria slot para primeira skill (antes do Change Form)
                iconWrap.setAttribute('data-level', skill.level);
          if (firstSkillData && changeFormDescSlot) {
            }
            const descSlot = document.createElement("div");
            descSlot.className = "skill-desc";
            descSlot.setAttribute("data-index", changeFormIndex);
            descBox.insertBefore(descSlot, changeFormDescSlot);
          }


            if (skill.desc_i18n) {
          // Cria slot para terceira skill (depois da skill com form_switch, antes da próxima skill fixa)
                if (skill.desc_i18n.pt) iconWrap.setAttribute('data-desc-pt', skill.desc_i18n.pt);
          if (thirdSkillData && nextFixedSkillDescSlot) {
                if (skill.desc_i18n.en) iconWrap.setAttribute('data-desc-en', skill.desc_i18n.en);
            const descSlot = document.createElement("div");
                if (skill.desc_i18n.es) iconWrap.setAttribute('data-desc-es', skill.desc_i18n.es);
            descSlot.className = "skill-desc";
                if (skill.desc_i18n.pl) iconWrap.setAttribute('data-desc-pl', skill.desc_i18n.pl);
            descSlot.setAttribute("data-index", nextFixedSkillIndex);
            }
            descBox.insertBefore(descSlot, nextFixedSkillDescSlot);
          }


            if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
          // Cria slot para quinta skill (depois da próxima skill fixa)
                iconWrap.setAttribute('data-subs', JSON.stringify(skill.subs));
          if (fifthSkillData && nextFixedSkillDescSlot) {
            }
            const descSlot = document.createElement("div");
             if (skill.suborder && Array.isArray(skill.suborder) && skill.suborder.length > 0) {
            descSlot.className = "skill-desc";
                iconWrap.setAttribute('data-suborder', JSON.stringify(skill.suborder));
            descSlot.setAttribute("data-index", nextFixedSkillIndex + 1);
             if (nextFixedSkillDescSlot.nextSibling) {
              descBox.insertBefore(
                descSlot,
                nextFixedSkillDescSlot.nextSibling
              );
            } else {
              descBox.appendChild(descSlot);
             }
             }
          }
        }


            if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
        // Re-numera todas as skills na ordem do DOM
                iconWrap.setAttribute('data-flags', JSON.stringify(skill.flags));
        const allIcons = Array.from(
            }
          iconBar.querySelectorAll(".skill-icon[data-index]")
        );
        let currentIndex = 1;
        allIcons.forEach((icon) => {
          const oldIndex = icon.dataset.index;
          icon.setAttribute("data-index", currentIndex);


            if (skill.weapon && typeof skill.weapon === 'object' && Object.keys(skill.weapon).length > 0) {
          // Atualiza slot de descrição
                iconWrap.setAttribute('data-weapon', JSON.stringify(skill.weapon));
          if (descBox && oldIndex) {
            const descSlot = descBox.querySelector(
              `.skill-desc[data-index="${oldIndex}"]`
            );
            if (descSlot) {
              descSlot.setAttribute("data-index", currentIndex);
             }
             }
          }
          currentIndex++;
        });


            const img = document.createElement('img');
        // Re-wire eventos
            img.className = 'skill-icon-img';
        wireClicksForCurrentBar();
            img.src = filePathURL(skill.icon || 'Nada.png');
        wireTooltipsForNewIcons();
            img.alt = '';
            iconWrap.appendChild(img);


             container.appendChild(iconWrap);
        // Remove active do Change Form após animação completar
         }
        setTimeout(() => {
          if (changeFormIconEl) {
             changeFormIconEl.classList.remove("active");
          }
         }, newIconsInBar.length * 24 + 180);
      }, 150);
    }


        function makeAttrString(pve, pvp, energy, cd) {
    function createSkillIconElement(skill, index) {
            const parts = [
      const iconWrap = document.createElement("div");
                (pve !== undefined && pve !== null && pve !== '') ? String(pve) : '-',
      iconWrap.className = "skill-icon";
                (pvp !== undefined && pvp !== null && pvp !== '') ? String(pvp) : '-',
      iconWrap.setAttribute("data-index", index);
                (energy !== undefined && energy !== null && energy !== '') ? String(energy) : '-',
      iconWrap.setAttribute("data-nome", skill.name || "");
                (cd !== undefined && cd !== null && cd !== '') ? String(cd) : '-'
      iconWrap.setAttribute("data-desc", "");
            ];
      iconWrap.setAttribute(
            return parts.join(', ');
        "data-atr",
        }
        makeAttrString(
          skill.powerpve,
          skill.powerpvp,
          skill.energy,
          skill.cooldown
        )
      );
      iconWrap.setAttribute(
        "data-video",
        skill.video ? filePathURL(skill.video) : ""
      );
      iconWrap.setAttribute("data-video-preload", "auto");
      iconWrap.setAttribute("data-icon-file", skill.icon || "");
      iconWrap.setAttribute("data-video-file", skill.video || "");


        function filePathURL(fileName) {
      if (
            // Evita requisições para Nada.png que não existe
        skill.level &&
            if (!fileName || fileName.trim() === '' || fileName === 'Nada.png' || fileName.toLowerCase() === 'nada.png') {
        skill.level !== "" &&
                return '';
         skill.level.toUpperCase() !== "NIVEL"
            }
      ) {
            const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ''));
         iconWrap.setAttribute("data-level", skill.level);
            const base = (window.mw && mw.util && typeof mw.util.wikiScript === 'function') ? mw.util.wikiScript() : (window.mw && window.mw.config ? (mw.config.get('wgScript') || '/index.php') : '/index.php');
      }
            // Garante HTTPS para evitar Mixed Content
            let url = `${base}?title=Especial:FilePath/${f}`;
            if (window.location.protocol === 'https:' && url.startsWith('http://')) {
                url = url.replace('http://', 'https://');
            }
            return url;
        } function slugify(s) {
            if (!s) return '';
            return String(s).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s-]/g, '').replace(/[\s:/\-]+/g, '-').replace(/^-+|-+$/g, '').replace(/-+/g, '-');
        } window.__skillSlugify = slugify;
        function getLangKey() {
            const skillsRoot = document.getElementById('skills');
            const raw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase();
            return raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
        } function chooseDescFrom(obj) {
            const lang = getLangKey();
            // Aceita tanto desc_i18n quanto desc para compatibilidade
            const pack = obj.desc_i18n || obj.desc || {
                pt: obj.descPt, en: obj.descEn, es: obj.descEs, pl: obj.descPl
            };
            return (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || '';
         } function renderSubAttributesFromObj(s, L) {
            const chip = (label, val) => (val ? `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${val}</span></div>` : '');
            const pve = (s.powerpve || '').toString().trim();
            const pvp = (s.powerpvp || '').toString().trim();
            const en = (s.energy || '').toString().trim();
            const cd = (s.cooldown || '').toString().trim();
            const rows = [cd ? chip(L.cooldown, cd) : '', en ? chip((en.startsWith('-') ? L.energy_cost : L.energy_gain), en.startsWith('-') ? en.replace(/^-/, '') : en.replace(/^\+?/, '')) : '', pve ? chip(L.power, pve) : '', pvp ? chip(L.power_pvp, pvp) : '',].filter(Boolean);
            return rows.length ? `<div class="attr-list">${rows.join('')}</div>` : '';
        } function getFlagIconURL(key) {
            if (!FLAG_ICON_FILES[key]) return '';
            if (!flagIconURLCache.has(key)) {
                flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key]));
            } return flagIconURLCache.get(key);
         } function renderFlagsRow(flags) {
            const arr = (flags || []).filter(Boolean);
            if (!arr.length) return '';
            const cacheKey = arr.join('|');
            if (flagRowCache.has(cacheKey)) {
                return flagRowCache.get(cacheKey);
            } const items = arr.map(k => {
                const url = getFlagIconURL(k);
                return url ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">` : '';
            }).join('');
            const html = items ? `<div class="skill-flags" role="group" aria-label="Características">${items}</div>` : '';
            if (html) flagRowCache.set(cacheKey, html);
            return html;
        } function applyFlagTooltips(container) {
            const skillsRoot = document.getElementById('skills');
            if (!skillsRoot) return;
            let pack = {
            };
            try {
                pack = JSON.parse(skillsRoot.dataset.i18nFlags || '{}');
            } catch (e) {
            } const lang = getLangKey();
            const dict = pack[lang] || pack.pt || {
            };
            const flags = container.querySelectorAll('.skill-flags .skill-flag[data-flag]');
            const tooltip = window.__globalSkillTooltip;
            if (!tooltip) return;
            flags.forEach(el => {
                const key = el.getAttribute('data-flag');
                const tip = (dict && dict[key]) || '';
                if (!tip) return;
                if (el.dataset.flagTipWired) return;
                el.dataset.flagTipWired = '1';
                el.setAttribute('aria-label', tip);
                if (el.hasAttribute('title')) el.removeAttribute('title');
                el.addEventListener('mouseenter', () => {
                    const tipEl = document.querySelector('.skill-tooltip');
                    if (tipEl) tipEl.classList.add('flag-tooltip');
                    tooltip.show(el, tip);
                });
                el.addEventListener('mousemove', () => {
                    if (performance.now() >= tooltip.lockUntil.value) {
                        tooltip.measureAndPos(el);
                    }
                });
                el.addEventListener('click', () => {
                    tooltip.lockUntil.value = performance.now() + 240;
                    tooltip.measureAndPos(el);
                });
                el.addEventListener('mouseleave', () => {
                    const tipEl = document.querySelector('.skill-tooltip');
                    if (tipEl) tipEl.classList.remove('flag-tooltip');
                    tooltip.hide();
                });
            });
        }


         // ====== Skill/Subskill inheritance helpers ======
      if (skill.desc_i18n) {
         const mainSkillsMeta = {
         if (skill.desc_i18n.pt)
            byIndex: new Map(),
          iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
            byName: new Map(),
         if (skill.desc_i18n.en)
            ready: false
          iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
        };
        if (skill.desc_i18n.es)
          iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
        if (skill.desc_i18n.pl)
          iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
      }


        function normalizeFileURL(raw, fallback = '') {
      if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
            if (!raw) return fallback;
        iconWrap.setAttribute("data-subs", JSON.stringify(skill.subs));
            const val = String(raw).trim();
      }
            if (!val) return fallback;
      if (
            if (/^(https?:)?\/\//i.test(val) || val.startsWith('data:') || val.includes('Especial:FilePath/')) {
        skill.suborder &&
                return val;
        Array.isArray(skill.suborder) &&
            } return filePathURL(val);
        skill.suborder.length > 0
        }
      ) {
        iconWrap.setAttribute("data-suborder", JSON.stringify(skill.suborder));
      }


        function extractFileNameFromURL(url) {
      if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
            if (!url) return '';
        iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
            const match = String(url).match(/(?:FilePath\/)([^&?]+)/i);
      }
            return match ? decodeURIComponent(match[1]) : '';
        }


         function parseAttrString(raw) {
      if (
            const parts = (raw || '').split(',').map(v => v.trim());
         skill.weapon &&
            const safe = idx => {
        typeof skill.weapon === "object" &&
                const val = parts[idx] || '';
        Object.keys(skill.weapon).length > 0
                return (val && val !== '-') ? val : '';
      ) {
            };
        iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
            return {
      }
                powerpve: safe(0),
      if (skill.effect && typeof skill.effect === "object") {
                powerpvp: safe(1),
        try {
                energy: safe(2),
          iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
                cooldown: safe(3)
        } catch (e) { }
            };
      }
        }


        function hasText(value) {
      const img = document.createElement("img");
            return typeof value === 'string' ? value.trim() !== '' : value !== undefined && value !== null;
      img.className = "skill-icon-img";
        }
      img.src = filePathURL(skill.icon || "");
      img.alt = "";
      iconWrap.appendChild(img);


        function pickFilled(current, fallback) {
      return iconWrap;
            if (current === 0 || current === '0') return current;
    }
            if (!hasText(current)) return fallback;
            return current;
        }


        function buildMainSkillsMeta(nodes) {
    function createSkillIcon(skill, index, container) {
            if (mainSkillsMeta.ready) {
      // Cria ícone similar ao que é gerado pelo servidor
                return mainSkillsMeta;
      const iconWrap = document.createElement("div");
            }
      iconWrap.className = "skill-icon";
            (nodes || []).forEach(icon => {
      iconWrap.setAttribute("data-index", index);
                const index = (icon.dataset.index || '').trim();
      iconWrap.setAttribute("data-nome", skill.name || "");
                if (!index) return;
      iconWrap.setAttribute("data-desc", "");
                const name = (icon.dataset.nome || icon.dataset.name || '').trim();
      iconWrap.setAttribute(
                const attrs = parseAttrString(icon.dataset.atr || '');
        "data-atr",
                let iconFile = (icon.dataset.iconFile || '').trim();
        makeAttrString(
                if (!iconFile) {
          skill.powerpve,
                    const imgSrc = icon.querySelector('img')?.src || '';
          skill.powerpvp,
                    const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
          skill.energy,
                    iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : '';
          skill.cooldown
                }
        )
                let videoFile = (icon.dataset.videoFile || '').trim();
      );
                if (!videoFile) {
      iconWrap.setAttribute(
                    videoFile = extractFileNameFromURL(icon.dataset.video || '');
        "data-video",
                }
        skill.video ? filePathURL(skill.video) : ""
                const meta = {
      );
                    index,
      iconWrap.setAttribute("data-video-preload", "auto");
                    name,
      iconWrap.setAttribute("data-icon-file", skill.icon || "");
                    icon: iconFile || 'Nada.png',
      iconWrap.setAttribute("data-video-file", skill.video || "");
                    level: icon.dataset.level || '',
                    video: videoFile || '',
                    powerpve: attrs.powerpve || '',
                    powerpvp: attrs.powerpvp || '',
                    energy: attrs.energy || '',
                    cooldown: attrs.cooldown || '',
                    desc: icon.dataset.desc || '',
                    descPt: icon.dataset.descPt || '',
                    descEn: icon.dataset.descEn || '',
                    descEs: icon.dataset.descEs || '',
                    descPl: icon.dataset.descPl || ''
                };
                mainSkillsMeta.byIndex.set(index, meta);
                mainSkillsMeta.byIndex.set(parseInt(index, 10), meta);
                if (name) {
                    mainSkillsMeta.byName.set(name, meta);
                }
            });
            mainSkillsMeta.ready = true;
            return mainSkillsMeta;
        }


        function inheritSubskillFromMain(sub, meta) {
      if (
            if (!sub || !meta) return sub;
        skill.level &&
            // Verifica se herança está desabilitada
        skill.level !== "" &&
            const shouldInherit = !(sub.inherit === false || sub.inherit === 'no' || sub.inherit === 'false');
        skill.level.toUpperCase() !== "NIVEL"
      ) {
        iconWrap.setAttribute("data-level", skill.level);
      }


            // Suporta refS (novo) e refM (legado)
      if (skill.desc_i18n) {
            const refS = ((sub.refS || sub.S || sub.s || '') + '').trim();
        if (skill.desc_i18n.pt)
            const refIndex = ((sub.refM || sub.M || sub.m || '') + '').trim();
          iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
            let name = (sub.name || sub.n || '').trim();
        if (skill.desc_i18n.en)
            let main = null;
          iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
        if (skill.desc_i18n.es)
          iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
        if (skill.desc_i18n.pl)
          iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
      }


            // Se herança está desabilitada, não busca a skill principal
      if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
            if (!shouldInherit) {
        iconWrap.setAttribute("data-subs", JSON.stringify(skill.subs));
                return { ...sub };
      }
            }
      if (
        skill.suborder &&
        Array.isArray(skill.suborder) &&
        skill.suborder.length > 0
      ) {
        iconWrap.setAttribute("data-suborder", JSON.stringify(skill.suborder));
      }


            // Primeiro tenta por refS
      if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
            if (refS) {
        iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
                main = meta.byIndex.get(refS) || meta.byIndex.get(parseInt(refS, 10));
      }
            }
            // Depois por refM
            if (!main && refIndex) {
                main = meta.byIndex.get(refIndex) || meta.byIndex.get(parseInt(refIndex, 10));
            }
            // Por último pelo nome
            if (!main && name) {
                main = meta.byName.get(name);
            }
            if (!main) {
                return sub;
            }


            const hydrated = { ...sub };
      if (
            if (!name && main.name) {
        skill.weapon &&
                name = main.name;
        typeof skill.weapon === "object" &&
            }
        Object.keys(skill.weapon).length > 0
            hydrated.name = name || hydrated.name || main.name || '';
      ) {
            hydrated.icon = pickFilled(hydrated.icon, main.icon || 'Nada.png');
        iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
            hydrated.level = pickFilled(hydrated.level, main.level || '');
      }
      if (skill.effect && typeof skill.effect === "object") {
        try {
          iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
        } catch (e) { }
      }


            // Vídeo NUNCA é herdado da skill principal
      const img = document.createElement("img");
            // Se a chave 'video' existe = foi especificado pelo editor (mesmo que vazio)
      img.className = "skill-icon-img";
            // Se não existe = não foi especificado = não herda, fica vazio
      img.src = filePathURL(skill.icon || "");
            const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, 'video');
      img.alt = "";
            hydrated.video = hasOwnVideo ? (sub.video || '') : '';
      iconWrap.appendChild(img);


            hydrated.powerpve = pickFilled(hydrated.powerpve, main.powerpve || '');
      container.appendChild(iconWrap);
            hydrated.powerpvp = pickFilled(hydrated.powerpvp, main.powerpvp || '');
    }
            hydrated.energy = pickFilled(hydrated.energy, main.energy || '');
            hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || '');


            if (!hasText(hydrated.descPt) && hasText(main.descPt)) hydrated.descPt = main.descPt;
    function makeAttrString(pve, pvp, energy, cd) {
            if (!hasText(hydrated.descEn) && hasText(main.descEn)) hydrated.descEn = main.descEn;
      const parts = [
            if (!hasText(hydrated.descEs) && hasText(main.descEs)) hydrated.descEs = main.descEs;
        pve !== undefined && pve !== null && pve !== "" ? String(pve) : "-",
            if (!hasText(hydrated.descPl) && hasText(main.descPl)) hydrated.descPl = main.descPl;
        pvp !== undefined && pvp !== null && pvp !== "" ? String(pvp) : "-",
            if (!hasText(hydrated.desc) && hasText(main.desc)) hydrated.desc = main.desc;
        energy !== undefined && energy !== null && energy !== ""
          ? String(energy)
          : "-",
        cd !== undefined && cd !== null && cd !== "" ? String(cd) : "-",
      ];
      return parts.join(", ");
    }


            if (!hydrated.desc_i18n && (hydrated.descPt || hydrated.descEn || hydrated.descEs || hydrated.descPl)) {
    function filePathURL(fileName) {
                hydrated.desc_i18n = {
      // Evita requisições para valores vazios
                    pt: hydrated.descPt || '',
      if (!fileName || fileName.trim() === "") {
                    en: hydrated.descEn || '',
        return "";
                    es: hydrated.descEs || '',
      }
                    pl: hydrated.descPl || ''
      const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
                };
      const base =
             }
        window.mw && mw.util && typeof mw.util.wikiScript === "function"
          ? mw.util.wikiScript()
          : window.mw && window.mw.config
            ? mw.config.get("wgScript") || "/index.php"
            : "/index.php";
      // Garante HTTPS para evitar Mixed Content
      let url = `${base}?title=Especial:FilePath/${f}`;
      if (window.location.protocol === "https:" && url.startsWith("http://")) {
        url = url.replace("http://", "https://");
      }
      return url;
    }
    function slugify(s) {
      if (!s) return "";
      return String(s)
        .toLowerCase()
        .normalize("NFD")
        .replace(/[\u0300-\u036f]/g, "")
        .replace(/[^\w\s-]/g, "")
        .replace(/[\s:/\-]+/g, "-")
        .replace(/^-+|-+$/g, "")
        .replace(/-+/g, "-");
    }
    window.__skillSlugify = slugify;
    function getLangKey() {
      const skillsRoot = document.getElementById("skills");
      const raw = (
        document.documentElement.lang ||
        skillsRoot?.dataset.i18nDefault ||
        "pt"
      ).toLowerCase();
      return raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
    }
    function chooseDescFrom(obj) {
      const lang = getLangKey();
      // Aceita tanto desc_i18n quanto desc para compatibilidade
      const pack = obj.desc_i18n ||
        obj.desc || {
        pt: obj.descPt,
        en: obj.descEn,
        es: obj.descEs,
        pl: obj.descPl,
      };
      return (
        (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || ""
      );
    }
    function renderSubAttributesFromObj(s, L) {
      const chip = (label, val) =>
        val
          ? `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${val}</span></div>`
          : "";
      const pve = (s.powerpve || "").toString().trim();
      const pvp = (s.powerpvp || "").toString().trim();
      const en = (s.energy || "").toString().trim();
      const cd = (s.cooldown || "").toString().trim();
      const rows = [
        cd ? chip(L.cooldown, cd) : "",
        en
          ? chip(
            en.startsWith("-") ? L.energy_cost : L.energy_gain,
            en.startsWith("-") ? en.replace(/^-/, "") : en.replace(/^\+?/, "")
          )
          : "",
        pve ? chip(L.power, pve) : "",
        pvp ? chip(L.power_pvp, pvp) : "",
      ].filter(Boolean);
      return rows.length ? `<div class="attr-list">${rows.join("")}</div>` : "";
    }
    function getFlagIconURL(key) {
      if (!FLAG_ICON_FILES[key]) return "";
      if (!flagIconURLCache.has(key)) {
        flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key]));
      }
      return flagIconURLCache.get(key);
    }
    function renderFlagsRow(flags) {
      const arr = (flags || []).filter(Boolean);
      if (!arr.length) return "";
      const cacheKey = arr.join("|");
      if (flagRowCache.has(cacheKey)) {
        return flagRowCache.get(cacheKey);
      }
      const items = arr
        .map((k) => {
          const url = getFlagIconURL(k);
          return url
            ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">`
            : "";
        })
        .join("");
      const html = items
        ? `<div class="skill-flags" role="group" aria-label="Características">${items}</div>`
        : "";
      if (html) flagRowCache.set(cacheKey, html);
      return html;
    }
    function applyFlagTooltips(container) {
      const skillsRoot = document.getElementById("skills");
      if (!skillsRoot) return;
      let pack = {};
      try {
        pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
      } catch (e) { }
      const lang = getLangKey();
      const dict = pack[lang] || pack.pt || {};
      const flags = container.querySelectorAll(
        ".skill-flags .skill-flag[data-flag]"
      );
      const tooltip = window.__globalSkillTooltip;
      if (!tooltip) return;
      flags.forEach((el) => {
        const key = el.getAttribute("data-flag");
        const tip = (dict && dict[key]) || "";
        if (!tip) return;
        if (el.dataset.flagTipWired) return;
        el.dataset.flagTipWired = "1";
        el.setAttribute("aria-label", tip);
        if (el.hasAttribute("title")) el.removeAttribute("title");
        el.addEventListener("mouseenter", () => {
          const tipEl = document.querySelector(".skill-tooltip");
          if (tipEl) tipEl.classList.add("flag-tooltip");
          tooltip.show(el, tip);
        });
        el.addEventListener("mousemove", () => {
          if (performance.now() >= tooltip.lockUntil.value) {
             tooltip.measureAndPos(el);
          }
        });
        el.addEventListener("click", () => {
          tooltip.lockUntil.value = performance.now() + 240;
          tooltip.measureAndPos(el);
        });
        el.addEventListener("mouseleave", () => {
          const tipEl = document.querySelector(".skill-tooltip");
          if (tipEl) tipEl.classList.remove("flag-tooltip");
          tooltip.hide();
        });
      });
    }
 
    // ====== Skill/Subskill inheritance helpers ======
    const mainSkillsMeta = {
      byIndex: new Map(),
      byName: new Map(),
      ready: false,
    };
 
    function normalizeFileURL(raw, fallback = "") {
      if (!raw) return fallback;
      const val = String(raw).trim();
      if (!val) return fallback;
      if (
        /^(https?:)?\/\//i.test(val) ||
        val.startsWith("data:") ||
        val.includes("Especial:FilePath/")
      ) {
        return val;
      }
      return filePathURL(val);
    }


            return hydrated;
    function extractFileNameFromURL(url) {
        }
      if (!url) return "";
      const match = String(url).match(/(?:FilePath\/)([^&?]+)/i);
      return match ? decodeURIComponent(match[1]) : "";
    }


        function inheritSubskillTree(subs, meta) {
    function parseAttrString(raw) {
            if (!Array.isArray(subs)) return [];
      const parts = (raw || "").split(",").map((v) => v.trim());
            return subs.map(sub => {
      const safe = (idx) => {
                const hydrated = inheritSubskillFromMain(sub, meta);
        const val = parts[idx] || "";
                if (Array.isArray(hydrated.subs)) {
        return val && val !== "-" ? val : "";
                    hydrated.subs = inheritSubskillTree(hydrated.subs, meta);
      };
                }
      return {
                return hydrated;
        powerpve: safe(0),
            });
        powerpvp: safe(1),
        }
        energy: safe(2),
        cooldown: safe(3),
      };
    }


        function collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet) {
    function hasText(value) {
            if (!Array.isArray(subs)) return;
      return typeof value === "string"
            subs.forEach(sub => {
         ? value.trim() !== ""
                const iconURL = normalizeFileURL(sub.icon || 'Nada.png', '');
        : value !== undefined && value !== null;
                if (iconURL && iconURL !== '') iconsSet.add(iconURL);
    }
                // Vídeo normal
                if (sub.video && sub.video.trim() !== '' && sub.video !== 'Nada.png' && !sub.video.toLowerCase().includes('nada.png')) {
                    const videoURL = normalizeFileURL(sub.video);
                    if (videoURL) videosSet.add(videoURL);
                }
                // Vídeo de weapon
                if (sub.weapon && typeof sub.weapon === 'object' && sub.weapon.video && sub.weapon.video.trim() !== '' && sub.weapon.video !== 'Nada.png' && !sub.weapon.video.toLowerCase().includes('nada.png')) {
                    const weaponVideoURL = normalizeFileURL(sub.weapon.video);
                    if (weaponVideoURL) videosSet.add(weaponVideoURL);
                }
                if (Array.isArray(sub.flags)) {
                    sub.flags.forEach(flagKey => {
                        const url = getFlagIconURL(flagKey);
                        if (url) flagsSet.add(url);
                    });
                } if (Array.isArray(sub.subs)) {
                    collectAssetsFromSubs(sub.subs, iconsSet, videosSet, flagsSet);
                }
            });
         } function buildAssetManifest() {
            if (window.__skillAssetManifest && window.__skillAssetManifest.ready) {
                return window.__skillAssetManifest;
            } const iconsSet = new Set();
            const videosSet = new Set();
            const flagsSet = new Set();
            iconItems.forEach(el => {
                const img = el.querySelector('img');
                if (img && img.src) {
                    iconsSet.add(img.src);
                } else if (el.dataset.iconFile) {
                    const iconURL = normalizeFileURL(el.dataset.iconFile);
                    if (iconURL) iconsSet.add(iconURL);
                }
                // Vídeo normal da skill
                const videoRaw = (el.dataset.videoFile || el.dataset.video || '').trim();
                if (videoRaw && videoRaw !== 'Nada.png' && !videoRaw.toLowerCase().includes('nada.png')) {
                    const videoURL = normalizeFileURL(videoRaw);
                    if (videoURL) videosSet.add(videoURL);
                }
                // Vídeo de weapon da skill
                if (el.dataset.weapon) {
                    try {
                        const weaponData = JSON.parse(el.dataset.weapon);
                        if (weaponData && weaponData.video && weaponData.video.trim() !== '' && weaponData.video !== 'Nada.png' && !weaponData.video.toLowerCase().includes('nada.png')) {
                            const weaponVideoURL = normalizeFileURL(weaponData.video);
                            if (weaponVideoURL) videosSet.add(weaponVideoURL);
                        }
                    } catch (e) {
                    }
                }
                if (el.dataset.flags) {
                    try {
                        const parsedFlags = JSON.parse(el.dataset.flags);
                        (parsedFlags || []).forEach(flagKey => {
                            const url = getFlagIconURL(flagKey);
                            if (url) flagsSet.add(url);
                        });
                    } catch (e) {
                    }
                } if (el.dataset.subs) {
                    try {
                        const subs = JSON.parse(el.dataset.subs);
                        collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet);
                    } catch (e) {
                    }
                }
            });
            Object.keys(FLAG_ICON_FILES).forEach(flagKey => {
                const url = getFlagIconURL(flagKey);
                if (url) flagsSet.add(url);
            });
            const manifest = {
                icons: iconsSet, videos: videosSet, flags: flagsSet, ready: true
            };
            window.__skillAssetManifest = manifest;
            return manifest;
        } const subskillVideosCache = new Map();
        window.__subskillVideosCache = subskillVideosCache;


        // Cache de vídeos que falharam (404) para evitar tentativas repetidas
    function pickFilled(current, fallback) {
        const failedVideosCache = new Set();
      if (current === 0 || current === "0") return current;
        const missingVideosReported = new Set(); // Para avisar apenas uma vez sobre vídeos faltantes
      if (!hasText(current)) return fallback;
      return current;
    }


         // Cache de parsing JSON para evitar re-parsear os mesmos dados
    function buildMainSkillsMeta(nodes) {
         const jsonParseCache = new WeakMap();
      if (mainSkillsMeta.ready) {
         function getCachedJSON(el, key) {
         return mainSkillsMeta;
            if (!el) return null;
      }
            const cache = jsonParseCache.get(el) || {};
      (nodes || []).forEach((icon) => {
            if (cache[key] !== undefined) return cache[key];
         const index = (icon.dataset.index || "").trim();
            const raw = el.dataset[key] || el.getAttribute(`data-${key}`);
         if (!index) return;
            if (!raw) {
        const name = (icon.dataset.nome || icon.dataset.name || "").trim();
                cache[key] = null;
        const attrs = parseAttrString(icon.dataset.atr || "");
                jsonParseCache.set(el, cache);
        let iconFile = (icon.dataset.iconFile || "").trim();
                return null;
        if (!iconFile) {
            }
          const imgSrc = icon.querySelector("img")?.src || "";
            try {
          const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
                const parsed = JSON.parse(raw);
          iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : "";
                cache[key] = parsed;
        }
                jsonParseCache.set(el, cache);
        let videoFile = (icon.dataset.videoFile || "").trim();
                return parsed;
        if (!videoFile) {
            } catch (e) {
          videoFile = extractFileNameFromURL(icon.dataset.video || "");
                cache[key] = null;
        }
                jsonParseCache.set(el, cache);
        const meta = {
                return null;
          index,
            }
          name,
          icon: iconFile || "",
          level: icon.dataset.level || "",
          video: videoFile || "",
          powerpve: attrs.powerpve || "",
          powerpvp: attrs.powerpvp || "",
          energy: attrs.energy || "",
          cooldown: attrs.cooldown || "",
          desc: icon.dataset.desc || "",
          descPt: icon.dataset.descPt || "",
          descEn: icon.dataset.descEn || "",
          descEs: icon.dataset.descEs || "",
          descPl: icon.dataset.descPl || "",
        };
        mainSkillsMeta.byIndex.set(index, meta);
        mainSkillsMeta.byIndex.set(parseInt(index, 10), meta);
        if (name) {
          mainSkillsMeta.byName.set(name, meta);
         }
         }
      });
      mainSkillsMeta.ready = true;
      return mainSkillsMeta;
    }


        let assetManifest = null;
    function inheritSubskillFromMain(sub, meta) {
        const skillsTab = $('#skills');
      if (!sub || !meta) return sub;
        const skinsTab = $('#skins');
      // Verifica se herança está desabilitada
         ensureRemoved('.top-rail');
      const shouldInherit = !(
         // NÃO remove .content-card aqui - será gerenciado abaixo
         sub.inherit === false ||
         // ensureRemoved('.content-card'); // REMOVIDO - pode estar removendo skills-container
         sub.inherit === "no" ||
        ensureRemoved('.video-placeholder');
         sub.inherit === "false"
        Array.from(document.querySelectorAll('.card-skins-title, .card-skins .card-skins-title, .cardskins-title, .rail-title')).forEach(t => {
      );
            if ((t.textContent || '').trim().toLowerCase().includes('skins')) {
 
                t.remove();
      // Suporta refS (novo) e refM (legado)
            }
      const refS = ((sub.refS || sub.S || sub.s || "") + "").trim();
        });
      const refIndex = ((sub.refM || sub.M || sub.m || "") + "").trim();
        if (skillsTab) {
      let name = (sub.name || sub.n || "").trim();
            const iconBar = skillsTab.querySelector('.icon-bar');
      let main = null;
            if (iconBar) {
 
                const rail = document.createElement('div');
      // Se herança está desabilitada, não busca a skill principal
                rail.className = 'top-rail skills';
      if (!shouldInherit) {
                // Criar wrapper de scroll para permitir glow sem clipping
        return { ...sub };
                if (!iconBar.parentElement || !iconBar.parentElement.classList.contains('icon-scroll-x')) {
      }
                    const scrollWrapper = document.createElement('div');
                    scrollWrapper.className = 'icon-scroll-x';
                    scrollWrapper.appendChild(iconBar);
                    rail.appendChild(scrollWrapper);
                } else {
                    rail.appendChild(iconBar.parentElement);
                }
                skillsTab.prepend(rail);
            }


            // Busca skills-container criado pelo Lua
      // Primeiro tenta por refS
            const skillsContainer = skillsTab.querySelector('.skills-container');
      if (refS) {
        main = meta.byIndex.get(refS) || meta.byIndex.get(parseInt(refS, 10));
      }
      // Depois por refM
      if (!main && refIndex) {
        main =
          meta.byIndex.get(refIndex) ||
          meta.byIndex.get(parseInt(refIndex, 10));
      }
      // Por último pelo nome
      if (!main && name) {
        main = meta.byName.get(name);
      }
      if (!main) {
        return sub;
      }


            // Busca skills-details e video-container (podem estar dentro de skills-container ou soltos)
      const hydrated = { ...sub };
            let details = skillsTab.querySelector('.skills-details');
      if (!name && main.name) {
            if (!details && skillsContainer) {
        name = main.name;
                details = skillsContainer.querySelector('.skills-details');
      }
            }
      hydrated.name = name || hydrated.name || main.name || "";
            if (!details) {
      hydrated.icon = pickFilled(hydrated.icon, main.icon || "");
                details = document.createElement('div');
      hydrated.level = pickFilled(hydrated.level, main.level || "");
                details.className = 'skills-details';
            }


            // Busca ou cria desc-box dentro de details
      // Vídeo NUNCA é herdado da skill principal
            let descBoxEl = details.querySelector('.desc-box');
      const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
            if (!descBoxEl) {
      hydrated.video = hasOwnVideo ? sub.video || "" : "";
                descBoxEl = document.createElement('div');
                descBoxEl.className = 'desc-box';
                details.appendChild(descBoxEl);
            }


            // Busca video-container
      hydrated.powerpve = pickFilled(hydrated.powerpve, main.powerpve || "");
            let videoContainer = skillsTab.querySelector('.video-container');
      hydrated.powerpvp = pickFilled(hydrated.powerpvp, main.powerpvp || "");
            if (!videoContainer && skillsContainer) {
      hydrated.energy = pickFilled(hydrated.energy, main.energy || "");
                videoContainer = skillsContainer.querySelector('.video-container');
      hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || "");
            }
            if (!videoContainer) {
                videoContainer = document.createElement('div');
                videoContainer.className = 'video-container';
            }


            // Cria ou atualiza content-card skills-grid (estrutura esperada pelo CSS)
      // Descrição: sempre vem da subskill, nunca herda
            let card = skillsTab.querySelector('.content-card.skills-grid');
      // PROTEÇÃO TOTAL: Remove qualquer descrição que possa ter sido copiada do main
            if (!card) {
      if (
                card = document.createElement('div');
        !sub.desc &&
                card.className = 'content-card skills-grid';
        !sub.descPt &&
                skillsTab.appendChild(card);
        !sub.descEn &&
            }
        !sub.descEs &&
        !sub.descPl &&
        !sub.desc_i18n
      ) {
        hydrated.desc = undefined;
        hydrated.descPt = undefined;
        hydrated.descEn = undefined;
        hydrated.descEs = undefined;
        hydrated.descPl = undefined;
        hydrated.desc_i18n = undefined;
      } else {
        // Se subskill tem descrição, normaliza para desc_i18n
        if (
          !hydrated.desc_i18n &&
          (hydrated.descPt ||
            hydrated.descEn ||
            hydrated.descEs ||
            hydrated.descPl)
        ) {
          hydrated.desc_i18n = {
            pt: hydrated.descPt || "",
            en: hydrated.descEn || "",
            es: hydrated.descEs || "",
            pl: hydrated.descPl || "",
          };
        }
      }


            // Move details e videoContainer para dentro do card (estrutura correta)
      return hydrated;
            // Remove skills-container se existir (não é necessário para o layout)
    }
            if (skillsContainer && skillsContainer.parentNode) {
                // Move os elementos filhos de skills-container para o card
                if (details.parentNode === skillsContainer) {
                    skillsContainer.removeChild(details);
                }
                if (videoContainer.parentNode === skillsContainer) {
                    skillsContainer.removeChild(videoContainer);
                }
                // Remove skills-container se estiver vazio
                if (skillsContainer.children.length === 0) {
                    skillsContainer.remove();
                }
            }


            // Garante que details e videoContainer estão no card na ordem correta
    function inheritSubskillTree(subs, meta) {
            // skills-details deve vir ANTES de video-container para o grid funcionar
      if (!Array.isArray(subs)) return [];
            if (details.parentNode !== card) {
      return subs.map((sub) => {
                // Se videoContainer já está no card, insere details antes dele
        const hydrated = inheritSubskillFromMain(sub, meta);
                if (videoContainer.parentNode === card) {
        if (Array.isArray(hydrated.subs)) {
                    card.insertBefore(details, videoContainer);
          hydrated.subs = inheritSubskillTree(hydrated.subs, meta);
                } else {
        }
                    card.appendChild(details);
        return hydrated;
                }
      });
            }
    }
            if (videoContainer.parentNode !== card) {
                card.appendChild(videoContainer);
            }


            // Garante ordem: details primeiro, videoContainer depois
    function collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet) {
            if (details.parentNode === card && videoContainer.parentNode === card) {
      if (!Array.isArray(subs)) return;
                if (details.nextSibling !== videoContainer) {
      subs.forEach((sub) => {
                    card.insertBefore(details, videoContainer);
        const iconURL = normalizeFileURL(sub.icon || "", "");
                }
        if (iconURL && iconURL !== "") iconsSet.add(iconURL);
        // Vídeo normal
        if (sub.video && sub.video.trim() !== "") {
          const videoURL = normalizeFileURL(sub.video);
          if (videoURL) videosSet.add(videoURL);
        }
        // Vídeo de weapon
        if (
          sub.weapon &&
          typeof sub.weapon === "object" &&
          sub.weapon.video &&
          sub.weapon.video.trim() !== ""
        ) {
          const weaponVideoURL = normalizeFileURL(sub.weapon.video);
          if (weaponVideoURL) videosSet.add(weaponVideoURL);
        }
        if (Array.isArray(sub.flags)) {
          sub.flags.forEach((flagKey) => {
            const url = getFlagIconURL(flagKey);
            if (url) flagsSet.add(url);
          });
        }
        if (Array.isArray(sub.subs)) {
          collectAssetsFromSubs(sub.subs, iconsSet, videosSet, flagsSet);
        }
      });
    }
    function buildAssetManifest() {
      if (window.__skillAssetManifest && window.__skillAssetManifest.ready) {
        return window.__skillAssetManifest;
      }
      const iconsSet = new Set();
      const videosSet = new Set();
      const flagsSet = new Set();
      iconItems.forEach((el) => {
        const img = el.querySelector("img");
        if (img && img.src) {
          iconsSet.add(img.src);
        } else if (el.dataset.iconFile) {
          const iconURL = normalizeFileURL(el.dataset.iconFile);
          if (iconURL) iconsSet.add(iconURL);
        }
        // Vídeo normal da skill
        const videoRaw = (
          el.dataset.videoFile ||
          el.dataset.video ||
          ""
        ).trim();
        if (videoRaw) {
          const videoURL = normalizeFileURL(videoRaw);
          if (videoURL) videosSet.add(videoURL);
        }
        // Vídeo de weapon da skill
        if (el.dataset.weapon) {
          try {
            const weaponData = JSON.parse(el.dataset.weapon);
            if (
              weaponData &&
              weaponData.video &&
              weaponData.video.trim() !== ""
            ) {
              const weaponVideoURL = normalizeFileURL(weaponData.video);
              if (weaponVideoURL) videosSet.add(weaponVideoURL);
             }
             }
          } catch (e) { }
         }
         }
         // Função para obter iconsBar de forma segura (com retry)
         if (el.dataset.flags) {
        function getIconsBar() {
          try {
             const skillsEl = document.getElementById('skills');
             const parsedFlags = JSON.parse(el.dataset.flags);
             if (!skillsEl) return null;
             (parsedFlags || []).forEach((flagKey) => {
            return skillsEl.querySelector('.icon-bar');
              const url = getFlagIconURL(flagKey);
        }
              if (url) flagsSet.add(url);
        const iconsBar = getIconsBar();
        const skillsTopRail = iconsBar ? iconsBar.closest('.top-rail.skills') : null;
        const iconItems = iconsBar ? Array.from(iconsBar.querySelectorAll('.skill-icon')) : [];
        buildMainSkillsMeta(iconItems);
        // Verifica se há weapon em skills principais OU em subskills
        function checkHasAnyWeapon() {
            // Verifica skills principais
            const hasMainWeapon = iconItems.some(el => {
                const weapon = el.dataset.weapon;
                return weapon && weapon.trim() !== '' && weapon !== '{}';
             });
             });
            if (hasMainWeapon) {
          } catch (e) { }
                return true;
            }
            // Verifica subskills
            for (const el of iconItems) {
                const subsRaw = el.getAttribute('data-subs');
                if (!subsRaw) continue;
                try {
                    const subs = JSON.parse(subsRaw);
                    if (Array.isArray(subs) && subs.some(s => s && s.weapon && typeof s.weapon === 'object' && Object.keys(s.weapon).length > 0)) {
                        return true;
                    }
                } catch (e) { }
            }
            return false;
         }
         }
         const hasWeaponSkillAvailable = checkHasAnyWeapon();
         if (el.dataset.subs) {
        let weaponToggleBtn = null;
          try {
        if (!assetManifest) {
            const subs = JSON.parse(el.dataset.subs);
            assetManifest = buildAssetManifest();
            collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet);
            // Pré-carrega apenas ícones e flags críticos
          } catch (e) { }
            if (assetManifest.icons && assetManifest.icons.size) {
                assetManifest.icons.forEach(url => {
                    if (!url || imagePreloadCache.has(url)) return;
                    const img = new Image();
                    img.decoding = 'async';
                    img.loading = 'eager';
                    img.referrerPolicy = 'same-origin';
                    img.src = url;
                    imagePreloadCache.set(url, img);
                });
            }
            if (assetManifest.flags && assetManifest.flags.size) {
                assetManifest.flags.forEach(url => {
                    if (!url || imagePreloadCache.has(url)) return;
                    const img = new Image();
                    img.decoding = 'async';
                    img.loading = 'eager';
                    img.referrerPolicy = 'same-origin';
                    img.src = url;
                    imagePreloadCache.set(url, img);
                });
            }
         }
         }
        // Busca descBox e videoBox após a estrutura estar organizada
      });
         const descBox = $('#skills') ? $('.desc-box', $('#skills')) : null;
      Object.keys(FLAG_ICON_FILES).forEach((flagKey) => {
        const videoBox = $('#skills') ? $('.video-container', $('#skills')) : null;
         const url = getFlagIconURL(flagKey);
        const videosCache = new Map();
        if (url) flagsSet.add(url);
        const nestedVideoElByIcon = new WeakMap();
      });
        const barStack = [];
      const manifest = {
        window.__barStack = barStack;
        icons: iconsSet,
         let initialBarSnapshot = null;
        videos: videosSet,
         let totalVideos = 0, loadedVideos = 0, autoplay = false;
        flags: flagsSet,
         window.__lastActiveSkillIcon = null;
        ready: true,
         let userHasInteracted = false;
      };
        let globalWeaponEnabled = false;
      window.__skillAssetManifest = manifest;
         try {
      return manifest;
            if (localStorage.getItem('glaWeaponEnabled') === '1') {
    }
                globalWeaponEnabled = true;
    const subskillVideosCache = new Map();
            }
    window.__subskillVideosCache = subskillVideosCache;
         } catch (err) {
 
    // Cache de vídeos que falharam (404) para evitar tentativas repetidas
    const failedVideosCache = new Set();
    const missingVideosReported = new Set(); // Para avisar apenas uma vez sobre vídeos faltantes
 
    // Cache de parsing JSON para evitar re-parsear os mesmos dados
    const jsonParseCache = new WeakMap();
    function getCachedJSON(el, key) {
      if (!el) return null;
      const cache = jsonParseCache.get(el) || {};
      if (cache[key] !== undefined) return cache[key];
      const raw = el.dataset[key] || el.getAttribute(`data-${key}`);
      if (!raw) {
         cache[key] = null;
         jsonParseCache.set(el, cache);
        return null;
      }
      try {
        const parsed = JSON.parse(raw);
        cache[key] = parsed;
         jsonParseCache.set(el, cache);
        return parsed;
      } catch (e) {
        cache[key] = null;
         jsonParseCache.set(el, cache);
        return null;
      }
    }
 
    let assetManifest = null;
    const skillsTab = $("#skills");
    const skinsTab = $("#skins");
    ensureRemoved(".top-rail");
    // NÃO remove .content-card aqui - será gerenciado abaixo
    // ensureRemoved('.content-card'); // REMOVIDO - pode estar removendo skills-container
    ensureRemoved(".video-placeholder");
    Array.from(
      document.querySelectorAll(
         ".card-skins-title, .card-skins .card-skins-title, .cardskins-title, .rail-title"
      )
    ).forEach((t) => {
      if ((t.textContent || "").trim().toLowerCase().includes("skins")) {
        t.remove();
      }
    });
    if (skillsTab) {
      const iconBar = skillsTab.querySelector(".icon-bar");
      if (iconBar) {
        const rail = document.createElement("div");
        rail.className = "top-rail skills";
        // Criar wrapper de scroll para permitir glow sem clipping
        if (
          !iconBar.parentElement ||
          !iconBar.parentElement.classList.contains("icon-scroll-x")
        ) {
          const scrollWrapper = document.createElement("div");
          scrollWrapper.className = "icon-scroll-x";
          scrollWrapper.appendChild(iconBar);
          rail.appendChild(scrollWrapper);
         } else {
          rail.appendChild(iconBar.parentElement);
         }
         }
         const weaponStateListeners = new Set();
         skillsTab.prepend(rail);
         let showWeaponPopupFn = null;
      }
         let popupShouldOpen = false;
 
         function attachWeaponPopupFn(fn) {
      // Busca skills-container criado pelo Lua
            if (typeof fn !== 'function') return;
      const skillsContainer = skillsTab.querySelector(".skills-container");
            showWeaponPopupFn = fn;
 
            if (popupShouldOpen) {
      // Busca skills-details e video-container (podem estar dentro de skills-container ou soltos)
                popupShouldOpen = false;
      let details = skillsTab.querySelector(".skills-details");
                try {
      if (!details && skillsContainer) {
                    showWeaponPopupFn();
         details = skillsContainer.querySelector(".skills-details");
                } catch (err) {
      }
                }
      if (!details) {
            }
         details = document.createElement("div");
         details.className = "skills-details";
      }
 
      // Busca ou cria desc-box dentro de details
      let descBoxEl = details.querySelector(".desc-box");
      if (!descBoxEl) {
        descBoxEl = document.createElement("div");
        descBoxEl.className = "desc-box";
        details.appendChild(descBoxEl);
      }
 
      // Busca video-container
      let videoContainer = skillsTab.querySelector(".video-container");
      if (!videoContainer && skillsContainer) {
        videoContainer = skillsContainer.querySelector(".video-container");
      }
      if (!videoContainer) {
        videoContainer = document.createElement("div");
        videoContainer.className = "video-container";
      }
 
      // Cria ou atualiza content-card skills-grid (estrutura esperada pelo CSS)
      let card = skillsTab.querySelector(".content-card.skills-grid");
      if (!card) {
        card = document.createElement("div");
        card.className = "content-card skills-grid";
        skillsTab.appendChild(card);
      }
 
      // Move details e videoContainer para dentro do card (estrutura correta)
      // Remove skills-container se existir (não é necessário para o layout)
      if (skillsContainer && skillsContainer.parentNode) {
        // Move os elementos filhos de skills-container para o card
        if (details.parentNode === skillsContainer) {
          skillsContainer.removeChild(details);
         }
         }
         attachWeaponPopupFn(window.__glaWeaponShowPopup);
         if (videoContainer.parentNode === skillsContainer) {
        function requestWeaponPopupDisplay() {
          skillsContainer.removeChild(videoContainer);
            try {
                if (localStorage.getItem('glaWeaponPopupDismissed') === '1') return;
            } catch (err) {
            }
            if (typeof showWeaponPopupFn === 'function') {
                showWeaponPopupFn();
                return;
            }
            popupShouldOpen = true;
         }
         }
         function onWeaponStateChange(fn) {
         // Remove skills-container se estiver vazio
            if (typeof fn !== 'function') return;
        if (skillsContainer.children.length === 0) {
            weaponStateListeners.add(fn);
          skillsContainer.remove();
         }
         }
        function syncWeaponButtonState(enabled) {
      }
            if (!weaponToggleBtn || !weaponToggleBtn.isConnected) return;
 
            // Usa .weapon-active em vez de .active para não conflitar com skills
      // Garante que details e videoContainer estão no card na ordem correta
            weaponToggleBtn.classList.toggle('weapon-active', !!enabled);
      // skills-details deve vir ANTES de video-container para o grid funcionar
            weaponToggleBtn.classList.remove('active'); // Garante que .active nunca seja aplicado
      if (details.parentNode !== card) {
            weaponToggleBtn.setAttribute('aria-pressed', enabled ? 'true' : 'false');
        // Se videoContainer já está no card, insere details antes dele
            weaponToggleBtn.setAttribute('aria-label', enabled ? 'Desativar Arma Especial' : 'Ativar Arma Especial');
        if (videoContainer.parentNode === card) {
          card.insertBefore(details, videoContainer);
        } else {
          card.appendChild(details);
         }
         }
         function syncWeaponRailState(enabled) {
      }
            if (skillsTopRail) {
      if (videoContainer.parentNode !== card) {
                skillsTopRail.classList.toggle('weapon-mode-on', !!enabled);
         card.appendChild(videoContainer);
            }
      }
 
      // Garante ordem: details primeiro, videoContainer depois
      if (details.parentNode === card && videoContainer.parentNode === card) {
        if (details.nextSibling !== videoContainer) {
          card.insertBefore(details, videoContainer);
         }
         }
         function notifyWeaponStateListeners(enabled) {
      }
            weaponStateListeners.forEach(listener => {
    }
                try {
    // Função para obter iconsBar de forma segura (com retry)
                    listener(enabled);
    function getIconsBar() {
                } catch (err) {
      const skillsEl = document.getElementById("skills");
      if (!skillsEl) return null;
      return skillsEl.querySelector(".icon-bar");
    }
    const iconsBar = getIconsBar();
    const skillsTopRail = iconsBar
      ? iconsBar.closest(".top-rail.skills")
      : null;
    const iconItems = iconsBar
      ? Array.from(iconsBar.querySelectorAll(".skill-icon"))
      : [];
    buildMainSkillsMeta(iconItems);
    // Verifica se há weapon em skills principais OU em subskills
    function checkHasAnyWeapon() {
      // Verifica skills principais
      const hasMainWeapon = iconItems.some((el) => {
        const weapon = el.dataset.weapon;
        return weapon && weapon.trim() !== "" && weapon !== "{}";
      });
      if (hasMainWeapon) {
        return true;
      }
      // Verifica subskills
      for (const el of iconItems) {
        const subsRaw = el.getAttribute("data-subs");
        if (!subsRaw) continue;
        try {
          const subs = JSON.parse(subsRaw);
          if (
            Array.isArray(subs) &&
            subs.some(
              (s) =>
                s &&
                s.weapon &&
                typeof s.weapon === "object" &&
                Object.keys(s.weapon).length > 0
            )
          ) {
            return true;
          }
        } catch (e) { }
      }
      return false;
    }
    const hasWeaponSkillAvailable = checkHasAnyWeapon();
    let weaponToggleBtn = null;
    if (!assetManifest) {
      assetManifest = buildAssetManifest();
      // Pré-carrega apenas ícones e flags críticos
      if (assetManifest.icons && assetManifest.icons.size) {
        assetManifest.icons.forEach((url) => {
          if (!url || imagePreloadCache.has(url)) return;
          const img = new Image();
          img.decoding = "async";
          img.loading = "eager";
          img.referrerPolicy = "same-origin";
          img.src = url;
          imagePreloadCache.set(url, img);
        });
      }
      if (assetManifest.flags && assetManifest.flags.size) {
        assetManifest.flags.forEach((url) => {
          if (!url || imagePreloadCache.has(url)) return;
          const img = new Image();
          img.decoding = "async";
          img.loading = "eager";
          img.referrerPolicy = "same-origin";
          img.src = url;
          imagePreloadCache.set(url, img);
        });
      }
    }
    // Busca descBox e videoBox após a estrutura estar organizada
    const descBox = $("#skills") ? $(".desc-box", $("#skills")) : null;
    const videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
    const videosCache = new Map();
    const nestedVideoElByIcon = new WeakMap();
    const barStack = [];
    window.__barStack = barStack;
    let initialBarSnapshot = null;
    let totalVideos = 0,
      loadedVideos = 0,
      autoplay = false;
    window.__lastActiveSkillIcon = null;
    let userHasInteracted = false;
    let globalWeaponEnabled = false;
    try {
      if (localStorage.getItem("glaWeaponEnabled") === "1") {
        globalWeaponEnabled = true;
      }
    } catch (err) { }
    const weaponStateListeners = new Set();
    let showWeaponPopupFn = null;
    let popupShouldOpen = false;
    function attachWeaponPopupFn(fn) {
      if (typeof fn !== "function") return;
      showWeaponPopupFn = fn;
      if (popupShouldOpen) {
        popupShouldOpen = false;
        try {
          showWeaponPopupFn();
        } catch (err) { }
      }
    }
    attachWeaponPopupFn(window.__glaWeaponShowPopup);
    function requestWeaponPopupDisplay() {
      try {
        if (localStorage.getItem("glaWeaponPopupDismissed") === "1") return;
      } catch (err) { }
      if (typeof showWeaponPopupFn === "function") {
        showWeaponPopupFn();
        return;
      }
      popupShouldOpen = true;
    }
    function onWeaponStateChange(fn) {
      if (typeof fn !== "function") return;
      weaponStateListeners.add(fn);
    }
    function syncWeaponButtonState(enabled) {
      if (!weaponToggleBtn || !weaponToggleBtn.isConnected) return;
      // Usa .weapon-active em vez de .active para não conflitar com skills
      weaponToggleBtn.classList.toggle("weapon-active", !!enabled);
      weaponToggleBtn.classList.remove("active"); // Garante que .active nunca seja aplicado
      weaponToggleBtn.setAttribute("aria-pressed", enabled ? "true" : "false");
      weaponToggleBtn.setAttribute(
        "aria-label",
         enabled ? "Desativar Arma Especial" : "Ativar Arma Especial"
      );
    }
    function syncWeaponRailState(enabled) {
      if (skillsTopRail) {
        skillsTopRail.classList.toggle("weapon-mode-on", !!enabled);
      }
    }
    function notifyWeaponStateListeners(enabled) {
      weaponStateListeners.forEach((listener) => {
        try {
          listener(enabled);
        } catch (err) { }
      });
    }
    let pendingWeaponState = null;
    window.addEventListener("weapon:ready", (ev) => {
      if (ev && ev.detail && ev.detail.showPopup) {
        attachWeaponPopupFn(ev.detail.showPopup);
      }
      if (pendingWeaponState === null) return;
      if (typeof window.__applyWeaponState === "function") {
        const target = pendingWeaponState;
        pendingWeaponState = null;
        window.__applyWeaponState(target);
      }
    });
    window.__setGlobalWeaponEnabled = (enabled) => {
      globalWeaponEnabled = enabled;
      notifyWeaponStateListeners(enabled);
    };
    function requestWeaponState(targetState) {
      if (typeof window.__applyWeaponState === "function") {
        pendingWeaponState = null;
        window.__applyWeaponState(targetState);
        return;
      }
      pendingWeaponState = targetState;
    }
    onWeaponStateChange(syncWeaponButtonState);
    function reapplyWeaponClassesToBar() {
      // SISTEMA UNIFICADO: Aplica em skills E subskills (sempre, não só quando weapon está ativo)
      // Obtém iconsBar dinamicamente (pode não estar pronto ainda quando há flags)
      const currentIconsBar = getIconsBar();
      if (!currentIconsBar) {
        return;
      }
      // Busca em skills principais (dentro de iconsBar)
      currentIconsBar
        .querySelectorAll(".skill-icon[data-weapon]")
        .forEach((el) => {
          const weapon = el.dataset.weapon;
          if (weapon && weapon.trim() !== "" && weapon !== "{}") {
            try {
              const weaponObj = JSON.parse(weapon);
              if (
                weaponObj &&
                typeof weaponObj === "object" &&
                Object.keys(weaponObj).length > 0
              ) {
                if (!el.classList.contains("has-weapon-available")) {
                  el.classList.add("has-weapon-available");
                }
                if (!el.querySelector(".weapon-indicator")) {
                  const ind = document.createElement("div");
                  ind.className = "weapon-indicator";
                  el.appendChild(ind);
                 }
                 }
            });
              }
        }
            } catch (e) {
        let pendingWeaponState = null;
              // Se não for JSON válido, não adiciona a classe
        window.addEventListener('weapon:ready', (ev) => {
            if (ev && ev.detail && ev.detail.showPopup) {
                attachWeaponPopupFn(ev.detail.showPopup);
             }
             }
            if (pendingWeaponState === null) return;
          }
            if (typeof window.__applyWeaponState === 'function') {
        });
                const target = pendingWeaponState;
      // Busca em subskills (dentro de subskills-rail)
                 pendingWeaponState = null;
      document
                 window.__applyWeaponState(target);
        .querySelectorAll(".subskills-rail .subicon[data-weapon]")
        .forEach((el) => {
          const weapon = el.dataset.weapon;
          if (weapon && weapon.trim() !== "" && weapon !== "{}") {
            try {
              const weaponObj = JSON.parse(weapon);
              if (
                weaponObj &&
                 typeof weaponObj === "object" &&
                Object.keys(weaponObj).length > 0
              ) {
                 if (!el.classList.contains("has-weapon-available")) {
                  el.classList.add("has-weapon-available");
                }
              }
            } catch (e) {
              // Se não for JSON válido, não adiciona a classe
             }
             }
          }
         });
         });
        window.__setGlobalWeaponEnabled = (enabled) => {
    }
            globalWeaponEnabled = enabled;
    // Aplica classes de weapon imediatamente ao carregar
            notifyWeaponStateListeners(enabled);
    reapplyWeaponClassesToBar();
        };
    // REMOVIDO: setupWeaponBarToggle - O toggle de weapon agora é criado apenas pelo C.WeaponToggle.html no header
        function requestWeaponState(targetState) {
    // Não cria mais botão na barra de skills - apenas aplica classes visuais
            if (typeof window.__applyWeaponState === 'function') {
    onWeaponStateChange(syncWeaponRailState);
                pendingWeaponState = null;
    // Reaplica classes quando o estado do weapon muda (para garantir que funcione mesmo quando toggle é ativado fora da barra)
                window.__applyWeaponState(targetState);
    onWeaponStateChange(() => {
                return;
      // Usa setTimeout para garantir que o DOM foi atualizado
            }
      setTimeout(() => {
            pendingWeaponState = targetState;
        }
        onWeaponStateChange(syncWeaponButtonState);
        function reapplyWeaponClassesToBar() {
            // SISTEMA UNIFICADO: Aplica em skills E subskills (sempre, não só quando weapon está ativo)
            // Obtém iconsBar dinamicamente (pode não estar pronto ainda quando há flags)
            const currentIconsBar = getIconsBar();
            if (!currentIconsBar) {
                return;
            }
            // Busca em skills principais (dentro de iconsBar)
            currentIconsBar.querySelectorAll('.skill-icon[data-weapon]').forEach(el => {
                const weapon = el.dataset.weapon;
                if (weapon && weapon.trim() !== '' && weapon !== '{}') {
                    try {
                        const weaponObj = JSON.parse(weapon);
                        if (weaponObj && typeof weaponObj === 'object' && Object.keys(weaponObj).length > 0) {
                            if (!el.classList.contains('has-weapon-available')) {
                                el.classList.add('has-weapon-available');
                            }
                            if (!el.querySelector('.weapon-indicator')) {
                                const ind = document.createElement('div');
                                ind.className = 'weapon-indicator';
                                el.appendChild(ind);
                            }
                        }
                    } catch (e) {
                        // Se não for JSON válido, não adiciona a classe
                    }
                }
            });
            // Busca em subskills (dentro de subskills-rail)
            document.querySelectorAll('.subskills-rail .subicon[data-weapon]').forEach(el => {
                const weapon = el.dataset.weapon;
                if (weapon && weapon.trim() !== '' && weapon !== '{}') {
                    try {
                        const weaponObj = JSON.parse(weapon);
                        if (weaponObj && typeof weaponObj === 'object' && Object.keys(weaponObj).length > 0) {
                            if (!el.classList.contains('has-weapon-available')) {
                                el.classList.add('has-weapon-available');
                            }
                        }
                    } catch (e) {
                        // Se não for JSON válido, não adiciona a classe
                    }
                }
            });
        }
        // Aplica classes de weapon imediatamente ao carregar
         reapplyWeaponClassesToBar();
         reapplyWeaponClassesToBar();
        // REMOVIDO: setupWeaponBarToggle - O toggle de weapon agora é criado apenas pelo C.WeaponToggle.html no header
      }, 50);
        // Não cria mais botão na barra de skills - apenas aplica classes visuais
    });
        onWeaponStateChange(syncWeaponRailState);
    syncWeaponRailState(globalWeaponEnabled);
        // Reaplica classes quando o estado do weapon muda (para garantir que funcione mesmo quando toggle é ativado fora da barra)
    (function injectWeaponStyles() {
        onWeaponStateChange(() => {
      if (document.getElementById("weapon-toggle-styles")) return;
            // Usa setTimeout para garantir que o DOM foi atualizado
      const style = document.createElement("style");
            setTimeout(() => {
      style.id = "weapon-toggle-styles";
                reapplyWeaponClassesToBar();
      style.textContent = `
            }, 50);
        });
        syncWeaponRailState(globalWeaponEnabled);
        (function injectWeaponStyles() {
            if (document.getElementById('weapon-toggle-styles')) return;
            const style = document.createElement('style');
            style.id = 'weapon-toggle-styles';
            style.textContent = `
         /* ========== ESTILOS DE WEAPON - NOVO SISTEMA ========== */
         /* ========== ESTILOS DE WEAPON - NOVO SISTEMA ========== */
          
          
Linha 1 347: Linha 1 827:
             0%, 100% { opacity: 0.7; }
             0%, 100% { opacity: 0.7; }
             50% { opacity: 1; }
             50% { opacity: 1; }
        }
        /* Animação do gradiente (sem girar a borda) */
        @property --effect-spin {
            syntax: "<angle>";
            inherits: false;
            initial-value: 0deg;
        }
        @keyframes effect-border-spin {
            from { --effect-spin: 0deg; }
            to { --effect-spin: 360deg; }
         }
         }


         /* ===== ÍCONE COM ARMA - Só mostra efeitos quando weapon-mode-on está ativo ===== */
         /* ===== ÍCONE COM ARMA - Só mostra efeitos quando weapon-mode-on está ativo ===== */
         /* Quando NÃO está em weapon-mode-on, os ícones com arma devem ser normais (mesma borda padrão) */
         /* Quando NÃO está em weapon-mode-on, os ícones com arma devem ser normais (mesma borda padrão) */
        .character-box .top-rail.skills .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
            /* Remove borda sólida base que aparece na transição do toggle */
            border-color: transparent !important;
            box-shadow: none !important;
        }
         .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
         .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
             /* Remove transform scale quando toggle está desativado */
             /* Remove transform scale quando toggle está desativado */
Linha 1 398: Linha 1 894:
             /* SEM transição no background/padding/mask para evitar "flash" durante a mudança */
             /* SEM transição no background/padding/mask para evitar "flash" durante a mudança */
             transition: box-shadow 0.15s ease !important;
             transition: box-shadow 0.15s ease !important;
        }
        .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before {
            /* Sem glow amarelo no modo normal para skills com weapon */
            opacity: 0 !important;
            box-shadow: none !important;
            transition: opacity 0.15s ease !important;
         }
         }


Linha 1 468: Linha 1 970:
             opacity: 1 !important;
             opacity: 1 !important;
         }
         }
             `;
 
             document.head.appendChild(style);
        .character-box .top-rail.skills .icon-bar {
         })();
             position: relative;
         function applyWeaponBadge(el, weaponData, equipped) {
        }
             // Apenas gerencia a classe weapon-equipped (badge visual removido)
        .character-box .top-rail.skills .icon-bar .skill-icon {
             if (equipped && weaponData) {
             z-index: 2;
                 el.classList.add('weapon-equipped');
        }
             } else {
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active) {
                 el.classList.remove('weapon-equipped');
            border-color: transparent !important;
            outline: 2px solid rgba(210, 60, 60, 0.95) !important;
            outline-offset: -2px;
            animation: effect-child-outline 1.6s ease-in-out infinite !important;
         }
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
            box-shadow:
                inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                0 0 6px rgba(255, 60, 60, 0.45),
                0 0 10px rgba(120, 20, 20, 0.6) !important;
            animation: effect-child-ring 1.6s ease-in-out infinite !important;
            opacity: 1 !important;
         }
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
            inset: 0 !important;
            border-radius: inherit !important;
            z-index: 1 !important;
            opacity: 1 !important;
            box-shadow:
                0 0 8px rgba(255, 60, 60, 0.35),
                0 0 14px rgba(120, 20, 20, 0.45);
            animation: effect-child-glow 1.6s ease-in-out infinite !important;
        }
        .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
             box-shadow:
                inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                0 0 6px rgba(255, 60, 60, 0.45),
                0 0 10px rgba(120, 20, 20, 0.6) !important;
            animation: effect-child-ring 1.6s ease-in-out infinite !important;
            opacity: 1 !important;
        }
        .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
            inset: 0 !important;
            border-radius: inherit !important;
            z-index: 1 !important;
            opacity: 1 !important;
            box-shadow:
                0 0 8px rgba(255, 60, 60, 0.35),
                0 0 14px rgba(120, 20, 20, 0.45);
            animation: effect-child-glow 1.6s ease-in-out infinite !important;
        }
        @keyframes effect-child-ring {
            0% {
                box-shadow:
                    inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                    0 0 6px rgba(255, 60, 60, 0.45),
                    0 0 10px rgba(120, 20, 20, 0.6);
            }
            50% {
                box-shadow:
                    inset 0 0 0 var(--icon-ring-w) rgba(120, 20, 20, 0.95),
                    0 0 9px rgba(255, 70, 70, 0.65),
                    0 0 14px rgba(20, 0, 0, 0.8);
            }
            100% {
                box-shadow:
                    inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                    0 0 6px rgba(255, 60, 60, 0.45),
                    0 0 10px rgba(120, 20, 20, 0.6);
            }
        }
        @keyframes effect-child-outline {
            0% {
                outline-color: rgba(210, 60, 60, 0.95);
            }
            50% {
                outline-color: rgba(120, 20, 20, 0.95);
            }
             100% {
                outline-color: rgba(210, 60, 60, 0.95);
            }
        }
        @keyframes effect-child-glow {
            0% {
                 box-shadow:
                    0 0 8px rgba(255, 60, 60, 0.35),
                    0 0 14px rgba(120, 20, 20, 0.45);
            }
            50% {
                box-shadow:
                    0 0 12px rgba(255, 70, 70, 0.55),
                    0 0 18px rgba(20, 0, 0, 0.75);
            }
            100% {
                box-shadow:
                    0 0 8px rgba(255, 60, 60, 0.35),
                    0 0 14px rgba(120, 20, 20, 0.45);
            }
        }
        .character-box .top-rail.skills .effect-lines-layer {
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 0;
            overflow: visible;
        }
        .character-box .top-rail.skills .effect-lines-layer .effect-line {
            fill: none;
            stroke-linecap: round;
            stroke-linejoin: round;
        }
        .character-box .top-rail.skills .effect-lines-layer .effect-line-glow {
            stroke: rgba(120, 20, 20, 0.7);
            stroke-width: 5;
            filter: drop-shadow(0 0 6px rgba(255, 60, 60, 0.45));
            animation: none;
        }
        .character-box .top-rail.skills .effect-lines-layer .effect-line-core {
            stroke: rgba(210, 60, 60, 0.95);
            stroke-width: 2.2;
            animation: none;
        }
        .character-box .top-rail.skills .effect-lines-layer .effect-line.effect-line-returning {
            animation: effect-line-return var(--effect-return-duration, 1.1s) ease forwards;
            animation-delay: var(--effect-return-delay, 0s);
        }
        @keyframes effect-line-pulse {
            0% {
                stroke: rgba(210, 60, 60, 0.95);
                stroke-width: 2.1;
            }
            50% {
                stroke: rgba(120, 20, 20, 0.95);
                stroke-width: 2.7;
            }
            100% {
                stroke: rgba(220, 70, 70, 0.95);
                stroke-width: 2.1;
            }
        }
        @keyframes effect-line-glow {
            0% {
                stroke: rgba(140, 25, 25, 0.65);
                filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
             }
            50% {
                 stroke: rgba(20, 0, 0, 0.85);
                filter: drop-shadow(0 0 9px rgba(255, 70, 70, 0.65));
            }
            100% {
                stroke: rgba(140, 25, 25, 0.65);
                filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
            }
        }
        @keyframes effect-line-return {
            0% {
                stroke-dasharray: var(--effect-line-length, 0) var(--effect-line-length, 0);
                stroke-dashoffset: 0;
                opacity: 1;
            }
            100% {
                stroke-dasharray: 0 var(--effect-line-length, 0);
                stroke-dashoffset: calc(var(--effect-line-length, 0) * -1);
                opacity: 0;
             }
             }
         } function getWeaponKey(el) {
         }
             return (el.dataset.index || '') + ':' + (el.dataset.nome || el.dataset.name || '');
        @media (prefers-reduced-motion: reduce) {
        } function isWeaponModeOn() {
             .character-box .top-rail.skills .effect-lines-layer .effect-line {
            try {
                 animation: none;
                 return localStorage.getItem('glaWeaponEnabled') === '1';
            } catch (e) {
                return false;
             }
             }
         } function getWeaponDataForIcon(iconEl) {
         }
            if (!iconEl || !iconEl.dataset.weapon) return null;
 
            // Usa cache de parsing JSON
        .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle).active::before {
            return getCachedJSON(iconEl, 'weapon');
            pointer-events: none !important;
         } function getEffectiveSkillVideoFromIcon(iconEl) {
            inset: 0 !important;
            const weaponOn = globalWeaponEnabled;
            border-radius: inherit !important;
            const weaponData = getWeaponDataForIcon(iconEl);
            z-index: 1 !important;
            const baseVideoFile = (iconEl.dataset.videoFile || '').trim();
            animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
             const baseVideoURL = (iconEl.dataset.video || '').trim();
            box-shadow:
                0 0 10px 0 rgba(220, 220, 220, 0.5),
                0 0 16px 0 rgba(190, 190, 190, 0.4),
                0 0 22px 0 rgba(220, 220, 220, 0.3) !important;
            opacity: 1 !important;
        }
 
        /* ========== ESTILOS DE SWAP DE PERSONAGENS (Sistema Genérico) ========== */
        /* Skills desabilitadas quando o personagem ativo não pode usá-las */
        .character-box .top-rail.skills .icon-bar .skill-icon.disabled-skill {
            opacity: 0.3 !important;
            filter: grayscale(100%) !important;
            cursor: not-allowed !important;
            pointer-events: none !important;
            transition: opacity 0.2s ease, filter 0.2s ease !important;
        }
            `;
      document.head.appendChild(style);
    })();
    function applyWeaponBadge(el, weaponData, equipped) {
      // Apenas gerencia a classe weapon-equipped (badge visual removido)
      if (equipped && weaponData) {
        el.classList.add("weapon-equipped");
      } else {
        el.classList.remove("weapon-equipped");
      }
    }
    function getWeaponKey(el) {
      return (
        (el.dataset.index || "") +
        ":" +
        (el.dataset.nome || el.dataset.name || "")
      );
    }
    function isWeaponModeOn() {
      try {
        return localStorage.getItem("glaWeaponEnabled") === "1";
      } catch (e) {
        return false;
      }
    }
    function getWeaponDataForIcon(iconEl) {
      if (!iconEl || !iconEl.dataset.weapon) return null;
      // Usa cache de parsing JSON
      return getCachedJSON(iconEl, "weapon");
    }
    const effectState = {
      skills: new Set(),
      videos: new Map(),
      expiresAt: 0,
      sourceIcon: null,
      timer: null,
    };
    let effectLinesLayer = null;
    let effectLinesRAF = null;
    let effectLinesCleanupTimer = null;
    let effectLinesScrollWrap = null;
    let effectLinesBound = false;
    let effectLinesLastState = null;
    function getSkillNameFromIcon(iconEl) {
      return (iconEl?.dataset?.nome || iconEl?.dataset?.name || "").trim();
    }
    function getEffectVideoKey(iconEl, videoValue) {
      if (!iconEl) return "";
      const baseIndex = iconEl.dataset.index || "";
      const subName =
        iconEl.dataset.subName ||
        iconEl.dataset.nome ||
        iconEl.dataset.name ||
         "";
      const namePart = baseIndex || subName || "";
      const videoPart = String(videoValue || "").trim();
      if (!namePart || !videoPart) return "";
      return `effect:${namePart}:${videoPart}`;
    }
    function normalizeEffectData(raw) {
      if (!raw || typeof raw !== "object") return null;
      const rawSkills = raw.skills;
      const timeValue = Number(raw.time ?? raw.duration ?? 0);
      const timeMs = Number.isFinite(timeValue) ? timeValue * 1000 : 0;
      if (!timeMs || timeMs <= 0) return null;
      const skills = Array.isArray(rawSkills)
        ? rawSkills.map((s) => String(s || "").trim()).filter(Boolean)
        : [];
      if (!skills.length) return null;
      const videos = new Map();
      if (raw.videos && typeof raw.videos === "object") {
        Object.keys(raw.videos).forEach((k) => {
          const key = String(k || "").trim();
          const val = String(raw.videos[k] || "").trim();
          if (key && val) videos.set(key, val);
        });
      }
      return { skills, timeMs, videos };
    }
    function handleEffectLinesScroll() {
      scheduleEffectLinesUpdate();
    }
    function bindEffectLinesEvents() {
      if (!effectLinesBound) {
        window.addEventListener("resize", scheduleEffectLinesUpdate);
        window.addEventListener("scroll", scheduleEffectLinesUpdate, {
          passive: true,
        });
        effectLinesBound = true;
      }
      const rail = document.querySelector(".top-rail.skills");
      const scrollWrap = rail ? rail.querySelector(".icon-scroll-x") : null;
      if (scrollWrap && scrollWrap !== effectLinesScrollWrap) {
        if (effectLinesScrollWrap) {
          effectLinesScrollWrap.removeEventListener(
             "scroll",
            handleEffectLinesScroll
          );
        }
        scrollWrap.addEventListener("scroll", handleEffectLinesScroll, {
          passive: true,
        });
        effectLinesScrollWrap = scrollWrap;
      }
    }
    function scheduleEffectLinesUpdate() {
      if (effectLinesRAF) return;
      effectLinesRAF = requestAnimationFrame(() => {
        effectLinesRAF = null;
        updateEffectLines();
      });
    }
    function getEffectLinesLayer() {
      const rail = document.querySelector(".top-rail.skills");
      if (!rail) return null;
      const iconBar = rail.querySelector(".icon-bar");
      if (!iconBar) return null;
      if (effectLinesLayer && effectLinesLayer.isConnected) {
        return effectLinesLayer;
      }
      const existing = iconBar.querySelector(".effect-lines-layer");
      if (existing) {
        effectLinesLayer = existing;
        return effectLinesLayer;
      }
      const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      svg.classList.add("effect-lines-layer");
      svg.setAttribute("aria-hidden", "true");
      iconBar.insertBefore(svg, iconBar.firstChild);
      effectLinesLayer = svg;
      return effectLinesLayer;
    }
    function updateEffectLines() {
      if (!effectState.sourceIcon || !effectState.sourceIcon.isConnected) {
        clearEffectLines();
        return;
      }
      if (effectState.expiresAt <= Date.now()) {
        clearEffectLines();
        return;
      }
      const layer = getEffectLinesLayer();
      if (!layer) return;
      const rail = layer.closest(".top-rail.skills");
      if (!rail) return;
      const iconBar = rail.querySelector(".icon-bar");
      if (!iconBar) return;
 
      bindEffectLinesEvents();
 
      const barRect = iconBar.getBoundingClientRect();
      const width = Math.max(1, Math.round(iconBar.scrollWidth || barRect.width));
      const height = Math.max(1, Math.round(iconBar.clientHeight || barRect.height));
      layer.style.left = "0px";
      layer.style.top = "0px";
      layer.setAttribute("width", String(width));
      layer.setAttribute("height", String(height));
      layer.setAttribute("viewBox", `0 0 ${width} ${height}`);
      layer.classList.remove("effect-lines-returning");
 
      const srcRect = effectState.sourceIcon.getBoundingClientRect();
      const startX =
        srcRect.left + srcRect.width / 2 - barRect.left + iconBar.scrollLeft;
      const startY = srcRect.top + srcRect.height / 2 - barRect.top;
 
      const allIcons = Array.from(
        document.querySelectorAll(".icon-bar .skill-icon[data-index]")
      ).filter((icon) => !icon.classList.contains("weapon-bar-toggle"));
      const targets = allIcons.filter((icon) => {
        if (icon === effectState.sourceIcon) return false;
        const name = getSkillNameFromIcon(icon);
        return name && effectState.skills.has(name);
      });
 
      if (!targets.length) {
        clearEffectLines();
        return;
      }


            // console.log('[Skills DEBUG]', {
      const frag = document.createDocumentFragment();
            //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
      const baselinePadding = 10;
            //    weaponOn,
      const baselineExtra = 12;
            //    hasWeaponData: !!weaponData,
      const baselineY = Math.max(
            //    weaponData: weaponData,
        startY,
            //    baseVideoFile,
        height - baselinePadding + baselineExtra
            //    baseVideoURL
      );
            // });
      const targetPoints = targets.map((target) => {
        const tgtRect = target.getBoundingClientRect();
        const endX =
          tgtRect.left + tgtRect.width / 2 - barRect.left + iconBar.scrollLeft;
        const endY = tgtRect.top + tgtRect.height / 2 - barRect.top;
        return { x: endX, y: endY };
      });
      const xs = targetPoints.map((p) => p.x);
      const minX = Math.min(startX, ...xs);
      const maxX = Math.max(startX, ...xs);
      effectLinesLastState = {
        startX,
        startY,
        baselineY,
        targets: targetPoints.map((p) => ({
          x: p.x,
          y: p.y,
          dist: Math.hypot(p.x - startX, p.y - startY),
        })),
      };
      const dParts = [
        `M ${startX} ${startY}`,
        `L ${startX} ${baselineY}`,
        `L ${minX} ${baselineY}`,
        `L ${maxX} ${baselineY}`,
      ];
      targetPoints.forEach((p) => {
        dParts.push(`M ${p.x} ${baselineY} L ${p.x} ${p.y}`);
      });
      const d = dParts.join(" ");


            if (weaponOn && weaponData) {
      const glow = document.createElementNS(
                // console.log('[Skills] checking weapon video', {
        "http://www.w3.org/2000/svg",
                //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
        "path"
                //    hasVideo: !!(weaponData.video),
      );
                //    videoValue: weaponData.video,
      glow.setAttribute("d", d);
                //    videoTrimmed: weaponData.video ? weaponData.video.trim() : ''
      glow.classList.add("effect-line", "effect-line-glow");
                // });
 
                if (weaponData.video && weaponData.video.trim() !== '') {
      const core = document.createElementNS(
                    // console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
        "http://www.w3.org/2000/svg",
                    return weaponData.video.trim();
        "path"
                }
      );
            }
      core.setAttribute("d", d);
            // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
      core.classList.add("effect-line", "effect-line-core");
            const result = baseVideoFile || baseVideoURL || '';
 
            // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
      frag.appendChild(glow);
            return result;
      frag.appendChild(core);
         } function createVideoElement(videoURL, extraAttrs = {
 
         }) {
      requestAnimationFrame(() => {
            // Se o vídeo já falhou antes, não cria novo elemento
        const length = Math.max(1, Math.round(core.getTotalLength()));
            if (failedVideosCache.has(videoURL)) {
        [core, glow].forEach((path) => {
                return null;
          path.style.setProperty("--effect-line-length", `${length}`);
            }
          path.style.strokeDasharray = `${length}`;
          path.style.strokeDashoffset = "0";
        });
      });
      layer.replaceChildren(frag);
    }
    function animateEffectLinesReturn() {
      const layer =
        (effectLinesLayer && effectLinesLayer.isConnected
          ? effectLinesLayer
          : document.querySelector(".effect-lines-layer")) || null;
      if (!layer || !effectLinesLastState) {
        clearEffectLines();
        return;
      }
      const { startX, startY, baselineY, targets } = effectLinesLastState;
      const sortedTargets = [...targets].sort((a, b) => b.dist - a.dist);
      const returnDuration = 1.1;
      const returnStagger = 0.22;
      const frag = document.createDocumentFragment();
      sortedTargets.forEach((target, idx) => {
        const d = [
          `M ${startX} ${startY}`,
          `L ${startX} ${baselineY}`,
          `L ${target.x} ${baselineY}`,
          `L ${target.x} ${target.y}`,
        ].join(" ");
 
        const glow = document.createElementNS(
          "http://www.w3.org/2000/svg",
          "path"
        );
        glow.setAttribute("d", d);
        glow.classList.add(
          "effect-line",
          "effect-line-glow",
          "effect-line-returning"
        );
        glow.style.setProperty("--effect-return-duration", `${returnDuration}s`);
         glow.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);
 
         const core = document.createElementNS(
          "http://www.w3.org/2000/svg",
          "path"
        );
        core.setAttribute("d", d);
        core.classList.add(
          "effect-line",
          "effect-line-core",
          "effect-line-returning"
        );
        core.style.setProperty("--effect-return-duration", `${returnDuration}s`);
        core.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);
 
        frag.appendChild(glow);
        frag.appendChild(core);
      });
      layer.replaceChildren(frag);


            const v = document.createElement('video');
      requestAnimationFrame(() => {
            v.className = 'skill-video';
        const paths = Array.from(layer.querySelectorAll("path.effect-line"));
            v.setAttribute('controls', '');
        paths.forEach((path) => {
            v.setAttribute('preload', 'auto'); // Mudado de 'metadata' para 'auto' para carregar tudo imediatamente
          const length = Math.max(1, Math.round(path.getTotalLength()));
            v.setAttribute('playsinline', '');
          path.style.setProperty("--effect-line-length", `${length}`);
            v.style.display = 'none';
          path.style.strokeDasharray = `${length} ${length}`;
            v.style.width = '100%';
          path.style.strokeDashoffset = "0";
            v.style.height = 'auto';
        });
            v.style.aspectRatio = '16/9';
      });
            v.style.objectFit = 'cover';
      if (effectLinesCleanupTimer) {
            Object.keys(extraAttrs).forEach(k => {
        clearTimeout(effectLinesCleanupTimer);
                v.dataset[k] = extraAttrs[k];
      }
            });
      effectLinesCleanupTimer = setTimeout(() => {
            // Detectar formato do vídeo pela extensão
        clearEffectLines();
             const ext = (videoURL.split('.').pop() || '').toLowerCase().split('?')[0];
      }, (returnDuration + returnStagger * Math.max(0, targets.length - 1)) * 1000 + 120);
            const mimeTypes = {
    }
                'mp4': 'video/mp4',
    function clearEffectLines() {
                'm4v': 'video/mp4',
      if (effectLinesCleanupTimer) {
                'webm': 'video/webm',
        clearTimeout(effectLinesCleanupTimer);
                'ogv': 'video/ogg',
        effectLinesCleanupTimer = null;
                'ogg': 'video/ogg',
      }
                'mov': 'video/quicktime'
      const layer =
            };
        (effectLinesLayer && effectLinesLayer.isConnected
            const mimeType = mimeTypes[ext] || 'video/mp4';
          ? effectLinesLayer
          : document.querySelector(".effect-lines-layer")) || null;
      if (layer) layer.remove();
      effectLinesLayer = null;
    }
    function applyEffectClasses() {
      const isActive = effectState.expiresAt > Date.now();
      const rail = document.querySelector(".top-rail.skills");
      if (rail) rail.classList.toggle("effect-mode-on", isActive);
      document
        .querySelectorAll(".icon-bar .skill-icon[data-index]")
        .forEach((icon) => {
          if (icon.classList.contains("weapon-bar-toggle")) return;
          const name = getSkillNameFromIcon(icon);
          const should =
             isActive && name && effectState.skills.has(name);
          icon.classList.toggle("effect-active", !!should);
          if (!should) {
            icon.style.removeProperty("--effect-strength");
          }
        });
      if (isActive) {
        bindEffectLinesEvents();
        scheduleEffectLinesUpdate();
      }
    }
    function clearEffectState() {
      const activeIcon = document.querySelector(
        ".icon-bar .skill-icon.active"
      );
      const activeName = getSkillNameFromIcon(activeIcon);
      const wasAffected =
        activeName && effectState.skills.has(activeName);


            const src = document.createElement('source');
      animateEffectLinesReturn();
            src.src = videoURL;
      if (effectState.timer) {
            src.type = mimeType;
        clearTimeout(effectState.timer);
            v.appendChild(src);
        effectState.timer = null;
      }
      effectState.skills.clear();
      effectState.videos.clear();
      effectState.expiresAt = 0;
      effectState.sourceIcon = null;
      applyEffectClasses();


            // Fallback para Safari/iOS mais antigos
      if (wasAffected && activeIcon) {
            v.setAttribute('webkit-playsinline', '');
        activeIcon.dispatchEvent(new Event("click", { bubbles: true }));
            v.setAttribute('x-webkit-airplay', 'allow');
      }
    }
    function activateEffectFromIcon(iconEl) {
      const effectRaw = getCachedJSON(iconEl, "effect");
      const normalized = normalizeEffectData(effectRaw);
      if (!normalized) return;


            // Tratamento silencioso de erros - marca como falhado e não tenta mais
      effectState.skills = new Set(normalized.skills);
            let errorHandled = false;
      effectState.videos = normalized.videos;
            v.addEventListener('error', (e) => {
      effectState.expiresAt = Date.now() + normalized.timeMs;
                if (errorHandled) return;
      effectState.sourceIcon = iconEl;
                errorHandled = true;
                // Marca o vídeo como falhado para não tentar carregar novamente
                failedVideosCache.add(videoURL);
                // Remove o vídeo do DOM se estiver lá
                if (v.parentNode) {
                    v.parentNode.removeChild(v);
                }
                // Avisa apenas uma vez sobre vídeos faltantes
                if (!missingVideosReported.has(videoURL)) {
                    missingVideosReported.add(videoURL);
                    // Extrai nome do arquivo da URL para o aviso
                    const fileName = videoURL.split('/').pop().split('?')[0] || videoURL;
                    console.info(`[Skills] Vídeo não encontrado na wiki: ${decodeURIComponent(fileName)}. Este aviso aparecerá apenas uma vez.`);
                }
            }, { once: true });


            return v;
      if (effectState.timer) clearTimeout(effectState.timer);
         }
      effectState.timer = setTimeout(() => {
         clearEffectState();
      }, normalized.timeMs + 5);


        // Função recursiva para carregar TODOS os vídeos de subskills (incluindo sub-subskills)
      applyEffectClasses();
        function preloadSubskillVideosRecursively(subs, parentIdx, parentPath = '') {
    }
            if (!videoBox || !Array.isArray(subs)) return 0;
    function getEffectVideoForIcon(iconEl) {
            let createdCount = 0;
      if (!iconEl) return "";
            subs.forEach(s => {
      if (effectState.expiresAt <= Date.now()) return "";
                const subName = (s.name || s.n || '').trim();
      const name = getSkillNameFromIcon(iconEl);
                const currentPath = parentPath ? `${parentPath}:${subName}` : subName;
      if (!name || !effectState.skills.has(name)) return "";
      if (effectState.videos.has(name)) return effectState.videos.get(name) || "";
      return "";
    }
    function getEffectiveSkillVideoFromIcon(iconEl) {
      const weaponOn = globalWeaponEnabled;
      const weaponData = getWeaponDataForIcon(iconEl);
      const baseVideoFile = (iconEl.dataset.videoFile || "").trim();
      const baseVideoURL = (iconEl.dataset.video || "").trim();


                // Vídeo normal da subskill
      // console.log('[Skills DEBUG]', {
                if (s.video && s.video.trim() !== '' && s.video !== 'Nada.png' && !s.video.toLowerCase().includes('nada.png')) {
      //    skillName: iconEl.dataset.nome || iconEl.dataset.name,
                    const key = `sub:${parentIdx}:${currentPath}`;
      //    weaponOn,
                    if (!subskillVideosCache.has(key)) {
      //    hasWeaponData: !!weaponData,
                        const videoURL = normalizeFileURL(s.video);
      //    weaponData: weaponData,
                        if (videoURL && videoURL.trim() !== '' && !failedVideosCache.has(videoURL)) {
      //     baseVideoFile,
                            const v = createVideoElement(videoURL, {
      //    baseVideoURL
                                sub: '1', parentIndex: parentIdx, subName: currentPath, cacheKey: key
      // });
                            });
                            if (v) { // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                                videoBox.appendChild(v);
                                subskillVideosCache.set(key, v);
                                createdCount++;
                                // FORÇA carregamento imediatamente (apenas uma vez)
                                v.load();
                            }
                        }
                    }
                }


                // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
      const effectVideo = getEffectVideoForIcon(iconEl);
                if (s.weapon && typeof s.weapon === 'object' && s.weapon.video && s.weapon.video.trim() !== '' && s.weapon.video !== 'Nada.png' && !s.weapon.video.toLowerCase().includes('nada.png')) {
      if (effectVideo && effectVideo.trim() !== "") {
                    const weaponKey = `sub:${parentIdx}:${currentPath}:weapon`;
        return effectVideo.trim();
                    if (!subskillVideosCache.has(weaponKey)) {
      }
                        const weaponVideoURL = normalizeFileURL(s.weapon.video);
                        if (weaponVideoURL && weaponVideoURL.trim() !== '' && !failedVideosCache.has(weaponVideoURL)) {
                            const v = createVideoElement(weaponVideoURL, {
                                sub: '1', parentIndex: parentIdx, subName: currentPath, weapon: '1', cacheKey: weaponKey
                            });
                            if (v) { // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                                videoBox.appendChild(v);
                                subskillVideosCache.set(weaponKey, v);
                                createdCount++;
                                // FORÇA carregamento imediatamente (apenas uma vez)
                                v.load();
                            }
                        }
                    }
                }


                // RECURSÃO: processa sub-subskills (SEMPRE processa, mesmo se o vídeo já estiver no cache)
      if (weaponOn && weaponData) {
                if (Array.isArray(s.subs) && s.subs.length > 0) {
        // console.log('[Skills] checking weapon video', {
                    createdCount += preloadSubskillVideosRecursively(s.subs, parentIdx, currentPath);
        //    skillName: iconEl.dataset.nome || iconEl.dataset.name,
                }
        //     hasVideo: !!(weaponData.video),
            });
        //    videoValue: weaponData.video,
            return createdCount;
        //    videoTrimmed: weaponData.video ? weaponData.video.trim() : ''
        // });
        if (weaponData.video && weaponData.video.trim() !== "") {
          // console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
          return weaponData.video.trim();
         }
         }
      }


        function precreateSubskillVideos() {
      // Sistema genérico de swap: verifica character_videos se houver personagem ativo
            if (!videoBox) return;
      if (activeCharacter !== null && iconEl.dataset.characterVideos) {
            let createdCount = 0;
        try {
            iconItems.forEach(parentIcon => {
          const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
                const subs = getCachedJSON(parentIcon, 'subs');
          const characterVideo = characterVideos[activeCharacter];
                if (!Array.isArray(subs)) return;
          if (characterVideo && characterVideo.trim() !== "") {
                const parentIdx = parentIcon.dataset.index || '';
            return characterVideo.trim();
                createdCount += preloadSubskillVideosRecursively(subs, parentIdx);
          }
            });
        } catch (e) {
          console.warn("[Swap] Erro ao processar character_videos:", e);
         }
         }
      }


        // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
      // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
        function preloadAllVideosRecursively() {
      const result = baseVideoFile || baseVideoURL || "";
            if (!videoBox || !iconItems.length) return;
      // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
      return result;
    }
    function createVideoElement(videoURL, extraAttrs = {}) {
      // Se o vídeo já falhou antes, não cria novo elemento
      if (failedVideosCache.has(videoURL)) {
        return null;
      }


            // 1. Carregar vídeos de skills principais
      const v = document.createElement("video");
            iconItems.forEach(el => {
      v.className = "skill-video";
                const idx = el.dataset.index || '';
      v.setAttribute("controls", "");
                if (!idx) return;
      v.setAttribute("preload", "auto"); // Mudado de 'metadata' para 'auto' para carregar tudo imediatamente
      v.setAttribute("playsinline", "");
      v.style.display = "none";
      v.style.width = "100%";
      v.style.height = "auto";
      v.style.aspectRatio = "16/9";
      v.style.objectFit = "cover";
      Object.keys(extraAttrs).forEach((k) => {
        v.dataset[k] = extraAttrs[k];
      });
      // Detectar formato do vídeo pela extensão
      const ext = (videoURL.split(".").pop() || "").toLowerCase().split("?")[0];
      const mimeTypes = {
        mp4: "video/mp4",
        m4v: "video/mp4",
        webm: "video/webm",
        ogv: "video/ogg",
        ogg: "video/ogg",
        mov: "video/quicktime",
      };
      const mimeType = mimeTypes[ext] || "video/mp4";


                // Vídeo normal da skill principal
      const src = document.createElement("source");
                // Prioriza data-video-file (nome do arquivo), depois data-video (pode ser URL completa)
      src.src = videoURL;
                let src = (el.dataset.videoFile || '').trim();
      src.type = mimeType;
                if (!src) {
      v.appendChild(src);
                    // Se não tem videoFile, tenta extrair do video (pode ser URL completa)
 
                    const videoAttr = (el.dataset.video || '').trim();
      // Fallback para Safari/iOS mais antigos
                    if (videoAttr) {
      v.setAttribute("webkit-playsinline", "");
                        // Se já é uma URL completa, usa direto; senão normaliza
      v.setAttribute("x-webkit-airplay", "allow");
                        if (videoAttr.includes('/') || videoAttr.startsWith('http')) {
                            src = videoAttr;
                        } else {
                            src = videoAttr;
                        }
                    }
                }
                if (src && src !== 'Nada.png' && !src.toLowerCase().includes('nada.png') && !videosCache.has(idx)) {
                    const videoURL = normalizeFileURL(src);
                    if (videoURL && !failedVideosCache.has(videoURL)) {
                        const v = createVideoElement(videoURL, {
                            index: idx
                        });
                        if (v) { // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                            totalVideos++;
                            v.style.maxWidth = '100%';
                            v.addEventListener('canplaythrough', () => {
                                loadedVideos++;
                                if (!userHasInteracted && loadedVideos === 1) {
                                    try {
                                        v.pause();
                                        v.currentTime = 0;
                                    } catch (e) {
                                    }
                                } if (loadedVideos === totalVideos) autoplay = true;
                            }, {
                                once: true
                            });
                            v.addEventListener('error', () => {
                                loadedVideos++;
                                if (loadedVideos === totalVideos) autoplay = true;
                            }, {
                                once: true
                            });
                            videoBox.appendChild(v);
                            videosCache.set(idx, v);
                            nestedVideoElByIcon.set(el, v);
                            // Força carregamento imediatamente (apenas uma vez)
                            v.load();
                        }
                    }
                }


                // Vídeo de weapon (sempre carrega, independente do toggle)
      // Tratamento silencioso de erros - marca como falhado e não tenta mais
                const weaponData = getCachedJSON(el, 'weapon');
      let errorHandled = false;
                if (weaponData && weaponData.video && weaponData.video.trim() !== '' && weaponData.video !== 'Nada.png' && !weaponData.video.toLowerCase().includes('nada.png')) {
      v.addEventListener(
                    const weaponKey = `weapon:${idx}:${(el.dataset.nome || el.dataset.name || '').trim()}`;
        "error",
                    if (!videosCache.has(weaponKey)) {
        (e) => {
                        const weaponVideoURL = normalizeFileURL(weaponData.video);
          if (errorHandled) return;
                        if (weaponVideoURL && !failedVideosCache.has(weaponVideoURL)) {
          errorHandled = true;
                            const v = createVideoElement(weaponVideoURL, {
          // Marca o vídeo como falhado para não tentar carregar novamente
                                index: idx, weapon: '1'
          failedVideosCache.add(videoURL);
                            });
          // Remove o vídeo do DOM se estiver lá
                            if (v) { // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
          if (v.parentNode) {
                                totalVideos++;
            v.parentNode.removeChild(v);
                                v.style.maxWidth = '100%';
          }
                                v.addEventListener('canplaythrough', () => {
          // Avisa apenas uma vez sobre vídeos faltantes
                                    loadedVideos++;
          if (!missingVideosReported.has(videoURL)) {
                                    if (loadedVideos === totalVideos) autoplay = true;
            missingVideosReported.add(videoURL);
                                }, {
            // Extrai nome do arquivo da URL para o aviso
                                    once: true
            const fileName =
                                });
              videoURL.split("/").pop().split("?")[0] || videoURL;
                                v.addEventListener('error', () => {
            console.info(
                                    loadedVideos++;
              `[Skills] Vídeo não encontrado na wiki: ${decodeURIComponent(
                                    if (loadedVideos === totalVideos) autoplay = true;
                fileName
                                }, {
              )}. Este aviso aparecerá apenas uma vez.`
                                    once: true
            );
                                });
          }
                                videoBox.appendChild(v);
        },
                                videosCache.set(weaponKey, v);
        { once: true }
                                // Força carregamento imediatamente (apenas uma vez)
      );
                                v.load();
                            }
                        }
                    }
                }
            });


            // 2. Carregar vídeos de subskills (recursivamente)
      return v;
            precreateSubskillVideos();
    }
        }


        // Função para pré-carregar TODOS os ícones recursivamente
    // Função recursiva para carregar TODOS os vídeos de subskills (incluindo sub-subskills)
        function preloadAllIconsRecursively() {
    function preloadSubskillVideosRecursively(
            const iconCache = new Set();
      subs,
      parentIdx,
      parentPath = ""
    ) {
      if (!videoBox || !Array.isArray(subs)) return 0;
      let createdCount = 0;
      subs.forEach((s) => {
        const subName = (s.name || s.n || "").trim();
        const currentPath = parentPath ? `${parentPath}:${subName}` : subName;


            // Função recursiva para processar subskills
        // Vídeo normal da subskill
            function processSubskillsRecursively(subs) {
        if (s.video && s.video.trim() !== "") {
                if (!Array.isArray(subs)) return;
          const key = `sub:${parentIdx}:${currentPath}`;
                subs.forEach(s => {
          if (!subskillVideosCache.has(key)) {
                    const icon = (s.icon || '').trim();
            const videoURL = normalizeFileURL(s.video);
                    if (icon && icon !== 'Nada.png' && !icon.toLowerCase().includes('nada.png')) {
            if (
                        const iconURL = normalizeFileURL(icon);
              videoURL &&
                        if (iconURL && !iconCache.has(iconURL)) {
              videoURL.trim() !== "" &&
                            iconCache.add(iconURL);
              !failedVideosCache.has(videoURL)
                            const img = new Image();
            ) {
                            img.decoding = 'async';
              const v = createVideoElement(videoURL, {
                            img.loading = 'eager';
                sub: "1",
                            img.referrerPolicy = 'same-origin';
                parentIndex: parentIdx,
                            img.src = iconURL;
                subName: currentPath,
                            imagePreloadCache.set(iconURL, img);
                cacheKey: key,
                        }
              });
                    }
              if (v) {
                    // Recursão para sub-subskills
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                    if (Array.isArray(s.subs)) {
                videoBox.appendChild(v);
                        processSubskillsRecursively(s.subs);
                subskillVideosCache.set(key, v);
                    }
                createdCount++;
                });
                // FORÇA carregamento imediatamente (apenas uma vez)
                v.load();
              }
             }
             }
          }
        }


            // Carregar ícones de skills principais
        // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
            iconItems.forEach(el => {
        if (
                const img = el.querySelector('img');
          s.weapon &&
                if (img && img.src) {
          typeof s.weapon === "object" &&
                    const iconURL = img.src;
          s.weapon.video &&
                    if (iconURL && !iconCache.has(iconURL)) {
          s.weapon.video.trim() !== ""
                        iconCache.add(iconURL);
        ) {
                        // Já está no DOM, mas força pré-carregamento
          const weaponKey = `sub:${parentIdx}:${currentPath}:weapon`;
                        const preloadImg = new Image();
          if (!subskillVideosCache.has(weaponKey)) {
                        preloadImg.decoding = 'async';
            const weaponVideoURL = normalizeFileURL(s.weapon.video);
                        preloadImg.loading = 'eager';
            if (
                        preloadImg.referrerPolicy = 'same-origin';
              weaponVideoURL &&
                        preloadImg.src = iconURL;
              weaponVideoURL.trim() !== "" &&
                        imagePreloadCache.set(iconURL, preloadImg);
              !failedVideosCache.has(weaponVideoURL)
                    }
            ) {
                }
              const v = createVideoElement(weaponVideoURL, {
                sub: "1",
                parentIndex: parentIdx,
                subName: currentPath,
                weapon: "1",
                cacheKey: weaponKey,
              });
              if (v) {
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                videoBox.appendChild(v);
                subskillVideosCache.set(weaponKey, v);
                createdCount++;
                // FORÇA carregamento imediatamente (apenas uma vez)
                v.load();
              }
            }
          }
        }


                // Carregar ícones de subskills recursivamente
        // RECURSÃO: processa sub-subskills (SEMPRE processa, mesmo se o vídeo já estiver no cache)
                const subs = getCachedJSON(el, 'subs');
        if (Array.isArray(s.subs) && s.subs.length > 0) {
                if (Array.isArray(subs)) {
          createdCount += preloadSubskillVideosRecursively(
                    processSubskillsRecursively(subs);
            s.subs,
                }
            parentIdx,
             });
             currentPath
          );
         }
         }
      });
      return createdCount;
    }


        // Função principal que carrega TUDO imediatamente
    function precreateSubskillVideos() {
        function preloadAllAssets() {
      if (!videoBox) return;
            // Carregar TUDO imediatamente - sem lazy loading
      let createdCount = 0;
            // 1. Carregar TODOS os vídeos (principais, subskills, weapon, sub-subskills)
      iconItems.forEach((parentIcon) => {
            preloadAllVideosRecursively();
        const subs = getCachedJSON(parentIcon, "subs");
        if (!Array.isArray(subs)) return;
        const parentIdx = parentIcon.dataset.index || "";
        createdCount += preloadSubskillVideosRecursively(subs, parentIdx);
      });
    }


            // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
    // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
             preloadAllIconsRecursively();
    function preloadAllVideosRecursively() {
      if (!videoBox || !iconItems.length) return;
 
      // 1. Carregar vídeos de skills principais
      iconItems.forEach((el) => {
        const idx = el.dataset.index || "";
        if (!idx) return;
 
        // Vídeo normal da skill principal
        // Prioriza data-video-file (nome do arquivo), depois data-video (pode ser URL completa)
        let src = (el.dataset.videoFile || "").trim();
        if (!src) {
          // Se não tem videoFile, tenta extrair do video (pode ser URL completa)
          const videoAttr = (el.dataset.video || "").trim();
          if (videoAttr) {
            // Se já é uma URL completa, usa direto; senão normaliza
             if (videoAttr.includes("/") || videoAttr.startsWith("http")) {
              src = videoAttr;
            } else {
              src = videoAttr;
            }
          }
         }
         }
 
         if (src && !videosCache.has(idx)) {
        // Executa pré-carregamento imediatamente
          const videoURL = normalizeFileURL(src);
         preloadAllAssets(); function wireTooltipsForNewIcons() {
          if (videoURL && !failedVideosCache.has(videoURL)) {
            const tip = document.querySelector('.skill-tooltip');
            const v = createVideoElement(videoURL, {
            if (!tip) return;
              index: idx,
            let lockUntil2 = 0;
            Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => {
                if (icon.dataset.weaponToggle === '1' || icon.classList.contains('weapon-bar-toggle')) return;
                if (icon.dataset.tipwired) return;
                icon.dataset.tipwired = '1';
                const label = icon.dataset.nome || icon.dataset.name || icon.title || '';
                if (label && !icon.hasAttribute('aria-label')) icon.setAttribute('aria-label', label);
                if (icon.hasAttribute('title')) icon.removeAttribute('title');
                const img = icon.querySelector('img');
                if (img) {
                    const imgAlt = img.getAttribute('alt') || '';
                    const imgTitle = img.getAttribute('title') || '';
                    if (!label && (imgAlt || imgTitle)) icon.setAttribute('aria-label', imgAlt || imgTitle);
                    img.setAttribute('alt', '');
                    if (img.hasAttribute('title')) img.removeAttribute('title');
                } const measureAndPos = (el) => {
                    if (!el || tip.getAttribute('aria-hidden') === 'true') return;
                    tip.style.left = '0px';
                    tip.style.top = '0px';
                    const rect = el.getBoundingClientRect();
                    const tr = tip.getBoundingClientRect();
                    let left = Math.round(rect.left + (rect.width - tr.width) / 2);
                    left = Math.max(8, Math.min(left, window.innerWidth - tr.width - 8));
                    const coarse = (window.matchMedia && matchMedia('(pointer: coarse)').matches) || (window.innerWidth <= 600);
                    let top = coarse ? Math.round(rect.bottom + 10) : Math.round(rect.top - tr.height - 8);
                    if (top < 8) top = Math.round(rect.bottom + 10);
                    tip.style.left = left + 'px';
                    tip.style.top = top + 'px';
                };
                const show = (el, text) => {
                    tip.textContent = text || '';
                    tip.setAttribute('aria-hidden', 'false');
                    measureAndPos(el);
                    tip.style.opacity = '1';
                };
                const hide = () => {
                    tip.setAttribute('aria-hidden', 'true');
                    tip.style.opacity = '0';
                    tip.style.left = '-9999px';
                    tip.style.top = '-9999px';
                };
                icon.addEventListener('mouseenter', () => show(icon, (icon.dataset.nome || icon.dataset.name || '')));
                icon.addEventListener('mousemove', () => {
                    if (performance.now() >= lockUntil2) measureAndPos(icon);
                });
                icon.addEventListener('click', () => {
                    lockUntil2 = performance.now() + 240;
                    measureAndPos(icon);
                });
                icon.addEventListener('mouseleave', hide);
             });
             });
        } function showVideoForIcon(el) {
            if (v) {
            userHasInteracted = true;
              // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
            if (!videoBox) return;
              totalVideos++;
            const effectiveVideo = getEffectiveSkillVideoFromIcon(el);
              v.style.maxWidth = "100%";
            if (!effectiveVideo || effectiveVideo.trim() === '') {
              v.addEventListener(
                 videoBox.style.display = 'none';
                "canplaythrough",
                return;
                () => {
                  loadedVideos++;
                  if (!userHasInteracted && loadedVideos === 1) {
                    try {
                      v.pause();
                      v.currentTime = 0;
                    } catch (e) { }
                  }
                  if (loadedVideos === totalVideos) autoplay = true;
                },
                {
                  once: true,
                }
              );
              v.addEventListener(
                "error",
                () => {
                  loadedVideos++;
                  if (loadedVideos === totalVideos) autoplay = true;
                },
                {
                  once: true,
                 }
              );
              videoBox.appendChild(v);
              videosCache.set(idx, v);
              nestedVideoElByIcon.set(el, v);
              // Força carregamento imediatamente (apenas uma vez)
              v.load();
             }
             }
             const videoURL = normalizeFileURL(effectiveVideo);
          }
             if (!videoURL || videoURL.trim() === '') {
        }
                 videoBox.style.display = 'none';
 
                 return;
        // Vídeo de weapon (sempre carrega, independente do toggle)
        const weaponData = getCachedJSON(el, "weapon");
        if (
          weaponData &&
          weaponData.video &&
          weaponData.video.trim() !== ""
        ) {
          const weaponKey = `weapon:${idx}:${(
            el.dataset.nome ||
            el.dataset.name ||
            ""
          ).trim()}`;
          if (!videosCache.has(weaponKey)) {
             const weaponVideoURL = normalizeFileURL(weaponData.video);
             if (weaponVideoURL && !failedVideosCache.has(weaponVideoURL)) {
              const v = createVideoElement(weaponVideoURL, {
                index: idx,
                weapon: "1",
              });
              if (v) {
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                totalVideos++;
                v.style.maxWidth = "100%";
                v.addEventListener(
                  "canplaythrough",
                  () => {
                    loadedVideos++;
                    if (loadedVideos === totalVideos) autoplay = true;
                  },
                  {
                    once: true,
                  }
                );
                v.addEventListener(
                  "error",
                  () => {
                    loadedVideos++;
                    if (loadedVideos === totalVideos) autoplay = true;
                  },
                  {
                    once: true,
                  }
                );
                 videoBox.appendChild(v);
                videosCache.set(weaponKey, v);
                 // Força carregamento imediatamente (apenas uma vez)
                v.load();
              }
             }
             }
            Array.from(videoBox.querySelectorAll('video.skill-video')).forEach(v => {
          }
                try {
        }
                    v.pause();
      });
                } catch (e) {
 
                }
      // 2. Carregar vídeos de subskills (recursivamente)
                v.style.display = 'none';
      precreateSubskillVideos();
            });
    }
            if (window.__subskills) window.__subskills.hideAll?.(videoBox);
            const hasIdx = !!el.dataset.index;
            const weaponOn = globalWeaponEnabled;
            const weaponData = getWeaponDataForIcon(el);
            const isWeaponVideo = weaponOn && weaponData && weaponData.video && weaponData.video.trim() !== '';


            // console.log('[Skills] showVideoForIcon chamado', {
     // Função para pré-carregar TODOS os ícones recursivamente
            //    skillName: el.dataset.nome || el.dataset.name,
     function preloadAllIconsRecursively() {
            //     weaponOn,
      const iconCache = new Set();
            //     isWeaponVideo,
            //     effectiveVideo: getEffectiveSkillVideoFromIcon(el)
            // });
            const videoKey = isWeaponVideo ? `weapon:${getWeaponKey(el)}` : (el.dataset.index || '');
            const isSubskill = !hasIdx || el.dataset.nested === '1';
            const parentIdx = el.dataset.parentIndex || '';
            const subName = el.dataset.subName || el.dataset.nome || el.dataset.name || '';


            if (hasIdx && !isWeaponVideo && videosCache.has(el.dataset.index)) {
      // Função recursiva para processar subskills
                const v = videosCache.get(el.dataset.index);
      function processSubskillsRecursively(subs) {
                videoBox.style.display = 'block';
        if (!Array.isArray(subs)) return;
                v.style.display = 'block';
        subs.forEach((s) => {
                try {
          const icon = (s.icon || "").trim();
                    v.currentTime = 0;
          if (icon) {
                } catch (e) {
            const iconURL = normalizeFileURL(icon);
                }
            if (iconURL && !iconCache.has(iconURL)) {
                const suppress = document.body.dataset.suppressSkillPlay === '1';
              iconCache.add(iconURL);
                if (!suppress) {
              const img = new Image();
                    v.play().catch(() => {
              img.decoding = "async";
                    });
              img.loading = "eager";
                } else {
              img.referrerPolicy = "same-origin";
                    try {
              img.src = iconURL;
                        v.pause();
              imagePreloadCache.set(iconURL, img);
                    } catch (e) {
                    }
                }
                return;
             }
             }
            // Para form_switch, permite criação dinâmica de vídeos (transições de forma)
          }
             // Vídeos normais devem estar pré-carregados
          // Recursão para sub-subskills
            const isFormSwitch = el.dataset.formSwitch === 'true' || el.getAttribute('data-form-switch') === 'true';
          if (Array.isArray(s.subs)) {
             let v = null;
             processSubskillsRecursively(s.subs);
             if (isWeaponVideo) {
          }
                const weaponKeyFull = `weapon:${getWeaponKey(el)}`;
        });
                v = videosCache.get(weaponKeyFull);
      }
                if (!v && isSubskill && parentIdx && subName) {
 
                    // Tenta buscar vídeo de weapon de subskill
      // Carregar ícones de skills principais
                    // O cache usa o formato: sub:${parentIdx}:${path}:weapon onde path NÃO inclui o nome da skill principal
      iconItems.forEach((el) => {
                    // O subName agora já está no formato correto (sem nome da skill principal)
        const img = el.querySelector("img");
        if (img && img.src) {
          const iconURL = img.src;
          if (iconURL && !iconCache.has(iconURL)) {
            iconCache.add(iconURL);
            // Já está no DOM, mas força pré-carregamento
            const preloadImg = new Image();
            preloadImg.decoding = "async";
            preloadImg.loading = "eager";
            preloadImg.referrerPolicy = "same-origin";
             preloadImg.src = iconURL;
             imagePreloadCache.set(iconURL, preloadImg);
          }
        }
 
        // Carregar ícones de subskills recursivamente
        const subs = getCachedJSON(el, "subs");
        if (Array.isArray(subs)) {
          processSubskillsRecursively(subs);
        }
      });
    }
 
    // Função principal que carrega TUDO imediatamente
    function preloadAllAssets() {
      // Carregar TUDO imediatamente - sem lazy loading
      // 1. Carregar TODOS os vídeos (principais, subskills, weapon, sub-subskills)
      preloadAllVideosRecursively();
 
      // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
      preloadAllIconsRecursively();
    }


                    // Tenta buscar diretamente com o subName
    // Executa pré-carregamento imediatamente
                    let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
    preloadAllAssets();
                    v = subskillVideosCache.get(subWeaponKey);
    function wireTooltipsForNewIcons() {
      const tip = document.querySelector(".skill-tooltip");
      if (!tip) return;
      let lockUntil2 = 0;
      Array.from(document.querySelectorAll(".icon-bar .skill-icon")).forEach(
        (icon) => {
          if (
            icon.dataset.weaponToggle === "1" ||
            icon.classList.contains("weapon-bar-toggle")
          )
            return;
          if (icon.dataset.tipwired) return;
          icon.dataset.tipwired = "1";
          const label =
            icon.dataset.nome || icon.dataset.name || icon.title || "";
          if (label && !icon.hasAttribute("aria-label"))
            icon.setAttribute("aria-label", label);
          if (icon.hasAttribute("title")) icon.removeAttribute("title");
          const img = icon.querySelector("img");
          if (img) {
            const imgAlt = img.getAttribute("alt") || "";
            const imgTitle = img.getAttribute("title") || "";
            if (!label && (imgAlt || imgTitle))
              icon.setAttribute("aria-label", imgAlt || imgTitle);
            img.setAttribute("alt", "");
            if (img.hasAttribute("title")) img.removeAttribute("title");
          }
          const measureAndPos = (el) => {
            if (!el || tip.getAttribute("aria-hidden") === "true") return;
            tip.style.left = "0px";
            tip.style.top = "0px";
            const rect = el.getBoundingClientRect();
            const tr = tip.getBoundingClientRect();
            let left = Math.round(rect.left + (rect.width - tr.width) / 2);
            left = Math.max(
              8,
              Math.min(left, window.innerWidth - tr.width - 8)
            );
            const coarse =
              (window.matchMedia && matchMedia("(pointer: coarse)").matches) ||
              window.innerWidth <= 600;
            let top = coarse
              ? Math.round(rect.bottom + 10)
              : Math.round(rect.top - tr.height - 8);
            if (top < 8) top = Math.round(rect.bottom + 10);
            tip.style.left = left + "px";
            tip.style.top = top + "px";
          };
          const show = (el, text) => {
            tip.textContent = text || "";
            tip.setAttribute("aria-hidden", "false");
            measureAndPos(el);
            tip.style.opacity = "1";
          };
          const hide = () => {
            tip.setAttribute("aria-hidden", "true");
            tip.style.opacity = "0";
            tip.style.left = "-9999px";
            tip.style.top = "-9999px";
          };
          icon.addEventListener("mouseenter", () =>
            show(icon, icon.dataset.nome || icon.dataset.name || "")
          );
          icon.addEventListener("mousemove", () => {
            if (performance.now() >= lockUntil2) measureAndPos(icon);
          });
          icon.addEventListener("click", () => {
            lockUntil2 = performance.now() + 240;
            measureAndPos(icon);
          });
          icon.addEventListener("mouseleave", hide);
        }
      );
    }
    function showVideoForIcon(el) {
      userHasInteracted = true;
      if (!videoBox) return;
      const effectiveVideo = getEffectiveSkillVideoFromIcon(el);
      if (!effectiveVideo || effectiveVideo.trim() === "") {
        videoBox.style.display = "none";
        return;
      }
      const videoURL = normalizeFileURL(effectiveVideo);
      if (!videoURL || videoURL.trim() === "") {
        videoBox.style.display = "none";
        return;
      }
      Array.from(videoBox.querySelectorAll("video.skill-video")).forEach(
        (v) => {
          try {
            v.pause();
          } catch (e) { }
          v.style.display = "none";
        }
      );
      if (window.__subskills) window.__subskills.hideAll?.(videoBox);
      const hasIdx = !!el.dataset.index;
      const weaponOn = globalWeaponEnabled;
      const weaponData = getWeaponDataForIcon(el);
      const isWeaponVideo =
        weaponOn &&
        weaponData &&
        weaponData.video &&
        weaponData.video.trim() !== "";
      const effectVideo = getEffectVideoForIcon(el);
      const isEffectVideo = !!(effectVideo && effectVideo.trim() !== "");
      const effectKey = isEffectVideo
        ? getEffectVideoKey(el, effectiveVideo)
        : "";


                    // Se não encontrou, tenta buscar recursivamente
      // console.log('[Skills] showVideoForIcon chamado', {
                    if (!v) {
      //    skillName: el.dataset.nome || el.dataset.name,
                        const basePattern = `sub:${parentIdx}:`;
      //    weaponOn,
                        const weaponSuffix = ':weapon';
      //     isWeaponVideo,
                        const searchName = subName.split(':').pop();
      //    effectiveVideo: getEffectiveSkillVideoFromIcon(el)
                        for (const [key, video] of subskillVideosCache.entries()) {
      // });
                            if (key.startsWith(basePattern) && key.endsWith(weaponSuffix)) {
      const videoKey = isWeaponVideo
                                const pathInKey = key.substring(basePattern.length, key.length - weaponSuffix.length);
        ? `weapon:${getWeaponKey(el)}`
                                // Tenta match exato, ou se termina com o nome da skill
        : isEffectVideo
                                if (pathInKey === subName ||
          ? effectKey
                                    pathInKey.endsWith(`:${searchName}`) ||
          : el.dataset.index || "";
                                    pathInKey === searchName) {
      const isSubskill = !hasIdx || el.dataset.nested === "1";
                                    v = video;
      const parentIdx = el.dataset.parentIndex || "";
                                    break;
      const subName =
                                }
        el.dataset.subName || el.dataset.nome || el.dataset.name || "";
                            }
                        }
                    }
                }
                if (!v) {
                    // Tenta buscar pelo padrão antigo também
                    v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`);
                }
            } else {
                if (isSubskill && parentIdx && subName) {
                    // Busca vídeo de subskill no cache correto
                    // O cache usa o formato: sub:${parentIdx}:${path} onde path NÃO inclui o nome da skill principal
                    // O subName agora já está no formato correto (sem nome da skill principal)


                    // Tenta buscar diretamente com o subName
      if (
                    let subKey = `sub:${parentIdx}:${subName}`;
        hasIdx &&
                    v = subskillVideosCache.get(subKey);
        !isWeaponVideo &&
        !isEffectVideo &&
        videosCache.has(el.dataset.index)
      ) {
        const v = videosCache.get(el.dataset.index);
        videoBox.style.display = "block";
        v.style.display = "block";
        try {
          v.currentTime = 0;
        } catch (e) { }
        const suppress = document.body.dataset.suppressSkillPlay === "1";
        if (!suppress) {
          v.play().catch(() => { });
        } else {
          try {
            v.pause();
          } catch (e) { }
        }
        return;
      }
      // Para form_switch, permite criação dinâmica de vídeos (transições de forma)
      // Vídeos normais devem estar pré-carregados
      const isFormSwitch =
        el.dataset.formSwitch === "true" ||
        el.getAttribute("data-form-switch") === "true";
      let v = null;
      if (isWeaponVideo) {
        const weaponKeyFull = `weapon:${getWeaponKey(el)}`;
        v = videosCache.get(weaponKeyFull);
        if (!v && isSubskill && parentIdx && subName) {
          // Tenta buscar vídeo de weapon de subskill
          // O cache usa o formato: sub:${parentIdx}:${path}:weapon onde path NÃO inclui o nome da skill principal
          // O subName agora já está no formato correto (sem nome da skill principal)


                    // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
          // Tenta buscar diretamente com o subName
                    // Isso é útil caso haja alguma diferença no formato do caminho
          let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
                    if (!v) {
          v = subskillVideosCache.get(subWeaponKey);
                        const basePattern = `sub:${parentIdx}:`;
                        const searchName = subName.split(':').pop(); // Último segmento do caminho
                        // Tenta também buscar apenas pelo último segmento (útil para sub-subskills)
                        const lastSegmentKey = `sub:${parentIdx}:${searchName}`;
                        v = subskillVideosCache.get(lastSegmentKey);


                        // Se ainda não encontrou, faz busca mais ampla
          // Se não encontrou, tenta buscar recursivamente
                        if (!v) {
          if (!v) {
                            for (const [key, video] of subskillVideosCache.entries()) {
            const basePattern = `sub:${parentIdx}:`;
                                if (key.startsWith(basePattern)) {
            const weaponSuffix = ":weapon";
                                    const pathInKey = key.substring(basePattern.length);
            const searchName = subName.split(":").pop();
                                    // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
            for (const [key, video] of subskillVideosCache.entries()) {
                                    if (pathInKey === subName ||
              if (key.startsWith(basePattern) && key.endsWith(weaponSuffix)) {
                                        pathInKey.endsWith(`:${searchName}`) ||
                const pathInKey = key.substring(
                                        pathInKey === searchName ||
                  basePattern.length,
                                        (subName.includes(':') && pathInKey.includes(subName)) ||
                  key.length - weaponSuffix.length
                                        (subName.includes(':') && pathInKey.endsWith(subName.split(':').slice(-2).join(':')))) {
                );
                                        v = video;
                // Tenta match exato, ou se termina com o nome da skill
                                        break;
                if (
                                    }
                  pathInKey === subName ||
                                }
                  pathInKey.endsWith(`:${searchName}`) ||
                            }
                  pathInKey === searchName
                        }
                ) {
                    }
                  v = video;
                } else {
                  break;
                    v = videosCache.get(el.dataset.index);
                    if (!v) {
                        v = nestedVideoElByIcon.get(el);
                    }
                 }
                 }
              }
             }
             }
          }
        }
        if (!v) {
          // Tenta buscar pelo padrão antigo também
          v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`);
        }
      } else if (isEffectVideo && effectKey) {
        v = videosCache.get(effectKey);
      } else {
        if (isSubskill && parentIdx && subName) {
          // Busca vídeo de subskill no cache correto
          // O cache usa o formato: sub:${parentIdx}:${path} onde path NÃO inclui o nome da skill principal
          // O subName agora já está no formato correto (sem nome da skill principal)


             // Se vídeo não foi encontrado no cache, verifica se falhou antes
          // Tenta buscar diretamente com o subName
          let subKey = `sub:${parentIdx}:${subName}`;
          v = subskillVideosCache.get(subKey);
 
          // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
          // Isso é útil caso haja alguma diferença no formato do caminho
          if (!v) {
            const basePattern = `sub:${parentIdx}:`;
            const searchName = subName.split(":").pop(); // Último segmento do caminho
            // Tenta também buscar apenas pelo último segmento (útil para sub-subskills)
            const lastSegmentKey = `sub:${parentIdx}:${searchName}`;
            v = subskillVideosCache.get(lastSegmentKey);
 
             // Se ainda não encontrou, faz busca mais ampla
             if (!v) {
             if (!v) {
                // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
              for (const [key, video] of subskillVideosCache.entries()) {
                 if (failedVideosCache.has(videoURL)) {
                 if (key.startsWith(basePattern)) {
                    videoBox.style.display = 'none';
                  const pathInKey = key.substring(basePattern.length);
                    return;
                  // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
                }
                  if (
                // Para form_switch, cria vídeo dinamicamente (transições de forma são dinâmicas)
                     pathInKey === subName ||
                // Usa uma chave única que inclui o nome do arquivo do vídeo para garantir que cada vídeo diferente seja cacheado separadamente
                    pathInKey.endsWith(`:${searchName}`) ||
                if (isFormSwitch && videoURL) {
                     pathInKey === searchName ||
                     const baseIndex = el.dataset.index || '';
                     (subName.includes(":") && pathInKey.includes(subName)) ||
                     const videoFileName = effectiveVideo || '';
                     (subName.includes(":") &&
                     // Chave única: index + nome do arquivo do vídeo
                      pathInKey.endsWith(
                    const videoKey = baseIndex + ':' + videoFileName;
                        subName.split(":").slice(-2).join(":")
                    v = videosCache.get(videoKey);
                      ))
                     if (!v) {
                  ) {
                        v = createVideoElement(videoURL, { index: baseIndex });
                     v = video;
                        if (v) {
                     break;
                            videoBox.appendChild(v);
                  }
                            videosCache.set(videoKey, v);
                            v.load();
                        } else {
                            videoBox.style.display = 'none';
                            return;
                        }
                    }
                } else {
                     // Vídeos normais devem estar pré-carregados
                    videoBox.style.display = 'none';
                     return;
                 }
                 }
              }
             }
             }
             videoBox.style.display = 'block';
          }
             v.style.display = 'block';
        } else {
             try {
          v = videosCache.get(el.dataset.index);
                v.currentTime = 0;
          if (!v) {
             } catch (e) {
             v = nestedVideoElByIcon.get(el);
          }
        }
      }
 
      // Se vídeo não foi encontrado no cache, verifica se falhou antes
      if (!v) {
        // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
        if (failedVideosCache.has(videoURL)) {
          videoBox.style.display = "none";
          return;
        }
        // Para effect, cria vídeo dinamicamente (vídeo alternativo por tempo)
        if (isEffectVideo && videoURL) {
          const baseIndex = el.dataset.index || "";
          const effectCacheKey =
             effectKey || `effect:${effectiveVideo || videoURL}`;
          v = videosCache.get(effectCacheKey);
          if (!v) {
            v = createVideoElement(videoURL, {
              index: baseIndex,
              effect: "1",
            });
             if (v) {
              videoBox.appendChild(v);
              videosCache.set(effectCacheKey, v);
              v.load();
             } else {
              videoBox.style.display = "none";
              return;
             }
             }
            const suppress = document.body.dataset.suppressSkillPlay === '1';
          }
            if (!suppress) {
        }
                v.play().catch(() => {
        // Para form_switch, cria vídeo dinamicamente (transições de forma são dinâmicas)
                });
        // Usa uma chave única que inclui o nome do arquivo do vídeo para garantir que cada vídeo diferente seja cacheado separadamente
        else if (isFormSwitch && videoURL) {
          const baseIndex = el.dataset.index || "";
          const videoFileName = effectiveVideo || "";
          // Chave única: index + nome do arquivo do vídeo
          const videoKey = baseIndex + ":" + videoFileName;
          v = videosCache.get(videoKey);
          if (!v) {
            v = createVideoElement(videoURL, { index: baseIndex });
            if (v) {
              videoBox.appendChild(v);
              videosCache.set(videoKey, v);
              v.load();
             } else {
             } else {
                try {
              videoBox.style.display = "none";
                    v.pause();
              return;
                } catch (e) {
                }
             }
             }
         } function activateSkill(el, options = {
          }
        }) {
        } else {
            const {
          // Vídeos normais devem estar pré-carregados
                openSubs = true
          videoBox.style.display = "none";
            } = options;
          return;
            const tip = document.querySelector('.skill-tooltip');
        }
            if (tip) {
      }
                tip.setAttribute('aria-hidden', 'true');
      videoBox.style.display = "block";
                tip.style.opacity = '0';
      v.style.display = "block";
                tip.style.left = '-9999px';
      try {
                tip.style.top = '-9999px';
        v.currentTime = 0;
            } const skillsRoot = document.getElementById('skills');
      } catch (e) { }
            const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {
      const suppress = document.body.dataset.suppressSkillPlay === "1";
            };
      if (!suppress) {
            const L = i18nMap[getLangKey()] || i18nMap.pt || {
        v.play().catch(() => { });
                cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível'
      } else {
            };
        try {
            const name = el.dataset.nome || el.dataset.name || '';
          v.pause();
            let weaponData = null;
         } catch (e) { }
            if (el.dataset.weapon) {
      }
                try {
    }
                    const parsed = JSON.parse(el.dataset.weapon);
    function activateSkill(el, options = {}) {
                    // Só considera weapon válido se for um objeto não vazio
      const { openSubs = true } = options;
                    if (parsed && typeof parsed === 'object' && Object.keys(parsed).length > 0) {
      const tip = document.querySelector(".skill-tooltip");
                        weaponData = parsed;
      if (tip) {
                    }
        tip.setAttribute("aria-hidden", "true");
                } catch (e) {
        tip.style.opacity = "0";
                    weaponData = null;
        tip.style.left = "-9999px";
                }
        tip.style.top = "-9999px";
            } const hasWeapon = !!weaponData;
      }
            const weaponEquipped = hasWeapon && globalWeaponEnabled;
      const skillsRoot = document.getElementById("skills");
            // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
      const i18nMap = skillsRoot
            let level = (el.dataset.level || '').trim();
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
            if (weaponEquipped && weaponData) {
        : {};
                // Verifica se weapon tem level definido (pode ser número ou string)
      const L = i18nMap[getLangKey()] ||
                const weaponLevel = weaponData.level;
        i18nMap.pt || {
                if (weaponLevel !== undefined && weaponLevel !== null && weaponLevel !== '') {
        cooldown: "Recarga",
                    level = weaponLevel.toString().trim();
        energy_gain: "Ganho de energia",
                 }
        energy_cost: "Custo de energia",
        power: "Poder",
        power_pvp: "Poder PvP",
        level: "Nível",
      };
      const name = el.dataset.nome || el.dataset.name || "";
      if (el.dataset.effect) {
        activateEffectFromIcon(el);
      }
      let weaponData = null;
      if (el.dataset.weapon) {
        try {
          const parsed = JSON.parse(el.dataset.weapon);
          // Só considera weapon válido se for um objeto não vazio
          if (
            parsed &&
            typeof parsed === "object" &&
            Object.keys(parsed).length > 0
          ) {
            weaponData = parsed;
          }
        } catch (e) {
          weaponData = null;
        }
      }
      const hasWeapon = !!weaponData;
      const weaponEquipped = hasWeapon && globalWeaponEnabled;
      // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
      let level = (el.dataset.level || "").trim();
      if (weaponEquipped && weaponData) {
        // Verifica se weapon tem level definido (pode ser número ou string)
        const weaponLevel = weaponData.level;
        if (
          weaponLevel !== undefined &&
          weaponLevel !== null &&
          weaponLevel !== ""
        ) {
          level = weaponLevel.toString().trim();
        }
      }
      const lang = getLangKey();
      const baseDescPack = {
        pt: el.dataset.descPt || "",
        en: el.dataset.descEn || "",
        es: el.dataset.descEs || "",
        pl: el.dataset.descPl || "",
      };
      const baseDesc =
        baseDescPack[lang] ||
        baseDescPack.pt ||
        baseDescPack.en ||
        baseDescPack.es ||
        baseDescPack.pl ||
        el.dataset.desc ||
        "";
      // Aceita tanto desc_i18n quanto desc para compatibilidade
      let weaponDescPack = {};
      if (weaponData) {
        if (weaponData.desc_i18n) {
          weaponDescPack = weaponData.desc_i18n;
        } else if (weaponData.desc) {
          weaponDescPack = weaponData.desc;
        } else {
          weaponDescPack = {
            pt: weaponData.descPt || "",
            en: weaponData.descEn || "",
            es: weaponData.descEs || "",
            pl: weaponData.descPl || "",
          };
        }
      }
      const weaponDesc =
        weaponDescPack[lang] ||
        weaponDescPack.pt ||
        weaponDescPack.en ||
        weaponDescPack.es ||
        weaponDescPack.pl ||
        "";
      const chosenDesc = weaponEquipped && weaponDesc ? weaponDesc : baseDesc;
      const descHtml = chosenDesc.replace(/'''(.*?)'''/g, "<b>$1</b>");
      let attrsHTML = "";
      if (weaponEquipped && weaponData) {
        // Faz merge: usa atributos da skill base e substitui apenas os que existem no weapon
        // Parse dos atributos da skill base (formato: "pve, pvp, energy, cooldown" ou "pve, pvp, energy, cooldown")
        const baseAttrsStr = el.dataset.atr || "";
        // Suporta tanto ", " quanto "," como separador
        const baseAttrs = baseAttrsStr.split(/\s*,\s*/).map((a) => a.trim());
        const basePve =
          baseAttrs[0] && baseAttrs[0] !== "-" ? baseAttrs[0] : "";
        const basePvp =
          baseAttrs[1] && baseAttrs[1] !== "-" ? baseAttrs[1] : "";
        const baseEnergy =
          baseAttrs[2] && baseAttrs[2] !== "-" ? baseAttrs[2] : "";
        const baseCd = baseAttrs[3] && baseAttrs[3] !== "-" ? baseAttrs[3] : "";
 
        // Valores do weapon (substituem os da skill base se existirem)
        const wPve =
          weaponData.powerpve !== undefined &&
            weaponData.powerpve !== null &&
            weaponData.powerpve !== ""
            ? weaponData.powerpve.toString().trim()
            : basePve;
        const wPvp =
          weaponData.powerpvp !== undefined &&
            weaponData.powerpvp !== null &&
            weaponData.powerpvp !== ""
            ? weaponData.powerpvp.toString().trim()
            : basePvp;
        const wEnergy =
          weaponData.energy !== undefined &&
            weaponData.energy !== null &&
            weaponData.energy !== ""
            ? weaponData.energy.toString().trim()
            : baseEnergy;
        const wCd =
          weaponData.cooldown !== undefined &&
            weaponData.cooldown !== null &&
            weaponData.cooldown !== ""
            ? weaponData.cooldown.toString().trim()
            : baseCd;
 
        // Monta string de atributos mesclados
        const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(",");
        attrsHTML = renderAttributes(mergedAttrs);
      } else {
        attrsHTML = el.dataset.atr
          ? renderAttributes(el.dataset.atr)
          : el.dataset.subattrs
            ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L)
            : "";
      }
      let flagsHTML = "";
      // Debug: verifica se é uma skill do Urouge (verifica pela URL da página ou pelo contexto)
      const isUrougePage =
        window.location.href &&
        window.location.href.toLowerCase().includes("urouge");
      if (el.dataset.flags) {
        try {
          const flags = JSON.parse(el.dataset.flags);
          if (flags && Array.isArray(flags) && flags.length > 0) {
            flagsHTML = renderFlagsRow(flags);
            // Debug para Urouge
            if (isUrougePage) {
              console.log("[Skills] Urouge - Flags processadas:", {
                 skill: name || el.dataset.nome,
                flags: flags,
                htmlLength: flagsHTML ? flagsHTML.length : 0,
                hasHTML: !!flagsHTML,
              });
             }
             }
             const lang = getLangKey();
          } else {
            const baseDescPack = {
             if (isUrougePage) {
                 pt: el.dataset.descPt || '', en: el.dataset.descEn || '', es: el.dataset.descEs || '', pl: el.dataset.descPl || ''
              console.warn("[Skills] Urouge - Flags inválidas:", {
            };
                 skill: name || el.dataset.nome,
            const baseDesc = baseDescPack[lang] || baseDescPack.pt || baseDescPack.en || baseDescPack.es || baseDescPack.pl || el.dataset.desc || '';
                flags: flags,
            // Aceita tanto desc_i18n quanto desc para compatibilidade
                rawData: el.dataset.flags,
            let weaponDescPack = {};
              });
            if (weaponData) {
                if (weaponData.desc_i18n) {
                    weaponDescPack = weaponData.desc_i18n;
                } else if (weaponData.desc) {
                    weaponDescPack = weaponData.desc;
                } else {
                    weaponDescPack = {
                        pt: weaponData.descPt || '', en: weaponData.descEn || '', es: weaponData.descEs || '', pl: weaponData.descPl || ''
                    };
                }
             }
             }
             const weaponDesc = weaponDescPack[lang] || weaponDescPack.pt || weaponDescPack.en || weaponDescPack.es || weaponDescPack.pl || '';
          }
             const chosenDesc = (weaponEquipped && weaponDesc) ? weaponDesc : baseDesc;
        } catch (e) {
             const descHtml = chosenDesc.replace(/'''(.*?)'''/g, '<b>$1</b>');
          console.warn(
            let attrsHTML = '';
             "[Skills] Erro ao processar flags:",
            if (weaponEquipped && weaponData) {
            e,
                // Faz merge: usa atributos da skill base e substitui apenas os que existem no weapon
            "flags data:",
                // Parse dos atributos da skill base (formato: "pve, pvp, energy, cooldown" ou "pve, pvp, energy, cooldown")
            el.dataset.flags,
                const baseAttrsStr = el.dataset.atr || '';
            "skill:",
                // Suporta tanto ", " quanto "," como separador
            name || el.dataset.nome
                const baseAttrs = baseAttrsStr.split(/\s*,\s*/).map(a => a.trim());
          );
                const basePve = baseAttrs[0] && baseAttrs[0] !== '-' ? baseAttrs[0] : '';
          if (isUrougePage) {
                const basePvp = baseAttrs[1] && baseAttrs[1] !== '-' ? baseAttrs[1] : '';
            console.error(
                const baseEnergy = baseAttrs[2] && baseAttrs[2] !== '-' ? baseAttrs[2] : '';
              "[Skills] Urouge - Erro ao processar flags:",
                const baseCd = baseAttrs[3] && baseAttrs[3] !== '-' ? baseAttrs[3] : '';
              e,
              "element:",
              el
             );
          }
        }
      } else {
        // Debug: verifica se deveria ter flags mas não tem (apenas para Urouge)
        if (isUrougePage) {
          console.warn("[Skills] Urouge - Skill sem data-flags:", {
            skill: name || el.dataset.nome,
            element: el,
             allAttributes: Array.from(el.attributes).map(
              (attr) => attr.name + "=" + attr.value
            ),
          });
        }
      }
      if (descBox) {
        descBox.innerHTML = `<div class="skill-title"><h3>${name}</h3></div>${level
          ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>`
          : ""
          }${attrsHTML}<div class="desc">${descHtml}</div>`;
      }
      if (hasWeapon) {
        applyWeaponBadge(el, weaponData, weaponEquipped);
      }
      if (videoBox) {
        const oldFlags = videoBox.querySelector(".skill-flags");
        if (oldFlags) oldFlags.remove();
        if (flagsHTML) {
          videoBox.insertAdjacentHTML("beforeend", flagsHTML);
          applyFlagTooltips(videoBox);
        }
      }
      const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
      currIcons.forEach((i) => i.classList.remove("active"));
      const subsRaw = el.dataset.subs || el.getAttribute("data-subs");
      const isFormSwitch =
        el.dataset.formSwitch === "true" ||
        el.getAttribute("data-form-switch") === "true";
      const isSwap =
        el.dataset.swap === "true" || el.getAttribute("data-swap") === "true";


                // Valores do weapon (substituem os da skill base se existirem)
      // Se for skill de swap, troca personagem (não marca como ativo, não processa como back)
                const wPve = (weaponData.powerpve !== undefined && weaponData.powerpve !== null && weaponData.powerpve !== '')
      if (isSwap && !isFormSwitch) {
                    ? weaponData.powerpve.toString().trim()
        handleSwapCharacter(el);
                    : basePve;
        // Não marca como ativo (similar ao form_switch)
                const wPvp = (weaponData.powerpvp !== undefined && weaponData.powerpvp !== null && weaponData.powerpvp !== '')
        return;
                    ? weaponData.powerpvp.toString().trim()
      }
                    : basePvp;
                const wEnergy = (weaponData.energy !== undefined && weaponData.energy !== null && weaponData.energy !== '')
                    ? weaponData.energy.toString().trim()
                    : baseEnergy;
                const wCd = (weaponData.cooldown !== undefined && weaponData.cooldown !== null && weaponData.cooldown !== '')
                    ? weaponData.cooldown.toString().trim()
                    : baseCd;


                // Monta string de atributos mesclados
      // Não marca como ativo se for form_switch (Change Form)
                const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(',');
      if (!isFormSwitch) {
                attrsHTML = renderAttributes(mergedAttrs);
        el.classList.add("active");
            } else {
        if (!autoplay && loadedVideos > 0) autoplay = true;
                attrsHTML = el.dataset.atr ? renderAttributes(el.dataset.atr) : (el.dataset.subattrs ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L) : '');
        window.__lastActiveSkillIcon = el;
            } let flagsHTML = '';
        // Lógica de vídeo: usa função centralizada que já considera weapon
            if (el.dataset.flags) {
        showVideoForIcon(el);
                try {
      }
                    const flags = JSON.parse(el.dataset.flags);
      const isBack =
                    if (flags && Array.isArray(flags) && flags.length > 0) {
        el.dataset.back === "true" ||
                        flagsHTML = renderFlagsRow(flags);
        el.getAttribute("data-back") === "true" ||
                        // Debug específico para Urouge
        el.dataset.back === "yes" ||
                        if (name && name.toLowerCase().includes('urouge') || (el.dataset.nome && el.dataset.nome.toLowerCase().includes('urouge'))) {
        el.getAttribute("data-back") === "yes" ||
                            console.log('[Skills] Urouge flags processadas:', flags, 'HTML gerado:', flagsHTML);
        el.dataset.back === "1" ||
                        }
        el.getAttribute("data-back") === "1";
                    } else {
 
                        if (name && name.toLowerCase().includes('urouge') || (el.dataset.nome && el.dataset.nome.toLowerCase().includes('urouge'))) {
      // Se for form_switch, alterna forma (não processa como back)
                            console.warn('[Skills] Urouge flags inválidas:', flags, 'raw data:', el.dataset.flags);
      if (isFormSwitch) {
                        }
        // Atualiza o data-video-file do ícone com o vídeo correto da transição baseado na forma atual
                    }
        try {
                } catch (e) {
          const formsJSON = skillsRoot.dataset.forms || "{}";
                     console.warn('[Skills] Erro ao processar flags:', e, 'flags data:', el.dataset.flags, 'element:', el);
          if (formsJSON && formsJSON !== "{}") {
            const tempFormsData = JSON.parse(formsJSON);
            const formNames = Object.keys(tempFormsData);
 
            // Busca vídeo de transição na skill Change Form
            const formVideosRaw =
              el.dataset.formVideos || el.getAttribute("data-form-videos");
            if (formVideosRaw) {
              const videos = JSON.parse(formVideosRaw);
              // Se currentForm é null, detecta qual forma está atualmente visível no DOM
              let formForVideo = currentForm;
              if (!formForVideo) {
                // Lê formsData se ainda não foi carregado
                if (Object.keys(formsData).length === 0) {
                  try {
                    formsData = JSON.parse(formsJSON);
                  } catch (e) {
                     formsData = {};
                  }
                 }
                 }
            } else {
                formForVideo = detectCurrentForm();
                 // Debug: verifica se deveria ter flags mas não tem
                 // Se ainda não conseguiu detectar, usa a primeira como fallback
                 if (name && name.toLowerCase().includes('urouge') || (el.dataset.nome && el.dataset.nome.toLowerCase().includes('urouge'))) {
                 if (!formForVideo && formNames.length > 0) {
                    console.warn('[Skills] Urouge skill sem data-flags:', name || el.dataset.nome, 'element:', el);
                  formForVideo = formNames[0];
                 }
                 }
            } if (descBox) {
              }
                 descBox.innerHTML = `<div class="skill-title"><h3>${name}</h3></div>${level ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>` : ''}${attrsHTML}<div class="desc">${descHtml}</div>`;
              const transitionVideo = formForVideo
            } if (hasWeapon) {
                 ? videos[formForVideo] || ""
                applyWeaponBadge(el, weaponData, weaponEquipped);
                : "";
            } if (videoBox) {
              if (transitionVideo && transitionVideo.trim() !== "") {
                const oldFlags = videoBox.querySelector('.skill-flags');
                 // Atualiza temporariamente o data-video-file (o sistema usa isso)
                if (oldFlags) oldFlags.remove();
                 el.dataset.videoFile = transitionVideo;
                if (flagsHTML) {
              }
                    videoBox.insertAdjacentHTML('beforeend', flagsHTML);
                    applyFlagTooltips(videoBox);
                 }
            } const currIcons = Array.from(iconsBar.querySelectorAll('.skill-icon'));
            currIcons.forEach(i => i.classList.remove('active'));
            const subsRaw = el.dataset.subs || el.getAttribute('data-subs');
            const isFormSwitch = el.dataset.formSwitch === 'true' || el.getAttribute('data-form-switch') === 'true';
            // Não marca como ativo se for form_switch (Change Form)
            if (!isFormSwitch) {
                 el.classList.add('active');
                if (!autoplay && loadedVideos > 0) autoplay = true;
                window.__lastActiveSkillIcon = el;
                // Lógica de vídeo: usa função centralizada que já considera weapon
                showVideoForIcon(el);
             }
             }
            const isBack = el.dataset.back === 'true' || el.getAttribute('data-back') === 'true' || el.dataset.back === 'yes' || el.getAttribute('data-back') === 'yes' || el.dataset.back === '1' || el.getAttribute('data-back') === '1';
          }
        } catch (e) {
          console.error("[Forms] Erro ao processar vídeo de transição:", e);
        }


            // Se for form_switch, alterna forma (não processa como back)
        // Usa o sistema normal de vídeo (mesmo que skills normais)
             if (isFormSwitch) {
        showVideoForIcon(el);
                // Atualiza o data-video-file do ícone com o vídeo correto da transição baseado na forma atual
        switchForm();
                try {
        return;
                    const formsJSON = skillsRoot.dataset.forms || '{}';
      }
                    if (formsJSON && formsJSON !== '{}') {
 
                        const tempFormsData = JSON.parse(formsJSON);
      if (isBack && !isFormSwitch && barStack.length) {
                        const formNames = Object.keys(tempFormsData);
        const prev = barStack.pop();
        // Restaura currentForm se estava salvo no snapshot
        if (prev.currentForm !== undefined) {
          currentForm = prev.currentForm;
        }
        renderBarFromItems(prev);
        const btn = document.querySelector(".skills-back-wrapper");
        if (btn) btn.style.display = barStack.length ? "block" : "none";
        return;
      }
      if (openSubs && subsRaw && subsRaw.trim() !== "") {
        if (barStack.length && barStack[barStack.length - 1].parentIcon === el)
          return;
        try {
          const subs = JSON.parse(subsRaw);
          pushSubBarFrom(subs, el);
        } catch { }
      }
    }
    function wireClicksForCurrentBar() {
      const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
      currIcons.forEach((el) => {
        if (
          el.dataset.weaponToggle === "1" ||
          el.classList.contains("weapon-bar-toggle")
        )
          return;
        if (el.dataset.wired) return;
        el.dataset.wired = "1";
        const label = el.dataset.nome || el.dataset.name || "";
        el.setAttribute("aria-label", label);
        if (el.hasAttribute("title")) el.removeAttribute("title");
        const img = el.querySelector("img");
        if (img) {
          img.setAttribute("alt", "");
          if (img.hasAttribute("title")) img.removeAttribute("title");
        }
        el.addEventListener("click", () => {
          activateSkill(el, {
            openSubs: true,
          });
        });
      });
      wireTooltipsForNewIcons();
      applyEffectClasses();
    }
    function animateIconsBarEntrance() {
      Array.from(iconsBar.children).forEach((c, i) => {
        c.style.opacity = "0";
        c.style.transform = "translateY(6px)";
        requestAnimationFrame(() => {
          setTimeout(() => {
            c.style.transition = "opacity .18s ease, transform .18s ease";
            c.style.opacity = "1";
            c.style.transform = "translateY(0)";
          }, i * 24);
        });
      });
    }
    function snapshotCurrentBarItemsFromDOM() {
      const items = Array.from(iconsBar.querySelectorAll(".skill-icon"))
        .filter((el) => el.dataset.weaponToggle !== "1")
        .map((el) => {
          const img = el.querySelector("img");
          const iconURL = img ? img.src : "";
          const subsRaw = el.dataset.subs || el.getAttribute("data-subs") || "";
          let subs = null;
          try {
            subs = subsRaw ? JSON.parse(subsRaw) : null;
          } catch {
            subs = null;
          }
          const subattrsRaw = el.dataset.subattrs || "";
          let flags = null;
          if (el.dataset.flags) {
            try {
              flags = JSON.parse(el.dataset.flags);
            } catch (e) { }
          }
          let weapon = null;
          if (el.dataset.weapon) {
            try {
              weapon = JSON.parse(el.dataset.weapon);
             } catch (e) { }
          }
          let effect = null;
          if (el.dataset.effect) {
            try {
              effect = JSON.parse(el.dataset.effect);
            } catch (e) { }
          }
          // Preserva data-form-videos para poder restaurar depois
          const formVideos =
            el.dataset.formVideos || el.getAttribute("data-form-videos") || "";
          return {
            name: el.dataset.nome || el.dataset.name || "",
            index: el.dataset.index || "",
            level: el.dataset.level || "",
            desc: el.dataset.desc || "",
            descPt: el.dataset.descPt || "",
            descEn: el.dataset.descEn || "",
            descEs: el.dataset.descEs || "",
            descPl: el.dataset.descPl || "",
            attrs: el.dataset.atr || el.dataset.attrs || "",
            video: el.dataset.video || "",
            iconURL,
            subs,
            subattrsStr: subattrsRaw,
            flags: flags,
            weapon: weapon,
            effect: effect,
            nested: el.dataset.nested || "",
            subName: el.dataset.subName || "",
            parentIndex: el.dataset.parentIndex || "",
            formSwitch:
              el.dataset.formSwitch ||
              el.getAttribute("data-form-switch") ||
              "",
            formVideos: formVideos,
          };
        });
      // Retorna objeto com items e currentForm para poder restaurar depois
      return { items, currentForm: currentForm };
    }
    function ensureBackButton() {
      const rail = iconsBar.closest(".top-rail.skills");
      if (!rail) return null;
      let wrap = rail.parentElement;
      if (
        !wrap ||
        !wrap.classList ||
        !wrap.classList.contains("skills-rail-wrap")
      ) {
        const parentNode = rail.parentNode;
        const newWrap = document.createElement("div");
        newWrap.className = "skills-rail-wrap";
        parentNode.insertBefore(newWrap, rail);
        newWrap.appendChild(rail);
        wrap = newWrap;
      }
      let backWrap = wrap.querySelector(".skills-back-wrapper");
      if (!backWrap) {
        backWrap = document.createElement("div");
        backWrap.className = "skills-back-wrapper";
        const btnInner = document.createElement("button");
        btnInner.className = "skills-back";
        btnInner.type = "button";
        btnInner.setAttribute("aria-label", "Voltar");
        btnInner.innerHTML =
          '<svg class="back-chevron" width="100%" height="100%" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" preserveAspectRatio="xMidYMid meet"><path d="M10 2L4 16L10 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 2L14 16L20 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M30 2L24 16L30 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
        backWrap.appendChild(btnInner);
        wrap.insertBefore(backWrap, rail);
        btnInner.addEventListener("click", () => {
          if (!barStack.length) return;
          const prev = barStack.pop();
          renderBarFromItems(prev);
          backWrap.style.display = barStack.length ? "block" : "none";
          wrap.classList.toggle("has-sub-bar", barStack.length > 0);
          if (!barStack.length) btnInner.classList.remove("peek");
        });
      }
      backWrap.style.display = barStack.length ? "block" : "none";
      wrap.classList.toggle("has-sub-bar", barStack.length > 0);
      const btnInner = backWrap.querySelector(".skills-back");
 
      // Calcula a posição do botão baseado na posição real do top-rail
      function updateBackButtonPosition() {
        if (!rail || !backWrap) return;
        const railRect = rail.getBoundingClientRect();
        const wrapRect = wrap.getBoundingClientRect();
        // Calcula a posição relativa do rail dentro do wrap
        const railLeft = railRect.left - wrapRect.left;
        // Posiciona o wrapper na borda esquerda do rail
        // O botão interno usa translateX(-97%) para ficar atrás da barra
        backWrap.style.left = railLeft + "px";
      }


                        // Busca vídeo de transição na skill Change Form
      // Atualiza a posição quando necessário
                        const formVideosRaw = el.dataset.formVideos || el.getAttribute('data-form-videos');
      if (backWrap.style.display !== "none") {
                        if (formVideosRaw) {
        // Usa requestAnimationFrame para garantir que o DOM foi atualizado
                            const videos = JSON.parse(formVideosRaw);
        requestAnimationFrame(() => {
                            // Se currentForm é null, detecta qual forma está atualmente visível no DOM
          updateBackButtonPosition();
                            let formForVideo = currentForm;
        });
                            if (!formForVideo) {
                                // Lê formsData se ainda não foi carregado
                                if (Object.keys(formsData).length === 0) {
                                    try {
                                        formsData = JSON.parse(formsJSON);
                                    } catch (e) {
                                        formsData = {};
                                    }
                                }
                                formForVideo = detectCurrentForm();
                                // Se ainda não conseguiu detectar, usa a primeira como fallback
                                if (!formForVideo && formNames.length > 0) {
                                    formForVideo = formNames[0];
                                }
                            }
                            const transitionVideo = formForVideo ? (videos[formForVideo] || '') : '';
                            if (transitionVideo && transitionVideo.trim() !== '') {
                                // Atualiza temporariamente o data-video-file (o sistema usa isso)
                                el.dataset.videoFile = transitionVideo;
                            }
                        }
                    }
                } catch (e) {
                    console.error('[Forms] Erro ao processar vídeo de transição:', e);
                }


                // Usa o sistema normal de vídeo (mesmo que skills normais)
        // Recalcula em resize e scroll
                showVideoForIcon(el);
        if (!backWrap.dataset.positionWired) {
                switchForm();
          backWrap.dataset.positionWired = "1";
                return;
          const updateOnResize = () => {
            if (backWrap.style.display !== "none") {
              updateBackButtonPosition();
            }
          };
          window.addEventListener("resize", updateOnResize);
          const observer = new ResizeObserver(() => {
            if (backWrap.style.display !== "none") {
              updateBackButtonPosition();
             }
             }
          });
          if (rail) observer.observe(rail);
          observer.observe(wrap);
        }
      }


            if (isBack && !isFormSwitch && barStack.length) {
      return btnInner;
                const prev = barStack.pop();
    }
                // Restaura currentForm se estava salvo no snapshot
    function renderBarFromItems(itemsOrSnapshot) {
                if (prev.currentForm !== undefined) {
      // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
                    currentForm = prev.currentForm;
      let items, savedCurrentForm;
                }
      if (Array.isArray(itemsOrSnapshot)) {
                renderBarFromItems(prev);
        items = itemsOrSnapshot;
                const btn = document.querySelector('.skills-back-wrapper');
        savedCurrentForm = null;
                if (btn) btn.style.display = barStack.length ? 'block' : 'none';
      } else if (itemsOrSnapshot && itemsOrSnapshot.items) {
                return;
        items = itemsOrSnapshot.items;
             } if (openSubs && subsRaw && subsRaw.trim() !== '') {
        savedCurrentForm = itemsOrSnapshot.currentForm;
                if (barStack.length && barStack[barStack.length - 1].parentIcon === el) return;
      } else {
                try {
        items = [];
                    const subs = JSON.parse(subsRaw);
        savedCurrentForm = null;
                    pushSubBarFrom(subs, el);
      }
                } catch {
      // Restaura currentForm se estava salvo
                 }
      if (savedCurrentForm !== undefined && savedCurrentForm !== null) {
        currentForm = savedCurrentForm;
      }
      const tip = document.querySelector(".skill-tooltip");
      if (tip) {
        tip.setAttribute("aria-hidden", "true");
        tip.style.opacity = "0";
        tip.style.left = "-9999px";
        tip.style.top = "-9999px";
      }
      iconsBar.innerHTML = "";
      items.forEach((it, idx) => {
        const node = document.createElement("div");
        node.className = "skill-icon";
        node.dataset.nome = it.name || "";
        if (it.index) node.dataset.index = it.index;
        if (it.level) node.dataset.level = it.level;
        if (it.desc) node.dataset.desc = it.desc;
        if (it.descPt) node.dataset.descPt = it.descPt;
        if (it.descEn) node.dataset.descEn = it.descEn;
        if (it.descEs) node.dataset.descEs = it.descEs;
        if (it.descPl) node.dataset.descPl = it.descPl;
        if (it.attrs) node.dataset.atr = it.attrs;
        if (it.video) node.dataset.video = it.video;
        if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
        if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr;
        if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
        if (
          it.weapon &&
          typeof it.weapon === "object" &&
          Object.keys(it.weapon).length > 0
        ) {
          node.dataset.weapon = JSON.stringify(it.weapon);
        }
        if (it.effect && typeof it.effect === "object") {
          try {
            node.dataset.effect = JSON.stringify(it.effect);
          } catch (e) { }
        }
        // Restaura informações de subskill importantes para busca de vídeos
        if (it.nested) node.dataset.nested = it.nested;
        if (it.subName) node.dataset.subName = it.subName;
        if (it.parentIndex) node.dataset.parentIndex = it.parentIndex;
        if (!it.index && !it.nested) node.dataset.nested = "1";
        // Restaura formSwitch (Change Form)
        if (it.formSwitch) {
          node.dataset.formSwitch = it.formSwitch;
          node.setAttribute("data-form-switch", it.formSwitch);
        }
        // Restaura formVideos (vídeos de transição de forma)
        if (it.formVideos) {
          node.dataset.formVideos = it.formVideos;
          node.setAttribute("data-form-videos", it.formVideos);
        }
        const img = document.createElement("img");
        img.alt = "";
        img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : "");
        img.decoding = "async";
        img.loading = "lazy";
        img.width = 50;
        img.height = 50;
        node.appendChild(img);
        iconsBar.appendChild(node);
      });
      animateIconsBarEntrance();
      wireClicksForCurrentBar();
      // Remove qualquer toggle antigo que possa aparecer
      const oldToggle = iconsBar.querySelector(".weapon-bar-toggle");
      if (oldToggle) oldToggle.remove();
      // Reaplica classes de weapon após renderizar barra
      reapplyWeaponClassesToBar();
      const b = ensureBackButton();
      if (b) b.classList.add("peek");
      // Atualiza a posição do botão back após a barra ser renderizada
      requestAnimationFrame(() => {
        const backWrap = document.querySelector(".skills-back-wrapper");
        if (backWrap && backWrap.style.display !== "none") {
          const rail = iconsBar.closest(".top-rail.skills");
          const wrap = rail ? rail.parentElement : null;
          if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
            const railRect = rail.getBoundingClientRect();
            const wrapRect = wrap.getBoundingClientRect();
            const railLeft = railRect.left - wrapRect.left;
            // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
            backWrap.style.left = railLeft + "px";
          }
        }
      });
    }
    function pushSubBarFrom(subs, parentIconEl) {
      const tip = document.querySelector(".skill-tooltip");
      if (tip) {
        tip.setAttribute("aria-hidden", "true");
        tip.style.opacity = "0";
        tip.style.left = "-9999px";
        tip.style.top = "-9999px";
      }
      const parentNameSnapshot = parentIconEl
        ? parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
        : "";
      // Para subskills, usa parentIndex; para skills principais, usa index
      const parentIndexSnapshot = parentIconEl
        ? parentIconEl.dataset.nested === "1"
          ? parentIconEl.dataset.parentIndex || ""
          : parentIconEl.dataset.index || ""
        : "";
      const snapshot = snapshotCurrentBarItemsFromDOM();
      barStack.push({
        items: snapshot.items,
        currentForm: snapshot.currentForm,
        parentIcon: parentIconEl,
        parentName: parentNameSnapshot,
        parentIndex: parentIndexSnapshot,
      });
      ensureBackButton();
      const langKey = getLangKey();
      let cacheKey = null;
      if (parentIconEl) {
        cacheKey = parentIconEl.dataset.subCacheKey || null;
        if (!cacheKey) {
          if (parentIconEl.dataset.index) {
            cacheKey = `idx:${parentIconEl.dataset.index}`;
          } else {
            const slug = slugify(
              parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
            );
             if (slug) cacheKey = `slug:${slug}`;
          }
          if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey;
        }
      }
      if (cacheKey) {
        const cached = subBarTemplateCache.get(cacheKey);
        if (cached && cached.lang === langKey) {
          iconsBar.innerHTML = "";
          const clone = cached.template.cloneNode(true);
          iconsBar.appendChild(clone);
          animateIconsBarEntrance();
          wireClicksForCurrentBar();
          // Remove qualquer toggle antigo que possa aparecer
          const oldToggle2 = iconsBar.querySelector(".weapon-bar-toggle");
          if (oldToggle2) oldToggle2.remove();
          // Reaplica classes de weapon após renderizar do cache
          reapplyWeaponClassesToBar();
          const cachedBtn = ensureBackButton();
          if (cachedBtn) cachedBtn.classList.add("peek");
          return;
        }
      }
      const skillsRoot = document.getElementById("skills");
      const i18nMap = skillsRoot
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
        : {};
      const L = i18nMap[getLangKey()] ||
        i18nMap.pt || {
        cooldown: "Recarga",
        energy_gain: "Ganho de energia",
        energy_cost: "Custo de energia",
        power: "Poder",
        power_pvp: "Poder PvP",
        level: "Nível",
      };
      const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
      const items = (hydratedSubs || [])
        .filter((s) => {
          // Filtra só se não tem nada útil
          const hasName = (s.name || s.n || "").trim() !== "";
          const hasIcon = (s.icon || "").trim() !== "";
          const hasRef = (s.refS || s.refM || "").toString().trim() !== "";
          return hasName || hasIcon || hasRef;
        })
        .map((s) => {
          const name = (s.name || s.n || "").trim();
          const desc = chooseDescFrom(s).replace(/'''(.*?)'''/g, "<b>$1</b>");
          const attrsHTML = renderSubAttributesFromObj(s, L);
          return {
            name,
            level: (s.level || "").toString().trim(),
            desc,
            descPt: s.descPt || (s.desc_i18n && s.desc_i18n.pt) || "",
            descEn: s.descEn || (s.desc_i18n && s.desc_i18n.en) || "",
            descEs: s.descEs || (s.desc_i18n && s.desc_i18n.es) || "",
            descPl: s.descPl || (s.desc_i18n && s.desc_i18n.pl) || "",
            attrs: "",
            icon: s.icon || "",
            iconURL: s.icon ? filePathURL(s.icon) : "",
            video: s.video ? filePathURL(s.video) : "",
            subs: Array.isArray(s.subs) ? s.subs : null,
            subattrs: s,
            flags: Array.isArray(s.flags) ? s.flags : null,
            back:
              s.back === true ||
                s.back === "true" ||
                s.back === "yes" ||
                s.back === "1"
                ? "true"
                : null,
            weapon: s.weapon || null,
          };
        });
      const fragment = document.createDocumentFragment();
      items.forEach((it, iIdx) => {
        const node = document.createElement("div");
        node.className = "skill-icon";
        node.dataset.nested = "1";
        node.dataset.nome = it.name || "";
        node.dataset.parentIndex = parentIndexSnapshot;
        // Constrói o caminho completo para sub-subskills
        // IMPORTANTE: O cache NÃO inclui o nome da skill principal no caminho
        // Formato do cache: "SubskillName" ou "SubskillName:SubSubskillName" (sem o nome da skill principal)
        let fullPath = it.name || "";
        if (parentIconEl) {
          // Se o pai é uma subskill (tem nested), adiciona o nome do pai ao caminho
          if (parentIconEl.dataset.nested === "1") {
            // Se o pai tem subName, usa ele (já está no formato correto, sem nome da skill principal)
            if (
              parentIconEl.dataset.subName &&
              parentIconEl.dataset.subName.trim() !== ""
            ) {
              fullPath = `${parentIconEl.dataset.subName}:${it.name || ""}`;
            } else {
              // Se o pai não tem subName, é a primeira subskill, então usa apenas o nome do pai
              const parentName = (
                parentIconEl.dataset.nome ||
                parentIconEl.dataset.name ||
                 ""
              ).trim();
              fullPath = `${parentName}:${it.name || ""}`;
             }
             }
        } function wireClicksForCurrentBar() {
          }
            const currIcons = Array.from(iconsBar.querySelectorAll('.skill-icon'));
          // Se o pai é uma skill principal (não é nested), o caminho é apenas o nome da subskill atual
            currIcons.forEach(el => {
          // (já está definido como it.name acima)
                if (el.dataset.weaponToggle === '1' || el.classList.contains('weapon-bar-toggle')) return;
        }
                if (el.dataset.wired) return;
        node.dataset.subName = fullPath;
                el.dataset.wired = '1';
        const subSlug = slugify(it.name || "");
                const label = el.dataset.nome || el.dataset.name || '';
        if (subSlug) node.dataset.slug = subSlug;
                el.setAttribute('aria-label', label);
        if (it.level) node.dataset.level = it.level;
                if (el.hasAttribute('title')) el.removeAttribute('title');
        if (it.desc) node.dataset.desc = it.desc;
                const img = el.querySelector('img');
        if (it.descPt) node.dataset.descPt = it.descPt;
                if (img) {
        if (it.descEn) node.dataset.descEn = it.descEn;
                    img.setAttribute('alt', '');
        if (it.descEs) node.dataset.descEs = it.descEs;
                    if (img.hasAttribute('title')) img.removeAttribute('title');
        if (it.descPl) node.dataset.descPl = it.descPl;
                } el.addEventListener('click', () => {
        if (it.video) node.dataset.video = it.video;
                    activateSkill(el, {
        if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
                        openSubs: true
        if (it.subattrs) node.dataset.subattrs = JSON.stringify(it.subattrs);
                    });
        if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
                });
        if (it.back) node.dataset.back = it.back;
            });
        if (
            wireTooltipsForNewIcons();
          it.weapon &&
         } function animateIconsBarEntrance() {
          typeof it.weapon === "object" &&
            Array.from(iconsBar.children).forEach((c, i) => {
          Object.keys(it.weapon).length > 0
                c.style.opacity = '0';
        ) {
                c.style.transform = 'translateY(6px)';
          try {
                requestAnimationFrame(() => {
            node.dataset.weapon = JSON.stringify(it.weapon);
                    setTimeout(() => {
          } catch (e) {
                        c.style.transition = 'opacity .18s ease, transform .18s ease';
            console.error(
                        c.style.opacity = '1';
              "[Skills] Erro ao serializar weapon de subskill",
                        c.style.transform = 'translateY(0)';
              it.name,
                    }, i * 24);
              e
                });
            );
            });
          }
        } function snapshotCurrentBarItemsFromDOM() {
        }
            const items = Array.from(iconsBar.querySelectorAll('.skill-icon')).filter(el => el.dataset.weaponToggle !== '1').map(el => {
        const img = document.createElement("img");
                const img = el.querySelector('img');
         img.alt = "";
                const iconURL = img ? img.src : '';
        img.src = it.iconURL;
                const subsRaw = el.dataset.subs || el.getAttribute('data-subs') || '';
        img.decoding = "async";
                let subs = null;
        img.loading = "lazy";
                try {
        img.width = 50;
                    subs = subsRaw ? JSON.parse(subsRaw) : null;
        img.height = 50;
                } catch {
        node.appendChild(img);
                    subs = null;
        fragment.appendChild(node);
                } const subattrsRaw = el.dataset.subattrs || '';
      });
                let flags = null;
      const templateClone = fragment.cloneNode(true);
                if (el.dataset.flags) {
      iconsBar.innerHTML = "";
                    try {
      iconsBar.appendChild(fragment);
                        flags = JSON.parse(el.dataset.flags);
      animateIconsBarEntrance();
                    } catch (e) {
      wireClicksForCurrentBar();
                    }
      // Remove qualquer toggle antigo que possa aparecer
                } let weapon = null;
      const oldToggle3 = iconsBar.querySelector(".weapon-bar-toggle");
                if (el.dataset.weapon) {
      if (oldToggle3) oldToggle3.remove();
                    try {
      // Reaplica classes de weapon após renderizar subskills
                        weapon = JSON.parse(el.dataset.weapon);
      reapplyWeaponClassesToBar();
                    } catch (e) {
      const b2 = ensureBackButton();
                    }
      if (b2) b2.classList.add("peek");
                }
      // Atualiza a posição do botão back após subskills serem renderizadas
                // Preserva data-form-videos para poder restaurar depois
      requestAnimationFrame(() => {
                const formVideos = el.dataset.formVideos || el.getAttribute('data-form-videos') || '';
        const backWrap = document.querySelector(".skills-back-wrapper");
                return {
        if (backWrap && backWrap.style.display !== "none") {
                    name: el.dataset.nome || el.dataset.name || '', index: el.dataset.index || '', level: el.dataset.level || '', desc: el.dataset.desc || '', descPt: el.dataset.descPt || '', descEn: el.dataset.descEn || '', descEs: el.dataset.descEs || '', descPl: el.dataset.descPl || '', attrs: el.dataset.atr || el.dataset.attrs || '', video: el.dataset.video || '', iconURL, subs, subattrsStr: subattrsRaw, flags: flags, weapon: weapon, nested: el.dataset.nested || '', subName: el.dataset.subName || '', parentIndex: el.dataset.parentIndex || '', formSwitch: el.dataset.formSwitch || el.getAttribute('data-form-switch') || '', formVideos: formVideos
          const rail = iconsBar.closest(".top-rail.skills");
                };
          const wrap = rail ? rail.parentElement : null;
            });
          if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
            // Retorna objeto com items e currentForm para poder restaurar depois
            const railRect = rail.getBoundingClientRect();
            return { items, currentForm: currentForm };
            const wrapRect = wrap.getBoundingClientRect();
        } function ensureBackButton() {
            const railLeft = railRect.left - wrapRect.left;
            const rail = iconsBar.closest('.top-rail.skills');
            // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
            if (!rail) return null;
            backWrap.style.left = railLeft + "px";
            let wrap = rail.parentElement;
          }
            if (!wrap || !wrap.classList || !wrap.classList.contains('skills-rail-wrap')) {
        }
                const parentNode = rail.parentNode;
      });
                const newWrap = document.createElement('div');
      if (cacheKey) {
                newWrap.className = 'skills-rail-wrap';
        subBarTemplateCache.set(cacheKey, {
                parentNode.insertBefore(newWrap, rail);
          template: templateClone,
                newWrap.appendChild(rail);
          lang: langKey,
                wrap = newWrap;
        });
             } let backWrap = wrap.querySelector('.skills-back-wrapper');
      }
             if (!backWrap) {
    }
                backWrap = document.createElement('div');
    window.addEventListener("gla:langChanged", () => {
                backWrap.className = 'skills-back-wrapper';
      subBarTemplateCache.clear();
                const btnInner = document.createElement('button');
      const skillsRoot = document.getElementById("skills");
                btnInner.className = 'skills-back';
      const i18nMap = skillsRoot
                btnInner.type = 'button';
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
                btnInner.setAttribute('aria-label', 'Voltar');
        : {};
                btnInner.innerHTML = '<svg class="back-chevron" width="100%" height="100%" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" preserveAspectRatio="xMidYMid meet"><path d="M10 2L4 16L10 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 2L14 16L20 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M30 2L24 16L30 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
      const lang = getLangKey();
                backWrap.appendChild(btnInner);
      Array.from(iconsBar.querySelectorAll(".skill-icon")).forEach((icon) => {
                wrap.insertBefore(backWrap, rail);
        const pack = {
                btnInner.addEventListener('click', () => {
          pt: icon.dataset.descPt || "",
                    if (!barStack.length) return;
          en: icon.dataset.descEn || "",
                    const prev = barStack.pop();
          es: icon.dataset.descEs || "",
                    renderBarFromItems(prev);
          pl: icon.dataset.descPl || "",
                    backWrap.style.display = barStack.length ? 'block' : 'none';
        };
                    wrap.classList.toggle('has-sub-bar', barStack.length > 0);
        const chosen = (
                    if (!barStack.length) btnInner.classList.remove('peek');
          pack[lang] ||
                });
          pack.pt ||
            } backWrap.style.display = barStack.length ? 'block' : 'none';
          pack.en ||
            wrap.classList.toggle('has-sub-bar', barStack.length > 0);
          pack.es ||
            const btnInner = backWrap.querySelector('.skills-back');
          pack.pl ||
          icon.dataset.desc ||
          ""
        ).trim();
        if (chosen) icon.dataset.desc = chosen;
      });
      barStack.forEach((frame) => {
        (frame.items || []).forEach((it) => {
          const pack = {
            pt: it.descPt,
            en: it.descEn,
            es: it.descEs,
            pl: it.descPl,
          };
          const chosen =
             pack[lang] ||
             pack.pt ||
            pack.en ||
            pack.es ||
            pack.pl ||
            it.desc ||
            "";
          it.desc = chosen;
        });
      });
      if (descBox) {
        applyFlagTooltips(descBox);
      }
      const activeIcon = window.__lastActiveSkillIcon;
      if (activeIcon && activeIcon.dataset.weapon) {
        activateSkill(activeIcon, {
          openSubs: false,
        });
      }
    });
    wireClicksForCurrentBar();


            // Calcula a posição do botão baseado na posição real do top-rail
    // Inicializa o sistema de swap de personagens (genérico)
            function updateBackButtonPosition() {
    // Aguarda um pouco para garantir que todos os ícones foram renderizados
                if (!rail || !backWrap) return;
    setTimeout(() => {
                const railRect = rail.getBoundingClientRect();
      initializeActiveCharacter();
                const wrapRect = wrap.getBoundingClientRect();
    }, 100);
                // Calcula a posição relativa do rail dentro do wrap
                const railLeft = railRect.left - wrapRect.left;
                // Posiciona o wrapper na borda esquerda do rail
                // O botão interno usa translateX(-97%) para ficar atrás da barra
                backWrap.style.left = railLeft + 'px';
            }


            // Atualiza a posição quando necessário
    const b0 = ensureBackButton();
            if (backWrap.style.display !== 'none') {
    if (b0) {
                // Usa requestAnimationFrame para garantir que o DOM foi atualizado
      b0.classList.add("peek");
                requestAnimationFrame(() => {
      b0.style.alignSelf = "stretch";
                    updateBackButtonPosition();
    }
                });


                // Recalcula em resize e scroll
    // Move inicialização de tooltip para requestIdleCallback (não crítico)
                if (!backWrap.dataset.positionWired) {
    if ("requestIdleCallback" in window) {
                    backWrap.dataset.positionWired = '1';
      requestIdleCallback(
                    const updateOnResize = () => {
        () => {
                        if (backWrap.style.display !== 'none') {
          (function initSkillTooltip() {
                            updateBackButtonPosition();
            if (document.querySelector(".skill-tooltip")) return;
                        }
            const tip = document.createElement("div");
                    };
            tip.className = "skill-tooltip";
                    window.addEventListener('resize', updateOnResize);
            tip.setAttribute("role", "tooltip");
                    const observer = new ResizeObserver(() => {
            tip.setAttribute("aria-hidden", "true");
                        if (backWrap.style.display !== 'none') {
            document.body.appendChild(tip);
                            updateBackButtonPosition();
            const lockUntilRef = {
                        }
              value: 0,
                    });
            };
                    if (rail) observer.observe(rail);
            function measureAndPos(el) {
                    observer.observe(wrap);
              if (!el || tip.getAttribute("aria-hidden") === "true") return;
                }
              tip.style.left = "0px";
              tip.style.top = "0px";
              const rect = el.getBoundingClientRect();
              const tr = tip.getBoundingClientRect();
              let left = Math.round(rect.left + (rect.width - tr.width) / 2);
              left = Math.max(
                8,
                Math.min(left, window.innerWidth - tr.width - 8)
              );
              const coarse =
                (window.matchMedia &&
                  matchMedia("(pointer: coarse)").matches) ||
                window.innerWidth <= 600;
              let top = coarse
                ? Math.round(rect.bottom + 10)
                : Math.round(rect.top - tr.height - 8);
              if (top < 8) top = Math.round(rect.bottom + 10);
              tip.style.left = left + "px";
              tip.style.top = top + "px";
             }
             }
 
             function show(el, text) {
             return btnInner;
              tip.textContent = text || "";
        } function renderBarFromItems(itemsOrSnapshot) {
              tip.setAttribute("aria-hidden", "false");
            // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
              measureAndPos(el);
            let items, savedCurrentForm;
              tip.style.opacity = "1";
            if (Array.isArray(itemsOrSnapshot)) {
                items = itemsOrSnapshot;
                savedCurrentForm = null;
            } else if (itemsOrSnapshot && itemsOrSnapshot.items) {
                items = itemsOrSnapshot.items;
                savedCurrentForm = itemsOrSnapshot.currentForm;
            } else {
                items = [];
                savedCurrentForm = null;
             }
             }
             // Restaura currentForm se estava salvo
             function hide() {
            if (savedCurrentForm !== undefined && savedCurrentForm !== null) {
              tip.setAttribute("aria-hidden", "true");
                currentForm = savedCurrentForm;
              tip.style.opacity = "0";
              tip.style.left = "-9999px";
              tip.style.top = "-9999px";
             }
             }
             const tip = document.querySelector('.skill-tooltip');
             window.__globalSkillTooltip = {
            if (tip) {
              show,
                tip.setAttribute('aria-hidden', 'true');
              hide,
                tip.style.opacity = '0';
              measureAndPos,
                tip.style.left = '-9999px';
              lockUntil: lockUntilRef,
                tip.style.top = '-9999px';
            };
             } iconsBar.innerHTML = '';
            Array.from(
            items.forEach((it, idx) => {
              document.querySelectorAll(".icon-bar .skill-icon")
                const node = document.createElement('div');
             ).forEach((icon) => {
                 node.className = 'skill-icon';
              if (
                node.dataset.nome = it.name || '';
                 icon.dataset.weaponToggle === "1" ||
                 if (it.index) node.dataset.index = it.index;
                 icon.classList.contains("weapon-bar-toggle")
                if (it.level) node.dataset.level = it.level;
              )
                if (it.desc) node.dataset.desc = it.desc;
                return;
                 if (it.descPt) node.dataset.descPt = it.descPt;
              if (icon.dataset.tipwired) return;
                if (it.descEn) node.dataset.descEn = it.descEn;
              icon.dataset.tipwired = "1";
                if (it.descEs) node.dataset.descEs = it.descEs;
              const label =
                 if (it.descPl) node.dataset.descPl = it.descPl;
                 icon.dataset.nome || icon.dataset.name || icon.title || "";
                if (it.attrs) node.dataset.atr = it.attrs;
              if (label && !icon.hasAttribute("aria-label"))
                if (it.video) node.dataset.video = it.video;
                 icon.setAttribute("aria-label", label);
                if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
              if (icon.hasAttribute("title")) icon.removeAttribute("title");
                if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr;
              const img = icon.querySelector("img");
                 if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
              if (img) {
                 if (it.weapon && typeof it.weapon === 'object' && Object.keys(it.weapon).length > 0) {
                const imgAlt = img.getAttribute("alt") || "";
                    node.dataset.weapon = JSON.stringify(it.weapon);
                 const imgTitle = img.getAttribute("title") || "";
                 }
                 if (!label && (imgAlt || imgTitle))
                // Restaura informações de subskill importantes para busca de vídeos
                  icon.setAttribute("aria-label", imgAlt || imgTitle);
                if (it.nested) node.dataset.nested = it.nested;
                 img.setAttribute("alt", "");
                 if (it.subName) node.dataset.subName = it.subName;
                 if (img.hasAttribute("title")) img.removeAttribute("title");
                if (it.parentIndex) node.dataset.parentIndex = it.parentIndex;
              }
                if (!it.index && !it.nested) node.dataset.nested = '1';
              icon.addEventListener("mouseenter", () => show(icon, label));
                // Restaura formSwitch (Change Form)
              icon.addEventListener("mousemove", () => {
                if (it.formSwitch) {
                 if (performance.now() >= lockUntilRef.value)
                    node.dataset.formSwitch = it.formSwitch;
                  measureAndPos(icon);
                    node.setAttribute('data-form-switch', it.formSwitch);
              });
                }
              icon.addEventListener("click", () => {
                // Restaura formVideos (vídeos de transição de forma)
                 lockUntilRef.value = performance.now() + 240;
                 if (it.formVideos) {
                 measureAndPos(icon);
                    node.dataset.formVideos = it.formVideos;
              });
                    node.setAttribute('data-form-videos', it.formVideos);
              icon.addEventListener("mouseleave", hide);
                }
                const img = document.createElement('img');
                img.alt = '';
                img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : '');
                img.decoding = 'async';
                 img.loading = 'lazy';
                img.width = 50;
                 img.height = 50;
                node.appendChild(img);
                iconsBar.appendChild(node);
             });
             });
             animateIconsBarEntrance();
             Array.from(
             wireClicksForCurrentBar();
              document.querySelectorAll(".subskills-rail .subicon")
            // Remove qualquer toggle antigo que possa aparecer
             ).forEach((sub) => {
            const oldToggle = iconsBar.querySelector('.weapon-bar-toggle');
              if (sub.dataset.tipwired) return;
            if (oldToggle) oldToggle.remove();
              sub.dataset.tipwired = "1";
            // Reaplica classes de weapon após renderizar barra
              const label =
            reapplyWeaponClassesToBar();
                sub.getAttribute("title") ||
            const b = ensureBackButton();
                sub.getAttribute("aria-label") ||
            if (b) b.classList.add('peek');
                "";
            // Atualiza a posição do botão back após a barra ser renderizada
              if (label && !sub.hasAttribute("aria-label"))
            requestAnimationFrame(() => {
                sub.setAttribute("aria-label", label);
                const backWrap = document.querySelector('.skills-back-wrapper');
              if (sub.hasAttribute("title")) sub.removeAttribute("title");
                 if (backWrap && backWrap.style.display !== 'none') {
              sub.addEventListener("mouseenter", () => show(sub, label));
                    const rail = iconsBar.closest('.top-rail.skills');
              sub.addEventListener("mousemove", () => {
                    const wrap = rail ? rail.parentElement : null;
                 if (performance.now() >= lockUntilRef.value) measureAndPos(sub);
                    if (rail && wrap && wrap.classList.contains('skills-rail-wrap')) {
              });
                        const railRect = rail.getBoundingClientRect();
              sub.addEventListener("click", () => {
                        const wrapRect = wrap.getBoundingClientRect();
                lockUntilRef.value = performance.now() + 240;
                        const railLeft = railRect.left - wrapRect.left;
                measureAndPos(sub);
                        // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
              });
                        backWrap.style.left = railLeft + 'px';
              sub.addEventListener("mouseleave", hide);
                    }
                }
             });
             });
        } function pushSubBarFrom(subs, parentIconEl) {
            window.addEventListener(
            const tip = document.querySelector('.skill-tooltip');
              "scroll",
            if (tip) {
              () => {
                 tip.setAttribute('aria-hidden', 'true');
                const visible = document.querySelector(
                tip.style.opacity = '0';
                  '.skill-tooltip[aria-hidden="false"]'
                tip.style.left = '-9999px';
                );
                tip.style.top = '-9999px';
                if (!visible) return;
            } const parentNameSnapshot = parentIconEl ? (parentIconEl.dataset.nome || parentIconEl.dataset.name || '') : '';
                 const target =
             // Para subskills, usa parentIndex; para skills principais, usa index
                  document.querySelector(".subskills-rail .subicon:hover") ||
            const parentIndexSnapshot = parentIconEl ? (
                  document.querySelector(".subskills-rail .subicon.active") ||
                 parentIconEl.dataset.nested === '1'
                  document.querySelector(".icon-bar .skill-icon:hover") ||
                    ? (parentIconEl.dataset.parentIndex || '')
                  document.querySelector(".icon-bar .skill-icon.active");
                    : (parentIconEl.dataset.index || '')
                measureAndPos(target);
            ) : '';
              },
            const snapshot = snapshotCurrentBarItemsFromDOM();
              true
            barStack.push({
             );
                items: snapshot.items,
            window.addEventListener("resize", () => {
                currentForm: snapshot.currentForm,
              const target =
                parentIcon: parentIconEl,
                 document.querySelector(".subskills-rail .subicon:hover") ||
                parentName: parentNameSnapshot,
                document.querySelector(".subskills-rail .subicon.active") ||
                parentIndex: parentIndexSnapshot
                document.querySelector(".icon-bar .skill-icon:hover") ||
                document.querySelector(".icon-bar .skill-icon.active");
              measureAndPos(target);
             });
             });
            ensureBackButton();
          })();
            const langKey = getLangKey();
        },
            let cacheKey = null;
        { timeout: 2000 }
            if (parentIconEl) {
      );
                cacheKey = parentIconEl.dataset.subCacheKey || null;
    } else {
                if (!cacheKey) {
      // Fallback para navegadores sem requestIdleCallback
                    if (parentIconEl.dataset.index) {
      setTimeout(() => {
                        cacheKey = `idx:${parentIconEl.dataset.index}`;
        (function initSkillTooltip() {
                    } else {
          if (document.querySelector(".skill-tooltip")) return;
                        const slug = slugify(parentIconEl.dataset.nome || parentIconEl.dataset.name || '');
          const tip = document.createElement("div");
                        if (slug) cacheKey = `slug:${slug}`;
          tip.className = "skill-tooltip";
                    } if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey;
          tip.setAttribute("role", "tooltip");
                }
          tip.setAttribute("aria-hidden", "true");
            } if (cacheKey) {
          document.body.appendChild(tip);
                const cached = subBarTemplateCache.get(cacheKey);
          window.__globalSkillTooltip = {
                if (cached && cached.lang === langKey) {
            show: (el, text) => {
                    iconsBar.innerHTML = '';
              if (!el || !text) return;
                    const clone = cached.template.cloneNode(true);
              tip.textContent = text;
                    iconsBar.appendChild(clone);
              tip.setAttribute("aria-hidden", "false");
                    animateIconsBarEntrance();
              measureAndPos(el);
                    wireClicksForCurrentBar();
            },
                    // Remove qualquer toggle antigo que possa aparecer
            hide: () => {
                    const oldToggle2 = iconsBar.querySelector('.weapon-bar-toggle');
              tip.setAttribute("aria-hidden", "true");
                    if (oldToggle2) oldToggle2.remove();
              tip.style.left = "-9999px";
                    // Reaplica classes de weapon após renderizar do cache
              tip.style.top = "-9999px";
                    reapplyWeaponClassesToBar();
            },
                    const cachedBtn = ensureBackButton();
             measureAndPos: (el) => {
                    if (cachedBtn) cachedBtn.classList.add('peek');
              if (!el || tip.getAttribute("aria-hidden") === "true") return;
                    return;
              const rect = el.getBoundingClientRect();
                }
              const tipRect = tip.getBoundingClientRect();
             } const skillsRoot = document.getElementById('skills');
              let left = rect.left + rect.width / 2 - tipRect.width / 2;
            const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {
              let top = rect.top - tipRect.height - 8;
            };
              if (left < 8) left = 8;
            const L = i18nMap[getLangKey()] || i18nMap.pt || {
              if (left + tipRect.width > window.innerWidth - 8)
                cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível'
                left = window.innerWidth - tipRect.width - 8;
            };
              if (top < 8) top = rect.bottom + 8;
            const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
              tip.style.left = left + "px";
            const items = (hydratedSubs || []).filter(s => {
              tip.style.top = top + "px";
                // Filtra só se não tem nada útil
             },
                const hasName = (s.name || s.n || '').trim() !== '';
            lockUntil: { value: 0 },
                const hasIcon = (s.icon || '').trim() !== '' && s.icon !== 'Nada.png';
          };
                const hasRef = (s.refS || s.refM || '').toString().trim() !== '';
          const { measureAndPos } = window.__globalSkillTooltip;
                return hasName || hasIcon || hasRef;
          window.addEventListener(
             }).map(s => {
            "scroll",
                const name = (s.name || s.n || '').trim();
            () => {
                const desc = chooseDescFrom(s).replace(/'''(.*?)'''/g, '<b>$1</b>');
              const visible = document.querySelector(
                const attrsHTML = renderSubAttributesFromObj(s, L);
                '.skill-tooltip[aria-hidden="false"]'
                 return {
              );
                    name, level: (s.level || '').toString().trim(), desc, descPt: (s.descPt || (s.desc_i18n && s.desc_i18n.pt) || ''), descEn: (s.descEn || (s.desc_i18n && s.desc_i18n.en) || ''), descEs: (s.descEs || (s.desc_i18n && s.desc_i18n.es) || ''), descPl: (s.descPl || (s.desc_i18n && s.desc_i18n.pl) || ''), attrs: '', icon: (s.icon || ''), iconURL: (s.icon && s.icon !== 'Nada.png' && s.icon.toLowerCase() !== 'nada.png') ? filePathURL(s.icon) : '', video: s.video ? filePathURL(s.video) : '', subs: Array.isArray(s.subs) ? s.subs : null, subattrs: s, flags: Array.isArray(s.flags) ? s.flags : null, back: (s.back === true || s.back === 'true' || s.back === 'yes' || s.back === '1') ? 'true' : null, weapon: s.weapon || null
              if (!visible) return;
                };
              const target =
             });
                 document.querySelector(".subskills-rail .subicon:hover") ||
             const fragment = document.createDocumentFragment();
                document.querySelector(".subskills-rail .subicon.active") ||
            items.forEach((it, iIdx) => {
                document.querySelector(".icon-bar .skill-icon:hover") ||
                const node = document.createElement('div');
                document.querySelector(".icon-bar .skill-icon.active");
                node.className = 'skill-icon';
              measureAndPos(target);
                node.dataset.nested = '1';
             },
                node.dataset.nome = it.name || '';
             true
                node.dataset.parentIndex = parentIndexSnapshot;
          );
                // Constrói o caminho completo para sub-subskills
          window.addEventListener("resize", () => {
                // IMPORTANTE: O cache NÃO inclui o nome da skill principal no caminho
            const target =
                // Formato do cache: "SubskillName" ou "SubskillName:SubSubskillName" (sem o nome da skill principal)
              document.querySelector(".subskills-rail .subicon:hover") ||
                let fullPath = it.name || '';
              document.querySelector(".subskills-rail .subicon.active") ||
                if (parentIconEl) {
              document.querySelector(".icon-bar .skill-icon:hover") ||
                    // Se o pai é uma subskill (tem nested), adiciona o nome do pai ao caminho
              document.querySelector(".icon-bar .skill-icon.active");
                    if (parentIconEl.dataset.nested === '1') {
            measureAndPos(target);
                        // Se o pai tem subName, usa ele (já está no formato correto, sem nome da skill principal)
          });
                        if (parentIconEl.dataset.subName && parentIconEl.dataset.subName.trim() !== '') {
        })();
                            fullPath = `${parentIconEl.dataset.subName}:${it.name || ''}`;
      }, 100);
                        } else {
    }
                            // Se o pai não tem subName, é a primeira subskill, então usa apenas o nome do pai
 
                            const parentName = (parentIconEl.dataset.nome || parentIconEl.dataset.name || '').trim();
    (function initTabs() {
                            fullPath = `${parentName}:${it.name || ''}`;
      const tabs = Array.from(document.querySelectorAll(".tab-btn"));
                        }
      if (!tabs.length) return;
                    }
      const contents = Array.from(document.querySelectorAll(".tab-content"));
                    // Se o pai é uma skill principal (não é nested), o caminho é apenas o nome da subskill atual
      const characterBox = document.querySelector(".character-box");
                    // (já está definido como it.name acima)
      let wrapper = characterBox.querySelector(".tabs-height-wrapper");
                }
      if (!wrapper) {
                node.dataset.subName = fullPath;
        wrapper = document.createElement("div");
                const subSlug = slugify(it.name || '');
        wrapper.className = "tabs-height-wrapper";
                if (subSlug) node.dataset.slug = subSlug;
        contents.forEach((c) => {
                if (it.level) node.dataset.level = it.level;
          wrapper.appendChild(c);
                if (it.desc) node.dataset.desc = it.desc;
        });
                if (it.descPt) node.dataset.descPt = it.descPt;
        const tabsElement = characterBox.querySelector(".character-tabs");
                if (it.descEn) node.dataset.descEn = it.descEn;
        if (tabsElement && tabsElement.nextSibling) {
                if (it.descEs) node.dataset.descEs = it.descEs;
          characterBox.insertBefore(wrapper, tabsElement.nextSibling);
                if (it.descPl) node.dataset.descPl = it.descPl;
        } else {
                if (it.video) node.dataset.video = it.video;
          characterBox.appendChild(wrapper);
                if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
        }
                if (it.subattrs) node.dataset.subattrs = JSON.stringify(it.subattrs);
      }
                if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
      async function smoothHeightTransition(fromTab, toTab) {
                if (it.back) node.dataset.back = it.back;
        if (!wrapper) return Promise.resolve();
                if (it.weapon && typeof it.weapon === 'object' && Object.keys(it.weapon).length > 0) {
        const scrollY = window.scrollY;
                    try {
        const currentHeight = wrapper.getBoundingClientRect().height;
                        node.dataset.weapon = JSON.stringify(it.weapon);
        await new Promise((resolve) => {
                    } catch (e) {
          const videoContainers = toTab.querySelectorAll(".video-container");
                        console.error('[Skills] Erro ao serializar weapon de subskill', it.name, e);
          const contentCard = toTab.querySelector(".content-card");
                    }
          if (videoContainers.length === 0) {
                }
                const img = document.createElement('img');
                img.alt = '';
                img.src = it.iconURL;
                img.decoding = 'async';
                img.loading = 'lazy';
                img.width = 50;
                img.height = 50;
                node.appendChild(img);
                fragment.appendChild(node);
            });
            const templateClone = fragment.cloneNode(true);
            iconsBar.innerHTML = '';
            iconsBar.appendChild(fragment);
            animateIconsBarEntrance();
            wireClicksForCurrentBar();
            // Remove qualquer toggle antigo que possa aparecer
            const oldToggle3 = iconsBar.querySelector('.weapon-bar-toggle');
            if (oldToggle3) oldToggle3.remove();
            // Reaplica classes de weapon após renderizar subskills
            reapplyWeaponClassesToBar();
            const b2 = ensureBackButton();
            if (b2) b2.classList.add('peek');
            // Atualiza a posição do botão back após subskills serem renderizadas
             requestAnimationFrame(() => {
             requestAnimationFrame(() => {
                const backWrap = document.querySelector('.skills-back-wrapper');
              requestAnimationFrame(() => {
                if (backWrap && backWrap.style.display !== 'none') {
                requestAnimationFrame(() => resolve());
                    const rail = iconsBar.closest('.top-rail.skills');
              });
                    const wrap = rail ? rail.parentElement : null;
                    if (rail && wrap && wrap.classList.contains('skills-rail-wrap')) {
                        const railRect = rail.getBoundingClientRect();
                        const wrapRect = wrap.getBoundingClientRect();
                        const railLeft = railRect.left - wrapRect.left;
                        // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
                        backWrap.style.left = railLeft + 'px';
                    }
                }
             });
             });
             if (cacheKey) {
             return;
                subBarTemplateCache.set(cacheKey, {
          }
                    template: templateClone, lang: langKey
          let lastHeight = 0;
                });
          let stableCount = 0;
          const checksNeeded = 3;
          let totalChecks = 0;
          const maxChecks = 15;
          function checkStability() {
            totalChecks++;
            const currentTabHeight = toTab.scrollHeight;
            if (Math.abs(currentTabHeight - lastHeight) < 5) {
              stableCount++;
            } else {
              stableCount = 0;
             }
             }
        } window.addEventListener('gla:langChanged', () => {
             lastHeight = currentTabHeight;
            subBarTemplateCache.clear();
             if (stableCount >= checksNeeded || totalChecks >= maxChecks) {
             const skillsRoot = document.getElementById('skills');
              resolve();
             const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {
             } else {
            };
              setTimeout(checkStability, 50);
            const lang = getLangKey();
            Array.from(iconsBar.querySelectorAll('.skill-icon')).forEach(icon => {
                const pack = {
                    pt: icon.dataset.descPt || '', en: icon.dataset.descEn || '', es: icon.dataset.descEs || '', pl: icon.dataset.descPl || ''
                };
                const chosen = (pack[lang] || pack.pt || pack.en || pack.es || pack.pl || icon.dataset.desc || '').trim();
                if (chosen) icon.dataset.desc = chosen;
            });
            barStack.forEach(frame => {
                (frame.items || []).forEach(it => {
                    const pack = {
                        pt: it.descPt, en: it.descEn, es: it.descEs, pl: it.descPl
                    };
                    const chosen = (pack[lang] || pack.pt || pack.en || pack.es || pack.pl || it.desc || '');
                    it.desc = chosen;
                });
            });
            if (descBox) {
                applyFlagTooltips(descBox);
             } const activeIcon = window.__lastActiveSkillIcon;
            if (activeIcon && activeIcon.dataset.weapon) {
                activateSkill(activeIcon, {
                    openSubs: false
                });
             }
             }
          }
          setTimeout(checkStability, 50);
         });
         });
         wireClicksForCurrentBar();
         const nextHeight = toTab.getBoundingClientRect().height;
         const b0 = ensureBackButton();
         const finalHeight = Math.max(nextHeight, 100);
         if (b0) {
         if (Math.abs(finalHeight - currentHeight) < 30) {
            b0.classList.add('peek');
          wrapper.style.height = "";
            b0.style.alignSelf = 'stretch';
          return Promise.resolve();
         }
         }
 
         wrapper.style.overflow = "hidden";
         // Move inicialização de tooltip para requestIdleCallback (não crítico)
        wrapper.style.height = currentHeight + "px";
        if ('requestIdleCallback' in window) {
        wrapper.offsetHeight;
            requestIdleCallback(() => {
        wrapper.style.transition = "height 0.3s cubic-bezier(0.4, 0, 0.2, 1)";
                (function initSkillTooltip() {
        requestAnimationFrame(() => {
                    if (document.querySelector('.skill-tooltip')) return;
          wrapper.style.height = finalHeight + "px";
                    const tip = document.createElement('div');
        });
                    tip.className = 'skill-tooltip';
        return new Promise((resolve) => {
                    tip.setAttribute('role', 'tooltip');
          setTimeout(() => {
                    tip.setAttribute('aria-hidden', 'true');
            wrapper.style.height = "";
                    document.body.appendChild(tip);
            wrapper.style.transition = "";
                    const lockUntilRef = {
            wrapper.style.overflow = "";
                        value: 0
            resolve();
                    };
          }, 320);
                    function measureAndPos(el) {
        });
                        if (!el || tip.getAttribute('aria-hidden') === 'true') return;
      }
                        tip.style.left = '0px';
      tabs.forEach((btn) => {
                        tip.style.top = '0px';
        if (btn.dataset.wiredTab) return;
                        const rect = el.getBoundingClientRect();
        btn.dataset.wiredTab = "1";
                        const tr = tip.getBoundingClientRect();
        btn.addEventListener("click", () => {
                        let left = Math.round(rect.left + (rect.width - tr.width) / 2);
          const target = btn.getAttribute("data-tab");
                        left = Math.max(8, Math.min(left, window.innerWidth - tr.width - 8));
          const currentActive = contents.find((c) =>
                        const coarse = (window.matchMedia && matchMedia('(pointer: coarse)').matches) || (window.innerWidth <= 600);
            c.classList.contains("active")
                        let top = coarse ? Math.round(rect.bottom + 10) : Math.round(rect.top - tr.height - 8);
          );
                        if (top < 8) top = Math.round(rect.bottom + 10);
          const nextActive = contents.find((c) => c.id === target);
                        tip.style.left = left + 'px';
          if (currentActive === nextActive) return;
                        tip.style.top = top + 'px';
          document.body.classList.add("transitioning-tabs");
                    } function show(el, text) {
          if (currentActive) {
                        tip.textContent = text || '';
            currentActive.style.opacity = "0";
                        tip.setAttribute('aria-hidden', 'false');
            currentActive.style.transform = "translateY(-8px)";
                        measureAndPos(el);
          }
                        tip.style.opacity = '1';
          setTimeout(async () => {
                    } function hide() {
            contents.forEach((c) => {
                        tip.setAttribute('aria-hidden', 'true');
              if (c !== nextActive) {
                        tip.style.opacity = '0';
                c.style.display = "none";
                        tip.style.left = '-9999px';
                c.classList.remove("active");
                        tip.style.top = '-9999px';
              }
                    } window.__globalSkillTooltip = {
            });
                        show, hide, measureAndPos, lockUntil: lockUntilRef
            tabs.forEach((b) => b.classList.toggle("active", b === btn));
                    };
            if (nextActive) {
                    Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => {
              nextActive.classList.add("active");
                        if (icon.dataset.weaponToggle === '1' || icon.classList.contains('weapon-bar-toggle')) return;
              nextActive.style.display = "block";
                        if (icon.dataset.tipwired) return;
              nextActive.style.opacity = "0";
                        icon.dataset.tipwired = '1';
              nextActive.style.visibility = "hidden";
                        const label = icon.dataset.nome || icon.dataset.name || icon.title || '';
              nextActive.offsetHeight;
                        if (label && !icon.hasAttribute('aria-label')) icon.setAttribute('aria-label', label);
              try {
                        if (icon.hasAttribute('title')) icon.removeAttribute('title');
                if (target === "skills") {
                        const img = icon.querySelector('img');
                  const tabEl = document.getElementById(target);
                        if (img) {
                  if (tabEl) {
                            const imgAlt = img.getAttribute('alt') || '';
                    const activeIcon = tabEl.querySelector(
                            const imgTitle = img.getAttribute('title') || '';
                      ".icon-bar .skill-icon.active"
                            if (!label && (imgAlt || imgTitle)) icon.setAttribute('aria-label', imgAlt || imgTitle);
                     );
                            img.setAttribute('alt', '');
                     const firstIcon = tabEl.querySelector(
                            if (img.hasAttribute('title')) img.removeAttribute('title');
                      ".icon-bar .skill-icon"
                        } icon.addEventListener('mouseenter', () => show(icon, label));
                    );
                        icon.addEventListener('mousemove', () => {
                     const toClick = activeIcon || firstIcon;
                            if (performance.now() >= lockUntilRef.value) measureAndPos(icon);
                    if (toClick) {
                        });
                      const had = document.body.dataset.suppressSkillPlay;
                        icon.addEventListener('click', () => {
                      document.body.dataset.suppressSkillPlay = "1";
                            lockUntilRef.value = performance.now() + 240;
                      toClick.click();
                            measureAndPos(icon);
                      if (had) document.body.dataset.suppressSkillPlay = had;
                        });
                     }
                        icon.addEventListener('mouseleave', hide);
                  }
                    });
                    Array.from(document.querySelectorAll('.subskills-rail .subicon')).forEach(sub => {
                        if (sub.dataset.tipwired) return;
                        sub.dataset.tipwired = '1';
                        const label = sub.getAttribute('title') || sub.getAttribute('aria-label') || '';
                        if (label && !sub.hasAttribute('aria-label')) sub.setAttribute('aria-label', label);
                        if (sub.hasAttribute('title')) sub.removeAttribute('title');
                        sub.addEventListener('mouseenter', () => show(sub, label));
                        sub.addEventListener('mousemove', () => {
                            if (performance.now() >= lockUntilRef.value) measureAndPos(sub);
                        });
                        sub.addEventListener('click', () => {
                            lockUntilRef.value = performance.now() + 240;
                            measureAndPos(sub);
                        });
                        sub.addEventListener('mouseleave', hide);
                    });
                    window.addEventListener('scroll', () => {
                        const visible = document.querySelector('.skill-tooltip[aria-hidden="false"]');
                        if (!visible) return;
                        const target = document.querySelector('.subskills-rail .subicon:hover') || document.querySelector('.subskills-rail .subicon.active') || document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active');
                        measureAndPos(target);
                    }, true);
                    window.addEventListener('resize', () => {
                        const target = document.querySelector('.subskills-rail .subicon:hover') || document.querySelector('.subskills-rail .subicon.active') || document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active');
                        measureAndPos(target);
                    });
                })();
            }, { timeout: 2000 });
        } else {
            // Fallback para navegadores sem requestIdleCallback
            setTimeout(() => {
                (function initSkillTooltip() {
                    if (document.querySelector('.skill-tooltip')) return;
                    const tip = document.createElement('div');
                    tip.className = 'skill-tooltip';
                    tip.setAttribute('role', 'tooltip');
                    tip.setAttribute('aria-hidden', 'true');
                    document.body.appendChild(tip);
                    window.__globalSkillTooltip = {
                        show: (el, text) => {
                            if (!el || !text) return;
                            tip.textContent = text;
                            tip.setAttribute('aria-hidden', 'false');
                            measureAndPos(el);
                        },
                        hide: () => {
                            tip.setAttribute('aria-hidden', 'true');
                            tip.style.left = '-9999px';
                            tip.style.top = '-9999px';
                        },
                        measureAndPos: (el) => {
                            if (!el || tip.getAttribute('aria-hidden') === 'true') return;
                            const rect = el.getBoundingClientRect();
                            const tipRect = tip.getBoundingClientRect();
                            let left = rect.left + (rect.width / 2) - (tipRect.width / 2);
                            let top = rect.top - tipRect.height - 8;
                            if (left < 8) left = 8;
                            if (left + tipRect.width > window.innerWidth - 8) left = window.innerWidth - tipRect.width - 8;
                            if (top < 8) top = rect.bottom + 8;
                            tip.style.left = left + 'px';
                            tip.style.top = top + 'px';
                        },
                        lockUntil: { value: 0 }
                    };
                    const { measureAndPos } = window.__globalSkillTooltip;
                    window.addEventListener('scroll', () => {
                        const visible = document.querySelector('.skill-tooltip[aria-hidden="false"]');
                        if (!visible) return;
                        const target = document.querySelector('.subskills-rail .subicon:hover') || document.querySelector('.subskills-rail .subicon.active') || document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active');
                        measureAndPos(target);
                     }, true);
                     window.addEventListener('resize', () => {
                        const target = document.querySelector('.subskills-rail .subicon:hover') || document.querySelector('.subskills-rail .subicon.active') || document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active');
                        measureAndPos(target);
                     });
                })();
            }, 100);
        }
 
        (function initTabs() {
            const tabs = Array.from(document.querySelectorAll('.tab-btn'));
            if (!tabs.length) return;
            const contents = Array.from(document.querySelectorAll('.tab-content'));
            const characterBox = document.querySelector('.character-box');
            let wrapper = characterBox.querySelector('.tabs-height-wrapper');
            if (!wrapper) {
                wrapper = document.createElement('div');
                wrapper.className = 'tabs-height-wrapper';
                contents.forEach(c => {
                     wrapper.appendChild(c);
                });
                const tabsElement = characterBox.querySelector('.character-tabs');
                if (tabsElement && tabsElement.nextSibling) {
                    characterBox.insertBefore(wrapper, tabsElement.nextSibling);
                } else {
                    characterBox.appendChild(wrapper);
                 }
                 }
            } async function smoothHeightTransition(fromTab, toTab) {
              } catch (e) { }
                if (!wrapper) return Promise.resolve();
            }
                const scrollY = window.scrollY;
             if (currentActive && nextActive) {
                const currentHeight = wrapper.getBoundingClientRect().height;
              await smoothHeightTransition(currentActive, nextActive);
                await new Promise((resolve) => {
                    const videoContainers = toTab.querySelectorAll('.video-container');
                    const contentCard = toTab.querySelector('.content-card');
                    if (videoContainers.length === 0) {
                        requestAnimationFrame(() => {
                            requestAnimationFrame(() => {
                                requestAnimationFrame(() => resolve());
                            });
                        });
                        return;
                    } let lastHeight = 0;
                    let stableCount = 0;
                    const checksNeeded = 3;
                    let totalChecks = 0;
                    const maxChecks = 15;
                    function checkStability() {
                        totalChecks++;
                        const currentTabHeight = toTab.scrollHeight;
                        if (Math.abs(currentTabHeight - lastHeight) < 5) {
                            stableCount++;
                        } else {
                            stableCount = 0;
                        } lastHeight = currentTabHeight;
                        if (stableCount >= checksNeeded || totalChecks >= maxChecks) {
                            resolve();
                        } else {
                            setTimeout(checkStability, 50);
                        }
                    } setTimeout(checkStability, 50);
                });
                const nextHeight = toTab.getBoundingClientRect().height;
                const finalHeight = Math.max(nextHeight, 100);
                if (Math.abs(finalHeight - currentHeight) < 30) {
                    wrapper.style.height = '';
                    return Promise.resolve();
                } wrapper.style.overflow = 'hidden';
                wrapper.style.height = currentHeight + 'px';
                wrapper.offsetHeight;
                wrapper.style.transition = 'height 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
                requestAnimationFrame(() => {
                    wrapper.style.height = finalHeight + 'px';
                });
                return new Promise(resolve => {
                    setTimeout(() => {
                        wrapper.style.height = '';
                        wrapper.style.transition = '';
                        wrapper.style.overflow = '';
                        resolve();
                    }, 320);
                });
             } tabs.forEach(btn => {
                if (btn.dataset.wiredTab) return;
                btn.dataset.wiredTab = '1';
                btn.addEventListener('click', () => {
                    const target = btn.getAttribute('data-tab');
                    const currentActive = contents.find(c => c.classList.contains('active'));
                    const nextActive = contents.find(c => c.id === target);
                    if (currentActive === nextActive) return;
                    document.body.classList.add('transitioning-tabs');
                    if (currentActive) {
                        currentActive.style.opacity = '0';
                        currentActive.style.transform = 'translateY(-8px)';
                    } setTimeout(async () => {
                        contents.forEach(c => {
                            if (c !== nextActive) {
                                c.style.display = 'none';
                                c.classList.remove('active');
                            }
                        });
                        tabs.forEach(b => b.classList.toggle('active', b === btn));
                        if (nextActive) {
                            nextActive.classList.add('active');
                            nextActive.style.display = 'block';
                            nextActive.style.opacity = '0';
                            nextActive.style.visibility = 'hidden';
                            nextActive.offsetHeight;
                            try {
                                if (target === 'skills') {
                                    const tabEl = document.getElementById(target);
                                    if (tabEl) {
                                        const activeIcon = tabEl.querySelector('.icon-bar .skill-icon.active');
                                        const firstIcon = tabEl.querySelector('.icon-bar .skill-icon');
                                        const toClick = activeIcon || firstIcon;
                                        if (toClick) {
                                            const had = document.body.dataset.suppressSkillPlay;
                                            document.body.dataset.suppressSkillPlay = '1';
                                            toClick.click();
                                            if (had) document.body.dataset.suppressSkillPlay = had;
                                        }
                                    }
                                }
                            } catch (e) {
                            }
                        } if (currentActive && nextActive) {
                            await smoothHeightTransition(currentActive, nextActive);
                        } if (nextActive) {
                            nextActive.style.visibility = '';
                            nextActive.style.transform = 'translateY(12px)';
                            requestAnimationFrame(() => {
                                nextActive.style.opacity = '1';
                                nextActive.style.transform = 'translateY(0)';
                                setTimeout(() => {
                                    nextActive.style.opacity = '';
                                    nextActive.style.transform = '';
                                    document.body.classList.remove('transitioning-tabs');
                                    try {
                                        delete document.body.dataset.suppressSkillPlay;
                                    } catch {
                                    }
                                }, 300);
                            });
                        }
                    }, 120);
                    setTimeout(() => {
                        syncDescHeight();
                        if (target === 'skins') {
                            videosCache.forEach(v => {
                                try {
                                    v.pause();
                                } catch (e) {
                                } v.style.display = 'none';
                            });
                            if (videoBox) {
                                videoBox.querySelectorAll('video.skill-video').forEach(v => {
                                    try {
                                        v.pause();
                                    } catch (e) {
                                    } v.style.display = 'none';
                                });
                            } if (window.__subskills) window.__subskills.hideAll?.(videoBox);
                            const placeholder = videoBox?.querySelector('.video-placeholder');
                            if (videoBox && placeholder) {
                                placeholder.style.display = 'none';
                                placeholder.classList.add('fade-out');
                            }
                        } else {
                            const activeIcon = document.querySelector('.icon-bar .skill-icon.active');
                            if (activeIcon) activeIcon.click();
                        }
                    }, 450);
                });
            });
        })();
        (function initSkinsArrows() {
            const carousel = $('.skins-carousel');
            const wrapper = $('.skins-carousel-wrapper');
            const left = $('.skins-arrow.left');
            const right = $('.skins-arrow.right');
            if (!carousel || !left || !right || !wrapper) return;
            if (wrapper.dataset.wired) return;
            wrapper.dataset.wired = '1';
            const scrollAmt = () => Math.round(carousel.clientWidth * 0.6);
            function setState() {
                const max = carousel.scrollWidth - carousel.clientWidth;
                const x = carousel.scrollLeft;
                const hasLeft = x > 5, hasRight = x < max - 5;
                left.style.display = hasLeft ? 'inline-block' : 'none';
                right.style.display = hasRight ? 'inline-block' : 'none';
                wrapper.classList.toggle('has-left', hasLeft);
                wrapper.classList.toggle('has-right', hasRight);
                carousel.style.justifyContent = (!hasLeft && !hasRight) ? 'center' : '';
            } function go(dir) {
                const max = carousel.scrollWidth - carousel.clientWidth;
                const next = dir < 0 ? Math.max(0, carousel.scrollLeft - scrollAmt()) : Math.min(max, carousel.scrollLeft + scrollAmt());
                carousel.scrollTo({
                    left: next, behavior: 'smooth'
                });
            } left.addEventListener('click', () => go(-1));
            right.addEventListener('click', () => go(1));
            carousel.addEventListener('scroll', setState);
            new ResizeObserver(setState).observe(carousel);
            setState();
        })();
        function renderAttributes(str) {
            const skillsRoot = document.getElementById('skills');
            const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {
            };
            const langRaw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase();
            const langKey = i18nMap[langRaw] ? langRaw : (i18nMap[langRaw.split('-')[0]] ? langRaw.split('-')[0] : 'pt');
            const L = i18nMap[langKey] || i18nMap.pt || {
                cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível'
            };
            const vals = (str || '').split(',').map(v => v.trim());
            // Processa valores, tratando strings vazias e "-" como NaN
            // IMPORTANTE: ordem fixa é [powerpve, powerpvp, energy, cooldown]
            const parseValue = (val) => {
                if (!val || val === '' || val === '-') return NaN;
                const parsed = parseFloat(val);
                return isNaN(parsed) ? NaN : parsed;
            };
            const pve = parseValue(vals[0]);
            const pvp = parseValue(vals[1]);
            const ene = parseValue(vals[2]);
            const cd = parseValue(vals[3]);
            // Debug: log se houver problema na ordem
            if (str && str.includes(',') && !isNaN(cd) && !isNaN(pvp) && cd === pvp) {
                console.warn('[Skills] renderAttributes: possível problema na ordem dos atributos', { str, vals, pve, pvp, ene, cd });
             }
             }
             const rows = [];
             if (nextActive) {
            // Ordem de exibição: cooldown, energy, power, power_pvp
              nextActive.style.visibility = "";
            if (!isNaN(cd)) rows.push([L.cooldown, cd]);
              nextActive.style.transform = "translateY(12px)";
            if (!isNaN(ene) && ene !== 0) {
              requestAnimationFrame(() => {
                const label = ene > 0 ? L.energy_gain : L.energy_cost;
                nextActive.style.opacity = "1";
                 rows.push([label, Math.abs(ene)]);
                nextActive.style.transform = "translateY(0)";
                setTimeout(() => {
                  nextActive.style.opacity = "";
                  nextActive.style.transform = "";
                  document.body.classList.remove("transitioning-tabs");
                  try {
                    delete document.body.dataset.suppressSkillPlay;
                  } catch { }
                 }, 300);
              });
             }
             }
            if (!isNaN(pve)) rows.push([L.power, pve]);
          }, 120);
            if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]);
          setTimeout(() => {
            // Debug: log se houver valores suspeitos (desabilitado para performance)
             syncDescHeight();
            // if (str && str.includes(',')) {
             if (target === "skins") {
             //    console.log('[Skills] renderAttributes processed', {
              videosCache.forEach((v) => {
            //        str,
                try {
            //        vals: vals.slice(0, 4),
                  v.pause();
            //        parsed: { pve, pvp, ene, cd },
                } catch (e) { }
            //        rows: rows.map(r => r[0])
                 v.style.display = "none";
            //    });
              });
            // }
              if (videoBox) {
             if (!rows.length) return '';
                videoBox.querySelectorAll("video.skill-video").forEach((v) => {
            const html = rows.map(([rowLabel, rowValue]) => `<div class="attr-row"><span class="attr-label">${rowLabel}</span><span class="attr-value">${rowValue}</span></div>`).join('');
                  try {
            return `<div class="attr-list">${html}</div>`;
                     v.pause();
        } function syncDescHeight() {
                  } catch (e) { }
        } window.addEventListener('resize', syncDescHeight);
                  v.style.display = "none";
        if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox);
        iconItems.forEach(el => {
            const wired = !!el.dataset._sync_wired;
            if (wired) return;
            el.dataset._sync_wired = '1';
            el.addEventListener('click', () => {
                 Promise.resolve().then(syncDescHeight);
            });
        });
        if (iconsBar) {
            const scrollWrapper = iconsBar.closest('.icon-scroll-x') || iconsBar.parentElement;
            const scrollContainer = scrollWrapper && scrollWrapper.classList.contains('icon-scroll-x') ? scrollWrapper : iconsBar;
            addOnce(scrollContainer, 'wheel', (e) => {
                if (e.deltaY) {
                     e.preventDefault();
                    scrollContainer.scrollLeft += e.deltaY;
                }
            }, { passive: false }); // passive: false necessário porque usamos preventDefault()
        }
        wireClicksForCurrentBar();
        if (iconItems.length) {
            const first = iconItems[0];
            if (first) {
                activateSkill(first, {
                    openSubs: false
                 });
                 });
              }
              if (window.__subskills) window.__subskills.hideAll?.(videoBox);
              const placeholder = videoBox?.querySelector(".video-placeholder");
              if (videoBox && placeholder) {
                placeholder.style.display = "none";
                placeholder.classList.add("fade-out");
              }
            } else {
              const activeIcon = document.querySelector(
                ".icon-bar .skill-icon.active"
              );
              if (activeIcon) activeIcon.click();
             }
             }
         }
          }, 450);
         });
      });
    })();
    (function initSkinsArrows() {
      const carousel = $(".skins-carousel");
      const wrapper = $(".skins-carousel-wrapper");
      const left = $(".skins-arrow.left");
      const right = $(".skins-arrow.right");
      if (!carousel || !left || !right || !wrapper) return;
      if (wrapper.dataset.wired) return;
      wrapper.dataset.wired = "1";
      const scrollAmt = () => Math.round(carousel.clientWidth * 0.6);
      function setState() {
        const max = carousel.scrollWidth - carousel.clientWidth;
        const x = carousel.scrollLeft;
        const hasLeft = x > 5,
          hasRight = x < max - 5;
        left.style.display = hasLeft ? "inline-block" : "none";
        right.style.display = hasRight ? "inline-block" : "none";
        wrapper.classList.toggle("has-left", hasLeft);
        wrapper.classList.toggle("has-right", hasRight);
        carousel.style.justifyContent = !hasLeft && !hasRight ? "center" : "";
      }
      function go(dir) {
        const max = carousel.scrollWidth - carousel.clientWidth;
        const next =
          dir < 0
            ? Math.max(0, carousel.scrollLeft - scrollAmt())
            : Math.min(max, carousel.scrollLeft + scrollAmt());
        carousel.scrollTo({
          left: next,
          behavior: "smooth",
        });
      }
      left.addEventListener("click", () => go(-1));
      right.addEventListener("click", () => go(1));
      carousel.addEventListener("scroll", setState);
      new ResizeObserver(setState).observe(carousel);
      setState();
    })();
    function renderAttributes(str) {
      const skillsRoot = document.getElementById("skills");
      const i18nMap = skillsRoot
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
        : {};
      const langRaw = (
        document.documentElement.lang ||
        skillsRoot?.dataset.i18nDefault ||
        "pt"
      ).toLowerCase();
      const langKey = i18nMap[langRaw]
        ? langRaw
        : i18nMap[langRaw.split("-")[0]]
          ? langRaw.split("-")[0]
          : "pt";
      const L = i18nMap[langKey] ||
        i18nMap.pt || {
        cooldown: "Recarga",
        energy_gain: "Ganho de energia",
        energy_cost: "Custo de energia",
        power: "Poder",
        power_pvp: "Poder PvP",
        level: "Nível",
      };
      const vals = (str || "").split(",").map((v) => v.trim());
      // Processa valores, tratando strings vazias e "-" como NaN
      // IMPORTANTE: ordem fixa é [powerpve, powerpvp, energy, cooldown]
      const parseValue = (val) => {
        if (!val || val === "" || val === "-") return NaN;
        const parsed = parseFloat(val);
        return isNaN(parsed) ? NaN : parsed;
      };
      const pve = parseValue(vals[0]);
      const pvp = parseValue(vals[1]);
      const ene = parseValue(vals[2]);
      const cd = parseValue(vals[3]);
      // Debug: log se houver problema na ordem
      if (str && str.includes(",") && !isNaN(cd) && !isNaN(pvp) && cd === pvp) {
        console.warn(
          "[Skills] renderAttributes: possível problema na ordem dos atributos",
          { str, vals, pve, pvp, ene, cd }
        );
      }
      const rows = [];
      // Ordem de exibição: cooldown, energy, power, power_pvp
      if (!isNaN(cd)) rows.push([L.cooldown, cd]);
      if (!isNaN(ene) && ene !== 0) {
        const label = ene > 0 ? L.energy_gain : L.energy_cost;
        rows.push([label, Math.abs(ene)]);
      }
      if (!isNaN(pve)) rows.push([L.power, pve]);
      if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]);
      // Debug: log se houver valores suspeitos (desabilitado para performance)
      // if (str && str.includes(',')) {
      //    console.log('[Skills] renderAttributes processed', {
      //        str,
      //        vals: vals.slice(0, 4),
      //        parsed: { pve, pvp, ene, cd },
      //        rows: rows.map(r => r[0])
      //    });
      // }
      if (!rows.length) return "";
      const html = rows
        .map(
          ([rowLabel, rowValue]) =>
            `<div class="attr-row"><span class="attr-label">${rowLabel}</span><span class="attr-value">${rowValue}</span></div>`
        )
        .join("");
      return `<div class="attr-list">${html}</div>`;
    }
    function syncDescHeight() { }
    window.addEventListener("resize", syncDescHeight);
    if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox);
    iconItems.forEach((el) => {
      const wired = !!el.dataset._sync_wired;
      if (wired) return;
      el.dataset._sync_wired = "1";
      el.addEventListener("click", () => {
        Promise.resolve().then(syncDescHeight);
      });
    });
    if (iconsBar) {
      const scrollWrapper =
        iconsBar.closest(".icon-scroll-x") || iconsBar.parentElement;
      const scrollContainer =
        scrollWrapper && scrollWrapper.classList.contains("icon-scroll-x")
          ? scrollWrapper
          : iconsBar;
      addOnce(
        scrollContainer,
        "wheel",
        (e) => {
          if (e.deltaY) {
            e.preventDefault();
            scrollContainer.scrollLeft += e.deltaY;
          }
        },
        { passive: false }
      ); // passive: false necessário porque usamos preventDefault()
    }
    wireClicksForCurrentBar();
    if (iconItems.length) {
      const first = iconItems[0];
      if (first) {
        activateSkill(first, {
          openSubs: false,
        });
      }
    }


        // Aplica lazy loading em imagens fora do viewport inicial
    // Aplica lazy loading em imagens fora do viewport inicial
        (function applyLazyLoading() {
    (function applyLazyLoading() {
            if ('IntersectionObserver' in window) {
      if ("IntersectionObserver" in window) {
                const imageObserver = new IntersectionObserver((entries, observer) => {
        const imageObserver = new IntersectionObserver((entries, observer) => {
                    entries.forEach(entry => {
          entries.forEach((entry) => {
                        if (entry.isIntersecting) {
            if (entry.isIntersecting) {
                            const img = entry.target;
              const img = entry.target;
                            if (img.tagName === 'IMG' && !img.hasAttribute('loading')) {
              if (img.tagName === "IMG" && !img.hasAttribute("loading")) {
                                img.loading = 'lazy';
                img.loading = "lazy";
                            }
              }
                            observer.unobserve(img);
              observer.unobserve(img);
                        }
            }
                    });
          });
                });
        });


                // Observa imagens que não estão no viewport inicial
        // Observa imagens que não estão no viewport inicial
                document.querySelectorAll('img:not(.topbar-icon):not([loading])').forEach(img => {
        document
                    const rect = img.getBoundingClientRect();
          .querySelectorAll("img:not(.topbar-icon):not([loading])")
                    if (rect.bottom > window.innerHeight || rect.top < 0) {
          .forEach((img) => {
                        imageObserver.observe(img);
            const rect = img.getBoundingClientRect();
                    }
            if (rect.bottom > window.innerHeight || rect.top < 0) {
                });
              imageObserver.observe(img);
             }
             }
        })();
          });
      }
    })();


        setTimeout(() => {
    setTimeout(() => {
            Array.from(document.querySelectorAll('.skill-icon')).forEach(el => {
      Array.from(document.querySelectorAll(".skill-icon")).forEach((el) => { });
            });
      videosCache.forEach((v, idx) => {
            videosCache.forEach((v, idx) => {
        const src = v.querySelector("source")
                const src = v.querySelector('source') ? v.querySelector('source').src : v.src;
          ? v.querySelector("source").src
                v.addEventListener('error', (ev) => {
          : v.src;
                });
        v.addEventListener("error", (ev) => { });
                v.addEventListener('loadedmetadata', () => {
        v.addEventListener("loadedmetadata", () => { });
                });
      });
            });
    }, 600);
        }, 600);
  })();
    })();
</script>
</script>

Edição atual tal como às 02h47min de 25 de janeiro de 2026

<script>

 (function () {
   const $ = (s, root = document) => root.querySelector(s);
   const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
   const ensureRemoved = (sel) => {
     Array.from(document.querySelectorAll(sel)).forEach((n) => n.remove());
   };
   const onceFlag = (el, key) => {
     if (!el) return false;
     if (el.dataset[key]) return false;
     el.dataset[key] = "1";
     return true;
   };
   const addOnce = (el, ev, fn, options = {}) => {
     if (!el) return;
     const attr = `data-wired-${ev}`;
     if (el.hasAttribute(attr)) return;
     el.addEventListener(ev, fn, options);
     el.setAttribute(attr, "1");
   };
   const FLAG_ICON_FILES = {
     aggro: "Enemyaggro-icon.png",
     bridge: "Bridgemaker-icon.png",
     wall: "Destroywall-icon.png",
     quickcast: "Quickcast-icon.png",
     wallpass: "Passthroughwall-icon.png",
   };
   const subBarTemplateCache =
     window.__skillSubBarTemplateCache ||
     (window.__skillSubBarTemplateCache = new Map());
   const imagePreloadCache =
     window.__skillImagePreloadCache ||
     (window.__skillImagePreloadCache = new Map());
   const videoPreloadCache =
     window.__skillVideoPreloadCache ||
     (window.__skillVideoPreloadCache = new Set());
   const flagRowCache =
     window.__skillFlagRowCache || (window.__skillFlagRowCache = new Map());
   const flagIconURLCache =
     window.__skillFlagIconURLCache ||
     (window.__skillFlagIconURLCache = new Map());
   // Sistema de múltiplas formas (genérico)
   let currentForm = null; // null = primeira forma, depois nome da forma atual
   let formsData = {};
   let fixedSkills = []; // Skills que sempre aparecem (Change Form, Guard Point, etc.)
   // Sistema genérico de swap de personagens (ex: Buchi & Sham, futuros personagens)
   let activeCharacter = null; // null = personagem padrão, depois nome do personagem ativo (ex: "Buchi", "Sham")
   function showFormTransitionVideo(changeFormIconEl) {
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return;
     // Busca videoBox
     let videoBox = skillsRoot.querySelector(".video-container");
     if (!videoBox) {
       const skillsContainer = skillsRoot.querySelector(".skills-container");
       if (skillsContainer) {
         videoBox = skillsContainer.querySelector(".video-container");
       }
     }
     if (!videoBox) return;
     try {
       // Lê dados de forms para determinar forma atual e próxima
       const formsJSON = skillsRoot.dataset.forms || "{}";
       if (formsJSON && formsJSON !== "{}") {
         const tempFormsData = JSON.parse(formsJSON);
         const formNames = Object.keys(tempFormsData);
         // Determina forma atual e próxima
         const currentIdx = currentForm ? formNames.indexOf(currentForm) : -1;
         const nextIdx = (currentIdx + 1) % formNames.length;
         const nextForm = formNames[nextIdx];
         // Busca vídeo de transição na skill Change Form
         // form_videos[forma_atual] = "video.mp4" (vídeo da transição atual → próxima)
         const formVideosRaw =
           changeFormIconEl.dataset.formVideos ||
           changeFormIconEl.getAttribute("data-form-videos");
         if (formVideosRaw) {
           try {
             const videos = JSON.parse(formVideosRaw);
             const transitionVideo = videos[currentForm] || "";
             if (transitionVideo && transitionVideo.trim() !== "") {
               const videoURL = filePathURL(transitionVideo);
               if (videoURL) {
                 // Busca ou cria elemento de vídeo para esta transição
                 const videoKey = `form_transition:${currentForm}:${nextForm}`;
                 let v = videosCache.get(videoKey);
                 if (!v) {
                   // Cria novo elemento de vídeo
                   v = document.createElement("video");
                   v.className = "skill-video";
                   v.src = videoURL;
                   v.preload = "auto";
                   v.controls = false;
                   v.muted = false;
                   v.loop = false;
                   v.playsInline = true;
                   videoBox.appendChild(v);
                   videosCache.set(videoKey, v);
                 }
                 // Mostra e reproduz o vídeo
                 Array.from(
                   videoBox.querySelectorAll("video.skill-video")
                 ).forEach((vid) => {
                   try {
                     vid.pause();
                   } catch (e) { }
                   vid.style.display = "none";
                 });
                 videoBox.style.display = "block";
                 v.style.display = "block";
                 try {
                   v.currentTime = 0;
                   v.play().catch(() => { });
                 } catch (e) { }
               }
             }
           } catch (e) {
             console.error("[Forms] Erro ao parsear form_videos:", e);
           }
         }
       }
     } catch (e) {
       console.error("[Forms] Erro ao processar vídeo de transição:", e);
     }
   }
   // Sistema genérico de swap de personagens (ex: Buchi & Sham, futuros personagens)
   // Detecta quais personagens estão disponíveis baseado nas skills
   function detectAvailableCharacters() {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return [];
     const characters = new Set();
     Array.from(
       iconBar.querySelectorAll(".skill-icon[data-only-character]")
     ).forEach((icon) => {
       const character = (icon.dataset.onlyCharacter || "").trim();
       if (character) {
         characters.add(character);
       }
     });
     return Array.from(characters);
   }
   // Inicializa o personagem padrão (deve ser chamado quando a página carrega)
   function initializeActiveCharacter() {
     const availableCharacters = detectAvailableCharacters();
     if (availableCharacters.length > 0 && activeCharacter === null) {
       // Inicializa com o primeiro personagem disponível (padrão)
       activeCharacter = availableCharacters[0];
       // Aplica o estado inicial (habilita/desabilita skills)
       applyCharacterSwapState();
     }
   }
   // Aplica o estado de swap nas skills (habilita/desabilita e atualiza vídeos)
   function applyCharacterSwapState() {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return;
     // Atualiza todas as skills na barra
     Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       (icon) => {
         const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
         const hasCharacterVideos =
           icon.dataset.characterVideos &&
           icon.dataset.characterVideos.trim() !== "";
         const baseVideoFile = icon.dataset.videoFile || "";
         const baseVideoURL = icon.dataset.video || "";
         // Salva o vídeo original se ainda não foi salvo (apenas para skills compartilhadas com character_videos)
         if (
           hasCharacterVideos &&
           !onlyCharacter &&
           !icon.dataset.originalVideoFile
         ) {
           icon.dataset.originalVideoFile =
             baseVideoFile || baseVideoURL || "";
         }
         // Desabilita/habilita skills baseado no personagem ativo
         if (onlyCharacter) {
           // Skill específica de um personagem
           if (onlyCharacter === activeCharacter) {
             // Skill do personagem ativo → habilitar
             icon.style.opacity = "1";
             icon.style.filter = "";
             icon.style.pointerEvents = "";
             icon.classList.remove("disabled-skill");
           } else {
             // Skill de outro personagem → desabilitar (escuras)
             icon.style.opacity = "0.3";
             icon.style.filter = "grayscale(100%)";
             icon.style.pointerEvents = "none";
             icon.classList.add("disabled-skill");
           }
         } else {
           // Skill compartilhada → sempre habilitada
           icon.style.opacity = "1";
           icon.style.filter = "";
           icon.style.pointerEvents = "";
           icon.classList.remove("disabled-skill");
           // Atualiza vídeo se houver character_videos e personagem ativo
           if (hasCharacterVideos && activeCharacter) {
             try {
               const characterVideos = JSON.parse(
                 icon.dataset.characterVideos
               );
               const characterVideo = characterVideos[activeCharacter];
               if (characterVideo && characterVideo.trim() !== "") {
                 icon.dataset.videoFile = characterVideo;
                 icon.dataset.video =
                   filePathURL(characterVideo) || characterVideo;
               }
             } catch (e) {
               console.error("[Swap] Erro ao processar character_videos:", e);
             }
           } else if (hasCharacterVideos && activeCharacter === null) {
             // Restaura vídeo original quando volta ao personagem padrão (null)
             const originalVideo = icon.dataset.originalVideoFile || "";
             if (originalVideo) {
               icon.dataset.videoFile = originalVideo;
               icon.dataset.video =
                 filePathURL(originalVideo) || originalVideo;
             }
           }
         }
       }
     );
   }
   // Troca entre personagens (genérico)
   function handleSwapCharacter(swapIconEl) {
     if (!swapIconEl) return;
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return;
     const availableCharacters = detectAvailableCharacters();
     if (availableCharacters.length === 0) {
       // Se não há personagens específicos, não faz nada (não há swap)
       return;
     }
     // Se activeCharacter é null, inicializa com o primeiro personagem disponível
     if (activeCharacter === null) {
       activeCharacter = availableCharacters[0];
     } else {
       // Encontra o índice do personagem atual e avança para o próximo
       const currentIndex = availableCharacters.indexOf(activeCharacter);
       const nextIndex = (currentIndex + 1) % availableCharacters.length;
       activeCharacter = availableCharacters[nextIndex];
     }
     // Aplica o novo estado
     applyCharacterSwapState();
     // Atualiza todas as skills na barra
     Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       (icon) => {
         const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
         const hasCharacterVideos =
           icon.dataset.characterVideos &&
           icon.dataset.characterVideos.trim() !== "";
         const baseVideoFile = icon.dataset.videoFile || "";
         const baseVideoURL = icon.dataset.video || "";
         // Salva o vídeo original se ainda não foi salvo
         if (hasCharacterVideos && !icon.dataset.originalVideoFile) {
           icon.dataset.originalVideoFile =
             baseVideoFile || baseVideoURL || "";
         }
         // Desabilita/habilita skills baseado no personagem ativo
         if (onlyCharacter) {
           // Skill específica de um personagem
           if (onlyCharacter === activeCharacter) {
             // Skill do personagem ativo → habilitar
             icon.style.opacity = "1";
             icon.style.filter = "";
             icon.style.pointerEvents = "";
             icon.classList.remove("disabled-skill");
           } else {
             // Skill de outro personagem → desabilitar (escuras)
             icon.style.opacity = "0.3";
             icon.style.filter = "grayscale(100%)";
             icon.style.pointerEvents = "none";
             icon.classList.add("disabled-skill");
           }
         } else {
           // Skill compartilhada → sempre habilitada
           icon.style.opacity = "1";
           icon.style.filter = "";
           icon.style.pointerEvents = "";
           icon.classList.remove("disabled-skill");
           // Atualiza vídeo se houver character_videos e personagem ativo
           if (hasCharacterVideos && activeCharacter) {
             try {
               const characterVideos = JSON.parse(
                 icon.dataset.characterVideos
               );
               const characterVideo = characterVideos[activeCharacter];
               if (characterVideo && characterVideo.trim() !== "") {
                 icon.dataset.videoFile = characterVideo;
                 icon.dataset.video =
                   filePathURL(characterVideo) || characterVideo;
               }
             } catch (e) {
               console.error("[Swap] Erro ao processar character_videos:", e);
             }
           } else if (hasCharacterVideos && activeCharacter === null) {
             // Restaura vídeo original quando volta ao personagem padrão (null)
             const originalVideo = icon.dataset.originalVideoFile || "";
             if (originalVideo) {
               icon.dataset.videoFile = originalVideo;
               icon.dataset.video =
                 filePathURL(originalVideo) || originalVideo;
             }
           }
         }
       }
     );
   }
   // Detecta qual forma está atualmente visível no DOM
   function detectCurrentForm() {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar || !formsData || Object.keys(formsData).length === 0)
       return null;
     // Coleta todas as skills de form que estão visíveis no DOM
     const allFormSkillNames = new Set();
     Object.values(formsData).forEach((form) => {
       if (form.order && Array.isArray(form.order)) {
         form.order.forEach((skillName) => allFormSkillNames.add(skillName));
       }
     });
     const visibleFormSkillNames = new Set();
     Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       (icon) => {
         const name = (icon.dataset.nome || "").trim();
         if (name && allFormSkillNames.has(name)) {
           visibleFormSkillNames.add(name);
         }
       }
     );
     // Compara com cada forma para ver qual corresponde
     for (const [formName, formData] of Object.entries(formsData)) {
       if (formData.order && Array.isArray(formData.order)) {
         const formSkillSet = new Set(formData.order);
         // Verifica se todas as skills desta forma estão visíveis
         let allMatch = true;
         for (const skillName of formData.order) {
           if (!visibleFormSkillNames.has(skillName)) {
             allMatch = false;
             break;
           }
         }
         if (
           allMatch &&
           formData.order.length === visibleFormSkillNames.size
         ) {
           return formName;
         }
       }
     }
     return null;
   }
   function switchForm() {
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return;
     // Lê dados de forms do atributo data-forms
     try {
       const formsJSON = skillsRoot.dataset.forms || "{}";
       if (formsJSON && formsJSON !== "{}") {
         formsData = JSON.parse(formsJSON);
       }
     } catch (e) {
       console.error("[Forms] Erro ao parsear forms:", e);
       return;
     }
     if (!formsData || Object.keys(formsData).length === 0) {
       return; // Não tem forms, não faz nada
     }
     // Identifica skills fixas (sempre presentes)
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return;
     // Busca a skill com form_switch dinamicamente (genérico)
     const changeFormIcon = Array.from(
       iconBar.querySelectorAll(".skill-icon[data-index]")
     ).find(
       (icon) =>
         icon.dataset.formSwitch === "true" ||
         icon.getAttribute("data-form-switch") === "true"
     );
     if (changeFormIcon) {
       changeFormIcon.classList.add("active");
     }
     // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
     const allFormSkillNames = new Set();
     Object.values(formsData).forEach((form) => {
       if (form.order && Array.isArray(form.order)) {
         form.order.forEach((skillName) => allFormSkillNames.add(skillName));
       }
     });
     // Skills fixas = todas as skills na barra que não estão em nenhuma forms
     fixedSkills = Array.from(
       iconBar.querySelectorAll(".skill-icon[data-index]")
     )
       .filter((icon) => {
         const name = (icon.dataset.nome || "").trim();
         return name && !allFormSkillNames.has(name);
       })
       .map((icon) => snapshotIconData(icon));
     // Obtém lista de formas disponíveis
     const formNames = Object.keys(formsData);
     if (formNames.length === 0) return;
     // Determina próxima forma
     // Se currentForm é null, detecta qual forma está atualmente visível no DOM
     if (currentForm === null) {
       currentForm = detectCurrentForm();
     }
     // Se ainda não conseguiu detectar, usa a primeira forma como fallback
     if (!currentForm && formNames.length > 0) {
       currentForm = formNames[0];
     }
     // Cria ordem circular fixa baseada na forma atual detectada
     // Se detectamos "Brain Point", ordem é: Brain → Kung Fu → Heavy → Brain
     // Se detectamos "Kung Fu Point", ordem é: Kung Fu → Heavy → Brain → Kung Fu
     // Se detectamos "Heavy Point", ordem é: Heavy → Brain → Kung Fu → Heavy
     let orderedFormNames = [];
     if (currentForm === "Brain Point" && formNames.length === 3) {
       // Ordem conhecida: Brain → Kung Fu → Heavy
       if (
         formNames.includes("Kung Fu Point") &&
         formNames.includes("Heavy Point")
       ) {
         orderedFormNames = ["Brain Point", "Kung Fu Point", "Heavy Point"];
       }
     } else if (currentForm === "Kung Fu Point" && formNames.length === 3) {
       // Ordem conhecida: Kung Fu → Heavy → Brain
       if (
         formNames.includes("Heavy Point") &&
         formNames.includes("Brain Point")
       ) {
         orderedFormNames = ["Kung Fu Point", "Heavy Point", "Brain Point"];
       }
     } else if (currentForm === "Heavy Point" && formNames.length === 3) {
       // Ordem conhecida: Heavy → Brain → Kung Fu
       if (
         formNames.includes("Brain Point") &&
         formNames.includes("Kung Fu Point")
       ) {
         orderedFormNames = ["Heavy Point", "Brain Point", "Kung Fu Point"];
       }
     }
     // Se não conseguiu criar ordem conhecida, usa ordem alfabética como fallback
     if (orderedFormNames.length === 0) {
       orderedFormNames = [...formNames].sort();
       // Se sabemos a forma atual, reorganiza para começar por ela
       if (currentForm) {
         const currentIdx = orderedFormNames.indexOf(currentForm);
         if (currentIdx !== -1) {
           orderedFormNames = [
             ...orderedFormNames.slice(currentIdx),
             ...orderedFormNames.slice(0, currentIdx),
           ];
         }
       }
     }
     const currentIdx = orderedFormNames.indexOf(currentForm);
     if (currentIdx === -1) return; // Forma não encontrada
     const nextIdx = (currentIdx + 1) % orderedFormNames.length;
     const nextForm = orderedFormNames[nextIdx];
     currentForm = nextForm;
     // Atualiza barra de skills (que vai remover o active depois da animação)
     updateSkillsBarForForm(nextForm, formsData[nextForm], changeFormIcon);
   }
   function snapshotIconData(icon) {
     const img = icon.querySelector("img");
     const iconURL = img ? img.src : "";
     const subsRaw = icon.dataset.subs || icon.getAttribute("data-subs") || "";
     let subs = null;
     try {
       subs = subsRaw ? JSON.parse(subsRaw) : null;
     } catch {
       subs = null;
     }
     let flags = null;
     if (icon.dataset.flags) {
       try {
         flags = JSON.parse(icon.dataset.flags);
       } catch (e) { }
     }
     let weapon = null;
     if (icon.dataset.weapon) {
       try {
         weapon = JSON.parse(icon.dataset.weapon);
       } catch (e) { }
     }
     return {
       name: icon.dataset.nome || icon.dataset.name || "",
       index: icon.dataset.index || "",
       level: icon.dataset.level || "",
       desc: icon.dataset.desc || "",
       descPt: icon.dataset.descPt || "",
       descEn: icon.dataset.descEn || "",
       descEs: icon.dataset.descEs || "",
       descPl: icon.dataset.descPl || "",
       attrs: icon.dataset.atr || icon.dataset.attrs || "",
       video: icon.dataset.video || "",
       iconURL,
       iconFile: icon.dataset.iconFile || "",
       subs,
       flags,
       weapon,
       formSwitch: icon.dataset.formSwitch || "",
     };
   }
   function updateSkillsBarForForm(formName, formData, changeFormIconEl) {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar || !formData || !formData.skills) return;
     // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
     const allFormSkillNames = new Set();
     Object.values(formsData).forEach((form) => {
       if (form.order && Array.isArray(form.order)) {
         form.order.forEach((skillName) => allFormSkillNames.add(skillName));
       }
     });
     // Remove skills de forma antigas (que não são fixas) com animação de saída
     const existingIcons = Array.from(
       iconBar.querySelectorAll(".skill-icon[data-index]")
     );
     const iconsToRemove = [];
     existingIcons.forEach((icon) => {
       const name = (icon.dataset.nome || "").trim();
       if (name && allFormSkillNames.has(name)) {
         iconsToRemove.push(icon);
       }
     });
     // Anima saída das skills antigas
     iconsToRemove.forEach((icon) => {
       icon.style.transition = "opacity .15s ease, transform .15s ease";
       icon.style.opacity = "0";
       icon.style.transform = "translateY(-6px)";
     });
     // Encontra a skill com form_switch dinamicamente (genérico)
     const changeFormIcon = Array.from(
       iconBar.querySelectorAll(".skill-icon[data-index]")
     ).find(
       (icon) =>
         icon.dataset.formSwitch === "true" ||
         icon.getAttribute("data-form-switch") === "true"
     );
     // Encontra a próxima skill fixa depois do form_switch dinamicamente
     let nextFixedSkillIcon = null;
     if (changeFormIcon) {
       const allIcons = Array.from(
         iconBar.querySelectorAll(".skill-icon[data-index]")
       ).sort((a, b) => {
         return (
           parseInt(a.dataset.index || "0", 10) -
           parseInt(b.dataset.index || "0", 10)
         );
       });
       const changeFormIndex = allIcons.indexOf(changeFormIcon);
       const allFormSkillNames = new Set();
       Object.values(formsData).forEach((form) => {
         if (form.order && Array.isArray(form.order)) {
           form.order.forEach((skillName) => allFormSkillNames.add(skillName));
         }
       });
       // Procura a próxima skill fixa (que não está em forms)
       for (let i = changeFormIndex + 1; i < allIcons.length; i++) {
         const icon = allIcons[i];
         const name = (icon.dataset.nome || "").trim();
         if (name && !allFormSkillNames.has(name)) {
           nextFixedSkillIcon = icon;
           break;
         }
       }
     }
     if (!changeFormIcon || !nextFixedSkillIcon) {
       console.warn(
         "[Forms] Skill com form_switch ou próxima skill fixa não encontrada"
       );
       return;
     }
     const formSkills = formData.skills || [];
     const formOrder = formData.order || [];
     // Primeira skill da forma (vai ANTES do Change Form)
     const firstSkillName = formOrder[0];
     const firstSkillData = formSkills.find((s) => s.name === firstSkillName);
     // Terceira skill da forma (vai DEPOIS do Change Form, ANTES do Guard Point)
     const thirdSkillName = formOrder[1]; // Segunda na ordem = terceira skill (índice 1)
     const thirdSkillData = formSkills.find((s) => s.name === thirdSkillName);
     // Quinta skill da forma (vai DEPOIS do Guard Point)
     const fifthSkillName = formOrder[2]; // Terceira na ordem = quinta skill (índice 2)
     const fifthSkillData = formSkills.find((s) => s.name === fifthSkillName);
     // Cria fragments para inserir
     const firstFragment = document.createDocumentFragment();
     const thirdFragment = document.createDocumentFragment();
     const fifthFragment = document.createDocumentFragment();
     if (firstSkillData) {
       const iconElement = createSkillIconElement(firstSkillData, 1);
       firstFragment.appendChild(iconElement);
     }
     if (thirdSkillData) {
       const changeFormIndex = parseInt(
         changeFormIcon.dataset.index || "2",
         10
       );
       const iconElement = createSkillIconElement(
         thirdSkillData,
         changeFormIndex + 1
       );
       thirdFragment.appendChild(iconElement);
     }
     if (fifthSkillData) {
       const nextFixedSkillIndex = parseInt(
         nextFixedSkillIcon.dataset.index || "4",
         10
       );
       const iconElement = createSkillIconElement(
         fifthSkillData,
         nextFixedSkillIndex + 1
       );
       fifthFragment.appendChild(iconElement);
     }
     // Remove os ícones antigos do DOM após animação
     setTimeout(() => {
       iconsToRemove.forEach((icon) => {
         if (icon.parentNode) {
           icon.remove();
         }
       });
       // Insere a primeira skill ANTES da skill com form_switch
       if (firstFragment.hasChildNodes()) {
         iconBar.insertBefore(firstFragment, changeFormIcon);
       }
       // Insere a terceira skill DEPOIS da skill com form_switch, ANTES da próxima skill fixa
       if (thirdFragment.hasChildNodes()) {
         iconBar.insertBefore(thirdFragment, nextFixedSkillIcon);
       }
       // Insere a quinta skill DEPOIS da próxima skill fixa
       if (fifthFragment.hasChildNodes()) {
         if (nextFixedSkillIcon.nextSibling) {
           iconBar.insertBefore(fifthFragment, nextFixedSkillIcon.nextSibling);
         } else {
           iconBar.appendChild(fifthFragment);
         }
       }
       // Anima entrada das novas skills (similar a animateIconsBarEntrance)
       const newIconsInBar = Array.from(
         iconBar.querySelectorAll(".skill-icon[data-index]")
       ).filter((icon) => {
         const name = (icon.dataset.nome || "").trim();
         return name && allFormSkillNames.has(name);
       });
       newIconsInBar.forEach((icon, idx) => {
         icon.style.opacity = "0";
         icon.style.transform = "translateY(6px)";
         requestAnimationFrame(() => {
           setTimeout(() => {
             icon.style.transition = "opacity .18s ease, transform .18s ease";
             icon.style.opacity = "1";
             icon.style.transform = "translateY(0)";
           }, idx * 24);
         });
       });
       // Remove slots de descrição das skills de forma antigas e cria novos
       const descBox = document.querySelector(".skills-details .desc-box");
       if (descBox) {
         // Encontra os slots de descrição dos elementos fixos
         const changeFormIndex = parseInt(
           changeFormIcon.dataset.index || "1",
           10
         );
         const nextFixedSkillIndex = parseInt(
           nextFixedSkillIcon.dataset.index || "1",
           10
         );
         const changeFormDescSlot = descBox.querySelector(
           `.skill-desc[data-index="${changeFormIndex}"]`
         );
         const nextFixedSkillDescSlot = descBox.querySelector(
           `.skill-desc[data-index="${nextFixedSkillIndex}"]`
         );
         // Cria slot para primeira skill (antes do Change Form)
         if (firstSkillData && changeFormDescSlot) {
           const descSlot = document.createElement("div");
           descSlot.className = "skill-desc";
           descSlot.setAttribute("data-index", changeFormIndex);
           descBox.insertBefore(descSlot, changeFormDescSlot);
         }
         // Cria slot para terceira skill (depois da skill com form_switch, antes da próxima skill fixa)
         if (thirdSkillData && nextFixedSkillDescSlot) {
           const descSlot = document.createElement("div");
           descSlot.className = "skill-desc";
           descSlot.setAttribute("data-index", nextFixedSkillIndex);
           descBox.insertBefore(descSlot, nextFixedSkillDescSlot);
         }
         // Cria slot para quinta skill (depois da próxima skill fixa)
         if (fifthSkillData && nextFixedSkillDescSlot) {
           const descSlot = document.createElement("div");
           descSlot.className = "skill-desc";
           descSlot.setAttribute("data-index", nextFixedSkillIndex + 1);
           if (nextFixedSkillDescSlot.nextSibling) {
             descBox.insertBefore(
               descSlot,
               nextFixedSkillDescSlot.nextSibling
             );
           } else {
             descBox.appendChild(descSlot);
           }
         }
       }
       // Re-numera todas as skills na ordem do DOM
       const allIcons = Array.from(
         iconBar.querySelectorAll(".skill-icon[data-index]")
       );
       let currentIndex = 1;
       allIcons.forEach((icon) => {
         const oldIndex = icon.dataset.index;
         icon.setAttribute("data-index", currentIndex);
         // Atualiza slot de descrição
         if (descBox && oldIndex) {
           const descSlot = descBox.querySelector(
             `.skill-desc[data-index="${oldIndex}"]`
           );
           if (descSlot) {
             descSlot.setAttribute("data-index", currentIndex);
           }
         }
         currentIndex++;
       });
       // Re-wire eventos
       wireClicksForCurrentBar();
       wireTooltipsForNewIcons();
       // Remove active do Change Form após animação completar
       setTimeout(() => {
         if (changeFormIconEl) {
           changeFormIconEl.classList.remove("active");
         }
       }, newIconsInBar.length * 24 + 180);
     }, 150);
   }
   function createSkillIconElement(skill, index) {
     const iconWrap = document.createElement("div");
     iconWrap.className = "skill-icon";
     iconWrap.setAttribute("data-index", index);
     iconWrap.setAttribute("data-nome", skill.name || "");
     iconWrap.setAttribute("data-desc", "");
     iconWrap.setAttribute(
       "data-atr",
       makeAttrString(
         skill.powerpve,
         skill.powerpvp,
         skill.energy,
         skill.cooldown
       )
     );
     iconWrap.setAttribute(
       "data-video",
       skill.video ? filePathURL(skill.video) : ""
     );
     iconWrap.setAttribute("data-video-preload", "auto");
     iconWrap.setAttribute("data-icon-file", skill.icon || "");
     iconWrap.setAttribute("data-video-file", skill.video || "");
     if (
       skill.level &&
       skill.level !== "" &&
       skill.level.toUpperCase() !== "NIVEL"
     ) {
       iconWrap.setAttribute("data-level", skill.level);
     }
     if (skill.desc_i18n) {
       if (skill.desc_i18n.pt)
         iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
       if (skill.desc_i18n.en)
         iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
       if (skill.desc_i18n.es)
         iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
       if (skill.desc_i18n.pl)
         iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
     }
     if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
       iconWrap.setAttribute("data-subs", JSON.stringify(skill.subs));
     }
     if (
       skill.suborder &&
       Array.isArray(skill.suborder) &&
       skill.suborder.length > 0
     ) {
       iconWrap.setAttribute("data-suborder", JSON.stringify(skill.suborder));
     }
     if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
       iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
     }
     if (
       skill.weapon &&
       typeof skill.weapon === "object" &&
       Object.keys(skill.weapon).length > 0
     ) {
       iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
     }
     if (skill.effect && typeof skill.effect === "object") {
       try {
         iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
       } catch (e) { }
     }
     const img = document.createElement("img");
     img.className = "skill-icon-img";
     img.src = filePathURL(skill.icon || "");
     img.alt = "";
     iconWrap.appendChild(img);
     return iconWrap;
   }
   function createSkillIcon(skill, index, container) {
     // Cria ícone similar ao que é gerado pelo servidor
     const iconWrap = document.createElement("div");
     iconWrap.className = "skill-icon";
     iconWrap.setAttribute("data-index", index);
     iconWrap.setAttribute("data-nome", skill.name || "");
     iconWrap.setAttribute("data-desc", "");
     iconWrap.setAttribute(
       "data-atr",
       makeAttrString(
         skill.powerpve,
         skill.powerpvp,
         skill.energy,
         skill.cooldown
       )
     );
     iconWrap.setAttribute(
       "data-video",
       skill.video ? filePathURL(skill.video) : ""
     );
     iconWrap.setAttribute("data-video-preload", "auto");
     iconWrap.setAttribute("data-icon-file", skill.icon || "");
     iconWrap.setAttribute("data-video-file", skill.video || "");
     if (
       skill.level &&
       skill.level !== "" &&
       skill.level.toUpperCase() !== "NIVEL"
     ) {
       iconWrap.setAttribute("data-level", skill.level);
     }
     if (skill.desc_i18n) {
       if (skill.desc_i18n.pt)
         iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
       if (skill.desc_i18n.en)
         iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
       if (skill.desc_i18n.es)
         iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
       if (skill.desc_i18n.pl)
         iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
     }
     if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
       iconWrap.setAttribute("data-subs", JSON.stringify(skill.subs));
     }
     if (
       skill.suborder &&
       Array.isArray(skill.suborder) &&
       skill.suborder.length > 0
     ) {
       iconWrap.setAttribute("data-suborder", JSON.stringify(skill.suborder));
     }
     if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
       iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
     }
     if (
       skill.weapon &&
       typeof skill.weapon === "object" &&
       Object.keys(skill.weapon).length > 0
     ) {
       iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
     }
     if (skill.effect && typeof skill.effect === "object") {
       try {
         iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
       } catch (e) { }
     }
     const img = document.createElement("img");
     img.className = "skill-icon-img";
     img.src = filePathURL(skill.icon || "");
     img.alt = "";
     iconWrap.appendChild(img);
     container.appendChild(iconWrap);
   }
   function makeAttrString(pve, pvp, energy, cd) {
     const parts = [
       pve !== undefined && pve !== null && pve !== "" ? String(pve) : "-",
       pvp !== undefined && pvp !== null && pvp !== "" ? String(pvp) : "-",
       energy !== undefined && energy !== null && energy !== ""
         ? String(energy)
         : "-",
       cd !== undefined && cd !== null && cd !== "" ? String(cd) : "-",
     ];
     return parts.join(", ");
   }
   function filePathURL(fileName) {
     // Evita requisições para valores vazios
     if (!fileName || fileName.trim() === "") {
       return "";
     }
     const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
     const base =
       window.mw && mw.util && typeof mw.util.wikiScript === "function"
         ? mw.util.wikiScript()
         : window.mw && window.mw.config
           ? mw.config.get("wgScript") || "/index.php"
           : "/index.php";
     // Garante HTTPS para evitar Mixed Content
     let url = `${base}?title=Especial:FilePath/${f}`;
     if (window.location.protocol === "https:" && url.startsWith("http://")) {
       url = url.replace("http://", "https://");
     }
     return url;
   }
   function slugify(s) {
     if (!s) return "";
     return String(s)
       .toLowerCase()
       .normalize("NFD")
       .replace(/[\u0300-\u036f]/g, "")
       .replace(/[^\w\s-]/g, "")
       .replace(/[\s:/\-]+/g, "-")
       .replace(/^-+|-+$/g, "")
       .replace(/-+/g, "-");
   }
   window.__skillSlugify = slugify;
   function getLangKey() {
     const skillsRoot = document.getElementById("skills");
     const raw = (
       document.documentElement.lang ||
       skillsRoot?.dataset.i18nDefault ||
       "pt"
     ).toLowerCase();
     return raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
   }
   function chooseDescFrom(obj) {
     const lang = getLangKey();
     // Aceita tanto desc_i18n quanto desc para compatibilidade
     const pack = obj.desc_i18n ||
       obj.desc || {
       pt: obj.descPt,
       en: obj.descEn,
       es: obj.descEs,
       pl: obj.descPl,
     };
     return (
       (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || ""
     );
   }
   function renderSubAttributesFromObj(s, L) {
     const chip = (label, val) =>
       val

 ? `

${label}${val}

`

         : "";
     const pve = (s.powerpve || "").toString().trim();
     const pvp = (s.powerpvp || "").toString().trim();
     const en = (s.energy || "").toString().trim();
     const cd = (s.cooldown || "").toString().trim();
     const rows = [
       cd ? chip(L.cooldown, cd) : "",
       en
         ? chip(
           en.startsWith("-") ? L.energy_cost : L.energy_gain,
           en.startsWith("-") ? en.replace(/^-/, "") : en.replace(/^\+?/, "")
         )
         : "",
       pve ? chip(L.power, pve) : "",
       pvp ? chip(L.power_pvp, pvp) : "",
     ].filter(Boolean);

return rows.length ? `

${rows.join("")}

` : "";

   }
   function getFlagIconURL(key) {
     if (!FLAG_ICON_FILES[key]) return "";
     if (!flagIconURLCache.has(key)) {
       flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key]));
     }
     return flagIconURLCache.get(key);
   }
   function renderFlagsRow(flags) {
     const arr = (flags || []).filter(Boolean);
     if (!arr.length) return "";
     const cacheKey = arr.join("|");
     if (flagRowCache.has(cacheKey)) {
       return flagRowCache.get(cacheKey);
     }
     const items = arr
       .map((k) => {
         const url = getFlagIconURL(k);
         return url
           ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">`
           : "";
       })
       .join("");
     const html = items

 ? `

${items}

`

       : "";
     if (html) flagRowCache.set(cacheKey, html);
     return html;
   }
   function applyFlagTooltips(container) {
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return;
     let pack = {};
     try {
       pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
     } catch (e) { }
     const lang = getLangKey();
     const dict = pack[lang] || pack.pt || {};
     const flags = container.querySelectorAll(
       ".skill-flags .skill-flag[data-flag]"
     );
     const tooltip = window.__globalSkillTooltip;
     if (!tooltip) return;
     flags.forEach((el) => {
       const key = el.getAttribute("data-flag");
       const tip = (dict && dict[key]) || "";
       if (!tip) return;
       if (el.dataset.flagTipWired) return;
       el.dataset.flagTipWired = "1";
       el.setAttribute("aria-label", tip);
       if (el.hasAttribute("title")) el.removeAttribute("title");
       el.addEventListener("mouseenter", () => {
         const tipEl = document.querySelector(".skill-tooltip");
         if (tipEl) tipEl.classList.add("flag-tooltip");
         tooltip.show(el, tip);
       });
       el.addEventListener("mousemove", () => {
         if (performance.now() >= tooltip.lockUntil.value) {
           tooltip.measureAndPos(el);
         }
       });
       el.addEventListener("click", () => {
         tooltip.lockUntil.value = performance.now() + 240;
         tooltip.measureAndPos(el);
       });
       el.addEventListener("mouseleave", () => {
         const tipEl = document.querySelector(".skill-tooltip");
         if (tipEl) tipEl.classList.remove("flag-tooltip");
         tooltip.hide();
       });
     });
   }
   // ====== Skill/Subskill inheritance helpers ======
   const mainSkillsMeta = {
     byIndex: new Map(),
     byName: new Map(),
     ready: false,
   };
   function normalizeFileURL(raw, fallback = "") {
     if (!raw) return fallback;
     const val = String(raw).trim();
     if (!val) return fallback;
     if (
       /^(https?:)?\/\//i.test(val) ||
       val.startsWith("data:") ||
       val.includes("Especial:FilePath/")
     ) {
       return val;
     }
     return filePathURL(val);
   }
   function extractFileNameFromURL(url) {
     if (!url) return "";
     const match = String(url).match(/(?:FilePath\/)([^&?]+)/i);
     return match ? decodeURIComponent(match[1]) : "";
   }
   function parseAttrString(raw) {
     const parts = (raw || "").split(",").map((v) => v.trim());
     const safe = (idx) => {
       const val = parts[idx] || "";
       return val && val !== "-" ? val : "";
     };
     return {
       powerpve: safe(0),
       powerpvp: safe(1),
       energy: safe(2),
       cooldown: safe(3),
     };
   }
   function hasText(value) {
     return typeof value === "string"
       ? value.trim() !== ""
       : value !== undefined && value !== null;
   }
   function pickFilled(current, fallback) {
     if (current === 0 || current === "0") return current;
     if (!hasText(current)) return fallback;
     return current;
   }
   function buildMainSkillsMeta(nodes) {
     if (mainSkillsMeta.ready) {
       return mainSkillsMeta;
     }
     (nodes || []).forEach((icon) => {
       const index = (icon.dataset.index || "").trim();
       if (!index) return;
       const name = (icon.dataset.nome || icon.dataset.name || "").trim();
       const attrs = parseAttrString(icon.dataset.atr || "");
       let iconFile = (icon.dataset.iconFile || "").trim();
       if (!iconFile) {
         const imgSrc = icon.querySelector("img")?.src || "";
         const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
         iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : "";
       }
       let videoFile = (icon.dataset.videoFile || "").trim();
       if (!videoFile) {
         videoFile = extractFileNameFromURL(icon.dataset.video || "");
       }
       const meta = {
         index,
         name,
         icon: iconFile || "",
         level: icon.dataset.level || "",
         video: videoFile || "",
         powerpve: attrs.powerpve || "",
         powerpvp: attrs.powerpvp || "",
         energy: attrs.energy || "",
         cooldown: attrs.cooldown || "",
         desc: icon.dataset.desc || "",
         descPt: icon.dataset.descPt || "",
         descEn: icon.dataset.descEn || "",
         descEs: icon.dataset.descEs || "",
         descPl: icon.dataset.descPl || "",
       };
       mainSkillsMeta.byIndex.set(index, meta);
       mainSkillsMeta.byIndex.set(parseInt(index, 10), meta);
       if (name) {
         mainSkillsMeta.byName.set(name, meta);
       }
     });
     mainSkillsMeta.ready = true;
     return mainSkillsMeta;
   }
   function inheritSubskillFromMain(sub, meta) {
     if (!sub || !meta) return sub;
     // Verifica se herança está desabilitada
     const shouldInherit = !(
       sub.inherit === false ||
       sub.inherit === "no" ||
       sub.inherit === "false"
     );
     // Suporta refS (novo) e refM (legado)
     const refS = ((sub.refS || sub.S || sub.s || "") + "").trim();
     const refIndex = ((sub.refM || sub.M || sub.m || "") + "").trim();
     let name = (sub.name || sub.n || "").trim();
     let main = null;
     // Se herança está desabilitada, não busca a skill principal
     if (!shouldInherit) {
       return { ...sub };
     }
     // Primeiro tenta por refS
     if (refS) {
       main = meta.byIndex.get(refS) || meta.byIndex.get(parseInt(refS, 10));
     }
     // Depois por refM
     if (!main && refIndex) {
       main =
         meta.byIndex.get(refIndex) ||
         meta.byIndex.get(parseInt(refIndex, 10));
     }
     // Por último pelo nome
     if (!main && name) {
       main = meta.byName.get(name);
     }
     if (!main) {
       return sub;
     }
     const hydrated = { ...sub };
     if (!name && main.name) {
       name = main.name;
     }
     hydrated.name = name || hydrated.name || main.name || "";
     hydrated.icon = pickFilled(hydrated.icon, main.icon || "");
     hydrated.level = pickFilled(hydrated.level, main.level || "");
     // Vídeo NUNCA é herdado da skill principal
     const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
     hydrated.video = hasOwnVideo ? sub.video || "" : "";
     hydrated.powerpve = pickFilled(hydrated.powerpve, main.powerpve || "");
     hydrated.powerpvp = pickFilled(hydrated.powerpvp, main.powerpvp || "");
     hydrated.energy = pickFilled(hydrated.energy, main.energy || "");
     hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || "");
     // Descrição: sempre vem da subskill, nunca herda
     // PROTEÇÃO TOTAL: Remove qualquer descrição que possa ter sido copiada do main
     if (
       !sub.desc &&
       !sub.descPt &&
       !sub.descEn &&
       !sub.descEs &&
       !sub.descPl &&
       !sub.desc_i18n
     ) {
       hydrated.desc = undefined;
       hydrated.descPt = undefined;
       hydrated.descEn = undefined;
       hydrated.descEs = undefined;
       hydrated.descPl = undefined;
       hydrated.desc_i18n = undefined;
     } else {
       // Se subskill tem descrição, normaliza para desc_i18n
       if (
         !hydrated.desc_i18n &&
         (hydrated.descPt ||
           hydrated.descEn ||
           hydrated.descEs ||
           hydrated.descPl)
       ) {
         hydrated.desc_i18n = {
           pt: hydrated.descPt || "",
           en: hydrated.descEn || "",
           es: hydrated.descEs || "",
           pl: hydrated.descPl || "",
         };
       }
     }
     return hydrated;
   }
   function inheritSubskillTree(subs, meta) {
     if (!Array.isArray(subs)) return [];
     return subs.map((sub) => {
       const hydrated = inheritSubskillFromMain(sub, meta);
       if (Array.isArray(hydrated.subs)) {
         hydrated.subs = inheritSubskillTree(hydrated.subs, meta);
       }
       return hydrated;
     });
   }
   function collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet) {
     if (!Array.isArray(subs)) return;
     subs.forEach((sub) => {
       const iconURL = normalizeFileURL(sub.icon || "", "");
       if (iconURL && iconURL !== "") iconsSet.add(iconURL);
       // Vídeo normal
       if (sub.video && sub.video.trim() !== "") {
         const videoURL = normalizeFileURL(sub.video);
         if (videoURL) videosSet.add(videoURL);
       }
       // Vídeo de weapon
       if (
         sub.weapon &&
         typeof sub.weapon === "object" &&
         sub.weapon.video &&
         sub.weapon.video.trim() !== ""
       ) {
         const weaponVideoURL = normalizeFileURL(sub.weapon.video);
         if (weaponVideoURL) videosSet.add(weaponVideoURL);
       }
       if (Array.isArray(sub.flags)) {
         sub.flags.forEach((flagKey) => {
           const url = getFlagIconURL(flagKey);
           if (url) flagsSet.add(url);
         });
       }
       if (Array.isArray(sub.subs)) {
         collectAssetsFromSubs(sub.subs, iconsSet, videosSet, flagsSet);
       }
     });
   }
   function buildAssetManifest() {
     if (window.__skillAssetManifest && window.__skillAssetManifest.ready) {
       return window.__skillAssetManifest;
     }
     const iconsSet = new Set();
     const videosSet = new Set();
     const flagsSet = new Set();
     iconItems.forEach((el) => {
       const img = el.querySelector("img");
       if (img && img.src) {
         iconsSet.add(img.src);
       } else if (el.dataset.iconFile) {
         const iconURL = normalizeFileURL(el.dataset.iconFile);
         if (iconURL) iconsSet.add(iconURL);
       }
       // Vídeo normal da skill
       const videoRaw = (
         el.dataset.videoFile ||
         el.dataset.video ||
         ""
       ).trim();
       if (videoRaw) {
         const videoURL = normalizeFileURL(videoRaw);
         if (videoURL) videosSet.add(videoURL);
       }
       // Vídeo de weapon da skill
       if (el.dataset.weapon) {
         try {
           const weaponData = JSON.parse(el.dataset.weapon);
           if (
             weaponData &&
             weaponData.video &&
             weaponData.video.trim() !== ""
           ) {
             const weaponVideoURL = normalizeFileURL(weaponData.video);
             if (weaponVideoURL) videosSet.add(weaponVideoURL);
           }
         } catch (e) { }
       }
       if (el.dataset.flags) {
         try {
           const parsedFlags = JSON.parse(el.dataset.flags);
           (parsedFlags || []).forEach((flagKey) => {
             const url = getFlagIconURL(flagKey);
             if (url) flagsSet.add(url);
           });
         } catch (e) { }
       }
       if (el.dataset.subs) {
         try {
           const subs = JSON.parse(el.dataset.subs);
           collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet);
         } catch (e) { }
       }
     });
     Object.keys(FLAG_ICON_FILES).forEach((flagKey) => {
       const url = getFlagIconURL(flagKey);
       if (url) flagsSet.add(url);
     });
     const manifest = {
       icons: iconsSet,
       videos: videosSet,
       flags: flagsSet,
       ready: true,
     };
     window.__skillAssetManifest = manifest;
     return manifest;
   }
   const subskillVideosCache = new Map();
   window.__subskillVideosCache = subskillVideosCache;
   // Cache de vídeos que falharam (404) para evitar tentativas repetidas
   const failedVideosCache = new Set();
   const missingVideosReported = new Set(); // Para avisar apenas uma vez sobre vídeos faltantes
   // Cache de parsing JSON para evitar re-parsear os mesmos dados
   const jsonParseCache = new WeakMap();
   function getCachedJSON(el, key) {
     if (!el) return null;
     const cache = jsonParseCache.get(el) || {};
     if (cache[key] !== undefined) return cache[key];
     const raw = el.dataset[key] || el.getAttribute(`data-${key}`);
     if (!raw) {
       cache[key] = null;
       jsonParseCache.set(el, cache);
       return null;
     }
     try {
       const parsed = JSON.parse(raw);
       cache[key] = parsed;
       jsonParseCache.set(el, cache);
       return parsed;
     } catch (e) {
       cache[key] = null;
       jsonParseCache.set(el, cache);
       return null;
     }
   }
   let assetManifest = null;
   const skillsTab = $("#skills");
   const skinsTab = $("#skins");
   ensureRemoved(".top-rail");
   // NÃO remove .content-card aqui - será gerenciado abaixo
   // ensureRemoved('.content-card'); // REMOVIDO - pode estar removendo skills-container
   ensureRemoved(".video-placeholder");
   Array.from(
     document.querySelectorAll(
       ".card-skins-title, .card-skins .card-skins-title, .cardskins-title, .rail-title"
     )
   ).forEach((t) => {
     if ((t.textContent || "").trim().toLowerCase().includes("skins")) {
       t.remove();
     }
   });
   if (skillsTab) {
     const iconBar = skillsTab.querySelector(".icon-bar");
     if (iconBar) {
       const rail = document.createElement("div");
       rail.className = "top-rail skills";
       // Criar wrapper de scroll para permitir glow sem clipping
       if (
         !iconBar.parentElement ||
         !iconBar.parentElement.classList.contains("icon-scroll-x")
       ) {
         const scrollWrapper = document.createElement("div");
         scrollWrapper.className = "icon-scroll-x";
         scrollWrapper.appendChild(iconBar);
         rail.appendChild(scrollWrapper);
       } else {
         rail.appendChild(iconBar.parentElement);
       }
       skillsTab.prepend(rail);
     }
     // Busca skills-container criado pelo Lua
     const skillsContainer = skillsTab.querySelector(".skills-container");
     // Busca skills-details e video-container (podem estar dentro de skills-container ou soltos)
     let details = skillsTab.querySelector(".skills-details");
     if (!details && skillsContainer) {
       details = skillsContainer.querySelector(".skills-details");
     }
     if (!details) {
       details = document.createElement("div");
       details.className = "skills-details";
     }
     // Busca ou cria desc-box dentro de details
     let descBoxEl = details.querySelector(".desc-box");
     if (!descBoxEl) {
       descBoxEl = document.createElement("div");
       descBoxEl.className = "desc-box";
       details.appendChild(descBoxEl);
     }
     // Busca video-container
     let videoContainer = skillsTab.querySelector(".video-container");
     if (!videoContainer && skillsContainer) {
       videoContainer = skillsContainer.querySelector(".video-container");
     }
     if (!videoContainer) {
       videoContainer = document.createElement("div");
       videoContainer.className = "video-container";
     }
     // Cria ou atualiza content-card skills-grid (estrutura esperada pelo CSS)
     let card = skillsTab.querySelector(".content-card.skills-grid");
     if (!card) {
       card = document.createElement("div");
       card.className = "content-card skills-grid";
       skillsTab.appendChild(card);
     }
     // Move details e videoContainer para dentro do card (estrutura correta)
     // Remove skills-container se existir (não é necessário para o layout)
     if (skillsContainer && skillsContainer.parentNode) {
       // Move os elementos filhos de skills-container para o card
       if (details.parentNode === skillsContainer) {
         skillsContainer.removeChild(details);
       }
       if (videoContainer.parentNode === skillsContainer) {
         skillsContainer.removeChild(videoContainer);
       }
       // Remove skills-container se estiver vazio
       if (skillsContainer.children.length === 0) {
         skillsContainer.remove();
       }
     }
     // Garante que details e videoContainer estão no card na ordem correta
     // skills-details deve vir ANTES de video-container para o grid funcionar
     if (details.parentNode !== card) {
       // Se videoContainer já está no card, insere details antes dele
       if (videoContainer.parentNode === card) {
         card.insertBefore(details, videoContainer);
       } else {
         card.appendChild(details);
       }
     }
     if (videoContainer.parentNode !== card) {
       card.appendChild(videoContainer);
     }
     // Garante ordem: details primeiro, videoContainer depois
     if (details.parentNode === card && videoContainer.parentNode === card) {
       if (details.nextSibling !== videoContainer) {
         card.insertBefore(details, videoContainer);
       }
     }
   }
   // Função para obter iconsBar de forma segura (com retry)
   function getIconsBar() {
     const skillsEl = document.getElementById("skills");
     if (!skillsEl) return null;
     return skillsEl.querySelector(".icon-bar");
   }
   const iconsBar = getIconsBar();
   const skillsTopRail = iconsBar
     ? iconsBar.closest(".top-rail.skills")
     : null;
   const iconItems = iconsBar
     ? Array.from(iconsBar.querySelectorAll(".skill-icon"))
     : [];
   buildMainSkillsMeta(iconItems);
   // Verifica se há weapon em skills principais OU em subskills
   function checkHasAnyWeapon() {
     // Verifica skills principais
     const hasMainWeapon = iconItems.some((el) => {
       const weapon = el.dataset.weapon;
       return weapon && weapon.trim() !== "" && weapon !== "{}";
     });
     if (hasMainWeapon) {
       return true;
     }
     // Verifica subskills
     for (const el of iconItems) {
       const subsRaw = el.getAttribute("data-subs");
       if (!subsRaw) continue;
       try {
         const subs = JSON.parse(subsRaw);
         if (
           Array.isArray(subs) &&
           subs.some(
             (s) =>
               s &&
               s.weapon &&
               typeof s.weapon === "object" &&
               Object.keys(s.weapon).length > 0
           )
         ) {
           return true;
         }
       } catch (e) { }
     }
     return false;
   }
   const hasWeaponSkillAvailable = checkHasAnyWeapon();
   let weaponToggleBtn = null;
   if (!assetManifest) {
     assetManifest = buildAssetManifest();
     // Pré-carrega apenas ícones e flags críticos
     if (assetManifest.icons && assetManifest.icons.size) {
       assetManifest.icons.forEach((url) => {
         if (!url || imagePreloadCache.has(url)) return;
         const img = new Image();
         img.decoding = "async";
         img.loading = "eager";
         img.referrerPolicy = "same-origin";
         img.src = url;
         imagePreloadCache.set(url, img);
       });
     }
     if (assetManifest.flags && assetManifest.flags.size) {
       assetManifest.flags.forEach((url) => {
         if (!url || imagePreloadCache.has(url)) return;
         const img = new Image();
         img.decoding = "async";
         img.loading = "eager";
         img.referrerPolicy = "same-origin";
         img.src = url;
         imagePreloadCache.set(url, img);
       });
     }
   }
   // Busca descBox e videoBox após a estrutura estar organizada
   const descBox = $("#skills") ? $(".desc-box", $("#skills")) : null;
   const videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
   const videosCache = new Map();
   const nestedVideoElByIcon = new WeakMap();
   const barStack = [];
   window.__barStack = barStack;
   let initialBarSnapshot = null;
   let totalVideos = 0,
     loadedVideos = 0,
     autoplay = false;
   window.__lastActiveSkillIcon = null;
   let userHasInteracted = false;
   let globalWeaponEnabled = false;
   try {
     if (localStorage.getItem("glaWeaponEnabled") === "1") {
       globalWeaponEnabled = true;
     }
   } catch (err) { }
   const weaponStateListeners = new Set();
   let showWeaponPopupFn = null;
   let popupShouldOpen = false;
   function attachWeaponPopupFn(fn) {
     if (typeof fn !== "function") return;
     showWeaponPopupFn = fn;
     if (popupShouldOpen) {
       popupShouldOpen = false;
       try {
         showWeaponPopupFn();
       } catch (err) { }
     }
   }
   attachWeaponPopupFn(window.__glaWeaponShowPopup);
   function requestWeaponPopupDisplay() {
     try {
       if (localStorage.getItem("glaWeaponPopupDismissed") === "1") return;
     } catch (err) { }
     if (typeof showWeaponPopupFn === "function") {
       showWeaponPopupFn();
       return;
     }
     popupShouldOpen = true;
   }
   function onWeaponStateChange(fn) {
     if (typeof fn !== "function") return;
     weaponStateListeners.add(fn);
   }
   function syncWeaponButtonState(enabled) {
     if (!weaponToggleBtn || !weaponToggleBtn.isConnected) return;
     // Usa .weapon-active em vez de .active para não conflitar com skills
     weaponToggleBtn.classList.toggle("weapon-active", !!enabled);
     weaponToggleBtn.classList.remove("active"); // Garante que .active nunca seja aplicado
     weaponToggleBtn.setAttribute("aria-pressed", enabled ? "true" : "false");
     weaponToggleBtn.setAttribute(
       "aria-label",
       enabled ? "Desativar Arma Especial" : "Ativar Arma Especial"
     );
   }
   function syncWeaponRailState(enabled) {
     if (skillsTopRail) {
       skillsTopRail.classList.toggle("weapon-mode-on", !!enabled);
     }
   }
   function notifyWeaponStateListeners(enabled) {
     weaponStateListeners.forEach((listener) => {
       try {
         listener(enabled);
       } catch (err) { }
     });
   }
   let pendingWeaponState = null;
   window.addEventListener("weapon:ready", (ev) => {
     if (ev && ev.detail && ev.detail.showPopup) {
       attachWeaponPopupFn(ev.detail.showPopup);
     }
     if (pendingWeaponState === null) return;
     if (typeof window.__applyWeaponState === "function") {
       const target = pendingWeaponState;
       pendingWeaponState = null;
       window.__applyWeaponState(target);
     }
   });
   window.__setGlobalWeaponEnabled = (enabled) => {
     globalWeaponEnabled = enabled;
     notifyWeaponStateListeners(enabled);
   };
   function requestWeaponState(targetState) {
     if (typeof window.__applyWeaponState === "function") {
       pendingWeaponState = null;
       window.__applyWeaponState(targetState);
       return;
     }
     pendingWeaponState = targetState;
   }
   onWeaponStateChange(syncWeaponButtonState);
   function reapplyWeaponClassesToBar() {
     // SISTEMA UNIFICADO: Aplica em skills E subskills (sempre, não só quando weapon está ativo)
     // Obtém iconsBar dinamicamente (pode não estar pronto ainda quando há flags)
     const currentIconsBar = getIconsBar();
     if (!currentIconsBar) {
       return;
     }
     // Busca em skills principais (dentro de iconsBar)
     currentIconsBar
       .querySelectorAll(".skill-icon[data-weapon]")
       .forEach((el) => {
         const weapon = el.dataset.weapon;
         if (weapon && weapon.trim() !== "" && weapon !== "{}") {
           try {
             const weaponObj = JSON.parse(weapon);
             if (
               weaponObj &&
               typeof weaponObj === "object" &&
               Object.keys(weaponObj).length > 0
             ) {
               if (!el.classList.contains("has-weapon-available")) {
                 el.classList.add("has-weapon-available");
               }
               if (!el.querySelector(".weapon-indicator")) {
                 const ind = document.createElement("div");
                 ind.className = "weapon-indicator";
                 el.appendChild(ind);
               }
             }
           } catch (e) {
             // Se não for JSON válido, não adiciona a classe
           }
         }
       });
     // Busca em subskills (dentro de subskills-rail)
     document
       .querySelectorAll(".subskills-rail .subicon[data-weapon]")
       .forEach((el) => {
         const weapon = el.dataset.weapon;
         if (weapon && weapon.trim() !== "" && weapon !== "{}") {
           try {
             const weaponObj = JSON.parse(weapon);
             if (
               weaponObj &&
               typeof weaponObj === "object" &&
               Object.keys(weaponObj).length > 0
             ) {
               if (!el.classList.contains("has-weapon-available")) {
                 el.classList.add("has-weapon-available");
               }
             }
           } catch (e) {
             // Se não for JSON válido, não adiciona a classe
           }
         }
       });
   }
   // Aplica classes de weapon imediatamente ao carregar
   reapplyWeaponClassesToBar();
   // REMOVIDO: setupWeaponBarToggle - O toggle de weapon agora é criado apenas pelo C.WeaponToggle.html no header
   // Não cria mais botão na barra de skills - apenas aplica classes visuais
   onWeaponStateChange(syncWeaponRailState);
   // Reaplica classes quando o estado do weapon muda (para garantir que funcione mesmo quando toggle é ativado fora da barra)
   onWeaponStateChange(() => {
     // Usa setTimeout para garantir que o DOM foi atualizado
     setTimeout(() => {
       reapplyWeaponClassesToBar();
     }, 50);
   });
   syncWeaponRailState(globalWeaponEnabled);
   (function injectWeaponStyles() {
     if (document.getElementById("weapon-toggle-styles")) return;
     const style = document.createElement("style");
     style.id = "weapon-toggle-styles";
     style.textContent = `
       /* ========== ESTILOS DE WEAPON - NOVO SISTEMA ========== */
       
       /* Animação simples para borda */
       @keyframes weapon-border-flow {
           0% { background-position: 0% 0%; }
           100% { background-position: 200% 0%; }
       }
       /* Animação sutil para brilho */
       @keyframes weapon-glow-breathe {
           0%, 100% { opacity: 0.7; }
           50% { opacity: 1; }
       }
       /* Animação do gradiente (sem girar a borda) */
       @property --effect-spin {
           syntax: "<angle>";
           inherits: false;
           initial-value: 0deg;
       }
       @keyframes effect-border-spin {
           from { --effect-spin: 0deg; }
           to { --effect-spin: 360deg; }
       }
       /* ===== ÍCONE COM ARMA - Só mostra efeitos quando weapon-mode-on está ativo ===== */
       /* Quando NÃO está em weapon-mode-on, os ícones com arma devem ser normais (mesma borda padrão) */
       .character-box .top-rail.skills .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
           /* Remove borda sólida base que aparece na transição do toggle */
           border-color: transparent !important;
           box-shadow: none !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
           /* Remove transform scale quando toggle está desativado */
           transform: none !important;
           transition: transform 0.15s ease !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle)::after {
           /* Reseta COMPLETAMENTE para borda padrão quando toggle está desativado */
           /* Remove background, padding e mask INSTANTANEAMENTE (sem transição) */
           background: none !important;
           background-size: unset !important;
           padding: 0 !important;
           -webkit-mask: none !important;
           mask: none !important;
           mask-composite: unset !important;
           -webkit-mask-composite: unset !important;
           animation: none !important;
           /* Apenas box-shadow tem transição suave */
           box-shadow: inset 0 0 0 var(--icon-ring-w) var(--icon-idle) !important;
           /* SEM transição no background/padding/mask para evitar "flash" durante a mudança */
           transition: box-shadow 0.15s ease !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle)::before {
           /* Remove efeitos de glow quando toggle está desativado - com transição suave */
           opacity: 0 !important;
           transition: opacity 0.15s ease !important;
       }
       /* Quando ativo mas toggle desativado, usa borda padrão de ativo */
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active {
           /* Mantém o zoom padrão mesmo quando toggle está desativado */
           transform: scale(1.10) translateZ(0) !important;
           transition: transform 0.15s ease !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::after {
           /* Reseta COMPLETAMENTE para borda padrão de ativo */
           /* Remove background, padding e mask INSTANTANEAMENTE (sem transição) */
           background: none !important;
           background-size: unset !important;
           padding: 0 !important;
           -webkit-mask: none !important;
           mask: none !important;
           mask-composite: unset !important;
           -webkit-mask-composite: unset !important;
           animation: none !important;
           /* Apenas box-shadow tem transição suave */
           box-shadow: inset 0 0 0 var(--icon-ring-w) var(--icon-active) !important;
           /* SEM transição no background/padding/mask para evitar "flash" durante a mudança */
           transition: box-shadow 0.15s ease !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before {
           /* Sem glow amarelo no modo normal para skills com weapon */
           opacity: 0 !important;
           box-shadow: none !important;
           transition: opacity 0.15s ease !important;
       }
       /* ===== MODO WEAPON ON - INATIVO ===== */
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::after {
           pointer-events: none !important;
           box-shadow: none !important;
           background: linear-gradient(90deg, 
               #FF3333 0%, 
               #FF0000 50%, 
               #FF3333 100%) !important;
           background-size: 200% 100% !important;
           animation: weapon-border-flow 3s linear infinite !important;
           -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
           mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
           -webkit-mask-composite: xor !important;
           mask-composite: exclude !important;
           padding: 2px !important;
           /* SEM transição para permitir remoção instantânea quando toggle é desativado */
           transition: none !important;
       }
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::before {
           pointer-events: none !important;
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           animation: weapon-glow-breathe 2s ease-in-out infinite !important;
           box-shadow: 
               0 0 8px 0 rgba(255, 0, 0, 0.4),
               0 0 12px 0 rgba(255, 51, 51, 0.3),
               0 0 16px 0 rgba(255, 0, 0, 0.2) !important;
           opacity: 1 !important;
       }
       /* ===== MODO WEAPON ON - ATIVO ===== */
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active {
           transform: scale(1.10) !important;
           z-index: 5 !important;
       }
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::after {
           pointer-events: none !important;
           box-shadow: none !important;
           background: linear-gradient(90deg, 
               #FFEB3B 0%, 
               #FFD700 50%, 
               #FFEB3B 100%) !important;
           background-size: 200% 100% !important;
           animation: weapon-border-flow 2s linear infinite !important;
           -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
           mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
           -webkit-mask-composite: xor !important;
           mask-composite: exclude !important;
           padding: 2px !important;
           /* SEM transição para permitir remoção instantânea quando toggle é desativado */
           transition: none !important;
       }
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before {
           pointer-events: none !important;
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           animation: weapon-glow-breathe 1.5s ease-in-out infinite !important;
           box-shadow: 
               0 0 10px 0 rgba(255, 215, 0, 0.5),
               0 0 16px 0 rgba(255, 235, 59, 0.4),
               0 0 22px 0 rgba(255, 215, 0, 0.3) !important;
           opacity: 1 !important;
       }
       .character-box .top-rail.skills .icon-bar {
           position: relative;
       }
       .character-box .top-rail.skills .icon-bar .skill-icon {
           z-index: 2;
       }
       .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active) {
           border-color: transparent !important;
           outline: 2px solid rgba(210, 60, 60, 0.95) !important;
           outline-offset: -2px;
           animation: effect-child-outline 1.6s ease-in-out infinite !important;
       }
       .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
           box-shadow:
               inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
               0 0 6px rgba(255, 60, 60, 0.45),
               0 0 10px rgba(120, 20, 20, 0.6) !important;
           animation: effect-child-ring 1.6s ease-in-out infinite !important;
           opacity: 1 !important;
       }
       .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           opacity: 1 !important;
           box-shadow:
               0 0 8px rgba(255, 60, 60, 0.35),
               0 0 14px rgba(120, 20, 20, 0.45);
           animation: effect-child-glow 1.6s ease-in-out infinite !important;
       }
       .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
           box-shadow:
               inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
               0 0 6px rgba(255, 60, 60, 0.45),
               0 0 10px rgba(120, 20, 20, 0.6) !important;
           animation: effect-child-ring 1.6s ease-in-out infinite !important;
           opacity: 1 !important;
       }
       .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           opacity: 1 !important;
           box-shadow:
               0 0 8px rgba(255, 60, 60, 0.35),
               0 0 14px rgba(120, 20, 20, 0.45);
           animation: effect-child-glow 1.6s ease-in-out infinite !important;
       }
       @keyframes effect-child-ring {
           0% {
               box-shadow:
                   inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                   0 0 6px rgba(255, 60, 60, 0.45),
                   0 0 10px rgba(120, 20, 20, 0.6);
           }
           50% {
               box-shadow:
                   inset 0 0 0 var(--icon-ring-w) rgba(120, 20, 20, 0.95),
                   0 0 9px rgba(255, 70, 70, 0.65),
                   0 0 14px rgba(20, 0, 0, 0.8);
           }
           100% {
               box-shadow:
                   inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                   0 0 6px rgba(255, 60, 60, 0.45),
                   0 0 10px rgba(120, 20, 20, 0.6);
           }
       }
       @keyframes effect-child-outline {
           0% {
               outline-color: rgba(210, 60, 60, 0.95);
           }
           50% {
               outline-color: rgba(120, 20, 20, 0.95);
           }
           100% {
               outline-color: rgba(210, 60, 60, 0.95);
           }
       }
       @keyframes effect-child-glow {
           0% {
               box-shadow:
                   0 0 8px rgba(255, 60, 60, 0.35),
                   0 0 14px rgba(120, 20, 20, 0.45);
           }
           50% {
               box-shadow:
                   0 0 12px rgba(255, 70, 70, 0.55),
                   0 0 18px rgba(20, 0, 0, 0.75);
           }
           100% {
               box-shadow:
                   0 0 8px rgba(255, 60, 60, 0.35),
                   0 0 14px rgba(120, 20, 20, 0.45);
           }
       }
       .character-box .top-rail.skills .effect-lines-layer {
           position: absolute;
           left: 0;
           top: 0;
           width: 100%;
           height: 100%;
           pointer-events: none;
           z-index: 0;
           overflow: visible;
       }
       .character-box .top-rail.skills .effect-lines-layer .effect-line {
           fill: none;
           stroke-linecap: round;
           stroke-linejoin: round;
       }
       .character-box .top-rail.skills .effect-lines-layer .effect-line-glow {
           stroke: rgba(120, 20, 20, 0.7);
           stroke-width: 5;
           filter: drop-shadow(0 0 6px rgba(255, 60, 60, 0.45));
           animation: none;
       }
       .character-box .top-rail.skills .effect-lines-layer .effect-line-core {
           stroke: rgba(210, 60, 60, 0.95);
           stroke-width: 2.2;
           animation: none;
       }
       .character-box .top-rail.skills .effect-lines-layer .effect-line.effect-line-returning {
           animation: effect-line-return var(--effect-return-duration, 1.1s) ease forwards;
           animation-delay: var(--effect-return-delay, 0s);
       }
       @keyframes effect-line-pulse {
           0% {
               stroke: rgba(210, 60, 60, 0.95);
               stroke-width: 2.1;
           }
           50% {
               stroke: rgba(120, 20, 20, 0.95);
               stroke-width: 2.7;
           }
           100% {
               stroke: rgba(220, 70, 70, 0.95);
               stroke-width: 2.1;
           }
       }
       @keyframes effect-line-glow {
           0% {
               stroke: rgba(140, 25, 25, 0.65);
               filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
           }
           50% {
               stroke: rgba(20, 0, 0, 0.85);
               filter: drop-shadow(0 0 9px rgba(255, 70, 70, 0.65));
           }
           100% {
               stroke: rgba(140, 25, 25, 0.65);
               filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
           }
       }
       @keyframes effect-line-return {
           0% {
               stroke-dasharray: var(--effect-line-length, 0) var(--effect-line-length, 0);
               stroke-dashoffset: 0;
               opacity: 1;
           }
           100% {
               stroke-dasharray: 0 var(--effect-line-length, 0);
               stroke-dashoffset: calc(var(--effect-line-length, 0) * -1);
               opacity: 0;
           }
       }
       @media (prefers-reduced-motion: reduce) {
           .character-box .top-rail.skills .effect-lines-layer .effect-line {
               animation: none;
           }
       }
       .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle).active::before {
           pointer-events: none !important;
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
           box-shadow: 
               0 0 10px 0 rgba(220, 220, 220, 0.5),
               0 0 16px 0 rgba(190, 190, 190, 0.4),
               0 0 22px 0 rgba(220, 220, 220, 0.3) !important;
           opacity: 1 !important;
       }
       /* ========== ESTILOS DE SWAP DE PERSONAGENS (Sistema Genérico) ========== */
       /* Skills desabilitadas quando o personagem ativo não pode usá-las */
       .character-box .top-rail.skills .icon-bar .skill-icon.disabled-skill {
           opacity: 0.3 !important;
           filter: grayscale(100%) !important;
           cursor: not-allowed !important;
           pointer-events: none !important;
           transition: opacity 0.2s ease, filter 0.2s ease !important;
       }
           `;
     document.head.appendChild(style);
   })();
   function applyWeaponBadge(el, weaponData, equipped) {
     // Apenas gerencia a classe weapon-equipped (badge visual removido)
     if (equipped && weaponData) {
       el.classList.add("weapon-equipped");
     } else {
       el.classList.remove("weapon-equipped");
     }
   }
   function getWeaponKey(el) {
     return (
       (el.dataset.index || "") +
       ":" +
       (el.dataset.nome || el.dataset.name || "")
     );
   }
   function isWeaponModeOn() {
     try {
       return localStorage.getItem("glaWeaponEnabled") === "1";
     } catch (e) {
       return false;
     }
   }
   function getWeaponDataForIcon(iconEl) {
     if (!iconEl || !iconEl.dataset.weapon) return null;
     // Usa cache de parsing JSON
     return getCachedJSON(iconEl, "weapon");
   }
   const effectState = {
     skills: new Set(),
     videos: new Map(),
     expiresAt: 0,
     sourceIcon: null,
     timer: null,
   };
   let effectLinesLayer = null;
   let effectLinesRAF = null;
   let effectLinesCleanupTimer = null;
   let effectLinesScrollWrap = null;
   let effectLinesBound = false;
   let effectLinesLastState = null;
   function getSkillNameFromIcon(iconEl) {
     return (iconEl?.dataset?.nome || iconEl?.dataset?.name || "").trim();
   }
   function getEffectVideoKey(iconEl, videoValue) {
     if (!iconEl) return "";
     const baseIndex = iconEl.dataset.index || "";
     const subName =
       iconEl.dataset.subName ||
       iconEl.dataset.nome ||
       iconEl.dataset.name ||
       "";
     const namePart = baseIndex || subName || "";
     const videoPart = String(videoValue || "").trim();
     if (!namePart || !videoPart) return "";
     return `effect:${namePart}:${videoPart}`;
   }
   function normalizeEffectData(raw) {
     if (!raw || typeof raw !== "object") return null;
     const rawSkills = raw.skills;
     const timeValue = Number(raw.time ?? raw.duration ?? 0);
     const timeMs = Number.isFinite(timeValue) ? timeValue * 1000 : 0;
     if (!timeMs || timeMs <= 0) return null;
     const skills = Array.isArray(rawSkills)
       ? rawSkills.map((s) => String(s || "").trim()).filter(Boolean)
       : [];
     if (!skills.length) return null;
     const videos = new Map();
     if (raw.videos && typeof raw.videos === "object") {
       Object.keys(raw.videos).forEach((k) => {
         const key = String(k || "").trim();
         const val = String(raw.videos[k] || "").trim();
         if (key && val) videos.set(key, val);
       });
     }
     return { skills, timeMs, videos };
   }
   function handleEffectLinesScroll() {
     scheduleEffectLinesUpdate();
   }
   function bindEffectLinesEvents() {
     if (!effectLinesBound) {
       window.addEventListener("resize", scheduleEffectLinesUpdate);
       window.addEventListener("scroll", scheduleEffectLinesUpdate, {
         passive: true,
       });
       effectLinesBound = true;
     }
     const rail = document.querySelector(".top-rail.skills");
     const scrollWrap = rail ? rail.querySelector(".icon-scroll-x") : null;
     if (scrollWrap && scrollWrap !== effectLinesScrollWrap) {
       if (effectLinesScrollWrap) {
         effectLinesScrollWrap.removeEventListener(
           "scroll",
           handleEffectLinesScroll
         );
       }
       scrollWrap.addEventListener("scroll", handleEffectLinesScroll, {
         passive: true,
       });
       effectLinesScrollWrap = scrollWrap;
     }
   }
   function scheduleEffectLinesUpdate() {
     if (effectLinesRAF) return;
     effectLinesRAF = requestAnimationFrame(() => {
       effectLinesRAF = null;
       updateEffectLines();
     });
   }
   function getEffectLinesLayer() {
     const rail = document.querySelector(".top-rail.skills");
     if (!rail) return null;
     const iconBar = rail.querySelector(".icon-bar");
     if (!iconBar) return null;
     if (effectLinesLayer && effectLinesLayer.isConnected) {
       return effectLinesLayer;
     }
     const existing = iconBar.querySelector(".effect-lines-layer");
     if (existing) {
       effectLinesLayer = existing;
       return effectLinesLayer;
     }
     const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
     svg.classList.add("effect-lines-layer");
     svg.setAttribute("aria-hidden", "true");
     iconBar.insertBefore(svg, iconBar.firstChild);
     effectLinesLayer = svg;
     return effectLinesLayer;
   }
   function updateEffectLines() {
     if (!effectState.sourceIcon || !effectState.sourceIcon.isConnected) {
       clearEffectLines();
       return;
     }
     if (effectState.expiresAt <= Date.now()) {
       clearEffectLines();
       return;
     }
     const layer = getEffectLinesLayer();
     if (!layer) return;
     const rail = layer.closest(".top-rail.skills");
     if (!rail) return;
     const iconBar = rail.querySelector(".icon-bar");
     if (!iconBar) return;
     bindEffectLinesEvents();
     const barRect = iconBar.getBoundingClientRect();
     const width = Math.max(1, Math.round(iconBar.scrollWidth || barRect.width));
     const height = Math.max(1, Math.round(iconBar.clientHeight || barRect.height));
     layer.style.left = "0px";
     layer.style.top = "0px";
     layer.setAttribute("width", String(width));
     layer.setAttribute("height", String(height));
     layer.setAttribute("viewBox", `0 0 ${width} ${height}`);
     layer.classList.remove("effect-lines-returning");
     const srcRect = effectState.sourceIcon.getBoundingClientRect();
     const startX =
       srcRect.left + srcRect.width / 2 - barRect.left + iconBar.scrollLeft;
     const startY = srcRect.top + srcRect.height / 2 - barRect.top;
     const allIcons = Array.from(
       document.querySelectorAll(".icon-bar .skill-icon[data-index]")
     ).filter((icon) => !icon.classList.contains("weapon-bar-toggle"));
     const targets = allIcons.filter((icon) => {
       if (icon === effectState.sourceIcon) return false;
       const name = getSkillNameFromIcon(icon);
       return name && effectState.skills.has(name);
     });
     if (!targets.length) {
       clearEffectLines();
       return;
     }
     const frag = document.createDocumentFragment();
     const baselinePadding = 10;
     const baselineExtra = 12;
     const baselineY = Math.max(
       startY,
       height - baselinePadding + baselineExtra
     );
     const targetPoints = targets.map((target) => {
       const tgtRect = target.getBoundingClientRect();
       const endX =
         tgtRect.left + tgtRect.width / 2 - barRect.left + iconBar.scrollLeft;
       const endY = tgtRect.top + tgtRect.height / 2 - barRect.top;
       return { x: endX, y: endY };
     });
     const xs = targetPoints.map((p) => p.x);
     const minX = Math.min(startX, ...xs);
     const maxX = Math.max(startX, ...xs);
     effectLinesLastState = {
       startX,
       startY,
       baselineY,
       targets: targetPoints.map((p) => ({
         x: p.x,
         y: p.y,
         dist: Math.hypot(p.x - startX, p.y - startY),
       })),
     };
     const dParts = [
       `M ${startX} ${startY}`,
       `L ${startX} ${baselineY}`,
       `L ${minX} ${baselineY}`,
       `L ${maxX} ${baselineY}`,
     ];
     targetPoints.forEach((p) => {
       dParts.push(`M ${p.x} ${baselineY} L ${p.x} ${p.y}`);
     });
     const d = dParts.join(" ");
     const glow = document.createElementNS(
       "http://www.w3.org/2000/svg",
       "path"
     );
     glow.setAttribute("d", d);
     glow.classList.add("effect-line", "effect-line-glow");
     const core = document.createElementNS(
       "http://www.w3.org/2000/svg",
       "path"
     );
     core.setAttribute("d", d);
     core.classList.add("effect-line", "effect-line-core");
     frag.appendChild(glow);
     frag.appendChild(core);
     requestAnimationFrame(() => {
       const length = Math.max(1, Math.round(core.getTotalLength()));
       [core, glow].forEach((path) => {
         path.style.setProperty("--effect-line-length", `${length}`);
         path.style.strokeDasharray = `${length}`;
         path.style.strokeDashoffset = "0";
       });
     });
     layer.replaceChildren(frag);
   }
   function animateEffectLinesReturn() {
     const layer =
       (effectLinesLayer && effectLinesLayer.isConnected
         ? effectLinesLayer
         : document.querySelector(".effect-lines-layer")) || null;
     if (!layer || !effectLinesLastState) {
       clearEffectLines();
       return;
     }
     const { startX, startY, baselineY, targets } = effectLinesLastState;
     const sortedTargets = [...targets].sort((a, b) => b.dist - a.dist);
     const returnDuration = 1.1;
     const returnStagger = 0.22;
     const frag = document.createDocumentFragment();
     sortedTargets.forEach((target, idx) => {
       const d = [
         `M ${startX} ${startY}`,
         `L ${startX} ${baselineY}`,
         `L ${target.x} ${baselineY}`,
         `L ${target.x} ${target.y}`,
       ].join(" ");
       const glow = document.createElementNS(
         "http://www.w3.org/2000/svg",
         "path"
       );
       glow.setAttribute("d", d);
       glow.classList.add(
         "effect-line",
         "effect-line-glow",
         "effect-line-returning"
       );
       glow.style.setProperty("--effect-return-duration", `${returnDuration}s`);
       glow.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);
       const core = document.createElementNS(
         "http://www.w3.org/2000/svg",
         "path"
       );
       core.setAttribute("d", d);
       core.classList.add(
         "effect-line",
         "effect-line-core",
         "effect-line-returning"
       );
       core.style.setProperty("--effect-return-duration", `${returnDuration}s`);
       core.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);
       frag.appendChild(glow);
       frag.appendChild(core);
     });
     layer.replaceChildren(frag);
     requestAnimationFrame(() => {
       const paths = Array.from(layer.querySelectorAll("path.effect-line"));
       paths.forEach((path) => {
         const length = Math.max(1, Math.round(path.getTotalLength()));
         path.style.setProperty("--effect-line-length", `${length}`);
         path.style.strokeDasharray = `${length} ${length}`;
         path.style.strokeDashoffset = "0";
       });
     });
     if (effectLinesCleanupTimer) {
       clearTimeout(effectLinesCleanupTimer);
     }
     effectLinesCleanupTimer = setTimeout(() => {
       clearEffectLines();
     }, (returnDuration + returnStagger * Math.max(0, targets.length - 1)) * 1000 + 120);
   }
   function clearEffectLines() {
     if (effectLinesCleanupTimer) {
       clearTimeout(effectLinesCleanupTimer);
       effectLinesCleanupTimer = null;
     }
     const layer =
       (effectLinesLayer && effectLinesLayer.isConnected
         ? effectLinesLayer
         : document.querySelector(".effect-lines-layer")) || null;
     if (layer) layer.remove();
     effectLinesLayer = null;
   }
   function applyEffectClasses() {
     const isActive = effectState.expiresAt > Date.now();
     const rail = document.querySelector(".top-rail.skills");
     if (rail) rail.classList.toggle("effect-mode-on", isActive);
     document
       .querySelectorAll(".icon-bar .skill-icon[data-index]")
       .forEach((icon) => {
         if (icon.classList.contains("weapon-bar-toggle")) return;
         const name = getSkillNameFromIcon(icon);
         const should =
           isActive && name && effectState.skills.has(name);
         icon.classList.toggle("effect-active", !!should);
         if (!should) {
           icon.style.removeProperty("--effect-strength");
         }
       });
     if (isActive) {
       bindEffectLinesEvents();
       scheduleEffectLinesUpdate();
     }
   }
   function clearEffectState() {
     const activeIcon = document.querySelector(
       ".icon-bar .skill-icon.active"
     );
     const activeName = getSkillNameFromIcon(activeIcon);
     const wasAffected =
       activeName && effectState.skills.has(activeName);
     animateEffectLinesReturn();
     if (effectState.timer) {
       clearTimeout(effectState.timer);
       effectState.timer = null;
     }
     effectState.skills.clear();
     effectState.videos.clear();
     effectState.expiresAt = 0;
     effectState.sourceIcon = null;
     applyEffectClasses();
     if (wasAffected && activeIcon) {
       activeIcon.dispatchEvent(new Event("click", { bubbles: true }));
     }
   }
   function activateEffectFromIcon(iconEl) {
     const effectRaw = getCachedJSON(iconEl, "effect");
     const normalized = normalizeEffectData(effectRaw);
     if (!normalized) return;
     effectState.skills = new Set(normalized.skills);
     effectState.videos = normalized.videos;
     effectState.expiresAt = Date.now() + normalized.timeMs;
     effectState.sourceIcon = iconEl;
     if (effectState.timer) clearTimeout(effectState.timer);
     effectState.timer = setTimeout(() => {
       clearEffectState();
     }, normalized.timeMs + 5);
     applyEffectClasses();
   }
   function getEffectVideoForIcon(iconEl) {
     if (!iconEl) return "";
     if (effectState.expiresAt <= Date.now()) return "";
     const name = getSkillNameFromIcon(iconEl);
     if (!name || !effectState.skills.has(name)) return "";
     if (effectState.videos.has(name)) return effectState.videos.get(name) || "";
     return "";
   }
   function getEffectiveSkillVideoFromIcon(iconEl) {
     const weaponOn = globalWeaponEnabled;
     const weaponData = getWeaponDataForIcon(iconEl);
     const baseVideoFile = (iconEl.dataset.videoFile || "").trim();
     const baseVideoURL = (iconEl.dataset.video || "").trim();
     // console.log('[Skills DEBUG]', {
     //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
     //     weaponOn,
     //     hasWeaponData: !!weaponData,
     //     weaponData: weaponData,
     //     baseVideoFile,
     //     baseVideoURL
     // });
     const effectVideo = getEffectVideoForIcon(iconEl);
     if (effectVideo && effectVideo.trim() !== "") {
       return effectVideo.trim();
     }
     if (weaponOn && weaponData) {
       // console.log('[Skills] checking weapon video', {
       //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
       //     hasVideo: !!(weaponData.video),
       //     videoValue: weaponData.video,
       //     videoTrimmed: weaponData.video ? weaponData.video.trim() : 
       // });
       if (weaponData.video && weaponData.video.trim() !== "") {
         // console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
         return weaponData.video.trim();
       }
     }
     // Sistema genérico de swap: verifica character_videos se houver personagem ativo
     if (activeCharacter !== null && iconEl.dataset.characterVideos) {
       try {
         const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
         const characterVideo = characterVideos[activeCharacter];
         if (characterVideo && characterVideo.trim() !== "") {
           return characterVideo.trim();
         }
       } catch (e) {
         console.warn("[Swap] Erro ao processar character_videos:", e);
       }
     }
     // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
     const result = baseVideoFile || baseVideoURL || "";
     // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
     return result;
   }
   function createVideoElement(videoURL, extraAttrs = {}) {
     // Se o vídeo já falhou antes, não cria novo elemento
     if (failedVideosCache.has(videoURL)) {
       return null;
     }
     const v = document.createElement("video");
     v.className = "skill-video";
     v.setAttribute("controls", "");
     v.setAttribute("preload", "auto"); // Mudado de 'metadata' para 'auto' para carregar tudo imediatamente
     v.setAttribute("playsinline", "");
     v.style.display = "none";
     v.style.width = "100%";
     v.style.height = "auto";
     v.style.aspectRatio = "16/9";
     v.style.objectFit = "cover";
     Object.keys(extraAttrs).forEach((k) => {
       v.dataset[k] = extraAttrs[k];
     });
     // Detectar formato do vídeo pela extensão
     const ext = (videoURL.split(".").pop() || "").toLowerCase().split("?")[0];
     const mimeTypes = {
       mp4: "video/mp4",
       m4v: "video/mp4",
       webm: "video/webm",
       ogv: "video/ogg",
       ogg: "video/ogg",
       mov: "video/quicktime",
     };
     const mimeType = mimeTypes[ext] || "video/mp4";
     const src = document.createElement("source");
     src.src = videoURL;
     src.type = mimeType;
     v.appendChild(src);
     // Fallback para Safari/iOS mais antigos
     v.setAttribute("webkit-playsinline", "");
     v.setAttribute("x-webkit-airplay", "allow");
     // Tratamento silencioso de erros - marca como falhado e não tenta mais
     let errorHandled = false;
     v.addEventListener(
       "error",
       (e) => {
         if (errorHandled) return;
         errorHandled = true;
         // Marca o vídeo como falhado para não tentar carregar novamente
         failedVideosCache.add(videoURL);
         // Remove o vídeo do DOM se estiver lá
         if (v.parentNode) {
           v.parentNode.removeChild(v);
         }
         // Avisa apenas uma vez sobre vídeos faltantes
         if (!missingVideosReported.has(videoURL)) {
           missingVideosReported.add(videoURL);
           // Extrai nome do arquivo da URL para o aviso
           const fileName =
             videoURL.split("/").pop().split("?")[0] || videoURL;
           console.info(
             `[Skills] Vídeo não encontrado na wiki: ${decodeURIComponent(
               fileName
             )}. Este aviso aparecerá apenas uma vez.`
           );
         }
       },
       { once: true }
     );
     return v;
   }
   // Função recursiva para carregar TODOS os vídeos de subskills (incluindo sub-subskills)
   function preloadSubskillVideosRecursively(
     subs,
     parentIdx,
     parentPath = ""
   ) {
     if (!videoBox || !Array.isArray(subs)) return 0;
     let createdCount = 0;
     subs.forEach((s) => {
       const subName = (s.name || s.n || "").trim();
       const currentPath = parentPath ? `${parentPath}:${subName}` : subName;
       // Vídeo normal da subskill
       if (s.video && s.video.trim() !== "") {
         const key = `sub:${parentIdx}:${currentPath}`;
         if (!subskillVideosCache.has(key)) {
           const videoURL = normalizeFileURL(s.video);
           if (
             videoURL &&
             videoURL.trim() !== "" &&
             !failedVideosCache.has(videoURL)
           ) {
             const v = createVideoElement(videoURL, {
               sub: "1",
               parentIndex: parentIdx,
               subName: currentPath,
               cacheKey: key,
             });
             if (v) {
               // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
               videoBox.appendChild(v);
               subskillVideosCache.set(key, v);
               createdCount++;
               // FORÇA carregamento imediatamente (apenas uma vez)
               v.load();
             }
           }
         }
       }
       // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
       if (
         s.weapon &&
         typeof s.weapon === "object" &&
         s.weapon.video &&
         s.weapon.video.trim() !== ""
       ) {
         const weaponKey = `sub:${parentIdx}:${currentPath}:weapon`;
         if (!subskillVideosCache.has(weaponKey)) {
           const weaponVideoURL = normalizeFileURL(s.weapon.video);
           if (
             weaponVideoURL &&
             weaponVideoURL.trim() !== "" &&
             !failedVideosCache.has(weaponVideoURL)
           ) {
             const v = createVideoElement(weaponVideoURL, {
               sub: "1",
               parentIndex: parentIdx,
               subName: currentPath,
               weapon: "1",
               cacheKey: weaponKey,
             });
             if (v) {
               // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
               videoBox.appendChild(v);
               subskillVideosCache.set(weaponKey, v);
               createdCount++;
               // FORÇA carregamento imediatamente (apenas uma vez)
               v.load();
             }
           }
         }
       }
       // RECURSÃO: processa sub-subskills (SEMPRE processa, mesmo se o vídeo já estiver no cache)
       if (Array.isArray(s.subs) && s.subs.length > 0) {
         createdCount += preloadSubskillVideosRecursively(
           s.subs,
           parentIdx,
           currentPath
         );
       }
     });
     return createdCount;
   }
   function precreateSubskillVideos() {
     if (!videoBox) return;
     let createdCount = 0;
     iconItems.forEach((parentIcon) => {
       const subs = getCachedJSON(parentIcon, "subs");
       if (!Array.isArray(subs)) return;
       const parentIdx = parentIcon.dataset.index || "";
       createdCount += preloadSubskillVideosRecursively(subs, parentIdx);
     });
   }
   // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
   function preloadAllVideosRecursively() {
     if (!videoBox || !iconItems.length) return;
     // 1. Carregar vídeos de skills principais
     iconItems.forEach((el) => {
       const idx = el.dataset.index || "";
       if (!idx) return;
       // Vídeo normal da skill principal
       // Prioriza data-video-file (nome do arquivo), depois data-video (pode ser URL completa)
       let src = (el.dataset.videoFile || "").trim();
       if (!src) {
         // Se não tem videoFile, tenta extrair do video (pode ser URL completa)
         const videoAttr = (el.dataset.video || "").trim();
         if (videoAttr) {
           // Se já é uma URL completa, usa direto; senão normaliza
           if (videoAttr.includes("/") || videoAttr.startsWith("http")) {
             src = videoAttr;
           } else {
             src = videoAttr;
           }
         }
       }
       if (src && !videosCache.has(idx)) {
         const videoURL = normalizeFileURL(src);
         if (videoURL && !failedVideosCache.has(videoURL)) {
           const v = createVideoElement(videoURL, {
             index: idx,
           });
           if (v) {
             // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
             totalVideos++;
             v.style.maxWidth = "100%";
             v.addEventListener(
               "canplaythrough",
               () => {
                 loadedVideos++;
                 if (!userHasInteracted && loadedVideos === 1) {
                   try {
                     v.pause();
                     v.currentTime = 0;
                   } catch (e) { }
                 }
                 if (loadedVideos === totalVideos) autoplay = true;
               },
               {
                 once: true,
               }
             );
             v.addEventListener(
               "error",
               () => {
                 loadedVideos++;
                 if (loadedVideos === totalVideos) autoplay = true;
               },
               {
                 once: true,
               }
             );
             videoBox.appendChild(v);
             videosCache.set(idx, v);
             nestedVideoElByIcon.set(el, v);
             // Força carregamento imediatamente (apenas uma vez)
             v.load();
           }
         }
       }
       // Vídeo de weapon (sempre carrega, independente do toggle)
       const weaponData = getCachedJSON(el, "weapon");
       if (
         weaponData &&
         weaponData.video &&
         weaponData.video.trim() !== ""
       ) {
         const weaponKey = `weapon:${idx}:${(
           el.dataset.nome ||
           el.dataset.name ||
           ""
         ).trim()}`;
         if (!videosCache.has(weaponKey)) {
           const weaponVideoURL = normalizeFileURL(weaponData.video);
           if (weaponVideoURL && !failedVideosCache.has(weaponVideoURL)) {
             const v = createVideoElement(weaponVideoURL, {
               index: idx,
               weapon: "1",
             });
             if (v) {
               // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
               totalVideos++;
               v.style.maxWidth = "100%";
               v.addEventListener(
                 "canplaythrough",
                 () => {
                   loadedVideos++;
                   if (loadedVideos === totalVideos) autoplay = true;
                 },
                 {
                   once: true,
                 }
               );
               v.addEventListener(
                 "error",
                 () => {
                   loadedVideos++;
                   if (loadedVideos === totalVideos) autoplay = true;
                 },
                 {
                   once: true,
                 }
               );
               videoBox.appendChild(v);
               videosCache.set(weaponKey, v);
               // Força carregamento imediatamente (apenas uma vez)
               v.load();
             }
           }
         }
       }
     });
     // 2. Carregar vídeos de subskills (recursivamente)
     precreateSubskillVideos();
   }
   // Função para pré-carregar TODOS os ícones recursivamente
   function preloadAllIconsRecursively() {
     const iconCache = new Set();
     // Função recursiva para processar subskills
     function processSubskillsRecursively(subs) {
       if (!Array.isArray(subs)) return;
       subs.forEach((s) => {
         const icon = (s.icon || "").trim();
         if (icon) {
           const iconURL = normalizeFileURL(icon);
           if (iconURL && !iconCache.has(iconURL)) {
             iconCache.add(iconURL);
             const img = new Image();
             img.decoding = "async";
             img.loading = "eager";
             img.referrerPolicy = "same-origin";
             img.src = iconURL;
             imagePreloadCache.set(iconURL, img);
           }
         }
         // Recursão para sub-subskills
         if (Array.isArray(s.subs)) {
           processSubskillsRecursively(s.subs);
         }
       });
     }
     // Carregar ícones de skills principais
     iconItems.forEach((el) => {
       const img = el.querySelector("img");
       if (img && img.src) {
         const iconURL = img.src;
         if (iconURL && !iconCache.has(iconURL)) {
           iconCache.add(iconURL);
           // Já está no DOM, mas força pré-carregamento
           const preloadImg = new Image();
           preloadImg.decoding = "async";
           preloadImg.loading = "eager";
           preloadImg.referrerPolicy = "same-origin";
           preloadImg.src = iconURL;
           imagePreloadCache.set(iconURL, preloadImg);
         }
       }
       // Carregar ícones de subskills recursivamente
       const subs = getCachedJSON(el, "subs");
       if (Array.isArray(subs)) {
         processSubskillsRecursively(subs);
       }
     });
   }
   // Função principal que carrega TUDO imediatamente
   function preloadAllAssets() {
     // Carregar TUDO imediatamente - sem lazy loading
     // 1. Carregar TODOS os vídeos (principais, subskills, weapon, sub-subskills)
     preloadAllVideosRecursively();
     // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
     preloadAllIconsRecursively();
   }
   // Executa pré-carregamento imediatamente
   preloadAllAssets();
   function wireTooltipsForNewIcons() {
     const tip = document.querySelector(".skill-tooltip");
     if (!tip) return;
     let lockUntil2 = 0;
     Array.from(document.querySelectorAll(".icon-bar .skill-icon")).forEach(
       (icon) => {
         if (
           icon.dataset.weaponToggle === "1" ||
           icon.classList.contains("weapon-bar-toggle")
         )
           return;
         if (icon.dataset.tipwired) return;
         icon.dataset.tipwired = "1";
         const label =
           icon.dataset.nome || icon.dataset.name || icon.title || "";
         if (label && !icon.hasAttribute("aria-label"))
           icon.setAttribute("aria-label", label);
         if (icon.hasAttribute("title")) icon.removeAttribute("title");
         const img = icon.querySelector("img");
         if (img) {
           const imgAlt = img.getAttribute("alt") || "";
           const imgTitle = img.getAttribute("title") || "";
           if (!label && (imgAlt || imgTitle))
             icon.setAttribute("aria-label", imgAlt || imgTitle);
           img.setAttribute("alt", "");
           if (img.hasAttribute("title")) img.removeAttribute("title");
         }
         const measureAndPos = (el) => {
           if (!el || tip.getAttribute("aria-hidden") === "true") return;
           tip.style.left = "0px";
           tip.style.top = "0px";
           const rect = el.getBoundingClientRect();
           const tr = tip.getBoundingClientRect();
           let left = Math.round(rect.left + (rect.width - tr.width) / 2);
           left = Math.max(
             8,
             Math.min(left, window.innerWidth - tr.width - 8)
           );
           const coarse =
             (window.matchMedia && matchMedia("(pointer: coarse)").matches) ||
             window.innerWidth <= 600;
           let top = coarse
             ? Math.round(rect.bottom + 10)
             : Math.round(rect.top - tr.height - 8);
           if (top < 8) top = Math.round(rect.bottom + 10);
           tip.style.left = left + "px";
           tip.style.top = top + "px";
         };
         const show = (el, text) => {
           tip.textContent = text || "";
           tip.setAttribute("aria-hidden", "false");
           measureAndPos(el);
           tip.style.opacity = "1";
         };
         const hide = () => {
           tip.setAttribute("aria-hidden", "true");
           tip.style.opacity = "0";
           tip.style.left = "-9999px";
           tip.style.top = "-9999px";
         };
         icon.addEventListener("mouseenter", () =>
           show(icon, icon.dataset.nome || icon.dataset.name || "")
         );
         icon.addEventListener("mousemove", () => {
           if (performance.now() >= lockUntil2) measureAndPos(icon);
         });
         icon.addEventListener("click", () => {
           lockUntil2 = performance.now() + 240;
           measureAndPos(icon);
         });
         icon.addEventListener("mouseleave", hide);
       }
     );
   }
   function showVideoForIcon(el) {
     userHasInteracted = true;
     if (!videoBox) return;
     const effectiveVideo = getEffectiveSkillVideoFromIcon(el);
     if (!effectiveVideo || effectiveVideo.trim() === "") {
       videoBox.style.display = "none";
       return;
     }
     const videoURL = normalizeFileURL(effectiveVideo);
     if (!videoURL || videoURL.trim() === "") {
       videoBox.style.display = "none";
       return;
     }
     Array.from(videoBox.querySelectorAll("video.skill-video")).forEach(
       (v) => {
         try {
           v.pause();
         } catch (e) { }
         v.style.display = "none";
       }
     );
     if (window.__subskills) window.__subskills.hideAll?.(videoBox);
     const hasIdx = !!el.dataset.index;
     const weaponOn = globalWeaponEnabled;
     const weaponData = getWeaponDataForIcon(el);
     const isWeaponVideo =
       weaponOn &&
       weaponData &&
       weaponData.video &&
       weaponData.video.trim() !== "";
     const effectVideo = getEffectVideoForIcon(el);
     const isEffectVideo = !!(effectVideo && effectVideo.trim() !== "");
     const effectKey = isEffectVideo
       ? getEffectVideoKey(el, effectiveVideo)
       : "";
     // console.log('[Skills] showVideoForIcon chamado', {
     //     skillName: el.dataset.nome || el.dataset.name,
     //     weaponOn,
     //     isWeaponVideo,
     //     effectiveVideo: getEffectiveSkillVideoFromIcon(el)
     // });
     const videoKey = isWeaponVideo
       ? `weapon:${getWeaponKey(el)}`
       : isEffectVideo
         ? effectKey
         : el.dataset.index || "";
     const isSubskill = !hasIdx || el.dataset.nested === "1";
     const parentIdx = el.dataset.parentIndex || "";
     const subName =
       el.dataset.subName || el.dataset.nome || el.dataset.name || "";
     if (
       hasIdx &&
       !isWeaponVideo &&
       !isEffectVideo &&
       videosCache.has(el.dataset.index)
     ) {
       const v = videosCache.get(el.dataset.index);
       videoBox.style.display = "block";
       v.style.display = "block";
       try {
         v.currentTime = 0;
       } catch (e) { }
       const suppress = document.body.dataset.suppressSkillPlay === "1";
       if (!suppress) {
         v.play().catch(() => { });
       } else {
         try {
           v.pause();
         } catch (e) { }
       }
       return;
     }
     // Para form_switch, permite criação dinâmica de vídeos (transições de forma)
     // Vídeos normais devem estar pré-carregados
     const isFormSwitch =
       el.dataset.formSwitch === "true" ||
       el.getAttribute("data-form-switch") === "true";
     let v = null;
     if (isWeaponVideo) {
       const weaponKeyFull = `weapon:${getWeaponKey(el)}`;
       v = videosCache.get(weaponKeyFull);
       if (!v && isSubskill && parentIdx && subName) {
         // Tenta buscar vídeo de weapon de subskill
         // O cache usa o formato: sub:${parentIdx}:${path}:weapon onde path NÃO inclui o nome da skill principal
         // O subName agora já está no formato correto (sem nome da skill principal)
         // Tenta buscar diretamente com o subName
         let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
         v = subskillVideosCache.get(subWeaponKey);
         // Se não encontrou, tenta buscar recursivamente
         if (!v) {
           const basePattern = `sub:${parentIdx}:`;
           const weaponSuffix = ":weapon";
           const searchName = subName.split(":").pop();
           for (const [key, video] of subskillVideosCache.entries()) {
             if (key.startsWith(basePattern) && key.endsWith(weaponSuffix)) {
               const pathInKey = key.substring(
                 basePattern.length,
                 key.length - weaponSuffix.length
               );
               // Tenta match exato, ou se termina com o nome da skill
               if (
                 pathInKey === subName ||
                 pathInKey.endsWith(`:${searchName}`) ||
                 pathInKey === searchName
               ) {
                 v = video;
                 break;
               }
             }
           }
         }
       }
       if (!v) {
         // Tenta buscar pelo padrão antigo também
         v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`);
       }
     } else if (isEffectVideo && effectKey) {
       v = videosCache.get(effectKey);
     } else {
       if (isSubskill && parentIdx && subName) {
         // Busca vídeo de subskill no cache correto
         // O cache usa o formato: sub:${parentIdx}:${path} onde path NÃO inclui o nome da skill principal
         // O subName agora já está no formato correto (sem nome da skill principal)
         // Tenta buscar diretamente com o subName
         let subKey = `sub:${parentIdx}:${subName}`;
         v = subskillVideosCache.get(subKey);
         // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
         // Isso é útil caso haja alguma diferença no formato do caminho
         if (!v) {
           const basePattern = `sub:${parentIdx}:`;
           const searchName = subName.split(":").pop(); // Último segmento do caminho
           // Tenta também buscar apenas pelo último segmento (útil para sub-subskills)
           const lastSegmentKey = `sub:${parentIdx}:${searchName}`;
           v = subskillVideosCache.get(lastSegmentKey);
           // Se ainda não encontrou, faz busca mais ampla
           if (!v) {
             for (const [key, video] of subskillVideosCache.entries()) {
               if (key.startsWith(basePattern)) {
                 const pathInKey = key.substring(basePattern.length);
                 // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
                 if (
                   pathInKey === subName ||
                   pathInKey.endsWith(`:${searchName}`) ||
                   pathInKey === searchName ||
                   (subName.includes(":") && pathInKey.includes(subName)) ||
                   (subName.includes(":") &&
                     pathInKey.endsWith(
                       subName.split(":").slice(-2).join(":")
                     ))
                 ) {
                   v = video;
                   break;
                 }
               }
             }
           }
         }
       } else {
         v = videosCache.get(el.dataset.index);
         if (!v) {
           v = nestedVideoElByIcon.get(el);
         }
       }
     }
     // Se vídeo não foi encontrado no cache, verifica se falhou antes
     if (!v) {
       // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
       if (failedVideosCache.has(videoURL)) {
         videoBox.style.display = "none";
         return;
       }
       // Para effect, cria vídeo dinamicamente (vídeo alternativo por tempo)
       if (isEffectVideo && videoURL) {
         const baseIndex = el.dataset.index || "";
         const effectCacheKey =
           effectKey || `effect:${effectiveVideo || videoURL}`;
         v = videosCache.get(effectCacheKey);
         if (!v) {
           v = createVideoElement(videoURL, {
             index: baseIndex,
             effect: "1",
           });
           if (v) {
             videoBox.appendChild(v);
             videosCache.set(effectCacheKey, v);
             v.load();
           } else {
             videoBox.style.display = "none";
             return;
           }
         }
       }
       // Para form_switch, cria vídeo dinamicamente (transições de forma são dinâmicas)
       // Usa uma chave única que inclui o nome do arquivo do vídeo para garantir que cada vídeo diferente seja cacheado separadamente
       else if (isFormSwitch && videoURL) {
         const baseIndex = el.dataset.index || "";
         const videoFileName = effectiveVideo || "";
         // Chave única: index + nome do arquivo do vídeo
         const videoKey = baseIndex + ":" + videoFileName;
         v = videosCache.get(videoKey);
         if (!v) {
           v = createVideoElement(videoURL, { index: baseIndex });
           if (v) {
             videoBox.appendChild(v);
             videosCache.set(videoKey, v);
             v.load();
           } else {
             videoBox.style.display = "none";
             return;
           }
         }
       } else {
         // Vídeos normais devem estar pré-carregados
         videoBox.style.display = "none";
         return;
       }
     }
     videoBox.style.display = "block";
     v.style.display = "block";
     try {
       v.currentTime = 0;
     } catch (e) { }
     const suppress = document.body.dataset.suppressSkillPlay === "1";
     if (!suppress) {
       v.play().catch(() => { });
     } else {
       try {
         v.pause();
       } catch (e) { }
     }
   }
   function activateSkill(el, options = {}) {
     const { openSubs = true } = options;
     const tip = document.querySelector(".skill-tooltip");
     if (tip) {
       tip.setAttribute("aria-hidden", "true");
       tip.style.opacity = "0";
       tip.style.left = "-9999px";
       tip.style.top = "-9999px";
     }
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const L = i18nMap[getLangKey()] ||
       i18nMap.pt || {
       cooldown: "Recarga",
       energy_gain: "Ganho de energia",
       energy_cost: "Custo de energia",
       power: "Poder",
       power_pvp: "Poder PvP",
       level: "Nível",
     };
     const name = el.dataset.nome || el.dataset.name || "";
     if (el.dataset.effect) {
       activateEffectFromIcon(el);
     }
     let weaponData = null;
     if (el.dataset.weapon) {
       try {
         const parsed = JSON.parse(el.dataset.weapon);
         // Só considera weapon válido se for um objeto não vazio
         if (
           parsed &&
           typeof parsed === "object" &&
           Object.keys(parsed).length > 0
         ) {
           weaponData = parsed;
         }
       } catch (e) {
         weaponData = null;
       }
     }
     const hasWeapon = !!weaponData;
     const weaponEquipped = hasWeapon && globalWeaponEnabled;
     // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
     let level = (el.dataset.level || "").trim();
     if (weaponEquipped && weaponData) {
       // Verifica se weapon tem level definido (pode ser número ou string)
       const weaponLevel = weaponData.level;
       if (
         weaponLevel !== undefined &&
         weaponLevel !== null &&
         weaponLevel !== ""
       ) {
         level = weaponLevel.toString().trim();
       }
     }
     const lang = getLangKey();
     const baseDescPack = {
       pt: el.dataset.descPt || "",
       en: el.dataset.descEn || "",
       es: el.dataset.descEs || "",
       pl: el.dataset.descPl || "",
     };
     const baseDesc =
       baseDescPack[lang] ||
       baseDescPack.pt ||
       baseDescPack.en ||
       baseDescPack.es ||
       baseDescPack.pl ||
       el.dataset.desc ||
       "";
     // Aceita tanto desc_i18n quanto desc para compatibilidade
     let weaponDescPack = {};
     if (weaponData) {
       if (weaponData.desc_i18n) {
         weaponDescPack = weaponData.desc_i18n;
       } else if (weaponData.desc) {
         weaponDescPack = weaponData.desc;
       } else {
         weaponDescPack = {
           pt: weaponData.descPt || "",
           en: weaponData.descEn || "",
           es: weaponData.descEs || "",
           pl: weaponData.descPl || "",
         };
       }
     }
     const weaponDesc =
       weaponDescPack[lang] ||
       weaponDescPack.pt ||
       weaponDescPack.en ||
       weaponDescPack.es ||
       weaponDescPack.pl ||
       "";
     const chosenDesc = weaponEquipped && weaponDesc ? weaponDesc : baseDesc;
     const descHtml = chosenDesc.replace(/(.*?)/g, "$1");
     let attrsHTML = "";
     if (weaponEquipped && weaponData) {
       // Faz merge: usa atributos da skill base e substitui apenas os que existem no weapon
       // Parse dos atributos da skill base (formato: "pve, pvp, energy, cooldown" ou "pve, pvp, energy, cooldown")
       const baseAttrsStr = el.dataset.atr || "";
       // Suporta tanto ", " quanto "," como separador
       const baseAttrs = baseAttrsStr.split(/\s*,\s*/).map((a) => a.trim());
       const basePve =
         baseAttrs[0] && baseAttrs[0] !== "-" ? baseAttrs[0] : "";
       const basePvp =
         baseAttrs[1] && baseAttrs[1] !== "-" ? baseAttrs[1] : "";
       const baseEnergy =
         baseAttrs[2] && baseAttrs[2] !== "-" ? baseAttrs[2] : "";
       const baseCd = baseAttrs[3] && baseAttrs[3] !== "-" ? baseAttrs[3] : "";
       // Valores do weapon (substituem os da skill base se existirem)
       const wPve =
         weaponData.powerpve !== undefined &&
           weaponData.powerpve !== null &&
           weaponData.powerpve !== ""
           ? weaponData.powerpve.toString().trim()
           : basePve;
       const wPvp =
         weaponData.powerpvp !== undefined &&
           weaponData.powerpvp !== null &&
           weaponData.powerpvp !== ""
           ? weaponData.powerpvp.toString().trim()
           : basePvp;
       const wEnergy =
         weaponData.energy !== undefined &&
           weaponData.energy !== null &&
           weaponData.energy !== ""
           ? weaponData.energy.toString().trim()
           : baseEnergy;
       const wCd =
         weaponData.cooldown !== undefined &&
           weaponData.cooldown !== null &&
           weaponData.cooldown !== ""
           ? weaponData.cooldown.toString().trim()
           : baseCd;
       // Monta string de atributos mesclados
       const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(",");
       attrsHTML = renderAttributes(mergedAttrs);
     } else {
       attrsHTML = el.dataset.atr
         ? renderAttributes(el.dataset.atr)
         : el.dataset.subattrs
           ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L)
           : "";
     }
     let flagsHTML = "";
     // Debug: verifica se é uma skill do Urouge (verifica pela URL da página ou pelo contexto)
     const isUrougePage =
       window.location.href &&
       window.location.href.toLowerCase().includes("urouge");
     if (el.dataset.flags) {
       try {
         const flags = JSON.parse(el.dataset.flags);
         if (flags && Array.isArray(flags) && flags.length > 0) {
           flagsHTML = renderFlagsRow(flags);
           // Debug para Urouge
           if (isUrougePage) {
             console.log("[Skills] Urouge - Flags processadas:", {
               skill: name || el.dataset.nome,
               flags: flags,
               htmlLength: flagsHTML ? flagsHTML.length : 0,
               hasHTML: !!flagsHTML,
             });
           }
         } else {
           if (isUrougePage) {
             console.warn("[Skills] Urouge - Flags inválidas:", {
               skill: name || el.dataset.nome,
               flags: flags,
               rawData: el.dataset.flags,
             });
           }
         }
       } catch (e) {
         console.warn(
           "[Skills] Erro ao processar flags:",
           e,
           "flags data:",
           el.dataset.flags,
           "skill:",
           name || el.dataset.nome
         );
         if (isUrougePage) {
           console.error(
             "[Skills] Urouge - Erro ao processar flags:",
             e,
             "element:",
             el
           );
         }
       }
     } else {
       // Debug: verifica se deveria ter flags mas não tem (apenas para Urouge)
       if (isUrougePage) {
         console.warn("[Skills] Urouge - Skill sem data-flags:", {
           skill: name || el.dataset.nome,
           element: el,
           allAttributes: Array.from(el.attributes).map(
             (attr) => attr.name + "=" + attr.value
           ),
         });
       }
     }
     if (descBox) {

descBox.innerHTML = `

${name}

${level  ? `

${L.level} ${level}

`

         : ""

}${attrsHTML}

${descHtml}

`;

     }
     if (hasWeapon) {
       applyWeaponBadge(el, weaponData, weaponEquipped);
     }
     if (videoBox) {
       const oldFlags = videoBox.querySelector(".skill-flags");
       if (oldFlags) oldFlags.remove();
       if (flagsHTML) {
         videoBox.insertAdjacentHTML("beforeend", flagsHTML);
         applyFlagTooltips(videoBox);
       }
     }
     const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
     currIcons.forEach((i) => i.classList.remove("active"));
     const subsRaw = el.dataset.subs || el.getAttribute("data-subs");
     const isFormSwitch =
       el.dataset.formSwitch === "true" ||
       el.getAttribute("data-form-switch") === "true";
     const isSwap =
       el.dataset.swap === "true" || el.getAttribute("data-swap") === "true";
     // Se for skill de swap, troca personagem (não marca como ativo, não processa como back)
     if (isSwap && !isFormSwitch) {
       handleSwapCharacter(el);
       // Não marca como ativo (similar ao form_switch)
       return;
     }
     // Não marca como ativo se for form_switch (Change Form)
     if (!isFormSwitch) {
       el.classList.add("active");
       if (!autoplay && loadedVideos > 0) autoplay = true;
       window.__lastActiveSkillIcon = el;
       // Lógica de vídeo: usa função centralizada que já considera weapon
       showVideoForIcon(el);
     }
     const isBack =
       el.dataset.back === "true" ||
       el.getAttribute("data-back") === "true" ||
       el.dataset.back === "yes" ||
       el.getAttribute("data-back") === "yes" ||
       el.dataset.back === "1" ||
       el.getAttribute("data-back") === "1";
     // Se for form_switch, alterna forma (não processa como back)
     if (isFormSwitch) {
       // Atualiza o data-video-file do ícone com o vídeo correto da transição baseado na forma atual
       try {
         const formsJSON = skillsRoot.dataset.forms || "{}";
         if (formsJSON && formsJSON !== "{}") {
           const tempFormsData = JSON.parse(formsJSON);
           const formNames = Object.keys(tempFormsData);
           // Busca vídeo de transição na skill Change Form
           const formVideosRaw =
             el.dataset.formVideos || el.getAttribute("data-form-videos");
           if (formVideosRaw) {
             const videos = JSON.parse(formVideosRaw);
             // Se currentForm é null, detecta qual forma está atualmente visível no DOM
             let formForVideo = currentForm;
             if (!formForVideo) {
               // Lê formsData se ainda não foi carregado
               if (Object.keys(formsData).length === 0) {
                 try {
                   formsData = JSON.parse(formsJSON);
                 } catch (e) {
                   formsData = {};
                 }
               }
               formForVideo = detectCurrentForm();
               // Se ainda não conseguiu detectar, usa a primeira como fallback
               if (!formForVideo && formNames.length > 0) {
                 formForVideo = formNames[0];
               }
             }
             const transitionVideo = formForVideo
               ? videos[formForVideo] || ""
               : "";
             if (transitionVideo && transitionVideo.trim() !== "") {
               // Atualiza temporariamente o data-video-file (o sistema usa isso)
               el.dataset.videoFile = transitionVideo;
             }
           }
         }
       } catch (e) {
         console.error("[Forms] Erro ao processar vídeo de transição:", e);
       }
       // Usa o sistema normal de vídeo (mesmo que skills normais)
       showVideoForIcon(el);
       switchForm();
       return;
     }
     if (isBack && !isFormSwitch && barStack.length) {
       const prev = barStack.pop();
       // Restaura currentForm se estava salvo no snapshot
       if (prev.currentForm !== undefined) {
         currentForm = prev.currentForm;
       }
       renderBarFromItems(prev);
       const btn = document.querySelector(".skills-back-wrapper");
       if (btn) btn.style.display = barStack.length ? "block" : "none";
       return;
     }
     if (openSubs && subsRaw && subsRaw.trim() !== "") {
       if (barStack.length && barStack[barStack.length - 1].parentIcon === el)
         return;
       try {
         const subs = JSON.parse(subsRaw);
         pushSubBarFrom(subs, el);
       } catch { }
     }
   }
   function wireClicksForCurrentBar() {
     const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
     currIcons.forEach((el) => {
       if (
         el.dataset.weaponToggle === "1" ||
         el.classList.contains("weapon-bar-toggle")
       )
         return;
       if (el.dataset.wired) return;
       el.dataset.wired = "1";
       const label = el.dataset.nome || el.dataset.name || "";
       el.setAttribute("aria-label", label);
       if (el.hasAttribute("title")) el.removeAttribute("title");
       const img = el.querySelector("img");
       if (img) {
         img.setAttribute("alt", "");
         if (img.hasAttribute("title")) img.removeAttribute("title");
       }
       el.addEventListener("click", () => {
         activateSkill(el, {
           openSubs: true,
         });
       });
     });
     wireTooltipsForNewIcons();
     applyEffectClasses();
   }
   function animateIconsBarEntrance() {
     Array.from(iconsBar.children).forEach((c, i) => {
       c.style.opacity = "0";
       c.style.transform = "translateY(6px)";
       requestAnimationFrame(() => {
         setTimeout(() => {
           c.style.transition = "opacity .18s ease, transform .18s ease";
           c.style.opacity = "1";
           c.style.transform = "translateY(0)";
         }, i * 24);
       });
     });
   }
   function snapshotCurrentBarItemsFromDOM() {
     const items = Array.from(iconsBar.querySelectorAll(".skill-icon"))
       .filter((el) => el.dataset.weaponToggle !== "1")
       .map((el) => {
         const img = el.querySelector("img");
         const iconURL = img ? img.src : "";
         const subsRaw = el.dataset.subs || el.getAttribute("data-subs") || "";
         let subs = null;
         try {
           subs = subsRaw ? JSON.parse(subsRaw) : null;
         } catch {
           subs = null;
         }
         const subattrsRaw = el.dataset.subattrs || "";
         let flags = null;
         if (el.dataset.flags) {
           try {
             flags = JSON.parse(el.dataset.flags);
           } catch (e) { }
         }
         let weapon = null;
         if (el.dataset.weapon) {
           try {
             weapon = JSON.parse(el.dataset.weapon);
           } catch (e) { }
         }
         let effect = null;
         if (el.dataset.effect) {
           try {
             effect = JSON.parse(el.dataset.effect);
           } catch (e) { }
         }
         // Preserva data-form-videos para poder restaurar depois
         const formVideos =
           el.dataset.formVideos || el.getAttribute("data-form-videos") || "";
         return {
           name: el.dataset.nome || el.dataset.name || "",
           index: el.dataset.index || "",
           level: el.dataset.level || "",
           desc: el.dataset.desc || "",
           descPt: el.dataset.descPt || "",
           descEn: el.dataset.descEn || "",
           descEs: el.dataset.descEs || "",
           descPl: el.dataset.descPl || "",
           attrs: el.dataset.atr || el.dataset.attrs || "",
           video: el.dataset.video || "",
           iconURL,
           subs,
           subattrsStr: subattrsRaw,
           flags: flags,
           weapon: weapon,
           effect: effect,
           nested: el.dataset.nested || "",
           subName: el.dataset.subName || "",
           parentIndex: el.dataset.parentIndex || "",
           formSwitch:
             el.dataset.formSwitch ||
             el.getAttribute("data-form-switch") ||
             "",
           formVideos: formVideos,
         };
       });
     // Retorna objeto com items e currentForm para poder restaurar depois
     return { items, currentForm: currentForm };
   }
   function ensureBackButton() {
     const rail = iconsBar.closest(".top-rail.skills");
     if (!rail) return null;
     let wrap = rail.parentElement;
     if (
       !wrap ||
       !wrap.classList ||
       !wrap.classList.contains("skills-rail-wrap")
     ) {
       const parentNode = rail.parentNode;
       const newWrap = document.createElement("div");
       newWrap.className = "skills-rail-wrap";
       parentNode.insertBefore(newWrap, rail);
       newWrap.appendChild(rail);
       wrap = newWrap;
     }
     let backWrap = wrap.querySelector(".skills-back-wrapper");
     if (!backWrap) {
       backWrap = document.createElement("div");
       backWrap.className = "skills-back-wrapper";
       const btnInner = document.createElement("button");
       btnInner.className = "skills-back";
       btnInner.type = "button";
       btnInner.setAttribute("aria-label", "Voltar");
       btnInner.innerHTML =
         '<svg class="back-chevron" width="100%" height="100%" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" preserveAspectRatio="xMidYMid meet"><path d="M10 2L4 16L10 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 2L14 16L20 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M30 2L24 16L30 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
       backWrap.appendChild(btnInner);
       wrap.insertBefore(backWrap, rail);
       btnInner.addEventListener("click", () => {
         if (!barStack.length) return;
         const prev = barStack.pop();
         renderBarFromItems(prev);
         backWrap.style.display = barStack.length ? "block" : "none";
         wrap.classList.toggle("has-sub-bar", barStack.length > 0);
         if (!barStack.length) btnInner.classList.remove("peek");
       });
     }
     backWrap.style.display = barStack.length ? "block" : "none";
     wrap.classList.toggle("has-sub-bar", barStack.length > 0);
     const btnInner = backWrap.querySelector(".skills-back");
     // Calcula a posição do botão baseado na posição real do top-rail
     function updateBackButtonPosition() {
       if (!rail || !backWrap) return;
       const railRect = rail.getBoundingClientRect();
       const wrapRect = wrap.getBoundingClientRect();
       // Calcula a posição relativa do rail dentro do wrap
       const railLeft = railRect.left - wrapRect.left;
       // Posiciona o wrapper na borda esquerda do rail
       // O botão interno usa translateX(-97%) para ficar atrás da barra
       backWrap.style.left = railLeft + "px";
     }
     // Atualiza a posição quando necessário
     if (backWrap.style.display !== "none") {
       // Usa requestAnimationFrame para garantir que o DOM foi atualizado
       requestAnimationFrame(() => {
         updateBackButtonPosition();
       });
       // Recalcula em resize e scroll
       if (!backWrap.dataset.positionWired) {
         backWrap.dataset.positionWired = "1";
         const updateOnResize = () => {
           if (backWrap.style.display !== "none") {
             updateBackButtonPosition();
           }
         };
         window.addEventListener("resize", updateOnResize);
         const observer = new ResizeObserver(() => {
           if (backWrap.style.display !== "none") {
             updateBackButtonPosition();
           }
         });
         if (rail) observer.observe(rail);
         observer.observe(wrap);
       }
     }
     return btnInner;
   }
   function renderBarFromItems(itemsOrSnapshot) {
     // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
     let items, savedCurrentForm;
     if (Array.isArray(itemsOrSnapshot)) {
       items = itemsOrSnapshot;
       savedCurrentForm = null;
     } else if (itemsOrSnapshot && itemsOrSnapshot.items) {
       items = itemsOrSnapshot.items;
       savedCurrentForm = itemsOrSnapshot.currentForm;
     } else {
       items = [];
       savedCurrentForm = null;
     }
     // Restaura currentForm se estava salvo
     if (savedCurrentForm !== undefined && savedCurrentForm !== null) {
       currentForm = savedCurrentForm;
     }
     const tip = document.querySelector(".skill-tooltip");
     if (tip) {
       tip.setAttribute("aria-hidden", "true");
       tip.style.opacity = "0";
       tip.style.left = "-9999px";
       tip.style.top = "-9999px";
     }
     iconsBar.innerHTML = "";
     items.forEach((it, idx) => {
       const node = document.createElement("div");
       node.className = "skill-icon";
       node.dataset.nome = it.name || "";
       if (it.index) node.dataset.index = it.index;
       if (it.level) node.dataset.level = it.level;
       if (it.desc) node.dataset.desc = it.desc;
       if (it.descPt) node.dataset.descPt = it.descPt;
       if (it.descEn) node.dataset.descEn = it.descEn;
       if (it.descEs) node.dataset.descEs = it.descEs;
       if (it.descPl) node.dataset.descPl = it.descPl;
       if (it.attrs) node.dataset.atr = it.attrs;
       if (it.video) node.dataset.video = it.video;
       if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
       if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr;
       if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
       if (
         it.weapon &&
         typeof it.weapon === "object" &&
         Object.keys(it.weapon).length > 0
       ) {
         node.dataset.weapon = JSON.stringify(it.weapon);
       }
       if (it.effect && typeof it.effect === "object") {
         try {
           node.dataset.effect = JSON.stringify(it.effect);
         } catch (e) { }
       }
       // Restaura informações de subskill importantes para busca de vídeos
       if (it.nested) node.dataset.nested = it.nested;
       if (it.subName) node.dataset.subName = it.subName;
       if (it.parentIndex) node.dataset.parentIndex = it.parentIndex;
       if (!it.index && !it.nested) node.dataset.nested = "1";
       // Restaura formSwitch (Change Form)
       if (it.formSwitch) {
         node.dataset.formSwitch = it.formSwitch;
         node.setAttribute("data-form-switch", it.formSwitch);
       }
       // Restaura formVideos (vídeos de transição de forma)
       if (it.formVideos) {
         node.dataset.formVideos = it.formVideos;
         node.setAttribute("data-form-videos", it.formVideos);
       }
       const img = document.createElement("img");
       img.alt = "";
       img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : "");
       img.decoding = "async";
       img.loading = "lazy";
       img.width = 50;
       img.height = 50;
       node.appendChild(img);
       iconsBar.appendChild(node);
     });
     animateIconsBarEntrance();
     wireClicksForCurrentBar();
     // Remove qualquer toggle antigo que possa aparecer
     const oldToggle = iconsBar.querySelector(".weapon-bar-toggle");
     if (oldToggle) oldToggle.remove();
     // Reaplica classes de weapon após renderizar barra
     reapplyWeaponClassesToBar();
     const b = ensureBackButton();
     if (b) b.classList.add("peek");
     // Atualiza a posição do botão back após a barra ser renderizada
     requestAnimationFrame(() => {
       const backWrap = document.querySelector(".skills-back-wrapper");
       if (backWrap && backWrap.style.display !== "none") {
         const rail = iconsBar.closest(".top-rail.skills");
         const wrap = rail ? rail.parentElement : null;
         if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
           const railRect = rail.getBoundingClientRect();
           const wrapRect = wrap.getBoundingClientRect();
           const railLeft = railRect.left - wrapRect.left;
           // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
           backWrap.style.left = railLeft + "px";
         }
       }
     });
   }
   function pushSubBarFrom(subs, parentIconEl) {
     const tip = document.querySelector(".skill-tooltip");
     if (tip) {
       tip.setAttribute("aria-hidden", "true");
       tip.style.opacity = "0";
       tip.style.left = "-9999px";
       tip.style.top = "-9999px";
     }
     const parentNameSnapshot = parentIconEl
       ? parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
       : "";
     // Para subskills, usa parentIndex; para skills principais, usa index
     const parentIndexSnapshot = parentIconEl
       ? parentIconEl.dataset.nested === "1"
         ? parentIconEl.dataset.parentIndex || ""
         : parentIconEl.dataset.index || ""
       : "";
     const snapshot = snapshotCurrentBarItemsFromDOM();
     barStack.push({
       items: snapshot.items,
       currentForm: snapshot.currentForm,
       parentIcon: parentIconEl,
       parentName: parentNameSnapshot,
       parentIndex: parentIndexSnapshot,
     });
     ensureBackButton();
     const langKey = getLangKey();
     let cacheKey = null;
     if (parentIconEl) {
       cacheKey = parentIconEl.dataset.subCacheKey || null;
       if (!cacheKey) {
         if (parentIconEl.dataset.index) {
           cacheKey = `idx:${parentIconEl.dataset.index}`;
         } else {
           const slug = slugify(
             parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
           );
           if (slug) cacheKey = `slug:${slug}`;
         }
         if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey;
       }
     }
     if (cacheKey) {
       const cached = subBarTemplateCache.get(cacheKey);
       if (cached && cached.lang === langKey) {
         iconsBar.innerHTML = "";
         const clone = cached.template.cloneNode(true);
         iconsBar.appendChild(clone);
         animateIconsBarEntrance();
         wireClicksForCurrentBar();
         // Remove qualquer toggle antigo que possa aparecer
         const oldToggle2 = iconsBar.querySelector(".weapon-bar-toggle");
         if (oldToggle2) oldToggle2.remove();
         // Reaplica classes de weapon após renderizar do cache
         reapplyWeaponClassesToBar();
         const cachedBtn = ensureBackButton();
         if (cachedBtn) cachedBtn.classList.add("peek");
         return;
       }
     }
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const L = i18nMap[getLangKey()] ||
       i18nMap.pt || {
       cooldown: "Recarga",
       energy_gain: "Ganho de energia",
       energy_cost: "Custo de energia",
       power: "Poder",
       power_pvp: "Poder PvP",
       level: "Nível",
     };
     const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
     const items = (hydratedSubs || [])
       .filter((s) => {
         // Filtra só se não tem nada útil
         const hasName = (s.name || s.n || "").trim() !== "";
         const hasIcon = (s.icon || "").trim() !== "";
         const hasRef = (s.refS || s.refM || "").toString().trim() !== "";
         return hasName || hasIcon || hasRef;
       })
       .map((s) => {
         const name = (s.name || s.n || "").trim();
         const desc = chooseDescFrom(s).replace(/(.*?)/g, "$1");
         const attrsHTML = renderSubAttributesFromObj(s, L);
         return {
           name,
           level: (s.level || "").toString().trim(),
           desc,
           descPt: s.descPt || (s.desc_i18n && s.desc_i18n.pt) || "",
           descEn: s.descEn || (s.desc_i18n && s.desc_i18n.en) || "",
           descEs: s.descEs || (s.desc_i18n && s.desc_i18n.es) || "",
           descPl: s.descPl || (s.desc_i18n && s.desc_i18n.pl) || "",
           attrs: "",
           icon: s.icon || "",
           iconURL: s.icon ? filePathURL(s.icon) : "",
           video: s.video ? filePathURL(s.video) : "",
           subs: Array.isArray(s.subs) ? s.subs : null,
           subattrs: s,
           flags: Array.isArray(s.flags) ? s.flags : null,
           back:
             s.back === true ||
               s.back === "true" ||
               s.back === "yes" ||
               s.back === "1"
               ? "true"
               : null,
           weapon: s.weapon || null,
         };
       });
     const fragment = document.createDocumentFragment();
     items.forEach((it, iIdx) => {
       const node = document.createElement("div");
       node.className = "skill-icon";
       node.dataset.nested = "1";
       node.dataset.nome = it.name || "";
       node.dataset.parentIndex = parentIndexSnapshot;
       // Constrói o caminho completo para sub-subskills
       // IMPORTANTE: O cache NÃO inclui o nome da skill principal no caminho
       // Formato do cache: "SubskillName" ou "SubskillName:SubSubskillName" (sem o nome da skill principal)
       let fullPath = it.name || "";
       if (parentIconEl) {
         // Se o pai é uma subskill (tem nested), adiciona o nome do pai ao caminho
         if (parentIconEl.dataset.nested === "1") {
           // Se o pai tem subName, usa ele (já está no formato correto, sem nome da skill principal)
           if (
             parentIconEl.dataset.subName &&
             parentIconEl.dataset.subName.trim() !== ""
           ) {
             fullPath = `${parentIconEl.dataset.subName}:${it.name || ""}`;
           } else {
             // Se o pai não tem subName, é a primeira subskill, então usa apenas o nome do pai
             const parentName = (
               parentIconEl.dataset.nome ||
               parentIconEl.dataset.name ||
               ""
             ).trim();
             fullPath = `${parentName}:${it.name || ""}`;
           }
         }
         // Se o pai é uma skill principal (não é nested), o caminho é apenas o nome da subskill atual
         // (já está definido como it.name acima)
       }
       node.dataset.subName = fullPath;
       const subSlug = slugify(it.name || "");
       if (subSlug) node.dataset.slug = subSlug;
       if (it.level) node.dataset.level = it.level;
       if (it.desc) node.dataset.desc = it.desc;
       if (it.descPt) node.dataset.descPt = it.descPt;
       if (it.descEn) node.dataset.descEn = it.descEn;
       if (it.descEs) node.dataset.descEs = it.descEs;
       if (it.descPl) node.dataset.descPl = it.descPl;
       if (it.video) node.dataset.video = it.video;
       if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
       if (it.subattrs) node.dataset.subattrs = JSON.stringify(it.subattrs);
       if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
       if (it.back) node.dataset.back = it.back;
       if (
         it.weapon &&
         typeof it.weapon === "object" &&
         Object.keys(it.weapon).length > 0
       ) {
         try {
           node.dataset.weapon = JSON.stringify(it.weapon);
         } catch (e) {
           console.error(
             "[Skills] Erro ao serializar weapon de subskill",
             it.name,
             e
           );
         }
       }
       const img = document.createElement("img");
       img.alt = "";
       img.src = it.iconURL;
       img.decoding = "async";
       img.loading = "lazy";
       img.width = 50;
       img.height = 50;
       node.appendChild(img);
       fragment.appendChild(node);
     });
     const templateClone = fragment.cloneNode(true);
     iconsBar.innerHTML = "";
     iconsBar.appendChild(fragment);
     animateIconsBarEntrance();
     wireClicksForCurrentBar();
     // Remove qualquer toggle antigo que possa aparecer
     const oldToggle3 = iconsBar.querySelector(".weapon-bar-toggle");
     if (oldToggle3) oldToggle3.remove();
     // Reaplica classes de weapon após renderizar subskills
     reapplyWeaponClassesToBar();
     const b2 = ensureBackButton();
     if (b2) b2.classList.add("peek");
     // Atualiza a posição do botão back após subskills serem renderizadas
     requestAnimationFrame(() => {
       const backWrap = document.querySelector(".skills-back-wrapper");
       if (backWrap && backWrap.style.display !== "none") {
         const rail = iconsBar.closest(".top-rail.skills");
         const wrap = rail ? rail.parentElement : null;
         if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
           const railRect = rail.getBoundingClientRect();
           const wrapRect = wrap.getBoundingClientRect();
           const railLeft = railRect.left - wrapRect.left;
           // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
           backWrap.style.left = railLeft + "px";
         }
       }
     });
     if (cacheKey) {
       subBarTemplateCache.set(cacheKey, {
         template: templateClone,
         lang: langKey,
       });
     }
   }
   window.addEventListener("gla:langChanged", () => {
     subBarTemplateCache.clear();
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const lang = getLangKey();
     Array.from(iconsBar.querySelectorAll(".skill-icon")).forEach((icon) => {
       const pack = {
         pt: icon.dataset.descPt || "",
         en: icon.dataset.descEn || "",
         es: icon.dataset.descEs || "",
         pl: icon.dataset.descPl || "",
       };
       const chosen = (
         pack[lang] ||
         pack.pt ||
         pack.en ||
         pack.es ||
         pack.pl ||
         icon.dataset.desc ||
         ""
       ).trim();
       if (chosen) icon.dataset.desc = chosen;
     });
     barStack.forEach((frame) => {
       (frame.items || []).forEach((it) => {
         const pack = {
           pt: it.descPt,
           en: it.descEn,
           es: it.descEs,
           pl: it.descPl,
         };
         const chosen =
           pack[lang] ||
           pack.pt ||
           pack.en ||
           pack.es ||
           pack.pl ||
           it.desc ||
           "";
         it.desc = chosen;
       });
     });
     if (descBox) {
       applyFlagTooltips(descBox);
     }
     const activeIcon = window.__lastActiveSkillIcon;
     if (activeIcon && activeIcon.dataset.weapon) {
       activateSkill(activeIcon, {
         openSubs: false,
       });
     }
   });
   wireClicksForCurrentBar();
   // Inicializa o sistema de swap de personagens (genérico)
   // Aguarda um pouco para garantir que todos os ícones foram renderizados
   setTimeout(() => {
     initializeActiveCharacter();
   }, 100);
   const b0 = ensureBackButton();
   if (b0) {
     b0.classList.add("peek");
     b0.style.alignSelf = "stretch";
   }
   // Move inicialização de tooltip para requestIdleCallback (não crítico)
   if ("requestIdleCallback" in window) {
     requestIdleCallback(
       () => {
         (function initSkillTooltip() {
           if (document.querySelector(".skill-tooltip")) return;
           const tip = document.createElement("div");
           tip.className = "skill-tooltip";
           tip.setAttribute("role", "tooltip");
           tip.setAttribute("aria-hidden", "true");
           document.body.appendChild(tip);
           const lockUntilRef = {
             value: 0,
           };
           function measureAndPos(el) {
             if (!el || tip.getAttribute("aria-hidden") === "true") return;
             tip.style.left = "0px";
             tip.style.top = "0px";
             const rect = el.getBoundingClientRect();
             const tr = tip.getBoundingClientRect();
             let left = Math.round(rect.left + (rect.width - tr.width) / 2);
             left = Math.max(
               8,
               Math.min(left, window.innerWidth - tr.width - 8)
             );
             const coarse =
               (window.matchMedia &&
                 matchMedia("(pointer: coarse)").matches) ||
               window.innerWidth <= 600;
             let top = coarse
               ? Math.round(rect.bottom + 10)
               : Math.round(rect.top - tr.height - 8);
             if (top < 8) top = Math.round(rect.bottom + 10);
             tip.style.left = left + "px";
             tip.style.top = top + "px";
           }
           function show(el, text) {
             tip.textContent = text || "";
             tip.setAttribute("aria-hidden", "false");
             measureAndPos(el);
             tip.style.opacity = "1";
           }
           function hide() {
             tip.setAttribute("aria-hidden", "true");
             tip.style.opacity = "0";
             tip.style.left = "-9999px";
             tip.style.top = "-9999px";
           }
           window.__globalSkillTooltip = {
             show,
             hide,
             measureAndPos,
             lockUntil: lockUntilRef,
           };
           Array.from(
             document.querySelectorAll(".icon-bar .skill-icon")
           ).forEach((icon) => {
             if (
               icon.dataset.weaponToggle === "1" ||
               icon.classList.contains("weapon-bar-toggle")
             )
               return;
             if (icon.dataset.tipwired) return;
             icon.dataset.tipwired = "1";
             const label =
               icon.dataset.nome || icon.dataset.name || icon.title || "";
             if (label && !icon.hasAttribute("aria-label"))
               icon.setAttribute("aria-label", label);
             if (icon.hasAttribute("title")) icon.removeAttribute("title");
             const img = icon.querySelector("img");
             if (img) {
               const imgAlt = img.getAttribute("alt") || "";
               const imgTitle = img.getAttribute("title") || "";
               if (!label && (imgAlt || imgTitle))
                 icon.setAttribute("aria-label", imgAlt || imgTitle);
               img.setAttribute("alt", "");
               if (img.hasAttribute("title")) img.removeAttribute("title");
             }
             icon.addEventListener("mouseenter", () => show(icon, label));
             icon.addEventListener("mousemove", () => {
               if (performance.now() >= lockUntilRef.value)
                 measureAndPos(icon);
             });
             icon.addEventListener("click", () => {
               lockUntilRef.value = performance.now() + 240;
               measureAndPos(icon);
             });
             icon.addEventListener("mouseleave", hide);
           });
           Array.from(
             document.querySelectorAll(".subskills-rail .subicon")
           ).forEach((sub) => {
             if (sub.dataset.tipwired) return;
             sub.dataset.tipwired = "1";
             const label =
               sub.getAttribute("title") ||
               sub.getAttribute("aria-label") ||
               "";
             if (label && !sub.hasAttribute("aria-label"))
               sub.setAttribute("aria-label", label);
             if (sub.hasAttribute("title")) sub.removeAttribute("title");
             sub.addEventListener("mouseenter", () => show(sub, label));
             sub.addEventListener("mousemove", () => {
               if (performance.now() >= lockUntilRef.value) measureAndPos(sub);
             });
             sub.addEventListener("click", () => {
               lockUntilRef.value = performance.now() + 240;
               measureAndPos(sub);
             });
             sub.addEventListener("mouseleave", hide);
           });
           window.addEventListener(
             "scroll",
             () => {
               const visible = document.querySelector(
                 '.skill-tooltip[aria-hidden="false"]'
               );
               if (!visible) return;
               const target =
                 document.querySelector(".subskills-rail .subicon:hover") ||
                 document.querySelector(".subskills-rail .subicon.active") ||
                 document.querySelector(".icon-bar .skill-icon:hover") ||
                 document.querySelector(".icon-bar .skill-icon.active");
               measureAndPos(target);
             },
             true
           );
           window.addEventListener("resize", () => {
             const target =
               document.querySelector(".subskills-rail .subicon:hover") ||
               document.querySelector(".subskills-rail .subicon.active") ||
               document.querySelector(".icon-bar .skill-icon:hover") ||
               document.querySelector(".icon-bar .skill-icon.active");
             measureAndPos(target);
           });
         })();
       },
       { timeout: 2000 }
     );
   } else {
     // Fallback para navegadores sem requestIdleCallback
     setTimeout(() => {
       (function initSkillTooltip() {
         if (document.querySelector(".skill-tooltip")) return;
         const tip = document.createElement("div");
         tip.className = "skill-tooltip";
         tip.setAttribute("role", "tooltip");
         tip.setAttribute("aria-hidden", "true");
         document.body.appendChild(tip);
         window.__globalSkillTooltip = {
           show: (el, text) => {
             if (!el || !text) return;
             tip.textContent = text;
             tip.setAttribute("aria-hidden", "false");
             measureAndPos(el);
           },
           hide: () => {
             tip.setAttribute("aria-hidden", "true");
             tip.style.left = "-9999px";
             tip.style.top = "-9999px";
           },
           measureAndPos: (el) => {
             if (!el || tip.getAttribute("aria-hidden") === "true") return;
             const rect = el.getBoundingClientRect();
             const tipRect = tip.getBoundingClientRect();
             let left = rect.left + rect.width / 2 - tipRect.width / 2;
             let top = rect.top - tipRect.height - 8;
             if (left < 8) left = 8;
             if (left + tipRect.width > window.innerWidth - 8)
               left = window.innerWidth - tipRect.width - 8;
             if (top < 8) top = rect.bottom + 8;
             tip.style.left = left + "px";
             tip.style.top = top + "px";
           },
           lockUntil: { value: 0 },
         };
         const { measureAndPos } = window.__globalSkillTooltip;
         window.addEventListener(
           "scroll",
           () => {
             const visible = document.querySelector(
               '.skill-tooltip[aria-hidden="false"]'
             );
             if (!visible) return;
             const target =
               document.querySelector(".subskills-rail .subicon:hover") ||
               document.querySelector(".subskills-rail .subicon.active") ||
               document.querySelector(".icon-bar .skill-icon:hover") ||
               document.querySelector(".icon-bar .skill-icon.active");
             measureAndPos(target);
           },
           true
         );
         window.addEventListener("resize", () => {
           const target =
             document.querySelector(".subskills-rail .subicon:hover") ||
             document.querySelector(".subskills-rail .subicon.active") ||
             document.querySelector(".icon-bar .skill-icon:hover") ||
             document.querySelector(".icon-bar .skill-icon.active");
           measureAndPos(target);
         });
       })();
     }, 100);
   }
   (function initTabs() {
     const tabs = Array.from(document.querySelectorAll(".tab-btn"));
     if (!tabs.length) return;
     const contents = Array.from(document.querySelectorAll(".tab-content"));
     const characterBox = document.querySelector(".character-box");
     let wrapper = characterBox.querySelector(".tabs-height-wrapper");
     if (!wrapper) {
       wrapper = document.createElement("div");
       wrapper.className = "tabs-height-wrapper";
       contents.forEach((c) => {
         wrapper.appendChild(c);
       });
       const tabsElement = characterBox.querySelector(".character-tabs");
       if (tabsElement && tabsElement.nextSibling) {
         characterBox.insertBefore(wrapper, tabsElement.nextSibling);
       } else {
         characterBox.appendChild(wrapper);
       }
     }
     async function smoothHeightTransition(fromTab, toTab) {
       if (!wrapper) return Promise.resolve();
       const scrollY = window.scrollY;
       const currentHeight = wrapper.getBoundingClientRect().height;
       await new Promise((resolve) => {
         const videoContainers = toTab.querySelectorAll(".video-container");
         const contentCard = toTab.querySelector(".content-card");
         if (videoContainers.length === 0) {
           requestAnimationFrame(() => {
             requestAnimationFrame(() => {
               requestAnimationFrame(() => resolve());
             });
           });
           return;
         }
         let lastHeight = 0;
         let stableCount = 0;
         const checksNeeded = 3;
         let totalChecks = 0;
         const maxChecks = 15;
         function checkStability() {
           totalChecks++;
           const currentTabHeight = toTab.scrollHeight;
           if (Math.abs(currentTabHeight - lastHeight) < 5) {
             stableCount++;
           } else {
             stableCount = 0;
           }
           lastHeight = currentTabHeight;
           if (stableCount >= checksNeeded || totalChecks >= maxChecks) {
             resolve();
           } else {
             setTimeout(checkStability, 50);
           }
         }
         setTimeout(checkStability, 50);
       });
       const nextHeight = toTab.getBoundingClientRect().height;
       const finalHeight = Math.max(nextHeight, 100);
       if (Math.abs(finalHeight - currentHeight) < 30) {
         wrapper.style.height = "";
         return Promise.resolve();
       }
       wrapper.style.overflow = "hidden";
       wrapper.style.height = currentHeight + "px";
       wrapper.offsetHeight;
       wrapper.style.transition = "height 0.3s cubic-bezier(0.4, 0, 0.2, 1)";
       requestAnimationFrame(() => {
         wrapper.style.height = finalHeight + "px";
       });
       return new Promise((resolve) => {
         setTimeout(() => {
           wrapper.style.height = "";
           wrapper.style.transition = "";
           wrapper.style.overflow = "";
           resolve();
         }, 320);
       });
     }
     tabs.forEach((btn) => {
       if (btn.dataset.wiredTab) return;
       btn.dataset.wiredTab = "1";
       btn.addEventListener("click", () => {
         const target = btn.getAttribute("data-tab");
         const currentActive = contents.find((c) =>
           c.classList.contains("active")
         );
         const nextActive = contents.find((c) => c.id === target);
         if (currentActive === nextActive) return;
         document.body.classList.add("transitioning-tabs");
         if (currentActive) {
           currentActive.style.opacity = "0";
           currentActive.style.transform = "translateY(-8px)";
         }
         setTimeout(async () => {
           contents.forEach((c) => {
             if (c !== nextActive) {
               c.style.display = "none";
               c.classList.remove("active");
             }
           });
           tabs.forEach((b) => b.classList.toggle("active", b === btn));
           if (nextActive) {
             nextActive.classList.add("active");
             nextActive.style.display = "block";
             nextActive.style.opacity = "0";
             nextActive.style.visibility = "hidden";
             nextActive.offsetHeight;
             try {
               if (target === "skills") {
                 const tabEl = document.getElementById(target);
                 if (tabEl) {
                   const activeIcon = tabEl.querySelector(
                     ".icon-bar .skill-icon.active"
                   );
                   const firstIcon = tabEl.querySelector(
                     ".icon-bar .skill-icon"
                   );
                   const toClick = activeIcon || firstIcon;
                   if (toClick) {
                     const had = document.body.dataset.suppressSkillPlay;
                     document.body.dataset.suppressSkillPlay = "1";
                     toClick.click();
                     if (had) document.body.dataset.suppressSkillPlay = had;
                   }
                 }
               }
             } catch (e) { }
           }
           if (currentActive && nextActive) {
             await smoothHeightTransition(currentActive, nextActive);
           }
           if (nextActive) {
             nextActive.style.visibility = "";
             nextActive.style.transform = "translateY(12px)";
             requestAnimationFrame(() => {
               nextActive.style.opacity = "1";
               nextActive.style.transform = "translateY(0)";
               setTimeout(() => {
                 nextActive.style.opacity = "";
                 nextActive.style.transform = "";
                 document.body.classList.remove("transitioning-tabs");
                 try {
                   delete document.body.dataset.suppressSkillPlay;
                 } catch { }
               }, 300);
             });
           }
         }, 120);
         setTimeout(() => {
           syncDescHeight();
           if (target === "skins") {
             videosCache.forEach((v) => {
               try {
                 v.pause();
               } catch (e) { }
               v.style.display = "none";
             });
             if (videoBox) {
               videoBox.querySelectorAll("video.skill-video").forEach((v) => {
                 try {
                   v.pause();
                 } catch (e) { }
                 v.style.display = "none";
               });
             }
             if (window.__subskills) window.__subskills.hideAll?.(videoBox);
             const placeholder = videoBox?.querySelector(".video-placeholder");
             if (videoBox && placeholder) {
               placeholder.style.display = "none";
               placeholder.classList.add("fade-out");
             }
           } else {
             const activeIcon = document.querySelector(
               ".icon-bar .skill-icon.active"
             );
             if (activeIcon) activeIcon.click();
           }
         }, 450);
       });
     });
   })();
   (function initSkinsArrows() {
     const carousel = $(".skins-carousel");
     const wrapper = $(".skins-carousel-wrapper");
     const left = $(".skins-arrow.left");
     const right = $(".skins-arrow.right");
     if (!carousel || !left || !right || !wrapper) return;
     if (wrapper.dataset.wired) return;
     wrapper.dataset.wired = "1";
     const scrollAmt = () => Math.round(carousel.clientWidth * 0.6);
     function setState() {
       const max = carousel.scrollWidth - carousel.clientWidth;
       const x = carousel.scrollLeft;
       const hasLeft = x > 5,
         hasRight = x < max - 5;
       left.style.display = hasLeft ? "inline-block" : "none";
       right.style.display = hasRight ? "inline-block" : "none";
       wrapper.classList.toggle("has-left", hasLeft);
       wrapper.classList.toggle("has-right", hasRight);
       carousel.style.justifyContent = !hasLeft && !hasRight ? "center" : "";
     }
     function go(dir) {
       const max = carousel.scrollWidth - carousel.clientWidth;
       const next =
         dir < 0
           ? Math.max(0, carousel.scrollLeft - scrollAmt())
           : Math.min(max, carousel.scrollLeft + scrollAmt());
       carousel.scrollTo({
         left: next,
         behavior: "smooth",
       });
     }
     left.addEventListener("click", () => go(-1));
     right.addEventListener("click", () => go(1));
     carousel.addEventListener("scroll", setState);
     new ResizeObserver(setState).observe(carousel);
     setState();
   })();
   function renderAttributes(str) {
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const langRaw = (
       document.documentElement.lang ||
       skillsRoot?.dataset.i18nDefault ||
       "pt"
     ).toLowerCase();
     const langKey = i18nMap[langRaw]
       ? langRaw
       : i18nMap[langRaw.split("-")[0]]
         ? langRaw.split("-")[0]
         : "pt";
     const L = i18nMap[langKey] ||
       i18nMap.pt || {
       cooldown: "Recarga",
       energy_gain: "Ganho de energia",
       energy_cost: "Custo de energia",
       power: "Poder",
       power_pvp: "Poder PvP",
       level: "Nível",
     };
     const vals = (str || "").split(",").map((v) => v.trim());
     // Processa valores, tratando strings vazias e "-" como NaN
     // IMPORTANTE: ordem fixa é [powerpve, powerpvp, energy, cooldown]
     const parseValue = (val) => {
       if (!val || val === "" || val === "-") return NaN;
       const parsed = parseFloat(val);
       return isNaN(parsed) ? NaN : parsed;
     };
     const pve = parseValue(vals[0]);
     const pvp = parseValue(vals[1]);
     const ene = parseValue(vals[2]);
     const cd = parseValue(vals[3]);
     // Debug: log se houver problema na ordem
     if (str && str.includes(",") && !isNaN(cd) && !isNaN(pvp) && cd === pvp) {
       console.warn(
         "[Skills] renderAttributes: possível problema na ordem dos atributos",
         { str, vals, pve, pvp, ene, cd }
       );
     }
     const rows = [];
     // Ordem de exibição: cooldown, energy, power, power_pvp
     if (!isNaN(cd)) rows.push([L.cooldown, cd]);
     if (!isNaN(ene) && ene !== 0) {
       const label = ene > 0 ? L.energy_gain : L.energy_cost;
       rows.push([label, Math.abs(ene)]);
     }
     if (!isNaN(pve)) rows.push([L.power, pve]);
     if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]);
     // Debug: log se houver valores suspeitos (desabilitado para performance)
     // if (str && str.includes(',')) {
     //     console.log('[Skills] renderAttributes processed', {
     //         str,
     //         vals: vals.slice(0, 4),
     //         parsed: { pve, pvp, ene, cd },
     //         rows: rows.map(r => r[0])
     //     });
     // }
     if (!rows.length) return "";
     const html = rows
       .map(
         ([rowLabel, rowValue]) =>

`

${rowLabel}${rowValue}

`

       )
       .join("");

return `

${html}

`;

   }
   function syncDescHeight() { }
   window.addEventListener("resize", syncDescHeight);
   if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox);
   iconItems.forEach((el) => {
     const wired = !!el.dataset._sync_wired;
     if (wired) return;
     el.dataset._sync_wired = "1";
     el.addEventListener("click", () => {
       Promise.resolve().then(syncDescHeight);
     });
   });
   if (iconsBar) {
     const scrollWrapper =
       iconsBar.closest(".icon-scroll-x") || iconsBar.parentElement;
     const scrollContainer =
       scrollWrapper && scrollWrapper.classList.contains("icon-scroll-x")
         ? scrollWrapper
         : iconsBar;
     addOnce(
       scrollContainer,
       "wheel",
       (e) => {
         if (e.deltaY) {
           e.preventDefault();
           scrollContainer.scrollLeft += e.deltaY;
         }
       },
       { passive: false }
     ); // passive: false necessário porque usamos preventDefault()
   }
   wireClicksForCurrentBar();
   if (iconItems.length) {
     const first = iconItems[0];
     if (first) {
       activateSkill(first, {
         openSubs: false,
       });
     }
   }
   // Aplica lazy loading em imagens fora do viewport inicial
   (function applyLazyLoading() {
     if ("IntersectionObserver" in window) {
       const imageObserver = new IntersectionObserver((entries, observer) => {
         entries.forEach((entry) => {
           if (entry.isIntersecting) {
             const img = entry.target;
             if (img.tagName === "IMG" && !img.hasAttribute("loading")) {
               img.loading = "lazy";
             }
             observer.unobserve(img);
           }
         });
       });
       // Observa imagens que não estão no viewport inicial
       document
         .querySelectorAll("img:not(.topbar-icon):not([loading])")
         .forEach((img) => {
           const rect = img.getBoundingClientRect();
           if (rect.bottom > window.innerHeight || rect.top < 0) {
             imageObserver.observe(img);
           }
         });
     }
   })();
   setTimeout(() => {
     Array.from(document.querySelectorAll(".skill-icon")).forEach((el) => { });
     videosCache.forEach((v, idx) => {
       const src = v.querySelector("source")
         ? v.querySelector("source").src
         : v.src;
       v.addEventListener("error", (ev) => { });
       v.addEventListener("loadedmetadata", () => { });
     });
   }, 600);
 })();

</script>