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

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
Linha 1: Linha 1:
<!-- SUBSKILLS SYSTEM -->
<!-- SUBSKILLS SYSTEM -->
<script>
<script>
    (function () {
  (function () {
        const SUBVIDEO_DEBUG = false; // Desabilitado para melhor performance
    const SUBVIDEO_DEBUG = false; // Desabilitado para melhor performance
        function logSubVideo(...args) {
    function logSubVideo(...args) {
            if (!SUBVIDEO_DEBUG) return;
      if (!SUBVIDEO_DEBUG) return;
            // console.log('[SubVideo]', ...args);
      // 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 api = (window.__subskills ||= {});
      const maps = {
         // Cache global de elementos de vídeo para subskills - baseado em URL
         byId: new Map(), // FASE 5: Fonte primária por ID
         const subskillVideoElementCache = new Map(); // key: videoURL (string), value: HTMLVideoElement
         byName: new Map(), // FASE 5: Mantido para compatibilidade (legado)
         const imagePreloadCache = new Map();
         byIndex: new Map(), // Mantido para compatibilidade
        let subRail, subBar, spacer;
      };


        // Cache das skills principais (capturado na carga da página)
      // Busca skills com data-index (skills principais, não subskills)
         let cachedMainSkills = null;
      const icons = document.querySelectorAll(
         ".icon-bar .skill-icon[data-index][data-nome]"
      );


        // ===== HERANÇA DE ATRIBUTOS: busca dados das skills principais =====
      icons.forEach((icon) => {
         // FASE 5: Refatorado para usar byId como fonte primária
         const name = (icon.dataset.nome || "").trim();
        function getMainSkillsMap() {
        if (!name) return;
            // Retorna cache se já foi construído E tem dados
            if (cachedMainSkills && cachedMainSkills.byId.size > 0) {
                return cachedMainSkills;
            }


            const maps = {
        // Extrai atributos do data-atr (formato: "pve, pvp, energy, cd")
                byId: new Map(),      // FASE 5: Fonte primária por ID
        const atrRaw = icon.dataset.atr || "";
                byName: new Map(),    // FASE 5: Mantido para compatibilidade (legado)
        const parts = atrRaw.split(",").map((x) => (x || "").trim());
                byIndex: new Map()    // Mantido para compatibilidade
        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] : "";


            // Busca skills com data-index (skills principais, não subskills)
        // Nome original do arquivo de ícone (armazenado no dataset pelo widget de skills)
            const icons = document.querySelectorAll('.icon-bar .skill-icon[data-index][data-nome]');
        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]) : "";
        }


            icons.forEach(icon => {
        // Nome original do arquivo de vídeo (caso exista)
                const name = (icon.dataset.nome || '').trim();
        let videoFile = (icon.dataset.videoFile || "").trim();
                if (!name) return;
        if (!videoFile) {
          const videoUrl = icon.dataset.video || "";
          const videoMatch = videoUrl.match(/FilePath\/([^&?]+)/);
          videoFile = videoMatch ? decodeURIComponent(videoMatch[1]) : "";
        }


                // Extrai atributos do data-atr (formato: "pve, pvp, energy, cd")
        const index = (icon.dataset.index || "").trim();
                const atrRaw = icon.dataset.atr || '';
        // FASE 5: Extrai ID do data-skill-id ou gera a partir do nome
                const parts = atrRaw.split(',').map(x => (x || '').trim());
        const skillId = icon.dataset.skillId || icon.dataset.id || name;
                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)
        const data = {
                let iconFile = (icon.dataset.iconFile || '').trim();
          id: skillId, // FASE 5: ID único
                if (!iconFile) {
          name: name, // Mantido para compatibilidade
                    const imgSrc = icon.querySelector('img')?.src || '';
          icon: iconFile,
                    const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
          level: icon.dataset.level || "",
                    iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : '';
          video: videoFile,
                }
          powerpve: powerpve,
          powerpvp: powerpvp,
          cooldown: cooldown,
          energy: energy,
        };


                // Nome original do arquivo de vídeo (caso exista)
        // Mantém descrições caso precise como fallback extra
                let videoFile = (icon.dataset.videoFile || '').trim();
        if (icon.dataset.descPt) data.descPt = icon.dataset.descPt;
                if (!videoFile) {
        if (icon.dataset.descEn) data.descEn = icon.dataset.descEn;
                    const videoUrl = icon.dataset.video || '';
        if (icon.dataset.descEs) data.descEs = icon.dataset.descEs;
                    const videoMatch = videoUrl.match(/FilePath\/([^&?]+)/);
        if (icon.dataset.descPl) data.descPl = icon.dataset.descPl;
                    videoFile = videoMatch ? decodeURIComponent(videoMatch[1]) : '';
                }


                const index = (icon.dataset.index || '').trim();
        // FASE 5: Processa desc_i18n se existir
                // FASE 5: Extrai ID do data-skill-id ou gera a partir do nome
        if (icon.dataset.descI18n) {
                const skillId = icon.dataset.skillId || icon.dataset.id || name;
          try {
            data.desc_i18n = JSON.parse(icon.dataset.descI18n);
          } catch (e) {
            // Ignora erro de parse
          }
        }


                const data = {
        // FASE 5: byId é a fonte primária
                    id: skillId,      // FASE 5: ID único
        maps.byId.set(skillId, data);
                    name: name,       // Mantido para compatibilidade
        maps.byName.set(name, data); // FASE 5: Mantido para compatibilidade (legado)
                    icon: iconFile,
        if (index) {
                    level: icon.dataset.level || '',
          // Guarda tanto como string quanto como número para compatibilidade
                    video: videoFile,
          maps.byIndex.set(index, data);
                    powerpve: powerpve,
          maps.byIndex.set(parseInt(index, 10), data);
                    powerpvp: powerpvp,
        }
                    cooldown: cooldown,
      });
                    energy: energy
                };


                // Mantém descrições caso precise como fallback extra
      // Cacheia para uso futuro (importante: as skills principais não mudam)
                if (icon.dataset.descPt) data.descPt = icon.dataset.descPt;
      cachedMainSkills = maps;
                if (icon.dataset.descEn) data.descEn = icon.dataset.descEn;
      return maps;
                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
    // FASE 4: Resolver único para toda resolução de skill/subskill
                if (icon.dataset.descI18n) {
    // Retorna tudo que a UI precisa renderizar de forma determinística
                    try {
    function resolveSkillView(skill, context) {
                        data.desc_i18n = JSON.parse(icon.dataset.descI18n);
      // context: { lang, weaponMode, mainSkills }
                    } catch (e) {
      // Retorna: { title, desc, video, attrs, flags, weapon }
                        // Ignora erro de parse
                    }
                }


                // FASE 5: byId é a fonte primária
      const lang = context.lang || "pt";
                maps.byId.set(skillId, data);
      const weaponMode = context.weaponMode || false;
                maps.byName.set(name, data); // FASE 5: Mantido para compatibilidade (legado)
      const mainSkills = context.mainSkills || null;
                if (index) {
                    // Guarda tanto como string quanto como número para compatibilidade
                    maps.byIndex.set(index, data);
                    maps.byIndex.set(parseInt(index, 10), data);
                }


            });
      // 1. Título: sempre display_name
      const title = skill.display_name || skill.name || skill.n || "";


            // Cacheia para uso futuro (importante: as skills principais não mudam)
      // 2. Descrição: sempre vem da skill/subskill, nunca herda
            cachedMainSkills = maps;
      let desc = "";
             return maps;
      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 || "";
         }
         }
      }


        // FASE 4: Resolver único para toda resolução de skill/subskill
      // 3. Vídeo: sempre da skill/subskill, nunca herdado
        // Retorna tudo que a UI precisa renderizar de forma determinística
      const video = skill.video || "";
        function resolveSkillView(skill, context) {
            // context: { lang, weaponMode, mainSkills }
            // Retorna: { title, desc, video, attrs, flags, weapon }


            const lang = context.lang || 'pt';
      // 4. Atributos: weapon mode ou normal
            const weaponMode = context.weaponMode || false;
      let attrs = {
            const mainSkills = context.mainSkills || null;
        powerpve: skill.powerpve,
        powerpvp: skill.powerpvp,
        energy: skill.energy,
        cooldown: skill.cooldown,
      };


            // 1. Título: sempre display_name
      if (weaponMode && skill.weapon) {
            const title = skill.display_name || skill.name || skill.n || '';
        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,
        };
      }


            // 2. Descrição: sempre vem da skill/subskill, nunca herda
      // 5. Level: weapon ou normal
            let desc = '';
      const level =
            if (weaponMode && skill.weapon?.desc_i18n) {
        weaponMode && skill.weapon?.level
                desc = skill.weapon.desc_i18n[lang] ||
          ? skill.weapon.level.toString().trim()
                    skill.weapon.desc_i18n.pt ||
          : (skill.level || "").toString().trim();
                    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
      return {
            const video = skill.video || '';
        title,
        desc,
        video,
        attrs,
        flags: skill.flags,
        weapon: skill.weapon,
        level,
      };
    }


            // 4. Atributos: weapon mode ou normal
    // applyInheritance: aplica herança de atributos (nunca herda descrição ou vídeo)
            let attrs = {
    function applyInheritance(sub, mainSkills) {
                powerpve: skill.powerpve,
      // NOVO SISTEMA: herança explícita
                powerpvp: skill.powerpvp,
      // inherit_from: DE qual skill herdar (obrigatório para herdar)
                energy: skill.energy,
      // inherit_fields: O QUE herdar (array de campos, obrigatório para herdar)
                cooldown: skill.cooldown
      // Se não especificar ambos, não herda nada (mais seguro)
            };


            if (weaponMode && skill.weapon) {
      const inheritFrom = sub.inherit_from_id || sub.inherit_from;
                attrs = {
      const inheritFields = sub.inherit_fields || [];
                    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
      // Converte para set para busca rápida
            const level = (weaponMode && skill.weapon?.level)
      const inheritFieldsSet = new Set(inheritFields);
                ? skill.weapon.level.toString().trim()
                : (skill.level || '').toString().trim();


            return {
      // Busca skill principal
                title,
      let main = null;
                desc,
      if (inheritFrom && mainSkills) {
                video,
        // FASE 5: Tenta por ID primeiro (fonte primária)
                attrs,
        if (mainSkills.byId && mainSkills.byId.has(inheritFrom)) {
                flags: skill.flags,
          main = mainSkills.byId.get(inheritFrom);
                weapon: skill.weapon,
        }
                level
        // 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);
             }
          }
         }
         }
      }


        // applyInheritance: aplica herança de atributos (nunca herda descrição ou vídeo)
      // Se não tem inherit_from ou main, não herda nada
        function applyInheritance(sub, mainSkills) {
      if (!inheritFrom || !main) {
            // NOVO SISTEMA: herança explícita
        return sub;
            // 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;
      // Função auxiliar: verifica se campo DEVE ser herdado
            const inheritFields = sub.inherit_fields || [];
      const shouldInheritField = (fieldName) => {
        return inheritFieldsSet.has(fieldName);
      };


            // Converte para set para busca rápida
      // Helper para verificar se um valor existe e não é string vazia
            const inheritFieldsSet = new Set(inheritFields);
      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";
      };


            // Busca skill principal
      // Vídeo NUNCA é herdado da skill principal
            let main = null;
      const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
            if (inheritFrom && mainSkills) {
      const finalVideo = hasOwnVideo ? sub.video || "" : "";
                // 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
      return {
             if (!inheritFrom || !main) {
        ...sub,
                return sub;
        name: sub.name || main.name,
            }
        // Herda apenas se campo estiver em inherit_fields
        icon:
          sub.icon && sub.icon !== "Nada.png" && sub.icon !== ""
            ? sub.icon
             : shouldInheritField("icon")
              ? main.icon || "Nada.png"
              : "",
        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,
      };
    }


            // Função auxiliar: verifica se campo DEVE ser herdado
    function filePathURL(fileName) {
            const shouldInheritField = (fieldName) => {
      // Evita requisições para Nada.png que não existe
                return inheritFieldsSet.has(fieldName);
      if (
            };
        !fileName ||
        fileName.trim() === "" ||
        fileName === "Nada.png" ||
        fileName.toLowerCase() === "nada.png"
      ) {
        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;
    }


            // Helper para verificar se um valor existe e não é string vazia
    function normalizeFileURL(raw, fallback = "") {
            const hasValue = (val) => {
      if (!raw) return fallback;
                if (val === undefined || val === null) return false;
      const val = String(raw).trim();
                if (typeof val === 'number') return !isNaN(val);
      if (!val) return fallback;
                const str = String(val).trim();
      if (
                return str !== '' && str !== 'NaN';
        /^(https?:)?\/\//i.test(val) ||
            };
        val.startsWith("data:") ||
        val.includes("Especial:FilePath/")
      ) {
        return val;
      }
      return filePathURL(val);
    }


            // Vídeo NUNCA é herdado da skill principal
    function preloadImage(iconFile) {
            const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, 'video');
      const url = filePathURL(iconFile || "Nada.png");
            const finalVideo = hasOwnVideo ? (sub.video || '') : '';
      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;
    }


            return {
    function getLabels() {
                ...sub,
      const skillsRoot = document.getElementById("skills");
                name: sub.name || main.name,
      const i18nMap = skillsRoot
                // Herda apenas se campo estiver em inherit_fields
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
                icon: (sub.icon && sub.icon !== 'Nada.png' && sub.icon !== '') ? sub.icon :
        : {};
                    (shouldInheritField("icon") ? (main.icon || 'Nada.png') : ''),
      const raw = (
                level: hasValue(sub.level) ? sub.level :
        document.documentElement.lang ||
                    (shouldInheritField("level") ? main.level : ''),
        skillsRoot?.dataset.i18nDefault ||
                video: finalVideo,  // Nunca herda
        "pt"
                powerpve: (sub.powerpve !== undefined && sub.powerpve !== null) ? sub.powerpve :
      ).toLowerCase();
                    (shouldInheritField("powerpve") ? main.powerpve : undefined),
      const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
                powerpvp: (sub.powerpvp !== undefined && sub.powerpvp !== null) ? sub.powerpvp :
      return (
                    (shouldInheritField("powerpvp") ? main.powerpvp : undefined),
        i18nMap[lang] ||
                cooldown: (sub.cooldown !== undefined && sub.cooldown !== null) ? sub.cooldown :
        i18nMap.pt || {
                    (shouldInheritField("cooldown") ? main.cooldown : undefined),
          cooldown: "Recarga",
                energy: (sub.energy !== undefined && sub.energy !== null) ? sub.energy :
          energy_gain: "Ganho de energia",
                    (shouldInheritField("energy") ? main.energy : undefined),
          energy_cost: "Custo de energia",
                // Descrição: sempre vem da subskill, nunca herda
          power: "Poder",
                // PROTEÇÃO TOTAL: NUNCA copia descrição do main, mesmo que subskill não tenha
          power_pvp: "Poder PvP",
                descPt: sub.desc_i18n?.pt || sub.descPt || undefined,
          level: "Nível",
                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) {
    // Verifica se o modo weapon está ativo
            // Evita requisições para Nada.png que não existe
    function isWeaponModeOn() {
            if (!fileName || fileName.trim() === '' || fileName === 'Nada.png' || fileName.toLowerCase() === 'nada.png') {
      try {
                return '';
        return localStorage.getItem("glaWeaponEnabled") === "1";
            }
      } catch (e) {
            const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ''));
        return false;
            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 = '') {
    // Retorna os atributos corretos (weapon ou normal)
            if (!raw) return fallback;
    function getEffectiveAttrs(s) {
            const val = String(raw).trim();
      const weaponOn = isWeaponModeOn();
            if (!val) return fallback;
      if (weaponOn && s.weapon) {
            if (/^(https?:)?\/\//i.test(val) || val.startsWith('data:') || val.includes('Especial:FilePath/')) {
        return {
                return val;
          powerpve: s.weapon.powerpve || s.powerpve,
            }
          powerpvp: s.weapon.powerpvp || s.powerpvp,
            return filePathURL(val);
          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,
      };
    }


        function preloadImage(iconFile) {
    // Retorna a descrição correta (weapon ou normal)
            const url = filePathURL(iconFile || 'Nada.png');
    // Aceita tanto desc_i18n quanto desc para compatibilidade
            if (imagePreloadCache.has(url)) {
    function getEffectiveDesc(s) {
                return imagePreloadCache.get(url);
      const weaponOn = isWeaponModeOn();
            }
      const raw = (document.documentElement.lang || "pt").toLowerCase();
            const promise = new Promise((resolve, reject) => {
      const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
                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() {
      // Para weapon: aceita tanto desc_i18n quanto desc
            const skillsRoot = document.getElementById('skills');
      if (weaponOn && s.weapon) {
            const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {};
        const wDesc = s.weapon.desc_i18n || s.weapon.desc;
            const raw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase();
        if (wDesc) {
            const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
          return wDesc[lang] || wDesc.pt || wDesc.en || "";
            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
      // Descrição: sempre vem da skill/subskill, nunca herda
        function isWeaponModeOn() {
      const base = s.desc_i18n || {};
            try {
      if (base && Object.keys(base).length > 0) {
                return localStorage.getItem('glaWeaponEnabled') === '1';
        return base[lang] || base.pt || base.en || "";
            } catch (e) {
      }
                return false;
            }
        }


        // Retorna os atributos corretos (weapon ou normal)
      // Fallback para campos individuais
        function getEffectiveAttrs(s) {
      const descI18n = {
            const weaponOn = isWeaponModeOn();
        pt: s.descPt || "",
            if (weaponOn && s.weapon) {
        en: s.descEn || "",
                return {
        es: s.descEs || "",
                    powerpve: s.weapon.powerpve || s.powerpve,
        pl: s.descPl || "",
                    powerpvp: s.weapon.powerpvp || s.powerpvp,
      };
                    energy: s.weapon.energy || s.energy,
      return descI18n[lang] || descI18n.pt || descI18n.en || "";
                    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)
    // Retorna o vídeo correto (weapon ou normal)
        // Aceita tanto desc_i18n quanto desc para compatibilidade
    function getEffectiveVideo(s) {
        function getEffectiveDesc(s) {
      const weaponOn = isWeaponModeOn();
            const weaponOn = isWeaponModeOn();
      if (
            const raw = (document.documentElement.lang || 'pt').toLowerCase();
        weaponOn &&
            const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
        s.weapon &&
        s.weapon.video &&
        s.weapon.video.trim() !== ""
      ) {
        return s.weapon.video;
      }
      return s.video || "";
    }


            // Para weapon: aceita tanto desc_i18n quanto desc
    // Função única para obtenção de vídeo de subskill - baseada em URL
            if (weaponOn && s.weapon) {
    function getOrCreateSubskillVideo(videoURL) {
                const wDesc = s.weapon.desc_i18n || s.weapon.desc;
      if (!videoURL || videoURL.trim() === "") return null;
                if (wDesc) {
                    return wDesc[lang] || wDesc.pt || wDesc.en || '';
                }
            }


            // Descrição: sempre vem da skill/subskill, nunca herda
      const normalizedURL = normalizeFileURL(videoURL);
            const base = s.desc_i18n || {};
      if (!normalizedURL || normalizedURL.trim() === "") return null;
            if (base && Object.keys(base).length > 0) {
                return base[lang] || base.pt || base.en || '';
            }


            // Fallback para campos individuais
      // 1. Tenta cache local primeiro (compatibilidade)
            const descI18n = {
      if (subskillVideoElementCache.has(normalizedURL)) {
                pt: s.descPt || '',
        logSubVideo("getOrCreateSubskillVideo: local cache hit", {
                en: s.descEn || '',
          videoURL: normalizedURL,
                es: s.descEs || '',
        });
                pl: s.descPl || ''
        return subskillVideoElementCache.get(normalizedURL);
            };
      }
            return descI18n[lang] || descI18n.pt || descI18n.en || '';
        }


         // Retorna o vídeo correto (weapon ou normal)
      // 2. Tenta cache global de subskills (window.__subskillVideosCache)
         function getEffectiveVideo(s) {
      const globalCache = window.__subskillVideosCache;
            const weaponOn = isWeaponModeOn();
      if (globalCache && globalCache instanceof Map) {
            if (weaponOn && s.weapon && s.weapon.video && s.weapon.video.trim() !== '') {
         // O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
                return s.weapon.video;
        // Precisamos buscar por URL normalizada
            }
         for (const [key, video] of globalCache.entries()) {
             return s.video || '';
          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;
          }
         }
         }
      }


        // Função única para obtenção de vídeo de subskill - baseada em URL
      // 3. REMOVIDO: criação lazy - todos os vídeos devem estar pré-carregados
        function getOrCreateSubskillVideo(videoURL) {
      // Se vídeo não foi encontrado, é um erro (não deve criar novo)
            if (!videoURL || videoURL.trim() === '') return null;
      console.warn("[Subskills] Vídeo não encontrado no cache:", {
        videoURL: normalizedURL,
        originalURL: videoURL,
      });
      return null;
    }


            const normalizedURL = normalizeFileURL(videoURL);
    function renderSubAttrs(s, L) {
            if (!normalizedURL || normalizedURL.trim() === '') return null;
      const chip = (label, val) =>
        val
          ? `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${val}</span></div>`
          : "";
      const pve = (s.powerpve || "").toString().trim();
      const pvp = (s.powerpvp || "").toString().trim();
      const en = (s.energy || "").toString().trim();
      const cd = (s.cooldown || "").toString().trim();
      const rows = [
        cd ? chip(L.cooldown, cd) : "",
        en
          ? chip(
            en.startsWith("-") ? L.energy_cost : L.energy_gain,
            en.startsWith("-") ? en.replace(/^-/, "") : en.replace(/^\+?/, "")
          )
          : "",
        pve ? chip(L.power, pve) : "",
        pvp ? chip(L.power_pvp, pvp) : "",
      ].filter(Boolean);
      return rows.length ? `<div class="attr-list">${rows.join("")}</div>` : "";
    }


            // 1. Tenta cache local primeiro (compatibilidade)
    function renderFlagsRow(flags) {
            if (subskillVideoElementCache.has(normalizedURL)) {
      const map = {
                logSubVideo('getOrCreateSubskillVideo: local cache hit', { videoURL: normalizedURL });
        aggro: "Enemyaggro-icon.png",
                return subskillVideoElementCache.get(normalizedURL);
        bridge: "Bridgemaker-icon.png",
            }
        wall: "Destroywall-icon.png",
        quickcast: "Quickcast-icon.png",
        wallpass: "Passthroughwall-icon.png",
      };
      const arr = (flags || []).filter(Boolean);
      if (!arr.length) return "";
      const items = arr
        .map(
          (k) =>
            `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(
              map[k]
            )}">`
        )
        .join("");
      return `<div class="skill-flags" role="group" aria-label="Características">${items}</div>`;
    }


            // 2. Tenta cache global de subskills (window.__subskillVideosCache)
    function applyFlagTooltips(container) {
            const globalCache = window.__subskillVideosCache;
      const skillsRoot = document.getElementById("skills");
            if (globalCache && globalCache instanceof Map) {
      if (!skillsRoot) return;
                // O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
      let pack = {};
                // Precisamos buscar por URL normalizada
      try {
                for (const [key, video] of globalCache.entries()) {
        pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
                    const src = video.querySelector('source');
      } catch (e) { }
                    if (src && src.src === normalizedURL) {
      const raw = (document.documentElement.lang || "pt").toLowerCase();
                        logSubVideo('getOrCreateSubskillVideo: global cache hit', { videoURL: normalizedURL, key });
      const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
                        // Adiciona ao cache local também para acesso rápido
      const dict = pack[lang] || pack.pt || {};
                        subskillVideoElementCache.set(normalizedURL, video);
      const flags = container.querySelectorAll(
                        return video;
        ".skill-flags .skill-flag[data-flag]"
                    }
      );
                }
      const tooltip = window.__globalSkillTooltip;
            }
      if (!tooltip) return;


            // 3. REMOVIDO: criação lazy - todos os vídeos devem estar pré-carregados
      flags.forEach((el) => {
            // Se vídeo não foi encontrado, é um erro (não deve criar novo)
        const key = el.getAttribute("data-flag");
            console.warn('[Subskills] Vídeo não encontrado no cache:', {
        const tip = (dict && dict[key]) || "";
                videoURL: normalizedURL,
        if (!tip) return;
                originalURL: videoURL
        if (el.dataset.flagTipWired) return;
            });
        el.dataset.flagTipWired = "1";
            return null;
        el.setAttribute("aria-label", tip);
         }
         if (el.hasAttribute("title")) el.removeAttribute("title");


         function renderSubAttrs(s, L) {
         el.addEventListener("mouseenter", () => {
            const chip = (label, val) => (val ? `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${val}</span></div>` : '');
          const tipEl = document.querySelector(".skill-tooltip");
            const pve = (s.powerpve || '').toString().trim();
          if (tipEl) tipEl.classList.add("flag-tooltip");
            const pvp = (s.powerpvp || '').toString().trim();
          tooltip.show(el, tip);
            const en = (s.energy || '').toString().trim();
        });
             const cd = (s.cooldown || '').toString().trim();
        el.addEventListener("mousemove", () => {
            const rows = [
          if (performance.now() >= tooltip.lockUntil.value) {
                cd ? chip(L.cooldown, cd) : '',
             tooltip.measureAndPos(el);
                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) : '',
        el.addEventListener("click", () => {
            ].filter(Boolean);
          tooltip.lockUntil.value = performance.now() + 240;
            return rows.length ? `<div class="attr-list">${rows.join('')}</div>` : '';
          tooltip.measureAndPos(el);
        }
        });
        el.addEventListener("mouseleave", () => {
          const tipEl = document.querySelector(".skill-tooltip");
          if (tipEl) tipEl.classList.remove("flag-tooltip");
          tooltip.hide();
        });
      });
    }


        function renderFlagsRow(flags) {
    function ensureRail(iconsBar) {
            const map = {
      const rail = iconsBar.closest(".top-rail");
                aggro: 'Enemyaggro-icon.png',
      if (!rail) {
                bridge: 'Bridgemaker-icon.png',
        return null;
                wall: 'Destroywall-icon.png',
      }
                quickcast: 'Quickcast-icon.png',
                wallpass: 'Passthroughwall-icon.png'
            };
            const arr = (flags || []).filter(Boolean);
            if (!arr.length) return '';
            const items = arr.map(k => `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(map[k])}">`).join('');
            return `<div class="skill-flags" role="group" aria-label="Características">${items}</div>`;
        }


        function applyFlagTooltips(container) {
      if (!subRail) {
            const skillsRoot = document.getElementById('skills');
        subRail = document.createElement("div");
            if (!skillsRoot) return;
        subRail.className = "subskills-rail collapsed hidden";
            let pack = {};
        rail.appendChild(subRail);
            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 => {
      if (!subBar) {
                const key = el.getAttribute('data-flag');
        subBar = document.createElement("div");
                const tip = (dict && dict[key]) || '';
        subBar.className = "subicon-bar";
                if (!tip) return;
        subRail.appendChild(subBar);
                if (el.dataset.flagTipWired) return;
      }
                el.dataset.flagTipWired = '1';
                el.setAttribute('aria-label', tip);
                if (el.hasAttribute('title')) el.removeAttribute('title');


                el.addEventListener('mouseenter', () => {
      if (!spacer) {
                    const tipEl = document.querySelector('.skill-tooltip');
        spacer = document.createElement("div");
                    if (tipEl) tipEl.classList.add('flag-tooltip');
        spacer.className = "subskills-spacer";
                    tooltip.show(el, tip);
        rail.parentNode.insertBefore(spacer, rail.nextSibling);
                });
      }
                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) {
      return rail;
            const rail = iconsBar.closest('.top-rail');
    }
            if (!rail) {
                return null;
            }


            if (!subRail) {
    // Função para mostrar vídeo de subskill usando cache baseado em URL
                subRail = document.createElement('div');
    function showSubVideo(videoURL, videoBox) {
                subRail.className = 'subskills-rail collapsed hidden';
      logSubVideo("showSubVideo called", {
                rail.appendChild(subRail);
        videoURL,
            }
        videoBoxExists: !!videoBox,
      });


            if (!subBar) {
      if (!videoBox) return;
                subBar = document.createElement('div');
                subBar.className = 'subicon-bar';
                subRail.appendChild(subBar);
            }


            if (!spacer) {
      // Ocultar todos os vídeos existentes no container
                spacer = document.createElement('div');
      videoBox
                spacer.className = 'subskills-spacer';
        .querySelectorAll('video.skill-video[data-sub="1"]')
                rail.parentNode.insertBefore(spacer, rail.nextSibling);
        .forEach((v) => {
            }
          v.style.display = "none";
        });


            return rail;
      // Obter vídeo via getOrCreateSubskillVideo (única forma de obter vídeos)
        }
      const video = getOrCreateSubskillVideo(videoURL);


        // Função para mostrar vídeo de subskill usando cache baseado em URL
      if (!video) {
        function showSubVideo(videoURL, videoBox) {
        logSubVideo("no video found for URL, hiding box", { videoURL });
            logSubVideo('showSubVideo called', { videoURL, videoBoxExists: !!videoBox });
        videoBox.style.display = "none";
        return;
      }


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


            // Ocultar todos os vídeos existentes no container
      logSubVideo("showing video", {
            videoBox.querySelectorAll('video.skill-video[data-sub="1"]').forEach(v => {
        videoURL,
                v.style.display = 'none';
        readyState: video.readyState,
            });
        paused: video.paused,
        currentTime: video.currentTime,
      });


            // Obter vídeo via getOrCreateSubskillVideo (única forma de obter vídeos)
      // Exibir o vídeo
            const video = getOrCreateSubskillVideo(videoURL);
      videoBox.style.display = "block";
      video.style.display = "block";


            if (!video) {
      // Reinicia o vídeo para feedback visual imediato
                logSubVideo('no video found for URL, hiding box', { videoURL });
      try {
                videoBox.style.display = 'none';
        video.currentTime = 0;
                return;
      } catch (e) { }
            }


            // Se o vídeo não estiver conectado ao DOM, anexá-lo ao container
      // Tenta dar play se o vídeo já está pronto
            if (!video.isConnected || video.parentNode !== videoBox) {
      if (video.readyState >= 2) {
                logSubVideo('video not in DOM, appending', {
        video
                    videoURL,
          .play()
                    isConnected: video.isConnected,
          .then(() => {
                    parentNode: video.parentNode
            logSubVideo("play() resolved", {
                });
              videoURL,
                if (video.parentNode) {
              currentTime: video.currentTime,
                    video.parentNode.removeChild(video);
              readyState: video.readyState,
                }
            });
                videoBox.appendChild(video);
          })
            }
          .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 });
      }
    }


            logSubVideo('showing video', {
    // Funções antigas removidas - usar getOrCreateSubskillVideo() em vez disso
                videoURL,
    // REMOVIDO: ensureSubVideoCached
                readyState: video.readyState,
    // REMOVIDO: ensureSubVideoInCache
                paused: video.paused,
                currentTime: video.currentTime
            });


            // Exibir o vídeo
    api.refreshCurrentSubSafe = function () {
            videoBox.style.display = 'block';
      const btn = document.querySelector(".subskills-rail .subicon.active");
            video.style.display = 'block';
      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;
    };


            // Reinicia o vídeo para feedback visual imediato
    // Função auxiliar para aplicar classes de weapon nas subskills renderizadas
            try {
    const applyWeaponClassesToSubskills = () => {
                video.currentTime = 0;
      const weaponOn = isWeaponModeOn();
            } catch (e) { }
      // Busca TODAS as subskills com weapon
      let weaponSubs = document.querySelectorAll(".subicon[data-weapon]");


            // Tenta dar play se o vídeo já está pronto
      weaponSubs.forEach((el) => {
            if (video.readyState >= 2) {
        const weaponData = el.getAttribute("data-weapon");
                video.play().then(() => {
        // Verifica se o weapon não está vazio (não é '{}' ou vazio)
                    logSubVideo('play() resolved', { videoURL, currentTime: video.currentTime, readyState: video.readyState });
        let hasValidWeapon = false;
                }).catch(err => {
        if (weaponData && weaponData.trim() !== "" && weaponData !== "{}") {
                    logSubVideo('play() rejected', { videoURL, error: err });
          try {
                });
            const weaponObj = JSON.parse(weaponData);
            } else {
            if (
                // Se não está pronto ainda, espera o evento canplay
              weaponObj &&
                logSubVideo('video not ready, waiting for canplay', { videoURL, readyState: video.readyState });
              typeof weaponObj === "object" &&
                const onCanPlay = () => {
              Object.keys(weaponObj).length > 0
                    video.removeEventListener('canplay', onCanPlay);
            ) {
                    video.play().catch(() => { });
              hasValidWeapon = true;
                };
                video.addEventListener('canplay', onCanPlay, { once: true });
             }
             }
          } catch (e) {
            // Se não for JSON válido, não considera como weapon válido
          }
         }
         }


         // Funções antigas removidas - usar getOrCreateSubskillVideo() em vez disso
         if (weaponOn && hasValidWeapon) {
        // REMOVIDO: ensureSubVideoCached
          el.classList.add("has-weapon-available");
        // REMOVIDO: ensureSubVideoInCache
          if (el.classList.contains("active")) {
 
             el.classList.add("weapon-equipped");
        api.refreshCurrentSubSafe = function () {
          }
            const btn = document.querySelector('.subskills-rail .subicon.active');
        } else {
            if (!btn) return false;
          el.classList.remove("has-weapon-available");
            const had = document.body.dataset.suppressSkillPlay;
          el.classList.remove("weapon-equipped");
             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
    api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) {
        const applyWeaponClassesToSubskills = () => {
      logSubVideo("api.renderBarFrom called", {
            const weaponOn = isWeaponModeOn();
        hasEl: !!el,
            // Busca TODAS as subskills com weapon
        hasIconsBar: !!iconsBar,
            let weaponSubs = document.querySelectorAll('.subicon[data-weapon]');
        hasDescBox: !!descBox,
        hasVideoBox: !!videoBox,
        parentIndex: el?.dataset?.index || "unknown",
      });


            weaponSubs.forEach(el => {
      const rail = ensureRail(iconsBar);
                const weaponData = el.getAttribute('data-weapon');
      if (!rail) {
                // Verifica se o weapon não está vazio (não é '{}' ou vazio)
        logSubVideo("api.renderBarFrom: no rail found, returning");
                let hasValidWeapon = false;
        return;
                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) {
      const rawSubs = el.getAttribute("data-subs") || "";
                    el.classList.add('has-weapon-available');
      const rawOrder = el.getAttribute("data-suborder") || "";
                    if (el.classList.contains('active')) {
      const parentIdx = el.dataset.index || "";
                        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: parsing subs", {
            logSubVideo('api.renderBarFrom called', {
        parentIdx,
                hasEl: !!el,
        hasRawSubs: !!rawSubs.trim(),
                hasIconsBar: !!iconsBar,
        rawSubsLength: rawSubs.length,
                hasDescBox: !!descBox,
      });
                hasVideoBox: !!videoBox,
                parentIndex: el?.dataset?.index || 'unknown'
            });


            const rail = ensureRail(iconsBar);
      if (!rawSubs.trim()) {
            if (!rail) {
        if (subRail) subRail.classList.add("collapsed");
                logSubVideo('api.renderBarFrom: no rail found, returning');
        if (subRail) subRail.classList.add("hidden");
                return;
        if (subBar) subBar.innerHTML = "";
            }
        if (spacer) spacer.style.height = "0px";
        return;
      }


            const rawSubs = el.getAttribute('data-subs') || '';
      let subs;
            const rawOrder = el.getAttribute('data-suborder') || '';
      try {
            const parentIdx = el.dataset.index || '';
        subs = JSON.parse(rawSubs);
      } catch {
        subs = [];
      }


            logSubVideo('api.renderBarFrom: parsing subs', {
      // NORMALIZADOR: Converte weaponPacked para weapon se necessário
                parentIdx,
      subs = subs.map((sub) => {
                hasRawSubs: !!rawSubs.trim(),
        // Se tem weaponPacked mas não tem weapon, tenta parsear
                 rawSubsLength: rawSubs.length
        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] || "Nada.png",
              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];
              }
             });
             });
 
          }
            if (!rawSubs.trim()) {
        }
                if (subRail) subRail.classList.add('collapsed');
        // Garante que weapon seja objeto válido
                if (subRail) subRail.classList.add('hidden');
        if (sub.weapon && typeof sub.weapon === "string") {
                if (subBar) subBar.innerHTML = '';
          // Tenta parsear como JSON primeiro
                 if (spacer) spacer.style.height = '0px';
          try {
                 return;
            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] || "Nada.png",
                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;
      });


            let subs;
      if (!Array.isArray(subs) || subs.length === 0) {
            try { subs = JSON.parse(rawSubs); } catch { subs = []; }
        subRail.classList.add("collapsed");
        subRail.classList.add("hidden");
        subBar.innerHTML = "";
        if (spacer) spacer.style.height = "0px";
        return;
      }


            // NORMALIZADOR: Converte weaponPacked para weapon se necessário
      // Busca mapa das skills principais para herança
            subs = subs.map(sub => {
      const mainSkills = getMainSkillsMap();
                // 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] || 'Nada.png',
                            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] || 'Nada.png',
                                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) {
      // Debug: log dos dados antes da herança
                subRail.classList.add('collapsed');
      if (SUBVIDEO_DEBUG && subs.length > 0) {
                subRail.classList.add('hidden');
        logSubVideo("subs before inheritance", {
                subBar.innerHTML = '';
          subsCount: subs.length,
                if (spacer) spacer.style.height = '0px';
          firstSub: JSON.stringify(subs[0]),
                return;
          allSubs: subs.map((s) => ({
            }
            refM: s.refM || s.M || s.m,
            video: s.video,
            hasVideo: s.hasOwnProperty("video"),
          })),
        });
      }


            // Busca mapa das skills principais para herança
      // Aplica herança ANTES de processar
            const mainSkills = getMainSkillsMap();
      subs = subs.map((sub) => applyInheritance(sub, mainSkills));


            // Debug: log dos dados antes da herança
      // Remove subskills que ficaram sem nome após herança
            if (SUBVIDEO_DEBUG && subs.length > 0) {
      subs = subs.filter((s) => (s.name || s.n || "").trim() !== "");
                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
      subRail.classList.add("hidden");
            subs = subs.map(sub => applyInheritance(sub, mainSkills));
      subBar.innerHTML = "";


            // Remove subskills que ficaram sem nome após herança
      // Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
            subs = subs.filter(s => (s.name || s.n || '').trim() !== '');
      const subsById = new Map();
      subs.forEach((s) => {
        const id = s.id || s.name || s.n || "";
        if (id) {
          subsById.set(id, s);
        }
      });


             subRail.classList.add('hidden');
      // Usa a ordem natural das subskills após herança
             subBar.innerHTML = '';
      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 { }
      }


            // Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
      // Pré-carrega TODOS os ícones ANTES de renderizar
            const subsById = new Map();
      const iconPreloadPromises = [];
            subs.forEach(s => {
      order.forEach((idOrName) => {
                const id = s.id || s.name || s.n || '';
        const s =
                if (id) {
          subsById.get(idOrName) ||
                    subsById.set(id, s);
          subs.find((x) => (x.name || x.n || "") === idOrName);
                }
        if (s && s.icon && s.icon.trim() !== "" && s.icon !== "Nada.png") {
            });
          iconPreloadPromises.push(preloadImage(s.icon));
        }
      });


      // Vídeos serão criados e carregados sob demanda quando necessário


            // Usa a ordem natural das subskills após herança
      // Função para renderizar a barra de subskills
            let order = subs.map(s => s.id || s.name || s.n || '');
      const renderSubskillsBar = () => {
            if (rawOrder.trim()) {
        order.forEach((idOrName) => {
                try {
          // CORREÇÃO: Usa ID para lookup (evita colisão de nomes)
                    const preferred = JSON.parse(rawOrder);
          const s =
                    if (Array.isArray(preferred) && preferred.length) {
            subsById.get(idOrName) ||
                        // Tenta por ID primeiro, depois por nome (compatibilidade)
            subs.find((x) => (x.name || x.n || "") === idOrName);
                        const byName = new Map(subs.map(s => [(s.name || s.n || ''), s]));
          if (!s) {
                        const byId = new Map(subs.map(s => [s.id || s.name || s.n || '', s]));
            return;
                        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 item = document.createElement("div");
            const iconPreloadPromises = [];
          item.className = "subicon";
            order.forEach(idOrName => {
          item.title = s.name || s.n || "";
                const s = subsById.get(idOrName) || subs.find(x => (x.name || x.n || '') === idOrName);
                if (s && s.icon && s.icon.trim() !== '' && s.icon !== 'Nada.png') {
                    iconPreloadPromises.push(preloadImage(s.icon));
                }
            });


            // Vídeos serão criados e carregados sob demanda quando necessário
          // CORREÇÃO: Adiciona data-skill-id para lookups únicos
          const skillId = s.id || s.name || s.n || "";
          if (skillId) {
            item.dataset.skillId = skillId;
          }


             // Função para renderizar a barra de subskills
          const slugify =
             const renderSubskillsBar = () => {
             window.__skillSlugify ||
                 order.forEach(idOrName => {
             ((str) =>
                    // CORREÇÃO: Usa ID para lookup (evita colisão de nomes)
              (str || "")
                    const s = subsById.get(idOrName) || subs.find(x => (x.name || x.n || '') === idOrName);
                 .toLowerCase()
                    if (!s) {
                .replace(/[^\w]+/g, "-")
                        return;
                .replace(/^-+|-+$/g, ""));
                    }
          item.dataset.slug = slugify(s.name || s.n || "");


                    const item = document.createElement('div');
          logSubVideo("creating subicon element", {
                    item.className = 'subicon';
            subName: (s.name || s.n || "").trim(),
                    item.title = s.name || s.n || '';
            parentIdx,
            className: item.className,
          });


                    // CORREÇÃO: Adiciona data-skill-id para lookups únicos
          const img = document.createElement("img");
                    const skillId = s.id || s.name || s.n || '';
          img.alt = "";
                    if (skillId) {
          const iconUrl = filePathURL(s.icon || "Nada.png");
                        item.dataset.skillId = skillId;
          if (iconUrl) {
                    }
            img.src = iconUrl;
          }
          img.decoding = "async";
          img.loading = "lazy";
          img.width = 42;
          img.height = 42;
          item.appendChild(img);


                    const slugify = window.__skillSlugify || ((str) => (str || '').toLowerCase().replace(/[^\w]+/g, '-').replace(/^-+|-+$/g, ''));
          // Verifica weapon de forma mais robusta
                    item.dataset.slug = slugify(s.name || s.n || '');
          const hasWeapon =
            s.weapon &&
            ((typeof s.weapon === "object" &&
              Object.keys(s.weapon).length > 0) ||
              (typeof s.weapon === "string" && s.weapon.trim() !== ""));


                    logSubVideo('creating subicon element', {
          const subName = (s.name || s.n || "").trim();
                        subName: (s.name || s.n || '').trim(),
                        parentIdx,
                        className: item.className
                    });


                    const img = document.createElement('img');
          if (hasWeapon) {
                    img.alt = '';
            // Normaliza weapon se for string
                    const iconUrl = filePathURL(s.icon || 'Nada.png');
            let weaponObj = s.weapon;
                     if (iconUrl) {
            if (typeof weaponObj === "string") {
                        img.src = iconUrl;
              try {
                weaponObj = JSON.parse(weaponObj);
              } catch {
                // Se falhar, tenta formato ~
                const parts = weaponObj.split("~");
                if (parts.length >= 2) {
                  weaponObj = {
                    icon: parts[0] || "Nada.png",
                    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];
                     }
                     }
                    img.decoding = 'async';
                  });
                    img.loading = 'lazy';
                } else {
                    img.width = 42;
                  weaponObj = null;
                    img.height = 42;
                }
                    item.appendChild(img);
              }
            }


                    // Verifica weapon de forma mais robusta
            if (
                    const hasWeapon = s.weapon && (
              weaponObj &&
                        (typeof s.weapon === 'object' && Object.keys(s.weapon).length > 0) ||
              typeof weaponObj === "object" &&
                        (typeof s.weapon === 'string' && s.weapon.trim() !== '')
              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
                );
              }


                    const subName = (s.name || s.n || '').trim();
              // Aplica classe inicial se o toggle já está ativo
              if (isWeaponModeOn()) {
                item.classList.add("has-weapon-available");
              }
            }
          }


                    if (hasWeapon) {
          // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
                        // Normaliza weapon se for string
          logSubVideo("attaching click handler to subicon", {
                        let weaponObj = s.weapon;
            subName: (s.name || s.n || "").trim(),
                        if (typeof weaponObj === 'string') {
            parentIdx,
                            try {
            itemClassName: item.className,
                                weaponObj = JSON.parse(weaponObj);
          });
                            } catch {
          item.addEventListener("click", () => {
                                // Se falhar, tenta formato ~
            const L = getLabels();
                                const parts = weaponObj.split('~');
                                if (parts.length >= 2) {
                                    weaponObj = {
                                        icon: parts[0] || 'Nada.png',
                                        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) {
            // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
                            try {
            const skillId = item.dataset.skillId || s.id || s.name || s.n || "";
                                item.dataset.weapon = JSON.stringify(weaponObj);
            if (!skillId) {
                            } catch (e) {
              console.error(
                                console.error('[Subskills] Erro ao serializar weapon de subskill', subName, e);
                "[Subskills] Click handler: skillId não encontrado",
                            }
                { item, s }
              );
              return;
            }


                            // Aplica classe inicial se o toggle já está ativo
            // CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
                            if (isWeaponModeOn()) {
            let currentSub = subsById.get(skillId);
                                item.classList.add('has-weapon-available');
            let lookupMethod = "id";
                            }
                        }
                    }


                    // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
            if (!currentSub) {
                    logSubVideo('attaching click handler to subicon', {
              // Fallback: tenta por nome (compatibilidade com dados antigos)
                        subName: (s.name || s.n || '').trim(),
              const subName = (s.name || s.n || "").trim();
                        parentIdx,
              currentSub = subs.find((x) => (x.name || x.n || "") === subName);
                        itemClassName: item.className
              lookupMethod = "name_fallback";
                    });
                    item.addEventListener('click', () => {
                        const L = getLabels();


                        // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
              if (!currentSub) {
                        const skillId = item.dataset.skillId || s.id || (s.name || s.n || '');
                console.error(
                        if (!skillId) {
                  "[Subskills] Click handler: subskill não encontrada",
                            console.error('[Subskills] Click handler: skillId não encontrado', { item, s });
                  {
                            return;
                    skillId,
                        }
                    subName,
                    availableIds: Array.from(subsById.keys()),
                  }
                );
                return;
              }
            }


                        // CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
            const subName = (currentSub.name || currentSub.n || "").trim();
                        let currentSub = subsById.get(skillId);
                        let lookupMethod = 'id';


                        if (!currentSub) {
            logSubVideo("subicon click HANDLER EXECUTED", {
                            // Fallback: tenta por nome (compatibilidade com dados antigos)
              skillId,
                            const subName = (s.name || s.n || '').trim();
              subName,
                            currentSub = subs.find(x => (x.name || x.n || '') === subName);
              parentIdx,
                            lookupMethod = 'name_fallback';
              weaponMode: isWeaponModeOn(),
              hasWeaponDataAttr: !!item.dataset.weapon,
              rawWeaponDataset: item.dataset.weapon || null,
            });


                            if (!currentSub) {
            // Lê weapon diretamente do atributo data-weapon
                                console.error('[Subskills] Click handler: subskill não encontrada', { skillId, subName, availableIds: Array.from(subsById.keys()) });
            let subWeaponData = null;
                                return;
            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 subName = (currentSub.name || currentSub.n || '').trim();
            const weaponOn = isWeaponModeOn();
            const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;


                        logSubVideo('subicon click HANDLER EXECUTED', {
            logSubVideo("weapon status for subclick", {
                            skillId,
              subName,
                            subName,
              weaponEquipped,
                            parentIdx,
              hasSubWeaponData: !!subWeaponData,
                            weaponMode: isWeaponModeOn(),
            });
                            hasWeaponDataAttr: !!item.dataset.weapon,
                            rawWeaponDataset: item.dataset.weapon || null
                        });


                        // Lê weapon diretamente do atributo data-weapon
            // FASE 4: Usa resolveSkillView para resolver tudo de forma determinística
                        let subWeaponData = null;
            const raw = (document.documentElement.lang || "pt").toLowerCase();
                        if (item.dataset.weapon) {
            const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
                            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();
            // Prepara skill com weapon se necessário
                        const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
            const skillForResolution = { ...currentSub };
            if (weaponEquipped && subWeaponData) {
              skillForResolution.weapon = subWeaponData;
            }


                        logSubVideo('weapon status for subclick', {
            // Resolve tudo usando o resolver único
                            subName,
            const resolved = resolveSkillView(skillForResolution, {
                            weaponEquipped,
              lang,
                            hasSubWeaponData: !!subWeaponData
              weaponMode: weaponEquipped,
                        });
              mainSkills,
            });


                        // FASE 4: Usa resolveSkillView para resolver tudo de forma determinística
            // Usa dados resolvidos
                        const raw = (document.documentElement.lang || 'pt').toLowerCase();
            const chosen = resolved.desc;
                        const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
            const attrsObj = resolved.attrs;
            const level = resolved.level;


                        // Prepara skill com weapon se necessário
            let flagsHTML = "";
                        const skillForResolution = { ...currentSub };
            if (
                        if (weaponEquipped && subWeaponData) {
              Array.isArray(currentSub.flags) &&
                            skillForResolution.weapon = subWeaponData;
              currentSub.flags.length > 0
                        }
            ) {
              flagsHTML = renderFlagsRow(currentSub.flags);
            }
 
            if (descBox) {
              descBox.innerHTML = `<div class="skill-title"><h3>${resolved.title || currentSub.name || currentSub.n || ""
                }</h3></div>${level
                  ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>`
                  : ""
                }${renderSubAttrs(attrsObj, L)}<div class="desc">${(
                  chosen || ""
                ).replace(/'''(.*?)'''/g, "<b>$1</b>")}</div>`;
            }


                        // Resolve tudo usando o resolver único
            if (videoBox) {
                        const resolved = resolveSkillView(skillForResolution, {
              const oldFlags = videoBox.querySelector(".skill-flags");
                            lang,
              if (oldFlags) oldFlags.remove();
                            weaponMode: weaponEquipped,
              if (flagsHTML) {
                            mainSkills
                videoBox.insertAdjacentHTML("beforeend", flagsHTML);
                        });
                applyFlagTooltips(videoBox);
              }
            }


                        // Usa dados resolvidos
            // FASE 4: Vídeo vem do resolved (já resolvido)
                        const chosen = resolved.desc;
            const effectiveVideo = resolved.video;
                        const attrsObj = resolved.attrs;
                        const level = resolved.level;


                        let flagsHTML = '';
            logSubVideo("effectiveVideo for sub", {
                        if (Array.isArray(currentSub.flags) && currentSub.flags.length > 0) {
              subName: currentSub.name || currentSub.n,
                            flagsHTML = renderFlagsRow(currentSub.flags);
              skillId,
                        }
              parentIdx,
              effectiveVideo,
              weaponEquipped,
              rawBaseVideo: currentSub.video,
              rawWeaponVideo: subWeaponData?.video,
            });


                        if (descBox) {
            if (
                            descBox.innerHTML = `<div class="skill-title"><h3>${resolved.title || currentSub.name || currentSub.n || ''}</h3></div>${level ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>` : ''}${renderSubAttrs(attrsObj, L)}<div class="desc">${(chosen || '').replace(/'''(.*?)'''/g, '<b>$1</b>')}</div>`;
              !effectiveVideo ||
                        }
              effectiveVideo.trim() === "" ||
              effectiveVideo === "Nada.png" ||
              effectiveVideo.toLowerCase().includes("nada.png")
            ) {
              if (videoBox) videoBox.style.display = "none";
            } else {
              logSubVideo("calling showSubVideo from click handler", {
                effectiveVideo,
              });


                        if (videoBox) {
              // Usa getOrCreateSubskillVideo internamente - nunca recria se já existe
                            const oldFlags = videoBox.querySelector('.skill-flags');
              showSubVideo(effectiveVideo, videoBox);
                            if (oldFlags) oldFlags.remove();
            }
                            if (flagsHTML) {
                                videoBox.insertAdjacentHTML('beforeend', flagsHTML);
                                applyFlagTooltips(videoBox);
                            }
                        }


                        // FASE 4: Vídeo vem do resolved (já resolvido)
            Array.from(subBar.children).forEach((c) => {
                        const effectiveVideo = resolved.video;
              c.classList.remove("active");
              c.classList.remove("weapon-equipped");
            });
            item.classList.add("active");


                        logSubVideo('effectiveVideo for sub', {
            // Aplica weapon-equipped se tem weapon e está ativo
                            subName: currentSub.name || currentSub.n,
            if (weaponEquipped) {
                            skillId,
              item.classList.add("weapon-equipped");
                            parentIdx,
            }
                            effectiveVideo,
            window.__lastActiveSkillIcon = item;
                            weaponEquipped,
          });
                            rawBaseVideo: currentSub.video,
                            rawWeaponVideo: subWeaponData?.video
                        });


                        if (!effectiveVideo || effectiveVideo.trim() === '' || effectiveVideo === 'Nada.png' || effectiveVideo.toLowerCase().includes('nada.png')) {
          if (window.__globalSkillTooltip) {
                            if (videoBox) videoBox.style.display = 'none';
            const { show, hide, measureAndPos, lockUntil } =
                        } else {
              window.__globalSkillTooltip;
                            logSubVideo('calling showSubVideo from click handler', { effectiveVideo });
            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);
          }


                            // Usa getOrCreateSubskillVideo internamente - nunca recria se já existe
          subBar.appendChild(item);
                            showSubVideo(effectiveVideo, videoBox);
          logSubVideo("subicon appended to subBar", {
                        }
            subName: (s.name || s.n || "").trim(),
            parentIdx,
            subBarChildrenCount: subBar.children.length,
          });
        });


                        Array.from(subBar.children).forEach(c => {
        // Aplica classes de weapon nas subskills recém-renderizadas
                            c.classList.remove('active');
        applyWeaponClassesToSubskills();
                            c.classList.remove('weapon-equipped');
                        });
                        item.classList.add('active');


                        // Aplica weapon-equipped se tem weapon e está ativo
        // Dispara evento para notificar que subskills estão prontas
                        if (weaponEquipped) {
        window.dispatchEvent(
                            item.classList.add('weapon-equipped');
          new CustomEvent("gla:subskills:ready", {
                        }
            detail: { count: order.length },
                        window.__lastActiveSkillIcon = item;
          })
                    });
        );


                    if (window.__globalSkillTooltip) {
        // Remove listener anterior se existir (evita duplicação)
                        const { show, hide, measureAndPos, lockUntil } = window.__globalSkillTooltip;
        if (subBar._weaponToggleListener) {
                        const label = item.title || '';
          window.removeEventListener(
                        item.setAttribute('aria-label', label);
            "gla:weaponToggled",
                        if (item.hasAttribute('title')) item.removeAttribute('title');
            subBar._weaponToggleListener
                        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);
        // Cria novo listener que opera no subBar atual
                    logSubVideo('subicon appended to subBar', {
        subBar._weaponToggleListener = (e) => {
                        subName: (s.name || s.n || '').trim(),
          const enabled = e.detail?.enabled ?? false;
                        parentIdx,
                        subBarChildrenCount: subBar.children.length
                    });
                });


                // Aplica classes de weapon nas subskills recém-renderizadas
          // Aplica classes usando a função auxiliar
                applyWeaponClassesToSubskills();
          applyWeaponClassesToSubskills();


                // Dispara evento para notificar que subskills estão prontas
          // Atualiza a subskill ativa se houver
                window.dispatchEvent(new CustomEvent('gla:subskills:ready', { detail: { count: order.length } }));
          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);
        };


                // Remove listener anterior se existir (evita duplicação)
        window.addEventListener(
                if (subBar._weaponToggleListener) {
          "gla:weaponToggled",
                    window.removeEventListener('gla:weaponToggled', subBar._weaponToggleListener);
          subBar._weaponToggleListener
                }
        );


                // Cria novo listener que opera no subBar atual
        requestAnimationFrame(() => {
                subBar._weaponToggleListener = (e) => {
          subRail.classList.remove("collapsed");
                    const enabled = e.detail?.enabled ?? false;
          subRail.classList.remove("hidden");
          const h = subRail.offsetHeight || 48;
          if (spacer) spacer.style.height = h + "px";
        });
      };


                    // Aplica classes usando a função auxiliar
      // Chama a função de renderização após pré-carregar ícones
                    applyWeaponClassesToSubskills();
      Promise.all(iconPreloadPromises)
        .then(() => {
          setTimeout(() => {
            renderSubskillsBar();
          }, 10);
        })
        .catch(() => {
          setTimeout(() => {
            renderSubskillsBar();
          }, 10);
        });
    };


                    // Atualiza a subskill ativa se houver
    api.hideAll = function (videoBox) {
                    setTimeout(() => {
      const videos =
                        const activeSub = subBar.querySelector('.subicon[data-weapon].active')
        videoBox?.querySelectorAll('.skill-video[data-sub="1"]') || [];
                            || subBar.querySelector('.subicon.active');
      logSubVideo("api.hideAll called", {
                        if (activeSub) {
        videoBoxExists: !!videoBox,
                            activeSub.dispatchEvent(new Event('click', { bubbles: true }));
        videoCount: videos.length,
                        } else {
      });
                            api.refreshCurrentSubSafe();
      videos.forEach((v) => {
                        }
        try {
                    }, 50);
          v.pause();
                };
        } catch { }
        v.style.display = "none";
      });
    };


                window.addEventListener('gla:weaponToggled', subBar._weaponToggleListener);
    window.renderSubskillsBarFrom = function (el, ctx) {
      api.renderBarFrom(el, ctx);
    };


                requestAnimationFrame(() => {
    api.preloadAllSubskillImages = function () {
                    subRail.classList.remove('collapsed');
      const allSkillIcons = document.querySelectorAll(
                    subRail.classList.remove('hidden');
        ".icon-bar .skill-icon[data-subs]"
                    const h = subRail.offsetHeight || 48;
      );
                    if (spacer) spacer.style.height = h + 'px';
      const preloadPromises = [];
                });
      let totalImages = 0;
            };


            // Chama a função de renderização após pré-carregar ícones
      allSkillIcons.forEach((icon) => {
            Promise.all(iconPreloadPromises).then(() => {
        try {
                setTimeout(() => {
          const subsRaw = icon.getAttribute("data-subs");
                    renderSubskillsBar();
          if (!subsRaw) return;
                }, 10);
          const subs = JSON.parse(subsRaw);
            }).catch(() => {
          if (!Array.isArray(subs)) return;
                setTimeout(() => {
                    renderSubskillsBar();
                }, 10);
            });
        };


        api.hideAll = function (videoBox) {
          subs.forEach((s) => {
             const videos = videoBox?.querySelectorAll('.skill-video[data-sub="1"]') || [];
             if (s && s.icon && s.icon.trim() !== "" && s.icon !== "Nada.png") {
             logSubVideo('api.hideAll called', { videoBoxExists: !!videoBox, videoCount: videos.length });
              preloadPromises.push(preloadImage(s.icon));
            videos.forEach(v => {
              totalImages++;
                 try { v.pause(); } catch { }
             }
                v.style.display = 'none';
            if (s && Array.isArray(s.subs)) {
            });
              s.subs.forEach((nested) => {
         };
                 if (
                  nested &&
                  nested.icon &&
                  nested.icon.trim() !== "" &&
                  nested.icon !== "Nada.png"
                ) {
                  preloadPromises.push(preloadImage(nested.icon));
                  totalImages++;
                }
              });
            }
          });
        } catch (e) {
          console.error("[Subskills] preloadAllSubskillImages error:", e);
         }
      });


         window.renderSubskillsBarFrom = function (el, ctx) { api.renderBarFrom(el, ctx); };
      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();
    };


        api.preloadAllSubskillImages = function () {
    // Função para pré-carregar vídeos de subskills usando getOrCreateSubskillVideo
            const allSkillIcons = document.querySelectorAll('.icon-bar .skill-icon[data-subs]');
    // Função preloadAllSubskillVideos removida - vídeos são carregados sob demanda
            const preloadPromises = [];
            let totalImages = 0;


            allSkillIcons.forEach(icon => {
    // Função syncVideoCaches removida - não é mais necessária com cache baseado em URL
                try {
                    const subsRaw = icon.getAttribute('data-subs');
                    if (!subsRaw) return;
                    const subs = JSON.parse(subsRaw);
                    if (!Array.isArray(subs)) return;


                    subs.forEach(s => {
    // Inicialização
                        if (s && s.icon && s.icon.trim() !== '' && s.icon !== 'Nada.png') {
    function init() {
                            preloadPromises.push(preloadImage(s.icon));
      logSubVideo("init: starting initialization");
                            totalImages++;
                        }
                        if (s && Array.isArray(s.subs)) {
                            s.subs.forEach(nested => {
                                if (nested && nested.icon && nested.icon.trim() !== '' && nested.icon !== 'Nada.png') {
                                    preloadPromises.push(preloadImage(nested.icon));
                                    totalImages++;
                                }
                            });
                        }
                    });
                } catch (e) {
                    console.error('[Subskills] preloadAllSubskillImages error:', e);
                }
            });


            if (totalImages > 0) {
      // Constrói cache das skills principais
                // console.log('[Subskills] preloadAllSubskillImages: pré-carregando', totalImages, 'ícones');
      getMainSkillsMap();
                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
      // Pré-carrega imagens das subskills IMEDIATAMENTE
        // Função preloadAllSubskillVideos removida - vídeos são carregados sob demanda
      api.preloadAllSubskillImages();


         // Função syncVideoCaches removida - não é mais necessária com cache baseado em URL
      //         if (video.readyState < 2) {
      //            video.muted = true;
      //            video.load();
      //        }
      //    });
      // }, 500);


        // Inicialização
      // Escuta mudanças no localStorage
         function init() {
      window.addEventListener("storage", (e) => {
            logSubVideo('init: starting initialization');
         if (e.key === "glaWeaponEnabled") {
          setTimeout(() => api.refreshCurrentSubSafe(), 50);
        }
      });


            // Constrói cache das skills principais
      // LISTENER GLOBAL: Escuta evento de toggle
            getMainSkillsMap();
      window.addEventListener("gla:weaponToggled", (e) => {
        const enabled = e.detail?.enabled ?? false;


            // Pré-carrega imagens das subskills IMEDIATAMENTE
        // Tenta aplicar classes imediatamente
            api.preloadAllSubskillImages();
        applyWeaponClassesToSubskills();


            //        if (video.readyState < 2) {
        // Atualiza a subskill ativa se houver
             //            video.muted = true;
         setTimeout(() => {
             //            video.load();
          const activeSub =
            //        }
            document.querySelector(".subicon[data-weapon].active") ||
            //    });
            document.querySelector(".subicon.active");
            // }, 500);
          if (activeSub) {
             activeSub.dispatchEvent(new Event("click", { bubbles: true }));
          } else {
             api.refreshCurrentSubSafe();
          }
        }, 50);
      });


            // Escuta mudanças no localStorage
      // LISTENER: Escuta quando subskills estão prontas
            window.addEventListener('storage', (e) => {
      window.addEventListener("gla:subskills:ready", (e) => {
                if (e.key === 'glaWeaponEnabled') {
        // Aplica classes de weapon se o toggle estiver ativo
                    setTimeout(() => api.refreshCurrentSubSafe(), 50);
        applyWeaponClassesToSubskills();
                }
      });
            });
    }


            // LISTENER GLOBAL: Escuta evento de toggle
    if (document.readyState === "loading") {
            window.addEventListener('gla:weaponToggled', (e) => {
      document.addEventListener("DOMContentLoaded", () => {
                const enabled = e.detail?.enabled ?? false;
        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;
  }


                // Tenta aplicar classes imediatamente
  .subicon-bar::-webkit-scrollbar {
                applyWeaponClassesToSubskills();
    height: 6px;
  }


                // Atualiza a subskill ativa se houver
  .subicon-bar::-webkit-scrollbar-thumb {
                setTimeout(() => {
    background: #151515;
                    const activeSub = document.querySelector('.subicon[data-weapon].active')
    border-radius: 3px;
                        || document.querySelector('.subicon.active');
  }
                    if (activeSub) {
                        activeSub.dispatchEvent(new Event('click', { bubbles: true }));
                    } else {
                        api.refreshCurrentSubSafe();
                    }
                }, 50);
            });


            // LISTENER: Escuta quando subskills estão prontas
  .subicon {
            window.addEventListener('gla:subskills:ready', (e) => {
    width: var(--icon-size, 42px);
                // Aplica classes de weapon se o toggle estiver ativo
    height: var(--icon-size, 42px);
                applyWeaponClassesToSubskills();
    border-radius: var(--icon-radius, 10px);
            });
    overflow: hidden;
        }
    position: relative;
    flex: 0 0 auto;
    cursor: pointer;
    isolation: isolate;
  }


        if (document.readyState === 'loading') {
  .subicon img {
            document.addEventListener('DOMContentLoaded', () => {
    width: 100%;
                setTimeout(init, 100);
    height: 100%;
            });
     aspect-ratio: 1 / 1;
        } else {
    object-fit: cover;
            setTimeout(init, 100);
    display: block;
        }
    border-radius: inherit;
    })();
  }
</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 {
  .subicon::after {
        height: 6px;
    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-bar::-webkit-scrollbar-thumb {
  .subicon:hover::after {
        background: #151515;
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
        border-radius: 3px;
  }
    }


    .subicon {
  .subicon.active::after {
        width: var(--icon-size, 42px);
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #ffd95a);
        height: var(--icon-size, 42px);
  }
        border-radius: var(--icon-radius, 10px);
        overflow: hidden;
        position: relative;
        flex: 0 0 auto;
        cursor: pointer;
        isolation: isolate;
    }


    .subicon img {
  .subicon.active {
        width: 100%;
    transform: scale(1.1);
        height: 100%;
    z-index: 5;
        aspect-ratio: 1 / 1;
  }
        object-fit: cover;
        display: block;
        border-radius: inherit;
    }


    .subicon::after {
  .subicon.active::before {
        content: "";
    content: "";
        position: absolute;
    position: absolute;
        inset: 0;
    inset: -4px;
        border-radius: inherit;
    border-radius: calc(var(--icon-radius, 10px) + 4px);
        box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-idle, #cfcfcf);
    pointer-events: none;
        pointer-events: none;
    z-index: 1;
        z-index: 2;
    opacity: 1;
        transition: box-shadow .12s ease;
    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:hover::after {
  .subicon.active img {
        box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
    transform: none !important;
    }
  }


    .subicon.active::after {
  .top-rail.skills {
        box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #FFD95A);
    position: relative;
     }
    display: flex;
    flex-direction: column;
    align-items: center;
     overflow: visible;
  }


    .subicon.active {
  .top-rail.skills .icon-bar {
        transform: scale(1.10);
    margin-bottom: 0;
        z-index: 5;
    position: relative;
    }
    z-index: 2;
  }


    .subicon.active::before {
  .subskills-rail {
        content: "";
    position: absolute;
        position: absolute;
    left: 50%;
        inset: -4px;
    transform: translateX(-50%);
        border-radius: calc(var(--icon-radius, 10px) + 4px);
    top: calc(100% - 1px);
        pointer-events: none;
    z-index: 3;
        z-index: 1;
    display: inline-flex;
        opacity: 1;
    justify-content: center;
        box-shadow: 0 0 12px 3px var(--icon-active-glow, rgba(255, 217, 90, .30)),
    align-items: center;
            0 0 0 calc(var(--icon-ring-w, 2px) * 2) var(--icon-active-ring, rgba(255, 217, 90, .50));
    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;
  }


     .subicon.active img {
  .subskills-rail::before {
         transform: none !important;
    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;
  }


    .top-rail.skills {
  .subskills-rail.collapsed {
        position: relative;
    opacity: 0;
        display: flex;
    pointer-events: none;
        flex-direction: column;
    transform: translate(-50%, -6px);
        align-items: center;
  }
        overflow: visible;
    }


    .top-rail.skills .icon-bar {
  .subskills-rail.hidden {
        margin-bottom: 0;
    visibility: hidden;
        position: relative;
  }
        z-index: 2;
    }


    .subskills-rail {
  .subskills-spacer {
        position: absolute;
    height: 0;
        left: 50%;
    transition: height 0.2s ease;
        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, .38);
        border: 1px solid rgba(255, 255, 255, .10);
        border-top: 1px solid rgba(255, 255, 255, .08);
        border-radius: 0 0 10px 10px;
        box-shadow: 0 3px 9px rgba(0, 0, 0, .22);
        -webkit-backdrop-filter: blur(2px);
        backdrop-filter: blur(2px);
        overflow: hidden;
        transition: opacity .14s ease, transform .14s ease;
        opacity: 1;
    }


    .subskills-rail::before {
  .subskills-rail .subicon-bar {
        content: "";
    display: inline-flex;
        position: absolute;
    align-items: center;
        top: -6px;
    gap: 0;
        left: 0;
    overflow-x: auto;
        right: 0;
    /* Firefox */
        height: 6px;
    scrollbar-width: thin;
        background: linear-gradient(to bottom, rgba(0, 0, 0, .20), rgba(0, 0, 0, 0));
    scrollbar-color: #ababab transparent;
        pointer-events: none;
  }
    }


    .subskills-rail.collapsed {
  .subskills-rail .subicon-bar::-webkit-scrollbar {
        opacity: 0;
    height: 6px;
        pointer-events: none;
  }
        transform: translate(-50%, -6px);
    }


    .subskills-rail.hidden {
  .subskills-rail .subicon-bar::-webkit-scrollbar-thumb {
        visibility: hidden;
    background: #151515;
     }
     border-radius: 3px;
  }


    .subskills-spacer {
  .subskills-rail .subicon {
        height: 0;
    width: 42px;
        transition: height .2s ease;
    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-bar {
  .subskills-rail .subicon+.subicon {
        display: inline-flex;
    margin-left: 4px;
        align-items: center;
  }
        gap: 0;
        overflow-x: auto;
        /* Firefox */
        scrollbar-width: thin;
        scrollbar-color: #ababab transparent;
    }


    .subskills-rail .subicon-bar::-webkit-scrollbar {
  .subskills-rail .subicon img {
        height: 6px;
    width: 100%;
     }
    height: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
     border-radius: inherit;
  }


    .subskills-rail .subicon-bar::-webkit-scrollbar-thumb {
  .subskills-rail .subicon::after {
        background: #151515;
    content: "";
        border-radius: 3px;
    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 {
  .subskills-rail .subicon:hover::after {
        width: 42px;
    box-shadow: inset 0 0 0 2px #e6e6e6;
        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 {
  .subskills-rail .subicon.active::after {
        margin-left: 4px;
    box-shadow: inset 0 0 0 2px var(--icon-active, #ffd95a);
    }
  }


    .subskills-rail .subicon img {
  .video-container .skill-video {
        width: 100%;
    width: 100%;
        height: 100%;
    height: auto;
        aspect-ratio: 1 / 1;
    aspect-ratio: 16 / 9;
        object-fit: cover;
    object-fit: cover;
        display: block;
    background: #000;
        border-radius: inherit;
    border-radius: 10px;
    }
  }


     .subskills-rail .subicon::after {
  @media (max-width: 900px) {
        content: "";
     .subskills-rail {
        position: absolute;
      position: static;
        inset: 0;
      transform: none;
        border-radius: inherit;
      margin-top: -2px;
        box-shadow: inset 0 0 0 2px var(--icon-idle, #cfcfcf);
      border-top: 0;
        pointer-events: none;
      border-radius: 0 0 10px 10px;
        z-index: 2;
        transition: box-shadow .12s ease;
     }
     }


     .subskills-rail .subicon:hover::after {
     .subskills-spacer {
        box-shadow: inset 0 0 0 2px #e6e6e6;
      height: 0 !important;
     }
     }
  }


    .subskills-rail .subicon.active::after {
  .skills-rail-wrap {
        box-shadow: inset 0 0 0 2px var(--icon-active, #FFD95A);
    position: relative;
    }
    display: block;
    width: max-content;
    margin: 0 auto;
  }


    .video-container .skill-video {
  /* Subskills com arma disponível - borda vermelha quando inativa */
        width: 100%;
  .character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after {
        height: auto;
    box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important;
        aspect-ratio: 16 / 9;
  }
        object-fit: cover;
        background: #000;
        border-radius: 10px;
    }


    @media (max-width: 900px) {
  /* Subskill com arma ATIVA - laranja/coral vibrante + brilho forte */
        .subskills-rail {
  .character-box .top-rail.skills .subicon.has-weapon-available.active {
            position: static;
    position: relative;
            transform: none;
  }
            margin-top: -2px;
            border-top: 0;
            border-radius: 0 0 10px 10px;
        }


        .subskills-spacer {
  .character-box .top-rail.skills .subicon.has-weapon-available.active::after {
            height: 0 !important;
    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;
  }


    .skills-rail-wrap {
  .character-box .top-rail.skills .subicon.has-weapon-available.active::before {
        position: relative;
    box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.95),
        display: block;
      0 0 40px 16px rgba(255, 87, 34, 0.75),
        width: max-content;
      0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6) !important;
        margin: 0 auto;
    opacity: 1 !important;
    }
  }


    /* Subskills com arma disponível - borda vermelha quando inativa */
  /* Modo arma ON - efeito animado nos subicons */
    .character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after {
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available {
        box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important;
    position: relative;
    }
  }


    /* Subskill com arma ATIVA - laranja/coral vibrante + brilho forte */
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::after {
    .character-box .top-rail.skills .subicon.has-weapon-available.active {
    box-shadow: none !important;
         position: relative;
    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 .subicon.has-weapon-available.active::after {
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::before {
        box-shadow: none !important;
    content: "";
        background: linear-gradient(135deg,
    position: absolute;
                #FF5722 0%,
    inset: -4px;
                #FF7043 20%,
    border-radius: calc(6px + 4px);
                #FF8A65 40%,
    pointer-events: none;
                #FF7043 60%,
    z-index: 1;
                #FF5722 80%,
    box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
                #FF3D00 100%) !important;
      0 0 32px 12px rgba(255, 80, 80, 0.6),
        background-size: 300% 300% !important;
      0 0 48px 18px rgba(255, 100, 60, 0.4) !important;
        animation: weapon-icon-border-scan 3s linear infinite !important;
     opacity: 1 !important;
        -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
    animation: weapon-subicon-pulse 3s ease-in-out infinite !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 {
  /* Subskill ativa com arma - mais intenso */
         box-shadow:
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::after {
            0 0 20px 8px rgba(255, 87, 34, 0.95),
    background: linear-gradient(135deg,
            0 0 40px 16px rgba(255, 87, 34, 0.75),
        #ff3d00 0%,
            0 0 60px 24px rgba(255, 120, 50, 0.5),
        #ff5722 15%,
            0 0 0 4px rgba(255, 87, 34, 0.6) !important;
        #ff7043 30%,
        opacity: 1 !important;
        #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;
  }


    /* Modo arma ON - efeito animado nos subicons */
  .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::before {
    .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available {
    box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
        position: relative;
      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;
  }


    .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::after {
  @keyframes weapon-icon-border-scan {
        box-shadow: none !important;
    0% {
        background: linear-gradient(90deg,
      background-position: 0% 0%;
                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 {
     100% {
        content: "";
      background-position: 400% 0%;
        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 */
  @keyframes weapon-subicon-border-scan {
    .character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::after {
    0% {
        background: linear-gradient(135deg,
      background-position: 0% 0%;
                #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 {
     100% {
        box-shadow:
      background-position: 400% 0%;
            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 {
  @keyframes weapon-subicon-pulse {
        0% {
            background-position: 0% 0%;
        }


        100% {
    0%,
            background-position: 400% 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);
     }
     }


     @keyframes weapon-subicon-border-scan {
     50% {
        0% {
      opacity: 1;
            background-position: 0% 0%;
      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);
         100% {
            background-position: 400% 0%;
        }
     }
     }
  }


    @keyframes weapon-subicon-pulse {
  @keyframes weapon-subicon-pulse-active {
 
        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% {
    0%,
            opacity: 1;
    100% {
            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);
      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);
     }
     }


     @keyframes weapon-subicon-pulse-active {
     50% {
 
      opacity: 1;
        0%,
      box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
        100% {
        0 0 56px 24px rgba(255, 87, 34, 0.85),
            opacity: 0.95;
        0 0 80px 32px rgba(255, 140, 60, 0.6), 0 0 0 5px rgba(255, 87, 34, 0.75);
            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>
</style>

Edição das 14h18min de 22 de janeiro 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 !== "Nada.png" && sub.icon !== ""
           ? sub.icon
           : shouldInheritField("icon")
             ? main.icon || "Nada.png"
             : "",
       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 Nada.png que não existe
     if (
       !fileName ||
       fileName.trim() === "" ||
       fileName === "Nada.png" ||
       fileName.toLowerCase() === "nada.png"
     ) {
       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 || "Nada.png");
     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);
     if (!arr.length) return "";
     const items = arr
       .map(
         (k) =>
           `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(
             map[k]
           )}">`
       )
       .join("");

return `

${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] || "Nada.png",
             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] || "Nada.png",
               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() !== "" && s.icon !== "Nada.png") {
         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 || "Nada.png");
         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] || "Nada.png",
                   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() === "" ||
             effectiveVideo === "Nada.png" ||
             effectiveVideo.toLowerCase().includes("nada.png")
           ) {
             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() !== "" && s.icon !== "Nada.png") {
             preloadPromises.push(preloadImage(s.icon));
             totalImages++;
           }
           if (s && Array.isArray(s.subs)) {
             s.subs.forEach((nested) => {
               if (
                 nested &&
                 nested.icon &&
                 nested.icon.trim() !== "" &&
                 nested.icon !== "Nada.png"
               ) {
                 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;
 }
 .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.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;
 }
 .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>