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

De Wiki Gla
Ir para navegação Ir para pesquisar
m
Etiqueta: Revertido
m
Etiqueta: Reversão manual
 
Linha 1: Linha 1:
<!-- SUBSKILLS SYSTEM -->
<!-- MAIN SKILLS SYSTEM -->
<script>
<script>
   (function () {
   (function () {
     const SUBVIDEO_DEBUG = false; // Desabilitado para melhor performance
     const $ = (s, root = document) => root.querySelector(s);
     function logSubVideo(...args) {
    const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
       if (!SUBVIDEO_DEBUG) return;
    const ensureRemoved = (sel) => {
       // console.log('[SubVideo]', ...args);
      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);
      }
     }
     }


     const api = (window.__subskills ||= {});
     // Sistema genérico de swap de personagens (ex: Buchi & Sham, futuros personagens)
     // Cache global de elementos de vídeo para subskills - baseado em URL
     // Detecta quais personagens estão disponíveis baseado nas skills
     const subskillVideoElementCache = new Map(); // key: videoURL (string), value: HTMLVideoElement
     function detectAvailableCharacters() {
    const imagePreloadCache = new Map();
      const iconBar = document.querySelector(".icon-bar");
    let subRail, subBar, spacer;
      if (!iconBar) return [];


    // Cache das skills principais (capturado na carga da página)
      const characters = new Set();
    let cachedMainSkills = null;
      Array.from(
        iconBar.querySelectorAll(".skill-icon[data-only-character]")
      ).forEach((icon) => {
        const character = (icon.dataset.onlyCharacter || "").trim();
        if (character) {
          characters.add(character);
        }
      });


     // ===== HERANÇA DE ATRIBUTOS: busca dados das skills principais =====
      return Array.from(characters);
     // FASE 5: Refatorado para usar byId como fonte primária
     }
     function getMainSkillsMap() {
 
       // Retorna cache se já foi construído E tem dados
     // Inicializa o personagem padrão (deve ser chamado quando a página carrega)
       if (cachedMainSkills && cachedMainSkills.byId.size > 0) {
     function initializeActiveCharacter() {
         return cachedMainSkills;
       const availableCharacters = detectAvailableCharacters();
       if (availableCharacters.length > 0 && activeCharacter === null) {
         // Inicializa com o primeiro personagem disponível (padrão)
        activeCharacter = availableCharacters[0];
        // Aplica o estado inicial (habilita/desabilita skills)
        applyCharacterSwapState();
       }
       }
    }
    // Aplica o estado de swap nas skills (habilita/desabilita e atualiza vídeos)
    function applyCharacterSwapState() {
      const iconBar = document.querySelector(".icon-bar");
      if (!iconBar) return;
      // Atualiza todas as skills na barra
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
        (icon) => {
          const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
          const hasCharacterVideos =
            icon.dataset.characterVideos &&
            icon.dataset.characterVideos.trim() !== "";
          const baseVideoFile = icon.dataset.videoFile || "";
          const baseVideoURL = icon.dataset.video || "";
          // Salva o vídeo original se ainda não foi salvo (apenas para skills compartilhadas com character_videos)
          if (
            hasCharacterVideos &&
            !onlyCharacter &&
            !icon.dataset.originalVideoFile
          ) {
            icon.dataset.originalVideoFile =
              baseVideoFile || baseVideoURL || "";
          }


      const maps = {
          // Desabilita/habilita skills baseado no personagem ativo
        byId: new Map(), // FASE 5: Fonte primária por ID
          if (onlyCharacter) {
        byName: new Map(), // FASE 5: Mantido para compatibilidade (legado)
            // Skill específica de um personagem
        byIndex: new Map(), // Mantido para compatibilidade
            if (onlyCharacter === activeCharacter) {
      };
              // Skill do personagem ativo → habilitar
              icon.style.opacity = "1";
              icon.style.filter = "";
              icon.style.pointerEvents = "";
              icon.classList.remove("disabled-skill");
            } else {
              // Skill de outro personagem → desabilitar (escuras)
              icon.style.opacity = "0.3";
              icon.style.filter = "grayscale(100%)";
              icon.style.pointerEvents = "none";
              icon.classList.add("disabled-skill");
            }
          } else {
            // Skill compartilhada → sempre habilitada
            icon.style.opacity = "1";
            icon.style.filter = "";
            icon.style.pointerEvents = "";
            icon.classList.remove("disabled-skill");


      // Busca skills com data-index (skills principais, não subskills)
            // Atualiza vídeo se houver character_videos e personagem ativo
      const icons = document.querySelectorAll(
            if (hasCharacterVideos && activeCharacter) {
        ".icon-bar .skill-icon[data-index][data-nome]"
              try {
                const characterVideos = JSON.parse(
                  icon.dataset.characterVideos
                );
                const characterVideo = characterVideos[activeCharacter];
                if (characterVideo && characterVideo.trim() !== "") {
                  icon.dataset.videoFile = characterVideo;
                  icon.dataset.video =
                    filePathURL(characterVideo) || characterVideo;
                }
              } catch (e) {
                console.error("[Swap] Erro ao processar character_videos:", e);
              }
            } else if (hasCharacterVideos && activeCharacter === null) {
              // Restaura vídeo original quando volta ao personagem padrão (null)
              const originalVideo = icon.dataset.originalVideoFile || "";
              if (originalVideo) {
                icon.dataset.videoFile = originalVideo;
                icon.dataset.video =
                  filePathURL(originalVideo) || originalVideo;
              }
            }
          }
        }
       );
       );
    }
    // Troca entre personagens (genérico)
    function handleSwapCharacter(swapIconEl) {
      if (!swapIconEl) return;


       icons.forEach((icon) => {
       const iconBar = document.querySelector(".icon-bar");
        const name = (icon.dataset.nome || "").trim();
      if (!iconBar) return;
        if (!name) return;


        // Extrai atributos do data-atr (formato: "pve, pvp, energy, cd")
      const availableCharacters = detectAvailableCharacters();
        const atrRaw = icon.dataset.atr || "";
      if (availableCharacters.length === 0) {
        const parts = atrRaw.split(",").map((x) => (x || "").trim());
         // Se não há personagens específicos, não faz nada (não há swap)
        const powerpve = parts[0] && parts[0] !== "-" ? parts[0] : "";
         return;
         const powerpvp = parts[1] && parts[1] !== "-" ? parts[1] : "";
      }
         const energy = parts[2] && parts[2] !== "-" ? parts[2] : "";
        const cooldown = parts[3] && parts[3] !== "-" ? parts[3] : "";


        // Nome original do arquivo de ícone (armazenado no dataset pelo widget de skills)
      // Se activeCharacter é null, inicializa com o primeiro personagem disponível
         let iconFile = (icon.dataset.iconFile || "").trim();
      if (activeCharacter === null) {
         if (!iconFile) {
         activeCharacter = availableCharacters[0];
          const imgSrc = icon.querySelector("img")?.src || "";
      } else {
          const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
         // Encontra o índice do personagem atual e avança para o próximo
          iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : "";
        const currentIndex = availableCharacters.indexOf(activeCharacter);
        }
        const nextIndex = (currentIndex + 1) % availableCharacters.length;
        activeCharacter = availableCharacters[nextIndex];
      }


        // Nome original do arquivo de vídeo (caso exista)
      // Aplica o novo estado
        let videoFile = (icon.dataset.videoFile || "").trim();
      applyCharacterSwapState();
        if (!videoFile) {
          const videoUrl = icon.dataset.video || "";
          const videoMatch = videoUrl.match(/FilePath\/([^&?]+)/);
          videoFile = videoMatch ? decodeURIComponent(videoMatch[1]) : "";
        }


         const index = (icon.dataset.index || "").trim();
      // Atualiza todas as skills na barra
        // FASE 5: Extrai ID do data-skill-id ou gera a partir do nome
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
        const skillId = icon.dataset.skillId || icon.dataset.id || name;
         (icon) => {
          const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
          const hasCharacterVideos =
            icon.dataset.characterVideos &&
            icon.dataset.characterVideos.trim() !== "";
          const baseVideoFile = icon.dataset.videoFile || "";
          const baseVideoURL = icon.dataset.video || "";


        const data = {
           // Salva o vídeo original se ainda não foi salvo
          id: skillId, // FASE 5: ID único
           if (hasCharacterVideos && !icon.dataset.originalVideoFile) {
           name: name, // Mantido para compatibilidade
            icon.dataset.originalVideoFile =
           icon: iconFile,
              baseVideoFile || baseVideoURL || "";
          level: icon.dataset.level || "",
           }
           video: videoFile,
          powerpve: powerpve,
          powerpvp: powerpvp,
          cooldown: cooldown,
          energy: energy,
        };


        // Mantém descrições caso precise como fallback extra
          // Desabilita/habilita skills baseado no personagem ativo
        if (icon.dataset.descPt) data.descPt = icon.dataset.descPt;
          if (onlyCharacter) {
        if (icon.dataset.descEn) data.descEn = icon.dataset.descEn;
            // Skill específica de um personagem
        if (icon.dataset.descEs) data.descEs = icon.dataset.descEs;
            if (onlyCharacter === activeCharacter) {
        if (icon.dataset.descPl) data.descPl = icon.dataset.descPl;
              // Skill do personagem ativo → habilitar
              icon.style.opacity = "1";
              icon.style.filter = "";
              icon.style.pointerEvents = "";
              icon.classList.remove("disabled-skill");
            } else {
              // Skill de outro personagem → desabilitar (escuras)
              icon.style.opacity = "0.3";
              icon.style.filter = "grayscale(100%)";
              icon.style.pointerEvents = "none";
              icon.classList.add("disabled-skill");
            }
          } else {
            // Skill compartilhada → sempre habilitada
            icon.style.opacity = "1";
            icon.style.filter = "";
            icon.style.pointerEvents = "";
            icon.classList.remove("disabled-skill");


        // FASE 5: Processa desc_i18n se existir
            // Atualiza vídeo se houver character_videos e personagem ativo
        if (icon.dataset.descI18n) {
            if (hasCharacterVideos && activeCharacter) {
          try {
              try {
            data.desc_i18n = JSON.parse(icon.dataset.descI18n);
                const characterVideos = JSON.parse(
          } catch (e) {
                  icon.dataset.characterVideos
             // Ignora erro de parse
                );
                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;
              }
            }
           }
           }
         }
         }
      );
    }


        // FASE 5: byId é a fonte primária
    // Detecta qual forma está atualmente visível no DOM
        maps.byId.set(skillId, data);
    function detectCurrentForm() {
        maps.byName.set(name, data); // FASE 5: Mantido para compatibilidade (legado)
      const iconBar = document.querySelector(".icon-bar");
        if (index) {
      if (!iconBar || !formsData || Object.keys(formsData).length === 0)
          // Guarda tanto como string quanto como número para compatibilidade
        return null;
          maps.byIndex.set(index, data);
 
           maps.byIndex.set(parseInt(index, 10), data);
      // 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));
         }
         }
       });
       });


       // Cacheia para uso futuro (importante: as skills principais não mudam)
      const visibleFormSkillNames = new Set();
      cachedMainSkills = maps;
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       return maps;
        (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;
     }
     }


    // FASE 4: Resolver único para toda resolução de skill/subskill
     function switchForm() {
    // Retorna tudo que a UI precisa renderizar de forma determinística
       const skillsRoot = document.getElementById("skills");
     function resolveSkillView(skill, context) {
       if (!skillsRoot) return;
       // context: { lang, weaponMode, mainSkills }
       // Retorna: { title, desc, video, attrs, flags, weapon }


       const lang = context.lang || "pt";
       // Lê dados de forms do atributo data-forms
      const weaponMode = context.weaponMode || false;
      try {
       const mainSkills = context.mainSkills || null;
        const formsJSON = skillsRoot.dataset.forms || "{}";
        if (formsJSON && formsJSON !== "{}") {
          formsData = JSON.parse(formsJSON);
        }
       } catch (e) {
        console.error("[Forms] Erro ao parsear forms:", e);
        return;
      }


       // 1. Título: sempre display_name
       if (!formsData || Object.keys(formsData).length === 0) {
      const title = skill.display_name || skill.name || skill.n || "";
        return; // Não tem forms, não faz nada
      }


       // 2. Descrição: sempre vem da skill/subskill, nunca herda
       // Identifica skills fixas (sempre presentes)
       let desc = "";
       const iconBar = document.querySelector(".icon-bar");
       if (weaponMode && skill.weapon?.desc_i18n) {
       if (!iconBar) return;
        desc =
 
          skill.weapon.desc_i18n[lang] ||
      // Busca a skill com form_switch dinamicamente (genérico)
          skill.weapon.desc_i18n.pt ||
      const changeFormIcon = Array.from(
           skill.weapon.desc_i18n.en ||
        iconBar.querySelectorAll(".skill-icon[data-index]")
           "";
      ).find(
       } else {
        (icon) =>
         desc =
           icon.dataset.formSwitch === "true" ||
          skill.desc_i18n?.[lang] ||
           icon.getAttribute("data-form-switch") === "true"
          skill.desc_i18n?.pt ||
      );
          skill.desc_i18n?.en ||
       if (changeFormIcon) {
          "";
         changeFormIcon.classList.add("active");
         if (!desc) {
      }
           desc =
 
            skill.descPt || skill.descEn || skill.descEs || skill.descPl || "";
      // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
      const allFormSkillNames = new Set();
      Object.values(formsData).forEach((form) => {
         if (form.order && Array.isArray(form.order)) {
           form.order.forEach((skillName) => allFormSkillNames.add(skillName));
         }
         }
      });
      // Skills fixas = todas as skills na barra que não estão em nenhuma forms
      fixedSkills = Array.from(
        iconBar.querySelectorAll(".skill-icon[data-index]")
      )
        .filter((icon) => {
          const name = (icon.dataset.nome || "").trim();
          return name && !allFormSkillNames.has(name);
        })
        .map((icon) => snapshotIconData(icon));
      // Obtém lista de formas disponíveis
      const formNames = Object.keys(formsData);
      if (formNames.length === 0) return;
      // Determina próxima forma
      // Se currentForm é null, detecta qual forma está atualmente visível no DOM
      if (currentForm === null) {
        currentForm = detectCurrentForm();
       }
       }


       // 3. Vídeo: sempre da skill/subskill, nunca herdado
       // Se ainda não conseguiu detectar, usa a primeira forma como fallback
       const video = skill.video || "";
       if (!currentForm && formNames.length > 0) {
        currentForm = formNames[0];
      }


       // 4. Atributos: weapon mode ou normal
       // Cria ordem circular fixa baseada na forma atual detectada
       let attrs = {
      // Se detectamos "Brain Point", ordem é: Brain → Kung Fu → Heavy → Brain
         powerpve: skill.powerpve,
      // Se detectamos "Kung Fu Point", ordem é: Kung Fu → Heavy → Brain → Kung Fu
         powerpvp: skill.powerpvp,
      // Se detectamos "Heavy Point", ordem é: Heavy → Brain → Kung Fu → Heavy
         energy: skill.energy,
       let orderedFormNames = [];
         cooldown: skill.cooldown,
      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"];
        }
       }


       if (weaponMode && skill.weapon) {
      // Se não conseguiu criar ordem conhecida, usa ordem alfabética como fallback
         attrs = {
       if (orderedFormNames.length === 0) {
          powerpve: skill.weapon.powerpve || skill.powerpve,
         orderedFormNames = [...formNames].sort();
           powerpvp: skill.weapon.powerpvp || skill.powerpvp,
        // Se sabemos a forma atual, reorganiza para começar por ela
           energy: skill.weapon.energy || skill.energy,
        if (currentForm) {
          cooldown: skill.weapon.cooldown || skill.cooldown,
           const currentIdx = orderedFormNames.indexOf(currentForm);
         };
           if (currentIdx !== -1) {
            orderedFormNames = [
              ...orderedFormNames.slice(currentIdx),
              ...orderedFormNames.slice(0, currentIdx),
            ];
          }
         }
       }
       }


      // 5. Level: weapon ou normal
       const currentIdx = orderedFormNames.indexOf(currentForm);
       const level =
      if (currentIdx === -1) return; // Forma não encontrada
        weaponMode && skill.weapon?.level
 
          ? skill.weapon.level.toString().trim()
      const nextIdx = (currentIdx + 1) % orderedFormNames.length;
          : (skill.level || "").toString().trim();
      const nextForm = orderedFormNames[nextIdx];


      currentForm = nextForm;
      // Atualiza barra de skills (que vai remover o active depois da animação)
      updateSkillsBarForForm(nextForm, formsData[nextForm], changeFormIcon);
    }
    function snapshotIconData(icon) {
      const img = icon.querySelector("img");
      const iconURL = img ? img.src : "";
      const subsRaw = icon.dataset.subs || icon.getAttribute("data-subs") || "";
      let subs = null;
      try {
        subs = subsRaw ? JSON.parse(subsRaw) : null;
      } catch {
        subs = null;
      }
      let flags = null;
      if (icon.dataset.flags) {
        try {
          flags = JSON.parse(icon.dataset.flags);
        } catch (e) { }
      }
      let weapon = null;
      if (icon.dataset.weapon) {
        try {
          weapon = JSON.parse(icon.dataset.weapon);
        } catch (e) { }
      }
       return {
       return {
         title,
         name: icon.dataset.nome || icon.dataset.name || "",
         desc,
        index: icon.dataset.index || "",
         video,
        level: icon.dataset.level || "",
         attrs,
         desc: icon.dataset.desc || "",
         flags: skill.flags,
        descPt: icon.dataset.descPt || "",
         weapon: skill.weapon,
         descEn: icon.dataset.descEn || "",
         level,
        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 || "",
       };
       };
     }
     }


    // applyInheritance: aplica herança de atributos (nunca herda descrição ou vídeo)
     function updateSkillsBarForForm(formName, formData, changeFormIconEl) {
     function applyInheritance(sub, mainSkills) {
       const iconBar = document.querySelector(".icon-bar");
       // NOVO SISTEMA: herança explícita
       if (!iconBar || !formData || !formData.skills) return;
      // inherit_from: DE qual skill herdar (obrigatório para herdar)
      // inherit_fields: O QUE herdar (array de campos, obrigatório para herdar)
       // Se não especificar ambos, não herda nada (mais seguro)


       const inheritFrom = sub.inherit_from_id || sub.inherit_from;
      // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
       const inheritFields = sub.inherit_fields || [];
       const allFormSkillNames = new Set();
      Object.values(formsData).forEach((form) => {
        if (form.order && Array.isArray(form.order)) {
          form.order.forEach((skillName) => allFormSkillNames.add(skillName));
        }
       });


       // Converte para set para busca rápida
       // Remove skills de forma antigas (que não são fixas) com animação de saída
      const inheritFieldsSet = new Set(inheritFields);


       // Busca skill principal
       const existingIcons = Array.from(
       let main = null;
        iconBar.querySelectorAll(".skill-icon[data-index]")
       if (inheritFrom && mainSkills) {
       );
         // FASE 5: Tenta por ID primeiro (fonte primária)
      const iconsToRemove = [];
         if (mainSkills.byId && mainSkills.byId.has(inheritFrom)) {
       existingIcons.forEach((icon) => {
           main = mainSkills.byId.get(inheritFrom);
         const name = (icon.dataset.nome || "").trim();
         if (name && allFormSkillNames.has(name)) {
           iconsToRemove.push(icon);
         }
         }
         // Fallback: tenta por nome (compatibilidade legado)
      });
         if (!main && mainSkills.byName && mainSkills.byName.has(inheritFrom)) {
 
           main = mainSkills.byName.get(inheritFrom);
      // Anima saída das skills antigas
        }
      iconsToRemove.forEach((icon) => {
        // Tenta por índice (compatibilidade com sistema antigo refM)
        icon.style.transition = "opacity .15s ease, transform .15s ease";
         if (!main && mainSkills.byIndex) {
        icon.style.opacity = "0";
           const refIndex = ((sub.refM || sub.m || sub.M || "") + "").trim();
         icon.style.transform = "translateY(-6px)";
           if (refIndex && mainSkills.byIndex.has(refIndex)) {
      });
             main = mainSkills.byIndex.get(refIndex);
 
      // 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));
           }
           }
          if (!main && refIndex) {
        });
            const numIndex = parseInt(refIndex, 10);
 
            if (!isNaN(numIndex) && mainSkills.byIndex.has(numIndex)) {
        // Procura a próxima skill fixa (que não está em forms)
              main = mainSkills.byIndex.get(numIndex);
        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;
           }
           }
         }
         }
       }
       }


       // Se não tem inherit_from ou main, não herda nada
      if (!changeFormIcon || !nextFixedSkillIcon) {
       if (!inheritFrom || !main) {
        console.warn(
         return sub;
          "[Forms] Skill com form_switch ou próxima skill fixa não encontrada"
        );
        return;
      }
 
      const formSkills = formData.skills || [];
      const formOrder = formData.order || [];
 
      // Primeira skill da forma (vai ANTES do Change Form)
      const firstSkillName = formOrder[0];
      const firstSkillData = formSkills.find((s) => s.name === firstSkillName);
 
      // Terceira skill da forma (vai DEPOIS do Change Form, ANTES do Guard Point)
      const thirdSkillName = formOrder[1]; // Segunda na ordem = terceira skill (índice 1)
      const thirdSkillData = formSkills.find((s) => s.name === thirdSkillName);
 
      // Quinta skill da forma (vai DEPOIS do Guard Point)
      const fifthSkillName = formOrder[2]; // Terceira na ordem = quinta skill (índice 2)
      const fifthSkillData = formSkills.find((s) => s.name === fifthSkillName);
 
       // Cria fragments para inserir
      const firstFragment = document.createDocumentFragment();
      const thirdFragment = document.createDocumentFragment();
      const fifthFragment = document.createDocumentFragment();
 
      if (firstSkillData) {
        const iconElement = createSkillIconElement(firstSkillData, 1);
        firstFragment.appendChild(iconElement);
      }
       if (thirdSkillData) {
        const changeFormIndex = parseInt(
          changeFormIcon.dataset.index || "2",
          10
        );
        const iconElement = createSkillIconElement(
          thirdSkillData,
          changeFormIndex + 1
        );
        thirdFragment.appendChild(iconElement);
      }
      if (fifthSkillData) {
         const nextFixedSkillIndex = parseInt(
          nextFixedSkillIcon.dataset.index || "4",
          10
        );
        const iconElement = createSkillIconElement(
          fifthSkillData,
          nextFixedSkillIndex + 1
        );
        fifthFragment.appendChild(iconElement);
       }
       }


       // Função auxiliar: verifica se campo DEVE ser herdado
       // Remove os ícones antigos do DOM após animação
       const shouldInheritField = (fieldName) => {
       setTimeout(() => {
        return inheritFieldsSet.has(fieldName);
        iconsToRemove.forEach((icon) => {
      };
          if (icon.parentNode) {
            icon.remove();
          }
        });
 
        // Insere a primeira skill ANTES da skill com form_switch
        if (firstFragment.hasChildNodes()) {
          iconBar.insertBefore(firstFragment, changeFormIcon);
        }
 
        // Insere a terceira skill DEPOIS da skill com form_switch, ANTES da próxima skill fixa
        if (thirdFragment.hasChildNodes()) {
          iconBar.insertBefore(thirdFragment, nextFixedSkillIcon);
        }
 
        // Insere a quinta skill DEPOIS da próxima skill fixa
        if (fifthFragment.hasChildNodes()) {
          if (nextFixedSkillIcon.nextSibling) {
            iconBar.insertBefore(fifthFragment, nextFixedSkillIcon.nextSibling);
          } else {
            iconBar.appendChild(fifthFragment);
          }
        }
 
        // Anima entrada das novas skills (similar a animateIconsBarEntrance)
        const newIconsInBar = Array.from(
          iconBar.querySelectorAll(".skill-icon[data-index]")
        ).filter((icon) => {
          const name = (icon.dataset.nome || "").trim();
          return name && allFormSkillNames.has(name);
        });
 
        newIconsInBar.forEach((icon, idx) => {
          icon.style.opacity = "0";
          icon.style.transform = "translateY(6px)";
          requestAnimationFrame(() => {
            setTimeout(() => {
              icon.style.transition = "opacity .18s ease, transform .18s ease";
              icon.style.opacity = "1";
              icon.style.transform = "translateY(0)";
            }, idx * 24);
          });
        });
 
        // Remove slots de descrição das skills de forma antigas e cria novos
        const descBox = document.querySelector(".skills-details .desc-box");
        if (descBox) {
          // Encontra os slots de descrição dos elementos fixos
          const changeFormIndex = parseInt(
            changeFormIcon.dataset.index || "1",
            10
          );
          const nextFixedSkillIndex = parseInt(
            nextFixedSkillIcon.dataset.index || "1",
            10
          );
          const changeFormDescSlot = descBox.querySelector(
            `.skill-desc[data-index="${changeFormIndex}"]`
          );
          const nextFixedSkillDescSlot = descBox.querySelector(
            `.skill-desc[data-index="${nextFixedSkillIndex}"]`
          );
 
          // Cria slot para primeira skill (antes do Change Form)
          if (firstSkillData && changeFormDescSlot) {
            const descSlot = document.createElement("div");
            descSlot.className = "skill-desc";
            descSlot.setAttribute("data-index", changeFormIndex);
            descBox.insertBefore(descSlot, changeFormDescSlot);
          }
 
          // Cria slot para terceira skill (depois da skill com form_switch, antes da próxima skill fixa)
          if (thirdSkillData && nextFixedSkillDescSlot) {
            const descSlot = document.createElement("div");
            descSlot.className = "skill-desc";
            descSlot.setAttribute("data-index", nextFixedSkillIndex);
            descBox.insertBefore(descSlot, nextFixedSkillDescSlot);
          }
 
          // Cria slot para quinta skill (depois da próxima skill fixa)
          if (fifthSkillData && nextFixedSkillDescSlot) {
            const descSlot = document.createElement("div");
            descSlot.className = "skill-desc";
            descSlot.setAttribute("data-index", nextFixedSkillIndex + 1);
            if (nextFixedSkillDescSlot.nextSibling) {
              descBox.insertBefore(
                descSlot,
                nextFixedSkillDescSlot.nextSibling
              );
            } else {
              descBox.appendChild(descSlot);
            }
          }
        }
 
        // Re-numera todas as skills na ordem do DOM
        const allIcons = Array.from(
          iconBar.querySelectorAll(".skill-icon[data-index]")
        );
        let currentIndex = 1;
        allIcons.forEach((icon) => {
          const oldIndex = icon.dataset.index;
          icon.setAttribute("data-index", currentIndex);


      // Helper para verificar se um valor existe e não é string vazia
          // Atualiza slot de descrição
      const hasValue = (val) => {
          if (descBox && oldIndex) {
        if (val === undefined || val === null) return false;
            const descSlot = descBox.querySelector(
        if (typeof val === "number") return !isNaN(val);
              `.skill-desc[data-index="${oldIndex}"]`
        const str = String(val).trim();
            );
        return str !== "" && str !== "NaN";
            if (descSlot) {
      };
              descSlot.setAttribute("data-index", currentIndex);
            }
          }
          currentIndex++;
        });


      // Vídeo NUNCA é herdado da skill principal
        // Re-wire eventos
      const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
        wireClicksForCurrentBar();
      const finalVideo = hasOwnVideo ? sub.video || "" : "";
        wireTooltipsForNewIcons();


      return {
         // Remove active do Change Form após animação completar
        ...sub,
         setTimeout(() => {
        name: sub.name || main.name,
           if (changeFormIconEl) {
         // Herda apenas se campo estiver em inherit_fields
             changeFormIconEl.classList.remove("active");
        icon:
           }
          sub.icon && sub.icon !== ""
         }, newIconsInBar.length * 24 + 180);
            ? sub.icon
       }, 150);
            : shouldInheritField("icon")
              ? main.icon || ""
              : "",
         level: hasValue(sub.level)
          ? sub.level
          : shouldInheritField("level")
            ? main.level
            : "",
        video: finalVideo, // Nunca herda
        powerpve:
          sub.powerpve !== undefined && sub.powerpve !== null
            ? sub.powerpve
            : shouldInheritField("powerpve")
              ? main.powerpve
              : undefined,
        powerpvp:
           sub.powerpvp !== undefined && sub.powerpvp !== null
            ? sub.powerpvp
            : shouldInheritField("powerpvp")
              ? main.powerpvp
              : undefined,
        cooldown:
          sub.cooldown !== undefined && sub.cooldown !== null
            ? sub.cooldown
             : shouldInheritField("cooldown")
              ? main.cooldown
              : undefined,
        energy:
          sub.energy !== undefined && sub.energy !== null
            ? sub.energy
            : shouldInheritField("energy")
              ? main.energy
              : undefined,
        // Descrição: sempre vem da subskill, nunca herda
        // PROTEÇÃO TOTAL: NUNCA copia descrição do main, mesmo que subskill não tenha
        descPt: sub.desc_i18n?.pt || sub.descPt || undefined,
        descEn: sub.desc_i18n?.en || sub.descEn || undefined,
        descEs: sub.desc_i18n?.es || sub.descEs || undefined,
        descPl: sub.desc_i18n?.pl || sub.descPl || undefined,
        desc_i18n: sub.desc_i18n || null,
        // GARANTIA: Remove qualquer campo legado que possa ter sido copiado
        desc: undefined,
        flags:
           sub.flags || (shouldInheritField("flags") ? main.flags : undefined),
         weapon:
          sub.weapon ||
          (shouldInheritField("weapon") ? main.weapon : undefined),
        back:
          sub.back !== undefined
            ? sub.back
            : shouldInheritField("back")
              ? main.back
              : undefined,
        // Preserva campos de herança
        inherit_from: inheritFrom,
        inherit_fields: inheritFields,
       };
     }
     }


     function filePathURL(fileName) {
     function createSkillIconElement(skill, index) {
       // Evita requisições para valores vazios
      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 (
       if (
         !fileName ||
         skill.level &&
         fileName.trim() === ""
        skill.level !== "" &&
         skill.level.toUpperCase() !== "NIVEL"
       ) {
       ) {
         return "";
         iconWrap.setAttribute("data-level", skill.level);
       }
       }
       const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
 
      const base =
       if (skill.desc_i18n) {
         window.mw && mw.util && typeof mw.util.wikiScript === "function"
         if (skill.desc_i18n.pt)
          ? mw.util.wikiScript()
          iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
          : window.mw && mw.config
        if (skill.desc_i18n.en)
            ? mw.config.get("wgScript") || "/index.php"
          iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
            : "/index.php";
        if (skill.desc_i18n.es)
      // Garante HTTPS para evitar Mixed Content
          iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
      let url = `${base}?title=Especial:FilePath/${f}`;
        if (skill.desc_i18n.pl)
      if (window.location.protocol === "https:" && url.startsWith("http://")) {
          iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
        url = url.replace("http://", "https://");
       }
       }
      return url;
    }


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


    function preloadImage(iconFile) {
      if (
      const url = filePathURL(iconFile || "");
        skill.weapon &&
      if (imagePreloadCache.has(url)) {
        typeof skill.weapon === "object" &&
         return imagePreloadCache.get(url);
        Object.keys(skill.weapon).length > 0
      ) {
         iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
       }
       }
       const promise = new Promise((resolve, reject) => {
       if (skill.effect && typeof skill.effect === "object") {
         const img = new Image();
         try {
        img.decoding = "async";
          iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
         img.loading = "eager";
         } catch (e) { }
        img.referrerPolicy = "same-origin";
      }
        img.onload = () => resolve(url);
 
        img.onerror = () => resolve(url);
      const img = document.createElement("img");
        img.src = url;
      img.className = "skill-icon-img";
      });
      img.src = filePathURL(skill.icon || "");
       imagePreloadCache.set(url, promise);
      img.alt = "";
       return promise;
       iconWrap.appendChild(img);
 
       return iconWrap;
     }
     }


     function getLabels() {
     function createSkillIcon(skill, index, container) {
       const skillsRoot = document.getElementById("skills");
      // Cria ícone similar ao que é gerado pelo servidor
       const i18nMap = skillsRoot
       const iconWrap = document.createElement("div");
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       iconWrap.className = "skill-icon";
        : {};
      iconWrap.setAttribute("data-index", index);
       const raw = (
       iconWrap.setAttribute("data-nome", skill.name || "");
        document.documentElement.lang ||
       iconWrap.setAttribute("data-desc", "");
        skillsRoot?.dataset.i18nDefault ||
       iconWrap.setAttribute(
        "pt"
         "data-atr",
      ).toLowerCase();
         makeAttrString(
       const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
           skill.powerpve,
       return (
           skill.powerpvp,
         i18nMap[lang] ||
           skill.energy,
         i18nMap.pt || {
           skill.cooldown
           cooldown: "Recarga",
        )
           energy_gain: "Ganho de energia",
      );
           energy_cost: "Custo de energia",
      iconWrap.setAttribute(
           power: "Poder",
        "data-video",
          power_pvp: "Poder PvP",
        skill.video ? filePathURL(skill.video) : ""
          level: "Nível",
        }
       );
       );
    }
      iconWrap.setAttribute("data-video-preload", "auto");
      iconWrap.setAttribute("data-icon-file", skill.icon || "");
      iconWrap.setAttribute("data-video-file", skill.video || "");


    // Verifica se o modo weapon está ativo
      if (
    function isWeaponModeOn() {
        skill.level &&
      try {
         skill.level !== "" &&
         return localStorage.getItem("glaWeaponEnabled") === "1";
        skill.level.toUpperCase() !== "NIVEL"
       } catch (e) {
       ) {
         return false;
         iconWrap.setAttribute("data-level", skill.level);
       }
       }
    }


    // Retorna os atributos corretos (weapon ou normal)
      if (skill.desc_i18n) {
    function getEffectiveAttrs(s) {
        if (skill.desc_i18n.pt)
      const weaponOn = isWeaponModeOn();
          iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
      if (weaponOn && s.weapon) {
        if (skill.desc_i18n.en)
        return {
           iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
           powerpve: s.weapon.powerpve || s.powerpve,
        if (skill.desc_i18n.es)
           powerpvp: s.weapon.powerpvp || s.powerpvp,
           iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
          energy: s.weapon.energy || s.energy,
        if (skill.desc_i18n.pl)
           cooldown: s.weapon.cooldown || s.cooldown,
           iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
        };
       }
       }
      return {
        powerpve: s.powerpve,
        powerpvp: s.powerpvp,
        energy: s.energy,
        cooldown: s.cooldown,
      };
    }


    // Retorna a descrição correta (weapon ou normal)
      if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
    // Aceita tanto desc_i18n quanto desc para compatibilidade
        iconWrap.setAttribute("data-subs", JSON.stringify(skill.subs));
    function getEffectiveDesc(s) {
       }
      const weaponOn = isWeaponModeOn();
       if (
      const raw = (document.documentElement.lang || "pt").toLowerCase();
        skill.suborder &&
      const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
        Array.isArray(skill.suborder) &&
 
         skill.suborder.length > 0
       // Para weapon: aceita tanto desc_i18n quanto desc
      ) {
       if (weaponOn && s.weapon) {
        iconWrap.setAttribute("data-suborder", JSON.stringify(skill.suborder));
         const wDesc = s.weapon.desc_i18n || s.weapon.desc;
        if (wDesc) {
          return wDesc[lang] || wDesc.pt || wDesc.en || "";
        }
       }
       }


       // Descrição: sempre vem da skill/subskill, nunca herda
       if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
      const base = s.desc_i18n || {};
         iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
      if (base && Object.keys(base).length > 0) {
         return base[lang] || base.pt || base.en || "";
       }
       }


      // Fallback para campos individuais
      const descI18n = {
        pt: s.descPt || "",
        en: s.descEn || "",
        es: s.descEs || "",
        pl: s.descPl || "",
      };
      return descI18n[lang] || descI18n.pt || descI18n.en || "";
    }
    // Retorna o vídeo correto (weapon ou normal)
    function getEffectiveVideo(s) {
      const weaponOn = isWeaponModeOn();
       if (
       if (
         weaponOn &&
         skill.weapon &&
        s.weapon &&
         typeof skill.weapon === "object" &&
         s.weapon.video &&
         Object.keys(skill.weapon).length > 0
         s.weapon.video.trim() !== ""
       ) {
       ) {
         return s.weapon.video;
         iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
       }
       }
       return s.video || "";
       if (skill.effect && typeof skill.effect === "object") {
        try {
          iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
        } catch (e) { }
      }
 
      const img = document.createElement("img");
      img.className = "skill-icon-img";
      img.src = filePathURL(skill.icon || "");
      img.alt = "";
      iconWrap.appendChild(img);
 
      container.appendChild(iconWrap);
     }
     }


    // Função única para obtenção de vídeo de subskill - baseada em URL
     function makeAttrString(pve, pvp, energy, cd) {
     function getOrCreateSubskillVideo(videoURL) {
       const parts = [
       if (!videoURL || videoURL.trim() === "") return null;
        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(", ");
    }


      const normalizedURL = normalizeFileURL(videoURL);
    function filePathURL(fileName) {
       if (!normalizedURL || normalizedURL.trim() === "") return null;
      // Evita requisições para valores vazios
 
       if (!fileName || fileName.trim() === "") {
      // 1. Tenta cache local primeiro (compatibilidade)
         return "";
      if (subskillVideoElementCache.has(normalizedURL)) {
         logSubVideo("getOrCreateSubskillVideo: local cache hit", {
          videoURL: normalizedURL,
        });
        return subskillVideoElementCache.get(normalizedURL);
       }
       }
 
       const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
       // 2. Tenta cache global de subskills (window.__subskillVideosCache)
       const base =
       const globalCache = window.__subskillVideosCache;
        window.mw && mw.util && typeof mw.util.wikiScript === "function"
      if (globalCache && globalCache instanceof Map) {
          ? mw.util.wikiScript()
        // O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
          : window.mw && window.mw.config
        // Precisamos buscar por URL normalizada
            ? mw.config.get("wgScript") || "/index.php"
        for (const [key, video] of globalCache.entries()) {
            : "/index.php";
          const src = video.querySelector("source");
      // Garante HTTPS para evitar Mixed Content
          if (src && src.src === normalizedURL) {
      let url = `${base}?title=Especial:FilePath/${f}`;
            logSubVideo("getOrCreateSubskillVideo: global cache hit", {
      if (window.location.protocol === "https:" && url.startsWith("http://")) {
              videoURL: normalizedURL,
        url = url.replace("http://", "https://");
              key,
            });
            // Adiciona ao cache local também para acesso rápido
            subskillVideoElementCache.set(normalizedURL, video);
            return video;
          }
        }
       }
       }
 
      return url;
       // 3. REMOVIDO: criação lazy - todos os vídeos devem estar pré-carregados
    }
      // Se vídeo não foi encontrado, é um erro (não deve criar novo)
    function slugify(s) {
       console.warn("[Subskills] Vídeo não encontrado no cache:", {
       if (!s) return "";
         videoURL: normalizedURL,
      return String(s)
         originalURL: videoURL,
        .toLowerCase()
       });
        .normalize("NFD")
       return null;
        .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) {
     function renderSubAttrs(s, L) {
       const chip = (label, val) =>
       const chip = (label, val) =>
         val
         val
Linha 524: Linha 1 056:
       return rows.length ? `<div class="attr-list">${rows.join("")}</div>` : "";
       return rows.length ? `<div class="attr-list">${rows.join("")}</div>` : "";
     }
     }
 
    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) {
     function renderFlagsRow(flags) {
      const map = {
       const arr = (flags || []).filter(Boolean).map((k) => String(k || "").trim().toLowerCase());
        aggro: "Enemyaggro-icon.png",
        bridge: "Bridgemaker-icon.png",
        wall: "Destroywall-icon.png",
        quickcast: "Quickcast-icon.png",
        wallpass: "Passthroughwall-icon.png",
      };
       const arr = (flags || [])
        .filter(Boolean)
        .map((k) => String(k || "").trim().toLowerCase());
       const arrFiltered = arr.filter(Boolean);
       const arrFiltered = arr.filter(Boolean);
       if (!arrFiltered.length) return "";
       if (!arrFiltered.length) return "";
      const cacheKey = arrFiltered.join("|");
      if (flagRowCache.has(cacheKey)) {
        return flagRowCache.get(cacheKey);
      }
       const items = arrFiltered
       const items = arrFiltered
         .map((k) => {
         .map((k) => {
           const file = map[k];
           const url = getFlagIconURL(k);
           if (!file) return "";
           return url
          return `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(file)}">`;
            ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">`
            : "";
         })
         })
        .filter(Boolean)
         .join("");
         .join("");
       return items ? `<div class="skill-flags" role="group" aria-label="Características">${items}</div>` : "";
       const html = items
        ? `<div class="skill-flags" role="group" aria-label="Características">${items}</div>`
        : "";
      if (html) flagRowCache.set(cacheKey, html);
      return html;
     }
     }
     function applyFlagTooltips(container) {
     function applyFlagTooltips(container) {
       const skillsRoot = document.getElementById("skills");
       const skillsRoot = document.getElementById("skills");
Linha 556: Linha 1 093:
         pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
         pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
       } catch (e) { }
       } catch (e) { }
       const raw = (document.documentElement.lang || "pt").toLowerCase();
       const lang = getLangKey();
      const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
       const dict = pack[lang] || pack.pt || {};
       const dict = pack[lang] || pack.pt || {};
       const flags = container.querySelectorAll(
       const flags = container.querySelectorAll(
Linha 564: Linha 1 100:
       const tooltip = window.__globalSkillTooltip;
       const tooltip = window.__globalSkillTooltip;
       if (!tooltip) return;
       if (!tooltip) return;
       flags.forEach((el) => {
       flags.forEach((el) => {
         const key = el.getAttribute("data-flag");
         const key = el.getAttribute("data-flag");
Linha 573: Linha 1 108:
         el.setAttribute("aria-label", tip);
         el.setAttribute("aria-label", tip);
         if (el.hasAttribute("title")) el.removeAttribute("title");
         if (el.hasAttribute("title")) el.removeAttribute("title");
         el.addEventListener("mouseenter", () => {
         el.addEventListener("mouseenter", () => {
           const tipEl = document.querySelector(".skill-tooltip");
           const tipEl = document.querySelector(".skill-tooltip");
Linha 596: Linha 1 130:
     }
     }


     function ensureRail(iconsBar) {
    // ====== Skill/Subskill inheritance helpers ======
       const rail = iconsBar.closest(".top-rail");
    const mainSkillsMeta = {
       if (!rail) {
      byIndex: new Map(),
         return null;
      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);
    }


       if (!subRail) {
    function extractFileNameFromURL(url) {
        subRail = document.createElement("div");
       if (!url) return "";
        subRail.className = "subskills-rail collapsed hidden";
      const match = String(url).match(/(?:FilePath\/)([^&?]+)/i);
        rail.appendChild(subRail);
      return match ? decodeURIComponent(match[1]) : "";
      }
    }


      if (!subBar) {
    function parseAttrString(raw) {
        subBar = document.createElement("div");
      const parts = (raw || "").split(",").map((v) => v.trim());
         subBar.className = "subicon-bar";
      const safe = (idx) => {
         subRail.appendChild(subBar);
         const val = parts[idx] || "";
      }
        return val && val !== "-" ? val : "";
      };
      return {
        powerpve: safe(0),
        powerpvp: safe(1),
        energy: safe(2),
         cooldown: safe(3),
      };
    }


      if (!spacer) {
    function hasText(value) {
        spacer = document.createElement("div");
      return typeof value === "string"
         spacer.className = "subskills-spacer";
         ? value.trim() !== ""
         rail.parentNode.insertBefore(spacer, rail.nextSibling);
         : value !== undefined && value !== null;
      }
    }


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


     // Função para mostrar vídeo de subskill usando cache baseado em URL
     function buildMainSkillsMeta(nodes) {
    function showSubVideo(videoURL, videoBox) {
      if (mainSkillsMeta.ready) {
      logSubVideo("showSubVideo called", {
        return mainSkillsMeta;
         videoURL,
      }
         videoBoxExists: !!videoBox,
      (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;
    }


       if (!videoBox) return;
    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"
      );


       // Ocultar todos os vídeos existentes no container
       // Suporta refS (novo) e refM (legado)
       videoBox
       const refS = ((sub.refS || sub.S || sub.s || "") + "").trim();
        .querySelectorAll('video.skill-video[data-sub="1"]')
      const refIndex = ((sub.refM || sub.M || sub.m || "") + "").trim();
        .forEach((v) => {
      let name = (sub.name || sub.n || "").trim();
          v.style.display = "none";
      let main = null;
        });


       // Obter vídeo via getOrCreateSubskillVideo (única forma de obter vídeos)
       // Se herança está desabilitada, não busca a skill principal
       const video = getOrCreateSubskillVideo(videoURL);
       if (!shouldInherit) {
        return { ...sub };
      }


       if (!video) {
      // Primeiro tenta por refS
         logSubVideo("no video found for URL, hiding box", { videoURL });
      if (refS) {
         videoBox.style.display = "none";
        main = meta.byIndex.get(refS) || meta.byIndex.get(parseInt(refS, 10));
         return;
      }
      // 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;
       }
       }


       // Se o vídeo não estiver conectado ao DOM, anexá-lo ao container
       const hydrated = { ...sub };
      if (!video.isConnected || video.parentNode !== videoBox) {
      if (!name && main.name) {
        logSubVideo("video not in DOM, appending", {
         name = main.name;
          videoURL,
          isConnected: video.isConnected,
          parentNode: video.parentNode,
        });
        if (video.parentNode) {
          video.parentNode.removeChild(video);
         }
        videoBox.appendChild(video);
       }
       }
      hydrated.name = name || hydrated.name || main.name || "";
      hydrated.icon = pickFilled(hydrated.icon, main.icon || "");
      hydrated.level = pickFilled(hydrated.level, main.level || "");


       logSubVideo("showing video", {
       // Vídeo NUNCA é herdado da skill principal
        videoURL,
      const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
        readyState: video.readyState,
      hydrated.video = hasOwnVideo ? sub.video || "" : "";
        paused: video.paused,
        currentTime: video.currentTime,
      });


       // Exibir o vídeo
       hydrated.powerpve = pickFilled(hydrated.powerpve, main.powerpve || "");
       videoBox.style.display = "block";
       hydrated.powerpvp = pickFilled(hydrated.powerpvp, main.powerpvp || "");
       video.style.display = "block";
      hydrated.energy = pickFilled(hydrated.energy, main.energy || "");
       hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || "");


       // Reinicia o vídeo para feedback visual imediato
       // Flags: herda da skill principal se a subskill não tiver
       try {
       if (
         video.currentTime = 0;
         (!hydrated.flags || !Array.isArray(hydrated.flags) || hydrated.flags.length === 0) &&
       } catch (e) { }
        main.flags && Array.isArray(main.flags) && main.flags.length > 0
       ) {
        hydrated.flags = main.flags;
      }


       // Tenta dar play se o vídeo já está pronto
       // Descrição: sempre vem da subskill, nunca herda
       if (video.readyState >= 2) {
      // PROTEÇÃO TOTAL: Remove qualquer descrição que possa ter sido copiada do main
         video
       if (
          .play()
        !sub.desc &&
          .then(() => {
        !sub.descPt &&
            logSubVideo("play() resolved", {
        !sub.descEn &&
              videoURL,
         !sub.descEs &&
              currentTime: video.currentTime,
        !sub.descPl &&
              readyState: video.readyState,
        !sub.desc_i18n
            });
      ) {
          })
        hydrated.desc = undefined;
          .catch((err) => {
        hydrated.descPt = undefined;
            logSubVideo("play() rejected", { videoURL, error: err });
        hydrated.descEn = undefined;
          });
        hydrated.descEs = undefined;
        hydrated.descPl = undefined;
        hydrated.desc_i18n = undefined;
       } else {
       } else {
         // Se não está pronto ainda, espera o evento canplay
         // Se subskill tem descrição, normaliza para desc_i18n
         logSubVideo("video not ready, waiting for canplay", {
         if (
           videoURL,
           !hydrated.desc_i18n &&
           readyState: video.readyState,
           (hydrated.descPt ||
        });
            hydrated.descEn ||
         const onCanPlay = () => {
            hydrated.descEs ||
          video.removeEventListener("canplay", onCanPlay);
            hydrated.descPl)
          video.play().catch(() => { });
         ) {
        };
          hydrated.desc_i18n = {
        video.addEventListener("canplay", onCanPlay, { once: true });
            pt: hydrated.descPt || "",
            en: hydrated.descEn || "",
            es: hydrated.descEs || "",
            pl: hydrated.descPl || "",
          };
        }
       }
       }
      return hydrated;
     }
     }


     // Funções antigas removidas - usar getOrCreateSubskillVideo() em vez disso
     function inheritSubskillTree(subs, meta) {
    // REMOVIDO: ensureSubVideoCached
      if (!Array.isArray(subs)) return [];
     // REMOVIDO: ensureSubVideoInCache
      return subs.map((sub) => {
        const hydrated = inheritSubskillFromMain(sub, meta);
        if (Array.isArray(hydrated.subs)) {
          hydrated.subs = inheritSubskillTree(hydrated.subs, meta);
        }
        return hydrated;
      });
     }


     api.refreshCurrentSubSafe = function () {
     function collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet) {
       const btn = document.querySelector(".subskills-rail .subicon.active");
       if (!Array.isArray(subs)) return;
      if (!btn) return false;
      subs.forEach((sub) => {
      const had = document.body.dataset.suppressSkillPlay;
        const iconURL = normalizeFileURL(sub.icon || "", "");
      document.body.dataset.suppressSkillPlay = "1";
        if (iconURL && iconURL !== "") iconsSet.add(iconURL);
      try {
        // Vídeo normal
        btn.dispatchEvent(new Event("click", { bubbles: true }));
        if (sub.video && sub.video.trim() !== "") {
      } finally {
          const videoURL = normalizeFileURL(sub.video);
         if (had) document.body.dataset.suppressSkillPlay = had;
          if (videoURL) videosSet.add(videoURL);
         else delete document.body.dataset.suppressSkillPlay;
        }
        // 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;
       }
       }
       return true;
       const iconsSet = new Set();
    };
       const videosSet = new Set();
 
       const flagsSet = new Set();
    // Função auxiliar para aplicar classes de weapon nas subskills renderizadas
       iconItems.forEach((el) => {
    const applyWeaponClassesToSubskills = () => {
         const img = el.querySelector("img");
       const weaponOn = isWeaponModeOn();
         if (img && img.src) {
       // Busca TODAS as subskills com weapon
          iconsSet.add(img.src);
      let weaponSubs = document.querySelectorAll(".subicon[data-weapon]");
        } else if (el.dataset.iconFile) {
 
          const iconURL = normalizeFileURL(el.dataset.iconFile);
       weaponSubs.forEach((el) => {
          if (iconURL) iconsSet.add(iconURL);
         const weaponData = el.getAttribute("data-weapon");
        }
         // Verifica se o weapon não está vazio (não é '{}' ou vazio)
        // Vídeo normal da skill
        let hasValidWeapon = false;
        const videoRaw = (
        if (weaponData && weaponData.trim() !== "" && weaponData !== "{}") {
          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 {
           try {
             const weaponObj = JSON.parse(weaponData);
             const weaponData = JSON.parse(el.dataset.weapon);
             if (
             if (
               weaponObj &&
               weaponData &&
               typeof weaponObj === "object" &&
               weaponData.video &&
               Object.keys(weaponObj).length > 0
               weaponData.video.trim() !== ""
             ) {
             ) {
               hasValidWeapon = true;
               const weaponVideoURL = normalizeFileURL(weaponData.video);
              if (weaponVideoURL) videosSet.add(weaponVideoURL);
             }
             }
           } catch (e) {
           } catch (e) { }
             // Se não for JSON válido, não considera como weapon válido
        }
           }
        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) {
         if (weaponOn && hasValidWeapon) {
           try {
          el.classList.add("has-weapon-available");
             const subs = JSON.parse(el.dataset.subs);
           if (el.classList.contains("active")) {
            collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet);
             el.classList.add("weapon-equipped");
           } catch (e) { }
          }
        } else {
          el.classList.remove("has-weapon-available");
           el.classList.remove("weapon-equipped");
         }
         }
       });
       });
    };
      Object.keys(FLAG_ICON_FILES).forEach((flagKey) => {
 
         const url = getFlagIconURL(flagKey);
    api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) {
         if (url) flagsSet.add(url);
      logSubVideo("api.renderBarFrom called", {
         hasEl: !!el,
         hasIconsBar: !!iconsBar,
        hasDescBox: !!descBox,
        hasVideoBox: !!videoBox,
        parentIndex: el?.dataset?.index || "unknown",
       });
       });
      const manifest = {
        icons: iconsSet,
        videos: videosSet,
        flags: flagsSet,
        ready: true,
      };
      window.__skillAssetManifest = manifest;
      return manifest;
    }
    const subskillVideosCache = new Map();
    window.__subskillVideosCache = subskillVideosCache;


      const rail = ensureRail(iconsBar);
    // Cache de vídeos que falharam (404) para evitar tentativas repetidas
      if (!rail) {
    const failedVideosCache = new Set();
        logSubVideo("api.renderBarFrom: no rail found, returning");
    const missingVideosReported = new Set(); // Para avisar apenas uma vez sobre vídeos faltantes
        return;
      }


      const rawSubs = el.getAttribute("data-subs") || "";
    // Cache de parsing JSON para evitar re-parsear os mesmos dados
       const rawOrder = el.getAttribute("data-suborder") || "";
    const jsonParseCache = new WeakMap();
       const parentIdx = el.dataset.index || "";
    function getCachedJSON(el, key) {
 
      if (!el) return null;
      logSubVideo("api.renderBarFrom: parsing subs", {
       const cache = jsonParseCache.get(el) || {};
        parentIdx,
      if (cache[key] !== undefined) return cache[key];
        hasRawSubs: !!rawSubs.trim(),
       const raw = el.dataset[key] || el.getAttribute(`data-${key}`);
        rawSubsLength: rawSubs.length,
       if (!raw) {
      });
         cache[key] = null;
 
         jsonParseCache.set(el, cache);
       if (!rawSubs.trim()) {
         return null;
         if (subRail) subRail.classList.add("collapsed");
         if (subRail) subRail.classList.add("hidden");
        if (subBar) subBar.innerHTML = "";
        if (spacer) spacer.style.height = "0px";
         return;
       }
       }
      let subs;
       try {
       try {
         subs = JSON.parse(rawSubs);
         const parsed = JSON.parse(raw);
       } catch {
        cache[key] = parsed;
         subs = [];
        jsonParseCache.set(el, cache);
        return parsed;
       } catch (e) {
         cache[key] = null;
        jsonParseCache.set(el, cache);
        return null;
       }
       }
    }


      // NORMALIZADOR: Converte weaponPacked para weapon se necessário
    let assetManifest = null;
       subs = subs.map((sub) => {
    const skillsTab = $("#skills");
         // Se tem weaponPacked mas não tem weapon, tenta parsear
    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 (
         if (
           sub.weaponPacked &&
           !iconBar.parentElement ||
           !sub.weapon &&
           !iconBar.parentElement.classList.contains("icon-scroll-x")
          typeof sub.weaponPacked === "string" &&
          sub.weaponPacked.trim() !== ""
         ) {
         ) {
           const parts = sub.weaponPacked.split("~");
           const scrollWrapper = document.createElement("div");
           if (parts.length >= 2) {
           scrollWrapper.className = "icon-scroll-x";
            sub.weapon = {
          scrollWrapper.appendChild(iconBar);
              icon: parts[0] || "",
           rail.appendChild(scrollWrapper);
              powerpve: parts[1] || null,
        } else {
              powerpvp: parts[2] || null,
          rail.appendChild(iconBar.parentElement);
              cooldown: parts[3] || null,
              video: parts[4] || "",
              energy: parts[5] || null,
            };
            // Remove valores vazios
            Object.keys(sub.weapon).forEach((k) => {
              if (sub.weapon[k] === "" || sub.weapon[k] === null) {
                delete sub.weapon[k];
              }
            });
           }
        }
        // Garante que weapon seja objeto válido
        if (sub.weapon && typeof sub.weapon === "string") {
          // Tenta parsear como JSON primeiro
          try {
            sub.weapon = JSON.parse(sub.weapon);
          } catch {
            // Se falhar, tenta formato ~
            const parts = sub.weapon.split("~");
            if (parts.length >= 2) {
              sub.weapon = {
                icon: parts[0] || "",
                powerpve: parts[1] || null,
                powerpvp: parts[2] || null,
                cooldown: parts[3] || null,
                video: parts[4] || "",
                energy: parts[5] || null,
              };
              Object.keys(sub.weapon).forEach((k) => {
                if (sub.weapon[k] === "" || sub.weapon[k] === null) {
                  delete sub.weapon[k];
                }
              });
            } else {
              sub.weapon = null;
            }
          }
         }
         }
         return sub;
         skillsTab.prepend(rail);
      });
 
      if (!Array.isArray(subs) || subs.length === 0) {
        subRail.classList.add("collapsed");
        subRail.classList.add("hidden");
        subBar.innerHTML = "";
        if (spacer) spacer.style.height = "0px";
        return;
       }
       }


       // Busca mapa das skills principais para herança
       // Busca skills-container criado pelo Lua
       const mainSkills = getMainSkillsMap();
       const skillsContainer = skillsTab.querySelector(".skills-container");


       // Debug: log dos dados antes da herança
       // Busca skills-details e video-container (podem estar dentro de skills-container ou soltos)
       if (SUBVIDEO_DEBUG && subs.length > 0) {
      let details = skillsTab.querySelector(".skills-details");
         logSubVideo("subs before inheritance", {
       if (!details && skillsContainer) {
          subsCount: subs.length,
         details = skillsContainer.querySelector(".skills-details");
          firstSub: JSON.stringify(subs[0]),
      }
          allSubs: subs.map((s) => ({
      if (!details) {
            refM: s.refM || s.M || s.m,
        details = document.createElement("div");
            video: s.video,
         details.className = "skills-details";
            hasVideo: s.hasOwnProperty("video"),
          })),
         });
       }
       }


       // Aplica herança ANTES de processar
       // Busca ou cria desc-box dentro de details
       subs = subs.map((sub) => applyInheritance(sub, mainSkills));
       let descBoxEl = details.querySelector(".desc-box");
      if (!descBoxEl) {
        descBoxEl = document.createElement("div");
        descBoxEl.className = "desc-box";
        details.appendChild(descBoxEl);
      }


       // Remove subskills que ficaram sem nome após herança
       // Busca video-container
       subs = subs.filter((s) => (s.name || s.n || "").trim() !== "");
       let videoContainer = skillsTab.querySelector(".video-container");
      if (!videoContainer && skillsContainer) {
        videoContainer = skillsContainer.querySelector(".video-container");
      }
      if (!videoContainer) {
        videoContainer = document.createElement("div");
        videoContainer.className = "video-container";
      }


       subRail.classList.add("hidden");
       // Cria ou atualiza content-card skills-grid (estrutura esperada pelo CSS)
      subBar.innerHTML = "";
      let card = skillsTab.querySelector(".content-card.skills-grid");
      if (!card) {
        card = document.createElement("div");
        card.className = "content-card skills-grid";
        skillsTab.appendChild(card);
      }


       // Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
       // Move details e videoContainer para dentro do card (estrutura correta)
       const subsById = new Map();
       // Remove skills-container se existir (não é necessário para o layout)
       subs.forEach((s) => {
       if (skillsContainer && skillsContainer.parentNode) {
         const id = s.id || s.name || s.n || "";
         // Move os elementos filhos de skills-container para o card
         if (id) {
        if (details.parentNode === skillsContainer) {
           subsById.set(id, s);
          skillsContainer.removeChild(details);
        }
        if (videoContainer.parentNode === skillsContainer) {
          skillsContainer.removeChild(videoContainer);
        }
        // Remove skills-container se estiver vazio
         if (skillsContainer.children.length === 0) {
           skillsContainer.remove();
         }
         }
       });
       }


       // Usa a ordem natural das subskills após herança
       // Garante que details e videoContainer estão no card na ordem correta
       let order = subs.map((s) => s.id || s.name || s.n || "");
       // skills-details deve vir ANTES de video-container para o grid funcionar
       if (rawOrder.trim()) {
       if (details.parentNode !== card) {
         try {
         // Se videoContainer já está no card, insere details antes dele
          const preferred = JSON.parse(rawOrder);
        if (videoContainer.parentNode === card) {
          if (Array.isArray(preferred) && preferred.length) {
          card.insertBefore(details, videoContainer);
            // Tenta por ID primeiro, depois por nome (compatibilidade)
        } else {
            const byName = new Map(subs.map((s) => [s.name || s.n || "", s]));
          card.appendChild(details);
            const byId = new Map(
        }
              subs.map((s) => [s.id || s.name || s.n || "", s])
      }
            );
      if (videoContainer.parentNode !== card) {
            order = preferred
        card.appendChild(videoContainer);
              .filter((n) => {
                // Tenta encontrar por nome (compatibilidade com dados antigos)
                if (byName.has(n)) {
                  const found = byName.get(n);
                  return found.id || found.name || found.n || "";
                }
                // Tenta por ID
                return byId.has(n);
              })
              .map((n) => {
                // Retorna o ID se existir, senão o nome
                const byName = new Map(
                  subs.map((s) => [s.name || s.n || "", s])
                );
                if (byName.has(n)) {
                  const found = byName.get(n);
                  return found.id || found.name || found.n || "";
                }
                return n;
              });
          }
        } catch { }
       }
       }


       // Pré-carrega TODOS os ícones ANTES de renderizar
       // Garante ordem: details primeiro, videoContainer depois
       const iconPreloadPromises = [];
       if (details.parentNode === card && videoContainer.parentNode === card) {
      order.forEach((idOrName) => {
         if (details.nextSibling !== videoContainer) {
        const s =
           card.insertBefore(details, videoContainer);
          subsById.get(idOrName) ||
          subs.find((x) => (x.name || x.n || "") === idOrName);
         if (s && s.icon && s.icon.trim() !== "") {
           iconPreloadPromises.push(preloadImage(s.icon));
         }
         }
      }
    }
    // 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) {
       // Vídeos serão criados e carregados sob demanda quando necessário
        return true;
 
       }
       // Função para renderizar a barra de subskills
       // Verifica subskills
       const renderSubskillsBar = () => {
       for (const el of iconItems) {
         order.forEach((idOrName) => {
         const subsRaw = el.getAttribute("data-subs");
          // CORREÇÃO: Usa ID para lookup (evita colisão de nomes)
        if (!subsRaw) continue;
          const s =
        try {
            subsById.get(idOrName) ||
           const subs = JSON.parse(subsRaw);
            subs.find((x) => (x.name || x.n || "") === idOrName);
           if (
          if (!s) {
             Array.isArray(subs) &&
            return;
             subs.some(
          }
              (s) =>
 
                s &&
           const item = document.createElement("div");
                 s.weapon &&
          item.className = "subicon";
                 typeof s.weapon === "object" &&
          item.title = s.name || s.n || "";
                 Object.keys(s.weapon).length > 0
 
             )
          // CORREÇÃO: Adiciona data-skill-id para lookups únicos
           ) {
          const skillId = s.id || s.name || s.n || "";
             return true;
           if (skillId) {
             item.dataset.skillId = skillId;
          }
 
          const slugify =
             window.__skillSlugify ||
            ((str) =>
              (str || "")
                 .toLowerCase()
                 .replace(/[^\w]+/g, "-")
                 .replace(/^-+|-+$/g, ""));
          item.dataset.slug = slugify(s.name || s.n || "");
 
          logSubVideo("creating subicon element", {
            subName: (s.name || s.n || "").trim(),
            parentIdx,
             className: item.className,
          });
 
          const img = document.createElement("img");
           img.alt = "";
          const iconUrl = filePathURL(s.icon || "");
          if (iconUrl) {
             img.src = iconUrl;
           }
           }
        } 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.decoding = "async";
           img.loading = "lazy";
           img.loading = "eager";
           img.width = 42;
           img.referrerPolicy = "same-origin";
           img.height = 42;
           img.src = url;
           item.appendChild(img);
           imagePreloadCache.set(url, img);
 
        });
          // Verifica weapon de forma mais robusta
      }
          const hasWeapon =
    }
            s.weapon &&
    // Busca descBox e videoBox após a estrutura estar organizada
            ((typeof s.weapon === "object" &&
    const descBox = $("#skills") ? $(".desc-box", $("#skills")) : null;
              Object.keys(s.weapon).length > 0) ||
    let videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
              (typeof s.weapon === "string" && s.weapon.trim() !== ""));
    // Busca dinâmica do videoBox (pode não existir no momento da inicialização)
 
    function getVideoBox() {
          const subName = (s.name || s.n || "").trim();
      if (videoBox && videoBox.isConnected) return videoBox;
 
      const skillsRoot = document.getElementById("skills");
           if (hasWeapon) {
      if (!skillsRoot) return null;
            // Normaliza weapon se for string
      videoBox = skillsRoot.querySelector(".video-container") ||
            let weaponObj = s.weapon;
        skillsRoot.querySelector(".skills-container .video-container") ||
            if (typeof weaponObj === "string") {
        skillsRoot.querySelector(".content-card .video-container");
              try {
      return videoBox;
                weaponObj = JSON.parse(weaponObj);
    }
              } catch {
    const videosCache = new Map();
                // Se falhar, tenta formato ~
    const nestedVideoElByIcon = new WeakMap();
                const parts = weaponObj.split("~");
    const barStack = [];
                if (parts.length >= 2) {
    window.__barStack = barStack;
                  weaponObj = {
    let initialBarSnapshot = null;
                    icon: parts[0] || "",
    let totalVideos = 0,
                    powerpve: parts[1] || null,
      loadedVideos = 0,
                    powerpvp: parts[2] || null,
      autoplay = false;
                    cooldown: parts[3] || null,
    window.__lastActiveSkillIcon = null;
                    video: parts[4] || "",
    let userHasInteracted = false;
                    energy: parts[5] || null,
    let globalWeaponEnabled = false;
                  };
    try {
                  Object.keys(weaponObj).forEach((k) => {
      if (localStorage.getItem("glaWeaponEnabled") === "1") {
                    if (weaponObj[k] === "" || weaponObj[k] === null) {
        globalWeaponEnabled = true;
                      delete weaponObj[k];
      }
                    }
    } catch (err) { }
                   });
    const weaponStateListeners = new Set();
                 } else {
    let showWeaponPopupFn = null;
                   weaponObj = 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
             }
             }
 
          }
             if (
        });
              weaponObj &&
      // Busca em subskills (dentro de subskills-rail)
              typeof weaponObj === "object" &&
      document
              Object.keys(weaponObj).length > 0
        .querySelectorAll(".subskills-rail .subicon[data-weapon]")
            ) {
        .forEach((el) => {
              try {
          const weapon = el.dataset.weapon;
                 item.dataset.weapon = JSON.stringify(weaponObj);
          if (weapon && weapon.trim() !== "" && weapon !== "{}") {
              } catch (e) {
             try {
                console.error(
              const weaponObj = JSON.parse(weapon);
                  "[Subskills] Erro ao serializar weapon de subskill",
              if (
                  subName,
                weaponObj &&
                  e
                typeof weaponObj === "object" &&
                );
                Object.keys(weaponObj).length > 0
              }
              ) {
 
                 if (!el.classList.contains("has-weapon-available")) {
              // Aplica classe inicial se o toggle já está ativo
                  el.classList.add("has-weapon-available");
              if (isWeaponModeOn()) {
                }
                item.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%; }
        }


          // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
        /* Animação sutil para brilho */
          logSubVideo("attaching click handler to subicon", {
        @keyframes weapon-glow-breathe {
             subName: (s.name || s.n || "").trim(),
            0%, 100% { opacity: 0.7; }
             parentIdx,
            50% { opacity: 1; }
             itemClassName: item.className,
        }
          });
 
          item.addEventListener("click", () => {
        /* Animação do gradiente (sem girar a borda) */
             const L = getLabels();
        @property --effect-spin {
             syntax: "<angle>";
             inherits: false;
             initial-value: 0deg;
        }
        @keyframes effect-border-spin {
            from { --effect-spin: 0deg; }
             to { --effect-spin: 360deg; }
        }


             // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
        /* ===== ÍCONE COM ARMA - Só mostra efeitos quando weapon-mode-on está ativo ===== */
             const skillId = item.dataset.skillId || s.id || s.name || s.n || "";
        /* Quando NÃO está em weapon-mode-on, os ícones com arma devem ser normais (mesma borda padrão) */
             if (!skillId) {
        .character-box .top-rail.skills .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
              console.error(
             /* Remove borda sólida base que aparece na transição do toggle */
                "[Subskills] Click handler: skillId não encontrado",
             border-color: transparent !important;
                { item, s }
            box-shadow: none !important;
              );
        }
              return;
        .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;
        }


            // CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
        /* ===== MODO WEAPON ON - INATIVO ===== */
             let currentSub = subsById.get(skillId);
        .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::after {
             let lookupMethod = "id";
             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;
        }


            if (!currentSub) {
        .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::before {
              // Fallback: tenta por nome (compatibilidade com dados antigos)
            pointer-events: none !important;
              const subName = (s.name || s.n || "").trim();
            inset: 0 !important;
              currentSub = subs.find((x) => (x.name || x.n || "") === subName);
            border-radius: inherit !important;
              lookupMethod = "name_fallback";
            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;
        }


              if (!currentSub) {
        /* ===== MODO WEAPON ON - ATIVO ===== */
                console.error(
        .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active {
                  "[Subskills] Click handler: subskill não encontrada",
            transform: scale(1.10) !important;
                  {
            z-index: 5 !important;
                    skillId,
        }
                    subName,
                    availableIds: Array.from(subsById.keys()),
                  }
                );
                return;
              }
            }


            const subName = (currentSub.name || currentSub.n || "").trim();
        .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;
        }


            logSubVideo("subicon click HANDLER EXECUTED", {
        .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before {
              skillId,
            pointer-events: none !important;
              subName,
            inset: 0 !important;
              parentIdx,
            border-radius: inherit !important;
              weaponMode: isWeaponModeOn(),
            z-index: 1 !important;
              hasWeaponDataAttr: !!item.dataset.weapon,
            animation: weapon-glow-breathe 1.5s ease-in-out infinite !important;
              rawWeaponDataset: item.dataset.weapon || null,
            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;
        }


             // Lê weapon diretamente do atributo data-weapon
        .character-box .top-rail.skills .icon-bar {
             let subWeaponData = null;
            position: relative;
             if (item.dataset.weapon) {
        }
              try {
        .character-box .top-rail.skills .icon-bar .skill-icon {
                 const parsed = JSON.parse(item.dataset.weapon);
             z-index: 2;
                 // Só considera weapon válido se for um objeto não vazio
        }
                 if (
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active) {
                  parsed &&
            border-color: transparent !important;
                  typeof parsed === "object" &&
            outline: 2px solid rgba(210, 60, 60, 0.95) !important;
                  Object.keys(parsed).length > 0
            outline-offset: -2px;
                 ) {
            animation: effect-child-outline 1.6s ease-in-out infinite !important;
                  subWeaponData = parsed;
        }
                 }
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
              } catch (e) {
            box-shadow:
                 subWeaponData = null;
                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;
             }
             }
            const hasSubWeapon = !!subWeaponData;
        }
 
        @keyframes effect-line-glow {
             const weaponOn = isWeaponModeOn();
             0% {
            const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
                stroke: rgba(140, 25, 25, 0.65);
 
                filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
            logSubVideo("weapon status for subclick", {
              subName,
              weaponEquipped,
              hasSubWeaponData: !!subWeaponData,
            });
 
            // FASE 4: Usa resolveSkillView para resolver tudo de forma determinística
            const raw = (document.documentElement.lang || "pt").toLowerCase();
            const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
 
            // Prepara skill com weapon se necessário
            const skillForResolution = { ...currentSub };
            if (weaponEquipped && subWeaponData) {
              skillForResolution.weapon = subWeaponData;
             }
             }
 
             50% {
             // Resolve tudo usando o resolver único
                stroke: rgba(20, 0, 0, 0.85);
            const resolved = resolveSkillView(skillForResolution, {
                filter: drop-shadow(0 0 9px rgba(255, 70, 70, 0.65));
              lang,
              weaponMode: weaponEquipped,
              mainSkills,
            });
 
            // Usa dados resolvidos
            const chosen = resolved.desc;
            const attrsObj = resolved.attrs;
            const level = resolved.level;
 
            let flagsHTML = "";
            if (
              Array.isArray(currentSub.flags) &&
              currentSub.flags.length > 0
            ) {
              flagsHTML = renderFlagsRow(currentSub.flags);
             }
             }
 
             100% {
             if (descBox) {
                 stroke: rgba(140, 25, 25, 0.65);
              descBox.innerHTML = `<div class="skill-title"><h3>${resolved.title || currentSub.name || currentSub.n || ""
                 filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
                 }</h3></div>${level
                  ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>`
                  : ""
                }${renderSubAttrs(attrsObj, L)}<div class="desc">${(
                  chosen || ""
                 ).replace(/'''(.*?)'''/g, "<b>$1</b>")}</div>`;
             }
             }
 
        }
             if (videoBox) {
        @keyframes effect-line-return {
              const oldFlags = videoBox.querySelector(".skill-flags");
             0% {
              if (oldFlags) oldFlags.remove();
                stroke-dasharray: var(--effect-line-length, 0) var(--effect-line-length, 0);
              if (flagsHTML) {
                 stroke-dashoffset: 0;
                 videoBox.insertAdjacentHTML("beforeend", flagsHTML);
                 opacity: 1;
                 applyFlagTooltips(videoBox);
              }
             }
             }
 
             100% {
            // FASE 4: Vídeo vem do resolved (já resolvido)
                stroke-dasharray: 0 var(--effect-line-length, 0);
            const effectiveVideo = resolved.video;
                stroke-dashoffset: calc(var(--effect-line-length, 0) * -1);
 
                 opacity: 0;
             logSubVideo("effectiveVideo for sub", {
              subName: currentSub.name || currentSub.n,
              skillId,
              parentIdx,
              effectiveVideo,
              weaponEquipped,
              rawBaseVideo: currentSub.video,
              rawWeaponVideo: subWeaponData?.video,
            });
 
            if (!effectiveVideo || effectiveVideo.trim() === "") {
              if (videoBox) videoBox.style.display = "none";
            } else {
              logSubVideo("calling showSubVideo from click handler", {
                 effectiveVideo,
              });
 
              // Usa getOrCreateSubskillVideo internamente - nunca recria se já existe
              showSubVideo(effectiveVideo, videoBox);
             }
             }
 
        }
            Array.from(subBar.children).forEach((c) => {
        @media (prefers-reduced-motion: reduce) {
              c.classList.remove("active");
            .character-box .top-rail.skills .effect-lines-layer .effect-line {
              c.classList.remove("weapon-equipped");
                animation: none;
            });
            item.classList.add("active");
 
            // Aplica weapon-equipped se tem weapon e está ativo
            if (weaponEquipped) {
              item.classList.add("weapon-equipped");
             }
             }
            window.__lastActiveSkillIcon = item;
        }
          });


          if (window.__globalSkillTooltip) {
        .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle).active::before {
             const { show, hide, measureAndPos, lockUntil } =
             pointer-events: none !important;
              window.__globalSkillTooltip;
            inset: 0 !important;
             const label = item.title || "";
             border-radius: inherit !important;
             item.setAttribute("aria-label", label);
             z-index: 1 !important;
             if (item.hasAttribute("title")) item.removeAttribute("title");
             animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
             item.addEventListener("mouseenter", () => show(item, label));
             box-shadow:
            item.addEventListener("mousemove", () => {
                0 0 10px 0 rgba(220, 220, 220, 0.5),
              if (performance.now() >= lockUntil.value) measureAndPos(item);
                0 0 16px 0 rgba(190, 190, 190, 0.4),
            });
                0 0 22px 0 rgba(220, 220, 220, 0.3) !important;
            item.addEventListener("click", () => {
             opacity: 1 !important;
              lockUntil.value = performance.now() + 240;
        }
              measureAndPos(item);
             });
            item.addEventListener("mouseleave", hide);
          }


          subBar.appendChild(item);
        /* ========== ESTILOS DE SWAP DE PERSONAGENS (Sistema Genérico) ========== */
          logSubVideo("subicon appended to subBar", {
        /* Skills desabilitadas quando o personagem ativo não pode usá-las */
            subName: (s.name || s.n || "").trim(),
        .character-box .top-rail.skills .icon-bar .skill-icon.disabled-skill {
            parentIdx,
            opacity: 0.3 !important;
            subBarChildrenCount: subBar.children.length,
            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;
        // Aplica classes de weapon nas subskills recém-renderizadas
      }
        applyWeaponClassesToSubskills();
      const rail = document.querySelector(".top-rail.skills");
 
      const scrollWrap = rail ? rail.querySelector(".icon-scroll-x") : null;
        // Dispara evento para notificar que subskills estão prontas
      if (scrollWrap && scrollWrap !== effectLinesScrollWrap) {
        window.dispatchEvent(
         if (effectLinesScrollWrap) {
          new CustomEvent("gla:subskills:ready", {
           effectLinesScrollWrap.removeEventListener(
            detail: { count: order.length },
             "scroll",
          })
             handleEffectLinesScroll
        );
 
        // Remove listener anterior se existir (evita duplicação)
         if (subBar._weaponToggleListener) {
           window.removeEventListener(
             "gla:weaponToggled",
             subBar._weaponToggleListener
           );
           );
         }
         }
        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;


        // Cria novo listener que opera no subBar atual
      bindEffectLinesEvents();
        subBar._weaponToggleListener = (e) => {
          const enabled = e.detail?.enabled ?? false;


          // Aplica classes usando a função auxiliar
      const barRect = iconBar.getBoundingClientRect();
          applyWeaponClassesToSubskills();
      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");


          // Atualiza a subskill ativa se houver
      const srcRect = effectState.sourceIcon.getBoundingClientRect();
          setTimeout(() => {
      const startX =
            const activeSub =
        srcRect.left + srcRect.width / 2 - barRect.left + iconBar.scrollLeft;
              subBar.querySelector(".subicon[data-weapon].active") ||
      const startY = srcRect.top + srcRect.height / 2 - barRect.top;
              subBar.querySelector(".subicon.active");
            if (activeSub) {
              activeSub.dispatchEvent(new Event("click", { bubbles: true }));
            } else {
              api.refreshCurrentSubSafe();
            }
          }, 50);
        };


         window.addEventListener(
      const allIcons = Array.from(
          "gla:weaponToggled",
         document.querySelectorAll(".icon-bar .skill-icon[data-index]")
          subBar._weaponToggleListener
      ).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);
      });


        requestAnimationFrame(() => {
      if (!targets.length) {
          subRail.classList.remove("collapsed");
        clearEffectLines();
          subRail.classList.remove("hidden");
         return;
          const h = subRail.offsetHeight || 48;
       }
          if (spacer) spacer.style.height = h + "px";
         });
       };


       // Chama a função de renderização após pré-carregar ícones
       const frag = document.createDocumentFragment();
      Promise.all(iconPreloadPromises)
      const baselinePadding = 10;
        .then(() => {
      const baselineExtra = 12;
          setTimeout(() => {
      const baselineY = Math.max(
            renderSubskillsBar();
        startY,
          }, 10);
         height - baselinePadding + baselineExtra
         })
      );
        .catch(() => {
      const targetPoints = targets.map((target) => {
          setTimeout(() => {
        const tgtRect = target.getBoundingClientRect();
            renderSubskillsBar();
        const endX =
           }, 10);
           tgtRect.left + tgtRect.width / 2 - barRect.left + iconBar.scrollLeft;
         });
         const endY = tgtRect.top + tgtRect.height / 2 - barRect.top;
    };
        return { x: endX, y: endY };
 
    api.hideAll = function (videoBox) {
      const videos =
        videoBox?.querySelectorAll('.skill-video[data-sub="1"]') || [];
      logSubVideo("api.hideAll called", {
        videoBoxExists: !!videoBox,
        videoCount: videos.length,
       });
       });
       videos.forEach((v) => {
       const xs = targetPoints.map((p) => p.x);
         try {
      const minX = Math.min(startX, ...xs);
           v.pause();
      const maxX = Math.max(startX, ...xs);
         } catch { }
      effectLinesLastState = {
         v.style.display = "none";
         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(" ");


    window.renderSubskillsBarFrom = function (el, ctx) {
      const glow = document.createElementNS(
       api.renderBarFrom(el, ctx);
        "http://www.w3.org/2000/svg",
    };
        "path"
      );
       glow.setAttribute("d", d);
      glow.classList.add("effect-line", "effect-line-glow");


    api.preloadAllSubskillImages = function () {
       const core = document.createElementNS(
       const allSkillIcons = document.querySelectorAll(
         "http://www.w3.org/2000/svg",
         ".icon-bar .skill-icon[data-subs]"
        "path"
       );
       );
       const preloadPromises = [];
       core.setAttribute("d", d);
       let totalImages = 0;
      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`);


      allSkillIcons.forEach((icon) => {
        const core = document.createElementNS(
         try {
          "http://www.w3.org/2000/svg",
           const subsRaw = icon.getAttribute("data-subs");
          "path"
           if (!subsRaw) return;
        );
          const subs = JSON.parse(subsRaw);
        core.setAttribute("d", d);
          if (!Array.isArray(subs)) return;
         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`);


          subs.forEach((s) => {
        frag.appendChild(glow);
            if (s && s.icon && s.icon.trim() !== "") {
         frag.appendChild(core);
              preloadPromises.push(preloadImage(s.icon));
              totalImages++;
            }
            if (s && Array.isArray(s.subs)) {
              s.subs.forEach((nested) => {
                if (nested && nested.icon && nested.icon.trim() !== "") {
                  preloadPromises.push(preloadImage(nested.icon));
                  totalImages++;
                }
              });
            }
          });
         } catch (e) {
          console.error("[Subskills] preloadAllSubskillImages error:", e);
        }
       });
       });
      layer.replaceChildren(frag);


       if (totalImages > 0) {
      requestAnimationFrame(() => {
         // console.log('[Subskills] preloadAllSubskillImages: pré-carregando', totalImages, 'ícones');
        const paths = Array.from(layer.querySelectorAll("path.effect-line"));
         return Promise.all(preloadPromises).then(() => {
        paths.forEach((path) => {
           // console.log('[Subskills] preloadAllSubskillImages: todos os ícones carregados');
          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();
       }
       }
       return Promise.resolve();
    }
    };
    function clearEffectState() {
       const activeIcon = document.querySelector(
        ".icon-bar .skill-icon.active"
      );
      const activeName = getSkillNameFromIcon(activeIcon);
      const wasAffected =
        activeName && effectState.skills.has(activeName);


    // Função para pré-carregar vídeos de subskills usando getOrCreateSubskillVideo
      animateEffectLinesReturn();
    // Função preloadAllSubskillVideos removida - vídeos são carregados sob demanda
      if (effectState.timer) {
        clearTimeout(effectState.timer);
        effectState.timer = null;
      }
      effectState.skills.clear();
      effectState.videos.clear();
      effectState.expiresAt = 0;
      effectState.sourceIcon = null;
      applyEffectClasses();


     // Função syncVideoCaches removida - não é mais necessária com cache baseado em URL
      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;


    // Inicialização
      effectState.skills = new Set(normalized.skills);
    function init() {
      effectState.videos = normalized.videos;
       logSubVideo("init: starting initialization");
       effectState.expiresAt = Date.now() + normalized.timeMs;
      effectState.sourceIcon = iconEl;


       // Constrói cache das skills principais
       if (effectState.timer) clearTimeout(effectState.timer);
       getMainSkillsMap();
       effectState.timer = setTimeout(() => {
        clearEffectState();
      }, normalized.timeMs + 5);


       // Pré-carrega imagens das subskills IMEDIATAMENTE
       applyEffectClasses();
       api.preloadAllSubskillImages();
    }
    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();


       //         if (video.readyState < 2) {
       // console.log('[Skills DEBUG]', {
       //             video.muted = true;
       //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
       //             video.load();
      //    weaponOn,
       //         }
      //    hasWeaponData: !!weaponData,
       //    });
       //     weaponData: weaponData,
       // }, 500);
       //     baseVideoFile,
       //    baseVideoURL
      // });
 
       const effectVideo = getEffectVideoForIcon(iconEl);
      if (effectVideo && effectVideo.trim() !== "") {
        return effectVideo.trim();
      }


       // Escuta mudanças no localStorage
       if (weaponOn && weaponData) {
      window.addEventListener("storage", (e) => {
        // console.log('[Skills] checking weapon video', {
         if (e.key === "glaWeaponEnabled") {
        //    skillName: iconEl.dataset.nome || iconEl.dataset.name,
           setTimeout(() => api.refreshCurrentSubSafe(), 50);
        //    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();
         }
         }
       });
       }


       // LISTENER GLOBAL: Escuta evento de toggle
       // Sistema genérico de swap: verifica character_videos se houver personagem ativo
       window.addEventListener("gla:weaponToggled", (e) => {
       if (activeCharacter !== null && iconEl.dataset.characterVideos) {
        const enabled = e.detail?.enabled ?? false;
         try {
 
           const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
        // Tenta aplicar classes imediatamente
          const characterVideo = characterVideos[activeCharacter];
        applyWeaponClassesToSubskills();
           if (characterVideo && characterVideo.trim() !== "") {
 
             return characterVideo.trim();
         // Atualiza a subskill ativa se houver
        setTimeout(() => {
           const activeSub =
            document.querySelector(".subicon[data-weapon].active") ||
            document.querySelector(".subicon.active");
           if (activeSub) {
            activeSub.dispatchEvent(new Event("click", { bubbles: true }));
          } else {
             api.refreshCurrentSubSafe();
           }
           }
         }, 50);
         } catch (e) {
       });
          console.warn("[Swap] Erro ao processar character_videos:", e);
        }
       }


       // LISTENER: Escuta quando subskills estão prontas
       // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
      window.addEventListener("gla:subskills:ready", (e) => {
      const result = baseVideoFile || baseVideoURL || "";
        // Aplica classes de weapon se o toggle estiver ativo
      // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
        applyWeaponClassesToSubskills();
       return result;
       });
     }
     }
    function createVideoElement(videoURL, extraAttrs = {}) {
      // Se o vídeo já falhou antes, não cria novo elemento
      if (failedVideosCache.has(videoURL)) {
        return null;
      }


    if (document.readyState === "loading") {
      const v = document.createElement("video");
       document.addEventListener("DOMContentLoaded", () => {
      v.className = "skill-video";
         setTimeout(init, 100);
      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];
       });
       });
    } else {
      // Detectar formato do vídeo pela extensão
       setTimeout(init, 100);
       const ext = (videoURL.split(".").pop() || "").toLowerCase().split("?")[0];
    }
      const mimeTypes = {
  })();
        mp4: "video/mp4",
</script>
        m4v: "video/mp4",
<style>
        webm: "video/webm",
  .subicon-bar {
        ogv: "video/ogg",
    display: flex;
        ogg: "video/ogg",
    gap: 10px;
        mov: "video/quicktime",
    padding: 6px 6px;
      };
    overflow-x: auto;
      const mimeType = mimeTypes[ext] || "video/mp4";
    /* Firefox */
    scrollbar-width: thin;
    scrollbar-color: #ababab transparent;
  }


  .subicon-bar::-webkit-scrollbar {
      const src = document.createElement("source");
    height: 6px;
      src.src = videoURL;
  }
      src.type = mimeType;
      v.appendChild(src);


  .subicon-bar::-webkit-scrollbar-thumb {
      // Fallback para Safari/iOS mais antigos
    background: #151515;
      v.setAttribute("webkit-playsinline", "");
    border-radius: 3px;
      v.setAttribute("x-webkit-airplay", "allow");
  }


  .subicon {
      // Tratamento silencioso de erros - marca como falhado e não tenta mais
    width: var(--icon-size, 42px);
      let errorHandled = false;
    height: var(--icon-size, 42px);
      v.addEventListener(
    border-radius: var(--icon-radius, 10px);
        "error",
    overflow: hidden;
        (e) => {
    position: relative;
          if (errorHandled) return;
    flex: 0 0 auto;
          errorHandled = true;
    cursor: pointer;
          // Marca o vídeo como falhado para não tentar carregar novamente
    isolation: isolate;
          failedVideosCache.add(videoURL);
    filter: brightness(0.92);
          // 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 }
      );


  .subicon img {
      return v;
    width: 100%;
     }
    height: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
     border-radius: inherit;
  }


  .subicon::after {
    // Função recursiva para carregar TODOS os vídeos de subskills (incluindo sub-subskills)
     content: "";
     function preloadSubskillVideosRecursively(
     position: absolute;
      subs,
    inset: 0;
      parentIdx,
    border-radius: inherit;
      parentPath = ""
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-idle, #cfcfcf);
     ) {
    pointer-events: none;
      const vb = getVideoBox();
    z-index: 2;
      if (!vb || !Array.isArray(subs)) return 0;
    transition: box-shadow 0.12s ease;
      let createdCount = 0;
  }
      subs.forEach((s) => {
        const subName = (s.name || s.n || "").trim();
        const currentPath = parentPath ? `${parentPath}:${subName}` : subName;


  .subicon:hover::after {
        // Vídeo normal da subskill
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
        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();
              }
            }
          }
        }


  .subicon:hover {
        // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
    filter: brightness(1);
        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();
              }
            }
          }
        }


  .subicon.active::after {
        // RECURSÃO: processa sub-subskills (SEMPRE processa, mesmo se o vídeo já estiver no cache)
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #ffd95a);
        if (Array.isArray(s.subs) && s.subs.length > 0) {
  }
          createdCount += preloadSubskillVideosRecursively(
            s.subs,
            parentIdx,
            currentPath
          );
        }
      });
      return createdCount;
    }


  .subicon.active {
    function precreateSubskillVideos() {
    transform: scale(1.1);
      if (!getVideoBox()) return;
    z-index: 5;
      let createdCount = 0;
    filter: brightness(1);
      iconItems.forEach((parentIcon) => {
  }
        const subs = getCachedJSON(parentIcon, "subs");
        if (!Array.isArray(subs)) return;
        const parentIdx = parentIcon.dataset.index || "";
        createdCount += preloadSubskillVideosRecursively(subs, parentIdx);
      });
    }


  .subicon.active::before {
     // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
    content: "";
     function preloadAllVideosRecursively() {
    position: absolute;
       const vb = getVideoBox();
     inset: -4px;
      if (!vb || !iconItems.length) return;
    border-radius: calc(var(--icon-radius, 10px) + 4px);
     pointer-events: none;
    z-index: 1;
    opacity: 1;
    box-shadow: 0 0 12px 3px var(--icon-active-glow, rgba(255, 217, 90, 0.3)),
       0 0 0 calc(var(--icon-ring-w, 2px) * 2) var(--icon-active-ring, rgba(255, 217, 90, 0.5));
  }


  .subicon.active img {
      // 1. Carregar vídeos de skills principais
    transform: none !important;
      iconItems.forEach((el) => {
  }
        const idx = el.dataset.index || "";
        if (!idx) return;


  .top-rail.skills {
        // Vídeo normal da skill principal
    position: relative;
        // Prioriza data-video-file (nome do arquivo), depois data-video (pode ser URL completa)
    display: flex;
        let src = (el.dataset.videoFile || "").trim();
    flex-direction: column;
        if (!src) {
    align-items: center;
          // Se não tem videoFile, tenta extrair do video (pode ser URL completa)
    overflow: visible;
          const videoAttr = (el.dataset.video || "").trim();
  }
          if (videoAttr) {
            // Se já é uma URL completa, usa direto; senão normaliza
            if (videoAttr.includes("/") || videoAttr.startsWith("http")) {
              src = videoAttr;
            } else {
              src = videoAttr;
            }
          }
        }
        if (src && !videosCache.has(idx)) {
          const videoURL = normalizeFileURL(src);
          if (videoURL && !failedVideosCache.has(videoURL)) {
            const v = createVideoElement(videoURL, {
              index: idx,
            });
            if (v) {
              // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
              totalVideos++;
              v.style.maxWidth = "100%";
              v.addEventListener(
                "canplaythrough",
                () => {
                  loadedVideos++;
                  if (!userHasInteracted && loadedVideos === 1) {
                    try {
                      v.pause();
                      v.currentTime = 0;
                    } catch (e) { }
                  }
                  if (loadedVideos === totalVideos) autoplay = true;
                },
                {
                  once: true,
                }
              );
              v.addEventListener(
                "error",
                () => {
                  loadedVideos++;
                  if (loadedVideos === totalVideos) autoplay = true;
                },
                {
                  once: true,
                }
              );
              vb.appendChild(v);
              videosCache.set(idx, v);
              nestedVideoElByIcon.set(el, v);
              // Força carregamento imediatamente (apenas uma vez)
              v.load();
            }
          }
        }


  .top-rail.skills .icon-bar {
        // Vídeo de weapon (sempre carrega, independente do toggle)
    margin-bottom: 0;
        const weaponData = getCachedJSON(el, "weapon");
    position: relative;
        if (
    z-index: 2;
          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();
              }
            }
          }
        }
      });


  .subskills-rail {
      // 2. Carregar vídeos de subskills (recursivamente)
    position: absolute;
      precreateSubskillVideos();
    left: 50%;
     }
    transform: translateX(-50%);
    top: calc(100% - 1px);
    z-index: 3;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    width: auto;
    max-width: 100%;
    padding: 3px 5px;
    background: rgba(0, 0, 0, 0.38);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0 0 10px 10px;
    box-shadow: 0 3px 9px rgba(0, 0, 0, 0.22);
     -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    overflow: hidden;
    transition: opacity 0.14s ease, transform 0.14s ease;
    opacity: 1;
  }


  .subskills-rail::before {
     // Função para pré-carregar TODOS os ícones recursivamente
    content: "";
     function preloadAllIconsRecursively() {
     position: absolute;
      const iconCache = new Set();
    top: -6px;
     left: 0;
    right: 0;
    height: 6px;
    background: linear-gradient(to bottom,
        rgba(0, 0, 0, 0.2),
        rgba(0, 0, 0, 0));
    pointer-events: none;
  }


  .subskills-rail.collapsed {
      // Função recursiva para processar subskills
    opacity: 0;
      function processSubskillsRecursively(subs) {
    pointer-events: none;
        if (!Array.isArray(subs)) return;
    transform: translate(-50%, -6px);
        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);
          }
        });
      }


  .subskills-rail.hidden {
      // Carregar ícones de skills principais
    visibility: hidden;
      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);
          }
        }


  .subskills-spacer {
        // Carregar ícones de subskills recursivamente
    height: 0;
        const subs = getCachedJSON(el, "subs");
    transition: height 0.2s ease;
        if (Array.isArray(subs)) {
  }
          processSubskillsRecursively(subs);
        }
      });
    }


  .subskills-rail .subicon-bar {
     // Função principal que carrega TUDO imediatamente
     display: inline-flex;
     function preloadAllAssets() {
     align-items: center;
      // Carregar TUDO imediatamente - sem lazy loading
    gap: 0;
      // 1. Carregar TODOS os vídeos (principais, subskills, weapon, sub-subskills)
    overflow-x: auto;
      preloadAllVideosRecursively();
    /* Firefox */
    scrollbar-width: thin;
    scrollbar-color: #ababab transparent;
  }


  .subskills-rail .subicon-bar::-webkit-scrollbar {
      // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
    height: 6px;
      preloadAllIconsRecursively();
  }
    }


  .subskills-rail .subicon-bar::-webkit-scrollbar-thumb {
    // Executa pré-carregamento imediatamente
     background: #151515;
    preloadAllAssets();
    border-radius: 3px;
    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)
        : "";


  .subskills-rail .subicon {
      // console.log('[Skills] showVideoForIcon chamado', {
     width: 42px;
      //     skillName: el.dataset.nome || el.dataset.name,
     height: 42px;
      //     weaponOn,
     border-radius: 6px;
      //     isWeaponVideo,
     position: relative;
      //     effectiveVideo: getEffectiveSkillVideoFromIcon(el)
    overflow: hidden;
      // });
    flex: 0 0 auto;
      const videoKey = isWeaponVideo
    cursor: pointer;
        ? `weapon:${getWeaponKey(el)}`
    isolation: isolate;
        : isEffectVideo
    -webkit-backface-visibility: hidden;
          ? effectKey
    backface-visibility: hidden;
          : el.dataset.index || "";
    transform: translateZ(0);
      const isSubskill = !hasIdx || el.dataset.nested === "1";
  }
      const parentIdx = el.dataset.parentIndex || "";
      const subName =
        el.dataset.subName || el.dataset.nome || el.dataset.name || "";


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


  .subskills-rail .subicon img {
          // Tenta buscar diretamente com o subName
    width: 100%;
          let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
    height: 100%;
          v = subskillVideosCache.get(subWeaponKey);
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
    border-radius: inherit;
  }


  .subskills-rail .subicon::after {
          // Se não encontrou, tenta buscar recursivamente
    content: "";
          if (!v) {
    position: absolute;
            const basePattern = `sub:${parentIdx}:`;
    inset: 0;
            const weaponSuffix = ":weapon";
    border-radius: inherit;
            const searchName = subName.split(":").pop();
    box-shadow: inset 0 0 0 2px var(--icon-idle, #cfcfcf);
            for (const [key, video] of subskillVideosCache.entries()) {
    pointer-events: none;
              if (key.startsWith(basePattern) && key.endsWith(weaponSuffix)) {
    z-index: 2;
                const pathInKey = key.substring(
    transition: box-shadow 0.12s ease;
                  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)


  .subskills-rail .subicon:hover::after {
          // Tenta buscar diretamente com o subName
    box-shadow: inset 0 0 0 2px #e6e6e6;
          let subKey = `sub:${parentIdx}:${subName}`;
  }
          v = subskillVideosCache.get(subKey);


  .subskills-rail .subicon.active::after {
          // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
    box-shadow: inset 0 0 0 2px var(--icon-active, #ffd95a);
          // 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);


  .video-container .skill-video {
            // Se ainda não encontrou, faz busca mais ampla
    width: 100%;
            if (!v) {
    height: auto;
              for (const [key, video] of subskillVideosCache.entries()) {
    aspect-ratio: 16 / 9;
                if (key.startsWith(basePattern)) {
    object-fit: cover;
                  const pathInKey = key.substring(basePattern.length);
    background: #000;
                  // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
    border-radius: 10px;
                  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);
          }
        }
      }


  @media (max-width: 900px) {
      // Se vídeo não foi encontrado no cache, verifica se falhou antes
    .subskills-rail {
      if (!v) {
      position: static;
        // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
      transform: none;
        if (failedVideosCache.has(videoURL)) {
       margin-top: -2px;
          videoBox.style.display = "none";
       border-top: 0;
          return;
       border-radius: 0 0 10px 10px;
        }
        // Para effect, cria vídeo dinamicamente (vídeo alternativo por tempo)
        if (isEffectVideo && videoURL) {
          const baseIndex = el.dataset.index || "";
          const effectCacheKey =
            effectKey || `effect:${effectiveVideo || videoURL}`;
          v = videosCache.get(effectCacheKey);
          if (!v) {
            v = createVideoElement(videoURL, {
              index: baseIndex,
              effect: "1",
            });
            if (v) {
              videoBox.appendChild(v);
              videosCache.set(effectCacheKey, v);
              v.load();
            } else {
              videoBox.style.display = "none";
              return;
            }
          }
        }
        // Para form_switch, cria vídeo dinamicamente (transições de forma são dinâmicas)
        // Usa uma chave única que inclui o nome do arquivo do vídeo para garantir que cada vídeo diferente seja cacheado separadamente
        else if (isFormSwitch && videoURL) {
          const baseIndex = el.dataset.index || "";
          const videoFileName = effectiveVideo || "";
          // Chave única: index + nome do arquivo do vídeo
          const videoKey = baseIndex + ":" + videoFileName;
          v = videosCache.get(videoKey);
          if (!v) {
            v = createVideoElement(videoURL, { index: baseIndex });
            if (v) {
              videoBox.appendChild(v);
              videosCache.set(videoKey, v);
              v.load();
            } else {
              videoBox.style.display = "none";
              return;
            }
          }
        } else {
          // Vídeos normais devem estar pré-carregados
          videoBox.style.display = "none";
          return;
        }
       }
      videoBox.style.display = "block";
      v.style.display = "block";
       try {
        v.currentTime = 0;
       } catch (e) { }
      const suppress = document.body.dataset.suppressSkillPlay === "1";
      if (!suppress) {
        v.play().catch(() => { });
      } else {
        try {
          v.pause();
        } catch (e) { }
      }
     }
     }
    function activateSkill(el, options = {}) {
      const { openSubs = true } = options;
      const tip = document.querySelector(".skill-tooltip");
      if (tip) {
        tip.setAttribute("aria-hidden", "true");
        tip.style.opacity = "0";
        tip.style.left = "-9999px";
        tip.style.top = "-9999px";
      }
      const skillsRoot = document.getElementById("skills");
      const i18nMap = skillsRoot
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
        : {};
      const L = i18nMap[getLangKey()] ||
        i18nMap.pt || {
        cooldown: "Recarga",
        energy_gain: "Ganho de energia",
        energy_cost: "Custo de energia",
        power: "Poder",
        power_pvp: "Poder PvP",
        level: "Nível",
      };
      const name = el.dataset.nome || el.dataset.name || "";
      if (el.dataset.effect) {
        activateEffectFromIcon(el);
      }
      let weaponData = null;
      if (el.dataset.weapon) {
        try {
          const parsed = JSON.parse(el.dataset.weapon);
          // Só considera weapon válido se for um objeto não vazio
          if (
            parsed &&
            typeof parsed === "object" &&
            Object.keys(parsed).length > 0
          ) {
            weaponData = parsed;
          }
        } catch (e) {
          weaponData = null;
        }
      }
      const hasWeapon = !!weaponData;
      const weaponEquipped = hasWeapon && globalWeaponEnabled;
      // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
      let level = (el.dataset.level || "").trim();
      if (weaponEquipped && weaponData) {
        // Verifica se weapon tem level definido (pode ser número ou string)
        const weaponLevel = weaponData.level;
        if (
          weaponLevel !== undefined &&
          weaponLevel !== null &&
          weaponLevel !== ""
        ) {
          level = weaponLevel.toString().trim();
        }
      }
      const lang = getLangKey();
      const baseDescPack = {
        pt: el.dataset.descPt || "",
        en: el.dataset.descEn || "",
        es: el.dataset.descEs || "",
        pl: el.dataset.descPl || "",
      };
      const baseDesc =
        baseDescPack[lang] ||
        baseDescPack.pt ||
        baseDescPack.en ||
        baseDescPack.es ||
        baseDescPack.pl ||
        el.dataset.desc ||
        "";
      // Aceita tanto desc_i18n quanto desc para compatibilidade
      let weaponDescPack = {};
      if (weaponData) {
        if (weaponData.desc_i18n) {
          weaponDescPack = weaponData.desc_i18n;
        } else if (weaponData.desc) {
          weaponDescPack = weaponData.desc;
        } else {
          weaponDescPack = {
            pt: weaponData.descPt || "",
            en: weaponData.descEn || "",
            es: weaponData.descEs || "",
            pl: weaponData.descPl || "",
          };
        }
      }
      const weaponDesc =
        weaponDescPack[lang] ||
        weaponDescPack.pt ||
        weaponDescPack.en ||
        weaponDescPack.es ||
        weaponDescPack.pl ||
        "";
      const chosenDesc = weaponEquipped && weaponDesc ? weaponDesc : baseDesc;
      const descHtml = chosenDesc.replace(/'''(.*?)'''/g, "<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] : "";


    .subskills-spacer {
        // Valores do weapon (substituem os da skill base se existirem)
      height: 0 !important;
        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;


  .skills-rail-wrap {
        // Monta string de atributos mesclados
    position: relative;
        const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(",");
    display: block;
        attrsHTML = renderAttributes(mergedAttrs);
    width: max-content;
      } else {
    margin: 0 auto;
        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";


  /* Subskills com arma disponível - borda vermelha quando inativa */
      // Se for skill de swap, troca personagem (não marca como ativo, não processa como back)
  .character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after {
      if (isSwap && !isFormSwitch) {
    box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important;
        handleSwapCharacter(el);
  }
        // Não marca como ativo (similar ao form_switch)
        return;
      }


  /* Subskill com arma ATIVA - laranja/coral vibrante + brilho forte */
      // Não marca como ativo se for form_switch (Change Form)
  .character-box .top-rail.skills .subicon.has-weapon-available.active {
      if (!isFormSwitch) {
    position: relative;
        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";


  .character-box .top-rail.skills .subicon.has-weapon-available.active::after {
      // Se for form_switch, alterna forma (não processa como back)
    box-shadow: none !important;
       if (isFormSwitch) {
    background: linear-gradient(135deg,
        // Atualiza o data-video-file do ícone com o vídeo correto da transição baseado na forma atual
        #ff5722 0%,
        try {
        #ff7043 20%,
          const formsJSON = skillsRoot.dataset.forms || "{}";
        #ff8a65 40%,
          if (formsJSON && formsJSON !== "{}") {
        #ff7043 60%,
            const tempFormsData = JSON.parse(formsJSON);
        #ff5722 80%,
            const formNames = Object.keys(tempFormsData);
        #ff3d00 100%) !important;
    background-size: 300% 300% !important;
    animation: weapon-icon-border-scan 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;
    filter: drop-shadow(0 0 4px rgba(255, 87, 34, 0.8)) drop-shadow(0 0 8px rgba(255, 87, 34, 0.6)) !important;
  }


  .character-box .top-rail.skills .subicon.has-weapon-available.active::before {
            // Busca vídeo de transição na skill Change Form
    box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.95),
            const formVideosRaw =
      0 0 40px 16px rgba(255, 87, 34, 0.75),
              el.dataset.formVideos || el.getAttribute("data-form-videos");
      0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6) !important;
            if (formVideosRaw) {
    opacity: 1 !important;
              const videos = JSON.parse(formVideosRaw);
  }
              // Se currentForm é null, detecta qual forma está atualmente visível no DOM
              let formForVideo = currentForm;
              if (!formForVideo) {
                // Lê formsData se ainda não foi carregado
                if (Object.keys(formsData).length === 0) {
                  try {
                    formsData = JSON.parse(formsJSON);
                  } catch (e) {
                    formsData = {};
                  }
                }
                formForVideo = detectCurrentForm();
                // Se ainda não conseguiu detectar, usa a primeira como fallback
                if (!formForVideo && formNames.length > 0) {
                  formForVideo = formNames[0];
                }
              }
              const transitionVideo = formForVideo
                ? videos[formForVideo] || ""
                : "";
              if (transitionVideo && transitionVideo.trim() !== "") {
                // Atualiza temporariamente o data-video-file (o sistema usa isso)
                el.dataset.videoFile = transitionVideo;
              }
            }
          }
        } catch (e) {
          console.error("[Forms] Erro ao processar vídeo de transição:", e);
        }


  /* Modo arma ON - efeito animado nos subicons */
        // Usa o sistema normal de vídeo (mesmo que skills normais)
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available {
        showVideoForIcon(el);
    position: relative;
        switchForm();
  }
        return;
      }


  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::after {
      if (isBack && !isFormSwitch && barStack.length) {
     box-shadow: none !important;
        const prev = barStack.pop();
    background: linear-gradient(90deg,
        // Restaura currentForm se estava salvo no snapshot
         rgba(255, 80, 80, 0.9) 0%,
        if (prev.currentForm !== undefined) {
         rgba(255, 120, 60, 1) 25%,
          currentForm = prev.currentForm;
         rgba(255, 80, 80, 0.9) 50%,
        }
         rgba(255, 120, 60, 1) 75%,
        renderBarFromItems(prev);
         rgba(255, 80, 80, 0.9) 100%) !important;
        const btn = document.querySelector(".skills-back-wrapper");
    background-size: 400% 100% !important;
        if (btn) btn.style.display = barStack.length ? "block" : "none";
    animation: weapon-subicon-border-scan 4s linear infinite !important;
        return;
    -webkit-mask: linear-gradient(#fff 0 0) content-box,
      }
       linear-gradient(#fff 0 0) !important;
      if (openSubs && subsRaw && subsRaw.trim() !== "") {
    mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
        if (barStack.length && barStack[barStack.length - 1].parentIcon === el)
    -webkit-mask-composite: xor !important;
          return;
    mask-composite: exclude !important;
        try {
    padding: 2px !important;
          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");


  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::before {
      // Calcula a posição do botão baseado na posição real do top-rail
    content: "";
      function updateBackButtonPosition() {
    position: absolute;
        if (!rail || !backWrap) return;
    inset: -4px;
        const railRect = rail.getBoundingClientRect();
    border-radius: calc(6px + 4px);
        const wrapRect = wrap.getBoundingClientRect();
    pointer-events: none;
        // Calcula a posição relativa do rail dentro do wrap
    z-index: 1;
        const railLeft = railRect.left - wrapRect.left;
    box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
        // Posiciona o wrapper na borda esquerda do rail
      0 0 32px 12px rgba(255, 80, 80, 0.6),
        // O botão interno usa translateX(-97%) para ficar atrás da barra
      0 0 48px 18px rgba(255, 100, 60, 0.4) !important;
        backWrap.style.left = railLeft + "px";
    opacity: 1 !important;
      }
    animation: weapon-subicon-pulse 3s ease-in-out infinite !important;
  }


  /* Subskill ativa com arma - mais intenso */
      // Atualiza a posição quando necessário
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::after {
      if (backWrap.style.display !== "none") {
    background: linear-gradient(135deg,
         // Usa requestAnimationFrame para garantir que o DOM foi atualizado
         #ff3d00 0%,
         requestAnimationFrame(() => {
         #ff5722 15%,
          updateBackButtonPosition();
        #ff7043 30%,
        });
        #ff8a65 45%,
        #ff7043 60%,
        #ff5722 75%,
        #ff3d00 90%,
        #ff5722 100%) !important;
    background-size: 400% 400% !important;
    animation: weapon-icon-border-scan 2.5s linear infinite !important;
    filter: drop-shadow(0 0 6px rgba(255, 87, 34, 1)) drop-shadow(0 0 12px rgba(255, 87, 34, 0.8)) drop-shadow(0 0 18px rgba(255, 120, 50, 0.6)) !important;
  }


  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::before {
        // Recalcula em resize e scroll
    box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
        if (!backWrap.dataset.positionWired) {
      0 0 56px 24px rgba(255, 87, 34, 0.85),
          backWrap.dataset.positionWired = "1";
      0 0 80px 32px rgba(255, 140, 60, 0.6), 0 0 0 5px rgba(255, 87, 34, 0.75) !important;
          const updateOnResize = () => {
    animation: weapon-subicon-pulse-active 2.5s ease-in-out infinite !important;
            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);
        }
      }


  @keyframes weapon-icon-border-scan {
      return btnInner;
    0% {
    }
      background-position: 0% 0%;
    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, "<b>$1</b>");
          const attrsHTML = renderSubAttributesFromObj(s, L);
          return {
            name,
            level: (s.level || "").toString().trim(),
            desc,
            descPt: s.descPt || (s.desc_i18n && s.desc_i18n.pt) || "",
            descEn: s.descEn || (s.desc_i18n && s.desc_i18n.en) || "",
            descEs: s.descEs || (s.desc_i18n && s.desc_i18n.es) || "",
            descPl: s.descPl || (s.desc_i18n && s.desc_i18n.pl) || "",
            attrs: "",
            icon: s.icon || "",
            iconURL: s.icon ? filePathURL(s.icon) : "",
            video: s.video ? filePathURL(s.video) : "",
            subs: Array.isArray(s.subs) ? s.subs : null,
            subattrs: s,
            flags: Array.isArray(s.flags) ? s.flags : null,
            back:
              s.back === true ||
                s.back === "true" ||
                s.back === "yes" ||
                s.back === "1"
                ? "true"
                : null,
            weapon: s.weapon || null,
          };
        });
      const fragment = document.createDocumentFragment();
      items.forEach((it, iIdx) => {
        const node = document.createElement("div");
        node.className = "skill-icon";
        node.dataset.nested = "1";
        node.dataset.nome = it.name || "";
        node.dataset.parentIndex = parentIndexSnapshot;
        // Constrói o caminho completo para sub-subskills
        // IMPORTANTE: O cache NÃO inclui o nome da skill principal no caminho
        // Formato do cache: "SubskillName" ou "SubskillName:SubSubskillName" (sem o nome da skill principal)
        let fullPath = it.name || "";
        if (parentIconEl) {
          // Se o pai é uma subskill (tem nested), adiciona o nome do pai ao caminho
          if (parentIconEl.dataset.nested === "1") {
            // Se o pai tem subName, usa ele (já está no formato correto, sem nome da skill principal)
            if (
              parentIconEl.dataset.subName &&
              parentIconEl.dataset.subName.trim() !== ""
            ) {
              fullPath = `${parentIconEl.dataset.subName}:${it.name || ""}`;
            } else {
              // Se o pai não tem subName, é a primeira subskill, então usa apenas o nome do pai
              const parentName = (
                parentIconEl.dataset.nome ||
                parentIconEl.dataset.name ||
                ""
              ).trim();
              fullPath = `${parentName}:${it.name || ""}`;
            }
          }
          // 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();


     100% {
     // Inicializa o sistema de swap de personagens (genérico)
       background-position: 400% 0%;
    // Aguarda um pouco para garantir que todos os ícones foram renderizados
     }
    setTimeout(() => {
  }
       initializeActiveCharacter();
     }, 100);


  @keyframes weapon-subicon-border-scan {
    const b0 = ensureBackButton();
     0% {
     if (b0) {
       background-position: 0% 0%;
       b0.classList.add("peek");
      b0.style.alignSelf = "stretch";
     }
     }


     100% {
     // Move inicialização de tooltip para requestIdleCallback (não crítico)
       background-position: 400% 0%;
    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);
     }
     }
  }


  @keyframes weapon-subicon-pulse {
    (function initTabs() {
 
      const tabs = Array.from(document.querySelectorAll(".tab-btn"));
    0%,
      if (!tabs.length) return;
    100% {
      const contents = Array.from(document.querySelectorAll(".tab-content"));
       opacity: 0.95;
      const characterBox = document.querySelector(".character-box");
       box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
      let wrapper = characterBox.querySelector(".tabs-height-wrapper");
         0 0 32px 12px rgba(255, 80, 80, 0.6),
      if (!wrapper) {
         0 0 48px 18px rgba(255, 100, 60, 0.4);
        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]) =>
            `<div class="attr-row"><span class="attr-label">${rowLabel}</span><span class="attr-value">${rowValue}</span></div>`
        )
        .join("");
      return `<div class="attr-list">${html}</div>`;
     }
     }
 
    function syncDescHeight() { }
     50% {
    window.addEventListener("resize", syncDescHeight);
       opacity: 1;
    const vbInit = getVideoBox();
       box-shadow: 0 0 24px 8px rgba(255, 80, 80, 1),
    if (vbInit) new ResizeObserver(syncDescHeight).observe(vbInit);
         0 0 48px 16px rgba(255, 80, 80, 0.75),
     iconItems.forEach((el) => {
         0 0 64px 24px rgba(255, 120, 60, 0.5);
       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,
          });
        }
      }
    })();


  @keyframes weapon-subicon-pulse-active {
    // 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);
            }
          });
        });


    0%,
        // Observa imagens que não estão no viewport inicial
    100% {
        document
      opacity: 0.95;
          .querySelectorAll("img:not(.topbar-icon):not([loading])")
      box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.9),
          .forEach((img) => {
        0 0 40px 16px rgba(255, 87, 34, 0.7),
            const rect = img.getBoundingClientRect();
        0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6);
            if (rect.bottom > window.innerHeight || rect.top < 0) {
     }
              imageObserver.observe(img);
            }
          });
      }
     })();


     50% {
     setTimeout(() => {
       opacity: 1;
       Array.from(document.querySelectorAll(".skill-icon")).forEach((el) => { });
       box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
       videosCache.forEach((v, idx) => {
         0 0 56px 24px rgba(255, 87, 34, 0.85),
         const src = v.querySelector("source")
         0 0 80px 32px rgba(255, 140, 60, 0.6), 0 0 0 5px rgba(255, 87, 34, 0.75);
          ? v.querySelector("source").src
     }
          : v.src;
   }
         v.addEventListener("error", (ev) => { });
</style>
        v.addEventListener("loadedmetadata", () => { });
      });
     }, 600);
   })();
</script>

Edição atual tal como às 00h53min de 11 de fevereiro de 2026

<script>

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

 ? `

${label}${val}

`

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

return rows.length ? `

${rows.join("")}

` : "";

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

descBox.innerHTML = `

${name}

${level  ? `

${L.level} ${level}

`

         : ""

}${attrsHTML}

${descHtml}

`;

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