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

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m (teste)
 
(2 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
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 !== ""
            ? sub.icon
            : shouldInheritField("icon")
              ? main.icon || ""
              : "",
        level: hasValue(sub.level)
          ? sub.level
          : shouldInheritField("level")
            ? main.level
             : "",
        video: finalVideo, // Nunca herda
        powerpve:
          sub.powerpve !== undefined && sub.powerpve !== null
            ? sub.powerpve
            : shouldInheritField("powerpve")
              ? main.powerpve
              : undefined,
        powerpvp:
          sub.powerpvp !== undefined && sub.powerpvp !== null
            ? sub.powerpvp
            : shouldInheritField("powerpvp")
              ? main.powerpvp
              : undefined,
        cooldown:
          sub.cooldown !== undefined && sub.cooldown !== null
            ? sub.cooldown
             : shouldInheritField("cooldown")
              ? main.cooldown
              : undefined,
        energy:
          sub.energy !== undefined && sub.energy !== null
            ? sub.energy
            : shouldInheritField("energy")
              ? main.energy
              : undefined,
        // Descrição: sempre vem da subskill, nunca herda
        // PROTEÇÃO TOTAL: NUNCA copia descrição do main, mesmo que subskill não tenha
        descPt: sub.desc_i18n?.pt || sub.descPt || undefined,
        descEn: sub.desc_i18n?.en || sub.descEn || undefined,
        descEs: sub.desc_i18n?.es || sub.descEs || undefined,
        descPl: sub.desc_i18n?.pl || sub.descPl || undefined,
        desc_i18n: sub.desc_i18n || null,
        // GARANTIA: Remove qualquer campo legado que possa ter sido copiado
        desc: undefined,
        flags:
          sub.flags || (shouldInheritField("flags") ? main.flags : undefined),
        weapon:
          sub.weapon ||
          (shouldInheritField("weapon") ? main.weapon : undefined),
        back:
          sub.back !== undefined
            ? sub.back
            : shouldInheritField("back")
              ? main.back
              : undefined,
        // Preserva campos de herança
        inherit_from: inheritFrom,
        inherit_fields: inheritFields,
      };
    }


            // Função auxiliar: verifica se campo DEVE ser herdado
    function filePathURL(fileName) {
            const shouldInheritField = (fieldName) => {
      // Evita requisições para valores vazios
                return inheritFieldsSet.has(fieldName);
      if (
            };
        !fileName ||
        fileName.trim() === ""
      ) {
        return "";
      }
      const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
      const base =
        window.mw && mw.util && typeof mw.util.wikiScript === "function"
          ? mw.util.wikiScript()
          : window.mw && mw.config
            ? mw.config.get("wgScript") || "/index.php"
            : "/index.php";
      // Garante HTTPS para evitar Mixed Content
      let url = `${base}?title=Especial:FilePath/${f}`;
      if (window.location.protocol === "https:" && url.startsWith("http://")) {
        url = url.replace("http://", "https://");
      }
      return url;
    }


            // 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 || "");
            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
            };
         }
         }
      );
    }
    // Verifica se o modo weapon está ativo
    function isWeaponModeOn() {
      try {
        return localStorage.getItem("glaWeaponEnabled") === "1";
      } catch (e) {
        return false;
      }
    }


        function filePathURL(fileName) {
    // Retorna os atributos corretos (weapon ou normal)
            // Evita requisições para Nada.png que não existe
    function getEffectiveAttrs(s) {
            if (!fileName || fileName.trim() === '' || fileName === 'Nada.png' || fileName.toLowerCase() === 'nada.png') {
      const weaponOn = isWeaponModeOn();
                return '';
      if (weaponOn && s.weapon) {
            }
        return {
            const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ''));
          powerpve: s.weapon.powerpve || s.powerpve,
            const base = (window.mw && mw.util && typeof mw.util.wikiScript === 'function')
          powerpvp: s.weapon.powerpvp || s.powerpvp,
                ? mw.util.wikiScript()
          energy: s.weapon.energy || s.energy,
                : (window.mw && mw.config ? (mw.config.get('wgScript') || '/index.php') : '/index.php');
          cooldown: s.weapon.cooldown || s.cooldown,
            // Garante HTTPS para evitar Mixed Content
        };
            let url = `${base}?title=Especial:FilePath/${f}`;
      }
            if (window.location.protocol === 'https:' && url.startsWith('http://')) {
      return {
                url = url.replace('http://', 'https://');
        powerpve: s.powerpve,
            }
        powerpvp: s.powerpvp,
            return url;
        energy: s.energy,
        }
        cooldown: s.cooldown,
      };
    }


        function normalizeFileURL(raw, fallback = '') {
    // Retorna a descrição correta (weapon ou normal)
            if (!raw) return fallback;
    // Aceita tanto desc_i18n quanto desc para compatibilidade
            const val = String(raw).trim();
    function getEffectiveDesc(s) {
            if (!val) return fallback;
      const weaponOn = isWeaponModeOn();
            if (/^(https?:)?\/\//i.test(val) || val.startsWith('data:') || val.includes('Especial:FilePath/')) {
      const raw = (document.documentElement.lang || "pt").toLowerCase();
                return val;
      const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
            }
            return filePathURL(val);
        }


        function preloadImage(iconFile) {
      // Para weapon: aceita tanto desc_i18n quanto desc
            const url = filePathURL(iconFile || 'Nada.png');
      if (weaponOn && s.weapon) {
            if (imagePreloadCache.has(url)) {
        const wDesc = s.weapon.desc_i18n || s.weapon.desc;
                return imagePreloadCache.get(url);
        if (wDesc) {
            }
          return wDesc[lang] || wDesc.pt || wDesc.en || "";
            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() {
      // Descrição: sempre vem da skill/subskill, nunca herda
            const skillsRoot = document.getElementById('skills');
      const base = s.desc_i18n || {};
            const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {};
      if (base && Object.keys(base).length > 0) {
            const raw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase();
        return base[lang] || base.pt || base.en || "";
            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
      // Fallback para campos individuais
         function isWeaponModeOn() {
      const descI18n = {
            try {
         pt: s.descPt || "",
                return localStorage.getItem('glaWeaponEnabled') === '1';
        en: s.descEn || "",
            } catch (e) {
        es: s.descEs || "",
                return false;
        pl: s.descPl || "",
            }
      };
        }
      return descI18n[lang] || descI18n.pt || descI18n.en || "";
    }


        // Retorna os atributos corretos (weapon ou normal)
    // Retorna o vídeo correto (weapon ou normal)
        function getEffectiveAttrs(s) {
    function getEffectiveVideo(s) {
            const weaponOn = isWeaponModeOn();
      const weaponOn = isWeaponModeOn();
            if (weaponOn && s.weapon) {
      if (
                return {
        weaponOn &&
                    powerpve: s.weapon.powerpve || s.powerpve,
        s.weapon &&
                    powerpvp: s.weapon.powerpvp || s.powerpvp,
        s.weapon.video &&
                    energy: s.weapon.energy || s.energy,
        s.weapon.video.trim() !== ""
                    cooldown: s.weapon.cooldown || s.cooldown
      ) {
                };
        return s.weapon.video;
            }
      }
            return {
      return s.video || "";
                powerpve: s.powerpve,
    }
                powerpvp: s.powerpvp,
                energy: s.energy,
                cooldown: s.cooldown
            };
        }


        // Retorna a descrição correta (weapon ou normal)
    // Função única para obtenção de vídeo de subskill - baseada em URL
        // Aceita tanto desc_i18n quanto desc para compatibilidade
    function getOrCreateSubskillVideo(videoURL) {
        function getEffectiveDesc(s) {
      if (!videoURL || videoURL.trim() === "") return null;
            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
      const normalizedURL = normalizeFileURL(videoURL);
            if (weaponOn && s.weapon) {
      if (!normalizedURL || normalizedURL.trim() === "") return null;
                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
      // 1. Tenta cache local primeiro (compatibilidade)
            const base = s.desc_i18n || {};
      if (subskillVideoElementCache.has(normalizedURL)) {
            if (base && Object.keys(base).length > 0) {
        logSubVideo("getOrCreateSubskillVideo: local cache hit", {
                return base[lang] || base.pt || base.en || '';
          videoURL: normalizedURL,
            }
        });
        return subskillVideoElementCache.get(normalizedURL);
      }


            // Fallback para campos individuais
      // 2. Tenta cache global de subskills (window.__subskillVideosCache)
            const descI18n = {
      const globalCache = window.__subskillVideosCache;
                pt: s.descPt || '',
      if (globalCache && globalCache instanceof Map) {
                en: s.descEn || '',
        // O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
                es: s.descEs || '',
        // Precisamos buscar por URL normalizada
                pl: s.descPl || ''
        for (const [key, video] of globalCache.entries()) {
             };
          const src = video.querySelector("source");
             return descI18n[lang] || descI18n.pt || descI18n.en || '';
          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;
          }
         }
         }
      }


        // Retorna o vídeo correto (weapon ou normal)
      // 3. REMOVIDO: criação lazy - todos os vídeos devem estar pré-carregados
         function getEffectiveVideo(s) {
      // Se vídeo não foi encontrado, é um erro (não deve criar novo)
            const weaponOn = isWeaponModeOn();
      console.warn("[Subskills] Vídeo não encontrado no cache:", {
            if (weaponOn && s.weapon && s.weapon.video && s.weapon.video.trim() !== '') {
         videoURL: normalizedURL,
                return s.weapon.video;
        originalURL: videoURL,
             }
      });
             return s.video || '';
      return null;
        }
    }
 
    function renderSubAttrs(s, L) {
      const chip = (label, val) =>
        val
          ? `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${val}</span></div>`
          : "";
      const pve = (s.powerpve || "").toString().trim();
      const pvp = (s.powerpvp || "").toString().trim();
      const en = (s.energy || "").toString().trim();
      const cd = (s.cooldown || "").toString().trim();
      const rows = [
        cd ? chip(L.cooldown, cd) : "",
        en
          ? chip(
             en.startsWith("-") ? L.energy_cost : L.energy_gain,
             en.startsWith("-") ? en.replace(/^-/, "") : en.replace(/^\+?/, "")
          )
          : "",
        pve ? chip(L.power, pve) : "",
        pvp ? chip(L.power_pvp, pvp) : "",
      ].filter(Boolean);
      return rows.length ? `<div class="attr-list">${rows.join("")}</div>` : "";
    }


         // Função única para obtenção de vídeo de subskill - baseada em URL
    function renderFlagsRow(flags) {
         function getOrCreateSubskillVideo(videoURL) {
      const map = {
            if (!videoURL || videoURL.trim() === '') return null;
        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 `<div class="skill-flags" role="group" aria-label="Características">${items}</div>`;
    }


            const normalizedURL = normalizeFileURL(videoURL);
    function applyFlagTooltips(container) {
            if (!normalizedURL || normalizedURL.trim() === '') return null;
      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;


            // 1. Tenta cache local primeiro (compatibilidade)
      flags.forEach((el) => {
            if (subskillVideoElementCache.has(normalizedURL)) {
        const key = el.getAttribute("data-flag");
                logSubVideo('getOrCreateSubskillVideo: local cache hit', { videoURL: normalizedURL });
        const tip = (dict && dict[key]) || "";
                return subskillVideoElementCache.get(normalizedURL);
        if (!tip) return;
            }
        if (el.dataset.flagTipWired) return;
        el.dataset.flagTipWired = "1";
        el.setAttribute("aria-label", tip);
        if (el.hasAttribute("title")) el.removeAttribute("title");


            // 2. Tenta cache global de subskills (window.__subskillVideosCache)
        el.addEventListener("mouseenter", () => {
            const globalCache = window.__subskillVideosCache;
          const tipEl = document.querySelector(".skill-tooltip");
            if (globalCache && globalCache instanceof Map) {
          if (tipEl) tipEl.classList.add("flag-tooltip");
                // O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
          tooltip.show(el, tip);
                // Precisamos buscar por URL normalizada
        });
                for (const [key, video] of globalCache.entries()) {
        el.addEventListener("mousemove", () => {
                    const src = video.querySelector('source');
          if (performance.now() >= tooltip.lockUntil.value) {
                    if (src && src.src === normalizedURL) {
            tooltip.measureAndPos(el);
                        logSubVideo('getOrCreateSubskillVideo: global cache hit', { videoURL: normalizedURL, key });
          }
                        // Adiciona ao cache local também para acesso rápido
        });
                        subskillVideoElementCache.set(normalizedURL, video);
        el.addEventListener("click", () => {
                        return video;
          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();
        });
      });
    }


            // 3. REMOVIDO: criação lazy - todos os vídeos devem estar pré-carregados
    function ensureRail(iconsBar) {
            // Se vídeo não foi encontrado, é um erro (não deve criar novo)
      const rail = iconsBar.closest(".top-rail");
            console.warn('[Subskills] Vídeo não encontrado no cache:', {
      if (!rail) {
                videoURL: normalizedURL,
        return null;
                originalURL: videoURL
      }
            });
            return null;
        }


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


        function renderFlagsRow(flags) {
      if (!subBar) {
            const map = {
        subBar = document.createElement("div");
                aggro: 'Enemyaggro-icon.png',
        subBar.className = "subicon-bar";
                bridge: 'Bridgemaker-icon.png',
        subRail.appendChild(subBar);
                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 (!spacer) {
            const skillsRoot = document.getElementById('skills');
        spacer = document.createElement("div");
            if (!skillsRoot) return;
        spacer.className = "subskills-spacer";
            let pack = {};
        rail.parentNode.insertBefore(spacer, rail.nextSibling);
            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 => {
      return rail;
                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', () => {
    // Função para mostrar vídeo de subskill usando cache baseado em URL
                    const tipEl = document.querySelector('.skill-tooltip');
    function showSubVideo(videoURL, videoBox) {
                    if (tipEl) tipEl.classList.add('flag-tooltip');
      logSubVideo("showSubVideo called", {
                    tooltip.show(el, tip);
        videoURL,
                });
        videoBoxExists: !!videoBox,
                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) {
      if (!videoBox) return;
            const rail = iconsBar.closest('.top-rail');
            if (!rail) {
                return null;
            }


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


            if (!subBar) {
      // Obter vídeo via getOrCreateSubskillVideo (única forma de obter vídeos)
                subBar = document.createElement('div');
      const video = getOrCreateSubskillVideo(videoURL);
                subBar.className = 'subicon-bar';
                subRail.appendChild(subBar);
            }


            if (!spacer) {
      if (!video) {
                spacer = document.createElement('div');
        logSubVideo("no video found for URL, hiding box", { videoURL });
                spacer.className = 'subskills-spacer';
        videoBox.style.display = "none";
                rail.parentNode.insertBefore(spacer, rail.nextSibling);
        return;
            }
      }


            return rail;
      // 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,
      });


        // Função para mostrar vídeo de subskill usando cache baseado em URL
      // Exibir o vídeo
        function showSubVideo(videoURL, videoBox) {
      videoBox.style.display = "block";
            logSubVideo('showSubVideo called', { videoURL, videoBoxExists: !!videoBox });
      video.style.display = "block";


            if (!videoBox) return;
      // Reinicia o vídeo para feedback visual imediato
      try {
        video.currentTime = 0;
      } catch (e) { }


            // Ocultar todos os vídeos existentes no container
      // Tenta dar play se o vídeo já está pronto
            videoBox.querySelectorAll('video.skill-video[data-sub="1"]').forEach(v => {
      if (video.readyState >= 2) {
                v.style.display = 'none';
        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 });
      }
    }


            // Obter vídeo via getOrCreateSubskillVideo (única forma de obter vídeos)
    // Funções antigas removidas - usar getOrCreateSubskillVideo() em vez disso
            const video = getOrCreateSubskillVideo(videoURL);
    // REMOVIDO: ensureSubVideoCached
    // REMOVIDO: ensureSubVideoInCache


            if (!video) {
    api.refreshCurrentSubSafe = function () {
                logSubVideo('no video found for URL, hiding box', { videoURL });
      const btn = document.querySelector(".subskills-rail .subicon.active");
                videoBox.style.display = 'none';
      if (!btn) return false;
                return;
      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]");


            // Se o vídeo não estiver conectado ao DOM, anexá-lo ao container
      weaponSubs.forEach((el) => {
            if (!video.isConnected || video.parentNode !== videoBox) {
        const weaponData = el.getAttribute("data-weapon");
                logSubVideo('video not in DOM, appending', {
        // Verifica se o weapon não está vazio (não é '{}' ou vazio)
                    videoURL,
        let hasValidWeapon = false;
                    isConnected: video.isConnected,
        if (weaponData && weaponData.trim() !== "" && weaponData !== "{}") {
                    parentNode: video.parentNode
          try {
                });
            const weaponObj = JSON.parse(weaponData);
                if (video.parentNode) {
            if (
                    video.parentNode.removeChild(video);
              weaponObj &&
                }
              typeof weaponObj === "object" &&
                videoBox.appendChild(video);
              Object.keys(weaponObj).length > 0
            ) {
              hasValidWeapon = true;
             }
             }
          } catch (e) {
            // Se não for JSON válido, não considera como weapon válido
          }
        }


            logSubVideo('showing video', {
        if (weaponOn && hasValidWeapon) {
                videoURL,
          el.classList.add("has-weapon-available");
                readyState: video.readyState,
          if (el.classList.contains("active")) {
                paused: video.paused,
            el.classList.add("weapon-equipped");
                currentTime: video.currentTime
          }
            });
        } else {
          el.classList.remove("has-weapon-available");
          el.classList.remove("weapon-equipped");
        }
      });
    };


            // Exibir o vídeo
    api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) {
            videoBox.style.display = 'block';
      logSubVideo("api.renderBarFrom called", {
            video.style.display = 'block';
        hasEl: !!el,
        hasIconsBar: !!iconsBar,
        hasDescBox: !!descBox,
        hasVideoBox: !!videoBox,
        parentIndex: el?.dataset?.index || "unknown",
      });


            // Reinicia o vídeo para feedback visual imediato
      const rail = ensureRail(iconsBar);
            try {
      if (!rail) {
                video.currentTime = 0;
        logSubVideo("api.renderBarFrom: no rail found, returning");
            } catch (e) { }
        return;
      }


            // Tenta dar play se o vídeo já está pronto
      const rawSubs = el.getAttribute("data-subs") || "";
            if (video.readyState >= 2) {
      const rawOrder = el.getAttribute("data-suborder") || "";
                video.play().then(() => {
      const parentIdx = el.dataset.index || "";
                    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
      logSubVideo("api.renderBarFrom: parsing subs", {
         // REMOVIDO: ensureSubVideoCached
         parentIdx,
        // REMOVIDO: ensureSubVideoInCache
        hasRawSubs: !!rawSubs.trim(),
         rawSubsLength: rawSubs.length,
      });


        api.refreshCurrentSubSafe = function () {
      if (!rawSubs.trim()) {
            const btn = document.querySelector('.subskills-rail .subicon.active');
        if (subRail) subRail.classList.add("collapsed");
            if (!btn) return false;
        if (subRail) subRail.classList.add("hidden");
            const had = document.body.dataset.suppressSkillPlay;
        if (subBar) subBar.innerHTML = "";
            document.body.dataset.suppressSkillPlay = '1';
        if (spacer) spacer.style.height = "0px";
            try {
        return;
                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
      let subs;
        const applyWeaponClassesToSubskills = () => {
      try {
            const weaponOn = isWeaponModeOn();
        subs = JSON.parse(rawSubs);
            // Busca TODAS as subskills com weapon
      } catch {
            let weaponSubs = document.querySelectorAll('.subicon[data-weapon]');
        subs = [];
      }


            weaponSubs.forEach(el => {
      // NORMALIZADOR: Converte weaponPacked para weapon se necessário
                const weaponData = el.getAttribute('data-weapon');
      subs = subs.map((sub) => {
                // Verifica se o weapon não está vazio (não é '{}' ou vazio)
        // Se tem weaponPacked mas não tem weapon, tenta parsear
                 let hasValidWeapon = false;
        if (
                if (weaponData && weaponData.trim() !== '' && weaponData !== '{}') {
          sub.weaponPacked &&
                    try {
          !sub.weapon &&
                        const weaponObj = JSON.parse(weaponData);
          typeof sub.weaponPacked === "string" &&
                        if (weaponObj && typeof weaponObj === 'object' && Object.keys(weaponObj).length > 0) {
          sub.weaponPacked.trim() !== ""
                            hasValidWeapon = true;
        ) {
                        }
          const parts = sub.weaponPacked.split("~");
                    } catch (e) {
          if (parts.length >= 2) {
                        // Se não for JSON válido, não considera como weapon válido
            sub.weapon = {
                    }
              icon: parts[0] || "",
              powerpve: parts[1] || null,
              powerpvp: parts[2] || null,
              cooldown: parts[3] || null,
              video: parts[4] || "",
              energy: parts[5] || null,
            };
            // Remove valores vazios
            Object.keys(sub.weapon).forEach((k) => {
              if (sub.weapon[k] === "" || sub.weapon[k] === null) {
                 delete sub.weapon[k];
              }
            });
          }
        }
        // Garante que weapon seja objeto válido
        if (sub.weapon && typeof sub.weapon === "string") {
          // Tenta parsear como JSON primeiro
          try {
            sub.weapon = JSON.parse(sub.weapon);
          } catch {
            // Se falhar, tenta formato ~
            const parts = sub.weapon.split("~");
            if (parts.length >= 2) {
              sub.weapon = {
                icon: parts[0] || "",
                powerpve: parts[1] || null,
                powerpvp: parts[2] || null,
                cooldown: parts[3] || null,
                video: parts[4] || "",
                energy: parts[5] || null,
              };
              Object.keys(sub.weapon).forEach((k) => {
                if (sub.weapon[k] === "" || sub.weapon[k] === null) {
                  delete sub.weapon[k];
                 }
                 }
              });
            } else {
              sub.weapon = null;
            }
          }
        }
        return sub;
      });


                if (weaponOn && hasValidWeapon) {
      if (!Array.isArray(subs) || subs.length === 0) {
                    el.classList.add('has-weapon-available');
        subRail.classList.add("collapsed");
                    if (el.classList.contains('active')) {
        subRail.classList.add("hidden");
                        el.classList.add('weapon-equipped');
        subBar.innerHTML = "";
                    }
        if (spacer) spacer.style.height = "0px";
                } else {
        return;
                    el.classList.remove('has-weapon-available');
      }
                    el.classList.remove('weapon-equipped');
                }
            });
        };


        api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) {
      // Busca mapa das skills principais para herança
            logSubVideo('api.renderBarFrom called', {
      const mainSkills = getMainSkillsMap();
                hasEl: !!el,
                hasIconsBar: !!iconsBar,
                hasDescBox: !!descBox,
                hasVideoBox: !!videoBox,
                parentIndex: el?.dataset?.index || 'unknown'
            });


            const rail = ensureRail(iconsBar);
      // Debug: log dos dados antes da herança
            if (!rail) {
      if (SUBVIDEO_DEBUG && subs.length > 0) {
                logSubVideo('api.renderBarFrom: no rail found, returning');
        logSubVideo("subs before inheritance", {
                return;
          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"),
          })),
        });
      }


            const rawSubs = el.getAttribute('data-subs') || '';
      // Aplica herança ANTES de processar
            const rawOrder = el.getAttribute('data-suborder') || '';
      subs = subs.map((sub) => applyInheritance(sub, mainSkills));
            const parentIdx = el.dataset.index || '';


            logSubVideo('api.renderBarFrom: parsing subs', {
      // Remove subskills que ficaram sem nome após herança
                parentIdx,
      subs = subs.filter((s) => (s.name || s.n || "").trim() !== "");
                hasRawSubs: !!rawSubs.trim(),
                rawSubsLength: rawSubs.length
            });


            if (!rawSubs.trim()) {
      subRail.classList.add("hidden");
                if (subRail) subRail.classList.add('collapsed');
      subBar.innerHTML = "";
                if (subRail) subRail.classList.add('hidden');
                if (subBar) subBar.innerHTML = '';
                if (spacer) spacer.style.height = '0px';
                return;
            }


            let subs;
      // Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
            try { subs = JSON.parse(rawSubs); } catch { subs = []; }
      const subsById = new Map();
      subs.forEach((s) => {
        const id = s.id || s.name || s.n || "";
        if (id) {
          subsById.set(id, s);
        }
      });


            // NORMALIZADOR: Converte weaponPacked para weapon se necessário
      // Usa a ordem natural das subskills após herança
            subs = subs.map(sub => {
      let order = subs.map((s) => s.id || s.name || s.n || "");
                // Se tem weaponPacked mas não tem weapon, tenta parsear
      if (rawOrder.trim()) {
                if (sub.weaponPacked && !sub.weapon && typeof sub.weaponPacked === 'string' && sub.weaponPacked.trim() !== '') {
        try {
                    const parts = sub.weaponPacked.split('~');
          const preferred = JSON.parse(rawOrder);
                    if (parts.length >= 2) {
          if (Array.isArray(preferred) && preferred.length) {
                        sub.weapon = {
            // Tenta por ID primeiro, depois por nome (compatibilidade)
                            icon: parts[0] || 'Nada.png',
            const byName = new Map(subs.map((s) => [s.name || s.n || "", s]));
                            powerpve: parts[1] || null,
            const byId = new Map(
                            powerpvp: parts[2] || null,
              subs.map((s) => [s.id || s.name || s.n || "", s])
                            cooldown: parts[3] || null,
            );
                            video: parts[4] || '',
            order = preferred
                            energy: parts[5] || null
              .filter((n) => {
                        };
                // Tenta encontrar por nome (compatibilidade com dados antigos)
                        // Remove valores vazios
                if (byName.has(n)) {
                        Object.keys(sub.weapon).forEach(k => {
                  const found = byName.get(n);
                            if (sub.weapon[k] === '' || sub.weapon[k] === null) {
                  return found.id || found.name || found.n || "";
                                delete sub.weapon[k];
                            }
                        });
                    }
                 }
                 }
                 // Garante que weapon seja objeto válido
                 // Tenta por ID
                 if (sub.weapon && typeof sub.weapon === 'string') {
                 return byId.has(n);
                    // Tenta parsear como JSON primeiro
              })
                    try {
              .map((n) => {
                        sub.weapon = JSON.parse(sub.weapon);
                // Retorna o ID se existir, senão o nome
                    } catch {
                const byName = new Map(
                        // Se falhar, tenta formato ~
                  subs.map((s) => [s.name || s.n || "", s])
                        const parts = sub.weapon.split('~');
                );
                        if (parts.length >= 2) {
                if (byName.has(n)) {
                            sub.weapon = {
                  const found = byName.get(n);
                                icon: parts[0] || 'Nada.png',
                  return found.id || found.name || found.n || "";
                                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;
                 return n;
            });
              });
          }
        } catch { }
      }
 
      // Pré-carrega TODOS os ícones ANTES de renderizar
      const iconPreloadPromises = [];
      order.forEach((idOrName) => {
        const s =
          subsById.get(idOrName) ||
          subs.find((x) => (x.name || x.n || "") === idOrName);
        if (s && s.icon && s.icon.trim() !== "") {
          iconPreloadPromises.push(preloadImage(s.icon));
        }
      });
 
      // Vídeos serão criados e carregados sob demanda quando necessário


            if (!Array.isArray(subs) || subs.length === 0) {
      // Função para renderizar a barra de subskills
                subRail.classList.add('collapsed');
      const renderSubskillsBar = () => {
                subRail.classList.add('hidden');
        order.forEach((idOrName) => {
                subBar.innerHTML = '';
          // CORREÇÃO: Usa ID para lookup (evita colisão de nomes)
                if (spacer) spacer.style.height = '0px';
          const s =
                return;
            subsById.get(idOrName) ||
            }
            subs.find((x) => (x.name || x.n || "") === idOrName);
          if (!s) {
            return;
          }


            // Busca mapa das skills principais para herança
          const item = document.createElement("div");
            const mainSkills = getMainSkillsMap();
          item.className = "subicon";
          item.title = s.name || s.n || "";


            // Debug: log dos dados antes da herança
          // CORREÇÃO: Adiciona data-skill-id para lookups únicos
            if (SUBVIDEO_DEBUG && subs.length > 0) {
          const skillId = s.id || s.name || s.n || "";
                logSubVideo('subs before inheritance', {
          if (skillId) {
                    subsCount: subs.length,
            item.dataset.skillId = skillId;
                    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
          const slugify =
             subs = subs.map(sub => applyInheritance(sub, mainSkills));
             window.__skillSlugify ||
             ((str) =>
              (str || "")
                .toLowerCase()
                .replace(/[^\w]+/g, "-")
                .replace(/^-+|-+$/g, ""));
          item.dataset.slug = slugify(s.name || s.n || "");


            // Remove subskills que ficaram sem nome após herança
          logSubVideo("creating subicon element", {
             subs = subs.filter(s => (s.name || s.n || '').trim() !== '');
             subName: (s.name || s.n || "").trim(),
            parentIdx,
            className: item.className,
          });


            subRail.classList.add('hidden');
          const img = document.createElement("img");
             subBar.innerHTML = '';
          img.alt = "";
          const iconUrl = filePathURL(s.icon || "");
          if (iconUrl) {
             img.src = iconUrl;
          }
          img.decoding = "async";
          img.loading = "lazy";
          img.width = 42;
          img.height = 42;
          item.appendChild(img);


            // Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
          // Verifica weapon de forma mais robusta
            const subsById = new Map();
          const hasWeapon =
             subs.forEach(s => {
             s.weapon &&
                const id = s.id || s.name || s.n || '';
            ((typeof s.weapon === "object" &&
                if (id) {
              Object.keys(s.weapon).length > 0) ||
                    subsById.set(id, s);
              (typeof s.weapon === "string" && s.weapon.trim() !== ""));
                }
            });


          const subName = (s.name || s.n || "").trim();


             // Usa a ordem natural das subskills após herança
          if (hasWeapon) {
             let order = subs.map(s => s.id || s.name || s.n || '');
             // Normaliza weapon se for string
             if (rawOrder.trim()) {
             let weaponObj = s.weapon;
                try {
             if (typeof weaponObj === "string") {
                    const preferred = JSON.parse(rawOrder);
              try {
                    if (Array.isArray(preferred) && preferred.length) {
                weaponObj = JSON.parse(weaponObj);
                        // Tenta por ID primeiro, depois por nome (compatibilidade)
              } catch {
                        const byName = new Map(subs.map(s => [(s.name || s.n || ''), s]));
                // Se falhar, tenta formato ~
                        const byId = new Map(subs.map(s => [s.id || s.name || s.n || '', s]));
                const parts = weaponObj.split("~");
                        order = preferred.filter(n => {
                if (parts.length >= 2) {
                            // Tenta encontrar por nome (compatibilidade com dados antigos)
                  weaponObj = {
                            if (byName.has(n)) {
                    icon: parts[0] || "",
                                const found = byName.get(n);
                    powerpve: parts[1] || null,
                                return found.id || found.name || found.n || '';
                    powerpvp: parts[2] || null,
                            }
                    cooldown: parts[3] || null,
                            // Tenta por ID
                    video: parts[4] || "",
                            return byId.has(n);
                    energy: parts[5] || null,
                        }).map(n => {
                  };
                            // Retorna o ID se existir, senão o nome
                  Object.keys(weaponObj).forEach((k) => {
                            const byName = new Map(subs.map(s => [(s.name || s.n || ''), s]));
                    if (weaponObj[k] === "" || weaponObj[k] === null) {
                            if (byName.has(n)) {
                      delete weaponObj[k];
                                const found = byName.get(n);
                                return found.id || found.name || found.n || '';
                            }
                            return n;
                        });
                     }
                     }
                 } catch { }
                  });
                 } else {
                  weaponObj = null;
                }
              }
             }
             }


             // Pré-carrega TODOS os ícones ANTES de renderizar
             if (
            const iconPreloadPromises = [];
              weaponObj &&
            order.forEach(idOrName => {
              typeof weaponObj === "object" &&
                 const s = subsById.get(idOrName) || subs.find(x => (x.name || x.n || '') === idOrName);
              Object.keys(weaponObj).length > 0
                if (s && s.icon && s.icon.trim() !== '' && s.icon !== 'Nada.png') {
            ) {
                    iconPreloadPromises.push(preloadImage(s.icon));
              try {
                }
                 item.dataset.weapon = JSON.stringify(weaponObj);
            });
              } catch (e) {
                console.error(
                  "[Subskills] Erro ao serializar weapon de subskill",
                  subName,
                  e
                );
              }


            // Vídeos serão criados e carregados sob demanda quando necessário
              // Aplica classe inicial se o toggle já está ativo
              if (isWeaponModeOn()) {
                item.classList.add("has-weapon-available");
              }
            }
          }


            // Função para renderizar a barra de subskills
          // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
            const renderSubskillsBar = () => {
          logSubVideo("attaching click handler to subicon", {
                order.forEach(idOrName => {
            subName: (s.name || s.n || "").trim(),
                    // CORREÇÃO: Usa ID para lookup (evita colisão de nomes)
            parentIdx,
                    const s = subsById.get(idOrName) || subs.find(x => (x.name || x.n || '') === idOrName);
            itemClassName: item.className,
                    if (!s) {
          });
                        return;
          item.addEventListener("click", () => {
                    }
            const L = getLabels();


                    const item = document.createElement('div');
            // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
                    item.className = 'subicon';
            const skillId = item.dataset.skillId || s.id || s.name || s.n || "";
                    item.title = s.name || s.n || '';
            if (!skillId) {
              console.error(
                "[Subskills] Click handler: skillId não encontrado",
                { item, s }
              );
              return;
            }


                    // CORREÇÃO: Adiciona data-skill-id para lookups únicos
            // CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
                    const skillId = s.id || s.name || s.n || '';
            let currentSub = subsById.get(skillId);
                    if (skillId) {
            let lookupMethod = "id";
                        item.dataset.skillId = skillId;
                    }


                    const slugify = window.__skillSlugify || ((str) => (str || '').toLowerCase().replace(/[^\w]+/g, '-').replace(/^-+|-+$/g, ''));
            if (!currentSub) {
                    item.dataset.slug = slugify(s.name || s.n || '');
              // 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";


                    logSubVideo('creating subicon element', {
              if (!currentSub) {
                        subName: (s.name || s.n || '').trim(),
                console.error(
                        parentIdx,
                  "[Subskills] Click handler: subskill não encontrada",
                        className: item.className
                  {
                    });
                    skillId,
                    subName,
                    availableIds: Array.from(subsById.keys()),
                  }
                );
                return;
              }
            }


                    const img = document.createElement('img');
            const subName = (currentSub.name || currentSub.n || "").trim();
                    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
            logSubVideo("subicon click HANDLER EXECUTED", {
                    const hasWeapon = s.weapon && (
              skillId,
                        (typeof s.weapon === 'object' && Object.keys(s.weapon).length > 0) ||
              subName,
                        (typeof s.weapon === 'string' && s.weapon.trim() !== '')
              parentIdx,
                    );
              weaponMode: isWeaponModeOn(),
              hasWeaponDataAttr: !!item.dataset.weapon,
              rawWeaponDataset: item.dataset.weapon || null,
            });


                    const subName = (s.name || s.n || '').trim();
            // 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;


                    if (hasWeapon) {
            const weaponOn = isWeaponModeOn();
                        // Normaliza weapon se for string
            const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
                        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) {
            logSubVideo("weapon status for subclick", {
                            try {
              subName,
                                item.dataset.weapon = JSON.stringify(weaponObj);
              weaponEquipped,
                            } catch (e) {
              hasSubWeaponData: !!subWeaponData,
                                console.error('[Subskills] Erro ao serializar weapon de subskill', subName, e);
            });
                            }


                            // Aplica classe inicial se o toggle já está ativo
            // FASE 4: Usa resolveSkillView para resolver tudo de forma determinística
                            if (isWeaponModeOn()) {
            const raw = (document.documentElement.lang || "pt").toLowerCase();
                                item.classList.add('has-weapon-available');
            const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
                            }
                        }
                    }


                    // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
            // Prepara skill com weapon se necessário
                    logSubVideo('attaching click handler to subicon', {
            const skillForResolution = { ...currentSub };
                        subName: (s.name || s.n || '').trim(),
            if (weaponEquipped && subWeaponData) {
                        parentIdx,
              skillForResolution.weapon = subWeaponData;
                        itemClassName: item.className
            }
                    });
                    item.addEventListener('click', () => {
                        const L = getLabels();


                        // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
            // Resolve tudo usando o resolver único
                        const skillId = item.dataset.skillId || s.id || (s.name || s.n || '');
            const resolved = resolveSkillView(skillForResolution, {
                        if (!skillId) {
              lang,
                            console.error('[Subskills] Click handler: skillId não encontrado', { item, s });
              weaponMode: weaponEquipped,
                            return;
              mainSkills,
                        }
            });


                        // CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
            // Usa dados resolvidos
                        let currentSub = subsById.get(skillId);
            const chosen = resolved.desc;
                        let lookupMethod = 'id';
            const attrsObj = resolved.attrs;
            const level = resolved.level;


                        if (!currentSub) {
            let flagsHTML = "";
                            // Fallback: tenta por nome (compatibilidade com dados antigos)
            if (
                            const subName = (s.name || s.n || '').trim();
              Array.isArray(currentSub.flags) &&
                            currentSub = subs.find(x => (x.name || x.n || '') === subName);
              currentSub.flags.length > 0
                            lookupMethod = 'name_fallback';
            ) {
              flagsHTML = renderFlagsRow(currentSub.flags);
            }


                            if (!currentSub) {
            if (descBox) {
                                console.error('[Subskills] Click handler: subskill não encontrada', { skillId, subName, availableIds: Array.from(subsById.keys()) });
              descBox.innerHTML = `<div class="skill-title"><h3>${resolved.title || currentSub.name || currentSub.n || ""
                                return;
                }</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>`;
            }


                        const subName = (currentSub.name || currentSub.n || '').trim();
            if (videoBox) {
              const oldFlags = videoBox.querySelector(".skill-flags");
              if (oldFlags) oldFlags.remove();
              if (flagsHTML) {
                videoBox.insertAdjacentHTML("beforeend", flagsHTML);
                applyFlagTooltips(videoBox);
              }
            }


                        logSubVideo('subicon click HANDLER EXECUTED', {
            // FASE 4: Vídeo vem do resolved (já resolvido)
                            skillId,
            const effectiveVideo = resolved.video;
                            subName,
                            parentIdx,
                            weaponMode: isWeaponModeOn(),
                            hasWeaponDataAttr: !!item.dataset.weapon,
                            rawWeaponDataset: item.dataset.weapon || null
                        });


                        // Lê weapon diretamente do atributo data-weapon
            logSubVideo("effectiveVideo for sub", {
                        let subWeaponData = null;
              subName: currentSub.name || currentSub.n,
                        if (item.dataset.weapon) {
              skillId,
                            try {
              parentIdx,
                                const parsed = JSON.parse(item.dataset.weapon);
              effectiveVideo,
                                // Só considera weapon válido se for um objeto não vazio
              weaponEquipped,
                                if (parsed && typeof parsed === 'object' && Object.keys(parsed).length > 0) {
              rawBaseVideo: currentSub.video,
                                    subWeaponData = parsed;
              rawWeaponVideo: subWeaponData?.video,
                                }
            });
                            } catch (e) {
                                subWeaponData = null;
                            }
                        }
                        const hasSubWeapon = !!subWeaponData;


                        const weaponOn = isWeaponModeOn();
            if (!effectiveVideo || effectiveVideo.trim() === "") {
                        const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
              if (videoBox) videoBox.style.display = "none";
            } else {
              logSubVideo("calling showSubVideo from click handler", {
                effectiveVideo,
              });


                        logSubVideo('weapon status for subclick', {
              // Usa getOrCreateSubskillVideo internamente - nunca recria se já existe
                            subName,
              showSubVideo(effectiveVideo, videoBox);
                            weaponEquipped,
            }
                            hasSubWeaponData: !!subWeaponData
                        });


                        // FASE 4: Usa resolveSkillView para resolver tudo de forma determinística
            Array.from(subBar.children).forEach((c) => {
                        const raw = (document.documentElement.lang || 'pt').toLowerCase();
              c.classList.remove("active");
                        const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
              c.classList.remove("weapon-equipped");
            });
            item.classList.add("active");


                        // Prepara skill com weapon se necessário
            // Aplica weapon-equipped se tem weapon e está ativo
                        const skillForResolution = { ...currentSub };
            if (weaponEquipped) {
                        if (weaponEquipped && subWeaponData) {
              item.classList.add("weapon-equipped");
                            skillForResolution.weapon = subWeaponData;
            }
                        }
            window.__lastActiveSkillIcon = item;
          });


                        // Resolve tudo usando o resolver único
          if (window.__globalSkillTooltip) {
                        const resolved = resolveSkillView(skillForResolution, {
            const { show, hide, measureAndPos, lockUntil } =
                            lang,
              window.__globalSkillTooltip;
                            weaponMode: weaponEquipped,
            const label = item.title || "";
                            mainSkills
            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 dados resolvidos
          subBar.appendChild(item);
                        const chosen = resolved.desc;
          logSubVideo("subicon appended to subBar", {
                        const attrsObj = resolved.attrs;
            subName: (s.name || s.n || "").trim(),
                        const level = resolved.level;
            parentIdx,
            subBarChildrenCount: subBar.children.length,
          });
        });


                        let flagsHTML = '';
        // Aplica classes de weapon nas subskills recém-renderizadas
                        if (Array.isArray(currentSub.flags) && currentSub.flags.length > 0) {
        applyWeaponClassesToSubskills();
                            flagsHTML = renderFlagsRow(currentSub.flags);
                        }


                        if (descBox) {
        // Dispara evento para notificar que subskills estão prontas
                            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>`;
        window.dispatchEvent(
                        }
          new CustomEvent("gla:subskills:ready", {
            detail: { count: order.length },
          })
        );


                        if (videoBox) {
        // Remove listener anterior se existir (evita duplicação)
                            const oldFlags = videoBox.querySelector('.skill-flags');
        if (subBar._weaponToggleListener) {
                            if (oldFlags) oldFlags.remove();
          window.removeEventListener(
                            if (flagsHTML) {
            "gla:weaponToggled",
                                videoBox.insertAdjacentHTML('beforeend', flagsHTML);
            subBar._weaponToggleListener
                                applyFlagTooltips(videoBox);
          );
                            }
        }
                        }


                        // FASE 4: Vídeo vem do resolved (já resolvido)
        // Cria novo listener que opera no subBar atual
                        const effectiveVideo = resolved.video;
        subBar._weaponToggleListener = (e) => {
          const enabled = e.detail?.enabled ?? false;


                        logSubVideo('effectiveVideo for sub', {
          // Aplica classes usando a função auxiliar
                            subName: currentSub.name || currentSub.n,
          applyWeaponClassesToSubskills();
                            skillId,
                            parentIdx,
                            effectiveVideo,
                            weaponEquipped,
                            rawBaseVideo: currentSub.video,
                            rawWeaponVideo: subWeaponData?.video
                        });


                        if (!effectiveVideo || effectiveVideo.trim() === '' || effectiveVideo === 'Nada.png' || effectiveVideo.toLowerCase().includes('nada.png')) {
          // Atualiza a subskill ativa se houver
                            if (videoBox) videoBox.style.display = 'none';
          setTimeout(() => {
                        } else {
            const activeSub =
                            logSubVideo('calling showSubVideo from click handler', { effectiveVideo });
              subBar.querySelector(".subicon[data-weapon].active") ||
              subBar.querySelector(".subicon.active");
            if (activeSub) {
              activeSub.dispatchEvent(new Event("click", { bubbles: true }));
            } else {
              api.refreshCurrentSubSafe();
            }
          }, 50);
        };


                            // Usa getOrCreateSubskillVideo internamente - nunca recria se já existe
        window.addEventListener(
                            showSubVideo(effectiveVideo, videoBox);
          "gla:weaponToggled",
                        }
          subBar._weaponToggleListener
        );


                        Array.from(subBar.children).forEach(c => {
        requestAnimationFrame(() => {
                            c.classList.remove('active');
          subRail.classList.remove("collapsed");
                            c.classList.remove('weapon-equipped');
          subRail.classList.remove("hidden");
                        });
          const h = subRail.offsetHeight || 48;
                        item.classList.add('active');
          if (spacer) spacer.style.height = h + "px";
        });
      };


                        // Aplica weapon-equipped se tem weapon e está ativo
      // Chama a função de renderização após pré-carregar ícones
                        if (weaponEquipped) {
      Promise.all(iconPreloadPromises)
                            item.classList.add('weapon-equipped');
        .then(() => {
                        }
          setTimeout(() => {
                        window.__lastActiveSkillIcon = item;
            renderSubskillsBar();
                    });
          }, 10);
        })
        .catch(() => {
          setTimeout(() => {
            renderSubskillsBar();
          }, 10);
        });
    };


                    if (window.__globalSkillTooltip) {
    api.hideAll = function (videoBox) {
                        const { show, hide, measureAndPos, lockUntil } = window.__globalSkillTooltip;
      const videos =
                        const label = item.title || '';
        videoBox?.querySelectorAll('.skill-video[data-sub="1"]') || [];
                        item.setAttribute('aria-label', label);
      logSubVideo("api.hideAll called", {
                        if (item.hasAttribute('title')) item.removeAttribute('title');
        videoBoxExists: !!videoBox,
                        item.addEventListener('mouseenter', () => show(item, label));
        videoCount: videos.length,
                        item.addEventListener('mousemove', () => { if (performance.now() >= lockUntil.value) measureAndPos(item); });
      });
                        item.addEventListener('click', () => { lockUntil.value = performance.now() + 240; measureAndPos(item); });
      videos.forEach((v) => {
                        item.addEventListener('mouseleave', hide);
        try {
                    }
          v.pause();
        } catch { }
        v.style.display = "none";
      });
    };


                    subBar.appendChild(item);
    window.renderSubskillsBarFrom = function (el, ctx) {
                    logSubVideo('subicon appended to subBar', {
      api.renderBarFrom(el, ctx);
                        subName: (s.name || s.n || '').trim(),
    };
                        parentIdx,
                        subBarChildrenCount: subBar.children.length
                    });
                });


                // Aplica classes de weapon nas subskills recém-renderizadas
    api.preloadAllSubskillImages = function () {
                applyWeaponClassesToSubskills();
      const allSkillIcons = document.querySelectorAll(
        ".icon-bar .skill-icon[data-subs]"
      );
      const preloadPromises = [];
      let totalImages = 0;


                // Dispara evento para notificar que subskills estão prontas
      allSkillIcons.forEach((icon) => {
                window.dispatchEvent(new CustomEvent('gla:subskills:ready', { detail: { count: order.length } }));
        try {
          const subsRaw = icon.getAttribute("data-subs");
          if (!subsRaw) return;
          const subs = JSON.parse(subsRaw);
          if (!Array.isArray(subs)) return;


                // Remove listener anterior se existir (evita duplicação)
          subs.forEach((s) => {
                 if (subBar._weaponToggleListener) {
            if (s && s.icon && s.icon.trim() !== "") {
                    window.removeEventListener('gla:weaponToggled', subBar._weaponToggleListener);
              preloadPromises.push(preloadImage(s.icon));
              totalImages++;
            }
            if (s && Array.isArray(s.subs)) {
              s.subs.forEach((nested) => {
                 if (nested && nested.icon && nested.icon.trim() !== "") {
                  preloadPromises.push(preloadImage(nested.icon));
                  totalImages++;
                 }
                 }
              });
            }
          });
        } catch (e) {
          console.error("[Subskills] preloadAllSubskillImages error:", e);
        }
      });


                // Cria novo listener que opera no subBar atual
      if (totalImages > 0) {
                subBar._weaponToggleListener = (e) => {
        // console.log('[Subskills] preloadAllSubskillImages: pré-carregando', totalImages, 'ícones');
                    const enabled = e.detail?.enabled ?? false;
        return Promise.all(preloadPromises).then(() => {
          // console.log('[Subskills] preloadAllSubskillImages: todos os ícones carregados');
        });
      }
      return Promise.resolve();
    };


                    // Aplica classes usando a função auxiliar
    // Função para pré-carregar vídeos de subskills usando getOrCreateSubskillVideo
                    applyWeaponClassesToSubskills();
    // Função preloadAllSubskillVideos removida - vídeos são carregados sob demanda


                    // Atualiza a subskill ativa se houver
    // Função syncVideoCaches removida - não é mais necessária com cache baseado em URL
                    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);
    // Inicialização
    function init() {
      logSubVideo("init: starting initialization");


                requestAnimationFrame(() => {
      // Constrói cache das skills principais
                    subRail.classList.remove('collapsed');
      getMainSkillsMap();
                    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
      // Pré-carrega imagens das subskills IMEDIATAMENTE
            Promise.all(iconPreloadPromises).then(() => {
      api.preloadAllSubskillImages();
                setTimeout(() => {
                    renderSubskillsBar();
                }, 10);
            }).catch(() => {
                setTimeout(() => {
                    renderSubskillsBar();
                }, 10);
            });
        };


         api.hideAll = function (videoBox) {
      //         if (video.readyState < 2) {
             const videos = videoBox?.querySelectorAll('.skill-video[data-sub="1"]') || [];
      //             video.muted = true;
             logSubVideo('api.hideAll called', { videoBoxExists: !!videoBox, videoCount: videos.length });
      //             video.load();
            videos.forEach(v => {
      //        }
                try { v.pause(); } catch { }
      //    });
                v.style.display = 'none';
      // }, 500);
            });
        };


        window.renderSubskillsBarFrom = function (el, ctx) { api.renderBarFrom(el, ctx); };
      // Escuta mudanças no localStorage
      window.addEventListener("storage", (e) => {
        if (e.key === "glaWeaponEnabled") {
          setTimeout(() => api.refreshCurrentSubSafe(), 50);
        }
      });


        api.preloadAllSubskillImages = function () {
      // LISTENER GLOBAL: Escuta evento de toggle
            const allSkillIcons = document.querySelectorAll('.icon-bar .skill-icon[data-subs]');
      window.addEventListener("gla:weaponToggled", (e) => {
            const preloadPromises = [];
        const enabled = e.detail?.enabled ?? false;
            let totalImages = 0;


            allSkillIcons.forEach(icon => {
        // Tenta aplicar classes imediatamente
                try {
        applyWeaponClassesToSubskills();
                    const subsRaw = icon.getAttribute('data-subs');
                    if (!subsRaw) return;
                    const subs = JSON.parse(subsRaw);
                    if (!Array.isArray(subs)) return;


                    subs.forEach(s => {
        // Atualiza a subskill ativa se houver
                        if (s && s.icon && s.icon.trim() !== '' && s.icon !== 'Nada.png') {
        setTimeout(() => {
                            preloadPromises.push(preloadImage(s.icon));
          const activeSub =
                            totalImages++;
            document.querySelector(".subicon[data-weapon].active") ||
                        }
            document.querySelector(".subicon.active");
                        if (s && Array.isArray(s.subs)) {
          if (activeSub) {
                            s.subs.forEach(nested => {
            activeSub.dispatchEvent(new Event("click", { bubbles: true }));
                                if (nested && nested.icon && nested.icon.trim() !== '' && nested.icon !== 'Nada.png') {
          } else {
                                    preloadPromises.push(preloadImage(nested.icon));
            api.refreshCurrentSubSafe();
                                    totalImages++;
          }
                                }
        }, 50);
                            });
      });
                        }
                    });
                } catch (e) {
                    console.error('[Subskills] preloadAllSubskillImages error:', e);
                }
            });


            if (totalImages > 0) {
      // LISTENER: Escuta quando subskills estão prontas
                // console.log('[Subskills] preloadAllSubskillImages: pré-carregando', totalImages, 'ícones');
      window.addEventListener("gla:subskills:ready", (e) => {
                return Promise.all(preloadPromises).then(() => {
        // Aplica classes de weapon se o toggle estiver ativo
                    // console.log('[Subskills] preloadAllSubskillImages: todos os ícones carregados');
        applyWeaponClassesToSubskills();
                });
      });
            }
    }
            return Promise.resolve();
        };


         // Função para pré-carregar vídeos de subskills usando getOrCreateSubskillVideo
    if (document.readyState === "loading") {
        // Função preloadAllSubskillVideos removida - vídeos são carregados sob demanda
      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;
  }


        // Função syncVideoCaches removida - não é mais necessária com cache baseado em URL
  .subicon-bar::-webkit-scrollbar {
    height: 6px;
  }


        // Inicialização
  .subicon-bar::-webkit-scrollbar-thumb {
        function init() {
    background: #151515;
            logSubVideo('init: starting initialization');
    border-radius: 3px;
  }


            // Constrói cache das skills principais
  .subicon {
            getMainSkillsMap();
    width: var(--icon-size, 42px);
    height: var(--icon-size, 42px);
    border-radius: var(--icon-radius, 10px);
    overflow: hidden;
    position: relative;
    flex: 0 0 auto;
    cursor: pointer;
    isolation: isolate;
    filter: brightness(0.92);
  }


            // Pré-carrega imagens das subskills IMEDIATAMENTE
  .subicon img {
            api.preloadAllSubskillImages();
    width: 100%;
    height: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
    border-radius: inherit;
  }


            //        if (video.readyState < 2) {
  .subicon::after {
            //            video.muted = true;
    content: "";
            //            video.load();
    position: absolute;
            //        }
    inset: 0;
            //     });
    border-radius: inherit;
            // }, 500);
    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;
  }


            // Escuta mudanças no localStorage
  .subicon:hover::after {
            window.addEventListener('storage', (e) => {
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
                if (e.key === 'glaWeaponEnabled') {
  }
                    setTimeout(() => api.refreshCurrentSubSafe(), 50);
                }
            });


            // LISTENER GLOBAL: Escuta evento de toggle
  .subicon:hover {
            window.addEventListener('gla:weaponToggled', (e) => {
    filter: brightness(1);
                const enabled = e.detail?.enabled ?? false;
  }


                // Tenta aplicar classes imediatamente
  .subicon.active::after {
                applyWeaponClassesToSubskills();
    box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #ffd95a);
  }


                // Atualiza a subskill ativa se houver
  .subicon.active {
                setTimeout(() => {
    transform: scale(1.1);
                    const activeSub = document.querySelector('.subicon[data-weapon].active')
    z-index: 5;
                        || document.querySelector('.subicon.active');
    filter: brightness(1);
                    if (activeSub) {
  }
                        activeSub.dispatchEvent(new Event('click', { bubbles: true }));
                    } else {
                        api.refreshCurrentSubSafe();
                    }
                }, 50);
            });


            // LISTENER: Escuta quando subskills estão prontas
  .subicon.active::before {
            window.addEventListener('gla:subskills:ready', (e) => {
    content: "";
                // Aplica classes de weapon se o toggle estiver ativo
    position: absolute;
                applyWeaponClassesToSubskills();
    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));
  }


        if (document.readyState === 'loading') {
  .subicon.active img {
            document.addEventListener('DOMContentLoaded', () => {
     transform: none !important;
                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 {
  .top-rail.skills {
        height: 6px;
    position: relative;
    }
    display: flex;
    flex-direction: column;
    align-items: center;
    overflow: visible;
  }


    .subicon-bar::-webkit-scrollbar-thumb {
  .top-rail.skills .icon-bar {
        background: #151515;
    margin-bottom: 0;
        border-radius: 3px;
    position: relative;
    }
    z-index: 2;
  }


    .subicon {
  .subskills-rail {
        width: var(--icon-size, 42px);
    position: absolute;
        height: var(--icon-size, 42px);
    left: 50%;
        border-radius: var(--icon-radius, 10px);
    transform: translateX(-50%);
        overflow: hidden;
    top: calc(100% - 1px);
        position: relative;
    z-index: 3;
        flex: 0 0 auto;
    display: inline-flex;
        cursor: pointer;
    justify-content: center;
        isolation: isolate;
    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;
  }


    .subicon img {
  .subskills-rail::before {
        width: 100%;
    content: "";
        height: 100%;
    position: absolute;
        aspect-ratio: 1 / 1;
    top: -6px;
        object-fit: cover;
    left: 0;
        display: block;
    right: 0;
         border-radius: inherit;
    height: 6px;
    }
    background: linear-gradient(to bottom,
        rgba(0, 0, 0, 0.2),
         rgba(0, 0, 0, 0));
    pointer-events: none;
  }


    .subicon::after {
  .subskills-rail.collapsed {
        content: "";
    opacity: 0;
        position: absolute;
    pointer-events: none;
        inset: 0;
    transform: translate(-50%, -6px);
        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 .12s ease;
    }


    .subicon:hover::after {
  .subskills-rail.hidden {
        box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
    visibility: hidden;
    }
  }


    .subicon.active::after {
  .subskills-spacer {
        box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #FFD95A);
    height: 0;
    }
    transition: height 0.2s ease;
  }


    .subicon.active {
  .subskills-rail .subicon-bar {
        transform: scale(1.10);
    display: inline-flex;
        z-index: 5;
    align-items: center;
     }
    gap: 0;
    overflow-x: auto;
    /* Firefox */
    scrollbar-width: thin;
     scrollbar-color: #ababab transparent;
  }


    .subicon.active::before {
  .subskills-rail .subicon-bar::-webkit-scrollbar {
        content: "";
    height: 6px;
        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, .30)),
            0 0 0 calc(var(--icon-ring-w, 2px) * 2) var(--icon-active-ring, rgba(255, 217, 90, .50));
    }


    .subicon.active img {
  .subskills-rail .subicon-bar::-webkit-scrollbar-thumb {
        transform: none !important;
    background: #151515;
     }
     border-radius: 3px;
  }


    .top-rail.skills {
  .subskills-rail .subicon {
        position: relative;
    width: 42px;
        display: flex;
    height: 42px;
        flex-direction: column;
    border-radius: 6px;
        align-items: center;
    position: relative;
        overflow: visible;
    overflow: hidden;
    }
    flex: 0 0 auto;
    cursor: pointer;
    isolation: isolate;
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
    transform: translateZ(0);
  }


    .top-rail.skills .icon-bar {
  .subskills-rail .subicon+.subicon {
        margin-bottom: 0;
    margin-left: 4px;
        position: relative;
  }
        z-index: 2;
    }


    .subskills-rail {
  .subskills-rail .subicon img {
        position: absolute;
    width: 100%;
        left: 50%;
    height: 100%;
        transform: translateX(-50%);
    aspect-ratio: 1 / 1;
        top: calc(100% - 1px);
    object-fit: cover;
        z-index: 3;
    display: block;
        display: inline-flex;
    border-radius: inherit;
        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::after {
        content: "";
    content: "";
        position: absolute;
    position: absolute;
        top: -6px;
    inset: 0;
        left: 0;
    border-radius: inherit;
        right: 0;
    box-shadow: inset 0 0 0 2px var(--icon-idle, #cfcfcf);
        height: 6px;
    pointer-events: none;
        background: linear-gradient(to bottom, rgba(0, 0, 0, .20), rgba(0, 0, 0, 0));
     z-index: 2;
        pointer-events: none;
    transition: box-shadow 0.12s ease;
     }
  }


    .subskills-rail.collapsed {
  .subskills-rail .subicon:hover::after {
        opacity: 0;
    box-shadow: inset 0 0 0 2px #e6e6e6;
        pointer-events: none;
  }
        transform: translate(-50%, -6px);
    }


    .subskills-rail.hidden {
  .subskills-rail .subicon.active::after {
        visibility: hidden;
    box-shadow: inset 0 0 0 2px var(--icon-active, #ffd95a);
    }
  }


    .subskills-spacer {
  .video-container .skill-video {
        height: 0;
    width: 100%;
        transition: height .2s ease;
    height: auto;
     }
    aspect-ratio: 16 / 9;
    object-fit: cover;
    background: #000;
     border-radius: 10px;
  }


     .subskills-rail .subicon-bar {
  @media (max-width: 900px) {
        display: inline-flex;
     .subskills-rail {
        align-items: center;
      position: static;
        gap: 0;
      transform: none;
        overflow-x: auto;
      margin-top: -2px;
        /* Firefox */
      border-top: 0;
        scrollbar-width: thin;
      border-radius: 0 0 10px 10px;
        scrollbar-color: #ababab transparent;
     }
     }


     .subskills-rail .subicon-bar::-webkit-scrollbar {
     .subskills-spacer {
        height: 6px;
      height: 0 !important;
     }
     }
  }


    .subskills-rail .subicon-bar::-webkit-scrollbar-thumb {
  .skills-rail-wrap {
        background: #151515;
    position: relative;
        border-radius: 3px;
    display: block;
    }
    width: max-content;
    margin: 0 auto;
  }


    .subskills-rail .subicon {
  /* Subskills com arma disponível - borda vermelha quando inativa */
        width: 42px;
  .character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after {
        height: 42px;
    box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important;
        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 {
  /* Subskill com arma ATIVA - laranja/coral vibrante + brilho forte */
        margin-left: 4px;
  .character-box .top-rail.skills .subicon.has-weapon-available.active {
    }
    position: relative;
  }


    .subskills-rail .subicon img {
  .character-box .top-rail.skills .subicon.has-weapon-available.active::after {
        width: 100%;
    box-shadow: none !important;
        height: 100%;
     background: linear-gradient(135deg,
        aspect-ratio: 1 / 1;
         #ff5722 0%,
        object-fit: cover;
         #ff7043 20%,
        display: block;
         #ff8a65 40%,
        border-radius: inherit;
         #ff7043 60%,
     }
         #ff5722 80%,
 
        #ff3d00 100%) !important;
    .subskills-rail .subicon::after {
    background-size: 300% 300% !important;
         content: "";
    animation: weapon-icon-border-scan 3s linear infinite !important;
         position: absolute;
     -webkit-mask: linear-gradient(#fff 0 0) content-box,
         inset: 0;
      linear-gradient(#fff 0 0) !important;
         border-radius: inherit;
     mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
         box-shadow: inset 0 0 0 2px var(--icon-idle, #cfcfcf);
     -webkit-mask-composite: xor !important;
        pointer-events: none;
    mask-composite: exclude !important;
        z-index: 2;
    padding: 2px !important;
        transition: box-shadow .12s ease;
     filter: drop-shadow(0 0 4px rgba(255, 87, 34, 0.8)) drop-shadow(0 0 8px rgba(255, 87, 34, 0.6)) !important;
     }
  }
 
    .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 {
  .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 atual tal como às 21h12min de 24 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 !== ""
           ? sub.icon
           : shouldInheritField("icon")
             ? main.icon || ""
             : "",
       level: hasValue(sub.level)
         ? sub.level
         : shouldInheritField("level")
           ? main.level
           : "",
       video: finalVideo, // Nunca herda
       powerpve:
         sub.powerpve !== undefined && sub.powerpve !== null
           ? sub.powerpve
           : shouldInheritField("powerpve")
             ? main.powerpve
             : undefined,
       powerpvp:
         sub.powerpvp !== undefined && sub.powerpvp !== null
           ? sub.powerpvp
           : shouldInheritField("powerpvp")
             ? main.powerpvp
             : undefined,
       cooldown:
         sub.cooldown !== undefined && sub.cooldown !== null
           ? sub.cooldown
           : shouldInheritField("cooldown")
             ? main.cooldown
             : undefined,
       energy:
         sub.energy !== undefined && sub.energy !== null
           ? sub.energy
           : shouldInheritField("energy")
             ? main.energy
             : undefined,
       // Descrição: sempre vem da subskill, nunca herda
       // PROTEÇÃO TOTAL: NUNCA copia descrição do main, mesmo que subskill não tenha
       descPt: sub.desc_i18n?.pt || sub.descPt || undefined,
       descEn: sub.desc_i18n?.en || sub.descEn || undefined,
       descEs: sub.desc_i18n?.es || sub.descEs || undefined,
       descPl: sub.desc_i18n?.pl || sub.descPl || undefined,
       desc_i18n: sub.desc_i18n || null,
       // GARANTIA: Remove qualquer campo legado que possa ter sido copiado
       desc: undefined,
       flags:
         sub.flags || (shouldInheritField("flags") ? main.flags : undefined),
       weapon:
         sub.weapon ||
         (shouldInheritField("weapon") ? main.weapon : undefined),
       back:
         sub.back !== undefined
           ? sub.back
           : shouldInheritField("back")
             ? main.back
             : undefined,
       // Preserva campos de herança
       inherit_from: inheritFrom,
       inherit_fields: inheritFields,
     };
   }
   function filePathURL(fileName) {
     // Evita requisições para valores vazios
     if (
       !fileName ||
       fileName.trim() === ""
     ) {
       return "";
     }
     const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
     const base =
       window.mw && mw.util && typeof mw.util.wikiScript === "function"
         ? mw.util.wikiScript()
         : window.mw && mw.config
           ? mw.config.get("wgScript") || "/index.php"
           : "/index.php";
     // Garante HTTPS para evitar Mixed Content
     let url = `${base}?title=Especial:FilePath/${f}`;
     if (window.location.protocol === "https:" && url.startsWith("http://")) {
       url = url.replace("http://", "https://");
     }
     return url;
   }
   function normalizeFileURL(raw, fallback = "") {
     if (!raw) return fallback;
     const val = String(raw).trim();
     if (!val) return fallback;
     if (
       /^(https?:)?\/\//i.test(val) ||
       val.startsWith("data:") ||
       val.includes("Especial:FilePath/")
     ) {
       return val;
     }
     return filePathURL(val);
   }
   function preloadImage(iconFile) {
     const url = filePathURL(iconFile || "");
     if (imagePreloadCache.has(url)) {
       return imagePreloadCache.get(url);
     }
     const promise = new Promise((resolve, reject) => {
       const img = new Image();
       img.decoding = "async";
       img.loading = "eager";
       img.referrerPolicy = "same-origin";
       img.onload = () => resolve(url);
       img.onerror = () => resolve(url);
       img.src = url;
     });
     imagePreloadCache.set(url, promise);
     return promise;
   }
   function getLabels() {
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const raw = (
       document.documentElement.lang ||
       skillsRoot?.dataset.i18nDefault ||
       "pt"
     ).toLowerCase();
     const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
     return (
       i18nMap[lang] ||
       i18nMap.pt || {
         cooldown: "Recarga",
         energy_gain: "Ganho de energia",
         energy_cost: "Custo de energia",
         power: "Poder",
         power_pvp: "Poder PvP",
         level: "Nível",
       }
     );
   }
   // Verifica se o modo weapon está ativo
   function isWeaponModeOn() {
     try {
       return localStorage.getItem("glaWeaponEnabled") === "1";
     } catch (e) {
       return false;
     }
   }
   // Retorna os atributos corretos (weapon ou normal)
   function getEffectiveAttrs(s) {
     const weaponOn = isWeaponModeOn();
     if (weaponOn && s.weapon) {
       return {
         powerpve: s.weapon.powerpve || s.powerpve,
         powerpvp: s.weapon.powerpvp || s.powerpvp,
         energy: s.weapon.energy || s.energy,
         cooldown: s.weapon.cooldown || s.cooldown,
       };
     }
     return {
       powerpve: s.powerpve,
       powerpvp: s.powerpvp,
       energy: s.energy,
       cooldown: s.cooldown,
     };
   }
   // Retorna a descrição correta (weapon ou normal)
   // Aceita tanto desc_i18n quanto desc para compatibilidade
   function getEffectiveDesc(s) {
     const weaponOn = isWeaponModeOn();
     const raw = (document.documentElement.lang || "pt").toLowerCase();
     const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
     // Para weapon: aceita tanto desc_i18n quanto desc
     if (weaponOn && s.weapon) {
       const wDesc = s.weapon.desc_i18n || s.weapon.desc;
       if (wDesc) {
         return wDesc[lang] || wDesc.pt || wDesc.en || "";
       }
     }
     // Descrição: sempre vem da skill/subskill, nunca herda
     const base = s.desc_i18n || {};
     if (base && Object.keys(base).length > 0) {
       return base[lang] || base.pt || base.en || "";
     }
     // Fallback para campos individuais
     const descI18n = {
       pt: s.descPt || "",
       en: s.descEn || "",
       es: s.descEs || "",
       pl: s.descPl || "",
     };
     return descI18n[lang] || descI18n.pt || descI18n.en || "";
   }
   // Retorna o vídeo correto (weapon ou normal)
   function getEffectiveVideo(s) {
     const weaponOn = isWeaponModeOn();
     if (
       weaponOn &&
       s.weapon &&
       s.weapon.video &&
       s.weapon.video.trim() !== ""
     ) {
       return s.weapon.video;
     }
     return s.video || "";
   }
   // Função única para obtenção de vídeo de subskill - baseada em URL
   function getOrCreateSubskillVideo(videoURL) {
     if (!videoURL || videoURL.trim() === "") return null;
     const normalizedURL = normalizeFileURL(videoURL);
     if (!normalizedURL || normalizedURL.trim() === "") return null;
     // 1. Tenta cache local primeiro (compatibilidade)
     if (subskillVideoElementCache.has(normalizedURL)) {
       logSubVideo("getOrCreateSubskillVideo: local cache hit", {
         videoURL: normalizedURL,
       });
       return subskillVideoElementCache.get(normalizedURL);
     }
     // 2. Tenta cache global de subskills (window.__subskillVideosCache)
     const globalCache = window.__subskillVideosCache;
     if (globalCache && globalCache instanceof Map) {
       // O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
       // Precisamos buscar por URL normalizada
       for (const [key, video] of globalCache.entries()) {
         const src = video.querySelector("source");
         if (src && src.src === normalizedURL) {
           logSubVideo("getOrCreateSubskillVideo: global cache hit", {
             videoURL: normalizedURL,
             key,
           });
           // Adiciona ao cache local também para acesso rápido
           subskillVideoElementCache.set(normalizedURL, video);
           return video;
         }
       }
     }
     // 3. REMOVIDO: criação lazy - todos os vídeos devem estar pré-carregados
     // Se vídeo não foi encontrado, é um erro (não deve criar novo)
     console.warn("[Subskills] Vídeo não encontrado no cache:", {
       videoURL: normalizedURL,
       originalURL: videoURL,
     });
     return null;
   }
   function renderSubAttrs(s, L) {
     const chip = (label, val) =>
       val

 ? `

${label}${val}

`

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

return rows.length ? `

${rows.join("")}

` : "";

   }
   function renderFlagsRow(flags) {
     const map = {
       aggro: "Enemyaggro-icon.png",
       bridge: "Bridgemaker-icon.png",
       wall: "Destroywall-icon.png",
       quickcast: "Quickcast-icon.png",
       wallpass: "Passthroughwall-icon.png",
     };
     const arr = (flags || []).filter(Boolean);
     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] || "",
             powerpve: parts[1] || null,
             powerpvp: parts[2] || null,
             cooldown: parts[3] || null,
             video: parts[4] || "",
             energy: parts[5] || null,
           };
           // Remove valores vazios
           Object.keys(sub.weapon).forEach((k) => {
             if (sub.weapon[k] === "" || sub.weapon[k] === null) {
               delete sub.weapon[k];
             }
           });
         }
       }
       // Garante que weapon seja objeto válido
       if (sub.weapon && typeof sub.weapon === "string") {
         // Tenta parsear como JSON primeiro
         try {
           sub.weapon = JSON.parse(sub.weapon);
         } catch {
           // Se falhar, tenta formato ~
           const parts = sub.weapon.split("~");
           if (parts.length >= 2) {
             sub.weapon = {
               icon: parts[0] || "",
               powerpve: parts[1] || null,
               powerpvp: parts[2] || null,
               cooldown: parts[3] || null,
               video: parts[4] || "",
               energy: parts[5] || null,
             };
             Object.keys(sub.weapon).forEach((k) => {
               if (sub.weapon[k] === "" || sub.weapon[k] === null) {
                 delete sub.weapon[k];
               }
             });
           } else {
             sub.weapon = null;
           }
         }
       }
       return sub;
     });
     if (!Array.isArray(subs) || subs.length === 0) {
       subRail.classList.add("collapsed");
       subRail.classList.add("hidden");
       subBar.innerHTML = "";
       if (spacer) spacer.style.height = "0px";
       return;
     }
     // Busca mapa das skills principais para herança
     const mainSkills = getMainSkillsMap();
     // Debug: log dos dados antes da herança
     if (SUBVIDEO_DEBUG && subs.length > 0) {
       logSubVideo("subs before inheritance", {
         subsCount: subs.length,
         firstSub: JSON.stringify(subs[0]),
         allSubs: subs.map((s) => ({
           refM: s.refM || s.M || s.m,
           video: s.video,
           hasVideo: s.hasOwnProperty("video"),
         })),
       });
     }
     // Aplica herança ANTES de processar
     subs = subs.map((sub) => applyInheritance(sub, mainSkills));
     // Remove subskills que ficaram sem nome após herança
     subs = subs.filter((s) => (s.name || s.n || "").trim() !== "");
     subRail.classList.add("hidden");
     subBar.innerHTML = "";
     // Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
     const subsById = new Map();
     subs.forEach((s) => {
       const id = s.id || s.name || s.n || "";
       if (id) {
         subsById.set(id, s);
       }
     });
     // Usa a ordem natural das subskills após herança
     let order = subs.map((s) => s.id || s.name || s.n || "");
     if (rawOrder.trim()) {
       try {
         const preferred = JSON.parse(rawOrder);
         if (Array.isArray(preferred) && preferred.length) {
           // Tenta por ID primeiro, depois por nome (compatibilidade)
           const byName = new Map(subs.map((s) => [s.name || s.n || "", s]));
           const byId = new Map(
             subs.map((s) => [s.id || s.name || s.n || "", s])
           );
           order = preferred
             .filter((n) => {
               // Tenta encontrar por nome (compatibilidade com dados antigos)
               if (byName.has(n)) {
                 const found = byName.get(n);
                 return found.id || found.name || found.n || "";
               }
               // Tenta por ID
               return byId.has(n);
             })
             .map((n) => {
               // Retorna o ID se existir, senão o nome
               const byName = new Map(
                 subs.map((s) => [s.name || s.n || "", s])
               );
               if (byName.has(n)) {
                 const found = byName.get(n);
                 return found.id || found.name || found.n || "";
               }
               return n;
             });
         }
       } catch { }
     }
     // Pré-carrega TODOS os ícones ANTES de renderizar
     const iconPreloadPromises = [];
     order.forEach((idOrName) => {
       const s =
         subsById.get(idOrName) ||
         subs.find((x) => (x.name || x.n || "") === idOrName);
       if (s && s.icon && s.icon.trim() !== "") {
         iconPreloadPromises.push(preloadImage(s.icon));
       }
     });
     // Vídeos serão criados e carregados sob demanda quando necessário
     // Função para renderizar a barra de subskills
     const renderSubskillsBar = () => {
       order.forEach((idOrName) => {
         // CORREÇÃO: Usa ID para lookup (evita colisão de nomes)
         const s =
           subsById.get(idOrName) ||
           subs.find((x) => (x.name || x.n || "") === idOrName);
         if (!s) {
           return;
         }
         const item = document.createElement("div");
         item.className = "subicon";
         item.title = s.name || s.n || "";
         // CORREÇÃO: Adiciona data-skill-id para lookups únicos
         const skillId = s.id || s.name || s.n || "";
         if (skillId) {
           item.dataset.skillId = skillId;
         }
         const slugify =
           window.__skillSlugify ||
           ((str) =>
             (str || "")
               .toLowerCase()
               .replace(/[^\w]+/g, "-")
               .replace(/^-+|-+$/g, ""));
         item.dataset.slug = slugify(s.name || s.n || "");
         logSubVideo("creating subicon element", {
           subName: (s.name || s.n || "").trim(),
           parentIdx,
           className: item.className,
         });
         const img = document.createElement("img");
         img.alt = "";
         const iconUrl = filePathURL(s.icon || "");
         if (iconUrl) {
           img.src = iconUrl;
         }
         img.decoding = "async";
         img.loading = "lazy";
         img.width = 42;
         img.height = 42;
         item.appendChild(img);
         // Verifica weapon de forma mais robusta
         const hasWeapon =
           s.weapon &&
           ((typeof s.weapon === "object" &&
             Object.keys(s.weapon).length > 0) ||
             (typeof s.weapon === "string" && s.weapon.trim() !== ""));
         const subName = (s.name || s.n || "").trim();
         if (hasWeapon) {
           // Normaliza weapon se for string
           let weaponObj = s.weapon;
           if (typeof weaponObj === "string") {
             try {
               weaponObj = JSON.parse(weaponObj);
             } catch {
               // Se falhar, tenta formato ~
               const parts = weaponObj.split("~");
               if (parts.length >= 2) {
                 weaponObj = {
                   icon: parts[0] || "",
                   powerpve: parts[1] || null,
                   powerpvp: parts[2] || null,
                   cooldown: parts[3] || null,
                   video: parts[4] || "",
                   energy: parts[5] || null,
                 };
                 Object.keys(weaponObj).forEach((k) => {
                   if (weaponObj[k] === "" || weaponObj[k] === null) {
                     delete weaponObj[k];
                   }
                 });
               } else {
                 weaponObj = null;
               }
             }
           }
           if (
             weaponObj &&
             typeof weaponObj === "object" &&
             Object.keys(weaponObj).length > 0
           ) {
             try {
               item.dataset.weapon = JSON.stringify(weaponObj);
             } catch (e) {
               console.error(
                 "[Subskills] Erro ao serializar weapon de subskill",
                 subName,
                 e
               );
             }
             // Aplica classe inicial se o toggle já está ativo
             if (isWeaponModeOn()) {
               item.classList.add("has-weapon-available");
             }
           }
         }
         // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
         logSubVideo("attaching click handler to subicon", {
           subName: (s.name || s.n || "").trim(),
           parentIdx,
           itemClassName: item.className,
         });
         item.addEventListener("click", () => {
           const L = getLabels();
           // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
           const skillId = item.dataset.skillId || s.id || s.name || s.n || "";
           if (!skillId) {
             console.error(
               "[Subskills] Click handler: skillId não encontrado",
               { item, s }
             );
             return;
           }
           // CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
           let currentSub = subsById.get(skillId);
           let lookupMethod = "id";
           if (!currentSub) {
             // Fallback: tenta por nome (compatibilidade com dados antigos)
             const subName = (s.name || s.n || "").trim();
             currentSub = subs.find((x) => (x.name || x.n || "") === subName);
             lookupMethod = "name_fallback";
             if (!currentSub) {
               console.error(
                 "[Subskills] Click handler: subskill não encontrada",
                 {
                   skillId,
                   subName,
                   availableIds: Array.from(subsById.keys()),
                 }
               );
               return;
             }
           }
           const subName = (currentSub.name || currentSub.n || "").trim();
           logSubVideo("subicon click HANDLER EXECUTED", {
             skillId,
             subName,
             parentIdx,
             weaponMode: isWeaponModeOn(),
             hasWeaponDataAttr: !!item.dataset.weapon,
             rawWeaponDataset: item.dataset.weapon || null,
           });
           // Lê weapon diretamente do atributo data-weapon
           let subWeaponData = null;
           if (item.dataset.weapon) {
             try {
               const parsed = JSON.parse(item.dataset.weapon);
               // Só considera weapon válido se for um objeto não vazio
               if (
                 parsed &&
                 typeof parsed === "object" &&
                 Object.keys(parsed).length > 0
               ) {
                 subWeaponData = parsed;
               }
             } catch (e) {
               subWeaponData = null;
             }
           }
           const hasSubWeapon = !!subWeaponData;
           const weaponOn = isWeaponModeOn();
           const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
           logSubVideo("weapon status for subclick", {
             subName,
             weaponEquipped,
             hasSubWeaponData: !!subWeaponData,
           });
           // FASE 4: Usa resolveSkillView para resolver tudo de forma determinística
           const raw = (document.documentElement.lang || "pt").toLowerCase();
           const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
           // Prepara skill com weapon se necessário
           const skillForResolution = { ...currentSub };
           if (weaponEquipped && subWeaponData) {
             skillForResolution.weapon = subWeaponData;
           }
           // Resolve tudo usando o resolver único
           const resolved = resolveSkillView(skillForResolution, {
             lang,
             weaponMode: weaponEquipped,
             mainSkills,
           });
           // Usa dados resolvidos
           const chosen = resolved.desc;
           const attrsObj = resolved.attrs;
           const level = resolved.level;
           let flagsHTML = "";
           if (
             Array.isArray(currentSub.flags) &&
             currentSub.flags.length > 0
           ) {
             flagsHTML = renderFlagsRow(currentSub.flags);
           }
           if (descBox) {

descBox.innerHTML = `

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

${level  ? `

${L.level} ${level}

`

                 : ""

}${renderSubAttrs(attrsObj, L)}

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

`;

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

</script> <style>

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

</style>