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

De Wiki Gla
Ir para navegação Ir para pesquisar
m (swap char atz)
m
 
(57 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
Linha 113: Linha 113:
                     try {
                     try {
                       vid.pause();
                       vid.pause();
                     } catch (e) {}
                     } catch (e) { }
                     vid.style.display = "none";
                     vid.style.display = "none";
                   });
                   });
Linha 121: Linha 121:
                   try {
                   try {
                     v.currentTime = 0;
                     v.currentTime = 0;
                     v.play().catch(() => {});
                     v.play().catch(() => { });
                   } catch (e) {}
                   } catch (e) { }
                 }
                 }
               }
               }
Linha 139: Linha 139:
     function detectAvailableCharacters() {
     function detectAvailableCharacters() {
       const iconBar = document.querySelector(".icon-bar");
       const iconBar = document.querySelector(".icon-bar");
      const skillsRoot = document.getElementById("skills");
       if (!iconBar) return [];
       if (!iconBar) return [];


Linha 151: Linha 152:
       });
       });


       return Array.from(characters);
       const arr = Array.from(characters);
      const explicitDefault = (
        skillsRoot?.dataset?.defaultCharacter || ""
      ).trim();
      if (explicitDefault && arr.includes(explicitDefault)) {
        const rest = arr.filter((c) => c !== explicitDefault).sort();
        return [explicitDefault, ...rest];
      }
      return arr.sort();
     }
     }


Linha 162: Linha 171:
         // Aplica o estado inicial (habilita/desabilita skills)
         // Aplica o estado inicial (habilita/desabilita skills)
         applyCharacterSwapState();
         applyCharacterSwapState();
        rebuildMainSkillsMetaFromBar();
       }
       }
     }
     }


     // Aplica o estado de swap nas skills (habilita/desabilita e atualiza vídeos)
     // Aplica o estado de swap nas skills (habilita/desabilita, vídeo, stats e descrição por personagem)
     function applyCharacterSwapState() {
     function applyCharacterSwapState() {
       const iconBar = document.querySelector(".icon-bar");
       const iconBar = document.querySelector(".icon-bar");
       if (!iconBar) return;
       if (!iconBar) return;


      // Atualiza todas as skills na barra
       Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
         (icon) => {
         (icon) => {
Linha 177: Linha 186:
             icon.dataset.characterVideos &&
             icon.dataset.characterVideos &&
             icon.dataset.characterVideos.trim() !== "";
             icon.dataset.characterVideos.trim() !== "";
           const baseVideoFile = icon.dataset.videoFile || "";
           const hasCharSkills =
          const baseVideoURL = icon.dataset.video || "";
            icon.dataset.characterSkills &&
 
             icon.dataset.characterSkills.trim() !== "";
          // 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) {
           if (onlyCharacter) {
            // Skill específica de um personagem
             if (onlyCharacter === activeCharacter) {
             if (onlyCharacter === activeCharacter) {
              // Skill do personagem ativo → habilitar
               icon.style.opacity = "1";
               icon.style.opacity = "1";
               icon.style.filter = "";
               icon.style.filter = "";
Linha 200: Linha 197:
               icon.classList.remove("disabled-skill");
               icon.classList.remove("disabled-skill");
             } else {
             } else {
              // Skill de outro personagem → desabilitar (escuras)
               icon.style.opacity = "0.3";
               icon.style.opacity = "0.3";
               icon.style.filter = "grayscale(100%)";
               icon.style.filter = "grayscale(100%)";
Linha 206: Linha 202:
               icon.classList.add("disabled-skill");
               icon.classList.add("disabled-skill");
             }
             }
           } else {
            return;
            // Skill compartilhada → sempre habilitada
           }
            icon.style.opacity = "1";
 
            icon.style.filter = "";
          icon.style.opacity = "1";
            icon.style.pointerEvents = "";
          icon.style.filter = "";
            icon.classList.remove("disabled-skill");
          icon.style.pointerEvents = "";
          icon.classList.remove("disabled-skill");
 
          if (!hasCharSkills && !hasCharacterVideos) {
            return;
          }
 
          if (!icon.dataset.swapBaselineStored) {
            icon.dataset.swapBaselineStored = "1";
            icon.dataset.swapBaselineAtr = icon.dataset.atr || "";
            icon.dataset.swapBaselineDescPt = icon.dataset.descPt || "";
            icon.dataset.swapBaselineDescEn = icon.dataset.descEn || "";
            icon.dataset.swapBaselineDescEs = icon.dataset.descEs || "";
            icon.dataset.swapBaselineDescPl = icon.dataset.descPl || "";
            icon.dataset.swapBaselineLevel = icon.dataset.level || "";
            icon.dataset.swapBaselineVideoFile = (
              icon.dataset.videoFile || ""
            ).trim();
            icon.dataset.swapBaselineVideo = (icon.dataset.video || "").trim();
            if (
              hasCharacterVideos &&
              !icon.dataset.originalVideoFile
            ) {
              icon.dataset.originalVideoFile =
                icon.dataset.swapBaselineVideoFile ||
                icon.dataset.swapBaselineVideo ||
                "";
            }
          }
 
          const oAtr = parseAttrString(
            icon.dataset.swapBaselineAtr || icon.dataset.atr || ""
          );
          let pve = oAtr.powerpve;
          let pvp = oAtr.powerpvp;
          let en = oAtr.energy;
          let cd = oAtr.cooldown;
          let dPt = icon.dataset.swapBaselineDescPt || "";
          let dEn = icon.dataset.swapBaselineDescEn || "";
          let dEs = icon.dataset.swapBaselineDescEs || "";
          let dPl = icon.dataset.swapBaselineDescPl || "";
          let lvl = icon.dataset.swapBaselineLevel || "";
          let vFile = icon.dataset.swapBaselineVideoFile || "";
          let vUrl = icon.dataset.swapBaselineVideo || "";


            // Atualiza vídeo se houver character_videos e personagem ativo
          if (hasCharSkills && activeCharacter) {
            if (hasCharacterVideos && activeCharacter) {
            try {
              try {
              const cs = JSON.parse(icon.dataset.characterSkills);
                const characterVideos = JSON.parse(
              const co = cs[activeCharacter] || {};
                  icon.dataset.characterVideos
              if (
                );
                co.powerpve !== undefined &&
                const characterVideo = characterVideos[activeCharacter];
                co.powerpve !== null &&
                 if (characterVideo && characterVideo.trim() !== "") {
                co.powerpve !== ""
                  icon.dataset.videoFile = characterVideo;
              )
                  icon.dataset.video =
                 pve = String(co.powerpve);
                    filePathURL(characterVideo) || characterVideo;
              if (
                 }
                co.powerpvp !== undefined &&
              } catch (e) {
                co.powerpvp !== null &&
                 console.error("[Swap] Erro ao processar character_videos:", e);
                co.powerpvp !== ""
              )
                pvp = String(co.powerpvp);
              if (
                co.energy !== undefined &&
                co.energy !== null &&
                co.energy !== ""
              )
                en = String(co.energy);
              if (
                co.cooldown !== undefined &&
                co.cooldown !== null &&
                co.cooldown !== ""
              )
                cd = String(co.cooldown);
              if (co.level !== undefined && co.level !== null && co.level !== "")
                lvl = String(co.level);
              const di = co.desc_i18n || co.desc;
              if (di) {
                 if (di.pt) dPt = di.pt;
                if (di.en) dEn = di.en;
                 if (di.es) dEs = di.es;
                if (di.pl) dPl = di.pl;
               }
               }
            } else if (hasCharacterVideos && activeCharacter === null) {
              if (co.video && String(co.video).trim()) {
              // Restaura vídeo original quando volta ao personagem padrão (null)
                const vf = String(co.video).trim();
              const originalVideo = icon.dataset.originalVideoFile || "";
                 vFile = vf;
              if (originalVideo) {
                 vUrl = filePathURL(vf) || vf;
                 icon.dataset.videoFile = originalVideo;
                 icon.dataset.video =
                  filePathURL(originalVideo) || originalVideo;
               }
               }
            } catch (e) {
              console.error("[Swap] Erro ao processar character_skills:", e);
             }
             }
           }
           }
        }
      );
    }


     // Troca entre personagens (genérico)
          if (hasCharacterVideos && activeCharacter) {
            try {
              const cv = JSON.parse(icon.dataset.characterVideos);
              const cvv = cv[activeCharacter];
              if (cvv && String(cvv).trim()) {
                const vf = String(cvv).trim();
                vFile = vf;
                vUrl = filePathURL(vf) || vf;
              }
            } catch (e) {
              console.error("[Swap] Erro ao processar character_videos:", e);
            }
          } else if (
            hasCharacterVideos &&
            activeCharacter === null &&
            icon.dataset.originalVideoFile
          ) {
            const originalVideo = icon.dataset.originalVideoFile || "";
            if (originalVideo) {
              vFile =
                extractFileNameFromURL(originalVideo) || originalVideo;
              vUrl = filePathURL(vFile) || originalVideo;
            }
          }
 
          icon.dataset.atr = makeAttrString(pve, pvp, en, cd);
          icon.dataset.descPt = dPt;
          icon.dataset.descEn = dEn;
          icon.dataset.descEs = dEs;
          icon.dataset.descPl = dPl;
          if (lvl && String(lvl).toUpperCase() !== "NIVEL") {
            icon.dataset.level = lvl;
          } else {
            delete icon.dataset.level;
          }
          icon.dataset.videoFile = vFile;
          icon.dataset.video = vUrl;
        }
      );
    }
 
     // Troca entre personagens (genérico)
     function handleSwapCharacter(swapIconEl) {
     function handleSwapCharacter(swapIconEl) {
       if (!swapIconEl) return;
       if (!swapIconEl) return;
Linha 251: Linha 349:
       const availableCharacters = detectAvailableCharacters();
       const availableCharacters = detectAvailableCharacters();
       if (availableCharacters.length === 0) {
       if (availableCharacters.length === 0) {
        // Se não há personagens específicos, não faz nada (não há swap)
         return;
         return;
       }
       }


      // Se activeCharacter é null, inicializa com o primeiro personagem disponível
       if (activeCharacter === null) {
       if (activeCharacter === null) {
         activeCharacter = availableCharacters[0];
         activeCharacter = availableCharacters[0];
       } else {
       } else {
        // Encontra o índice do personagem atual e avança para o próximo
         const currentIndex = availableCharacters.indexOf(activeCharacter);
         const currentIndex = availableCharacters.indexOf(activeCharacter);
         const nextIndex = (currentIndex + 1) % availableCharacters.length;
         const nextIndex = (currentIndex + 1) % availableCharacters.length;
Linha 265: Linha 360:
       }
       }


      // Aplica o novo estado
       applyCharacterSwapState();
       applyCharacterSwapState();
      rebuildMainSkillsMetaFromBar();
    }
    // 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));
        }
      });


       // Atualiza todas as skills na barra
       const visibleFormSkillNames = new Set();
       Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
         (icon) => {
         (icon) => {
           const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
           const name = (icon.dataset.nome || "").trim();
           const hasCharacterVideos =
           if (name && allFormSkillNames.has(name)) {
            icon.dataset.characterVideos &&
             visibleFormSkillNames.add(name);
            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
      // Compara com cada forma para ver qual corresponde
          if (onlyCharacter) {
      for (const [formName, formData] of Object.entries(formsData)) {
            // Skill específica de um personagem
        if (formData.order && Array.isArray(formData.order)) {
            if (onlyCharacter === activeCharacter) {
          const formSkillSet = new Set(formData.order);
              // Skill do personagem ativo → habilitar
           // Verifica se todas as skills desta forma estão visíveis
              icon.style.opacity = "1";
          let allMatch = true;
              icon.style.filter = "";
          for (const skillName of formData.order) {
              icon.style.pointerEvents = "";
            if (!visibleFormSkillNames.has(skillName)) {
              icon.classList.remove("disabled-skill");
               allMatch = false;
            } else {
               break;
              // 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;
              }
             }
             }
          }
          if (
            allMatch &&
            formData.order.length === visibleFormSkillNames.size
          ) {
            return formName;
           }
           }
         }
         }
       );
       }
 
      return null;
     }
     }


    // Detecta qual forma está atualmente visível no DOM
     function getNextFormName() {
     function detectCurrentForm() {
       const formNames = Object.keys(formsData);
       const iconBar = document.querySelector(".icon-bar");
      if (formNames.length === 0) return null;
       if (!iconBar || !formsData || Object.keys(formsData).length === 0)
 
         return null;
      let cur = currentForm;
       if (cur === null) {
        cur = detectCurrentForm();
      }
      if (!cur && formNames.length > 0) {
         cur = formNames[0];
      }


       // Coleta todas as skills de form que estão visíveis no DOM
       let orderedFormNames = [];
       const allFormSkillNames = new Set();
       if (cur === "Brain Point" && formNames.length === 3 &&
      Object.values(formsData).forEach((form) => {
        formNames.includes("Kung Fu Point") && formNames.includes("Heavy Point")) {
         if (form.order && Array.isArray(form.order)) {
         orderedFormNames = ["Brain Point", "Kung Fu Point", "Heavy Point"];
          form.order.forEach((skillName) => allFormSkillNames.add(skillName));
      } else if (cur === "Kung Fu Point" && formNames.length === 3 &&
         }
        formNames.includes("Heavy Point") && formNames.includes("Brain Point")) {
       });
        orderedFormNames = ["Kung Fu Point", "Heavy Point", "Brain Point"];
      } else if (cur === "Heavy Point" && formNames.length === 3 &&
        formNames.includes("Brain Point") && formNames.includes("Kung Fu Point")) {
         orderedFormNames = ["Heavy Point", "Brain Point", "Kung Fu Point"];
       }


       const visibleFormSkillNames = new Set();
       if (orderedFormNames.length === 0) {
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
        orderedFormNames = [...formNames].sort();
         (icon) => {
         if (cur) {
           const name = (icon.dataset.nome || "").trim();
           const idx = orderedFormNames.indexOf(cur);
           if (name && allFormSkillNames.has(name)) {
           if (idx !== -1) {
            visibleFormSkillNames.add(name);
            orderedFormNames = [
              ...orderedFormNames.slice(idx),
              ...orderedFormNames.slice(0, idx),
            ];
           }
           }
         }
         }
       );
       }
 
      const idx = orderedFormNames.indexOf(cur);
      if (idx === -1) return null;
      return orderedFormNames[(idx + 1) % orderedFormNames.length];
    }


      // Compara com cada forma para ver qual corresponde
     function switchForm() {
      for (const [formName, formData] of Object.entries(formsData)) {
       const skillsRoot = document.getElementById("skills");
        if (formData.order && Array.isArray(formData.order)) {
       if (!skillsRoot) return;
          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
       // Lê dados de forms do atributo data-forms
Linha 441: Linha 511:
       if (formNames.length === 0) return;
       if (formNames.length === 0) return;


      // Determina próxima forma
      // Se currentForm é null, detecta qual forma está atualmente visível no DOM
       if (currentForm === null) {
       if (currentForm === null) {
         currentForm = detectCurrentForm();
         currentForm = detectCurrentForm();
        if (!currentForm && formNames.length > 0) {
          currentForm = formNames[0];
        }
       }
       }


       // Se ainda não conseguiu detectar, usa a primeira forma como fallback
       const nextForm = getNextFormName();
       if (!currentForm && formNames.length > 0) {
       if (!nextForm) return;
        currentForm = formNames[0];
      }


       // Cria ordem circular fixa baseada na forma atual detectada
       currentForm = nextForm;
      // 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
       // Atualiza barra de skills (que vai remover o active depois da animação)
      if (orderedFormNames.length === 0) {
       updateSkillsBarForForm(nextForm, formsData[nextForm], changeFormIcon);
        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);
     }
     }


Linha 524: Linha 541:
         try {
         try {
           flags = JSON.parse(icon.dataset.flags);
           flags = JSON.parse(icon.dataset.flags);
         } catch (e) {}
         } catch (e) { }
       }
       }
       let weapon = null;
       let weapon = null;
Linha 530: Linha 547:
         try {
         try {
           weapon = JSON.parse(icon.dataset.weapon);
           weapon = JSON.parse(icon.dataset.weapon);
         } catch (e) {}
         } catch (e) { }
       }
       }
       return {
       return {
Linha 641: Linha 658:
       const thirdSkillData = formSkills.find((s) => s.name === thirdSkillName);
       const thirdSkillData = formSkills.find((s) => s.name === thirdSkillName);


       // Quinta skill da forma (vai DEPOIS do Guard Point)
       // Skills "extras" da forma (order[2..N]) vão DEPOIS do Guard Point,
       const fifthSkillName = formOrder[2]; // Terceira na ordem = quinta skill (índice 2)
      // em sequência contígua. Para forms com 3 skills (caso clássico do Chopper)
      const fifthSkillData = formSkills.find((s) => s.name === fifthSkillName);
       // isso insere apenas order[2] (quinta skill) mantendo o layout antigo.
      // Para forms com 4+ skills, as demais ocupam os slots seguintes.
      const extraSkillsData = [];
      for (let i = 2; i < formOrder.length; i++) {
        const nm = formOrder[i];
        const dt = formSkills.find((s) => s.name === nm);
        if (dt) extraSkillsData.push(dt);
      }


       // Cria fragments para inserir
       // Cria fragments para inserir
       const firstFragment = document.createDocumentFragment();
       const firstFragment = document.createDocumentFragment();
       const thirdFragment = document.createDocumentFragment();
       const thirdFragment = document.createDocumentFragment();
       const fifthFragment = document.createDocumentFragment();
       const extrasFragment = document.createDocumentFragment();


       if (firstSkillData) {
       if (firstSkillData) {
Linha 665: Linha 689:
         thirdFragment.appendChild(iconElement);
         thirdFragment.appendChild(iconElement);
       }
       }
       if (fifthSkillData) {
       if (extraSkillsData.length > 0) {
         const nextFixedSkillIndex = parseInt(
         const nextFixedSkillIndex = parseInt(
           nextFixedSkillIcon.dataset.index || "4",
           nextFixedSkillIcon.dataset.index || "4",
           10
           10
         );
         );
         const iconElement = createSkillIconElement(
         extraSkillsData.forEach((dt, offset) => {
          fifthSkillData,
          const iconElement = createSkillIconElement(
          nextFixedSkillIndex + 1
            dt,
        );
            nextFixedSkillIndex + 1 + offset
        fifthFragment.appendChild(iconElement);
          );
          extrasFragment.appendChild(iconElement);
        });
       }
       }


Linha 695: Linha 721:
         }
         }


         // Insere a quinta skill DEPOIS da próxima skill fixa
         // Insere as skills extras (order[2..N]) DEPOIS da próxima skill fixa,
         if (fifthFragment.hasChildNodes()) {
        // em sequência contígua.
         if (extrasFragment.hasChildNodes()) {
           if (nextFixedSkillIcon.nextSibling) {
           if (nextFixedSkillIcon.nextSibling) {
             iconBar.insertBefore(fifthFragment, nextFixedSkillIcon.nextSibling);
             iconBar.insertBefore(extrasFragment, nextFixedSkillIcon.nextSibling);
           } else {
           } else {
             iconBar.appendChild(fifthFragment);
             iconBar.appendChild(extrasFragment);
           }
           }
         }
         }
