Widget:C.Subskills

De Wiki Gla
Revisão de 22h01min de 23 de janeiro de 2026 por Gurren1 (discussão | contribs)
Ir para navegação Ir para pesquisar

<script>

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

 ? `

${label}${val}

`

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

return rows.length ? `

${rows.join("")}

` : "";

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

return `

${items}

`;

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

descBox.innerHTML = `

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

${level  ? `

${L.level} ${level}

`

                 : ""

}${renderSubAttrs(attrsObj, L)}

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

`;

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

</script> <style>

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

</style>