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

De Wiki Gla
Ir para navegação Ir para pesquisar
(Criou página com '<!-- SUBSKILLS SYSTEM - Suporte recursivo nativo --> <script> (function () { 'use strict'; const { $, $$, filePathURL, getLangKey, chooseDescFrom } = windo...')
 
m
 
(26 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
Linha 1: Linha 1:
<!-- SUBSKILLS SYSTEM - Suporte recursivo nativo -->
<!-- SUBSKILLS SYSTEM -->
<script>
<script>
    (function () {
  (function () {
        'use strict';
    const SUBVIDEO_DEBUG = false; // Desabilitado para melhor performance
        const { $, $$, filePathURL, getLangKey, chooseDescFrom } = window.__CBase || {};
    function logSubVideo(...args) {
      if (!SUBVIDEO_DEBUG) return;
      // console.log('[SubVideo]', ...args);
    }


        const api = (window.__subskills ||= {});
    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;


         function getLabels() {
    // Cache das skills principais (capturado na carga da página)
            const skillsRoot = document.getElementById('skills');
    let cachedMainSkills = null;
            const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {};
 
            const lang = getLangKey ? getLangKey() : 'pt';
    // ===== HERANÇA DE ATRIBUTOS: busca dados das skills principais =====
            return i18nMap[lang] || i18nMap.pt || {
    // FASE 5: Refatorado para usar byId como fonte primária
                cooldown: 'Recarga',
    function getMainSkillsMap() {
                energy_gain: 'Ganho de energia',
      // Retorna cache se já foi construído E tem dados
                energy_cost: 'Custo de energia',
      if (cachedMainSkills && cachedMainSkills.byId.size > 0) {
                power: 'Poder',
        return cachedMainSkills;
                power_pvp: 'Poder PvP',
      }
                level: 'Nível'
 
             };
      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
          ? `<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>` : "";
    }
 
    function renderFlagsRow(flags) {
      const map = {
        aggro: "Enemyaggro-icon.png",
        bridge: "Bridgemaker-icon.png",
        wall: "Destroywall-icon.png",
        quickcast: "Quickcast-icon.png",
        wallpass: "Passthroughwall-icon.png",
      };
      const arr = (flags || [])
        .filter(Boolean)
        .map((k) => String(k || "").trim().toLowerCase());
      const arrFiltered = arr.filter(Boolean);
      if (!arrFiltered.length) return "";
      const items = arrFiltered
        .map((k) => {
          const file = map[k];
          if (!file) return "";
          return `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(file)}">`;
        })
        .filter(Boolean)
        .join("");
      return items ? `<div class="skill-flags" role="group" aria-label="Características">${items}</div>` : "";
    }
 
    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
          }
         }
         }


         function renderSubAttrs(s, L) {
         if (weaponOn && hasValidWeapon) {
            const chip = (label, val) => (val ? `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${val}</span></div>` : '');
          el.classList.add("has-weapon-available");
            const pve = (s.powerpve || '').toString().trim();
          if (el.classList.contains("active")) {
             const pvp = (s.powerpvp || '').toString().trim();
             el.classList.add("weapon-equipped");
            const en = (s.energy || '').toString().trim();
          }
            const cd = (s.cooldown || '').toString().trim();
        } else {
            const rows = [
          el.classList.remove("has-weapon-available");
                cd ? chip(L.cooldown, cd) : '',
          el.classList.remove("weapon-equipped");
                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>` : '';
         }
         }
      });
    };
    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;
      }


         function renderFlagsRow(flags) {
      let subs;
             const FLAG_ICON_FILES = {
      try {
                aggro: 'Enemyaggro-icon.png',
         subs = JSON.parse(rawSubs);
                bridge: 'Bridgemaker-icon.png',
      } catch {
                wall: 'Destroywall-icon.png',
        subs = [];
                quickcast: 'Quickcast-icon.png',
      }
                wallpass: 'Passthroughwall-icon.png'
 
      // 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,
             };
             };
             const arr = (flags || []).filter(Boolean);
             // Remove valores vazios
             if (!arr.length) return '';
            Object.keys(sub.weapon).forEach((k) => {
            const items = arr.map(k => {
              if (sub.weapon[k] === "" || sub.weapon[k] === null) {
                 const url = filePathURL ? filePathURL(FLAG_ICON_FILES[k]) : '';
                delete sub.weapon[k];
                 return url ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">` : '';
              }
             }).join('');
            });
            return items ? `<div class="skill-flags" role="group" aria-label="Características">${items}</div>` : '';
          }
        }
        // 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);
         }
         }
      });


        // Função recursiva para processar subskills (suporte ilimitado)
      // Usa a ordem natural das subskills após herança
         function processSubskillsRecursive(subs) {
      let order = subs.map((s) => s.id || s.name || s.n || "");
            if (!Array.isArray(subs)) return [];
      if (rawOrder.trim()) {
             return subs.map(sub => {
         try {
                const processed = {
          const preferred = JSON.parse(rawOrder);
                    name: (sub.name || sub.n || '').trim(),
          if (Array.isArray(preferred) && preferred.length) {
                    icon: sub.icon || '',
            // Tenta por ID primeiro, depois por nome (compatibilidade)
                    level: sub.level || '',
             const byName = new Map(subs.map((s) => [s.name || s.n || "", s]));
                    powerpve: sub.powerpve,
            const byId = new Map(
                    powerpvp: sub.powerpvp,
              subs.map((s) => [s.id || s.name || s.n || "", s])
                    energy: sub.energy,
            );
                    cooldown: sub.cooldown,
            order = preferred
                    video: sub.video || '',
              .filter((n) => {
                    desc_i18n: sub.desc_i18n,
                // Tenta encontrar por nome (compatibilidade com dados antigos)
                    flags: sub.flags,
                if (byName.has(n)) {
                    weapon: sub.weapon,
                  const found = byName.get(n);
                    back: sub.back
                  return found.id || found.name || found.n || "";
                };
                }
                 // RECURSÃO: processa sub-subskills
                // Tenta por ID
                 if (Array.isArray(sub.subs) && sub.subs.length > 0) {
                return byId.has(n);
                    processed.subs = processSubskillsRecursive(sub.subs);
              })
              .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 processed;
                 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));
         }
         }
      });


        // Renderiza subskills recursivamente
      // Vídeos serão criados e carregados sob demanda quando necessário
        api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) {
            if (!el || !iconsBar) return;


            const rawSubs = el.getAttribute('data-subs') || '';
      // Função para renderizar a barra de subskills
            if (!rawSubs.trim()) return;
      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;
          }


             let subs;
          const item = document.createElement("div");
             try {
          item.className = "subicon";
                 subs = JSON.parse(rawSubs);
          item.title = s.name || s.n || "";
            } catch {
 
                 subs = [];
          // 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 (!Array.isArray(subs) || subs.length === 0) return;
             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");
              }
            }
          }


            // Processa recursivamente
          // CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
             const processedSubs = processSubskillsRecursive(subs);
          logSubVideo("attaching click handler to subicon", {
             subName: (s.name || s.n || "").trim(),
            parentIdx,
            itemClassName: item.className,
          });
          item.addEventListener("click", () => {
             const L = getLabels();
             const L = getLabels();


             // Renderiza cada subskill
             // Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
             processedSubs.forEach(s => {
             const skillId = item.dataset.skillId || s.id || s.name || s.n || "";
                const name = s.name || '';
            if (!skillId) {
                 const desc = chooseDescFrom ? chooseDescFrom(s) : '';
              console.error(
                const descHtml = desc.replace(/'''(.*?)'''/g, '<b>$1</b>');
                 "[Subskills] Click handler: skillId não encontrado",
                const attrsHTML = renderSubAttrs(s, L);
                { item, s }
                const flagsHTML = s.flags ? renderFlagsRow(s.flags) : '';
              );
              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";


                // Cria elemento de subskill
              if (!currentSub) {
                 const subEl = document.createElement('div');
                 console.error(
                subEl.className = 'skill-icon';
                  "[Subskills] Click handler: subskill não encontrada",
                subEl.dataset.nome = name;
                  {
                subEl.dataset.desc = desc;
                    skillId,
                subEl.dataset.descPt = (s.desc_i18n && s.desc_i18n.pt) || '';
                    subName,
                subEl.dataset.descEn = (s.desc_i18n && s.desc_i18n.en) || '';
                    availableIds: Array.from(subsById.keys()),
                subEl.dataset.descEs = (s.desc_i18n && s.desc_i18n.es) || '';
                  }
                subEl.dataset.descPl = (s.desc_i18n && s.desc_i18n.pl) || '';
                 );
                if (s.level) subEl.dataset.level = s.level;
                 return;
                 if (s.video) subEl.dataset.video = filePathURL ? filePathURL(s.video) : '';
              }
                 if (s.subs) subEl.dataset.subs = JSON.stringify(s.subs);
            }
                if (s.flags) subEl.dataset.flags = JSON.stringify(s.flags);
                if (s.weapon) subEl.dataset.weapon = JSON.stringify(s.weapon);


                const img = document.createElement('img');
            const subName = (currentSub.name || currentSub.n || "").trim();
                img.alt = '';
                img.src = (s.icon && s.icon !== 'Nada.png') ? (filePathURL ? filePathURL(s.icon) : '') : '';
                img.decoding = 'async';
                img.loading = 'lazy';
                subEl.appendChild(img);


                // Badge para indicar sub-subskills
            logSubVideo("subicon click HANDLER EXECUTED", {
                if (s.subs && Array.isArray(s.subs) && s.subs.length > 0) {
              skillId,
                    const badge = document.createElement('div');
              subName,
                    badge.className = 'sub-badge';
              parentIdx,
                    badge.textContent = '+';
              weaponMode: isWeaponModeOn(),
                    subEl.appendChild(badge);
              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);
            }


                subEl.addEventListener('click', () => {
            if (descBox) {
                    $$('.skill-icon', iconsBar).forEach(i => i.classList.remove('active'));
              descBox.innerHTML = `<div class="skill-title"><h3>${resolved.title || currentSub.name || currentSub.n || ""
                    subEl.classList.add('active');
                }</h3></div>${level
                  ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>`
                  : ""
                }${renderSubAttrs(attrsObj, L)}<div class="desc">${(
                  chosen || ""
                ).replace(/'''(.*?)'''/g, "<b>$1</b>")}</div>`;
            }


                    if (descBox) {
            if (videoBox) {
                        descBox.innerHTML = `<div class="skill-title"><h3>${name}</h3></div>${s.level ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${s.level}</span></div>` : ''}${attrsHTML}<div class="desc">${descHtml}</div>`;
              const oldFlags = videoBox.querySelector(".skill-flags");
                    }
              if (oldFlags) oldFlags.remove();
              if (flagsHTML) {
                videoBox.insertAdjacentHTML("beforeend", flagsHTML);
                applyFlagTooltips(videoBox);
              }
            }


                    if (videoBox) {
            // FASE 4: Vídeo vem do resolved (já resolvido)
                        const oldFlags = videoBox.querySelector('.skill-flags');
            const effectiveVideo = resolved.video;
                        if (oldFlags) oldFlags.remove();
                        if (flagsHTML) {
                            videoBox.insertAdjacentHTML('beforeend', flagsHTML);
                        }


                        if (s.video && s.video.trim() !== '') {
            logSubVideo("effectiveVideo for sub", {
                            const videoURL = filePathURL ? filePathURL(s.video) : '';
              subName: currentSub.name || currentSub.n,
                            if (videoURL) {
              skillId,
                                let video = videoBox.querySelector(`video[data-sub="${name}"]`);
              parentIdx,
                                if (!video) {
              effectiveVideo,
                                    video = document.createElement('video');
              weaponEquipped,
                                    video.className = 'skill-video';
              rawBaseVideo: currentSub.video,
                                    video.dataset.sub = name;
              rawWeaponVideo: subWeaponData?.video,
                                    video.setAttribute('controls', '');
            });
                                    video.setAttribute('preload', 'metadata');
                                    video.setAttribute('playsinline', '');
                                    video.style.width = '100%';
                                    video.style.height = 'auto';
                                    video.style.aspectRatio = '16/9';
                                    video.style.objectFit = 'contain';


                                    const ext = (videoURL.split('.').pop() || '').toLowerCase().split('?')[0];
            if (!effectiveVideo || effectiveVideo.trim() === "") {
                                    const mimeTypes = {
              if (videoBox) videoBox.style.display = "none";
                                        'mp4': 'video/mp4',
            } else {
                                        'm4v': 'video/mp4',
              logSubVideo("calling showSubVideo from click handler", {
                                        'webm': 'video/webm',
                effectiveVideo,
                                        'ogv': 'video/ogg',
              });
                                        'ogg': 'video/ogg',
                                        'mov': 'video/quicktime'
                                    };
                                    const mimeType = mimeTypes[ext] || 'video/mp4';
                                    const src = document.createElement('source');
                                    src.src = videoURL;
                                    src.type = mimeType;
                                    video.appendChild(src);
                                    videoBox.appendChild(video);
                                }


                                videoBox.querySelectorAll('video').forEach(v => v.style.display = 'none');
              // Usa getOrCreateSubskillVideo internamente - nunca recria se já existe
                                video.style.display = 'block';
              showSubVideo(effectiveVideo, videoBox);
                                videoBox.style.display = 'block';
            }
                                try {
 
                                    video.currentTime = 0;
            Array.from(subBar.children).forEach((c) => {
                                    video.play().catch(() => { });
              c.classList.remove("active");
                                } catch (e) { }
              c.classList.remove("weapon-equipped");
                            }
            });
                        } else {
            item.classList.add("active");
                            videoBox.style.display = 'none';
                        }
                    }


                    // RECURSÃO: se tem sub-subskills, renderiza
            // Aplica weapon-equipped se tem weapon e está ativo
                    if (s.subs && Array.isArray(s.subs) && s.subs.length > 0) {
            if (weaponEquipped) {
                        api.renderBarFrom(subEl, { iconsBar, descBox, videoBox });
              item.classList.add("weapon-equipped");
                    }
            }
                });
            window.__lastActiveSkillIcon = item;
          });


                iconsBar.appendChild(subEl);
          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);
         };
         };


         api.hideAll = function (videoBox) {
         window.addEventListener(
             if (videoBox) {
          "gla:weaponToggled",
                videoBox.querySelectorAll('video[data-sub]').forEach(v => {
          subBar._weaponToggleListener
                    v.style.display = 'none';
        );
                    try {
 
                        v.pause();
        requestAnimationFrame(() => {
                    } catch (e) { }
          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>
</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>

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

<script>

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

 ? `

${label}${val}

`

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

return rows.length ? `

${rows.join("")}

` : "";

   }
   function renderFlagsRow(flags) {
     const map = {
       aggro: "Enemyaggro-icon.png",
       bridge: "Bridgemaker-icon.png",
       wall: "Destroywall-icon.png",
       quickcast: "Quickcast-icon.png",
       wallpass: "Passthroughwall-icon.png",
     };
     const arr = (flags || [])
       .filter(Boolean)
       .map((k) => String(k || "").trim().toLowerCase());
     const arrFiltered = arr.filter(Boolean);
     if (!arrFiltered.length) return "";
     const items = arrFiltered
       .map((k) => {
         const file = map[k];
         if (!file) return "";
         return `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(file)}">`;
       })
       .filter(Boolean)
       .join("");

return items ? `

${items}

` : "";

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

descBox.innerHTML = `

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

${level  ? `

${L.level} ${level}

`

                 : ""

}${renderSubAttrs(attrsObj, L)}

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

`;

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

</script> <style>

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

</style>