Linha 759: Linha 786:
           }
           }


           // Cria slot para quinta skill (depois da próxima skill fixa)
           // Cria slots para TODAS as skills extras (depois da próxima skill fixa),
           if (fifthSkillData && nextFixedSkillDescSlot) {
          // em sequência contígua. Cada uma recebe um data-index incremental.
             const descSlot = document.createElement("div");
           if (extraSkillsData.length > 0 && nextFixedSkillDescSlot) {
            descSlot.className = "skill-desc";
             // anchor mantém a referência de inserção: cada novo slot entra
            descSlot.setAttribute("data-index", nextFixedSkillIndex + 1);
            // logo depois do anterior, preservando a ordem visual.
            if (nextFixedSkillDescSlot.nextSibling) {
            let anchor = nextFixedSkillDescSlot;
              descBox.insertBefore(
            extraSkillsData.forEach((_, offset) => {
                descSlot,
              const descSlot = document.createElement("div");
                nextFixedSkillDescSlot.nextSibling
              descSlot.className = "skill-desc";
              );
              descSlot.setAttribute("data-index", nextFixedSkillIndex + 1 + offset);
            } else {
              if (anchor.nextSibling) {
              descBox.appendChild(descSlot);
                descBox.insertBefore(descSlot, anchor.nextSibling);
             }
              } else {
                descBox.appendChild(descSlot);
              }
              anchor = descSlot;
             });
           }
           }
         }
         }
Linha 829: Linha 860:
       );
       );
       iconWrap.setAttribute("data-video-preload", "auto");
       iconWrap.setAttribute("data-video-preload", "auto");
       iconWrap.setAttribute("data-icon-file", skill.icon || "Nada.png");
       iconWrap.setAttribute("data-icon-file", skill.icon || "");
       iconWrap.setAttribute("data-video-file", skill.video || "");
       iconWrap.setAttribute("data-video-file", skill.video || "");


Linha 835: Linha 866:
         skill.level &&
         skill.level &&
         skill.level !== "" &&
         skill.level !== "" &&
         skill.level.toUpperCase() !== "NIVEL"
         String(skill.level).toUpperCase() !== "NIVEL"
       ) {
       ) {
         iconWrap.setAttribute("data-level", skill.level);
         iconWrap.setAttribute("data-level", skill.level);
Linha 872: Linha 903:
       ) {
       ) {
         iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
         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) { }
      }
      if (skill.formSwitch) {
        iconWrap.setAttribute("data-form-switch", skill.formSwitch);
      }
      if (skill.formVideos && typeof skill.formVideos === "string" && skill.formVideos.trim() !== "") {
        iconWrap.setAttribute("data-form-videos", skill.formVideos);
       }
       }


       const img = document.createElement("img");
       const img = document.createElement("img");
       img.className = "skill-icon-img";
       img.className = "skill-icon-img";
       img.src = filePathURL(skill.icon || "Nada.png");
       img.src = filePathURL(skill.icon || "");
       img.alt = "";
       img.alt = "";
       iconWrap.appendChild(img);
       iconWrap.appendChild(img);
Linha 904: Linha 947:
       );
       );
       iconWrap.setAttribute("data-video-preload", "auto");
       iconWrap.setAttribute("data-video-preload", "auto");
       iconWrap.setAttribute("data-icon-file", skill.icon || "Nada.png");
       iconWrap.setAttribute("data-icon-file", skill.icon || "");
       iconWrap.setAttribute("data-video-file", skill.video || "");
       iconWrap.setAttribute("data-video-file", skill.video || "");


Linha 910: Linha 953:
         skill.level &&
         skill.level &&
         skill.level !== "" &&
         skill.level !== "" &&
         skill.level.toUpperCase() !== "NIVEL"
         String(skill.level).toUpperCase() !== "NIVEL"
       ) {
       ) {
         iconWrap.setAttribute("data-level", skill.level);
         iconWrap.setAttribute("data-level", skill.level);
Linha 947: Linha 990:
       ) {
       ) {
         iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
         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) { }
      }
      if (skill.formSwitch) {
        iconWrap.setAttribute("data-form-switch", skill.formSwitch);
      }
      if (skill.formVideos && typeof skill.formVideos === "string" && skill.formVideos.trim() !== "") {
        iconWrap.setAttribute("data-form-videos", skill.formVideos);
       }
       }


       const img = document.createElement("img");
       const img = document.createElement("img");
       img.className = "skill-icon-img";
       img.className = "skill-icon-img";
       img.src = filePathURL(skill.icon || "Nada.png");
       img.src = filePathURL(skill.icon || "");
       img.alt = "";
       img.alt = "";
       iconWrap.appendChild(img);
       iconWrap.appendChild(img);
Linha 971: Linha 1 026:


     function filePathURL(fileName) {
     function filePathURL(fileName) {
       // Evita requisições para Nada.png que não existe
       // Evita requisições para valores vazios
       if (
       if (!fileName || fileName.trim() === "") {
        !fileName ||
        fileName.trim() === "" ||
        fileName === "Nada.png" ||
        fileName.toLowerCase() === "nada.png"
      ) {
         return "";
         return "";
       }
       }
Linha 985: Linha 1 035:
           ? mw.util.wikiScript()
           ? mw.util.wikiScript()
           : window.mw && window.mw.config
           : window.mw && window.mw.config
          ? mw.config.get("wgScript") || "/index.php"
            ? mw.config.get("wgScript") || "/index.php"
          : "/index.php";
            : "/index.php";
       // Garante HTTPS para evitar Mixed Content
       // Garante HTTPS para evitar Mixed Content
       let url = `${base}?title=Especial:FilePath/${f}`;
       let url = `${base}?title=Especial:FilePath/${f}`;
Linha 1 020: Linha 1 070:
       const pack = obj.desc_i18n ||
       const pack = obj.desc_i18n ||
         obj.desc || {
         obj.desc || {
          pt: obj.descPt,
        pt: obj.descPt,
          en: obj.descEn,
        en: obj.descEn,
          es: obj.descEs,
        es: obj.descEs,
          pl: obj.descPl,
        pl: obj.descPl,
        };
      };
       return (
       return (
         (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || ""
         (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || ""
Linha 1 042: Linha 1 092:
         en
         en
           ? chip(
           ? chip(
              en.startsWith("-") ? L.energy_cost : L.energy_gain,
            en.startsWith("-") ? L.energy_cost : L.energy_gain,
              en.startsWith("-") ? en.replace(/^-/, "") : en.replace(/^\+?/, "")
            en.startsWith("-") ? en.replace(/^-/, "") : en.replace(/^\+?/, "")
            )
          )
           : "",
           : "",
         pve ? chip(L.power, pve) : "",
         pve ? chip(L.power, pve) : "",
Linha 1 052: Linha 1 102:
     }
     }
     function getFlagIconURL(key) {
     function getFlagIconURL(key) {
       if (!FLAG_ICON_FILES[key]) return "";
      const keyNorm = String(key || "").trim().toLowerCase();
       if (!flagIconURLCache.has(key)) {
       if (!keyNorm || !FLAG_ICON_FILES[keyNorm]) return "";
         flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key]));
       if (!flagIconURLCache.has(keyNorm)) {
         flagIconURLCache.set(keyNorm, filePathURL(FLAG_ICON_FILES[keyNorm]));
       }
       }
       return flagIconURLCache.get(key);
       return flagIconURLCache.get(keyNorm);
     }
     }
     function renderFlagsRow(flags) {
     function renderFlagsRow(flags) {
       const arr = (flags || []).filter(Boolean);
       const arr = (flags || []).filter(Boolean).map((k) => String(k || "").trim().toLowerCase());
       if (!arr.length) return "";
      const arrFiltered = arr.filter(Boolean);
       const cacheKey = arr.join("|");
       if (!arrFiltered.length) return "";
       const cacheKey = arrFiltered.join("|");
       if (flagRowCache.has(cacheKey)) {
       if (flagRowCache.has(cacheKey)) {
         return flagRowCache.get(cacheKey);
         return flagRowCache.get(cacheKey);
       }
       }
       const items = arr
       const items = arrFiltered
         .map((k) => {
         .map((k) => {
           const url = getFlagIconURL(k);
           const url = getFlagIconURL(k);
Linha 1 085: Linha 1 137:
       try {
       try {
         pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
         pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
       } catch (e) {}
       } catch (e) { }
       const lang = getLangKey();
       const lang = getLangKey();
       const dict = pack[lang] || pack.pt || {};
       const dict = pack[lang] || pack.pt || {};
Linha 1 194: Linha 1 246:
         if (!videoFile) {
         if (!videoFile) {
           videoFile = extractFileNameFromURL(icon.dataset.video || "");
           videoFile = extractFileNameFromURL(icon.dataset.video || "");
        }
        let flags = null;
        if (icon.dataset.flags) {
          try {
            flags = JSON.parse(icon.dataset.flags);
            if (!Array.isArray(flags) || flags.length === 0) flags = null;
          } catch (e) { flags = null; }
         }
         }
         const meta = {
         const meta = {
           index,
           index,
           name,
           name,
           icon: iconFile || "Nada.png",
           icon: iconFile || "",
           level: icon.dataset.level || "",
           level: icon.dataset.level || "",
           video: videoFile || "",
           video: videoFile || "",
Linha 1 210: Linha 1 269:
           descEs: icon.dataset.descEs || "",
           descEs: icon.dataset.descEs || "",
           descPl: icon.dataset.descPl || "",
           descPl: icon.dataset.descPl || "",
          flags: flags || null,
         };
         };
         mainSkillsMeta.byIndex.set(index, meta);
         mainSkillsMeta.byIndex.set(index, meta);
Linha 1 219: Linha 1 279:
       mainSkillsMeta.ready = true;
       mainSkillsMeta.ready = true;
       return mainSkillsMeta;
       return mainSkillsMeta;
    }
    function rebuildMainSkillsMetaFromBar() {
      const iconBar = document.querySelector(".icon-bar");
      if (!iconBar) return;
      mainSkillsMeta.ready = false;
      mainSkillsMeta.byIndex.clear();
      mainSkillsMeta.byName.clear();
      buildMainSkillsMeta(
        Array.from(iconBar.querySelectorAll(".skill-icon[data-index]"))
      );
     }
     }


Linha 1 264: Linha 1 335:
       }
       }
       hydrated.name = name || hydrated.name || main.name || "";
       hydrated.name = name || hydrated.name || main.name || "";
       hydrated.icon = pickFilled(hydrated.icon, main.icon || "Nada.png");
       hydrated.icon = pickFilled(hydrated.icon, main.icon || "");
       hydrated.level = pickFilled(hydrated.level, main.level || "");
       hydrated.level = pickFilled(hydrated.level, main.level || "");


Linha 1 275: Linha 1 346:
       hydrated.energy = pickFilled(hydrated.energy, main.energy || "");
       hydrated.energy = pickFilled(hydrated.energy, main.energy || "");
       hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || "");
       hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || "");
      // Flags: herda da skill principal se a subskill não tiver
      if (
        (!hydrated.flags || !Array.isArray(hydrated.flags) || hydrated.flags.length === 0) &&
        main.flags && Array.isArray(main.flags) && main.flags.length > 0
      ) {
        hydrated.flags = main.flags;
      }


       // Descrição: sempre vem da subskill, nunca herda
       // Descrição: sempre vem da subskill, nunca herda
Linha 1 327: Linha 1 406:
       if (!Array.isArray(subs)) return;
       if (!Array.isArray(subs)) return;
       subs.forEach((sub) => {
       subs.forEach((sub) => {
         const iconURL = normalizeFileURL(sub.icon || "Nada.png", "");
         const iconURL = normalizeFileURL(sub.icon || "", "");
         if (iconURL && iconURL !== "") iconsSet.add(iconURL);
         if (iconURL && iconURL !== "") iconsSet.add(iconURL);
         // Vídeo normal
         // Vídeo normal
         if (
         if (sub.video && sub.video.trim() !== "") {
          sub.video &&
          sub.video.trim() !== "" &&
          sub.video !== "Nada.png" &&
          !sub.video.toLowerCase().includes("nada.png")
        ) {
           const videoURL = normalizeFileURL(sub.video);
           const videoURL = normalizeFileURL(sub.video);
           if (videoURL) videosSet.add(videoURL);
           if (videoURL) videosSet.add(videoURL);
Linha 1 344: Linha 1 418:
           typeof sub.weapon === "object" &&
           typeof sub.weapon === "object" &&
           sub.weapon.video &&
           sub.weapon.video &&
           sub.weapon.video.trim() !== "" &&
           sub.weapon.video.trim() !== ""
          sub.weapon.video !== "Nada.png" &&
          !sub.weapon.video.toLowerCase().includes("nada.png")
         ) {
         ) {
           const weaponVideoURL = normalizeFileURL(sub.weapon.video);
           const weaponVideoURL = normalizeFileURL(sub.weapon.video);
Linha 1 383: Linha 1 455:
           ""
           ""
         ).trim();
         ).trim();
         if (
         if (videoRaw) {
          videoRaw &&
          videoRaw !== "Nada.png" &&
          !videoRaw.toLowerCase().includes("nada.png")
        ) {
           const videoURL = normalizeFileURL(videoRaw);
           const videoURL = normalizeFileURL(videoRaw);
           if (videoURL) videosSet.add(videoURL);
           if (videoURL) videosSet.add(videoURL);
Linha 1 398: Linha 1 466:
               weaponData &&
               weaponData &&
               weaponData.video &&
               weaponData.video &&
               weaponData.video.trim() !== "" &&
               weaponData.video.trim() !== ""
              weaponData.video !== "Nada.png" &&
              !weaponData.video.toLowerCase().includes("nada.png")
             ) {
             ) {
               const weaponVideoURL = normalizeFileURL(weaponData.video);
               const weaponVideoURL = normalizeFileURL(weaponData.video);
               if (weaponVideoURL) videosSet.add(weaponVideoURL);
               if (weaponVideoURL) videosSet.add(weaponVideoURL);
             }
             }
           } catch (e) {}
           } catch (e) { }
         }
         }
         if (el.dataset.flags) {
         if (el.dataset.flags) {
Linha 1 414: Linha 1 480:
               if (url) flagsSet.add(url);
               if (url) flagsSet.add(url);
             });
             });
           } catch (e) {}
           } catch (e) { }
         }
         }
         if (el.dataset.subs) {
         if (el.dataset.subs) {
Linha 1 420: Linha 1 486:
             const subs = JSON.parse(el.dataset.subs);
             const subs = JSON.parse(el.dataset.subs);
             collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet);
             collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet);
           } catch (e) {}
           } catch (e) { }
         }
         }
       });
       });
Linha 1 621: Linha 1 687:
             return true;
             return true;
           }
           }
         } catch (e) {}
         } catch (e) { }
       }
       }
       return false;
       return false;
Linha 1 655: Linha 1 721:
     // Busca descBox e videoBox após a estrutura estar organizada
     // Busca descBox e videoBox após a estrutura estar organizada
     const descBox = $("#skills") ? $(".desc-box", $("#skills")) : null;
     const descBox = $("#skills") ? $(".desc-box", $("#skills")) : null;
     const videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
     let videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
    // Busca dinâmica do videoBox (pode não existir no momento da inicialização)
    function getVideoBox() {
      if (videoBox && videoBox.isConnected) return videoBox;
      const skillsRoot = document.getElementById("skills");
      if (!skillsRoot) return null;
      videoBox = skillsRoot.querySelector(".video-container") ||
        skillsRoot.querySelector(".skills-container .video-container") ||
        skillsRoot.querySelector(".content-card .video-container");
      return videoBox;
    }
     const videosCache = new Map();
     const videosCache = new Map();
     const nestedVideoElByIcon = new WeakMap();
     const nestedVideoElByIcon = new WeakMap();
Linha 1 671: Linha 1 747:
         globalWeaponEnabled = true;
         globalWeaponEnabled = true;
       }
       }
     } catch (err) {}
     } catch (err) { }
     const weaponStateListeners = new Set();
     const weaponStateListeners = new Set();
     let showWeaponPopupFn = null;
     let showWeaponPopupFn = null;
Linha 1 682: Linha 1 758:
         try {
         try {
           showWeaponPopupFn();
           showWeaponPopupFn();
         } catch (err) {}
         } catch (err) { }
       }
       }
     }
     }
Linha 1 689: Linha 1 765:
       try {
       try {
         if (localStorage.getItem("glaWeaponPopupDismissed") === "1") return;
         if (localStorage.getItem("glaWeaponPopupDismissed") === "1") return;
       } catch (err) {}
       } catch (err) { }
       if (typeof showWeaponPopupFn === "function") {
       if (typeof showWeaponPopupFn === "function") {
         showWeaponPopupFn();
         showWeaponPopupFn();
Linha 1 720: Linha 1 796:
         try {
         try {
           listener(enabled);
           listener(enabled);
         } catch (err) {}
         } catch (err) { }
       });
       });
     }
     }
