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

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
Etiqueta: Revertido
Linha 1: Linha 1:
<!-- MAIN SKILLS SYSTEM -->
<!-- SUBSKILLS SYSTEM -->
<script>
<script>
   (function () {
   (function () {
     const $ = (s, root = document) => root.querySelector(s);
     const SUBVIDEO_DEBUG = false; // Desabilitado para melhor performance
    const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
     function logSubVideo(...args) {
     const ensureRemoved = (sel) => {
       if (!SUBVIDEO_DEBUG) return;
      Array.from(document.querySelectorAll(sel)).forEach((n) => n.remove());
       // console.log('[SubVideo]', ...args);
    };
     }
    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)
    const api = (window.__subskills ||= {});
     let currentForm = null; // null = primeira forma, depois nome da forma atual
     // Cache global de elementos de vídeo para subskills - baseado em URL
     let formsData = {};
     const subskillVideoElementCache = new Map(); // key: videoURL (string), value: HTMLVideoElement
     let fixedSkills = []; // Skills que sempre aparecem (Change Form, Guard Point, etc.)
     const imagePreloadCache = new Map();
     let subRail, subBar, spacer;


     // Sistema genérico de swap de personagens (ex: Buchi & Sham, futuros personagens)
     // Cache das skills principais (capturado na carga da página)
     let activeCharacter = null; // null = personagem padrão, depois nome do personagem ativo (ex: "Buchi", "Sham")
     let cachedMainSkills = null;


     function showFormTransitionVideo(changeFormIconEl) {
     // ===== HERANÇA DE ATRIBUTOS: busca dados das skills principais =====
      const skillsRoot = document.getElementById("skills");
    // FASE 5: Refatorado para usar byId como fonte primária
      if (!skillsRoot) return;
    function getMainSkillsMap() {
 
       // Retorna cache se já foi construído E tem dados
       // Busca videoBox
       if (cachedMainSkills && cachedMainSkills.byId.size > 0) {
      let videoBox = skillsRoot.querySelector(".video-container");
         return cachedMainSkills;
       if (!videoBox) {
        const skillsContainer = skillsRoot.querySelector(".skills-container");
         if (skillsContainer) {
          videoBox = skillsContainer.querySelector(".video-container");
        }
       }
       }
      if (!videoBox) return;


       try {
       const maps = {
         // Lê dados de forms para determinar forma atual e próxima
         byId: new Map(), // FASE 5: Fonte primária por ID
         const formsJSON = skillsRoot.dataset.forms || "{}";
         byName: new Map(), // FASE 5: Mantido para compatibilidade (legado)
        if (formsJSON && formsJSON !== "{}") {
        byIndex: new Map(), // Mantido para compatibilidade
          const tempFormsData = JSON.parse(formsJSON);
      };
          const formNames = Object.keys(tempFormsData);


          // Determina forma atual e próxima
      // Busca skills com data-index (skills principais, não subskills)
          const currentIdx = currentForm ? formNames.indexOf(currentForm) : -1;
      const icons = document.querySelectorAll(
          const nextIdx = (currentIdx + 1) % formNames.length;
        ".icon-bar .skill-icon[data-index][data-nome]"
          const nextForm = formNames[nextIdx];
      );


          // Busca vídeo de transição na skill Change Form
      icons.forEach((icon) => {
          // form_videos[forma_atual] = "video.mp4" (vídeo da transição atual → próxima)
        const name = (icon.dataset.nome || "").trim();
          const formVideosRaw =
        if (!name) return;
            changeFormIconEl.dataset.formVideos ||
            changeFormIconEl.getAttribute("data-form-videos");
          if (formVideosRaw) {
            try {
              const videos = JSON.parse(formVideosRaw);
              const transitionVideo = videos[currentForm] || "";


              if (transitionVideo && transitionVideo.trim() !== "") {
        // Extrai atributos do data-atr (formato: "pve, pvp, energy, cd")
                const videoURL = filePathURL(transitionVideo);
        const atrRaw = icon.dataset.atr || "";
                if (videoURL) {
        const parts = atrRaw.split(",").map((x) => (x || "").trim());
                  // Busca ou cria elemento de vídeo para esta transição
        const powerpve = parts[0] && parts[0] !== "-" ? parts[0] : "";
                  const videoKey = `form_transition:${currentForm}:${nextForm}`;
        const powerpvp = parts[1] && parts[1] !== "-" ? parts[1] : "";
                  let v = videosCache.get(videoKey);
        const energy = parts[2] && parts[2] !== "-" ? parts[2] : "";
        const cooldown = parts[3] && parts[3] !== "-" ? parts[3] : "";


                  if (!v) {
        // Nome original do arquivo de ícone (armazenado no dataset pelo widget de skills)
                    // Cria novo elemento de vídeo
        let iconFile = (icon.dataset.iconFile || "").trim();
                    v = document.createElement("video");
        if (!iconFile) {
                    v.className = "skill-video";
          const imgSrc = icon.querySelector("img")?.src || "";
                    v.src = videoURL;
          const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
                    v.preload = "auto";
          iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : "";
                    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)
        // Nome original do arquivo de vídeo (caso exista)
    // Detecta quais personagens estão disponíveis baseado nas skills
        let videoFile = (icon.dataset.videoFile || "").trim();
    function detectAvailableCharacters() {
         if (!videoFile) {
      const iconBar = document.querySelector(".icon-bar");
          const videoUrl = icon.dataset.video || "";
      if (!iconBar) return [];
          const videoMatch = videoUrl.match(/FilePath\/([^&?]+)/);
 
           videoFile = videoMatch ? decodeURIComponent(videoMatch[1]) : "";
      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);
        const index = (icon.dataset.index || "").trim();
    }
        // FASE 5: Extrai ID do data-skill-id ou gera a partir do nome
        const skillId = icon.dataset.skillId || icon.dataset.id || name;


    // Inicializa o personagem padrão (deve ser chamado quando a página carrega)
        const data = {
    function initializeActiveCharacter() {
          id: skillId, // FASE 5: ID único
      const availableCharacters = detectAvailableCharacters();
          name: name, // Mantido para compatibilidade
      if (availableCharacters.length > 0 && activeCharacter === null) {
          icon: iconFile,
        // Inicializa com o primeiro personagem disponível (padrão)
          level: icon.dataset.level || "",
        activeCharacter = availableCharacters[0];
          video: videoFile,
        // Aplica o estado inicial (habilita/desabilita skills)
          powerpve: powerpve,
        applyCharacterSwapState();
          powerpvp: powerpvp,
      }
          cooldown: cooldown,
    }
          energy: energy,
        };


    // Aplica o estado de swap nas skills (habilita/desabilita e atualiza vídeos)
        // Mantém descrições caso precise como fallback extra
    function applyCharacterSwapState() {
        if (icon.dataset.descPt) data.descPt = icon.dataset.descPt;
      const iconBar = document.querySelector(".icon-bar");
        if (icon.dataset.descEn) data.descEn = icon.dataset.descEn;
      if (!iconBar) return;
        if (icon.dataset.descEs) data.descEs = icon.dataset.descEs;
        if (icon.dataset.descPl) data.descPl = icon.dataset.descPl;


      // Atualiza todas as skills na barra
         // FASE 5: Processa desc_i18n se existir
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
        if (icon.dataset.descI18n) {
         (icon) => {
           try {
          const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
             data.desc_i18n = JSON.parse(icon.dataset.descI18n);
          const hasCharacterVideos =
          } catch (e) {
            icon.dataset.characterVideos &&
             // Ignora erro de parse
            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
        // FASE 5: byId é a fonte primária
    function detectCurrentForm() {
        maps.byId.set(skillId, data);
      const iconBar = document.querySelector(".icon-bar");
        maps.byName.set(name, data); // FASE 5: Mantido para compatibilidade (legado)
      if (!iconBar || !formsData || Object.keys(formsData).length === 0)
        if (index) {
        return null;
          // Guarda tanto como string quanto como número para compatibilidade
 
          maps.byIndex.set(index, data);
      // Coleta todas as skills de form que estão visíveis no DOM
           maps.byIndex.set(parseInt(index, 10), data);
      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();
       // Cacheia para uso futuro (importante: as skills principais não mudam)
      Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
      cachedMainSkills = maps;
        (icon) => {
       return maps;
          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() {
    // FASE 4: Resolver único para toda resolução de skill/subskill
       const skillsRoot = document.getElementById("skills");
    // Retorna tudo que a UI precisa renderizar de forma determinística
       if (!skillsRoot) return;
     function resolveSkillView(skill, context) {
       // context: { lang, weaponMode, mainSkills }
       // Retorna: { title, desc, video, attrs, flags, weapon }


       // Lê dados de forms do atributo data-forms
       const lang = context.lang || "pt";
      try {
      const weaponMode = context.weaponMode || false;
        const formsJSON = skillsRoot.dataset.forms || "{}";
       const mainSkills = context.mainSkills || null;
        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) {
       // 1. Título: sempre display_name
        return; // Não tem forms, não faz nada
      const title = skill.display_name || skill.name || skill.n || "";
      }


       // Identifica skills fixas (sempre presentes)
       // 2. Descrição: sempre vem da skill/subskill, nunca herda
       const iconBar = document.querySelector(".icon-bar");
       let desc = "";
       if (!iconBar) return;
       if (weaponMode && skill.weapon?.desc_i18n) {
 
        desc =
      // Busca a skill com form_switch dinamicamente (genérico)
          skill.weapon.desc_i18n[lang] ||
      const changeFormIcon = Array.from(
          skill.weapon.desc_i18n.pt ||
        iconBar.querySelectorAll(".skill-icon[data-index]")
           skill.weapon.desc_i18n.en ||
      ).find(
           "";
        (icon) =>
       } else {
           icon.dataset.formSwitch === "true" ||
         desc =
           icon.getAttribute("data-form-switch") === "true"
          skill.desc_i18n?.[lang] ||
      );
          skill.desc_i18n?.pt ||
       if (changeFormIcon) {
          skill.desc_i18n?.en ||
         changeFormIcon.classList.add("active");
          "";
      }
         if (!desc) {
 
           desc =
      // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
            skill.descPt || skill.descEn || skill.descEs || skill.descPl || "";
      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
       // 3. Vídeo: sempre da skill/subskill, nunca herdado
       if (!currentForm && formNames.length > 0) {
       const video = skill.video || "";
        currentForm = formNames[0];
      }


       // Cria ordem circular fixa baseada na forma atual detectada
       // 4. Atributos: weapon mode ou normal
      // Se detectamos "Brain Point", ordem é: Brain → Kung Fu → Heavy → Brain
       let attrs = {
      // Se detectamos "Kung Fu Point", ordem é: Kung Fu → Heavy → Brain → Kung Fu
         powerpve: skill.powerpve,
      // Se detectamos "Heavy Point", ordem é: Heavy → Brain → Kung Fu → Heavy
         powerpvp: skill.powerpvp,
       let orderedFormNames = [];
         energy: skill.energy,
      if (currentForm === "Brain Point" && formNames.length === 3) {
         cooldown: skill.cooldown,
         // 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 (weaponMode && skill.weapon) {
       if (orderedFormNames.length === 0) {
         attrs = {
         orderedFormNames = [...formNames].sort();
          powerpve: skill.weapon.powerpve || skill.powerpve,
        // Se sabemos a forma atual, reorganiza para começar por ela
           powerpvp: skill.weapon.powerpvp || skill.powerpvp,
        if (currentForm) {
           energy: skill.weapon.energy || skill.energy,
           const currentIdx = orderedFormNames.indexOf(currentForm);
          cooldown: skill.weapon.cooldown || skill.cooldown,
           if (currentIdx !== -1) {
         };
            orderedFormNames = [
              ...orderedFormNames.slice(currentIdx),
              ...orderedFormNames.slice(0, currentIdx),
            ];
          }
         }
       }
       }


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


      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 {
         name: icon.dataset.nome || icon.dataset.name || "",
         title,
        index: icon.dataset.index || "",
         desc,
        level: icon.dataset.level || "",
         video,
         desc: icon.dataset.desc || "",
         attrs,
         descPt: icon.dataset.descPt || "",
         flags: skill.flags,
        descEn: icon.dataset.descEn || "",
         weapon: skill.weapon,
        descEs: icon.dataset.descEs || "",
         level,
        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) {
    // applyInheritance: aplica herança de atributos (nunca herda descrição ou vídeo)
       const iconBar = document.querySelector(".icon-bar");
     function applyInheritance(sub, mainSkills) {
       if (!iconBar || !formData || !formData.skills) return;
       // NOVO SISTEMA: herança explícita
      // 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)


      // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
       const inheritFrom = sub.inherit_from_id || sub.inherit_from;
       const allFormSkillNames = new Set();
       const inheritFields = sub.inherit_fields || [];
       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
       // Converte para set para busca rápida
      const inheritFieldsSet = new Set(inheritFields);


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


       if (!changeFormIcon || !nextFixedSkillIcon) {
      // Se não tem inherit_from ou main, não herda nada
        console.warn(
       if (!inheritFrom || !main) {
          "[Forms] Skill com form_switch ou próxima skill fixa não encontrada"
         return sub;
        );
         return;
       }
       }


       const formSkills = formData.skills || [];
      // Função auxiliar: verifica se campo DEVE ser herdado
       const formOrder = formData.order || [];
       const shouldInheritField = (fieldName) => {
        return inheritFieldsSet.has(fieldName);
       };


       // Primeira skill da forma (vai ANTES do Change Form)
       // Helper para verificar se um valor existe e não é string vazia
      const firstSkillName = formOrder[0];
      const hasValue = (val) => {
      const firstSkillData = formSkills.find((s) => s.name === firstSkillName);
        if (val === undefined || val === null) return false;
        if (typeof val === "number") return !isNaN(val);
        const str = String(val).trim();
        return str !== "" && str !== "NaN";
      };


       // Terceira skill da forma (vai DEPOIS do Change Form, ANTES do Guard Point)
       // Vídeo NUNCA é herdado da skill principal
       const thirdSkillName = formOrder[1]; // Segunda na ordem = terceira skill (índice 1)
       const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
       const thirdSkillData = formSkills.find((s) => s.name === thirdSkillName);
       const finalVideo = hasOwnVideo ? sub.video || "" : "";


       // Quinta skill da forma (vai DEPOIS do Guard Point)
       return {
      const fifthSkillName = formOrder[2]; // Terceira na ordem = quinta skill (índice 2)
         ...sub,
      const fifthSkillData = formSkills.find((s) => s.name === fifthSkillName);
         name: sub.name || main.name,
 
         // Herda apenas se campo estiver em inherit_fields
      // Cria fragments para inserir
         icon:
      const firstFragment = document.createDocumentFragment();
           sub.icon && sub.icon !== ""
      const thirdFragment = document.createDocumentFragment();
             ? sub.icon
      const fifthFragment = document.createDocumentFragment();
            : shouldInheritField("icon")
 
              ? main.icon || ""
      if (firstSkillData) {
              : "",
         const iconElement = createSkillIconElement(firstSkillData, 1);
         level: hasValue(sub.level)
        firstFragment.appendChild(iconElement);
           ? sub.level
      }
           : shouldInheritField("level")
      if (thirdSkillData) {
             ? main.level
        const changeFormIndex = parseInt(
             : "",
          changeFormIcon.dataset.index || "2",
         video: finalVideo, // Nunca herda
          10
         powerpve:
         );
           sub.powerpve !== undefined && sub.powerpve !== null
        const iconElement = createSkillIconElement(
            ? sub.powerpve
          thirdSkillData,
             : shouldInheritField("powerpve")
          changeFormIndex + 1
               ? main.powerpve
        );
               : undefined,
        thirdFragment.appendChild(iconElement);
         powerpvp:
      }
          sub.powerpvp !== undefined && sub.powerpvp !== null
      if (fifthSkillData) {
             ? sub.powerpvp
        const nextFixedSkillIndex = parseInt(
             : shouldInheritField("powerpvp")
          nextFixedSkillIcon.dataset.index || "4",
              ? main.powerpvp
          10
              : undefined,
        );
        cooldown:
        const iconElement = createSkillIconElement(
           sub.cooldown !== undefined && sub.cooldown !== null
          fifthSkillData,
             ? sub.cooldown
          nextFixedSkillIndex + 1
             : shouldInheritField("cooldown")
        );
              ? main.cooldown
         fifthFragment.appendChild(iconElement);
              : undefined,
      }
        energy:
 
           sub.energy !== undefined && sub.energy !== null
      // Remove os ícones antigos do DOM após animação
             ? sub.energy
      setTimeout(() => {
             : shouldInheritField("energy")
         iconsToRemove.forEach((icon) => {
              ? main.energy
           if (icon.parentNode) {
              : undefined,
             icon.remove();
        // 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,
        // Insere a primeira skill ANTES da skill com form_switch
        descEs: sub.desc_i18n?.es || sub.descEs || undefined,
        if (firstFragment.hasChildNodes()) {
        descPl: sub.desc_i18n?.pl || sub.descPl || undefined,
          iconBar.insertBefore(firstFragment, changeFormIcon);
         desc_i18n: sub.desc_i18n || null,
        }
         // GARANTIA: Remove qualquer campo legado que possa ter sido copiado
 
        desc: undefined,
        // Insere a terceira skill DEPOIS da skill com form_switch, ANTES da próxima skill fixa
         flags:
         if (thirdFragment.hasChildNodes()) {
           sub.flags || (shouldInheritField("flags") ? main.flags : undefined),
           iconBar.insertBefore(thirdFragment, nextFixedSkillIcon);
         weapon:
        }
           sub.weapon ||
 
           (shouldInheritField("weapon") ? main.weapon : undefined),
        // Insere a quinta skill DEPOIS da próxima skill fixa
        back:
        if (fifthFragment.hasChildNodes()) {
           sub.back !== undefined
           if (nextFixedSkillIcon.nextSibling) {
             ? sub.back
             iconBar.insertBefore(fifthFragment, nextFixedSkillIcon.nextSibling);
             : shouldInheritField("back")
          } else {
               ? main.back
             iconBar.appendChild(fifthFragment);
              : undefined,
          }
         // Preserva campos de herança
        }
         inherit_from: inheritFrom,
 
         inherit_fields: inheritFields,
         // 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) {
     function filePathURL(fileName) {
       const iconWrap = document.createElement("div");
       // Evita requisições para valores vazios
      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 (
         skill.level &&
         !fileName ||
        skill.level !== "" &&
         fileName.trim() === ""
         skill.level.toUpperCase() !== "NIVEL"
       ) {
       ) {
         iconWrap.setAttribute("data-level", skill.level);
         return "";
       }
       }
 
       const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
       if (skill.desc_i18n) {
      const base =
         if (skill.desc_i18n.pt)
         window.mw && mw.util && typeof mw.util.wikiScript === "function"
          iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
          ? mw.util.wikiScript()
        if (skill.desc_i18n.en)
          : window.mw && mw.config
          iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
            ? mw.config.get("wgScript") || "/index.php"
        if (skill.desc_i18n.es)
            : "/index.php";
          iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
      // Garante HTTPS para evitar Mixed Content
         if (skill.desc_i18n.pl)
      let url = `${base}?title=Especial:FilePath/${f}`;
          iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
      if (window.location.protocol === "https:" && url.startsWith("http://")) {
         url = url.replace("http://", "https://");
       }
       }
      return url;
    }


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


       if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
    function preloadImage(iconFile) {
         iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
      const url = filePathURL(iconFile || "");
       if (imagePreloadCache.has(url)) {
         return imagePreloadCache.get(url);
       }
       }
 
       const promise = new Promise((resolve, reject) => {
       if (
         const img = new Image();
         skill.weapon &&
         img.decoding = "async";
        typeof skill.weapon === "object" &&
        img.loading = "eager";
        Object.keys(skill.weapon).length > 0
         img.referrerPolicy = "same-origin";
      ) {
         img.onload = () => resolve(url);
         iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
        img.onerror = () => resolve(url);
      }
        img.src = url;
      if (skill.effect && typeof skill.effect === "object") {
       });
         try {
       imagePreloadCache.set(url, promise);
          iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
       return promise;
         } 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) {
     function getLabels() {
      // Cria ícone similar ao que é gerado pelo servidor
       const skillsRoot = document.getElementById("skills");
       const iconWrap = document.createElement("div");
       const i18nMap = skillsRoot
       iconWrap.className = "skill-icon";
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
      iconWrap.setAttribute("data-index", index);
        : {};
       iconWrap.setAttribute("data-nome", skill.name || "");
       const raw = (
       iconWrap.setAttribute("data-desc", "");
        document.documentElement.lang ||
       iconWrap.setAttribute(
        skillsRoot?.dataset.i18nDefault ||
         "data-atr",
        "pt"
        makeAttrString(
      ).toLowerCase();
           skill.powerpve,
       const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
           skill.powerpvp,
       return (
           skill.energy,
         i18nMap[lang] ||
           skill.cooldown
        i18nMap.pt || {
         )
          cooldown: "Recarga",
          energy_gain: "Ganho de energia",
           energy_cost: "Custo de energia",
           power: "Poder",
           power_pvp: "Poder PvP",
           level: "Nível",
         }
       );
       );
      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 (
    // Verifica se o modo weapon está ativo
        skill.level &&
    function isWeaponModeOn() {
         skill.level !== "" &&
      try {
        skill.level.toUpperCase() !== "NIVEL"
         return localStorage.getItem("glaWeaponEnabled") === "1";
       ) {
       } catch (e) {
         iconWrap.setAttribute("data-level", skill.level);
         return false;
       }
       }
    }


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


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


       if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
       // Fallback para campos individuais
         iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
      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 (
         skill.weapon &&
         weaponOn &&
         typeof skill.weapon === "object" &&
        s.weapon &&
         Object.keys(skill.weapon).length > 0
         s.weapon.video &&
         s.weapon.video.trim() !== ""
       ) {
       ) {
         iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
         return s.weapon.video;
      }
      if (skill.effect && typeof skill.effect === "object") {
        try {
          iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
        } catch (e) { }
       }
       }
      return s.video || "";
    }


      const img = document.createElement("img");
    // Função única para obtenção de vídeo de subskill - baseada em URL
      img.className = "skill-icon-img";
    function getOrCreateSubskillVideo(videoURL) {
       img.src = filePathURL(skill.icon || "");
       if (!videoURL || videoURL.trim() === "") return null;
      img.alt = "";
      iconWrap.appendChild(img);


       container.appendChild(iconWrap);
       const normalizedURL = normalizeFileURL(videoURL);
    }
      if (!normalizedURL || normalizedURL.trim() === "") return null;


    function makeAttrString(pve, pvp, energy, cd) {
      // 1. Tenta cache local primeiro (compatibilidade)
       const parts = [
       if (subskillVideoElementCache.has(normalizedURL)) {
        pve !== undefined && pve !== null && pve !== "" ? String(pve) : "-",
         logSubVideo("getOrCreateSubskillVideo: local cache hit", {
         pvp !== undefined && pvp !== null && pvp !== "" ? String(pvp) : "-",
           videoURL: normalizedURL,
        energy !== undefined && energy !== null && energy !== ""
         });
          ? String(energy)
        return subskillVideoElementCache.get(normalizedURL);
           : "-",
      }
         cd !== undefined && cd !== null && cd !== "" ? String(cd) : "-",
      ];
      return parts.join(", ");
    }


    function filePathURL(fileName) {
      // 2. Tenta cache global de subskills (window.__subskillVideosCache)
      // Evita requisições para valores vazios
      const globalCache = window.__subskillVideosCache;
      if (!fileName || fileName.trim() === "") {
      if (globalCache && globalCache instanceof Map) {
        return "";
        // O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
        // Precisamos buscar por URL normalizada
        for (const [key, video] of globalCache.entries()) {
          const src = video.querySelector("source");
          if (src && src.src === normalizedURL) {
            logSubVideo("getOrCreateSubskillVideo: global cache hit", {
              videoURL: normalizedURL,
              key,
            });
            // Adiciona ao cache local também para acesso rápido
            subskillVideoElementCache.set(normalizedURL, video);
            return video;
          }
        }
       }
       }
       const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
 
      const base =
       // 3. REMOVIDO: criação lazy - todos os vídeos devem estar pré-carregados
        window.mw && mw.util && typeof mw.util.wikiScript === "function"
       // Se vídeo não foi encontrado, é um erro (não deve criar novo)
          ? mw.util.wikiScript()
       console.warn("[Subskills] Vídeo não encontrado no cache:", {
          : window.mw && window.mw.config
         videoURL: normalizedURL,
            ? mw.config.get("wgScript") || "/index.php"
        originalURL: videoURL,
            : "/index.php";
       });
       // Garante HTTPS para evitar Mixed Content
       return null;
      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 "";
     function renderSubAttrs(s, L) {
      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) =>
       const chip = (label, val) =>
         val
         val
Linha 1 056: Linha 524:
       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 arr = (flags || []).filter(Boolean).map((k) => String(k || "").trim().toLowerCase());
      const map = {
        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 url = getFlagIconURL(k);
           const file = map[k];
           return url
          if (!file) return "";
            ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">`
           return `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(file)}">`;
            : "";
         })
         })
        .filter(Boolean)
         .join("");
         .join("");
       const html = items
       return items ? `<div class="skill-flags" role="group" aria-label="Características">${items}</div>` : "";
        ? `<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 1 093: Linha 556:
         pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
         pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
       } catch (e) { }
       } catch (e) { }
       const lang = getLangKey();
       const raw = (document.documentElement.lang || "pt").toLowerCase();
      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 1 100: Linha 564:
       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 1 108: Linha 573:
         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 1 130: Linha 596:
     }
     }


     // ====== Skill/Subskill inheritance helpers ======
     function ensureRail(iconsBar) {
    const mainSkillsMeta = {
      const rail = iconsBar.closest(".top-rail");
      byIndex: new Map(),
       if (!rail) {
       byName: new Map(),
        return null;
       ready: false,
       }
    };


    function normalizeFileURL(raw, fallback = "") {
       if (!subRail) {
       if (!raw) return fallback;
        subRail = document.createElement("div");
      const val = String(raw).trim();
         subRail.className = "subskills-rail collapsed hidden";
      if (!val) return fallback;
         rail.appendChild(subRail);
      if (
         /^(https?:)?\/\//i.test(val) ||
        val.startsWith("data:") ||
         val.includes("Especial:FilePath/")
      ) {
        return val;
       }
       }
      return filePathURL(val);
    }


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


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


    function hasText(value) {
       return rail;
       return typeof value === "string"
        ? value.trim() !== ""
        : value !== undefined && value !== null;
     }
     }


     function pickFilled(current, fallback) {
    // Função para mostrar vídeo de subskill usando cache baseado em URL
       if (current === 0 || current === "0") return current;
     function showSubVideo(videoURL, videoBox) {
      if (!hasText(current)) return fallback;
       logSubVideo("showSubVideo called", {
      return current;
         videoURL,
    }
         videoBoxExists: !!videoBox,
 
    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 (!videoBox) return;
       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)
       // Ocultar todos os vídeos existentes no container
       const refS = ((sub.refS || sub.S || sub.s || "") + "").trim();
       videoBox
      const refIndex = ((sub.refM || sub.M || sub.m || "") + "").trim();
        .querySelectorAll('video.skill-video[data-sub="1"]')
      let name = (sub.name || sub.n || "").trim();
        .forEach((v) => {
      let main = null;
          v.style.display = "none";
        });


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


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


       const hydrated = { ...sub };
       // Se o vídeo não estiver conectado ao DOM, anexá-lo ao container
      if (!name && main.name) {
      if (!video.isConnected || video.parentNode !== videoBox) {
         name = main.name;
        logSubVideo("video not in DOM, appending", {
          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 || "");


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


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


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


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


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


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


    // Cache de vídeos que falharam (404) para evitar tentativas repetidas
      const rail = ensureRail(iconsBar);
    const failedVideosCache = new Set();
      if (!rail) {
    const missingVideosReported = new Set(); // Para avisar apenas uma vez sobre vídeos faltantes
        logSubVideo("api.renderBarFrom: no rail found, returning");
        return;
      }
 
      const rawSubs = el.getAttribute("data-subs") || "";
      const rawOrder = el.getAttribute("data-suborder") || "";
      const parentIdx = el.dataset.index || "";
 
      logSubVideo("api.renderBarFrom: parsing subs", {
        parentIdx,
        hasRawSubs: !!rawSubs.trim(),
        rawSubsLength: rawSubs.length,
      });


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


    let assetManifest = null;
      // NORMALIZADOR: Converte weaponPacked para weapon se necessário
    const skillsTab = $("#skills");
       subs = subs.map((sub) => {
    const skinsTab = $("#skins");
         // Se tem weaponPacked mas não tem weapon, tenta parsear
    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 (
           !iconBar.parentElement ||
           sub.weaponPacked &&
           !iconBar.parentElement.classList.contains("icon-scroll-x")
           !sub.weapon &&
          typeof sub.weaponPacked === "string" &&
          sub.weaponPacked.trim() !== ""
         ) {
         ) {
           const scrollWrapper = document.createElement("div");
           const parts = sub.weaponPacked.split("~");
           scrollWrapper.className = "icon-scroll-x";
           if (parts.length >= 2) {
           scrollWrapper.appendChild(iconBar);
            sub.weapon = {
           rail.appendChild(scrollWrapper);
              icon: parts[0] || "",
        } else {
              powerpve: parts[1] || null,
          rail.appendChild(iconBar.parentElement);
              powerpvp: parts[2] || null,
              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;
            }
          }
         }
         }
         skillsTab.prepend(rail);
         return sub;
      });
 
      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 skills-container criado pelo Lua
       // Busca mapa das skills principais para herança
       const skillsContainer = skillsTab.querySelector(".skills-container");
       const mainSkills = getMainSkillsMap();


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


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


       // Busca video-container
       // Remove subskills que ficaram sem nome após herança
       let videoContainer = skillsTab.querySelector(".video-container");
       subs = subs.filter((s) => (s.name || s.n || "").trim() !== "");
      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)
       subRail.classList.add("hidden");
      let card = skillsTab.querySelector(".content-card.skills-grid");
       subBar.innerHTML = "";
       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)
       // Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
       // Remove skills-container se existir (não é necessário para o layout)
       const subsById = new Map();
       if (skillsContainer && skillsContainer.parentNode) {
       subs.forEach((s) => {
         // Move os elementos filhos de skills-container para o card
         const id = s.id || s.name || s.n || "";
         if (details.parentNode === skillsContainer) {
         if (id) {
           skillsContainer.removeChild(details);
           subsById.set(id, s);
         }
         }
        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
       // Usa a ordem natural das subskills após herança
       // skills-details deve vir ANTES de video-container para o grid funcionar
       let order = subs.map((s) => s.id || s.name || s.n || "");
       if (details.parentNode !== card) {
       if (rawOrder.trim()) {
        // Se videoContainer já está no card, insere details antes dele
        try {
        if (videoContainer.parentNode === card) {
          const preferred = JSON.parse(rawOrder);
          card.insertBefore(details, videoContainer);
          if (Array.isArray(preferred) && preferred.length) {
        } else {
            // Tenta por ID primeiro, depois por nome (compatibilidade)
          card.appendChild(details);
            const byName = new Map(subs.map((s) => [s.name || s.n || "", s]));
        }
            const byId = new Map(
      }
              subs.map((s) => [s.id || s.name || s.n || "", s])
      if (videoContainer.parentNode !== card) {
            );
        card.appendChild(videoContainer);
            order = preferred
              .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 { }
       }
       }


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


        /* Animação sutil para brilho */
          // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
        @keyframes weapon-glow-breathe {
          logSubVideo("attaching click handler to subicon", {
            0%, 100% { opacity: 0.7; }
            subName: (s.name || s.n || "").trim(),
             50% { opacity: 1; }
            parentIdx,
        }
            itemClassName: item.className,
          });
          item.addEventListener("click", () => {
            const L = getLabels();
 
            // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
            const skillId = item.dataset.skillId || s.id || s.name || s.n || "";
            if (!skillId) {
              console.error(
                "[Subskills] Click handler: skillId não encontrado",
                { item, s }
              );
              return;
            }
 
            // CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
            let currentSub = subsById.get(skillId);
            let lookupMethod = "id";
 
             if (!currentSub) {
              // Fallback: tenta por nome (compatibilidade com dados antigos)
              const subName = (s.name || s.n || "").trim();
              currentSub = subs.find((x) => (x.name || x.n || "") === subName);
              lookupMethod = "name_fallback";


        /* Animação do gradiente (sem girar a borda) */
              if (!currentSub) {
        @property --effect-spin {
                console.error(
            syntax: "<angle>";
                  "[Subskills] Click handler: subskill não encontrada",
            inherits: false;
                  {
            initial-value: 0deg;
                    skillId,
        }
                    subName,
        @keyframes effect-border-spin {
                    availableIds: Array.from(subsById.keys()),
            from { --effect-spin: 0deg; }
                  }
             to { --effect-spin: 360deg; }
                );
        }
                return;
              }
             }


        /* ===== ÍCONE COM ARMA - Só mostra efeitos quando weapon-mode-on está ativo ===== */
            const subName = (currentSub.name || currentSub.n || "").trim();
        /* 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 ===== */
             logSubVideo("subicon click HANDLER EXECUTED", {
        .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::after {
              skillId,
            pointer-events: none !important;
              subName,
            box-shadow: none !important;
              parentIdx,
             background: linear-gradient(90deg,  
              weaponMode: isWeaponModeOn(),
                #FF3333 0%,  
              hasWeaponDataAttr: !!item.dataset.weapon,
                #FF0000 50%,  
              rawWeaponDataset: item.dataset.weapon || null,
                #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 {
            // Lê weapon diretamente do atributo data-weapon
            pointer-events: none !important;
            let subWeaponData = null;
            inset: 0 !important;
            if (item.dataset.weapon) {
            border-radius: inherit !important;
              try {
            z-index: 1 !important;
                const parsed = JSON.parse(item.dataset.weapon);
            animation: weapon-glow-breathe 2s ease-in-out infinite !important;
                // Só considera weapon válido se for um objeto não vazio
            box-shadow:
                if (
                 0 0 8px 0 rgba(255, 0, 0, 0.4),
                  parsed &&
                 0 0 12px 0 rgba(255, 51, 51, 0.3),
                  typeof parsed === "object" &&
                 0 0 16px 0 rgba(255, 0, 0, 0.2) !important;
                  Object.keys(parsed).length > 0
             opacity: 1 !important;
                 ) {
        }
                  subWeaponData = parsed;
                 }
              } catch (e) {
                 subWeaponData = null;
              }
             }
            const hasSubWeapon = !!subWeaponData;


        /* ===== MODO WEAPON ON - ATIVO ===== */
            const weaponOn = isWeaponModeOn();
        .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active {
             const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
            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 {
            logSubVideo("weapon status for subclick", {
            pointer-events: none !important;
              subName,
            box-shadow: none !important;
              weaponEquipped,
            background: linear-gradient(90deg,  
              hasSubWeaponData: !!subWeaponData,
                #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 {
            // FASE 4: Usa resolveSkillView para resolver tudo de forma determinística
            pointer-events: none !important;
            const raw = (document.documentElement.lang || "pt").toLowerCase();
             inset: 0 !important;
             const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
            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 {
             // Prepara skill com weapon se necessário
            position: relative;
             const skillForResolution = { ...currentSub };
        }
             if (weaponEquipped && subWeaponData) {
        .character-box .top-rail.skills .icon-bar .skill-icon {
              skillForResolution.weapon = subWeaponData;
             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:
            // Resolve tudo usando o resolver único
                    inset 0 0 0 var(--icon-ring-w) rgba(120, 20, 20, 0.95),
             const resolved = resolveSkillView(skillForResolution, {
                    0 0 9px rgba(255, 70, 70, 0.65),
              lang,
                    0 0 14px rgba(20, 0, 0, 0.8);
              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% {
 
                 box-shadow:
             if (descBox) {
                    inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
              descBox.innerHTML = `<div class="skill-title"><h3>${resolved.title || currentSub.name || currentSub.n || ""
                    0 0 6px rgba(255, 60, 60, 0.45),
                 }</h3></div>${level
                    0 0 10px rgba(120, 20, 20, 0.6);
                  ? `<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>`;
             }
             }
        }
 
        @keyframes effect-child-outline {
            if (videoBox) {
            0% {
              const oldFlags = videoBox.querySelector(".skill-flags");
                 outline-color: rgba(210, 60, 60, 0.95);
              if (oldFlags) oldFlags.remove();
              if (flagsHTML) {
                 videoBox.insertAdjacentHTML("beforeend", flagsHTML);
                applyFlagTooltips(videoBox);
              }
             }
             }
             50% {
 
                outline-color: rgba(120, 20, 20, 0.95);
            // FASE 4: Vídeo vem do resolved (já resolvido)
             const effectiveVideo = resolved.video;
 
            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);
             }
             }
             100% {
 
                outline-color: rgba(210, 60, 60, 0.95);
             Array.from(subBar.children).forEach((c) => {
              c.classList.remove("active");
              c.classList.remove("weapon-equipped");
            });
            item.classList.add("active");
 
            // Aplica weapon-equipped se tem weapon e está ativo
            if (weaponEquipped) {
              item.classList.add("weapon-equipped");
             }
             }
        }
             window.__lastActiveSkillIcon = item;
        @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 {
          if (window.__globalSkillTooltip) {
             pointer-events: none !important;
             const { show, hide, measureAndPos, lockUntil } =
             inset: 0 !important;
              window.__globalSkillTooltip;
             border-radius: inherit !important;
             const label = item.title || "";
             z-index: 1 !important;
             item.setAttribute("aria-label", label);
             animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
             if (item.hasAttribute("title")) item.removeAttribute("title");
             box-shadow:
             item.addEventListener("mouseenter", () => show(item, label));
                0 0 10px 0 rgba(220, 220, 220, 0.5),
             item.addEventListener("mousemove", () => {
                0 0 16px 0 rgba(190, 190, 190, 0.4),
              if (performance.now() >= lockUntil.value) measureAndPos(item);
                0 0 22px 0 rgba(220, 220, 220, 0.3) !important;
            });
             opacity: 1 !important;
            item.addEventListener("click", () => {
        }
              lockUntil.value = performance.now() + 240;
              measureAndPos(item);
            });
             item.addEventListener("mouseleave", hide);
          }


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


      const barRect = iconBar.getBoundingClientRect();
          // Atualiza a subskill ativa se houver
      const width = Math.max(1, Math.round(iconBar.scrollWidth || barRect.width));
          setTimeout(() => {
      const height = Math.max(1, Math.round(iconBar.clientHeight || barRect.height));
            const activeSub =
      layer.style.left = "0px";
              subBar.querySelector(".subicon[data-weapon].active") ||
      layer.style.top = "0px";
              subBar.querySelector(".subicon.active");
      layer.setAttribute("width", String(width));
            if (activeSub) {
      layer.setAttribute("height", String(height));
              activeSub.dispatchEvent(new Event("click", { bubbles: true }));
      layer.setAttribute("viewBox", `0 0 ${width} ${height}`);
            } else {
      layer.classList.remove("effect-lines-returning");
              api.refreshCurrentSubSafe();
            }
          }, 50);
        };


      const srcRect = effectState.sourceIcon.getBoundingClientRect();
        window.addEventListener(
      const startX =
          "gla:weaponToggled",
        srcRect.left + srcRect.width / 2 - barRect.left + iconBar.scrollLeft;
          subBar._weaponToggleListener
      const startY = srcRect.top + srcRect.height / 2 - barRect.top;
        );


      const allIcons = Array.from(
        requestAnimationFrame(() => {
        document.querySelectorAll(".icon-bar .skill-icon[data-index]")
          subRail.classList.remove("collapsed");
      ).filter((icon) => !icon.classList.contains("weapon-bar-toggle"));
          subRail.classList.remove("hidden");
      const targets = allIcons.filter((icon) => {
          const h = subRail.offsetHeight || 48;
        if (icon === effectState.sourceIcon) return false;
          if (spacer) spacer.style.height = h + "px";
        const name = getSkillNameFromIcon(icon);
         });
         return name && effectState.skills.has(name);
       };
       });


       if (!targets.length) {
       // Chama a função de renderização após pré-carregar ícones
         clearEffectLines();
      Promise.all(iconPreloadPromises)
         return;
        .then(() => {
      }
          setTimeout(() => {
            renderSubskillsBar();
          }, 10);
        })
         .catch(() => {
          setTimeout(() => {
            renderSubskillsBar();
          }, 10);
         });
    };


      const frag = document.createDocumentFragment();
    api.hideAll = function (videoBox) {
       const baselinePadding = 10;
       const videos =
      const baselineExtra = 12;
        videoBox?.querySelectorAll('.skill-video[data-sub="1"]') || [];
      const baselineY = Math.max(
       logSubVideo("api.hideAll called", {
        startY,
         videoBoxExists: !!videoBox,
        height - baselinePadding + baselineExtra
         videoCount: videos.length,
      );
       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);
       videos.forEach((v) => {
      const minX = Math.min(startX, ...xs);
         try {
      const maxX = Math.max(startX, ...xs);
           v.pause();
      effectLinesLastState = {
         } catch { }
         startX,
         v.style.display = "none";
        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(
    window.renderSubskillsBarFrom = function (el, ctx) {
        "http://www.w3.org/2000/svg",
       api.renderBarFrom(el, ctx);
        "path"
    };
      );
       glow.setAttribute("d", d);
      glow.classList.add("effect-line", "effect-line-glow");


       const core = document.createElementNS(
    api.preloadAllSubskillImages = function () {
         "http://www.w3.org/2000/svg",
       const allSkillIcons = document.querySelectorAll(
        "path"
         ".icon-bar .skill-icon[data-subs]"
       );
       );
       core.setAttribute("d", d);
       const preloadPromises = [];
       core.classList.add("effect-line", "effect-line-core");
       let totalImages = 0;


       frag.appendChild(glow);
       allSkillIcons.forEach((icon) => {
      frag.appendChild(core);
        try {
          const subsRaw = icon.getAttribute("data-subs");
          if (!subsRaw) return;
          const subs = JSON.parse(subsRaw);
          if (!Array.isArray(subs)) return;


      requestAnimationFrame(() => {
          subs.forEach((s) => {
        const length = Math.max(1, Math.round(core.getTotalLength()));
            if (s && s.icon && s.icon.trim() !== "") {
        [core, glow].forEach((path) => {
              preloadPromises.push(preloadImage(s.icon));
          path.style.setProperty("--effect-line-length", `${length}`);
              totalImages++;
           path.style.strokeDasharray = `${length}`;
            }
           path.style.strokeDashoffset = "0";
            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);
    }
    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(
      if (totalImages > 0) {
          "http://www.w3.org/2000/svg",
         // console.log('[Subskills] preloadAllSubskillImages: pré-carregando', totalImages, 'ícones');
          "path"
         return Promise.all(preloadPromises).then(() => {
        );
           // console.log('[Subskills] preloadAllSubskillImages: todos os ícones carregados');
        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(() => {
       return Promise.resolve();
        clearEffectLines();
     };
      }, (returnDuration + returnStagger * Math.max(0, targets.length - 1)) * 1000 + 120);
 
     }
     // Função para pré-carregar vídeos de subskills usando getOrCreateSubskillVideo
    function clearEffectLines() {
     // Função preloadAllSubskillVideos removida - vídeos são carregados sob demanda
      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();
    // Função syncVideoCaches removida - não é mais necessária com cache baseado em URL
      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) {
     // Inicialização
        activeIcon.dispatchEvent(new Event("click", { bubbles: true }));
     function init() {
      }
       logSubVideo("init: starting initialization");
     }
     function activateEffectFromIcon(iconEl) {
       const effectRaw = getCachedJSON(iconEl, "effect");
      const normalized = normalizeEffectData(effectRaw);
      if (!normalized) return;


       effectState.skills = new Set(normalized.skills);
       // Constrói cache das skills principais
       effectState.videos = normalized.videos;
       getMainSkillsMap();
      effectState.expiresAt = Date.now() + normalized.timeMs;
      effectState.sourceIcon = iconEl;


       if (effectState.timer) clearTimeout(effectState.timer);
       // Pré-carrega imagens das subskills IMEDIATAMENTE
       effectState.timer = setTimeout(() => {
       api.preloadAllSubskillImages();
        clearEffectState();
      }, normalized.timeMs + 5);


       applyEffectClasses();
       //        if (video.readyState < 2) {
    }
       //            video.muted = true;
    function getEffectVideoForIcon(iconEl) {
       //            video.load();
      if (!iconEl) return "";
       //        }
      if (effectState.expiresAt <= Date.now()) return "";
       //     });
       const name = getSkillNameFromIcon(iconEl);
       // }, 500);
       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]', {
       // Escuta mudanças no localStorage
      //    skillName: iconEl.dataset.nome || iconEl.dataset.name,
      window.addEventListener("storage", (e) => {
      //    weaponOn,
        if (e.key === "glaWeaponEnabled") {
      //    hasWeaponData: !!weaponData,
          setTimeout(() => api.refreshCurrentSubSafe(), 50);
      //    weaponData: weaponData,
        }
      //    baseVideoFile,
       });
      //    baseVideoURL
       // });


       const effectVideo = getEffectVideoForIcon(iconEl);
       // LISTENER GLOBAL: Escuta evento de toggle
       if (effectVideo && effectVideo.trim() !== "") {
       window.addEventListener("gla:weaponToggled", (e) => {
         return effectVideo.trim();
         const enabled = e.detail?.enabled ?? false;
      }


      if (weaponOn && weaponData) {
         // Tenta aplicar classes imediatamente
        // console.log('[Skills] checking weapon video', {
         applyWeaponClassesToSubskills();
        //    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
        // Atualiza a subskill ativa se houver
      if (activeCharacter !== null && iconEl.dataset.characterVideos) {
        setTimeout(() => {
        try {
           const activeSub =
           const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
            document.querySelector(".subicon[data-weapon].active") ||
          const characterVideo = characterVideos[activeCharacter];
            document.querySelector(".subicon.active");
           if (characterVideo && characterVideo.trim() !== "") {
           if (activeSub) {
             return characterVideo.trim();
            activeSub.dispatchEvent(new Event("click", { bubbles: true }));
          } else {
             api.refreshCurrentSubSafe();
           }
           }
         } catch (e) {
         }, 50);
          console.warn("[Swap] Erro ao processar character_videos:", e);
       });
        }
       }


       // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
       // LISTENER: Escuta quando subskills estão prontas
      const result = baseVideoFile || baseVideoURL || "";
      window.addEventListener("gla:subskills:ready", (e) => {
      // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
        // Aplica classes de weapon se o toggle estiver ativo
       return result;
        applyWeaponClassesToSubskills();
       });
     }
     }
    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");
    if (document.readyState === "loading") {
      v.className = "skill-video";
       document.addEventListener("DOMContentLoaded", () => {
      v.setAttribute("controls", "");
         setTimeout(init, 100);
       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
    } else {
       const ext = (videoURL.split(".").pop() || "").toLowerCase().split("?")[0];
       setTimeout(init, 100);
      const mimeTypes = {
    }
        mp4: "video/mp4",
  })();
        m4v: "video/mp4",
</script>
        webm: "video/webm",
<style>
        ogv: "video/ogg",
  .subicon-bar {
        ogg: "video/ogg",
    display: flex;
        mov: "video/quicktime",
    gap: 10px;
      };
    padding: 6px 6px;
      const mimeType = mimeTypes[ext] || "video/mp4";
    overflow-x: auto;
    /* Firefox */
    scrollbar-width: thin;
    scrollbar-color: #ababab transparent;
  }


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


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


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


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


        // Vídeo normal da subskill
  .subicon:hover::after {
        if (s.video && s.video.trim() !== "") {
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
          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)
  .subicon:hover {
        if (
    filter: brightness(1);
          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)
  .subicon.active::after {
        if (Array.isArray(s.subs) && s.subs.length > 0) {
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #ffd95a);
          createdCount += preloadSubskillVideosRecursively(
  }
            s.subs,
            parentIdx,
            currentPath
          );
        }
      });
      return createdCount;
    }


    function precreateSubskillVideos() {
  .subicon.active {
      if (!getVideoBox()) return;
    transform: scale(1.1);
      let createdCount = 0;
    z-index: 5;
      iconItems.forEach((parentIcon) => {
    filter: brightness(1);
        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)
  .subicon.active::before {
     function preloadAllVideosRecursively() {
    content: "";
       const vb = getVideoBox();
    position: absolute;
      if (!vb || !iconItems.length) return;
     inset: -4px;
    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));
  }


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


        // Vídeo normal da skill principal
  .top-rail.skills {
        // Prioriza data-video-file (nome do arquivo), depois data-video (pode ser URL completa)
    position: relative;
        let src = (el.dataset.videoFile || "").trim();
    display: flex;
        if (!src) {
    flex-direction: column;
          // Se não tem videoFile, tenta extrair do video (pode ser URL completa)
    align-items: center;
          const videoAttr = (el.dataset.video || "").trim();
    overflow: visible;
          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)
  .top-rail.skills .icon-bar {
        const weaponData = getCachedJSON(el, "weapon");
    margin-bottom: 0;
        if (
    position: relative;
          weaponData &&
    z-index: 2;
          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)
  .subskills-rail {
      precreateSubskillVideos();
    position: absolute;
     }
    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;
  }


     // Função para pré-carregar TODOS os ícones recursivamente
  .subskills-rail::before {
     function preloadAllIconsRecursively() {
    content: "";
      const iconCache = new Set();
     position: absolute;
    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;
  }


      // Função recursiva para processar subskills
  .subskills-rail.collapsed {
      function processSubskillsRecursively(subs) {
    opacity: 0;
        if (!Array.isArray(subs)) return;
    pointer-events: none;
        subs.forEach((s) => {
    transform: translate(-50%, -6px);
          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
  .subskills-rail.hidden {
      iconItems.forEach((el) => {
    visibility: hidden;
        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
  .subskills-spacer {
        const subs = getCachedJSON(el, "subs");
    height: 0;
        if (Array.isArray(subs)) {
    transition: height 0.2s ease;
          processSubskillsRecursively(subs);
  }
        }
      });
    }


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


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


    // Executa pré-carregamento imediatamente
  .subskills-rail .subicon-bar::-webkit-scrollbar-thumb {
    preloadAllAssets();
    background: #151515;
    function wireTooltipsForNewIcons() {
     border-radius: 3px;
      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', {
  .subskills-rail .subicon {
      //     skillName: el.dataset.nome || el.dataset.name,
     width: 42px;
      //     weaponOn,
     height: 42px;
      //     isWeaponVideo,
     border-radius: 6px;
      //     effectiveVideo: getEffectiveSkillVideoFromIcon(el)
     position: relative;
      // });
    overflow: hidden;
      const videoKey = isWeaponVideo
    flex: 0 0 auto;
        ? `weapon:${getWeaponKey(el)}`
    cursor: pointer;
        : isEffectVideo
    isolation: isolate;
          ? effectKey
    -webkit-backface-visibility: hidden;
          : el.dataset.index || "";
    backface-visibility: hidden;
      const isSubskill = !hasIdx || el.dataset.nested === "1";
    transform: translateZ(0);
      const parentIdx = el.dataset.parentIndex || "";
  }
      const subName =
        el.dataset.subName || el.dataset.nome || el.dataset.name || "";


      if (
  .subskills-rail .subicon+.subicon {
        hasIdx &&
    margin-left: 4px;
        !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
  .subskills-rail .subicon img {
          let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
    width: 100%;
          v = subskillVideosCache.get(subWeaponKey);
    height: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
    border-radius: inherit;
  }


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


          // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
  .subskills-rail .subicon.active::after {
          // Isso é útil caso haja alguma diferença no formato do caminho
    box-shadow: inset 0 0 0 2px var(--icon-active, #ffd95a);
          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
  .video-container .skill-video {
            if (!v) {
    width: 100%;
              for (const [key, video] of subskillVideosCache.entries()) {
    height: auto;
                if (key.startsWith(basePattern)) {
    aspect-ratio: 16 / 9;
                  const pathInKey = key.substring(basePattern.length);
    object-fit: cover;
                  // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
    background: #000;
                  if (
    border-radius: 10px;
                    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
  @media (max-width: 900px) {
      if (!v) {
    .subskills-rail {
        // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
      position: static;
        if (failedVideosCache.has(videoURL)) {
      transform: none;
          videoBox.style.display = "none";
      margin-top: -2px;
          return;
       border-top: 0;
        }
       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] : "";


        // Valores do weapon (substituem os da skill base se existirem)
    .subskills-spacer {
        const wPve =
      height: 0 !important;
          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
  .skills-rail-wrap {
        const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(",");
    position: relative;
        attrsHTML = renderAttributes(mergedAttrs);
    display: block;
      } else {
    width: max-content;
        attrsHTML = el.dataset.atr
    margin: 0 auto;
          ? renderAttributes(el.dataset.atr)
  }
          : el.dataset.subattrs
            ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L)
            : "";
      }
      let flagsHTML = "";
      // Debug: verifica se é uma skill do Urouge (verifica pela URL da página ou pelo contexto)
      const isUrougePage =
        window.location.href &&
        window.location.href.toLowerCase().includes("urouge");
      if (el.dataset.flags) {
        try {
          const flags = JSON.parse(el.dataset.flags);
          if (flags && Array.isArray(flags) && flags.length > 0) {
            flagsHTML = renderFlagsRow(flags);
            // Debug para Urouge
            if (isUrougePage) {
              console.log("[Skills] Urouge - Flags processadas:", {
                skill: name || el.dataset.nome,
                flags: flags,
                htmlLength: flagsHTML ? flagsHTML.length : 0,
                hasHTML: !!flagsHTML,
              });
            }
          } else {
            if (isUrougePage) {
              console.warn("[Skills] Urouge - Flags inválidas:", {
                skill: name || el.dataset.nome,
                flags: flags,
                rawData: el.dataset.flags,
              });
            }
          }
        } catch (e) {
          console.warn(
            "[Skills] Erro ao processar flags:",
            e,
            "flags data:",
            el.dataset.flags,
            "skill:",
            name || el.dataset.nome
          );
          if (isUrougePage) {
            console.error(
              "[Skills] Urouge - Erro ao processar flags:",
              e,
              "element:",
              el
            );
          }
        }
      } else {
        // Debug: verifica se deveria ter flags mas não tem (apenas para Urouge)
        if (isUrougePage) {
          console.warn("[Skills] Urouge - Skill sem data-flags:", {
            skill: name || el.dataset.nome,
            element: el,
            allAttributes: Array.from(el.attributes).map(
              (attr) => attr.name + "=" + attr.value
            ),
          });
        }
      }
      if (descBox) {
        descBox.innerHTML = `<div class="skill-title"><h3>${name}</h3></div>${level
          ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>`
          : ""
          }${attrsHTML}<div class="desc">${descHtml}</div>`;
      }
      if (hasWeapon) {
        applyWeaponBadge(el, weaponData, weaponEquipped);
      }
      const vb = getVideoBox();
      if (vb) {
        const oldFlags = vb.querySelector(".skill-flags");
        if (oldFlags) oldFlags.remove();
        if (flagsHTML) {
          vb.insertAdjacentHTML("beforeend", flagsHTML);
          applyFlagTooltips(vb);
        }
      }
      const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
      currIcons.forEach((i) => i.classList.remove("active"));
      const subsRaw = el.dataset.subs || el.getAttribute("data-subs");
      const isFormSwitch =
        el.dataset.formSwitch === "true" ||
        el.getAttribute("data-form-switch") === "true";
      const isSwap =
        el.dataset.swap === "true" || el.getAttribute("data-swap") === "true";


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


      // Não marca como ativo se for form_switch (Change Form)
  /* Subskill com arma ATIVA - laranja/coral vibrante + brilho forte */
      if (!isFormSwitch) {
  .character-box .top-rail.skills .subicon.has-weapon-available.active {
        el.classList.add("active");
    position: relative;
        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)
  .character-box .top-rail.skills .subicon.has-weapon-available.active::after {
       if (isFormSwitch) {
    box-shadow: none !important;
        // Atualiza o data-video-file do ícone com o vídeo correto da transição baseado na forma atual
    background: linear-gradient(135deg,
        try {
        #ff5722 0%,
          const formsJSON = skillsRoot.dataset.forms || "{}";
        #ff7043 20%,
          if (formsJSON && formsJSON !== "{}") {
        #ff8a65 40%,
            const tempFormsData = JSON.parse(formsJSON);
        #ff7043 60%,
            const formNames = Object.keys(tempFormsData);
        #ff5722 80%,
        #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;
  }


            // Busca vídeo de transição na skill Change Form
  .character-box .top-rail.skills .subicon.has-weapon-available.active::before {
            const formVideosRaw =
    box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.95),
              el.dataset.formVideos || el.getAttribute("data-form-videos");
      0 0 40px 16px rgba(255, 87, 34, 0.75),
            if (formVideosRaw) {
      0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6) !important;
              const videos = JSON.parse(formVideosRaw);
    opacity: 1 !important;
              // 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)
  /* Modo arma ON - efeito animado nos subicons */
        showVideoForIcon(el);
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available {
        switchForm();
    position: relative;
        return;
  }
      }


      if (isBack && !isFormSwitch && barStack.length) {
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::after {
        const prev = barStack.pop();
    box-shadow: none !important;
        // Restaura currentForm se estava salvo no snapshot
     background: linear-gradient(90deg,
        if (prev.currentForm !== undefined) {
         rgba(255, 80, 80, 0.9) 0%,
          currentForm = prev.currentForm;
         rgba(255, 120, 60, 1) 25%,
        }
         rgba(255, 80, 80, 0.9) 50%,
        renderBarFromItems(prev);
         rgba(255, 120, 60, 1) 75%,
        const btn = document.querySelector(".skills-back-wrapper");
         rgba(255, 80, 80, 0.9) 100%) !important;
        if (btn) btn.style.display = barStack.length ? "block" : "none";
    background-size: 400% 100% !important;
        return;
    animation: weapon-subicon-border-scan 4s linear infinite !important;
      }
    -webkit-mask: linear-gradient(#fff 0 0) content-box,
      if (openSubs && subsRaw && subsRaw.trim() !== "") {
       linear-gradient(#fff 0 0) !important;
        if (barStack.length && barStack[barStack.length - 1].parentIcon === el)
    mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
          return;
    -webkit-mask-composite: xor !important;
        try {
    mask-composite: exclude !important;
          const subs = JSON.parse(subsRaw);
    padding: 2px !important;
          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
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::before {
      function updateBackButtonPosition() {
    content: "";
        if (!rail || !backWrap) return;
    position: absolute;
        const railRect = rail.getBoundingClientRect();
    inset: -4px;
        const wrapRect = wrap.getBoundingClientRect();
    border-radius: calc(6px + 4px);
        // Calcula a posição relativa do rail dentro do wrap
    pointer-events: none;
        const railLeft = railRect.left - wrapRect.left;
    z-index: 1;
        // Posiciona o wrapper na borda esquerda do rail
    box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
        // O botão interno usa translateX(-97%) para ficar atrás da barra
      0 0 32px 12px rgba(255, 80, 80, 0.6),
        backWrap.style.left = railLeft + "px";
      0 0 48px 18px rgba(255, 100, 60, 0.4) !important;
      }
    opacity: 1 !important;
    animation: weapon-subicon-pulse 3s ease-in-out infinite !important;
  }


      // Atualiza a posição quando necessário
  /* Subskill ativa com arma - mais intenso */
      if (backWrap.style.display !== "none") {
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::after {
         // Usa requestAnimationFrame para garantir que o DOM foi atualizado
    background: linear-gradient(135deg,
         requestAnimationFrame(() => {
        #ff3d00 0%,
          updateBackButtonPosition();
         #ff5722 15%,
        });
        #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;
  }


        // Recalcula em resize e scroll
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::before {
        if (!backWrap.dataset.positionWired) {
    box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
          backWrap.dataset.positionWired = "1";
      0 0 56px 24px rgba(255, 87, 34, 0.85),
          const updateOnResize = () => {
      0 0 80px 32px rgba(255, 140, 60, 0.6), 0 0 0 5px rgba(255, 87, 34, 0.75) !important;
            if (backWrap.style.display !== "none") {
    animation: weapon-subicon-pulse-active 2.5s ease-in-out infinite !important;
              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;
  @keyframes weapon-icon-border-scan {
    0% {
       background-position: 0% 0%;
     }
     }
     function renderBarFromItems(itemsOrSnapshot) {
 
       // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
     100% {
      let items, savedCurrentForm;
       background-position: 400% 0%;
      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) {
  @keyframes weapon-subicon-border-scan {
        tip.setAttribute("aria-hidden", "true");
    0% {
        tip.style.opacity = "0";
       background-position: 0% 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();


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


    const b0 = ensureBackButton();
  @keyframes weapon-subicon-pulse {
    if (b0) {
      b0.classList.add("peek");
      b0.style.alignSelf = "stretch";
    }


     // Move inicialização de tooltip para requestIdleCallback (não crítico)
     0%,
     if ("requestIdleCallback" in window) {
     100% {
       requestIdleCallback(
       opacity: 0.95;
        () => {
      box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
          (function initSkillTooltip() {
        0 0 32px 12px rgba(255, 80, 80, 0.6),
            if (document.querySelector(".skill-tooltip")) return;
         0 0 48px 18px rgba(255, 100, 60, 0.4);
            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() {
     50% {
      const tabs = Array.from(document.querySelectorAll(".tab-btn"));
       opacity: 1;
      if (!tabs.length) return;
       box-shadow: 0 0 24px 8px rgba(255, 80, 80, 1),
      const contents = Array.from(document.querySelectorAll(".tab-content"));
         0 0 48px 16px rgba(255, 80, 80, 0.75),
      const characterBox = document.querySelector(".character-box");
         0 0 64px 24px rgba(255, 120, 60, 0.5);
      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]) =>
            `<div class="attr-row"><span class="attr-label">${rowLabel}</span><span class="attr-value">${rowValue}</span></div>`
        )
        .join("");
      return `<div class="attr-list">${html}</div>`;
     }
     }
    function syncDescHeight() { }
  }
    window.addEventListener("resize", syncDescHeight);
    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
  @keyframes weapon-subicon-pulse-active {
    (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
    0%,
        document
    100% {
          .querySelectorAll("img:not(.topbar-icon):not([loading])")
      opacity: 0.95;
          .forEach((img) => {
      box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.9),
            const rect = img.getBoundingClientRect();
        0 0 40px 16px rgba(255, 87, 34, 0.7),
            if (rect.bottom > window.innerHeight || rect.top < 0) {
        0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6);
              imageObserver.observe(img);
     }
            }
          });
      }
     })();


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

Edição das 00h53min de 11 de fevereiro de 2026

<script>

 (function () {
   const SUBVIDEO_DEBUG = false; // Desabilitado para melhor performance
   function logSubVideo(...args) {
     if (!SUBVIDEO_DEBUG) return;
     // console.log('[SubVideo]', ...args);
   }
   const api = (window.__subskills ||= {});
   // Cache global de elementos de vídeo para subskills - baseado em URL
   const subskillVideoElementCache = new Map(); // key: videoURL (string), value: HTMLVideoElement
   const imagePreloadCache = new Map();
   let subRail, subBar, spacer;
   // Cache das skills principais (capturado na carga da página)
   let cachedMainSkills = null;
   // ===== HERANÇA DE ATRIBUTOS: busca dados das skills principais =====
   // FASE 5: Refatorado para usar byId como fonte primária
   function getMainSkillsMap() {
     // Retorna cache se já foi construído E tem dados
     if (cachedMainSkills && cachedMainSkills.byId.size > 0) {
       return cachedMainSkills;
     }
     const maps = {
       byId: new Map(), // FASE 5: Fonte primária por ID
       byName: new Map(), // FASE 5: Mantido para compatibilidade (legado)
       byIndex: new Map(), // Mantido para compatibilidade
     };
     // Busca skills com data-index (skills principais, não subskills)
     const icons = document.querySelectorAll(
       ".icon-bar .skill-icon[data-index][data-nome]"
     );
     icons.forEach((icon) => {
       const name = (icon.dataset.nome || "").trim();
       if (!name) return;
       // Extrai atributos do data-atr (formato: "pve, pvp, energy, cd")
       const atrRaw = icon.dataset.atr || "";
       const parts = atrRaw.split(",").map((x) => (x || "").trim());
       const powerpve = parts[0] && parts[0] !== "-" ? parts[0] : "";
       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)
       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]) : "";
       }
       // Nome original do arquivo de vídeo (caso exista)
       let videoFile = (icon.dataset.videoFile || "").trim();
       if (!videoFile) {
         const videoUrl = icon.dataset.video || "";
         const videoMatch = videoUrl.match(/FilePath\/([^&?]+)/);
         videoFile = videoMatch ? decodeURIComponent(videoMatch[1]) : "";
       }
       const index = (icon.dataset.index || "").trim();
       // FASE 5: Extrai ID do data-skill-id ou gera a partir do nome
       const skillId = icon.dataset.skillId || icon.dataset.id || name;
       const data = {
         id: skillId, // FASE 5: ID único
         name: name, // Mantido para compatibilidade
         icon: iconFile,
         level: icon.dataset.level || "",
         video: videoFile,
         powerpve: powerpve,
         powerpvp: powerpvp,
         cooldown: cooldown,
         energy: energy,
       };
       // Mantém descrições caso precise como fallback extra
       if (icon.dataset.descPt) data.descPt = icon.dataset.descPt;
       if (icon.dataset.descEn) data.descEn = icon.dataset.descEn;
       if (icon.dataset.descEs) data.descEs = icon.dataset.descEs;
       if (icon.dataset.descPl) data.descPl = icon.dataset.descPl;
       // FASE 5: Processa desc_i18n se existir
       if (icon.dataset.descI18n) {
         try {
           data.desc_i18n = JSON.parse(icon.dataset.descI18n);
         } catch (e) {
           // Ignora erro de parse
         }
       }
       // FASE 5: byId é a fonte primária
       maps.byId.set(skillId, data);
       maps.byName.set(name, data); // FASE 5: Mantido para compatibilidade (legado)
       if (index) {
         // Guarda tanto como string quanto como número para compatibilidade
         maps.byIndex.set(index, data);
         maps.byIndex.set(parseInt(index, 10), data);
       }
     });
     // Cacheia para uso futuro (importante: as skills principais não mudam)
     cachedMainSkills = maps;
     return maps;
   }
   // FASE 4: Resolver único para toda resolução de skill/subskill
   // Retorna tudo que a UI precisa renderizar de forma determinística
   function resolveSkillView(skill, context) {
     // context: { lang, weaponMode, mainSkills }
     // Retorna: { title, desc, video, attrs, flags, weapon }
     const lang = context.lang || "pt";
     const weaponMode = context.weaponMode || false;
     const mainSkills = context.mainSkills || null;
     // 1. Título: sempre display_name
     const title = skill.display_name || skill.name || skill.n || "";
     // 2. Descrição: sempre vem da skill/subskill, nunca herda
     let desc = "";
     if (weaponMode && skill.weapon?.desc_i18n) {
       desc =
         skill.weapon.desc_i18n[lang] ||
         skill.weapon.desc_i18n.pt ||
         skill.weapon.desc_i18n.en ||
         "";
     } else {
       desc =
         skill.desc_i18n?.[lang] ||
         skill.desc_i18n?.pt ||
         skill.desc_i18n?.en ||
         "";
       if (!desc) {
         desc =
           skill.descPt || skill.descEn || skill.descEs || skill.descPl || "";
       }
     }
     // 3. Vídeo: sempre da skill/subskill, nunca herdado
     const video = skill.video || "";
     // 4. Atributos: weapon mode ou normal
     let attrs = {
       powerpve: skill.powerpve,
       powerpvp: skill.powerpvp,
       energy: skill.energy,
       cooldown: skill.cooldown,
     };
     if (weaponMode && skill.weapon) {
       attrs = {
         powerpve: skill.weapon.powerpve || skill.powerpve,
         powerpvp: skill.weapon.powerpvp || skill.powerpvp,
         energy: skill.weapon.energy || skill.energy,
         cooldown: skill.weapon.cooldown || skill.cooldown,
       };
     }
     // 5. Level: weapon ou normal
     const level =
       weaponMode && skill.weapon?.level
         ? skill.weapon.level.toString().trim()
         : (skill.level || "").toString().trim();
     return {
       title,
       desc,
       video,
       attrs,
       flags: skill.flags,
       weapon: skill.weapon,
       level,
     };
   }
   // applyInheritance: aplica herança de atributos (nunca herda descrição ou vídeo)
   function applyInheritance(sub, mainSkills) {
     // NOVO SISTEMA: herança explícita
     // 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;
     const inheritFields = sub.inherit_fields || [];
     // Converte para set para busca rápida
     const inheritFieldsSet = new Set(inheritFields);
     // Busca skill principal
     let main = null;
     if (inheritFrom && mainSkills) {
       // FASE 5: Tenta por ID primeiro (fonte primária)
       if (mainSkills.byId && mainSkills.byId.has(inheritFrom)) {
         main = mainSkills.byId.get(inheritFrom);
       }
       // Fallback: tenta por nome (compatibilidade legado)
       if (!main && mainSkills.byName && mainSkills.byName.has(inheritFrom)) {
         main = mainSkills.byName.get(inheritFrom);
       }
       // Tenta por índice (compatibilidade com sistema antigo refM)
       if (!main && mainSkills.byIndex) {
         const refIndex = ((sub.refM || sub.m || sub.M || "") + "").trim();
         if (refIndex && mainSkills.byIndex.has(refIndex)) {
           main = mainSkills.byIndex.get(refIndex);
         }
         if (!main && refIndex) {
           const numIndex = parseInt(refIndex, 10);
           if (!isNaN(numIndex) && mainSkills.byIndex.has(numIndex)) {
             main = mainSkills.byIndex.get(numIndex);
           }
         }
       }
     }
     // Se não tem inherit_from ou main, não herda nada
     if (!inheritFrom || !main) {
       return sub;
     }
     // Função auxiliar: verifica se campo DEVE ser herdado
     const shouldInheritField = (fieldName) => {
       return inheritFieldsSet.has(fieldName);
     };
     // Helper para verificar se um valor existe e não é string vazia
     const hasValue = (val) => {
       if (val === undefined || val === null) return false;
       if (typeof val === "number") return !isNaN(val);
       const str = String(val).trim();
       return str !== "" && str !== "NaN";
     };
     // Vídeo NUNCA é herdado da skill principal
     const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
     const finalVideo = hasOwnVideo ? sub.video || "" : "";
     return {
       ...sub,
       name: sub.name || main.name,
       // Herda apenas se campo estiver em inherit_fields
       icon:
         sub.icon && sub.icon !== ""
           ? sub.icon
           : 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) {
     // 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 && 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 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 preloadImage(iconFile) {
     const url = filePathURL(iconFile || "");
     if (imagePreloadCache.has(url)) {
       return imagePreloadCache.get(url);
     }
     const promise = new Promise((resolve, reject) => {
       const img = new Image();
       img.decoding = "async";
       img.loading = "eager";
       img.referrerPolicy = "same-origin";
       img.onload = () => resolve(url);
       img.onerror = () => resolve(url);
       img.src = url;
     });
     imagePreloadCache.set(url, promise);
     return promise;
   }
   function getLabels() {
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const raw = (
       document.documentElement.lang ||
       skillsRoot?.dataset.i18nDefault ||
       "pt"
     ).toLowerCase();
     const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
     return (
       i18nMap[lang] ||
       i18nMap.pt || {
         cooldown: "Recarga",
         energy_gain: "Ganho de energia",
         energy_cost: "Custo de energia",
         power: "Poder",
         power_pvp: "Poder PvP",
         level: "Nível",
       }
     );
   }
   // Verifica se o modo weapon está ativo
   function isWeaponModeOn() {
     try {
       return localStorage.getItem("glaWeaponEnabled") === "1";
     } catch (e) {
       return false;
     }
   }
   // Retorna os atributos corretos (weapon ou normal)
   function getEffectiveAttrs(s) {
     const weaponOn = isWeaponModeOn();
     if (weaponOn && s.weapon) {
       return {
         powerpve: s.weapon.powerpve || s.powerpve,
         powerpvp: s.weapon.powerpvp || s.powerpvp,
         energy: s.weapon.energy || s.energy,
         cooldown: s.weapon.cooldown || s.cooldown,
       };
     }
     return {
       powerpve: s.powerpve,
       powerpvp: s.powerpvp,
       energy: s.energy,
       cooldown: s.cooldown,
     };
   }
   // Retorna a descrição correta (weapon ou normal)
   // Aceita tanto desc_i18n quanto desc para compatibilidade
   function getEffectiveDesc(s) {
     const weaponOn = isWeaponModeOn();
     const raw = (document.documentElement.lang || "pt").toLowerCase();
     const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
     // Para weapon: aceita tanto desc_i18n quanto desc
     if (weaponOn && s.weapon) {
       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
     const base = s.desc_i18n || {};
     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 (
       weaponOn &&
       s.weapon &&
       s.weapon.video &&
       s.weapon.video.trim() !== ""
     ) {
       return s.weapon.video;
     }
     return s.video || "";
   }
   // Função única para obtenção de vídeo de subskill - baseada em URL
   function getOrCreateSubskillVideo(videoURL) {
     if (!videoURL || videoURL.trim() === "") return null;
     const normalizedURL = normalizeFileURL(videoURL);
     if (!normalizedURL || normalizedURL.trim() === "") return null;
     // 1. Tenta cache local primeiro (compatibilidade)
     if (subskillVideoElementCache.has(normalizedURL)) {
       logSubVideo("getOrCreateSubskillVideo: local cache hit", {
         videoURL: normalizedURL,
       });
       return subskillVideoElementCache.get(normalizedURL);
     }
     // 2. Tenta cache global de subskills (window.__subskillVideosCache)
     const globalCache = window.__subskillVideosCache;
     if (globalCache && globalCache instanceof Map) {
       // O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
       // Precisamos buscar por URL normalizada
       for (const [key, video] of globalCache.entries()) {
         const src = video.querySelector("source");
         if (src && src.src === normalizedURL) {
           logSubVideo("getOrCreateSubskillVideo: global cache hit", {
             videoURL: normalizedURL,
             key,
           });
           // Adiciona ao cache local também para acesso rápido
           subskillVideoElementCache.set(normalizedURL, video);
           return video;
         }
       }
     }
     // 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)
     console.warn("[Subskills] Vídeo não encontrado no cache:", {
       videoURL: normalizedURL,
       originalURL: videoURL,
     });
     return null;
   }
   function renderSubAttrs(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 renderFlagsRow(flags) {
     const map = {
       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);
     if (!arrFiltered.length) return "";
     const items = arrFiltered
       .map((k) => {
         const file = map[k];
         if (!file) return "";
         return `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(file)}">`;
       })
       .filter(Boolean)
       .join("");

return items ? `

${items}

` : "";

   }
   function applyFlagTooltips(container) {
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return;
     let pack = {};
     try {
       pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
     } catch (e) { }
     const raw = (document.documentElement.lang || "pt").toLowerCase();
     const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
     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();
       });
     });
   }
   function ensureRail(iconsBar) {
     const rail = iconsBar.closest(".top-rail");
     if (!rail) {
       return null;
     }
     if (!subRail) {
       subRail = document.createElement("div");
       subRail.className = "subskills-rail collapsed hidden";
       rail.appendChild(subRail);
     }
     if (!subBar) {
       subBar = document.createElement("div");
       subBar.className = "subicon-bar";
       subRail.appendChild(subBar);
     }
     if (!spacer) {
       spacer = document.createElement("div");
       spacer.className = "subskills-spacer";
       rail.parentNode.insertBefore(spacer, rail.nextSibling);
     }
     return rail;
   }
   // Função para mostrar vídeo de subskill usando cache baseado em URL
   function showSubVideo(videoURL, videoBox) {
     logSubVideo("showSubVideo called", {
       videoURL,
       videoBoxExists: !!videoBox,
     });
     if (!videoBox) return;
     // Ocultar todos os vídeos existentes no container
     videoBox
       .querySelectorAll('video.skill-video[data-sub="1"]')
       .forEach((v) => {
         v.style.display = "none";
       });
     // Obter vídeo via getOrCreateSubskillVideo (única forma de obter vídeos)
     const video = getOrCreateSubskillVideo(videoURL);
     if (!video) {
       logSubVideo("no video found for URL, hiding box", { videoURL });
       videoBox.style.display = "none";
       return;
     }
     // Se o vídeo não estiver conectado ao DOM, anexá-lo ao container
     if (!video.isConnected || video.parentNode !== videoBox) {
       logSubVideo("video not in DOM, appending", {
         videoURL,
         isConnected: video.isConnected,
         parentNode: video.parentNode,
       });
       if (video.parentNode) {
         video.parentNode.removeChild(video);
       }
       videoBox.appendChild(video);
     }
     logSubVideo("showing video", {
       videoURL,
       readyState: video.readyState,
       paused: video.paused,
       currentTime: video.currentTime,
     });
     // Exibir o vídeo
     videoBox.style.display = "block";
     video.style.display = "block";
     // Reinicia o vídeo para feedback visual imediato
     try {
       video.currentTime = 0;
     } catch (e) { }
     // Tenta dar play se o vídeo já está pronto
     if (video.readyState >= 2) {
       video
         .play()
         .then(() => {
           logSubVideo("play() resolved", {
             videoURL,
             currentTime: video.currentTime,
             readyState: video.readyState,
           });
         })
         .catch((err) => {
           logSubVideo("play() rejected", { videoURL, error: err });
         });
     } else {
       // Se não está pronto ainda, espera o evento canplay
       logSubVideo("video not ready, waiting for canplay", {
         videoURL,
         readyState: video.readyState,
       });
       const onCanPlay = () => {
         video.removeEventListener("canplay", onCanPlay);
         video.play().catch(() => { });
       };
       video.addEventListener("canplay", onCanPlay, { once: true });
     }
   }
   // Funções antigas removidas - usar getOrCreateSubskillVideo() em vez disso
   // REMOVIDO: ensureSubVideoCached
   // REMOVIDO: ensureSubVideoInCache
   api.refreshCurrentSubSafe = function () {
     const btn = document.querySelector(".subskills-rail .subicon.active");
     if (!btn) return false;
     const had = document.body.dataset.suppressSkillPlay;
     document.body.dataset.suppressSkillPlay = "1";
     try {
       btn.dispatchEvent(new Event("click", { bubbles: true }));
     } finally {
       if (had) document.body.dataset.suppressSkillPlay = had;
       else delete document.body.dataset.suppressSkillPlay;
     }
     return true;
   };
   // Função auxiliar para aplicar classes de weapon nas subskills renderizadas
   const applyWeaponClassesToSubskills = () => {
     const weaponOn = isWeaponModeOn();
     // Busca TODAS as subskills com weapon
     let weaponSubs = document.querySelectorAll(".subicon[data-weapon]");
     weaponSubs.forEach((el) => {
       const weaponData = el.getAttribute("data-weapon");
       // Verifica se o weapon não está vazio (não é '{}' ou vazio)
       let hasValidWeapon = false;
       if (weaponData && weaponData.trim() !== "" && weaponData !== "{}") {
         try {
           const weaponObj = JSON.parse(weaponData);
           if (
             weaponObj &&
             typeof weaponObj === "object" &&
             Object.keys(weaponObj).length > 0
           ) {
             hasValidWeapon = true;
           }
         } catch (e) {
           // Se não for JSON válido, não considera como weapon válido
         }
       }
       if (weaponOn && hasValidWeapon) {
         el.classList.add("has-weapon-available");
         if (el.classList.contains("active")) {
           el.classList.add("weapon-equipped");
         }
       } else {
         el.classList.remove("has-weapon-available");
         el.classList.remove("weapon-equipped");
       }
     });
   };
   api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) {
     logSubVideo("api.renderBarFrom called", {
       hasEl: !!el,
       hasIconsBar: !!iconsBar,
       hasDescBox: !!descBox,
       hasVideoBox: !!videoBox,
       parentIndex: el?.dataset?.index || "unknown",
     });
     const rail = ensureRail(iconsBar);
     if (!rail) {
       logSubVideo("api.renderBarFrom: no rail found, returning");
       return;
     }
     const rawSubs = el.getAttribute("data-subs") || "";
     const rawOrder = el.getAttribute("data-suborder") || "";
     const parentIdx = el.dataset.index || "";
     logSubVideo("api.renderBarFrom: parsing subs", {
       parentIdx,
       hasRawSubs: !!rawSubs.trim(),
       rawSubsLength: rawSubs.length,
     });
     if (!rawSubs.trim()) {
       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 {
       subs = JSON.parse(rawSubs);
     } catch {
       subs = [];
     }
     // NORMALIZADOR: Converte weaponPacked para weapon se necessário
     subs = subs.map((sub) => {
       // Se tem weaponPacked mas não tem weapon, tenta parsear
       if (
         sub.weaponPacked &&
         !sub.weapon &&
         typeof sub.weaponPacked === "string" &&
         sub.weaponPacked.trim() !== ""
       ) {
         const parts = sub.weaponPacked.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,
           };
           // 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;
     });
     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
     const mainSkills = getMainSkillsMap();
     // Debug: log dos dados antes da herança
     if (SUBVIDEO_DEBUG && subs.length > 0) {
       logSubVideo("subs before inheritance", {
         subsCount: subs.length,
         firstSub: JSON.stringify(subs[0]),
         allSubs: subs.map((s) => ({
           refM: s.refM || s.M || s.m,
           video: s.video,
           hasVideo: s.hasOwnProperty("video"),
         })),
       });
     }
     // Aplica herança ANTES de processar
     subs = subs.map((sub) => applyInheritance(sub, mainSkills));
     // Remove subskills que ficaram sem nome após herança
     subs = subs.filter((s) => (s.name || s.n || "").trim() !== "");
     subRail.classList.add("hidden");
     subBar.innerHTML = "";
     // Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
     const subsById = new Map();
     subs.forEach((s) => {
       const id = s.id || s.name || s.n || "";
       if (id) {
         subsById.set(id, s);
       }
     });
     // Usa a ordem natural das subskills após herança
     let order = subs.map((s) => s.id || s.name || s.n || "");
     if (rawOrder.trim()) {
       try {
         const preferred = JSON.parse(rawOrder);
         if (Array.isArray(preferred) && preferred.length) {
           // Tenta por ID primeiro, depois por nome (compatibilidade)
           const byName = new Map(subs.map((s) => [s.name || s.n || "", s]));
           const byId = new Map(
             subs.map((s) => [s.id || s.name || s.n || "", s])
           );
           order = preferred
             .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
     const iconPreloadPromises = [];
     order.forEach((idOrName) => {
       const s =
         subsById.get(idOrName) ||
         subs.find((x) => (x.name || x.n || "") === idOrName);
       if (s && s.icon && s.icon.trim() !== "") {
         iconPreloadPromises.push(preloadImage(s.icon));
       }
     });
     // Vídeos serão criados e carregados sob demanda quando necessário
     // Função para renderizar a barra de subskills
     const renderSubskillsBar = () => {
       order.forEach((idOrName) => {
         // CORREÇÃO: Usa ID para lookup (evita colisão de nomes)
         const s =
           subsById.get(idOrName) ||
           subs.find((x) => (x.name || x.n || "") === idOrName);
         if (!s) {
           return;
         }
         const item = document.createElement("div");
         item.className = "subicon";
         item.title = s.name || s.n || "";
         // CORREÇÃO: Adiciona data-skill-id para lookups únicos
         const skillId = s.id || s.name || s.n || "";
         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;
         }
         img.decoding = "async";
         img.loading = "lazy";
         img.width = 42;
         img.height = 42;
         item.appendChild(img);
         // Verifica weapon de forma mais robusta
         const hasWeapon =
           s.weapon &&
           ((typeof s.weapon === "object" &&
             Object.keys(s.weapon).length > 0) ||
             (typeof s.weapon === "string" && s.weapon.trim() !== ""));
         const subName = (s.name || s.n || "").trim();
         if (hasWeapon) {
           // Normaliza weapon se for string
           let weaponObj = s.weapon;
           if (typeof weaponObj === "string") {
             try {
               weaponObj = JSON.parse(weaponObj);
             } catch {
               // Se falhar, tenta formato ~
               const parts = weaponObj.split("~");
               if (parts.length >= 2) {
                 weaponObj = {
                   icon: parts[0] || "",
                   powerpve: parts[1] || null,
                   powerpvp: parts[2] || null,
                   cooldown: parts[3] || null,
                   video: parts[4] || "",
                   energy: parts[5] || null,
                 };
                 Object.keys(weaponObj).forEach((k) => {
                   if (weaponObj[k] === "" || weaponObj[k] === null) {
                     delete weaponObj[k];
                   }
                 });
               } else {
                 weaponObj = null;
               }
             }
           }
           if (
             weaponObj &&
             typeof weaponObj === "object" &&
             Object.keys(weaponObj).length > 0
           ) {
             try {
               item.dataset.weapon = JSON.stringify(weaponObj);
             } catch (e) {
               console.error(
                 "[Subskills] Erro ao serializar weapon de subskill",
                 subName,
                 e
               );
             }
             // Aplica classe inicial se o toggle já está ativo
             if (isWeaponModeOn()) {
               item.classList.add("has-weapon-available");
             }
           }
         }
         // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
         logSubVideo("attaching click handler to subicon", {
           subName: (s.name || s.n || "").trim(),
           parentIdx,
           itemClassName: item.className,
         });
         item.addEventListener("click", () => {
           const L = getLabels();
           // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
           const skillId = item.dataset.skillId || s.id || s.name || s.n || "";
           if (!skillId) {
             console.error(
               "[Subskills] Click handler: skillId não encontrado",
               { item, s }
             );
             return;
           }
           // CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
           let currentSub = subsById.get(skillId);
           let lookupMethod = "id";
           if (!currentSub) {
             // Fallback: tenta por nome (compatibilidade com dados antigos)
             const subName = (s.name || s.n || "").trim();
             currentSub = subs.find((x) => (x.name || x.n || "") === subName);
             lookupMethod = "name_fallback";
             if (!currentSub) {
               console.error(
                 "[Subskills] Click handler: subskill não encontrada",
                 {
                   skillId,
                   subName,
                   availableIds: Array.from(subsById.keys()),
                 }
               );
               return;
             }
           }
           const subName = (currentSub.name || currentSub.n || "").trim();
           logSubVideo("subicon click HANDLER EXECUTED", {
             skillId,
             subName,
             parentIdx,
             weaponMode: isWeaponModeOn(),
             hasWeaponDataAttr: !!item.dataset.weapon,
             rawWeaponDataset: item.dataset.weapon || null,
           });
           // Lê weapon diretamente do atributo data-weapon
           let subWeaponData = null;
           if (item.dataset.weapon) {
             try {
               const parsed = JSON.parse(item.dataset.weapon);
               // Só considera weapon válido se for um objeto não vazio
               if (
                 parsed &&
                 typeof parsed === "object" &&
                 Object.keys(parsed).length > 0
               ) {
                 subWeaponData = parsed;
               }
             } catch (e) {
               subWeaponData = null;
             }
           }
           const hasSubWeapon = !!subWeaponData;
           const weaponOn = isWeaponModeOn();
           const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
           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;
           }
           // Resolve tudo usando o resolver único
           const resolved = resolveSkillView(skillForResolution, {
             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);
           }
           if (descBox) {

descBox.innerHTML = `

${resolved.title || currentSub.name || currentSub.n || "" }

${level  ? `

${L.level} ${level}

`

                 : ""

}${renderSubAttrs(attrsObj, L)}

${(
                 chosen || ""
).replace(/(.*?)/g, "$1")}

`;

           }
           if (videoBox) {
             const oldFlags = videoBox.querySelector(".skill-flags");
             if (oldFlags) oldFlags.remove();
             if (flagsHTML) {
               videoBox.insertAdjacentHTML("beforeend", flagsHTML);
               applyFlagTooltips(videoBox);
             }
           }
           // FASE 4: Vídeo vem do resolved (já resolvido)
           const effectiveVideo = resolved.video;
           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) => {
             c.classList.remove("active");
             c.classList.remove("weapon-equipped");
           });
           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) {
           const { show, hide, measureAndPos, lockUntil } =
             window.__globalSkillTooltip;
           const label = item.title || "";
           item.setAttribute("aria-label", label);
           if (item.hasAttribute("title")) item.removeAttribute("title");
           item.addEventListener("mouseenter", () => show(item, label));
           item.addEventListener("mousemove", () => {
             if (performance.now() >= lockUntil.value) measureAndPos(item);
           });
           item.addEventListener("click", () => {
             lockUntil.value = performance.now() + 240;
             measureAndPos(item);
           });
           item.addEventListener("mouseleave", hide);
         }
         subBar.appendChild(item);
         logSubVideo("subicon appended to subBar", {
           subName: (s.name || s.n || "").trim(),
           parentIdx,
           subBarChildrenCount: subBar.children.length,
         });
       });
       // Aplica classes de weapon nas subskills recém-renderizadas
       applyWeaponClassesToSubskills();
       // Dispara evento para notificar que subskills estão prontas
       window.dispatchEvent(
         new CustomEvent("gla:subskills:ready", {
           detail: { count: order.length },
         })
       );
       // Remove listener anterior se existir (evita duplicação)
       if (subBar._weaponToggleListener) {
         window.removeEventListener(
           "gla:weaponToggled",
           subBar._weaponToggleListener
         );
       }
       // Cria novo listener que opera no subBar atual
       subBar._weaponToggleListener = (e) => {
         const enabled = e.detail?.enabled ?? false;
         // Aplica classes usando a função auxiliar
         applyWeaponClassesToSubskills();
         // Atualiza a subskill ativa se houver
         setTimeout(() => {
           const activeSub =
             subBar.querySelector(".subicon[data-weapon].active") ||
             subBar.querySelector(".subicon.active");
           if (activeSub) {
             activeSub.dispatchEvent(new Event("click", { bubbles: true }));
           } else {
             api.refreshCurrentSubSafe();
           }
         }, 50);
       };
       window.addEventListener(
         "gla:weaponToggled",
         subBar._weaponToggleListener
       );
       requestAnimationFrame(() => {
         subRail.classList.remove("collapsed");
         subRail.classList.remove("hidden");
         const h = subRail.offsetHeight || 48;
         if (spacer) spacer.style.height = h + "px";
       });
     };
     // Chama a função de renderização após pré-carregar ícones
     Promise.all(iconPreloadPromises)
       .then(() => {
         setTimeout(() => {
           renderSubskillsBar();
         }, 10);
       })
       .catch(() => {
         setTimeout(() => {
           renderSubskillsBar();
         }, 10);
       });
   };
   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) => {
       try {
         v.pause();
       } catch { }
       v.style.display = "none";
     });
   };
   window.renderSubskillsBarFrom = function (el, ctx) {
     api.renderBarFrom(el, ctx);
   };
   api.preloadAllSubskillImages = function () {
     const allSkillIcons = document.querySelectorAll(
       ".icon-bar .skill-icon[data-subs]"
     );
     const preloadPromises = [];
     let totalImages = 0;
     allSkillIcons.forEach((icon) => {
       try {
         const subsRaw = icon.getAttribute("data-subs");
         if (!subsRaw) return;
         const subs = JSON.parse(subsRaw);
         if (!Array.isArray(subs)) return;
         subs.forEach((s) => {
           if (s && s.icon && s.icon.trim() !== "") {
             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);
       }
     });
     if (totalImages > 0) {
       // console.log('[Subskills] preloadAllSubskillImages: pré-carregando', totalImages, 'ícones');
       return Promise.all(preloadPromises).then(() => {
         // console.log('[Subskills] preloadAllSubskillImages: todos os ícones carregados');
       });
     }
     return Promise.resolve();
   };
   // Função para pré-carregar vídeos de subskills usando getOrCreateSubskillVideo
   // Função preloadAllSubskillVideos removida - vídeos são carregados sob demanda
   // Função syncVideoCaches removida - não é mais necessária com cache baseado em URL
   // Inicialização
   function init() {
     logSubVideo("init: starting initialization");
     // Constrói cache das skills principais
     getMainSkillsMap();
     // Pré-carrega imagens das subskills IMEDIATAMENTE
     api.preloadAllSubskillImages();
     //         if (video.readyState < 2) {
     //             video.muted = true;
     //             video.load();
     //         }
     //     });
     // }, 500);
     // Escuta mudanças no localStorage
     window.addEventListener("storage", (e) => {
       if (e.key === "glaWeaponEnabled") {
         setTimeout(() => api.refreshCurrentSubSafe(), 50);
       }
     });
     // LISTENER GLOBAL: Escuta evento de toggle
     window.addEventListener("gla:weaponToggled", (e) => {
       const enabled = e.detail?.enabled ?? false;
       // Tenta aplicar classes imediatamente
       applyWeaponClassesToSubskills();
       // 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);
     });
     // LISTENER: Escuta quando subskills estão prontas
     window.addEventListener("gla:subskills:ready", (e) => {
       // Aplica classes de weapon se o toggle estiver ativo
       applyWeaponClassesToSubskills();
     });
   }
   if (document.readyState === "loading") {
     document.addEventListener("DOMContentLoaded", () => {
       setTimeout(init, 100);
     });
   } else {
     setTimeout(init, 100);
   }
 })();

</script> <style>

 .subicon-bar {
   display: flex;
   gap: 10px;
   padding: 6px 6px;
   overflow-x: auto;
   /* Firefox */
   scrollbar-width: thin;
   scrollbar-color: #ababab transparent;
 }
 .subicon-bar::-webkit-scrollbar {
   height: 6px;
 }
 .subicon-bar::-webkit-scrollbar-thumb {
   background: #151515;
   border-radius: 3px;
 }
 .subicon {
   width: var(--icon-size, 42px);
   height: var(--icon-size, 42px);
   border-radius: var(--icon-radius, 10px);
   overflow: hidden;
   position: relative;
   flex: 0 0 auto;
   cursor: pointer;
   isolation: isolate;
   filter: brightness(0.92);
 }
 .subicon img {
   width: 100%;
   height: 100%;
   aspect-ratio: 1 / 1;
   object-fit: cover;
   display: block;
   border-radius: inherit;
 }
 .subicon::after {
   content: "";
   position: absolute;
   inset: 0;
   border-radius: inherit;
   box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-idle, #cfcfcf);
   pointer-events: none;
   z-index: 2;
   transition: box-shadow 0.12s ease;
 }
 .subicon:hover::after {
   box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
 }
 .subicon:hover {
   filter: brightness(1);
 }
 .subicon.active::after {
   box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #ffd95a);
 }
 .subicon.active {
   transform: scale(1.1);
   z-index: 5;
   filter: brightness(1);
 }
 .subicon.active::before {
   content: "";
   position: absolute;
   inset: -4px;
   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 {
   transform: none !important;
 }
 .top-rail.skills {
   position: relative;
   display: flex;
   flex-direction: column;
   align-items: center;
   overflow: visible;
 }
 .top-rail.skills .icon-bar {
   margin-bottom: 0;
   position: relative;
   z-index: 2;
 }
 .subskills-rail {
   position: absolute;
   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 {
   content: "";
   position: absolute;
   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 {
   opacity: 0;
   pointer-events: none;
   transform: translate(-50%, -6px);
 }
 .subskills-rail.hidden {
   visibility: hidden;
 }
 .subskills-spacer {
   height: 0;
   transition: height 0.2s ease;
 }
 .subskills-rail .subicon-bar {
   display: inline-flex;
   align-items: center;
   gap: 0;
   overflow-x: auto;
   /* Firefox */
   scrollbar-width: thin;
   scrollbar-color: #ababab transparent;
 }
 .subskills-rail .subicon-bar::-webkit-scrollbar {
   height: 6px;
 }
 .subskills-rail .subicon-bar::-webkit-scrollbar-thumb {
   background: #151515;
   border-radius: 3px;
 }
 .subskills-rail .subicon {
   width: 42px;
   height: 42px;
   border-radius: 6px;
   position: relative;
   overflow: hidden;
   flex: 0 0 auto;
   cursor: pointer;
   isolation: isolate;
   -webkit-backface-visibility: hidden;
   backface-visibility: hidden;
   transform: translateZ(0);
 }
 .subskills-rail .subicon+.subicon {
   margin-left: 4px;
 }
 .subskills-rail .subicon img {
   width: 100%;
   height: 100%;
   aspect-ratio: 1 / 1;
   object-fit: cover;
   display: block;
   border-radius: inherit;
 }
 .subskills-rail .subicon::after {
   content: "";
   position: absolute;
   inset: 0;
   border-radius: inherit;
   box-shadow: inset 0 0 0 2px var(--icon-idle, #cfcfcf);
   pointer-events: none;
   z-index: 2;
   transition: box-shadow 0.12s ease;
 }
 .subskills-rail .subicon:hover::after {
   box-shadow: inset 0 0 0 2px #e6e6e6;
 }
 .subskills-rail .subicon.active::after {
   box-shadow: inset 0 0 0 2px var(--icon-active, #ffd95a);
 }
 .video-container .skill-video {
   width: 100%;
   height: auto;
   aspect-ratio: 16 / 9;
   object-fit: cover;
   background: #000;
   border-radius: 10px;
 }
 @media (max-width: 900px) {
   .subskills-rail {
     position: static;
     transform: none;
     margin-top: -2px;
     border-top: 0;
     border-radius: 0 0 10px 10px;
   }
   .subskills-spacer {
     height: 0 !important;
   }
 }
 .skills-rail-wrap {
   position: relative;
   display: block;
   width: max-content;
   margin: 0 auto;
 }
 /* Subskills com arma disponível - borda vermelha quando inativa */
 .character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after {
   box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important;
 }
 /* Subskill com arma ATIVA - laranja/coral vibrante + brilho forte */
 .character-box .top-rail.skills .subicon.has-weapon-available.active {
   position: relative;
 }
 .character-box .top-rail.skills .subicon.has-weapon-available.active::after {
   box-shadow: none !important;
   background: linear-gradient(135deg,
       #ff5722 0%,
       #ff7043 20%,
       #ff8a65 40%,
       #ff7043 60%,
       #ff5722 80%,
       #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 {
   box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.95),
     0 0 40px 16px rgba(255, 87, 34, 0.75),
     0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6) !important;
   opacity: 1 !important;
 }
 /* Modo arma ON - efeito animado nos subicons */
 .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available {
   position: relative;
 }
 .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::after {
   box-shadow: none !important;
   background: linear-gradient(90deg,
       rgba(255, 80, 80, 0.9) 0%,
       rgba(255, 120, 60, 1) 25%,
       rgba(255, 80, 80, 0.9) 50%,
       rgba(255, 120, 60, 1) 75%,
       rgba(255, 80, 80, 0.9) 100%) !important;
   background-size: 400% 100% !important;
   animation: weapon-subicon-border-scan 4s 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;
 }
 .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::before {
   content: "";
   position: absolute;
   inset: -4px;
   border-radius: calc(6px + 4px);
   pointer-events: none;
   z-index: 1;
   box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
     0 0 32px 12px rgba(255, 80, 80, 0.6),
     0 0 48px 18px rgba(255, 100, 60, 0.4) !important;
   opacity: 1 !important;
   animation: weapon-subicon-pulse 3s ease-in-out infinite !important;
 }
 /* Subskill ativa com arma - mais intenso */
 .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::after {
   background: linear-gradient(135deg,
       #ff3d00 0%,
       #ff5722 15%,
       #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 {
   box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
     0 0 56px 24px rgba(255, 87, 34, 0.85),
     0 0 80px 32px rgba(255, 140, 60, 0.6), 0 0 0 5px rgba(255, 87, 34, 0.75) !important;
   animation: weapon-subicon-pulse-active 2.5s ease-in-out infinite !important;
 }
 @keyframes weapon-icon-border-scan {
   0% {
     background-position: 0% 0%;
   }
   100% {
     background-position: 400% 0%;
   }
 }
 @keyframes weapon-subicon-border-scan {
   0% {
     background-position: 0% 0%;
   }
   100% {
     background-position: 400% 0%;
   }
 }
 @keyframes weapon-subicon-pulse {
   0%,
   100% {
     opacity: 0.95;
     box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
       0 0 32px 12px rgba(255, 80, 80, 0.6),
       0 0 48px 18px rgba(255, 100, 60, 0.4);
   }
   50% {
     opacity: 1;
     box-shadow: 0 0 24px 8px rgba(255, 80, 80, 1),
       0 0 48px 16px rgba(255, 80, 80, 0.75),
       0 0 64px 24px rgba(255, 120, 60, 0.5);
   }
 }
 @keyframes weapon-subicon-pulse-active {
   0%,
   100% {
     opacity: 0.95;
     box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.9),
       0 0 40px 16px rgba(255, 87, 34, 0.7),
       0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6);
   }
   50% {
     opacity: 1;
     box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
       0 0 56px 24px rgba(255, 87, 34, 0.85),
       0 0 80px 32px rgba(255, 140, 60, 0.6), 0 0 0 5px rgba(255, 87, 34, 0.75);
   }
 }

</style>