Linha 1 835: Linha 1 911:
             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 886: Linha 1 978:
             /* 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 957: Linha 2 055:
         }
         }


         /* ========== ESTILOS DE SWAP DE PERSONAGENS (Sistema Genérico) ========== */
         .character-box .top-rail.skills .icon-bar {
         /* Skills desabilitadas quando o personagem ativo não pode usá-las */
            position: relative;
         .character-box .top-rail.skills .icon-bar .skill-icon.disabled-skill {
        }
             opacity: 0.3 !important;
         .character-box .top-rail.skills .icon-bar .skill-icon {
             filter: grayscale(100%) !important;
            z-index: 2;
             cursor: not-allowed !important;
        }
             pointer-events: none !important;
         .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active) {
            transition: opacity 0.2s ease, filter 0.2s ease !important;
             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 {
      document.head.appendChild(style);
             box-shadow:
    })();
                inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
    function applyWeaponBadge(el, weaponData, equipped) {
                0 0 6px rgba(255, 60, 60, 0.45),
      // Apenas gerencia a classe weapon-equipped (badge visual removido)
                0 0 10px rgba(120, 20, 20, 0.6) !important;
      if (equipped && weaponData) {
            animation: effect-child-ring 1.6s ease-in-out infinite !important;
        el.classList.add("weapon-equipped");
            opacity: 1 !important;
      } else {
        }
         el.classList.remove("weapon-equipped");
        .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;
    function getWeaponKey(el) {
            z-index: 1 !important;
      return (
            opacity: 1 !important;
        (el.dataset.index || "") +
            box-shadow:
        ":" +
                0 0 8px rgba(255, 60, 60, 0.35),
        (el.dataset.nome || el.dataset.name || "")
                0 0 14px rgba(120, 20, 20, 0.45);
      );
            animation: effect-child-glow 1.6s ease-in-out infinite !important;
    }
        }
    function isWeaponModeOn() {
        .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
      try {
            box-shadow:
        return localStorage.getItem("glaWeaponEnabled") === "1";
                inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
      } catch (e) {
                0 0 6px rgba(255, 60, 60, 0.45),
        return false;
                0 0 10px rgba(120, 20, 20, 0.6) !important;
      }
            animation: effect-child-ring 1.6s ease-in-out infinite !important;
    }
            opacity: 1 !important;
    function getWeaponDataForIcon(iconEl) {
        }
      if (!iconEl || !iconEl.dataset.weapon) return null;
         .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
      // Usa cache de parsing JSON
            inset: 0 !important;
      return getCachedJSON(iconEl, "weapon");
            border-radius: inherit !important;
    }
            z-index: 1 !important;
    function getEffectiveSkillVideoFromIcon(iconEl) {
            opacity: 1 !important;
      const weaponOn = globalWeaponEnabled;
            box-shadow:
      const weaponData = getWeaponDataForIcon(iconEl);
                0 0 8px rgba(255, 60, 60, 0.35),
      const baseVideoFile = (iconEl.dataset.videoFile || "").trim();
                0 0 14px rgba(120, 20, 20, 0.45);
      const baseVideoURL = (iconEl.dataset.video || "").trim();
            animation: effect-child-glow 1.6s ease-in-out infinite !important;
 
        }
      // console.log('[Skills DEBUG]', {
        @keyframes effect-child-ring {
      //    skillName: iconEl.dataset.nome || iconEl.dataset.name,
            0% {
      //    weaponOn,
                box-shadow:
      //    hasWeaponData: !!weaponData,
                    inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
      //    weaponData: weaponData,
                    0 0 6px rgba(255, 60, 60, 0.45),
      //    baseVideoFile,
                    0 0 10px rgba(120, 20, 20, 0.6);
      //    baseVideoURL
            }
      // });
            50% {
 
                box-shadow:
      if (weaponOn && weaponData) {
                    inset 0 0 0 var(--icon-ring-w) rgba(120, 20, 20, 0.95),
         // console.log('[Skills] checking weapon video', {
                    0 0 9px rgba(255, 70, 70, 0.65),
         //    skillName: iconEl.dataset.nome || iconEl.dataset.name,
                    0 0 14px rgba(20, 0, 0, 0.8);
        //    hasVideo: !!(weaponData.video),
            }
         //    videoValue: weaponData.video,
            100% {
        //    videoTrimmed: weaponData.video ? weaponData.video.trim() : ''
                box-shadow:
         // });
                    inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
        if (weaponData.video && weaponData.video.trim() !== "") {
                    0 0 6px rgba(255, 60, 60, 0.45),
          // console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
                    0 0 10px rgba(120, 20, 20, 0.6);
          return weaponData.video.trim();
            }
        }
        @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;
            }
         }
         }
      }


      // Sistema genérico de swap: verifica character_videos se houver personagem ativo
        .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle).active::before {
      if (activeCharacter !== null && iconEl.dataset.characterVideos) {
            pointer-events: none !important;
        try {
            inset: 0 !important;
          const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
            border-radius: inherit !important;
          const characterVideo = characterVideos[activeCharacter];
            z-index: 1 !important;
          if (characterVideo && characterVideo.trim() !== "") {
             animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
             return characterVideo.trim();
            box-shadow:
          }
                0 0 10px 0 rgba(220, 220, 220, 0.5),
        } catch (e) {
                0 0 16px 0 rgba(190, 190, 190, 0.4),
          console.warn("[Swap] Erro ao processar character_videos:", e);
                0 0 22px 0 rgba(220, 220, 220, 0.3) !important;
            opacity: 1 !important;
         }
         }
      }


      // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
        /* ========== ESTILOS DE SWAP DE PERSONAGENS (Sistema Genérico) ========== */
       const result = baseVideoFile || baseVideoURL || "";
        /* Skills desabilitadas quando o personagem ativo não pode usá-las */
       // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
        .character-box .top-rail.skills .icon-bar .skill-icon.disabled-skill {
       return result;
            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 createVideoElement(videoURL, extraAttrs = {}) {
     function isWeaponModeOn() {
       // Se o vídeo já falhou antes, não cria novo elemento
       try {
      if (failedVideosCache.has(videoURL)) {
        return localStorage.getItem("glaWeaponEnabled") === "1";
         return null;
      } catch (e) {
         return false;
       }
       }
 
    }
       const v = document.createElement("video");
    function getWeaponDataForIcon(iconEl) {
       v.className = "skill-video";
       if (!iconEl || !iconEl.dataset.weapon) return null;
       v.setAttribute("controls", "");
      // Usa cache de parsing JSON
       v.setAttribute("preload", "auto"); // Mudado de 'metadata' para 'auto' para carregar tudo imediatamente
      return getCachedJSON(iconEl, "weapon");
       v.setAttribute("playsinline", "");
    }
       v.style.display = "none";
    const effectState = {
       v.style.width = "100%";
       skills: new Set(),
      v.style.height = "auto";
      videos: new Map(),
       v.style.aspectRatio = "16/9";
      expiresAt: 0,
       v.style.objectFit = "cover";
      sourceIcon: null,
       Object.keys(extraAttrs).forEach((k) => {
      timer: null,
         v.dataset[k] = extraAttrs[k];
    };
    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();
       });
       });
       // Detectar formato do vídeo pela extensão
    }
       const ext = (videoURL.split(".").pop() || "").toLowerCase().split("?")[0];
    function getEffectLinesLayer() {
       const mimeTypes = {
      const rail = document.querySelector(".top-rail.skills");
         mp4: "video/mp4",
      if (!rail) return null;
         m4v: "video/mp4",
      const iconBar = rail.querySelector(".icon-bar");
         webm: "video/webm",
      if (!iconBar) return null;
        ogv: "video/ogg",
      if (effectLinesLayer && effectLinesLayer.isConnected) {
        ogg: "video/ogg",
        return effectLinesLayer;
         mov: "video/quicktime",
      }
      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 mimeType = mimeTypes[ext] || "video/mp4";
       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 src = document.createElement("source");
       const core = document.createElementNS(
       src.src = videoURL;
        "http://www.w3.org/2000/svg",
       src.type = mimeType;
        "path"
      v.appendChild(src);
      );
       core.setAttribute("d", d);
       core.classList.add("effect-line", "effect-line-core");


       // Fallback para Safari/iOS mais antigos
       frag.appendChild(glow);
      v.setAttribute("webkit-playsinline", "");
       frag.appendChild(core);
       v.setAttribute("x-webkit-airplay", "allow");


       // Tratamento silencioso de erros - marca como falhado e não tenta mais
       requestAnimationFrame(() => {
      let errorHandled = false;
        const length = Math.max(1, Math.round(core.getTotalLength()));
      v.addEventListener(
         [core, glow].forEach((path) => {
         "error",
           path.style.setProperty("--effect-line-length", `${length}`);
        (e) => {
           path.style.strokeDasharray = `${length}`;
           if (errorHandled) return;
           path.style.strokeDashoffset = "0";
           errorHandled = true;
        });
           // Marca o vídeo como falhado para não tentar carregar novamente
      });
          failedVideosCache.add(videoURL);
      layer.replaceChildren(frag);
          // Remove o vídeo do DOM se estiver lá
    }
          if (v.parentNode) {
    function animateEffectLinesReturn() {
            v.parentNode.removeChild(v);
      const layer =
          }
        (effectLinesLayer && effectLinesLayer.isConnected
          // Avisa apenas uma vez sobre vídeos faltantes
          ? effectLinesLayer
          if (!missingVideosReported.has(videoURL)) {
          : document.querySelector(".effect-lines-layer")) || null;
            missingVideosReported.add(videoURL);
      if (!layer || !effectLinesLastState) {
            // Extrai nome do arquivo da URL para o aviso
        clearEffectLines();
            const fileName =
        return;
              videoURL.split("/").pop().split("?")[0] || videoURL;
      }
            console.info(
      const { startX, startY, baselineY, targets } = effectLinesLastState;
              `[Skills] Vídeo não encontrado na wiki: ${decodeURIComponent(
      const sortedTargets = [...targets].sort((a, b) => b.dist - a.dist);
                fileName
      const returnDuration = 1.1;
              )}. Este aviso aparecerá apenas uma vez.`
      const returnStagger = 0.22;
            );
      const frag = document.createDocumentFragment();
           }
      sortedTargets.forEach((target, idx) => {
        },
        const d = [
        { once: true }
          `M ${startX} ${startY}`,
      );
          `L ${startX} ${baselineY}`,
           `L ${target.x} ${baselineY}`,
          `L ${target.x} ${target.y}`,
        ].join(" ");


      return v;
        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`);


    // Função recursiva para carregar TODOS os vídeos de subskills (incluindo sub-subskills)
        const core = document.createElementNS(
    function preloadSubskillVideosRecursively(
          "http://www.w3.org/2000/svg",
      subs,
          "path"
      parentIdx,
        );
      parentPath = ""
        core.setAttribute("d", d);
    ) {
        core.classList.add(
      if (!videoBox || !Array.isArray(subs)) return 0;
          "effect-line",
      let createdCount = 0;
          "effect-line-core",
      subs.forEach((s) => {
          "effect-line-returning"
         const subName = (s.name || s.n || "").trim();
        );
        const currentPath = parentPath ? `${parentPath}:${subName}` : subName;
        core.style.setProperty("--effect-return-duration", `${returnDuration}s`);
         core.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);


         // Vídeo normal da subskill
         frag.appendChild(glow);
         if (
         frag.appendChild(core);
          s.video &&
      });
          s.video.trim() !== "" &&
      layer.replaceChildren(frag);
          s.video !== "Nada.png" &&
 
          !s.video.toLowerCase().includes("nada.png")
      requestAnimationFrame(() => {
        ) {
        const paths = Array.from(layer.querySelectorAll("path.effect-line"));
           const key = `sub:${parentIdx}:${currentPath}`;
        paths.forEach((path) => {
           if (!subskillVideosCache.has(key)) {
          const length = Math.max(1, Math.round(path.getTotalLength()));
            const videoURL = normalizeFileURL(s.video);
          path.style.setProperty("--effect-line-length", `${length}`);
            if (
           path.style.strokeDasharray = `${length} ${length}`;
              videoURL &&
           path.style.strokeDashoffset = "0";
              videoURL.trim() !== "" &&
        });
              !failedVideosCache.has(videoURL)
      });
            ) {
      if (effectLinesCleanupTimer) {
              const v = createVideoElement(videoURL, {
        clearTimeout(effectLinesCleanupTimer);
                sub: "1",
      }
                parentIndex: parentIdx,
      effectLinesCleanupTimer = setTimeout(() => {
                subName: currentPath,
        clearEffectLines();
                cacheKey: key,
      }, (returnDuration + returnStagger * Math.max(0, targets.length - 1)) * 1000 + 120);
              });
    }
              if (v) {
    function clearEffectLines() {
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
      if (effectLinesCleanupTimer) {
                videoBox.appendChild(v);
        clearTimeout(effectLinesCleanupTimer);
                subskillVideosCache.set(key, v);
        effectLinesCleanupTimer = null;
                createdCount++;
      }
                // FORÇA carregamento imediatamente (apenas uma vez)
      const layer =
                v.load();
        (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);


        // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
      animateEffectLinesReturn();
        if (
      if (effectState.timer) {
          s.weapon &&
        clearTimeout(effectState.timer);
          typeof s.weapon === "object" &&
        effectState.timer = null;
          s.weapon.video &&
      }
          s.weapon.video.trim() !== "" &&
      effectState.skills.clear();
          s.weapon.video !== "Nada.png" &&
      effectState.videos.clear();
          !s.weapon.video.toLowerCase().includes("nada.png")
      effectState.expiresAt = 0;
        ) {
      effectState.sourceIcon = null;
          const weaponKey = `sub:${parentIdx}:${currentPath}:weapon`;
      applyEffectClasses();
          if (!subskillVideosCache.has(weaponKey)) {
 
            const weaponVideoURL = normalizeFileURL(s.weapon.video);
      if (wasAffected && activeIcon) {
            if (
        activeIcon.dispatchEvent(new Event("click", { bubbles: true }));
              weaponVideoURL &&
      }
              weaponVideoURL.trim() !== "" &&
    }
              !failedVideosCache.has(weaponVideoURL)
    function activateEffectFromIcon(iconEl) {
            ) {
      const effectRaw = getCachedJSON(iconEl, "effect");
              const v = createVideoElement(weaponVideoURL, {
      const normalized = normalizeEffectData(effectRaw);
                sub: "1",
      if (!normalized) return;
                parentIndex: parentIdx,
 
                subName: currentPath,
      effectState.skills = new Set(normalized.skills);
                weapon: "1",
      effectState.videos = normalized.videos;
                cacheKey: weaponKey,
      effectState.expiresAt = Date.now() + normalized.timeMs;
              });
      effectState.sourceIcon = iconEl;
              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 (effectState.timer) clearTimeout(effectState.timer);
        if (Array.isArray(s.subs) && s.subs.length > 0) {
      effectState.timer = setTimeout(() => {
          createdCount += preloadSubskillVideosRecursively(
        clearEffectState();
            s.subs,
       }, normalized.timeMs + 5);
            parentIdx,
 
            currentPath
       applyEffectClasses();
          );
        }
       });
       return createdCount;
     }
     }
 
     function getEffectVideoForIcon(iconEl) {
     function precreateSubskillVideos() {
       if (!iconEl) return "";
       if (!videoBox) return;
       if (effectState.expiresAt <= Date.now()) return "";
       let createdCount = 0;
      const name = getSkillNameFromIcon(iconEl);
      iconItems.forEach((parentIcon) => {
      if (!name || !effectState.skills.has(name)) return "";
        const subs = getCachedJSON(parentIcon, "subs");
      if (effectState.videos.has(name)) return effectState.videos.get(name) || "";
        if (!Array.isArray(subs)) return;
       return "";
        const parentIdx = parentIcon.dataset.index || "";
        createdCount += preloadSubskillVideosRecursively(subs, parentIdx);
       });
     }
     }
    function getEffectiveSkillVideoFromIcon(iconEl) {
      const weaponOn = globalWeaponEnabled;
      const weaponData = getWeaponDataForIcon(iconEl);
      const baseVideoFile = (iconEl.dataset.videoFile || "").trim();
      const baseVideoURL = (iconEl.dataset.video || "").trim();


    // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
      // console.log('[Skills DEBUG]', {
     function preloadAllVideosRecursively() {
      //    skillName: iconEl.dataset.nome || iconEl.dataset.name,
       if (!videoBox || !iconItems.length) return;
      //     weaponOn,
       //    hasWeaponData: !!weaponData,
      //    weaponData: weaponData,
      //    baseVideoFile,
      //    baseVideoURL
      // });


       // 1. Carregar vídeos de skills principais
       const effectVideo = getEffectVideoForIcon(iconEl);
       iconItems.forEach((el) => {
       if (effectVideo && effectVideo.trim() !== "") {
        const idx = el.dataset.index || "";
         return effectVideo.trim();
         if (!idx) return;
      }


         // Vídeo normal da skill principal
      if (weaponOn && weaponData) {
         // Prioriza data-video-file (nome do arquivo), depois data-video (pode ser URL completa)
        // console.log('[Skills] checking weapon video', {
         let src = (el.dataset.videoFile || "").trim();
         //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
         if (!src) {
         //     hasVideo: !!(weaponData.video),
           // Se não tem videoFile, tenta extrair do video (pode ser URL completa)
        //    videoValue: weaponData.video,
          const videoAttr = (el.dataset.video || "").trim();
         //    videoTrimmed: weaponData.video ? weaponData.video.trim() : ''
           if (videoAttr) {
        // });
            // Se já é uma URL completa, usa direto; senão normaliza
         if (weaponData.video && weaponData.video.trim() !== "") {
            if (videoAttr.includes("/") || videoAttr.startsWith("http")) {
           // console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
              src = videoAttr;
           return weaponData.video.trim();
            } else {
        }
              src = videoAttr;
      }
            }
 
          }
      // Sistema genérico de swap: verifica character_videos se houver personagem ativo
        }
      if (activeCharacter !== null && iconEl.dataset.characterVideos) {
        if (
        try {
          src &&
           const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
          src !== "Nada.png" &&
           const characterVideo = characterVideos[activeCharacter];
          !src.toLowerCase().includes("nada.png") &&
           if (characterVideo && characterVideo.trim() !== "") {
           !videosCache.has(idx)
             return characterVideo.trim();
        ) {
          }
           const videoURL = normalizeFileURL(src);
        } catch (e) {
           if (videoURL && !failedVideosCache.has(videoURL)) {
          console.warn("[Swap] Erro ao processar character_videos:", e);
             const v = createVideoElement(videoURL, {
        }
              index: idx,
      }
            });
 
            if (v) {
      // form_switch (Change Form): usa vídeo da PRÓXIMA forma (transição atual → próxima)
              // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
      const isFormSwitch =
              totalVideos++;
        iconEl.dataset.formSwitch === "true" ||
              v.style.maxWidth = "100%";
        iconEl.getAttribute("data-form-switch") === "true";
              v.addEventListener(
      if (isFormSwitch) {
                "canplaythrough",
        const formVideosRaw =
                () => {
          iconEl.dataset.formVideos || iconEl.getAttribute("data-form-videos");
                  loadedVideos++;
        if (formVideosRaw) {
                  if (!userHasInteracted && loadedVideos === 1) {
          try {
                    try {
            const videos = JSON.parse(formVideosRaw);
                      v.pause();
            let formVideo = "";
                      v.currentTime = 0;
            const nextForm = typeof getNextFormName === "function" ? getNextFormName() : null;
                    } catch (e) {}
            if (nextForm) {
                  }
              formVideo = videos[nextForm] || "";
                  if (loadedVideos === totalVideos) autoplay = true;
            }
                },
            // Fallback: se nextForm for null (formsData ainda não carregado), usa primeiro vídeo disponível
                {
            if ((!formVideo || formVideo.trim() === "") && Object.keys(videos).length > 0) {
                  once: true,
               const firstKey = Object.keys(videos)[0];
                }
               formVideo = videos[firstKey] || "";
              );
            }
              v.addEventListener(
            if (formVideo && formVideo.trim() !== "") {
                "error",
               return formVideo.trim();
                () => {
                  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();
             }
             }
          } catch (e) {
            console.warn("[Forms] Erro ao parsear form_videos em getEffectiveSkillVideoFromIcon:", 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";


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


       // 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 = ""
    ) {
       const vb = getVideoBox();
      if (!vb || !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 (
            if (
            icon &&
              videoURL &&
            icon !== "Nada.png" &&
              videoURL.trim() !== "" &&
            !icon.toLowerCase().includes("nada.png")
              !failedVideosCache.has(videoURL)
          ) {
            ) {
            const iconURL = normalizeFileURL(icon);
              const v = createVideoElement(videoURL, {
            if (iconURL && !iconCache.has(iconURL)) {
                sub: "1",
              iconCache.add(iconURL);
                parentIndex: parentIdx,
              const img = new Image();
                subName: currentPath,
              img.decoding = "async";
                cacheKey: key,
              img.loading = "eager";
              });
              img.referrerPolicy = "same-origin";
              if (v) {
              img.src = iconURL;
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
               imagePreloadCache.set(iconURL, img);
                vb.appendChild(v);
                subskillVideosCache.set(key, v);
                createdCount++;
                // FORÇA carregamento imediatamente (apenas uma vez)
                v.load();
               }
             }
             }
          }
          // 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
         // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
         const subs = getCachedJSON(el, "subs");
        if (
         if (Array.isArray(subs)) {
          s.weapon &&
           processSubskillsRecursively(subs);
          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)
                vb.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;
     }
     }


    // Função principal que carrega TUDO imediatamente
     function precreateSubskillVideos() {
     function preloadAllAssets() {
       if (!getVideoBox()) 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);
       });
    }
 
    // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
    function preloadAllVideosRecursively() {
       const vb = getVideoBox();
      if (!vb || !iconItems.length) return;
 
      // 1. Carregar vídeos de skills principais
      iconItems.forEach((el) => {
        const idx = el.dataset.index || "";
        if (!idx) return;


      // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
        // Vídeo normal da skill principal
      preloadAllIconsRecursively();
        // form_switch com form_videos: NÃO pré-carrega o vídeo base (ex: Change Form-Video.mp4) pois pode não existir
    }
        const isFormSwitchPreload =
          el.dataset.formSwitch === "true" ||
          el.getAttribute("data-form-switch") === "true";
        const hasFormVideos =
          (el.dataset.formVideos || el.getAttribute("data-form-videos") || "").trim() !== "";
        const skipBaseVideo = isFormSwitchPreload && hasFormVideos;


    // Executa pré-carregamento imediatamente
        let src = "";
    preloadAllAssets();
        if (!skipBaseVideo) {
    function wireTooltipsForNewIcons() {
          src = (el.dataset.videoFile || "").trim();
      const tip = document.querySelector(".skill-tooltip");
           if (!src) {
      if (!tip) return;
             const videoAttr = (el.dataset.video || "").trim();
      let lockUntil2 = 0;
             if (videoAttr) {
      Array.from(document.querySelectorAll(".icon-bar .skill-icon")).forEach(
              src = videoAttr.includes("/") || videoAttr.startsWith("http")
        (icon) => {
                ? videoAttr
           if (
                : videoAttr;
             icon.dataset.weaponToggle === "1" ||
            }
            icon.classList.contains("weapon-bar-toggle")
           }
          )
        }
             return;
        if (src && !videosCache.has(idx)) {
          if (icon.dataset.tipwired) return;
          const videoURL = normalizeFileURL(src);
          icon.dataset.tipwired = "1";
           if (videoURL && !failedVideosCache.has(videoURL)) {
          const label =
            const v = createVideoElement(videoURL, {
            icon.dataset.nome || icon.dataset.name || icon.title || "";
              index: idx,
           if (label && !icon.hasAttribute("aria-label"))
            });
            icon.setAttribute("aria-label", label);
            if (v) {
           if (icon.hasAttribute("title")) icon.removeAttribute("title");
              // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
          const img = icon.querySelector("img");
              totalVideos++;
          if (img) {
              v.style.maxWidth = "100%";
            const imgAlt = img.getAttribute("alt") || "";
              v.addEventListener(
            const imgTitle = img.getAttribute("title") || "";
                "canplaythrough",
            if (!label && (imgAlt || imgTitle))
                () => {
              icon.setAttribute("aria-label", imgAlt || imgTitle);
                  loadedVideos++;
            img.setAttribute("alt", "");
                  if (!userHasInteracted && loadedVideos === 1) {
            if (img.hasAttribute("title")) img.removeAttribute("title");
                    try {
          }
                      v.pause();
          const measureAndPos = (el) => {
                      v.currentTime = 0;
            if (!el || tip.getAttribute("aria-hidden") === "true") return;
                    } catch (e) { }
            tip.style.left = "0px";
                  }
            tip.style.top = "0px";
                  if (loadedVideos === totalVideos) autoplay = true;
            const rect = el.getBoundingClientRect();
                },
            const tr = tip.getBoundingClientRect();
                {
            let left = Math.round(rect.left + (rect.width - tr.width) / 2);
                  once: true,
            left = Math.max(
                }
              8,
              );
               Math.min(left, window.innerWidth - tr.width - 8)
              v.addEventListener(
            );
                "error",
            const coarse =
                () => {
               (window.matchMedia && matchMedia("(pointer: coarse)").matches) ||
                  loadedVideos++;
               window.innerWidth <= 600;
                  if (loadedVideos === totalVideos) autoplay = true;
             let top = coarse
                },
              ? Math.round(rect.bottom + 10)
                {
              : Math.round(rect.top - tr.height - 8);
                  once: true,
            if (top < 8) top = Math.round(rect.bottom + 10);
                }
            tip.style.left = left + "px";
              );
            tip.style.top = top + "px";
              vb.appendChild(v);
          };
              videosCache.set(idx, v);
           const show = (el, text) => {
               nestedVideoElByIcon.set(el, v);
             tip.textContent = text || "";
               // Força carregamento imediatamente (apenas uma vez)
            tip.setAttribute("aria-hidden", "false");
               v.load();
             measureAndPos(el);
             }
            tip.style.opacity = "1";
          }
          };
        }
          const hide = () => {
 
            tip.setAttribute("aria-hidden", "true");
        // form_switch: pré-carrega TODOS os vídeos de form_videos para transições
            tip.style.opacity = "0";
        const isFormSwitch =
            tip.style.left = "-9999px";
          el.dataset.formSwitch === "true" ||
            tip.style.top = "-9999px";
          el.getAttribute("data-form-switch") === "true";
          };
        if (isFormSwitch) {
          icon.addEventListener("mouseenter", () =>
           const formVideosRaw =
            show(icon, icon.dataset.nome || icon.dataset.name || "")
             el.dataset.formVideos || el.getAttribute("data-form-videos");
          );
          if (formVideosRaw) {
          icon.addEventListener("mousemove", () => {
             try {
            if (performance.now() >= lockUntil2) measureAndPos(icon);
              const videos = JSON.parse(formVideosRaw);
          });
              Object.keys(videos).forEach((formName) => {
          icon.addEventListener("click", () => {
                const videoFile = videos[formName];
            lockUntil2 = performance.now() + 240;
                if (videoFile && videoFile.trim() !== "") {
            measureAndPos(icon);
                  const videoKey = idx + ":" + videoFile;
          });
                  if (!videosCache.has(videoKey)) {
          icon.addEventListener("mouseleave", hide);
                    const videoURL = normalizeFileURL(videoFile);
                    if (videoURL && !failedVideosCache.has(videoURL)) {
                      const v = createVideoElement(videoURL, { index: idx });
                      if (v) {
                        totalVideos++;
                        v.addEventListener(
                          "canplaythrough",
                          () => {
                            loadedVideos++;
                            if (loadedVideos === totalVideos) autoplay = true;
                          },
                          { once: true }
                        );
                        v.addEventListener(
                          "error",
                          () => {
                            loadedVideos++;
                            if (loadedVideos === totalVideos) autoplay = true;
                          },
                          { once: true }
                        );
                        vb.appendChild(v);
                        videosCache.set(videoKey, v);
                        v.load();
                      }
                    }
                  }
                }
              });
            } catch (e) {
              console.warn("[Forms] Erro ao pré-carregar form_videos:", e);
            }
          }
         }
         }
      );
    }
    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() !== "";


      // console.log('[Skills] showVideoForIcon chamado', {
        // Vídeo de weapon (sempre carrega, independente do toggle)
      //    skillName: el.dataset.nome || el.dataset.name,
        const weaponData = getCachedJSON(el, "weapon");
      //    weaponOn,
        if (
      //    isWeaponVideo,
          weaponData &&
      //    effectiveVideo: getEffectiveSkillVideoFromIcon(el)
          weaponData.video &&
      // });
          weaponData.video.trim() !== ""
      const videoKey = isWeaponVideo
        ) {
        ? `weapon:${getWeaponKey(el)}`
          const weaponKey = `weapon:${idx}:${(
        : el.dataset.index || "";
            el.dataset.nome ||
      const isSubskill = !hasIdx || el.dataset.nested === "1";
            el.dataset.name ||
      const parentIdx = el.dataset.parentIndex || "";
            ""
      const subName =
          ).trim()}`;
        el.dataset.subName || el.dataset.nome || el.dataset.name || "";
          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,
                  }
                );
                vb.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();


       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);
        const suppress = document.body.dataset.suppressSkillPlay === "1";
            if (iconURL && !iconCache.has(iconURL)) {
        if (!suppress) {
              iconCache.add(iconURL);
          v.play().catch(() => {});
              const img = new Image();
        } else {
              img.decoding = "async";
          try {
              img.loading = "eager";
             v.pause();
              img.referrerPolicy = "same-origin";
           } catch (e) {}
              img.src = iconURL;
         }
              imagePreloadCache.set(iconURL, img);
        return;
            }
          }
          // Recursão para sub-subskills
          if (Array.isArray(s.subs)) {
             processSubskillsRecursively(s.subs);
           }
         });
       }
       }
      // 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
      // Carregar ícones de skills principais
           let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
      iconItems.forEach((el) => {
           v = subskillVideosCache.get(subWeaponKey);
        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);
        }
      });
    }


          // Se não encontrou, tenta buscar recursivamente
    // Função principal que carrega TUDO imediatamente
          if (!v) {
    function preloadAllAssets() {
            const basePattern = `sub:${parentIdx}:`;
      // Carregar TUDO imediatamente - sem lazy loading
            const weaponSuffix = ":weapon";
      // 1. Carregar TODOS os vídeos (principais, subskills, weapon, sub-subskills)
            const searchName = subName.split(":").pop();
       preloadAllVideosRecursively();
            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 (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
      // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
          let subKey = `sub:${parentIdx}:${subName}`;
      preloadAllIconsRecursively();
          v = subskillVideosCache.get(subKey);
    }


          // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
    // Executa pré-carregamento imediatamente
          // Isso é útil caso haja alguma diferença no formato do caminho
    preloadAllAssets();
          if (!v) {
    function wireTooltipsForNewIcons() {
            const basePattern = `sub:${parentIdx}:`;
      const tip = document.querySelector(".skill-tooltip");
            const searchName = subName.split(":").pop(); // Último segmento do caminho
      if (!tip) return;
             // Tenta também buscar apenas pelo último segmento (útil para sub-subskills)
      let lockUntil2 = 0;
             const lastSegmentKey = `sub:${parentIdx}:${searchName}`;
      Array.from(document.querySelectorAll(".icon-bar .skill-icon")).forEach(
            v = subskillVideosCache.get(lastSegmentKey);
        (icon) => {
 
          if (
             // Se ainda não encontrou, faz busca mais ampla
            icon.dataset.weaponToggle === "1" ||
            if (!v) {
             icon.classList.contains("weapon-bar-toggle")
              for (const [key, video] of subskillVideosCache.entries()) {
          )
                if (key.startsWith(basePattern)) {
             return;
                  const pathInKey = key.substring(basePattern.length);
          if (icon.dataset.tipwired) return;
                  // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
          icon.dataset.tipwired = "1";
                  if (
          const label =
                    pathInKey === subName ||
             icon.dataset.nome || icon.dataset.name || icon.title || "";
                    pathInKey.endsWith(`:${searchName}`) ||
          if (label && !icon.hasAttribute("aria-label"))
                    pathInKey === searchName ||
            icon.setAttribute("aria-label", label);
                    (subName.includes(":") && pathInKey.includes(subName)) ||
          if (icon.hasAttribute("title")) icon.removeAttribute("title");
                    (subName.includes(":") &&
          const img = icon.querySelector("img");
                      pathInKey.endsWith(
          if (img) {
                        subName.split(":").slice(-2).join(":")
            const imgAlt = img.getAttribute("alt") || "";
                      ))
            const imgTitle = img.getAttribute("title") || "";
                  ) {
            if (!label && (imgAlt || imgTitle))
                    v = video;
              icon.setAttribute("aria-label", imgAlt || imgTitle);
                    break;
            img.setAttribute("alt", "");
                  }
            if (img.hasAttribute("title")) img.removeAttribute("title");
                }
              }
            }
           }
           }
        } else {
           const measureAndPos = (el) => {
           v = videosCache.get(el.dataset.index);
            if (!el || tip.getAttribute("aria-hidden") === "true") return;
          if (!v) {
            tip.style.left = "0px";
             v = nestedVideoElByIcon.get(el);
            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(
      // Se vídeo não foi encontrado no cache, verifica se falhou antes
              8,
      if (!v) {
              Math.min(left, window.innerWidth - tr.width - 8)
        // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
            );
        if (failedVideosCache.has(videoURL)) {
            const coarse =
          videoBox.style.display = "none";
              (window.matchMedia && matchMedia("(pointer: coarse)").matches) ||
          return;
              window.innerWidth <= 600;
        }
            let top = coarse
        // Para form_switch, cria vídeo dinamicamente (transições de forma são dinâmicas)
              ? Math.round(rect.bottom + 10)
        // Usa uma chave única que inclui o nome do arquivo do vídeo para garantir que cada vídeo diferente seja cacheado separadamente
              : Math.round(rect.top - tr.height - 8);
        if (isFormSwitch && videoURL) {
            if (top < 8) top = Math.round(rect.bottom + 10);
          const baseIndex = el.dataset.index || "";
            tip.style.left = left + "px";
          const videoFileName = effectiveVideo || "";
            tip.style.top = top + "px";
           // Chave única: index + nome do arquivo do vídeo
           };
           const videoKey = baseIndex + ":" + videoFileName;
           const show = (el, text) => {
          v = videosCache.get(videoKey);
            tip.textContent = text || "";
          if (!v) {
            tip.setAttribute("aria-hidden", "false");
             v = createVideoElement(videoURL, { index: baseIndex });
            measureAndPos(el);
            if (v) {
             tip.style.opacity = "1";
              videoBox.appendChild(v);
          };
              videosCache.set(videoKey, v);
          const hide = () => {
              v.load();
            tip.setAttribute("aria-hidden", "true");
             } else {
            tip.style.opacity = "0";
              videoBox.style.display = "none";
            tip.style.left = "-9999px";
              return;
             tip.style.top = "-9999px";
             }
          };
           }
          icon.addEventListener("mouseenter", () =>
        } else {
             show(icon, icon.dataset.nome || icon.dataset.name || "")
           // Vídeos normais devem estar pré-carregados
           );
           videoBox.style.display = "none";
          icon.addEventListener("mousemove", () => {
           return;
            if (performance.now() >= lockUntil2) measureAndPos(icon);
           });
           icon.addEventListener("click", () => {
            lockUntil2 = performance.now() + 240;
            measureAndPos(icon);
          });
           icon.addEventListener("mouseleave", hide);
         }
         }
      );
    }
    function showVideoForIcon(el) {
      userHasInteracted = true;
      const videoBox = getVideoBox();
      if (!videoBox) return;
      const effectiveVideo = getEffectiveSkillVideoFromIcon(el);
      if (!effectiveVideo || effectiveVideo.trim() === "") {
        videoBox.style.display = "none";
        return;
       }
       }
       videoBox.style.display = "block";
       const videoURL = normalizeFileURL(effectiveVideo);
       v.style.display = "block";
       if (!videoURL || videoURL.trim() === "") {
      try {
         videoBox.style.display = "none";
        v.currentTime = 0;
         return;
      } catch (e) {}
      const suppress = document.body.dataset.suppressSkillPlay === "1";
      if (!suppress) {
         v.play().catch(() => {});
      } else {
         try {
          v.pause();
        } catch (e) {}
       }
       }
    }
      Array.from(videoBox.querySelectorAll("video.skill-video")).forEach(
    function activateSkill(el, options = {}) {
        (v) => {
      const { openSubs = true } = options;
          try {
      const tip = document.querySelector(".skill-tooltip");
            v.pause();
      if (tip) {
          } catch (e) { }
        tip.setAttribute("aria-hidden", "true");
          v.style.display = "none";
        tip.style.opacity = "0";
         }
         tip.style.left = "-9999px";
      );
         tip.style.top = "-9999px";
      if (window.__subskills) window.__subskills.hideAll?.(videoBox);
       }
      const hasIdx = !!el.dataset.index;
       const skillsRoot = document.getElementById("skills");
      const weaponOn = globalWeaponEnabled;
       const i18nMap = skillsRoot
      const weaponData = getWeaponDataForIcon(el);
         ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
      const isWeaponVideo =
        : {};
         weaponOn &&
       const L = i18nMap[getLangKey()] ||
        weaponData &&
         i18nMap.pt || {
        weaponData.video &&
          cooldown: "Recarga",
        weaponData.video.trim() !== "";
           energy_gain: "Ganho de energia",
       const effectVideo = getEffectVideoForIcon(el);
           energy_cost: "Custo de energia",
       const isEffectVideo = !!(effectVideo && effectVideo.trim() !== "");
          power: "Poder",
       const effectKey = isEffectVideo
          power_pvp: "Poder PvP",
         ? getEffectVideoKey(el, effectiveVideo)
          level: "Nível",
        : "";
        };
 
       const name = el.dataset.nome || el.dataset.name || "";
      // console.log('[Skills] showVideoForIcon chamado', {
      let weaponData = null;
      //    skillName: el.dataset.nome || el.dataset.name,
       if (el.dataset.weapon) {
      //    weaponOn,
         try {
      //    isWeaponVideo,
          const parsed = JSON.parse(el.dataset.weapon);
      //    effectiveVideo: getEffectiveSkillVideoFromIcon(el)
          // Só considera weapon válido se for um objeto não vazio
      // });
          if (
       const videoKey = isWeaponVideo
            parsed &&
        ? `weapon:${getWeaponKey(el)}`
             typeof parsed === "object" &&
         : isEffectVideo
             Object.keys(parsed).length > 0
           ? effectKey
           ) {
           : el.dataset.index || "";
             weaponData = parsed;
      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);
        // Verifica se o vídeo cacheado corresponde ao vídeo atual da skill
        // (ao trocar de form, o índice é reutilizado mas o vídeo muda)
        const cachedSrc = v.querySelector("source")?.getAttribute("src") || "";
        const currentVideoFile = (el.dataset.videoFile || "").trim();
        const currentVideoURL = currentVideoFile ? normalizeFileURL(currentVideoFile) : "";
        const decodeCached = decodeURIComponent(cachedSrc.split("/").pop().split("?")[0] || "");
        const decodeTarget = decodeURIComponent((currentVideoURL || currentVideoFile).split("/").pop().split("?")[0] || "");
        const srcMismatch = decodeTarget && decodeCached && decodeCached !== decodeTarget;
        if (!srcMismatch) {
          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) { }
           }
           }
        } catch (e) {
           return;
           weaponData = null;
         }
         }
        // Vídeo não corresponde - remove do cache e do DOM
        v.style.display = "none";
        try { v.pause(); } catch (e) { }
        if (v.parentNode) v.parentNode.removeChild(v);
        videosCache.delete(el.dataset.index);
       }
       }
       const hasWeapon = !!weaponData;
       // Para form_switch, permite criação dinâmica de vídeos (transições de forma)
      const weaponEquipped = hasWeapon && globalWeaponEnabled;
       // Vídeos normais devem estar pré-carregados
       // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
       const isFormSwitch =
       let level = (el.dataset.level || "").trim();
        el.dataset.formSwitch === "true" ||
       if (weaponEquipped && weaponData) {
        el.getAttribute("data-form-switch") === "true";
         // Verifica se weapon tem level definido (pode ser número ou string)
      let v = null;
         const weaponLevel = weaponData.level;
       if (isWeaponVideo) {
         if (
         const weaponKeyFull = `weapon:${getWeaponKey(el)}`;
          weaponLevel !== undefined &&
         v = videosCache.get(weaponKeyFull);
           weaponLevel !== null &&
         if (!v && isSubskill && parentIdx && subName) {
           weaponLevel !== ""
           // 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
           level = weaponLevel.toString().trim();
          // O subName agora já está no formato correto (sem nome da skill principal)
        }
 
      }
          // Tenta buscar diretamente com o subName
      const lang = getLangKey();
          let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
      const baseDescPack = {
           v = subskillVideosCache.get(subWeaponKey);
        pt: el.dataset.descPt || "",
 
        en: el.dataset.descEn || "",
          // Se não encontrou, tenta buscar recursivamente
        es: el.dataset.descEs || "",
          if (!v) {
        pl: el.dataset.descPl || "",
            const basePattern = `sub:${parentIdx}:`;
      };
            const weaponSuffix = ":weapon";
      const baseDesc =
            const searchName = subName.split(":").pop();
        baseDescPack[lang] ||
            for (const [key, video] of subskillVideosCache.entries()) {
        baseDescPack.pt ||
              if (key.startsWith(basePattern) && key.endsWith(weaponSuffix)) {
        baseDescPack.en ||
                const pathInKey = key.substring(
        baseDescPack.es ||
                  basePattern.length,
        baseDescPack.pl ||
                  key.length - weaponSuffix.length
        el.dataset.desc ||
                );
        "";
                // Tenta match exato, ou se termina com o nome da skill
      // Aceita tanto desc_i18n quanto desc para compatibilidade
                if (
      let weaponDescPack = {};
                  pathInKey === subName ||
      if (weaponData) {
                  pathInKey.endsWith(`:${searchName}`) ||
        if (weaponData.desc_i18n) {
                  pathInKey === searchName
          weaponDescPack = weaponData.desc_i18n;
                ) {
        } else if (weaponData.desc) {
                  v = video;
          weaponDescPack = weaponData.desc;
                  break;
        } else {
                }
          weaponDescPack = {
              }
             pt: weaponData.descPt || "",
             }
            en: weaponData.descEn || "",
           }
            es: weaponData.descEs || "",
            pl: weaponData.descPl || "",
           };
         }
         }
      }
         if (!v) {
      const weaponDesc =
          // Tenta buscar pelo padrão antigo também
         weaponDescPack[lang] ||
          v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`);
        weaponDescPack.pt ||
        }
        weaponDescPack.en ||
       } else if (isEffectVideo && effectKey) {
        weaponDescPack.es ||
        v = videosCache.get(effectKey);
        weaponDescPack.pl ||
       } else {
        "";
        if (isSubskill && parentIdx && subName) {
       const chosenDesc = weaponEquipped && weaponDesc ? weaponDesc : baseDesc;
          // Busca vídeo de subskill no cache correto
      const descHtml = chosenDesc.replace(/'''(.*?)'''/g, "<b>$1</b>");
          // O cache usa o formato: sub:${parentIdx}:${path} onde path NÃO inclui o nome da skill principal
       let attrsHTML = "";
          // O subName agora já está no formato correto (sem nome da skill principal)
      if (weaponEquipped && weaponData) {
 
        // Faz merge: usa atributos da skill base e substitui apenas os que existem no weapon
          // Tenta buscar diretamente com o subName
        // Parse dos atributos da skill base (formato: "pve, pvp, energy, cooldown" ou "pve, pvp, energy, cooldown")
           let subKey = `sub:${parentIdx}:${subName}`;
        const baseAttrsStr = el.dataset.atr || "";
           v = subskillVideosCache.get(subKey);
        // 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)
          // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
        const wPve =
           // Isso é útil caso haja alguma diferença no formato do caminho
           weaponData.powerpve !== undefined &&
           if (!v) {
           weaponData.powerpve !== null &&
             const basePattern = `sub:${parentIdx}:`;
          weaponData.powerpve !== ""
            const searchName = subName.split(":").pop(); // Último segmento do caminho
            ? weaponData.powerpve.toString().trim()
             // Tenta também buscar apenas pelo último segmento (útil para sub-subskills)
             : basePve;
             const lastSegmentKey = `sub:${parentIdx}:${searchName}`;
        const wPvp =
            v = subskillVideosCache.get(lastSegmentKey);
          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
            // Se ainda não encontrou, faz busca mais ampla
        const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(",");
            if (!v) {
        attrsHTML = renderAttributes(mergedAttrs);
              for (const [key, video] of subskillVideosCache.entries()) {
      } else {
                if (key.startsWith(basePattern)) {
        attrsHTML = el.dataset.atr
                  const pathInKey = key.substring(basePattern.length);
          ? renderAttributes(el.dataset.atr)
                  // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
          : el.dataset.subattrs
                  if (
          ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L)
                    pathInKey === subName ||
          : "";
                    pathInKey.endsWith(`:${searchName}`) ||
      }
                    pathInKey === searchName ||
      let flagsHTML = "";
                    (subName.includes(":") && pathInKey.includes(subName)) ||
      // Debug: verifica se é uma skill do Urouge (verifica pela URL da página ou pelo contexto)
                    (subName.includes(":") &&
      const isUrougePage =
                      pathInKey.endsWith(
        window.location.href &&
                        subName.split(":").slice(-2).join(":")
        window.location.href.toLowerCase().includes("urouge");
                      ))
      if (el.dataset.flags) {
                  ) {
        try {
                    v = video;
          const flags = JSON.parse(el.dataset.flags);
                    break;
          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) {
         } else {
           console.warn(
           v = videosCache.get(el.dataset.index);
            "[Skills] Erro ao processar flags:",
           if (!v) {
            e,
             v = nestedVideoElByIcon.get(el);
            "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 = `<div class="skill-title"><h3>${name}</h3></div>${
      // Se vídeo não foi encontrado no cache, verifica se falhou antes
           level
       if (!v) {
             ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>`
         // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
            : ""
        if (failedVideosCache.has(videoURL)) {
        }${attrsHTML}<div class="desc">${descHtml}</div>`;
          videoBox.style.display = "none";
      }
          return;
      if (hasWeapon) {
        }
        applyWeaponBadge(el, weaponData, weaponEquipped);
        // Para effect, cria vídeo dinamicamente (vídeo alternativo por tempo)
      }
        if (isEffectVideo && videoURL) {
      if (videoBox) {
           const baseIndex = el.dataset.index || "";
        const oldFlags = videoBox.querySelector(".skill-flags");
          const effectCacheKey =
        if (oldFlags) oldFlags.remove();
             effectKey || `effect:${effectiveVideo || videoURL}`;
        if (flagsHTML) {
          v = videosCache.get(effectCacheKey);
           videoBox.insertAdjacentHTML("beforeend", flagsHTML);
          if (!v) {
           applyFlagTooltips(videoBox);
            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;
            }
          }
        }
        // Vídeos de form skills ou outros que não estão no cache - cria dinamicamente
        else if (videoURL) {
          const idx = el.dataset.index || "";
          v = createVideoElement(videoURL, { index: idx });
          if (v) {
            videoBox.appendChild(v);
            if (idx) videosCache.set(idx, v);
            v.load();
          } else {
            videoBox.style.display = "none";
            return;
          }
        } else {
           videoBox.style.display = "none";
           return;
         }
         }
       }
       }
       const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
       videoBox.style.display = "block";
       currIcons.forEach((i) => i.classList.remove("active"));
       v.style.display = "block";
      const subsRaw = el.dataset.subs || el.getAttribute("data-subs");
       try {
       const isFormSwitch =
         v.currentTime = 0;
         el.dataset.formSwitch === "true" ||
      } catch (e) { }
        el.getAttribute("data-form-switch") === "true";
       const suppress = document.body.dataset.suppressSkillPlay === "1";
       const isSwap =
       if (!suppress) {
        el.dataset.swap === "true" || el.getAttribute("data-swap") === "true";
         v.play().catch(() => { });
 
      } else {
      // Se for skill de swap, troca personagem (não marca como ativo, não processa como back)
         try {
       if (isSwap && !isFormSwitch) {
          v.pause();
         handleSwapCharacter(el);
         } catch (e) { }
         // Não marca como ativo (similar ao form_switch)
         return;
       }
       }
 
    }
       // Não marca como ativo se for form_switch (Change Form)
    function activateSkill(el, options = {}) {
       if (!isFormSwitch) {
       const { openSubs = true } = options;
         el.classList.add("active");
      const tip = document.querySelector(".skill-tooltip");
         if (!autoplay && loadedVideos > 0) autoplay = true;
       if (tip) {
         window.__lastActiveSkillIcon = el;
         tip.setAttribute("aria-hidden", "true");
         // Lógica de vídeo: usa função centralizada que já considera weapon
         tip.style.opacity = "0";
        showVideoForIcon(el);
         tip.style.left = "-9999px";
         tip.style.top = "-9999px";
       }
       }
       const isBack =
       const skillsRoot = document.getElementById("skills");
         el.dataset.back === "true" ||
      const i18nMap = skillsRoot
         el.getAttribute("data-back") === "true" ||
         ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
         el.dataset.back === "yes" ||
        : {};
         el.getAttribute("data-back") === "yes" ||
      const L = i18nMap[getLangKey()] ||
        el.dataset.back === "1" ||
         i18nMap.pt || {
        el.getAttribute("data-back") === "1";
        cooldown: "Recarga",
 
        energy_gain: "Ganho de energia",
       // Se for form_switch, alterna forma (não processa como back)
        energy_cost: "Custo de energia",
       if (isFormSwitch) {
         power: "Poder",
        // Atualiza o data-video-file do ícone com o vídeo correto da transição baseado na forma atual
         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 {
         try {
           const formsJSON = skillsRoot.dataset.forms || "{}";
           const parsed = JSON.parse(el.dataset.weapon);
          if (formsJSON && formsJSON !== "{}") {
          // Só considera weapon válido se for um objeto não vazio
            const tempFormsData = JSON.parse(formsJSON);
          if (
            const formNames = Object.keys(tempFormsData);
            parsed &&
 
            typeof parsed === "object" &&
            // Busca vídeo de transição na skill Change Form
            Object.keys(parsed).length > 0
            const formVideosRaw =
          ) {
              el.dataset.formVideos || el.getAttribute("data-form-videos");
            weaponData = parsed;
            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) {
         } catch (e) {
           console.error("[Forms] Erro ao processar vídeo de transição:", e);
           weaponData = null;
         }
         }
 
      }
         // Usa o sistema normal de vídeo (mesmo que skills normais)
      const hasWeapon = !!weaponData;
         showVideoForIcon(el);
      const weaponEquipped = hasWeapon && globalWeaponEnabled;
         switchForm();
      // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
         return;
      let level = (el.dataset.level || "").trim();
       }
      if (weaponEquipped && weaponData) {
 
        // Verifica se weapon tem level definido (pode ser número ou string)
       if (isBack && !isFormSwitch && barStack.length) {
        const weaponLevel = weaponData.level;
         const prev = barStack.pop();
        if (
         // Restaura currentForm se estava salvo no snapshot
          weaponLevel !== undefined &&
         if (prev.currentForm !== undefined) {
          weaponLevel !== null &&
           currentForm = prev.currentForm;
          weaponLevel !== ""
         }
        ) {
         renderBarFromItems(prev);
          level = weaponLevel.toString().trim();
         const btn = document.querySelector(".skills-back-wrapper");
        }
         if (btn) btn.style.display = barStack.length ? "block" : "none";
      }
         return;
      const lang = getLangKey();
       }
      const baseDescPack = {
       if (openSubs && subsRaw && subsRaw.trim() !== "") {
        pt: el.dataset.descPt || "",
         if (barStack.length && barStack[barStack.length - 1].parentIcon === el)
        en: el.dataset.descEn || "",
           return;
        es: el.dataset.descEs || "",
         try {
        pl: el.dataset.descPl || "",
           const subs = JSON.parse(subsRaw);
      };
           pushSubBarFrom(subs, el);
      const baseDesc =
         } catch {}
        baseDescPack[lang] ||
       }
        baseDescPack.pt ||
     }
        baseDescPack.en ||
     function wireClicksForCurrentBar() {
        baseDescPack.es ||
       const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
        baseDescPack.pl ||
       currIcons.forEach((el) => {
        el.dataset.desc ||
         if (
        "";
           el.dataset.weaponToggle === "1" ||
      // Aceita tanto desc_i18n quanto desc para compatibilidade
           el.classList.contains("weapon-bar-toggle")
      let weaponDescPack = {};
         )
      if (weaponData) {
           return;
        if (weaponData.desc_i18n) {
         if (el.dataset.wired) return;
          weaponDescPack = weaponData.desc_i18n;
         el.dataset.wired = "1";
        } else if (weaponData.desc) {
         const label = el.dataset.nome || el.dataset.name || "";
          weaponDescPack = weaponData.desc;
         el.setAttribute("aria-label", label);
        } else {
         if (el.hasAttribute("title")) el.removeAttribute("title");
          weaponDescPack = {
         const img = el.querySelector("img");
            pt: weaponData.descPt || "",
         if (img) {
            en: weaponData.descEn || "",
           img.setAttribute("alt", "");
            es: weaponData.descEs || "",
           if (img.hasAttribute("title")) img.removeAttribute("title");
            pl: weaponData.descPl || "",
         }
          };
         el.addEventListener("click", () => {
        }
           activateSkill(el, {
      }
             openSubs: true,
      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,
              });
            }
          } 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 = `<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);
      }
      const vb = getVideoBox();
      if (vb) {
        const oldFlags = vb.querySelector(".skill-flags");
        if (oldFlags) oldFlags.remove();
        if (flagsHTML) {
          vb.insertAdjacentHTML("beforeend", flagsHTML);
          applyFlagTooltips(vb);
        }
      }
      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) {
        try {
          const formsJSON = skillsRoot.dataset.forms || "{}";
          if (formsJSON && formsJSON !== "{}") {
            if (Object.keys(formsData).length === 0) {
              formsData = JSON.parse(formsJSON);
            }
          }
 
          // Inicializa currentForm se necessário
          if (currentForm === null) {
            currentForm = detectCurrentForm();
            const formNames = Object.keys(formsData);
            if (!currentForm && formNames.length > 0) {
              currentForm = formNames[0];
            }
          }
 
          // Calcula a próxima forma ANTES de trocar
          const nextForm = getNextFormName();
 
          // Vídeo de transição: usa o vídeo da PRÓXIMA forma
          const formVideosRaw =
            el.dataset.formVideos || el.getAttribute("data-form-videos");
          if (formVideosRaw && nextForm) {
            const videos = JSON.parse(formVideosRaw);
            const transitionVideo = videos[nextForm] || "";
            if (transitionVideo && transitionVideo.trim() !== "") {
              el.dataset.videoFile = transitionVideo;
            }
          }
        } catch (e) {
          console.error("[Forms] Erro ao processar transição:", e);
        }
 
         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
       wireTooltipsForNewIcons();
       return { items, currentForm: currentForm };
     }
     }
     function animateIconsBarEntrance() {
     function ensureBackButton() {
       Array.from(iconsBar.children).forEach((c, i) => {
       const rail = iconsBar.closest(".top-rail.skills");
         c.style.opacity = "0";
      if (!rail) return null;
         c.style.transform = "translateY(6px)";
      let wrap = rail.parentElement;
         requestAnimationFrame(() => {
      if (
          setTimeout(() => {
        !wrap ||
            c.style.transition = "opacity .18s ease, transform .18s ease";
        !wrap.classList ||
            c.style.opacity = "1";
        !wrap.classList.contains("skills-rail-wrap")
            c.style.transform = "translateY(0)";
      ) {
           }, i * 24);
        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";
    function snapshotCurrentBarItemsFromDOM() {
      wrap.classList.toggle("has-sub-bar", barStack.length > 0);
       const items = Array.from(iconsBar.querySelectorAll(".skill-icon"))
      const btnInner = backWrap.querySelector(".skills-back");
        .filter((el) => el.dataset.weaponToggle !== "1")
 
        .map((el) => {
      // Calcula a posição do botão baseado na posição real do top-rail
          const img = el.querySelector("img");
      function updateBackButtonPosition() {
          const iconURL = img ? img.src : "";
        if (!rail || !backWrap) return;
          const subsRaw = el.dataset.subs || el.getAttribute("data-subs") || "";
        const railRect = rail.getBoundingClientRect();
          let subs = null;
        const wrapRect = wrap.getBoundingClientRect();
          try {
        // Calcula a posição relativa do rail dentro do wrap
            subs = subsRaw ? JSON.parse(subsRaw) : null;
        const railLeft = railRect.left - wrapRect.left;
          } catch {
        // Posiciona o wrapper na borda esquerda do rail
            subs = null;
        // O botão interno usa translateX(-97%) para ficar atrás da barra
          }
        backWrap.style.left = railLeft + "px";
          const subattrsRaw = el.dataset.subattrs || "";
      }
          let flags = null;
 
          if (el.dataset.flags) {
      // Atualiza a posição quando necessário
            try {
      if (backWrap.style.display !== "none") {
              flags = JSON.parse(el.dataset.flags);
        // Usa requestAnimationFrame para garantir que o DOM foi atualizado
            } catch (e) {}
        requestAnimationFrame(() => {
          }
          updateBackButtonPosition();
          let weapon = null;
        });
          if (el.dataset.weapon) {
 
            try {
        // Recalcula em resize e scroll
              weapon = JSON.parse(el.dataset.weapon);
        if (!backWrap.dataset.positionWired) {
            } catch (e) {}
           backWrap.dataset.positionWired = "1";
          }
          const updateOnResize = () => {
          // Preserva data-form-videos para poder restaurar depois
             if (backWrap.style.display !== "none") {
          const formVideos =
               updateBackButtonPosition();
            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,
            nested: el.dataset.nested || "",
            subName: el.dataset.subName || "",
            parentIndex: el.dataset.parentIndex || "",
            formSwitch:
              el.dataset.formSwitch ||
               el.getAttribute("data-form-switch") ||
              "",
             formVideos: formVideos,
           };
           };
        });
          window.addEventListener("resize", updateOnResize);
       // Retorna objeto com items e currentForm para poder restaurar depois
          const observer = new ResizeObserver(() => {
       return { items, currentForm: currentForm };
            if (backWrap.style.display !== "none") {
              updateBackButtonPosition();
            }
          });
          if (rail) observer.observe(rail);
          observer.observe(wrap);
        }
       }
 
       return btnInner;
     }
     }
     function ensureBackButton() {
     function renderBarFromItems(itemsOrSnapshot) {
       const rail = iconsBar.closest(".top-rail.skills");
       // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
      if (!rail) return null;
       let items, savedCurrentForm;
       let wrap = rail.parentElement;
       if (Array.isArray(itemsOrSnapshot)) {
       if (
         items = itemsOrSnapshot;
        !wrap ||
         savedCurrentForm = null;
        !wrap.classList ||
      } else if (itemsOrSnapshot && itemsOrSnapshot.items) {
        !wrap.classList.contains("skills-rail-wrap")
         items = itemsOrSnapshot.items;
      ) {
         savedCurrentForm = itemsOrSnapshot.currentForm;
         const parentNode = rail.parentNode;
      } else {
         const newWrap = document.createElement("div");
         items = [];
         newWrap.className = "skills-rail-wrap";
         savedCurrentForm = null;
         parentNode.insertBefore(newWrap, rail);
         newWrap.appendChild(rail);
         wrap = newWrap;
       }
       }
       let backWrap = wrap.querySelector(".skills-back-wrapper");
       // Restaura currentForm se estava salvo
       if (!backWrap) {
       if (savedCurrentForm !== undefined && savedCurrentForm !== null) {
        backWrap = document.createElement("div");
         currentForm = savedCurrentForm;
        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";
       const tip = document.querySelector(".skill-tooltip");
      wrap.classList.toggle("has-sub-bar", barStack.length > 0);
       if (tip) {
       const btnInner = backWrap.querySelector(".skills-back");
         tip.setAttribute("aria-hidden", "true");
 
         tip.style.opacity = "0";
       // Calcula a posição do botão baseado na posição real do top-rail
         tip.style.left = "-9999px";
      function updateBackButtonPosition() {
         tip.style.top = "-9999px";
         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";
       }
       }
 
       iconsBar.innerHTML = "";
       // Atualiza a posição quando necessário
       items.forEach((it, idx) => {
       if (backWrap.style.display !== "none") {
        const node = document.createElement("div");
         // Usa requestAnimationFrame para garantir que o DOM foi atualizado
        node.className = "skill-icon";
         requestAnimationFrame(() => {
        node.dataset.nome = it.name || "";
          updateBackButtonPosition();
        if (it.index) node.dataset.index = it.index;
         });
        if (it.level) node.dataset.level = it.level;
 
        if (it.desc) node.dataset.desc = it.desc;
         // Recalcula em resize e scroll
         if (it.descPt) node.dataset.descPt = it.descPt;
         if (!backWrap.dataset.positionWired) {
         if (it.descEn) node.dataset.descEn = it.descEn;
           backWrap.dataset.positionWired = "1";
        if (it.descEs) node.dataset.descEs = it.descEs;
           const updateOnResize = () => {
        if (it.descPl) node.dataset.descPl = it.descPl;
            if (backWrap.style.display !== "none") {
         if (it.attrs) node.dataset.atr = it.attrs;
              updateBackButtonPosition();
        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;
          window.addEventListener("resize", updateOnResize);
        if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
          const observer = new ResizeObserver(() => {
        if (
            if (backWrap.style.display !== "none") {
           it.weapon &&
              updateBackButtonPosition();
          typeof it.weapon === "object" &&
            }
           Object.keys(it.weapon).length > 0
          });
        ) {
          if (rail) observer.observe(rail);
          node.dataset.weapon = JSON.stringify(it.weapon);
           observer.observe(wrap);
        }
        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 = "";
       return btnInner;
        img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : "");
    }
        img.decoding = "async";
    function renderBarFromItems(itemsOrSnapshot) {
        img.loading = "lazy";
       // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
        img.width = 50;
       let items, savedCurrentForm;
        img.height = 50;
       if (Array.isArray(itemsOrSnapshot)) {
        node.appendChild(img);
         items = itemsOrSnapshot;
        iconsBar.appendChild(node);
         savedCurrentForm = null;
       });
      } else if (itemsOrSnapshot && itemsOrSnapshot.items) {
       animateIconsBarEntrance();
        items = itemsOrSnapshot.items;
      wireClicksForCurrentBar();
        savedCurrentForm = itemsOrSnapshot.currentForm;
       // Remove qualquer toggle antigo que possa aparecer
      } else {
      const oldToggle = iconsBar.querySelector(".weapon-bar-toggle");
        items = [];
      if (oldToggle) oldToggle.remove();
        savedCurrentForm = null;
      // Reaplica classes de weapon após renderizar barra
      }
      reapplyWeaponClassesToBar();
      // Restaura currentForm se estava salvo
       const b = ensureBackButton();
      if (savedCurrentForm !== undefined && savedCurrentForm !== null) {
       if (b) b.classList.add("peek");
        currentForm = savedCurrentForm;
      // 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");
       const tip = document.querySelector(".skill-tooltip");
       if (tip) {
       if (tip) {
Linha 3 223: Linha 4 096:
         tip.style.top = "-9999px";
         tip.style.top = "-9999px";
       }
       }
       iconsBar.innerHTML = "";
       const parentNameSnapshot = parentIconEl
       items.forEach((it, idx) => {
        ? parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
        const node = document.createElement("div");
        : "";
         node.className = "skill-icon";
       // Para subskills, usa parentIndex; para skills principais, usa index
        node.dataset.nome = it.name || "";
      const parentIndexSnapshot = parentIconEl
        if (it.index) node.dataset.index = it.index;
         ? parentIconEl.dataset.nested === "1"
        if (it.level) node.dataset.level = it.level;
          ? parentIconEl.dataset.parentIndex || ""
         if (it.desc) node.dataset.desc = it.desc;
          : parentIconEl.dataset.index || ""
        if (it.descPt) node.dataset.descPt = it.descPt;
        : "";
        if (it.descEn) node.dataset.descEn = it.descEn;
      const snapshot = snapshotCurrentBarItemsFromDOM();
        if (it.descEs) node.dataset.descEs = it.descEs;
      barStack.push({
        if (it.descPl) node.dataset.descPl = it.descPl;
        items: snapshot.items,
         if (it.attrs) node.dataset.atr = it.attrs;
        currentForm: snapshot.currentForm,
         if (it.video) node.dataset.video = it.video;
        parentIcon: parentIconEl,
        if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
        parentName: parentNameSnapshot,
        if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr;
         parentIndex: parentIndexSnapshot,
        if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
      });
        if (
      ensureBackButton();
           it.weapon &&
      const langKey = getLangKey();
           typeof it.weapon === "object" &&
      let cacheKey = null;
          Object.keys(it.weapon).length > 0
      if (parentIconEl) {
        ) {
         cacheKey = parentIconEl.dataset.subCacheKey || null;
          node.dataset.weapon = JSON.stringify(it.weapon);
         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;
         }
         }
        // Restaura informações de subskill importantes para busca de vídeos
      }
        if (it.nested) node.dataset.nested = it.nested;
      if (cacheKey) {
         if (it.subName) node.dataset.subName = it.subName;
         const cached = subBarTemplateCache.get(cacheKey);
        if (it.parentIndex) node.dataset.parentIndex = it.parentIndex;
         if (cached && cached.lang === langKey) {
         if (!it.index && !it.nested) node.dataset.nested = "1";
          iconsBar.innerHTML = "";
        // Restaura formSwitch (Change Form)
          const clone = cached.template.cloneNode(true);
        if (it.formSwitch) {
          iconsBar.appendChild(clone);
           node.dataset.formSwitch = it.formSwitch;
          animateIconsBarEntrance();
           node.setAttribute("data-form-switch", it.formSwitch);
           wireClicksForCurrentBar();
        }
           // Remove qualquer toggle antigo que possa aparecer
        // Restaura formVideos (vídeos de transição de forma)
          const oldToggle2 = iconsBar.querySelector(".weapon-bar-toggle");
        if (it.formVideos) {
          if (oldToggle2) oldToggle2.remove();
           node.dataset.formVideos = it.formVideos;
          // Reaplica classes de weapon após renderizar do cache
           node.setAttribute("data-form-videos", it.formVideos);
          reapplyWeaponClassesToBar();
           const cachedBtn = ensureBackButton();
           if (cachedBtn) cachedBtn.classList.add("peek");
          return;
         }
         }
        const img = document.createElement("img");
      }
        img.alt = "";
      const skillsRoot = document.getElementById("skills");
         img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : "");
      const i18nMap = skillsRoot
         img.decoding = "async";
         ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
         img.loading = "lazy";
        : {};
         img.width = 50;
      const L = i18nMap[getLangKey()] ||
         img.height = 50;
         i18nMap.pt || {
         node.appendChild(img);
        cooldown: "Recarga",
         iconsBar.appendChild(node);
         energy_gain: "Ganho de energia",
       });
         energy_cost: "Custo de energia",
       animateIconsBarEntrance();
         power: "Poder",
       wireClicksForCurrentBar();
         power_pvp: "Poder PvP",
      // Remove qualquer toggle antigo que possa aparecer
         level: "Nível",
      const oldToggle = iconsBar.querySelector(".weapon-bar-toggle");
       };
      if (oldToggle) oldToggle.remove();
       const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
      // Reaplica classes de weapon após renderizar barra
       const items = (hydratedSubs || [])
      reapplyWeaponClassesToBar();
        .filter((s) => {
      const b = ensureBackButton();
          // Filtra só se não tem nada útil
      if (b) b.classList.add("peek");
          const hasName = (s.name || s.n || "").trim() !== "";
      // Atualiza a posição do botão back após a barra ser renderizada
          const hasIcon = (s.icon || "").trim() !== "";
      requestAnimationFrame(() => {
          const hasRef = (s.refS || s.refM || "").toString().trim() !== "";
        const backWrap = document.querySelector(".skills-back-wrapper");
          return hasName || hasIcon || hasRef;
        if (backWrap && backWrap.style.display !== "none") {
        })
           const rail = iconsBar.closest(".top-rail.skills");
        .map((s) => {
           const wrap = rail ? rail.parentElement : null;
          const name = (s.name || s.n || "").trim();
           if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
           const desc = chooseDescFrom(s).replace(/'''(.*?)'''/g, "<b>$1</b>");
            const railRect = rail.getBoundingClientRect();
           const attrsHTML = renderSubAttributesFromObj(s, L);
            const wrapRect = wrap.getBoundingClientRect();
           return {
             const railLeft = railRect.left - wrapRect.left;
            name,
             // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
            level: (s.level || "").toString().trim(),
             backWrap.style.left = railLeft + "px";
             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) || "",
    function pushSubBarFrom(subs, parentIconEl) {
            attrs: "",
      const tip = document.querySelector(".skill-tooltip");
            icon: s.icon || "",
      if (tip) {
            iconURL: s.icon ? filePathURL(s.icon) : "",
        tip.setAttribute("aria-hidden", "true");
            video: s.video ? filePathURL(s.video) : "",
        tip.style.opacity = "0";
            subs: Array.isArray(s.subs) ? s.subs : null,
        tip.style.left = "-9999px";
            subattrs: s,
        tip.style.top = "-9999px";
            flags: Array.isArray(s.flags) ? s.flags : null,
      }
            back:
      const parentNameSnapshot = parentIconEl
              s.back === true ||
        ? parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
                s.back === "true" ||
        : "";
                s.back === "yes" ||
      // Para subskills, usa parentIndex; para skills principais, usa index
                s.back === "1"
      const parentIndexSnapshot = parentIconEl
                ? "true"
        ? parentIconEl.dataset.nested === "1"
                : null,
          ? parentIconEl.dataset.parentIndex || ""
            weapon: s.weapon || null,
          : parentIconEl.dataset.index || ""
          };
         : "";
         });
       const snapshot = snapshotCurrentBarItemsFromDOM();
       const fragment = document.createDocumentFragment();
       barStack.push({
       items.forEach((it, iIdx) => {
         items: snapshot.items,
         const node = document.createElement("div");
         currentForm: snapshot.currentForm,
         node.className = "skill-icon";
         parentIcon: parentIconEl,
         node.dataset.nested = "1";
         parentName: parentNameSnapshot,
         node.dataset.nome = it.name || "";
         parentIndex: parentIndexSnapshot,
         node.dataset.parentIndex = parentIndexSnapshot;
      });
        // Constrói o caminho completo para sub-subskills
      ensureBackButton();
        // IMPORTANTE: O cache NÃO inclui o nome da skill principal no caminho
      const langKey = getLangKey();
        // Formato do cache: "SubskillName" ou "SubskillName:SubSubskillName" (sem o nome da skill principal)
      let cacheKey = null;
        let fullPath = it.name || "";
      if (parentIconEl) {
        if (parentIconEl) {
        cacheKey = parentIconEl.dataset.subCacheKey || null;
          // Se o pai é uma subskill (tem nested), adiciona o nome do pai ao caminho
        if (!cacheKey) {
          if (parentIconEl.dataset.nested === "1") {
          if (parentIconEl.dataset.index) {
            // Se o pai tem subName, usa ele (já está no formato correto, sem nome da skill principal)
            cacheKey = `idx:${parentIconEl.dataset.index}`;
            if (
          } else {
              parentIconEl.dataset.subName &&
            const slug = slugify(
              parentIconEl.dataset.subName.trim() !== ""
              parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
            ) {
            );
              fullPath = `${parentIconEl.dataset.subName}:${it.name || ""}`;
            if (slug) cacheKey = `slug:${slug}`;
            } 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 || ""}`;
            }
           }
           }
           if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey;
           // 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;
      if (cacheKey) {
        const subSlug = slugify(it.name || "");
         const cached = subBarTemplateCache.get(cacheKey);
        if (subSlug) node.dataset.slug = subSlug;
         if (cached && cached.lang === langKey) {
         if (it.level) node.dataset.level = it.level;
          iconsBar.innerHTML = "";
        if (it.desc) node.dataset.desc = it.desc;
          const clone = cached.template.cloneNode(true);
         if (it.descPt) node.dataset.descPt = it.descPt;
          iconsBar.appendChild(clone);
        if (it.descEn) node.dataset.descEn = it.descEn;
          animateIconsBarEntrance();
        if (it.descEs) node.dataset.descEs = it.descEs;
          wireClicksForCurrentBar();
        if (it.descPl) node.dataset.descPl = it.descPl;
          // Remove qualquer toggle antigo que possa aparecer
        if (it.video) node.dataset.video = it.video;
          const oldToggle2 = iconsBar.querySelector(".weapon-bar-toggle");
        if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
          if (oldToggle2) oldToggle2.remove();
        if (it.subattrs) node.dataset.subattrs = JSON.stringify(it.subattrs);
           // Reaplica classes de weapon após renderizar do cache
        if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
           reapplyWeaponClassesToBar();
        if (it.back) node.dataset.back = it.back;
           const cachedBtn = ensureBackButton();
        if (
           if (cachedBtn) cachedBtn.classList.add("peek");
          it.weapon &&
           return;
           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");
      const skillsRoot = document.getElementById("skills");
         img.alt = "";
      const i18nMap = skillsRoot
         img.src = it.iconURL;
         ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
         img.decoding = "async";
         : {};
        img.loading = "lazy";
      const L = i18nMap[getLangKey()] ||
        img.width = 50;
         i18nMap.pt || {
        img.height = 50;
          cooldown: "Recarga",
        node.appendChild(img);
          energy_gain: "Ganho de energia",
        fragment.appendChild(node);
          energy_cost: "Custo de energia",
      });
          power: "Poder",
       const templateClone = fragment.cloneNode(true);
          power_pvp: "Poder PvP",
       iconsBar.innerHTML = "";
          level: "Nível",
      iconsBar.appendChild(fragment);
        };
      animateIconsBarEntrance();
       const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
      wireClicksForCurrentBar();
       const items = (hydratedSubs || [])
      // Remove qualquer toggle antigo que possa aparecer
        .filter((s) => {
      const oldToggle3 = iconsBar.querySelector(".weapon-bar-toggle");
          // Filtra só se não tem nada útil
      if (oldToggle3) oldToggle3.remove();
          const hasName = (s.name || s.n || "").trim() !== "";
      // Reaplica classes de weapon após renderizar subskills
          const hasIcon = (s.icon || "").trim() !== "" && s.icon !== "Nada.png";
      reapplyWeaponClassesToBar();
           const hasRef = (s.refS || s.refM || "").toString().trim() !== "";
      const b2 = ensureBackButton();
           return hasName || hasIcon || hasRef;
      if (b2) b2.classList.add("peek");
         })
      // Atualiza a posição do botão back após subskills serem renderizadas
        .map((s) => {
      requestAnimationFrame(() => {
          const name = (s.name || s.n || "").trim();
        const backWrap = document.querySelector(".skills-back-wrapper");
          const desc = chooseDescFrom(s).replace(/'''(.*?)'''/g, "<b>$1</b>");
        if (backWrap && backWrap.style.display !== "none") {
          const attrsHTML = renderSubAttributesFromObj(s, L);
          const rail = iconsBar.closest(".top-rail.skills");
          return {
           const wrap = rail ? rail.parentElement : null;
            name,
          if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
            level: (s.level || "").toString().trim(),
            const railRect = rail.getBoundingClientRect();
            desc,
            const wrapRect = wrap.getBoundingClientRect();
            descPt: s.descPt || (s.desc_i18n && s.desc_i18n.pt) || "",
            const railLeft = railRect.left - wrapRect.left;
            descEn: s.descEn || (s.desc_i18n && s.desc_i18n.en) || "",
            // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
            descEs: s.descEs || (s.desc_i18n && s.desc_i18n.es) || "",
            backWrap.style.left = railLeft + "px";
            descPl: s.descPl || (s.desc_i18n && s.desc_i18n.pl) || "",
           }
            attrs: "",
        }
            icon: s.icon || "",
      });
            iconURL:
      if (cacheKey) {
              s.icon &&
        subBarTemplateCache.set(cacheKey, {
              s.icon !== "Nada.png" &&
          template: templateClone,
              s.icon.toLowerCase() !== "nada.png"
          lang: langKey,
                ? filePathURL(s.icon)
         });
                : "",
      }
             video: s.video ? filePathURL(s.video) : "",
    }
             subs: Array.isArray(s.subs) ? s.subs : null,
    window.addEventListener("gla:langChanged", () => {
             subattrs: s,
      subBarTemplateCache.clear();
             flags: Array.isArray(s.flags) ? s.flags : null,
      const skillsRoot = document.getElementById("skills");
             back:
      const i18nMap = skillsRoot
              s.back === true ||
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
              s.back === "true" ||
        : {};
              s.back === "yes" ||
      const lang = getLangKey();
              s.back === "1"
      Array.from(iconsBar.querySelectorAll(".skill-icon")).forEach((icon) => {
                ? "true"
        const pack = {
                : null,
          pt: icon.dataset.descPt || "",
             weapon: s.weapon || null,
          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;
         });
         });
      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);
       if (descBox) {
      iconsBar.innerHTML = "";
        applyFlagTooltips(descBox);
      iconsBar.appendChild(fragment);
       }
       animateIconsBarEntrance();
       const activeIcon = window.__lastActiveSkillIcon;
      wireClicksForCurrentBar();
       if (activeIcon && activeIcon.dataset.weapon) {
      // Remove qualquer toggle antigo que possa aparecer
         activateSkill(activeIcon, {
       const oldToggle3 = iconsBar.querySelector(".weapon-bar-toggle");
           openSubs: false,
       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", () => {
     wireClicksForCurrentBar();
      subBarTemplateCache.clear();
 
      const skillsRoot = document.getElementById("skills");
     // Inicializa o sistema de swap de personagens (genérico)
      const i18nMap = skillsRoot
     // Aguarda um pouco para garantir que todos os ícones foram renderizados
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
     setTimeout(() => {
        : {};
       initializeActiveCharacter();
      const lang = getLangKey();
     }, 100);
      Array.from(iconsBar.querySelectorAll(".skill-icon")).forEach((icon) => {
 
        const pack = {
     const b0 = ensureBackButton();
          pt: icon.dataset.descPt || "",
     if (b0) {
          en: icon.dataset.descEn || "",
       b0.classList.add("peek");
          es: icon.dataset.descEs || "",
       b0.style.alignSelf = "stretch";
          pl: icon.dataset.descPl || "",
     }
        };
 
        const chosen = (
     // Move inicialização de tooltip para requestIdleCallback (não crítico)
          pack[lang] ||
     if ("requestIdleCallback" in window) {
          pack.pt ||
       requestIdleCallback(
          pack.en ||
         () => {
          pack.es ||
           (function initSkillTooltip() {
          pack.pl ||
             if (document.querySelector(".skill-tooltip")) return;
          icon.dataset.desc ||
             const tip = document.createElement("div");
          ""
             tip.className = "skill-tooltip";
        ).trim();
             tip.setAttribute("role", "tooltip");
        if (chosen) icon.dataset.desc = chosen;
             tip.setAttribute("aria-hidden", "true");
      });
             document.body.appendChild(tip);
      barStack.forEach((frame) => {
             const lockUntilRef = {
        (frame.items || []).forEach((it) => {
               value: 0,
          const pack = {
            };
            pt: it.descPt,
            function measureAndPos(el) {
            en: it.descEn,
              if (!el || tip.getAttribute("aria-hidden") === "true") return;
            es: it.descEs,
              tip.style.left = "0px";
            pl: it.descPl,
              tip.style.top = "0px";
          };
              const rect = el.getBoundingClientRect();
          const chosen =
              const tr = tip.getBoundingClientRect();
            pack[lang] ||
              let left = Math.round(rect.left + (rect.width - tr.width) / 2);
            pack.pt ||
              left = Math.max(
            pack.en ||
                8,
            pack.es ||
                Math.min(left, window.innerWidth - tr.width - 8)
            pack.pl ||
              );
            it.desc ||
              const coarse =
            "";
                (window.matchMedia &&
          it.desc = chosen;
                  matchMedia("(pointer: coarse)").matches) ||
        });
                window.innerWidth <= 600;
      });
              let top = coarse
      if (descBox) {
                ? Math.round(rect.bottom + 10)
        applyFlagTooltips(descBox);
                : Math.round(rect.top - tr.height - 8);
      }
              if (top < 8) top = Math.round(rect.bottom + 10);
      const activeIcon = window.__lastActiveSkillIcon;
              tip.style.left = left + "px";
      if (activeIcon && activeIcon.dataset.weapon) {
              tip.style.top = top + "px";
        activateSkill(activeIcon, {
            }
          openSubs: false,
            function show(el, text) {
        });
              tip.textContent = text || "";
      }
              tip.setAttribute("aria-hidden", "false");
     });
              measureAndPos(el);
     wireClicksForCurrentBar();
              tip.style.opacity = "1";
 
            }
     // Inicializa o sistema de swap de personagens (genérico)
            function hide() {
     // Aguarda um pouco para garantir que todos os ícones foram renderizados
              tip.setAttribute("aria-hidden", "true");
     setTimeout(() => {
              tip.style.opacity = "0";
       initializeActiveCharacter();
              tip.style.left = "-9999px";
     }, 100);
              tip.style.top = "-9999px";
 
            }
     const b0 = ensureBackButton();
            window.__globalSkillTooltip = {
     if (b0) {
              show,
       b0.classList.add("peek");
              hide,
       b0.style.alignSelf = "stretch";
              measureAndPos,
     }
              lockUntil: lockUntilRef,
 
     // 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) {
             Array.from(
              if (!el || tip.getAttribute("aria-hidden") === "true") return;
               document.querySelectorAll(".icon-bar .skill-icon")
              tip.style.left = "0px";
             ).forEach((icon) => {
              tip.style.top = "0px";
               if (
              const rect = el.getBoundingClientRect();
                 icon.dataset.weaponToggle === "1" ||
              const tr = tip.getBoundingClientRect();
                 icon.classList.contains("weapon-bar-toggle")
              let left = Math.round(rect.left + (rect.width - tr.width) / 2);
               )
              left = Math.max(
                 return;
                8,
               if (icon.dataset.tipwired) return;
                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";
               icon.dataset.tipwired = "1";
               const label =
               const label =
Linha 3 903: Linha 4 689:
                   const tabEl = document.getElementById(target);
                   const tabEl = document.getElementById(target);
                   if (tabEl) {
                   if (tabEl) {
                    const allIcons = tabEl.querySelectorAll(
                      ".icon-bar .skill-icon"
                    );
                     const activeIcon = tabEl.querySelector(
                     const activeIcon = tabEl.querySelector(
                       ".icon-bar .skill-icon.active"
                       ".icon-bar .skill-icon.active"
Linha 3 909: Linha 4 698:
                       ".icon-bar .skill-icon"
                       ".icon-bar .skill-icon"
                     );
                     );
                     const toClick = activeIcon || firstIcon;
                     const isFormSwitch = (el) =>
                      el?.dataset?.formSwitch === "true" ||
                      el?.getAttribute?.("data-form-switch") === "true";
                    let toClick = activeIcon && !isFormSwitch(activeIcon)
                      ? activeIcon
                      : null;
                    if (!toClick)
                      for (const icon of allIcons || [])
                        if (!isFormSwitch(icon)) {
                          toClick = icon;
                          break;
                        }
                    if (!toClick) toClick = firstIcon;
                     if (toClick) {
                     if (toClick) {
                       const had = document.body.dataset.suppressSkillPlay;
                       const had = document.body.dataset.suppressSkillPlay;
Linha 3 918: Linha 4 719:
                   }
                   }
                 }
                 }
               } catch (e) {}
               } catch (e) { }
             }
             }
             if (currentActive && nextActive) {
             if (currentActive && nextActive) {
Linha 3 935: Linha 4 736:
                   try {
                   try {
                     delete document.body.dataset.suppressSkillPlay;
                     delete document.body.dataset.suppressSkillPlay;
                   } catch {}
                   } catch { }
                 }, 300);
                 }, 300);
               });
               });
Linha 3 946: Linha 4 747:
                 try {
                 try {
                   v.pause();
                   v.pause();
                 } catch (e) {}
                 } catch (e) { }
                 v.style.display = "none";
                 v.style.display = "none";
               });
               });
               if (videoBox) {
              const vb = getVideoBox();
                 videoBox.querySelectorAll("video.skill-video").forEach((v) => {
               if (vb) {
                 vb.querySelectorAll("video.skill-video").forEach((v) => {
                   try {
                   try {
                     v.pause();
                     v.pause();
                   } catch (e) {}
                   } catch (e) { }
                   v.style.display = "none";
                   v.style.display = "none";
                 });
                 });
               }
               }
               if (window.__subskills) window.__subskills.hideAll?.(videoBox);
               if (window.__subskills) window.__subskills.hideAll?.(getVideoBox());
               const placeholder = videoBox?.querySelector(".video-placeholder");
               const placeholder = getVideoBox()?.querySelector(".video-placeholder");
               if (videoBox && placeholder) {
              const vb2 = getVideoBox();
               if (vb2 && placeholder) {
                 placeholder.style.display = "none";
                 placeholder.style.display = "none";
                 placeholder.classList.add("fade-out");
                 placeholder.classList.add("fade-out");
Linha 3 967: Linha 4 770:
                 ".icon-bar .skill-icon.active"
                 ".icon-bar .skill-icon.active"
               );
               );
               if (activeIcon) activeIcon.click();
              const isFormSwitch = (el) =>
                el?.dataset?.formSwitch === "true" ||
                el?.getAttribute?.("data-form-switch") === "true";
               if (activeIcon && !isFormSwitch(activeIcon)) activeIcon.click();
              else {
                const first = document.querySelector(
                  ".icon-bar .skill-icon:not([data-form-switch='true'])"
                );
                if (first) first.click();
              }
             }
             }
           }, 450);
           }, 450);
Linha 4 023: Linha 4 835:
         ? langRaw
         ? langRaw
         : i18nMap[langRaw.split("-")[0]]
         : i18nMap[langRaw.split("-")[0]]
        ? langRaw.split("-")[0]
          ? langRaw.split("-")[0]
        : "pt";
          : "pt";
       const L = i18nMap[langKey] ||
       const L = i18nMap[langKey] ||
         i18nMap.pt || {
         i18nMap.pt || {
          cooldown: "Recarga",
        cooldown: "Recarga",
          energy_gain: "Ganho de energia",
        energy_gain: "Ganho de energia",
          energy_cost: "Custo de energia",
        energy_cost: "Custo de energia",
          power: "Poder",
        power: "Poder",
          power_pvp: "Poder PvP",
        power_pvp: "Poder PvP",
          level: "Nível",
        level: "Nível",
        };
      };
       const vals = (str || "").split(",").map((v) => v.trim());
       const vals = (str || "").split(",").map((v) => v.trim());
       // Processa valores, tratando strings vazias e "-" como NaN
       // Processa valores, tratando strings vazias e "-" como NaN
Linha 4 080: Linha 4 892:
       return `<div class="attr-list">${html}</div>`;
       return `<div class="attr-list">${html}</div>`;
     }
     }
     function syncDescHeight() {}
     function syncDescHeight() { }
     window.addEventListener("resize", syncDescHeight);
     window.addEventListener("resize", syncDescHeight);
     if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox);
    const vbInit = getVideoBox();
     if (vbInit) new ResizeObserver(syncDescHeight).observe(vbInit);
     iconItems.forEach((el) => {
     iconItems.forEach((el) => {
       const wired = !!el.dataset._sync_wired;
       const wired = !!el.dataset._sync_wired;
Linha 4 110: Linha 4 923:
       ); // passive: false necessário porque usamos preventDefault()
       ); // passive: false necessário porque usamos preventDefault()
     }
     }
     wireClicksForCurrentBar();
     (function maybeInjectFormSkillsOnLoad() {
    if (iconItems.length) {
      try {
      const first = iconItems[0];
        const skillsRoot = document.getElementById("skills");
      if (first) {
        if (skillsRoot && iconsBar && iconItems.length > 0) {
        activateSkill(first, {
          const formsJSON = skillsRoot.dataset.forms || "{}";
          openSubs: false,
          if (formsJSON && formsJSON !== "{}") {
        });
            formsData = JSON.parse(formsJSON);
            const firstIcon = iconItems[0];
            const isFormSwitch =
              firstIcon?.dataset?.formSwitch === "true" ||
              firstIcon?.getAttribute?.("data-form-switch") === "true";
            if (
              formsData &&
              Object.keys(formsData).length > 0 &&
              isFormSwitch
            ) {
              const firstFormName = Object.keys(formsData)[0];
              const firstFormData = formsData[firstFormName];
              if (firstFormData) {
                updateSkillsBarForForm(firstFormName, firstFormData, firstIcon);
                setTimeout(() => {
                  wireClicksForCurrentBar();
                  const freshIcons = Array.from(
                    iconsBar.querySelectorAll(".skill-icon")
                  );
                  const isFS = (el) =>
                    el?.dataset?.formSwitch === "true" ||
                    el?.getAttribute?.("data-form-switch") === "true";
                  const firstNonSwitch = freshIcons.find((el) => !isFS(el));
                  if (firstNonSwitch)
                    activateSkill(firstNonSwitch, { openSubs: false });
                }, 200);
                return;
              }
            }
          }
        }
      } catch (e) {
        console.error("[Forms] init inject error", e);
      }
      wireClicksForCurrentBar();
      if (iconItems.length) {
        const isFormSwitch = (el) =>
          el?.dataset?.formSwitch === "true" ||
          el?.getAttribute?.("data-form-switch") === "true";
        let first = null;
        for (const el of iconItems) {
          if (!isFormSwitch(el)) {
            first = el;
            break;
          }
        }
        if (!first) 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
Linha 4 148: Linha 5 012:


     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.querySelector("source").src
           : v.src;
           : v.src;
         v.addEventListener("error", (ev) => {});
         v.addEventListener("error", (ev) => { });
         v.addEventListener("loadedmetadata", () => {});
         v.addEventListener("loadedmetadata", () => { });
       });
       });
     }, 600);
     }, 600);
   })();
   })();
</script>
</script>

Edição atual tal como às 22h33min de 20 de abril 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");
     const skillsRoot = document.getElementById("skills");
     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);
       }
     });
     const arr = Array.from(characters);
     const explicitDefault = (
       skillsRoot?.dataset?.defaultCharacter || ""
     ).trim();
     if (explicitDefault && arr.includes(explicitDefault)) {
       const rest = arr.filter((c) => c !== explicitDefault).sort();
       return [explicitDefault, ...rest];
     }
     return arr.sort();
   }
   // 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();
       rebuildMainSkillsMetaFromBar();
     }
   }
   // Aplica o estado de swap nas skills (habilita/desabilita, vídeo, stats e descrição por personagem)
   function applyCharacterSwapState() {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return;
     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 hasCharSkills =
           icon.dataset.characterSkills &&
           icon.dataset.characterSkills.trim() !== "";
         if (onlyCharacter) {
           if (onlyCharacter === activeCharacter) {
             icon.style.opacity = "1";
             icon.style.filter = "";
             icon.style.pointerEvents = "";
             icon.classList.remove("disabled-skill");
           } else {
             icon.style.opacity = "0.3";
             icon.style.filter = "grayscale(100%)";
             icon.style.pointerEvents = "none";
             icon.classList.add("disabled-skill");
           }
           return;
         }
         icon.style.opacity = "1";
         icon.style.filter = "";
         icon.style.pointerEvents = "";
         icon.classList.remove("disabled-skill");
         if (!hasCharSkills && !hasCharacterVideos) {
           return;
         }
         if (!icon.dataset.swapBaselineStored) {
           icon.dataset.swapBaselineStored = "1";
           icon.dataset.swapBaselineAtr = icon.dataset.atr || "";
           icon.dataset.swapBaselineDescPt = icon.dataset.descPt || "";
           icon.dataset.swapBaselineDescEn = icon.dataset.descEn || "";
           icon.dataset.swapBaselineDescEs = icon.dataset.descEs || "";
           icon.dataset.swapBaselineDescPl = icon.dataset.descPl || "";
           icon.dataset.swapBaselineLevel = icon.dataset.level || "";
           icon.dataset.swapBaselineVideoFile = (
             icon.dataset.videoFile || ""
           ).trim();
           icon.dataset.swapBaselineVideo = (icon.dataset.video || "").trim();
           if (
             hasCharacterVideos &&
             !icon.dataset.originalVideoFile
           ) {
             icon.dataset.originalVideoFile =
               icon.dataset.swapBaselineVideoFile ||
               icon.dataset.swapBaselineVideo ||
               "";
           }
         }
         const oAtr = parseAttrString(
           icon.dataset.swapBaselineAtr || icon.dataset.atr || ""
         );
         let pve = oAtr.powerpve;
         let pvp = oAtr.powerpvp;
         let en = oAtr.energy;
         let cd = oAtr.cooldown;
         let dPt = icon.dataset.swapBaselineDescPt || "";
         let dEn = icon.dataset.swapBaselineDescEn || "";
         let dEs = icon.dataset.swapBaselineDescEs || "";
         let dPl = icon.dataset.swapBaselineDescPl || "";
         let lvl = icon.dataset.swapBaselineLevel || "";
         let vFile = icon.dataset.swapBaselineVideoFile || "";
         let vUrl = icon.dataset.swapBaselineVideo || "";
         if (hasCharSkills && activeCharacter) {
           try {
             const cs = JSON.parse(icon.dataset.characterSkills);
             const co = cs[activeCharacter] || {};
             if (
               co.powerpve !== undefined &&
               co.powerpve !== null &&
               co.powerpve !== ""
             )
               pve = String(co.powerpve);
             if (
               co.powerpvp !== undefined &&
               co.powerpvp !== null &&
               co.powerpvp !== ""
             )
               pvp = String(co.powerpvp);
             if (
               co.energy !== undefined &&
               co.energy !== null &&
               co.energy !== ""
             )
               en = String(co.energy);
             if (
               co.cooldown !== undefined &&
               co.cooldown !== null &&
               co.cooldown !== ""
             )
               cd = String(co.cooldown);
             if (co.level !== undefined && co.level !== null && co.level !== "")
               lvl = String(co.level);
             const di = co.desc_i18n || co.desc;
             if (di) {
               if (di.pt) dPt = di.pt;
               if (di.en) dEn = di.en;
               if (di.es) dEs = di.es;
               if (di.pl) dPl = di.pl;
             }
             if (co.video && String(co.video).trim()) {
               const vf = String(co.video).trim();
               vFile = vf;
               vUrl = filePathURL(vf) || vf;
             }
           } catch (e) {
             console.error("[Swap] Erro ao processar character_skills:", e);
           }
         }
         if (hasCharacterVideos && activeCharacter) {
           try {
             const cv = JSON.parse(icon.dataset.characterVideos);
             const cvv = cv[activeCharacter];
             if (cvv && String(cvv).trim()) {
               const vf = String(cvv).trim();
               vFile = vf;
               vUrl = filePathURL(vf) || vf;
             }
           } catch (e) {
             console.error("[Swap] Erro ao processar character_videos:", e);
           }
         } else if (
           hasCharacterVideos &&
           activeCharacter === null &&
           icon.dataset.originalVideoFile
         ) {
           const originalVideo = icon.dataset.originalVideoFile || "";
           if (originalVideo) {
             vFile =
               extractFileNameFromURL(originalVideo) || originalVideo;
             vUrl = filePathURL(vFile) || originalVideo;
           }
         }
         icon.dataset.atr = makeAttrString(pve, pvp, en, cd);
         icon.dataset.descPt = dPt;
         icon.dataset.descEn = dEn;
         icon.dataset.descEs = dEs;
         icon.dataset.descPl = dPl;
         if (lvl && String(lvl).toUpperCase() !== "NIVEL") {
           icon.dataset.level = lvl;
         } else {
           delete icon.dataset.level;
         }
         icon.dataset.videoFile = vFile;
         icon.dataset.video = vUrl;
       }
     );
   }
   // 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) {
       return;
     }
     if (activeCharacter === null) {
       activeCharacter = availableCharacters[0];
     } else {
       const currentIndex = availableCharacters.indexOf(activeCharacter);
       const nextIndex = (currentIndex + 1) % availableCharacters.length;
       activeCharacter = availableCharacters[nextIndex];
     }
     applyCharacterSwapState();
     rebuildMainSkillsMetaFromBar();
   }
   // 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 getNextFormName() {
     const formNames = Object.keys(formsData);
     if (formNames.length === 0) return null;
     let cur = currentForm;
     if (cur === null) {
       cur = detectCurrentForm();
     }
     if (!cur && formNames.length > 0) {
       cur = formNames[0];
     }
     let orderedFormNames = [];
     if (cur === "Brain Point" && formNames.length === 3 &&
       formNames.includes("Kung Fu Point") && formNames.includes("Heavy Point")) {
       orderedFormNames = ["Brain Point", "Kung Fu Point", "Heavy Point"];
     } else if (cur === "Kung Fu Point" && formNames.length === 3 &&
       formNames.includes("Heavy Point") && formNames.includes("Brain Point")) {
       orderedFormNames = ["Kung Fu Point", "Heavy Point", "Brain Point"];
     } else if (cur === "Heavy Point" && formNames.length === 3 &&
       formNames.includes("Brain Point") && formNames.includes("Kung Fu Point")) {
       orderedFormNames = ["Heavy Point", "Brain Point", "Kung Fu Point"];
     }
     if (orderedFormNames.length === 0) {
       orderedFormNames = [...formNames].sort();
       if (cur) {
         const idx = orderedFormNames.indexOf(cur);
         if (idx !== -1) {
           orderedFormNames = [
             ...orderedFormNames.slice(idx),
             ...orderedFormNames.slice(0, idx),
           ];
         }
       }
     }
     const idx = orderedFormNames.indexOf(cur);
     if (idx === -1) return null;
     return orderedFormNames[(idx + 1) % orderedFormNames.length];
   }
   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;
     if (currentForm === null) {
       currentForm = detectCurrentForm();
       if (!currentForm && formNames.length > 0) {
         currentForm = formNames[0];
       }
     }
     const nextForm = getNextFormName();
     if (!nextForm) return;
     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);
     // Skills "extras" da forma (order[2..N]) vão DEPOIS do Guard Point,
     // em sequência contígua. Para forms com 3 skills (caso clássico do Chopper)
     // isso insere apenas order[2] (quinta skill) mantendo o layout antigo.
     // Para forms com 4+ skills, as demais ocupam os slots seguintes.
     const extraSkillsData = [];
     for (let i = 2; i < formOrder.length; i++) {
       const nm = formOrder[i];
       const dt = formSkills.find((s) => s.name === nm);
       if (dt) extraSkillsData.push(dt);
     }
     // Cria fragments para inserir
     const firstFragment = document.createDocumentFragment();
     const thirdFragment = document.createDocumentFragment();
     const extrasFragment = 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 (extraSkillsData.length > 0) {
       const nextFixedSkillIndex = parseInt(
         nextFixedSkillIcon.dataset.index || "4",
         10
       );
       extraSkillsData.forEach((dt, offset) => {
         const iconElement = createSkillIconElement(
           dt,
           nextFixedSkillIndex + 1 + offset
         );
         extrasFragment.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 as skills extras (order[2..N]) DEPOIS da próxima skill fixa,
       // em sequência contígua.
       if (extrasFragment.hasChildNodes()) {
         if (nextFixedSkillIcon.nextSibling) {
           iconBar.insertBefore(extrasFragment, nextFixedSkillIcon.nextSibling);
         } else {
           iconBar.appendChild(extrasFragment);
         }
       }
       // 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 slots para TODAS as skills extras (depois da próxima skill fixa),
         // em sequência contígua. Cada uma recebe um data-index incremental.
         if (extraSkillsData.length > 0 && nextFixedSkillDescSlot) {
           // anchor mantém a referência de inserção: cada novo slot entra
           // logo depois do anterior, preservando a ordem visual.
           let anchor = nextFixedSkillDescSlot;
           extraSkillsData.forEach((_, offset) => {
             const descSlot = document.createElement("div");
             descSlot.className = "skill-desc";
             descSlot.setAttribute("data-index", nextFixedSkillIndex + 1 + offset);
             if (anchor.nextSibling) {
               descBox.insertBefore(descSlot, anchor.nextSibling);
             } else {
               descBox.appendChild(descSlot);
             }
             anchor = 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 !== "" &&
       String(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) { }
     }
     if (skill.formSwitch) {
       iconWrap.setAttribute("data-form-switch", skill.formSwitch);
     }
     if (skill.formVideos && typeof skill.formVideos === "string" && skill.formVideos.trim() !== "") {
       iconWrap.setAttribute("data-form-videos", skill.formVideos);
     }
     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 !== "" &&
       String(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) { }
     }
     if (skill.formSwitch) {
       iconWrap.setAttribute("data-form-switch", skill.formSwitch);
     }
     if (skill.formVideos && typeof skill.formVideos === "string" && skill.formVideos.trim() !== "") {
       iconWrap.setAttribute("data-form-videos", skill.formVideos);
     }
     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) {
     const keyNorm = String(key || "").trim().toLowerCase();
     if (!keyNorm || !FLAG_ICON_FILES[keyNorm]) return "";
     if (!flagIconURLCache.has(keyNorm)) {
       flagIconURLCache.set(keyNorm, filePathURL(FLAG_ICON_FILES[keyNorm]));
     }
     return flagIconURLCache.get(keyNorm);
   }
   function renderFlagsRow(flags) {
     const arr = (flags || []).filter(Boolean).map((k) => String(k || "").trim().toLowerCase());
     const arrFiltered = arr.filter(Boolean);
     if (!arrFiltered.length) return "";
     const cacheKey = arrFiltered.join("|");
     if (flagRowCache.has(cacheKey)) {
       return flagRowCache.get(cacheKey);
     }
     const items = arrFiltered
       .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 || "");
       }
       let flags = null;
       if (icon.dataset.flags) {
         try {
           flags = JSON.parse(icon.dataset.flags);
           if (!Array.isArray(flags) || flags.length === 0) flags = null;
         } catch (e) { flags = null; }
       }
       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 || "",
         flags: flags || null,
       };
       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 rebuildMainSkillsMetaFromBar() {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return;
     mainSkillsMeta.ready = false;
     mainSkillsMeta.byIndex.clear();
     mainSkillsMeta.byName.clear();
     buildMainSkillsMeta(
       Array.from(iconBar.querySelectorAll(".skill-icon[data-index]"))
     );
   }
   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 || "");
     // Flags: herda da skill principal se a subskill não tiver
     if (
       (!hydrated.flags || !Array.isArray(hydrated.flags) || hydrated.flags.length === 0) &&
       main.flags && Array.isArray(main.flags) && main.flags.length > 0
     ) {
       hydrated.flags = main.flags;
     }
     // 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;
   let videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
   // Busca dinâmica do videoBox (pode não existir no momento da inicialização)
   function getVideoBox() {
     if (videoBox && videoBox.isConnected) return videoBox;
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return null;
     videoBox = skillsRoot.querySelector(".video-container") ||
       skillsRoot.querySelector(".skills-container .video-container") ||
       skillsRoot.querySelector(".content-card .video-container");
     return videoBox;
   }
   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);
       }
     }
     // form_switch (Change Form): usa vídeo da PRÓXIMA forma (transição atual → próxima)
     const isFormSwitch =
       iconEl.dataset.formSwitch === "true" ||
       iconEl.getAttribute("data-form-switch") === "true";
     if (isFormSwitch) {
       const formVideosRaw =
         iconEl.dataset.formVideos || iconEl.getAttribute("data-form-videos");
       if (formVideosRaw) {
         try {
           const videos = JSON.parse(formVideosRaw);
           let formVideo = "";
           const nextForm = typeof getNextFormName === "function" ? getNextFormName() : null;
           if (nextForm) {
             formVideo = videos[nextForm] || "";
           }
           // Fallback: se nextForm for null (formsData ainda não carregado), usa primeiro vídeo disponível
           if ((!formVideo || formVideo.trim() === "") && Object.keys(videos).length > 0) {
             const firstKey = Object.keys(videos)[0];
             formVideo = videos[firstKey] || "";
           }
           if (formVideo && formVideo.trim() !== "") {
             return formVideo.trim();
           }
         } catch (e) {
           console.warn("[Forms] Erro ao parsear form_videos em getEffectiveSkillVideoFromIcon:", 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 = ""
   ) {
     const vb = getVideoBox();
     if (!vb || !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)
               vb.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)
               vb.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 (!getVideoBox()) 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() {
     const vb = getVideoBox();
     if (!vb || !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
       // form_switch com form_videos: NÃO pré-carrega o vídeo base (ex: Change Form-Video.mp4) pois pode não existir
       const isFormSwitchPreload =
         el.dataset.formSwitch === "true" ||
         el.getAttribute("data-form-switch") === "true";
       const hasFormVideos =
         (el.dataset.formVideos || el.getAttribute("data-form-videos") || "").trim() !== "";
       const skipBaseVideo = isFormSwitchPreload && hasFormVideos;
       let src = "";
       if (!skipBaseVideo) {
         src = (el.dataset.videoFile || "").trim();
         if (!src) {
           const videoAttr = (el.dataset.video || "").trim();
           if (videoAttr) {
             src = videoAttr.includes("/") || videoAttr.startsWith("http")
               ? videoAttr
               : 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,
               }
             );
             vb.appendChild(v);
             videosCache.set(idx, v);
             nestedVideoElByIcon.set(el, v);
             // Força carregamento imediatamente (apenas uma vez)
             v.load();
           }
         }
       }
       // form_switch: pré-carrega TODOS os vídeos de form_videos para transições
       const isFormSwitch =
         el.dataset.formSwitch === "true" ||
         el.getAttribute("data-form-switch") === "true";
       if (isFormSwitch) {
         const formVideosRaw =
           el.dataset.formVideos || el.getAttribute("data-form-videos");
         if (formVideosRaw) {
           try {
             const videos = JSON.parse(formVideosRaw);
             Object.keys(videos).forEach((formName) => {
               const videoFile = videos[formName];
               if (videoFile && videoFile.trim() !== "") {
                 const videoKey = idx + ":" + videoFile;
                 if (!videosCache.has(videoKey)) {
                   const videoURL = normalizeFileURL(videoFile);
                   if (videoURL && !failedVideosCache.has(videoURL)) {
                     const v = createVideoElement(videoURL, { index: idx });
                     if (v) {
                       totalVideos++;
                       v.addEventListener(
                         "canplaythrough",
                         () => {
                           loadedVideos++;
                           if (loadedVideos === totalVideos) autoplay = true;
                         },
                         { once: true }
                       );
                       v.addEventListener(
                         "error",
                         () => {
                           loadedVideos++;
                           if (loadedVideos === totalVideos) autoplay = true;
                         },
                         { once: true }
                       );
                       vb.appendChild(v);
                       videosCache.set(videoKey, v);
                       v.load();
                     }
                   }
                 }
               }
             });
           } catch (e) {
             console.warn("[Forms] Erro ao pré-carregar form_videos:", e);
           }
         }
       }
       // 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,
                 }
               );
               vb.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;
     const videoBox = getVideoBox();
     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);
       // Verifica se o vídeo cacheado corresponde ao vídeo atual da skill
       // (ao trocar de form, o índice é reutilizado mas o vídeo muda)
       const cachedSrc = v.querySelector("source")?.getAttribute("src") || "";
       const currentVideoFile = (el.dataset.videoFile || "").trim();
       const currentVideoURL = currentVideoFile ? normalizeFileURL(currentVideoFile) : "";
       const decodeCached = decodeURIComponent(cachedSrc.split("/").pop().split("?")[0] || "");
       const decodeTarget = decodeURIComponent((currentVideoURL || currentVideoFile).split("/").pop().split("?")[0] || "");
       const srcMismatch = decodeTarget && decodeCached && decodeCached !== decodeTarget;
       if (!srcMismatch) {
         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;
       }
       // Vídeo não corresponde - remove do cache e do DOM
       v.style.display = "none";
       try { v.pause(); } catch (e) { }
       if (v.parentNode) v.parentNode.removeChild(v);
       videosCache.delete(el.dataset.index);
     }
     // 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;
           }
         }
       }
       // Vídeos de form skills ou outros que não estão no cache - cria dinamicamente
       else if (videoURL) {
         const idx = el.dataset.index || "";
         v = createVideoElement(videoURL, { index: idx });
         if (v) {
           videoBox.appendChild(v);
           if (idx) videosCache.set(idx, v);
           v.load();
         } else {
           videoBox.style.display = "none";
           return;
         }
       } else {
         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);
     }
     const vb = getVideoBox();
     if (vb) {
       const oldFlags = vb.querySelector(".skill-flags");
       if (oldFlags) oldFlags.remove();
       if (flagsHTML) {
         vb.insertAdjacentHTML("beforeend", flagsHTML);
         applyFlagTooltips(vb);
       }
     }
     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) {
       try {
         const formsJSON = skillsRoot.dataset.forms || "{}";
         if (formsJSON && formsJSON !== "{}") {
           if (Object.keys(formsData).length === 0) {
             formsData = JSON.parse(formsJSON);
           }
         }
         // Inicializa currentForm se necessário
         if (currentForm === null) {
           currentForm = detectCurrentForm();
           const formNames = Object.keys(formsData);
           if (!currentForm && formNames.length > 0) {
             currentForm = formNames[0];
           }
         }
         // Calcula a próxima forma ANTES de trocar
         const nextForm = getNextFormName();
         // Vídeo de transição: usa o vídeo da PRÓXIMA forma
         const formVideosRaw =
           el.dataset.formVideos || el.getAttribute("data-form-videos");
         if (formVideosRaw && nextForm) {
           const videos = JSON.parse(formVideosRaw);
           const transitionVideo = videos[nextForm] || "";
           if (transitionVideo && transitionVideo.trim() !== "") {
             el.dataset.videoFile = transitionVideo;
           }
         }
       } catch (e) {
         console.error("[Forms] Erro ao processar transição:", e);
       }
       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 allIcons = tabEl.querySelectorAll(
                     ".icon-bar .skill-icon"
                   );
                   const activeIcon = tabEl.querySelector(
                     ".icon-bar .skill-icon.active"
                   );
                   const firstIcon = tabEl.querySelector(
                     ".icon-bar .skill-icon"
                   );
                   const isFormSwitch = (el) =>
                     el?.dataset?.formSwitch === "true" ||
                     el?.getAttribute?.("data-form-switch") === "true";
                   let toClick = activeIcon && !isFormSwitch(activeIcon)
                     ? activeIcon
                     : null;
                   if (!toClick)
                     for (const icon of allIcons || [])
                       if (!isFormSwitch(icon)) {
                         toClick = icon;
                         break;
                       }
                   if (!toClick) toClick = 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";
             });
             const vb = getVideoBox();
             if (vb) {
               vb.querySelectorAll("video.skill-video").forEach((v) => {
                 try {
                   v.pause();
                 } catch (e) { }
                 v.style.display = "none";
               });
             }
             if (window.__subskills) window.__subskills.hideAll?.(getVideoBox());
             const placeholder = getVideoBox()?.querySelector(".video-placeholder");
             const vb2 = getVideoBox();
             if (vb2 && placeholder) {
               placeholder.style.display = "none";
               placeholder.classList.add("fade-out");
             }
           } else {
             const activeIcon = document.querySelector(
               ".icon-bar .skill-icon.active"
             );
             const isFormSwitch = (el) =>
               el?.dataset?.formSwitch === "true" ||
               el?.getAttribute?.("data-form-switch") === "true";
             if (activeIcon && !isFormSwitch(activeIcon)) activeIcon.click();
             else {
               const first = document.querySelector(
                 ".icon-bar .skill-icon:not([data-form-switch='true'])"
               );
               if (first) first.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);
   const vbInit = getVideoBox();
   if (vbInit) new ResizeObserver(syncDescHeight).observe(vbInit);
   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()
   }
   (function maybeInjectFormSkillsOnLoad() {
     try {
       const skillsRoot = document.getElementById("skills");
       if (skillsRoot && iconsBar && iconItems.length > 0) {
         const formsJSON = skillsRoot.dataset.forms || "{}";
         if (formsJSON && formsJSON !== "{}") {
           formsData = JSON.parse(formsJSON);
           const firstIcon = iconItems[0];
           const isFormSwitch =
             firstIcon?.dataset?.formSwitch === "true" ||
             firstIcon?.getAttribute?.("data-form-switch") === "true";
           if (
             formsData &&
             Object.keys(formsData).length > 0 &&
             isFormSwitch
           ) {
             const firstFormName = Object.keys(formsData)[0];
             const firstFormData = formsData[firstFormName];
             if (firstFormData) {
               updateSkillsBarForForm(firstFormName, firstFormData, firstIcon);
               setTimeout(() => {
                 wireClicksForCurrentBar();
                 const freshIcons = Array.from(
                   iconsBar.querySelectorAll(".skill-icon")
                 );
                 const isFS = (el) =>
                   el?.dataset?.formSwitch === "true" ||
                   el?.getAttribute?.("data-form-switch") === "true";
                 const firstNonSwitch = freshIcons.find((el) => !isFS(el));
                 if (firstNonSwitch)
                   activateSkill(firstNonSwitch, { openSubs: false });
               }, 200);
               return;
             }
           }
         }
       }
     } catch (e) {
       console.error("[Forms] init inject error", e);
     }
     wireClicksForCurrentBar();
     if (iconItems.length) {
       const isFormSwitch = (el) =>
         el?.dataset?.formSwitch === "true" ||
         el?.getAttribute?.("data-form-switch") === "true";
       let first = null;
       for (const el of iconItems) {
         if (!isFormSwitch(el)) {
           first = el;
           break;
         }
       }
       if (!first) 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>