Mudanças entre as edições de "Widget:C.Subskills"
Ir para navegação
Ir para pesquisar
m |
m (teste) |
||
| Linha 1 465: | Linha 1 465: | ||
cursor: pointer; | cursor: pointer; | ||
isolation: isolate; | isolation: isolate; | ||
filter: brightness(0.92); | |||
} | } | ||
| Linha 1 489: | Linha 1 490: | ||
.subicon:hover::after { | .subicon:hover::after { | ||
box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6; | box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6; | ||
} | |||
.subicon:hover { | |||
filter: brightness(1); | |||
} | } | ||
| Linha 1 498: | Linha 1 503: | ||
transform: scale(1.1); | transform: scale(1.1); | ||
z-index: 5; | z-index: 5; | ||
filter: brightness(1); | |||
} | } | ||
Edição atual tal como às 21h12min de 24 de janeiro de 2026
<script>
(function () {
const SUBVIDEO_DEBUG = false; // Desabilitado para melhor performance
function logSubVideo(...args) {
if (!SUBVIDEO_DEBUG) return;
// console.log('[SubVideo]', ...args);
}
const api = (window.__subskills ||= {});
// Cache global de elementos de vídeo para subskills - baseado em URL
const subskillVideoElementCache = new Map(); // key: videoURL (string), value: HTMLVideoElement
const imagePreloadCache = new Map();
let subRail, subBar, spacer;
// Cache das skills principais (capturado na carga da página) let cachedMainSkills = null;
// ===== HERANÇA DE ATRIBUTOS: busca dados das skills principais =====
// FASE 5: Refatorado para usar byId como fonte primária
function getMainSkillsMap() {
// Retorna cache se já foi construído E tem dados
if (cachedMainSkills && cachedMainSkills.byId.size > 0) {
return cachedMainSkills;
}
const maps = {
byId: new Map(), // FASE 5: Fonte primária por ID
byName: new Map(), // FASE 5: Mantido para compatibilidade (legado)
byIndex: new Map(), // Mantido para compatibilidade
};
// Busca skills com data-index (skills principais, não subskills)
const icons = document.querySelectorAll(
".icon-bar .skill-icon[data-index][data-nome]"
);
icons.forEach((icon) => {
const name = (icon.dataset.nome || "").trim();
if (!name) return;
// Extrai atributos do data-atr (formato: "pve, pvp, energy, cd")
const atrRaw = icon.dataset.atr || "";
const parts = atrRaw.split(",").map((x) => (x || "").trim());
const powerpve = parts[0] && parts[0] !== "-" ? parts[0] : "";
const powerpvp = parts[1] && parts[1] !== "-" ? parts[1] : "";
const energy = parts[2] && parts[2] !== "-" ? parts[2] : "";
const cooldown = parts[3] && parts[3] !== "-" ? parts[3] : "";
// Nome original do arquivo de ícone (armazenado no dataset pelo widget de skills)
let iconFile = (icon.dataset.iconFile || "").trim();
if (!iconFile) {
const imgSrc = icon.querySelector("img")?.src || "";
const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : "";
}
// Nome original do arquivo de vídeo (caso exista)
let videoFile = (icon.dataset.videoFile || "").trim();
if (!videoFile) {
const videoUrl = icon.dataset.video || "";
const videoMatch = videoUrl.match(/FilePath\/([^&?]+)/);
videoFile = videoMatch ? decodeURIComponent(videoMatch[1]) : "";
}
const index = (icon.dataset.index || "").trim();
// FASE 5: Extrai ID do data-skill-id ou gera a partir do nome
const skillId = icon.dataset.skillId || icon.dataset.id || name;
const data = {
id: skillId, // FASE 5: ID único
name: name, // Mantido para compatibilidade
icon: iconFile,
level: icon.dataset.level || "",
video: videoFile,
powerpve: powerpve,
powerpvp: powerpvp,
cooldown: cooldown,
energy: energy,
};
// Mantém descrições caso precise como fallback extra
if (icon.dataset.descPt) data.descPt = icon.dataset.descPt;
if (icon.dataset.descEn) data.descEn = icon.dataset.descEn;
if (icon.dataset.descEs) data.descEs = icon.dataset.descEs;
if (icon.dataset.descPl) data.descPl = icon.dataset.descPl;
// FASE 5: Processa desc_i18n se existir
if (icon.dataset.descI18n) {
try {
data.desc_i18n = JSON.parse(icon.dataset.descI18n);
} catch (e) {
// Ignora erro de parse
}
}
// FASE 5: byId é a fonte primária
maps.byId.set(skillId, data);
maps.byName.set(name, data); // FASE 5: Mantido para compatibilidade (legado)
if (index) {
// Guarda tanto como string quanto como número para compatibilidade
maps.byIndex.set(index, data);
maps.byIndex.set(parseInt(index, 10), data);
}
});
// Cacheia para uso futuro (importante: as skills principais não mudam)
cachedMainSkills = maps;
return maps;
}
// FASE 4: Resolver único para toda resolução de skill/subskill
// Retorna tudo que a UI precisa renderizar de forma determinística
function resolveSkillView(skill, context) {
// context: { lang, weaponMode, mainSkills }
// Retorna: { title, desc, video, attrs, flags, weapon }
const lang = context.lang || "pt";
const weaponMode = context.weaponMode || false;
const mainSkills = context.mainSkills || null;
// 1. Título: sempre display_name
const title = skill.display_name || skill.name || skill.n || "";
// 2. Descrição: sempre vem da skill/subskill, nunca herda
let desc = "";
if (weaponMode && skill.weapon?.desc_i18n) {
desc =
skill.weapon.desc_i18n[lang] ||
skill.weapon.desc_i18n.pt ||
skill.weapon.desc_i18n.en ||
"";
} else {
desc =
skill.desc_i18n?.[lang] ||
skill.desc_i18n?.pt ||
skill.desc_i18n?.en ||
"";
if (!desc) {
desc =
skill.descPt || skill.descEn || skill.descEs || skill.descPl || "";
}
}
// 3. Vídeo: sempre da skill/subskill, nunca herdado
const video = skill.video || "";
// 4. Atributos: weapon mode ou normal
let attrs = {
powerpve: skill.powerpve,
powerpvp: skill.powerpvp,
energy: skill.energy,
cooldown: skill.cooldown,
};
if (weaponMode && skill.weapon) {
attrs = {
powerpve: skill.weapon.powerpve || skill.powerpve,
powerpvp: skill.weapon.powerpvp || skill.powerpvp,
energy: skill.weapon.energy || skill.energy,
cooldown: skill.weapon.cooldown || skill.cooldown,
};
}
// 5. Level: weapon ou normal
const level =
weaponMode && skill.weapon?.level
? skill.weapon.level.toString().trim()
: (skill.level || "").toString().trim();
return {
title,
desc,
video,
attrs,
flags: skill.flags,
weapon: skill.weapon,
level,
};
}
// applyInheritance: aplica herança de atributos (nunca herda descrição ou vídeo)
function applyInheritance(sub, mainSkills) {
// NOVO SISTEMA: herança explícita
// inherit_from: DE qual skill herdar (obrigatório para herdar)
// inherit_fields: O QUE herdar (array de campos, obrigatório para herdar)
// Se não especificar ambos, não herda nada (mais seguro)
const inheritFrom = sub.inherit_from_id || sub.inherit_from;
const inheritFields = sub.inherit_fields || [];
// Converte para set para busca rápida
const inheritFieldsSet = new Set(inheritFields);
// Busca skill principal
let main = null;
if (inheritFrom && mainSkills) {
// FASE 5: Tenta por ID primeiro (fonte primária)
if (mainSkills.byId && mainSkills.byId.has(inheritFrom)) {
main = mainSkills.byId.get(inheritFrom);
}
// Fallback: tenta por nome (compatibilidade legado)
if (!main && mainSkills.byName && mainSkills.byName.has(inheritFrom)) {
main = mainSkills.byName.get(inheritFrom);
}
// Tenta por índice (compatibilidade com sistema antigo refM)
if (!main && mainSkills.byIndex) {
const refIndex = ((sub.refM || sub.m || sub.M || "") + "").trim();
if (refIndex && mainSkills.byIndex.has(refIndex)) {
main = mainSkills.byIndex.get(refIndex);
}
if (!main && refIndex) {
const numIndex = parseInt(refIndex, 10);
if (!isNaN(numIndex) && mainSkills.byIndex.has(numIndex)) {
main = mainSkills.byIndex.get(numIndex);
}
}
}
}
// Se não tem inherit_from ou main, não herda nada
if (!inheritFrom || !main) {
return sub;
}
// Função auxiliar: verifica se campo DEVE ser herdado
const shouldInheritField = (fieldName) => {
return inheritFieldsSet.has(fieldName);
};
// Helper para verificar se um valor existe e não é string vazia
const hasValue = (val) => {
if (val === undefined || val === null) return false;
if (typeof val === "number") return !isNaN(val);
const str = String(val).trim();
return str !== "" && str !== "NaN";
};
// Vídeo NUNCA é herdado da skill principal
const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
const finalVideo = hasOwnVideo ? sub.video || "" : "";
return {
...sub,
name: sub.name || main.name,
// Herda apenas se campo estiver em inherit_fields
icon:
sub.icon && sub.icon !== ""
? sub.icon
: shouldInheritField("icon")
? main.icon || ""
: "",
level: hasValue(sub.level)
? sub.level
: shouldInheritField("level")
? main.level
: "",
video: finalVideo, // Nunca herda
powerpve:
sub.powerpve !== undefined && sub.powerpve !== null
? sub.powerpve
: shouldInheritField("powerpve")
? main.powerpve
: undefined,
powerpvp:
sub.powerpvp !== undefined && sub.powerpvp !== null
? sub.powerpvp
: shouldInheritField("powerpvp")
? main.powerpvp
: undefined,
cooldown:
sub.cooldown !== undefined && sub.cooldown !== null
? sub.cooldown
: shouldInheritField("cooldown")
? main.cooldown
: undefined,
energy:
sub.energy !== undefined && sub.energy !== null
? sub.energy
: shouldInheritField("energy")
? main.energy
: undefined,
// Descrição: sempre vem da subskill, nunca herda
// PROTEÇÃO TOTAL: NUNCA copia descrição do main, mesmo que subskill não tenha
descPt: sub.desc_i18n?.pt || sub.descPt || undefined,
descEn: sub.desc_i18n?.en || sub.descEn || undefined,
descEs: sub.desc_i18n?.es || sub.descEs || undefined,
descPl: sub.desc_i18n?.pl || sub.descPl || undefined,
desc_i18n: sub.desc_i18n || null,
// GARANTIA: Remove qualquer campo legado que possa ter sido copiado
desc: undefined,
flags:
sub.flags || (shouldInheritField("flags") ? main.flags : undefined),
weapon:
sub.weapon ||
(shouldInheritField("weapon") ? main.weapon : undefined),
back:
sub.back !== undefined
? sub.back
: shouldInheritField("back")
? main.back
: undefined,
// Preserva campos de herança
inherit_from: inheritFrom,
inherit_fields: inheritFields,
};
}
function filePathURL(fileName) {
// Evita requisições para valores vazios
if (
!fileName ||
fileName.trim() === ""
) {
return "";
}
const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
const base =
window.mw && mw.util && typeof mw.util.wikiScript === "function"
? mw.util.wikiScript()
: window.mw && mw.config
? mw.config.get("wgScript") || "/index.php"
: "/index.php";
// Garante HTTPS para evitar Mixed Content
let url = `${base}?title=Especial:FilePath/${f}`;
if (window.location.protocol === "https:" && url.startsWith("http://")) {
url = url.replace("http://", "https://");
}
return url;
}
function normalizeFileURL(raw, fallback = "") {
if (!raw) return fallback;
const val = String(raw).trim();
if (!val) return fallback;
if (
/^(https?:)?\/\//i.test(val) ||
val.startsWith("data:") ||
val.includes("Especial:FilePath/")
) {
return val;
}
return filePathURL(val);
}
function preloadImage(iconFile) {
const url = filePathURL(iconFile || "");
if (imagePreloadCache.has(url)) {
return imagePreloadCache.get(url);
}
const promise = new Promise((resolve, reject) => {
const img = new Image();
img.decoding = "async";
img.loading = "eager";
img.referrerPolicy = "same-origin";
img.onload = () => resolve(url);
img.onerror = () => resolve(url);
img.src = url;
});
imagePreloadCache.set(url, promise);
return promise;
}
function getLabels() {
const skillsRoot = document.getElementById("skills");
const i18nMap = skillsRoot
? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
: {};
const raw = (
document.documentElement.lang ||
skillsRoot?.dataset.i18nDefault ||
"pt"
).toLowerCase();
const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
return (
i18nMap[lang] ||
i18nMap.pt || {
cooldown: "Recarga",
energy_gain: "Ganho de energia",
energy_cost: "Custo de energia",
power: "Poder",
power_pvp: "Poder PvP",
level: "Nível",
}
);
}
// Verifica se o modo weapon está ativo
function isWeaponModeOn() {
try {
return localStorage.getItem("glaWeaponEnabled") === "1";
} catch (e) {
return false;
}
}
// Retorna os atributos corretos (weapon ou normal)
function getEffectiveAttrs(s) {
const weaponOn = isWeaponModeOn();
if (weaponOn && s.weapon) {
return {
powerpve: s.weapon.powerpve || s.powerpve,
powerpvp: s.weapon.powerpvp || s.powerpvp,
energy: s.weapon.energy || s.energy,
cooldown: s.weapon.cooldown || s.cooldown,
};
}
return {
powerpve: s.powerpve,
powerpvp: s.powerpvp,
energy: s.energy,
cooldown: s.cooldown,
};
}
// Retorna a descrição correta (weapon ou normal)
// Aceita tanto desc_i18n quanto desc para compatibilidade
function getEffectiveDesc(s) {
const weaponOn = isWeaponModeOn();
const raw = (document.documentElement.lang || "pt").toLowerCase();
const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
// Para weapon: aceita tanto desc_i18n quanto desc
if (weaponOn && s.weapon) {
const wDesc = s.weapon.desc_i18n || s.weapon.desc;
if (wDesc) {
return wDesc[lang] || wDesc.pt || wDesc.en || "";
}
}
// Descrição: sempre vem da skill/subskill, nunca herda
const base = s.desc_i18n || {};
if (base && Object.keys(base).length > 0) {
return base[lang] || base.pt || base.en || "";
}
// Fallback para campos individuais
const descI18n = {
pt: s.descPt || "",
en: s.descEn || "",
es: s.descEs || "",
pl: s.descPl || "",
};
return descI18n[lang] || descI18n.pt || descI18n.en || "";
}
// Retorna o vídeo correto (weapon ou normal)
function getEffectiveVideo(s) {
const weaponOn = isWeaponModeOn();
if (
weaponOn &&
s.weapon &&
s.weapon.video &&
s.weapon.video.trim() !== ""
) {
return s.weapon.video;
}
return s.video || "";
}
// Função única para obtenção de vídeo de subskill - baseada em URL
function getOrCreateSubskillVideo(videoURL) {
if (!videoURL || videoURL.trim() === "") return null;
const normalizedURL = normalizeFileURL(videoURL);
if (!normalizedURL || normalizedURL.trim() === "") return null;
// 1. Tenta cache local primeiro (compatibilidade)
if (subskillVideoElementCache.has(normalizedURL)) {
logSubVideo("getOrCreateSubskillVideo: local cache hit", {
videoURL: normalizedURL,
});
return subskillVideoElementCache.get(normalizedURL);
}
// 2. Tenta cache global de subskills (window.__subskillVideosCache)
const globalCache = window.__subskillVideosCache;
if (globalCache && globalCache instanceof Map) {
// O cache global usa chaves como "sub:parentIdx:subName" ou "sub:parentIdx:subName:weapon"
// Precisamos buscar por URL normalizada
for (const [key, video] of globalCache.entries()) {
const src = video.querySelector("source");
if (src && src.src === normalizedURL) {
logSubVideo("getOrCreateSubskillVideo: global cache hit", {
videoURL: normalizedURL,
key,
});
// Adiciona ao cache local também para acesso rápido
subskillVideoElementCache.set(normalizedURL, video);
return video;
}
}
}
// 3. REMOVIDO: criação lazy - todos os vídeos devem estar pré-carregados
// Se vídeo não foi encontrado, é um erro (não deve criar novo)
console.warn("[Subskills] Vídeo não encontrado no cache:", {
videoURL: normalizedURL,
originalURL: videoURL,
});
return null;
}
function renderSubAttrs(s, L) {
const chip = (label, val) =>
val
? `
${label}${val}
`
: "";
const pve = (s.powerpve || "").toString().trim();
const pvp = (s.powerpvp || "").toString().trim();
const en = (s.energy || "").toString().trim();
const cd = (s.cooldown || "").toString().trim();
const rows = [
cd ? chip(L.cooldown, cd) : "",
en
? chip(
en.startsWith("-") ? L.energy_cost : L.energy_gain,
en.startsWith("-") ? en.replace(/^-/, "") : en.replace(/^\+?/, "")
)
: "",
pve ? chip(L.power, pve) : "",
pvp ? chip(L.power_pvp, pvp) : "",
].filter(Boolean);
return rows.length ? `
${rows.join("")}
` : "";
}
function renderFlagsRow(flags) {
const map = {
aggro: "Enemyaggro-icon.png",
bridge: "Bridgemaker-icon.png",
wall: "Destroywall-icon.png",
quickcast: "Quickcast-icon.png",
wallpass: "Passthroughwall-icon.png",
};
const arr = (flags || []).filter(Boolean);
if (!arr.length) return "";
const items = arr
.map(
(k) =>
`<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(
map[k]
)}">`
)
.join("");
return `
${items}
`;
}
function applyFlagTooltips(container) {
const skillsRoot = document.getElementById("skills");
if (!skillsRoot) return;
let pack = {};
try {
pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
} catch (e) { }
const raw = (document.documentElement.lang || "pt").toLowerCase();
const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
const dict = pack[lang] || pack.pt || {};
const flags = container.querySelectorAll(
".skill-flags .skill-flag[data-flag]"
);
const tooltip = window.__globalSkillTooltip;
if (!tooltip) return;
flags.forEach((el) => {
const key = el.getAttribute("data-flag");
const tip = (dict && dict[key]) || "";
if (!tip) return;
if (el.dataset.flagTipWired) return;
el.dataset.flagTipWired = "1";
el.setAttribute("aria-label", tip);
if (el.hasAttribute("title")) el.removeAttribute("title");
el.addEventListener("mouseenter", () => {
const tipEl = document.querySelector(".skill-tooltip");
if (tipEl) tipEl.classList.add("flag-tooltip");
tooltip.show(el, tip);
});
el.addEventListener("mousemove", () => {
if (performance.now() >= tooltip.lockUntil.value) {
tooltip.measureAndPos(el);
}
});
el.addEventListener("click", () => {
tooltip.lockUntil.value = performance.now() + 240;
tooltip.measureAndPos(el);
});
el.addEventListener("mouseleave", () => {
const tipEl = document.querySelector(".skill-tooltip");
if (tipEl) tipEl.classList.remove("flag-tooltip");
tooltip.hide();
});
});
}
function ensureRail(iconsBar) {
const rail = iconsBar.closest(".top-rail");
if (!rail) {
return null;
}
if (!subRail) {
subRail = document.createElement("div");
subRail.className = "subskills-rail collapsed hidden";
rail.appendChild(subRail);
}
if (!subBar) {
subBar = document.createElement("div");
subBar.className = "subicon-bar";
subRail.appendChild(subBar);
}
if (!spacer) {
spacer = document.createElement("div");
spacer.className = "subskills-spacer";
rail.parentNode.insertBefore(spacer, rail.nextSibling);
}
return rail; }
// Função para mostrar vídeo de subskill usando cache baseado em URL
function showSubVideo(videoURL, videoBox) {
logSubVideo("showSubVideo called", {
videoURL,
videoBoxExists: !!videoBox,
});
if (!videoBox) return;
// Ocultar todos os vídeos existentes no container
videoBox
.querySelectorAll('video.skill-video[data-sub="1"]')
.forEach((v) => {
v.style.display = "none";
});
// Obter vídeo via getOrCreateSubskillVideo (única forma de obter vídeos)
const video = getOrCreateSubskillVideo(videoURL);
if (!video) {
logSubVideo("no video found for URL, hiding box", { videoURL });
videoBox.style.display = "none";
return;
}
// Se o vídeo não estiver conectado ao DOM, anexá-lo ao container
if (!video.isConnected || video.parentNode !== videoBox) {
logSubVideo("video not in DOM, appending", {
videoURL,
isConnected: video.isConnected,
parentNode: video.parentNode,
});
if (video.parentNode) {
video.parentNode.removeChild(video);
}
videoBox.appendChild(video);
}
logSubVideo("showing video", {
videoURL,
readyState: video.readyState,
paused: video.paused,
currentTime: video.currentTime,
});
// Exibir o vídeo
videoBox.style.display = "block";
video.style.display = "block";
// Reinicia o vídeo para feedback visual imediato
try {
video.currentTime = 0;
} catch (e) { }
// Tenta dar play se o vídeo já está pronto
if (video.readyState >= 2) {
video
.play()
.then(() => {
logSubVideo("play() resolved", {
videoURL,
currentTime: video.currentTime,
readyState: video.readyState,
});
})
.catch((err) => {
logSubVideo("play() rejected", { videoURL, error: err });
});
} else {
// Se não está pronto ainda, espera o evento canplay
logSubVideo("video not ready, waiting for canplay", {
videoURL,
readyState: video.readyState,
});
const onCanPlay = () => {
video.removeEventListener("canplay", onCanPlay);
video.play().catch(() => { });
};
video.addEventListener("canplay", onCanPlay, { once: true });
}
}
// Funções antigas removidas - usar getOrCreateSubskillVideo() em vez disso // REMOVIDO: ensureSubVideoCached // REMOVIDO: ensureSubVideoInCache
api.refreshCurrentSubSafe = function () {
const btn = document.querySelector(".subskills-rail .subicon.active");
if (!btn) return false;
const had = document.body.dataset.suppressSkillPlay;
document.body.dataset.suppressSkillPlay = "1";
try {
btn.dispatchEvent(new Event("click", { bubbles: true }));
} finally {
if (had) document.body.dataset.suppressSkillPlay = had;
else delete document.body.dataset.suppressSkillPlay;
}
return true;
};
// Função auxiliar para aplicar classes de weapon nas subskills renderizadas
const applyWeaponClassesToSubskills = () => {
const weaponOn = isWeaponModeOn();
// Busca TODAS as subskills com weapon
let weaponSubs = document.querySelectorAll(".subicon[data-weapon]");
weaponSubs.forEach((el) => {
const weaponData = el.getAttribute("data-weapon");
// Verifica se o weapon não está vazio (não é '{}' ou vazio)
let hasValidWeapon = false;
if (weaponData && weaponData.trim() !== "" && weaponData !== "{}") {
try {
const weaponObj = JSON.parse(weaponData);
if (
weaponObj &&
typeof weaponObj === "object" &&
Object.keys(weaponObj).length > 0
) {
hasValidWeapon = true;
}
} catch (e) {
// Se não for JSON válido, não considera como weapon válido
}
}
if (weaponOn && hasValidWeapon) {
el.classList.add("has-weapon-available");
if (el.classList.contains("active")) {
el.classList.add("weapon-equipped");
}
} else {
el.classList.remove("has-weapon-available");
el.classList.remove("weapon-equipped");
}
});
};
api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) {
logSubVideo("api.renderBarFrom called", {
hasEl: !!el,
hasIconsBar: !!iconsBar,
hasDescBox: !!descBox,
hasVideoBox: !!videoBox,
parentIndex: el?.dataset?.index || "unknown",
});
const rail = ensureRail(iconsBar);
if (!rail) {
logSubVideo("api.renderBarFrom: no rail found, returning");
return;
}
const rawSubs = el.getAttribute("data-subs") || "";
const rawOrder = el.getAttribute("data-suborder") || "";
const parentIdx = el.dataset.index || "";
logSubVideo("api.renderBarFrom: parsing subs", {
parentIdx,
hasRawSubs: !!rawSubs.trim(),
rawSubsLength: rawSubs.length,
});
if (!rawSubs.trim()) {
if (subRail) subRail.classList.add("collapsed");
if (subRail) subRail.classList.add("hidden");
if (subBar) subBar.innerHTML = "";
if (spacer) spacer.style.height = "0px";
return;
}
let subs;
try {
subs = JSON.parse(rawSubs);
} catch {
subs = [];
}
// NORMALIZADOR: Converte weaponPacked para weapon se necessário
subs = subs.map((sub) => {
// Se tem weaponPacked mas não tem weapon, tenta parsear
if (
sub.weaponPacked &&
!sub.weapon &&
typeof sub.weaponPacked === "string" &&
sub.weaponPacked.trim() !== ""
) {
const parts = sub.weaponPacked.split("~");
if (parts.length >= 2) {
sub.weapon = {
icon: parts[0] || "",
powerpve: parts[1] || null,
powerpvp: parts[2] || null,
cooldown: parts[3] || null,
video: parts[4] || "",
energy: parts[5] || null,
};
// Remove valores vazios
Object.keys(sub.weapon).forEach((k) => {
if (sub.weapon[k] === "" || sub.weapon[k] === null) {
delete sub.weapon[k];
}
});
}
}
// Garante que weapon seja objeto válido
if (sub.weapon && typeof sub.weapon === "string") {
// Tenta parsear como JSON primeiro
try {
sub.weapon = JSON.parse(sub.weapon);
} catch {
// Se falhar, tenta formato ~
const parts = sub.weapon.split("~");
if (parts.length >= 2) {
sub.weapon = {
icon: parts[0] || "",
powerpve: parts[1] || null,
powerpvp: parts[2] || null,
cooldown: parts[3] || null,
video: parts[4] || "",
energy: parts[5] || null,
};
Object.keys(sub.weapon).forEach((k) => {
if (sub.weapon[k] === "" || sub.weapon[k] === null) {
delete sub.weapon[k];
}
});
} else {
sub.weapon = null;
}
}
}
return sub;
});
if (!Array.isArray(subs) || subs.length === 0) {
subRail.classList.add("collapsed");
subRail.classList.add("hidden");
subBar.innerHTML = "";
if (spacer) spacer.style.height = "0px";
return;
}
// Busca mapa das skills principais para herança
const mainSkills = getMainSkillsMap();
// Debug: log dos dados antes da herança
if (SUBVIDEO_DEBUG && subs.length > 0) {
logSubVideo("subs before inheritance", {
subsCount: subs.length,
firstSub: JSON.stringify(subs[0]),
allSubs: subs.map((s) => ({
refM: s.refM || s.M || s.m,
video: s.video,
hasVideo: s.hasOwnProperty("video"),
})),
});
}
// Aplica herança ANTES de processar
subs = subs.map((sub) => applyInheritance(sub, mainSkills));
// Remove subskills que ficaram sem nome após herança
subs = subs.filter((s) => (s.name || s.n || "").trim() !== "");
subRail.classList.add("hidden");
subBar.innerHTML = "";
// Cria mapa de IDs para lookups rápidos (evita colisão de nomes)
const subsById = new Map();
subs.forEach((s) => {
const id = s.id || s.name || s.n || "";
if (id) {
subsById.set(id, s);
}
});
// Usa a ordem natural das subskills após herança
let order = subs.map((s) => s.id || s.name || s.n || "");
if (rawOrder.trim()) {
try {
const preferred = JSON.parse(rawOrder);
if (Array.isArray(preferred) && preferred.length) {
// Tenta por ID primeiro, depois por nome (compatibilidade)
const byName = new Map(subs.map((s) => [s.name || s.n || "", s]));
const byId = new Map(
subs.map((s) => [s.id || s.name || s.n || "", s])
);
order = preferred
.filter((n) => {
// Tenta encontrar por nome (compatibilidade com dados antigos)
if (byName.has(n)) {
const found = byName.get(n);
return found.id || found.name || found.n || "";
}
// Tenta por ID
return byId.has(n);
})
.map((n) => {
// Retorna o ID se existir, senão o nome
const byName = new Map(
subs.map((s) => [s.name || s.n || "", s])
);
if (byName.has(n)) {
const found = byName.get(n);
return found.id || found.name || found.n || "";
}
return n;
});
}
} catch { }
}
// Pré-carrega TODOS os ícones ANTES de renderizar
const iconPreloadPromises = [];
order.forEach((idOrName) => {
const s =
subsById.get(idOrName) ||
subs.find((x) => (x.name || x.n || "") === idOrName);
if (s && s.icon && s.icon.trim() !== "") {
iconPreloadPromises.push(preloadImage(s.icon));
}
});
// Vídeos serão criados e carregados sob demanda quando necessário
// Função para renderizar a barra de subskills
const renderSubskillsBar = () => {
order.forEach((idOrName) => {
// CORREÇÃO: Usa ID para lookup (evita colisão de nomes)
const s =
subsById.get(idOrName) ||
subs.find((x) => (x.name || x.n || "") === idOrName);
if (!s) {
return;
}
const item = document.createElement("div");
item.className = "subicon";
item.title = s.name || s.n || "";
// CORREÇÃO: Adiciona data-skill-id para lookups únicos
const skillId = s.id || s.name || s.n || "";
if (skillId) {
item.dataset.skillId = skillId;
}
const slugify =
window.__skillSlugify ||
((str) =>
(str || "")
.toLowerCase()
.replace(/[^\w]+/g, "-")
.replace(/^-+|-+$/g, ""));
item.dataset.slug = slugify(s.name || s.n || "");
logSubVideo("creating subicon element", {
subName: (s.name || s.n || "").trim(),
parentIdx,
className: item.className,
});
const img = document.createElement("img");
img.alt = "";
const iconUrl = filePathURL(s.icon || "");
if (iconUrl) {
img.src = iconUrl;
}
img.decoding = "async";
img.loading = "lazy";
img.width = 42;
img.height = 42;
item.appendChild(img);
// Verifica weapon de forma mais robusta
const hasWeapon =
s.weapon &&
((typeof s.weapon === "object" &&
Object.keys(s.weapon).length > 0) ||
(typeof s.weapon === "string" && s.weapon.trim() !== ""));
const subName = (s.name || s.n || "").trim();
if (hasWeapon) {
// Normaliza weapon se for string
let weaponObj = s.weapon;
if (typeof weaponObj === "string") {
try {
weaponObj = JSON.parse(weaponObj);
} catch {
// Se falhar, tenta formato ~
const parts = weaponObj.split("~");
if (parts.length >= 2) {
weaponObj = {
icon: parts[0] || "",
powerpve: parts[1] || null,
powerpvp: parts[2] || null,
cooldown: parts[3] || null,
video: parts[4] || "",
energy: parts[5] || null,
};
Object.keys(weaponObj).forEach((k) => {
if (weaponObj[k] === "" || weaponObj[k] === null) {
delete weaponObj[k];
}
});
} else {
weaponObj = null;
}
}
}
if (
weaponObj &&
typeof weaponObj === "object" &&
Object.keys(weaponObj).length > 0
) {
try {
item.dataset.weapon = JSON.stringify(weaponObj);
} catch (e) {
console.error(
"[Subskills] Erro ao serializar weapon de subskill",
subName,
e
);
}
// Aplica classe inicial se o toggle já está ativo
if (isWeaponModeOn()) {
item.classList.add("has-weapon-available");
}
}
}
// CORREÇÃO 4: Evento de clique otimizado para não recarregar vídeos
logSubVideo("attaching click handler to subicon", {
subName: (s.name || s.n || "").trim(),
parentIdx,
itemClassName: item.className,
});
item.addEventListener("click", () => {
const L = getLabels();
// Determina skillId e obtém currentSub OBRIGATORIAMENTE do subsById
const skillId = item.dataset.skillId || s.id || s.name || s.n || "";
if (!skillId) {
console.error(
"[Subskills] Click handler: skillId não encontrado",
{ item, s }
);
return;
}
// CORREÇÃO CRÍTICA: Obtém currentSub do mapa por ID (evita colisão de nomes)
let currentSub = subsById.get(skillId);
let lookupMethod = "id";
if (!currentSub) {
// Fallback: tenta por nome (compatibilidade com dados antigos)
const subName = (s.name || s.n || "").trim();
currentSub = subs.find((x) => (x.name || x.n || "") === subName);
lookupMethod = "name_fallback";
if (!currentSub) {
console.error(
"[Subskills] Click handler: subskill não encontrada",
{
skillId,
subName,
availableIds: Array.from(subsById.keys()),
}
);
return;
}
}
const subName = (currentSub.name || currentSub.n || "").trim();
logSubVideo("subicon click HANDLER EXECUTED", {
skillId,
subName,
parentIdx,
weaponMode: isWeaponModeOn(),
hasWeaponDataAttr: !!item.dataset.weapon,
rawWeaponDataset: item.dataset.weapon || null,
});
// Lê weapon diretamente do atributo data-weapon
let subWeaponData = null;
if (item.dataset.weapon) {
try {
const parsed = JSON.parse(item.dataset.weapon);
// Só considera weapon válido se for um objeto não vazio
if (
parsed &&
typeof parsed === "object" &&
Object.keys(parsed).length > 0
) {
subWeaponData = parsed;
}
} catch (e) {
subWeaponData = null;
}
}
const hasSubWeapon = !!subWeaponData;
const weaponOn = isWeaponModeOn();
const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
logSubVideo("weapon status for subclick", {
subName,
weaponEquipped,
hasSubWeaponData: !!subWeaponData,
});
// FASE 4: Usa resolveSkillView para resolver tudo de forma determinística
const raw = (document.documentElement.lang || "pt").toLowerCase();
const lang = raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
// Prepara skill com weapon se necessário
const skillForResolution = { ...currentSub };
if (weaponEquipped && subWeaponData) {
skillForResolution.weapon = subWeaponData;
}
// Resolve tudo usando o resolver único
const resolved = resolveSkillView(skillForResolution, {
lang,
weaponMode: weaponEquipped,
mainSkills,
});
// Usa dados resolvidos
const chosen = resolved.desc;
const attrsObj = resolved.attrs;
const level = resolved.level;
let flagsHTML = "";
if (
Array.isArray(currentSub.flags) &&
currentSub.flags.length > 0
) {
flagsHTML = renderFlagsRow(currentSub.flags);
}
if (descBox) {
descBox.innerHTML = `
${resolved.title || currentSub.name || currentSub.n || "" }
${level ? `
${L.level} ${level}
`
: ""
}${renderSubAttrs(attrsObj, L)}
${(
chosen || "").replace(/(.*?)/g, "$1")}
`;
}
if (videoBox) {
const oldFlags = videoBox.querySelector(".skill-flags");
if (oldFlags) oldFlags.remove();
if (flagsHTML) {
videoBox.insertAdjacentHTML("beforeend", flagsHTML);
applyFlagTooltips(videoBox);
}
}
// FASE 4: Vídeo vem do resolved (já resolvido)
const effectiveVideo = resolved.video;
logSubVideo("effectiveVideo for sub", {
subName: currentSub.name || currentSub.n,
skillId,
parentIdx,
effectiveVideo,
weaponEquipped,
rawBaseVideo: currentSub.video,
rawWeaponVideo: subWeaponData?.video,
});
if (!effectiveVideo || effectiveVideo.trim() === "") {
if (videoBox) videoBox.style.display = "none";
} else {
logSubVideo("calling showSubVideo from click handler", {
effectiveVideo,
});
// Usa getOrCreateSubskillVideo internamente - nunca recria se já existe
showSubVideo(effectiveVideo, videoBox);
}
Array.from(subBar.children).forEach((c) => {
c.classList.remove("active");
c.classList.remove("weapon-equipped");
});
item.classList.add("active");
// Aplica weapon-equipped se tem weapon e está ativo
if (weaponEquipped) {
item.classList.add("weapon-equipped");
}
window.__lastActiveSkillIcon = item;
});
if (window.__globalSkillTooltip) {
const { show, hide, measureAndPos, lockUntil } =
window.__globalSkillTooltip;
const label = item.title || "";
item.setAttribute("aria-label", label);
if (item.hasAttribute("title")) item.removeAttribute("title");
item.addEventListener("mouseenter", () => show(item, label));
item.addEventListener("mousemove", () => {
if (performance.now() >= lockUntil.value) measureAndPos(item);
});
item.addEventListener("click", () => {
lockUntil.value = performance.now() + 240;
measureAndPos(item);
});
item.addEventListener("mouseleave", hide);
}
subBar.appendChild(item);
logSubVideo("subicon appended to subBar", {
subName: (s.name || s.n || "").trim(),
parentIdx,
subBarChildrenCount: subBar.children.length,
});
});
// Aplica classes de weapon nas subskills recém-renderizadas
applyWeaponClassesToSubskills();
// Dispara evento para notificar que subskills estão prontas
window.dispatchEvent(
new CustomEvent("gla:subskills:ready", {
detail: { count: order.length },
})
);
// Remove listener anterior se existir (evita duplicação)
if (subBar._weaponToggleListener) {
window.removeEventListener(
"gla:weaponToggled",
subBar._weaponToggleListener
);
}
// Cria novo listener que opera no subBar atual
subBar._weaponToggleListener = (e) => {
const enabled = e.detail?.enabled ?? false;
// Aplica classes usando a função auxiliar
applyWeaponClassesToSubskills();
// Atualiza a subskill ativa se houver
setTimeout(() => {
const activeSub =
subBar.querySelector(".subicon[data-weapon].active") ||
subBar.querySelector(".subicon.active");
if (activeSub) {
activeSub.dispatchEvent(new Event("click", { bubbles: true }));
} else {
api.refreshCurrentSubSafe();
}
}, 50);
};
window.addEventListener(
"gla:weaponToggled",
subBar._weaponToggleListener
);
requestAnimationFrame(() => {
subRail.classList.remove("collapsed");
subRail.classList.remove("hidden");
const h = subRail.offsetHeight || 48;
if (spacer) spacer.style.height = h + "px";
});
};
// Chama a função de renderização após pré-carregar ícones
Promise.all(iconPreloadPromises)
.then(() => {
setTimeout(() => {
renderSubskillsBar();
}, 10);
})
.catch(() => {
setTimeout(() => {
renderSubskillsBar();
}, 10);
});
};
api.hideAll = function (videoBox) {
const videos =
videoBox?.querySelectorAll('.skill-video[data-sub="1"]') || [];
logSubVideo("api.hideAll called", {
videoBoxExists: !!videoBox,
videoCount: videos.length,
});
videos.forEach((v) => {
try {
v.pause();
} catch { }
v.style.display = "none";
});
};
window.renderSubskillsBarFrom = function (el, ctx) {
api.renderBarFrom(el, ctx);
};
api.preloadAllSubskillImages = function () {
const allSkillIcons = document.querySelectorAll(
".icon-bar .skill-icon[data-subs]"
);
const preloadPromises = [];
let totalImages = 0;
allSkillIcons.forEach((icon) => {
try {
const subsRaw = icon.getAttribute("data-subs");
if (!subsRaw) return;
const subs = JSON.parse(subsRaw);
if (!Array.isArray(subs)) return;
subs.forEach((s) => {
if (s && s.icon && s.icon.trim() !== "") {
preloadPromises.push(preloadImage(s.icon));
totalImages++;
}
if (s && Array.isArray(s.subs)) {
s.subs.forEach((nested) => {
if (nested && nested.icon && nested.icon.trim() !== "") {
preloadPromises.push(preloadImage(nested.icon));
totalImages++;
}
});
}
});
} catch (e) {
console.error("[Subskills] preloadAllSubskillImages error:", e);
}
});
if (totalImages > 0) {
// console.log('[Subskills] preloadAllSubskillImages: pré-carregando', totalImages, 'ícones');
return Promise.all(preloadPromises).then(() => {
// console.log('[Subskills] preloadAllSubskillImages: todos os ícones carregados');
});
}
return Promise.resolve();
};
// Função para pré-carregar vídeos de subskills usando getOrCreateSubskillVideo // Função preloadAllSubskillVideos removida - vídeos são carregados sob demanda
// Função syncVideoCaches removida - não é mais necessária com cache baseado em URL
// Inicialização
function init() {
logSubVideo("init: starting initialization");
// Constrói cache das skills principais
getMainSkillsMap();
// Pré-carrega imagens das subskills IMEDIATAMENTE
api.preloadAllSubskillImages();
// if (video.readyState < 2) {
// video.muted = true;
// video.load();
// }
// });
// }, 500);
// Escuta mudanças no localStorage
window.addEventListener("storage", (e) => {
if (e.key === "glaWeaponEnabled") {
setTimeout(() => api.refreshCurrentSubSafe(), 50);
}
});
// LISTENER GLOBAL: Escuta evento de toggle
window.addEventListener("gla:weaponToggled", (e) => {
const enabled = e.detail?.enabled ?? false;
// Tenta aplicar classes imediatamente
applyWeaponClassesToSubskills();
// Atualiza a subskill ativa se houver
setTimeout(() => {
const activeSub =
document.querySelector(".subicon[data-weapon].active") ||
document.querySelector(".subicon.active");
if (activeSub) {
activeSub.dispatchEvent(new Event("click", { bubbles: true }));
} else {
api.refreshCurrentSubSafe();
}
}, 50);
});
// LISTENER: Escuta quando subskills estão prontas
window.addEventListener("gla:subskills:ready", (e) => {
// Aplica classes de weapon se o toggle estiver ativo
applyWeaponClassesToSubskills();
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
setTimeout(init, 100);
});
} else {
setTimeout(init, 100);
}
})();
</script> <style>
.subicon-bar {
display: flex;
gap: 10px;
padding: 6px 6px;
overflow-x: auto;
/* Firefox */
scrollbar-width: thin;
scrollbar-color: #ababab transparent;
}
.subicon-bar::-webkit-scrollbar {
height: 6px;
}
.subicon-bar::-webkit-scrollbar-thumb {
background: #151515;
border-radius: 3px;
}
.subicon {
width: var(--icon-size, 42px);
height: var(--icon-size, 42px);
border-radius: var(--icon-radius, 10px);
overflow: hidden;
position: relative;
flex: 0 0 auto;
cursor: pointer;
isolation: isolate;
filter: brightness(0.92);
}
.subicon img {
width: 100%;
height: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
display: block;
border-radius: inherit;
}
.subicon::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-idle, #cfcfcf);
pointer-events: none;
z-index: 2;
transition: box-shadow 0.12s ease;
}
.subicon:hover::after {
box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
}
.subicon:hover {
filter: brightness(1);
}
.subicon.active::after {
box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #ffd95a);
}
.subicon.active {
transform: scale(1.1);
z-index: 5;
filter: brightness(1);
}
.subicon.active::before {
content: "";
position: absolute;
inset: -4px;
border-radius: calc(var(--icon-radius, 10px) + 4px);
pointer-events: none;
z-index: 1;
opacity: 1;
box-shadow: 0 0 12px 3px var(--icon-active-glow, rgba(255, 217, 90, 0.3)),
0 0 0 calc(var(--icon-ring-w, 2px) * 2) var(--icon-active-ring, rgba(255, 217, 90, 0.5));
}
.subicon.active img {
transform: none !important;
}
.top-rail.skills {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
overflow: visible;
}
.top-rail.skills .icon-bar {
margin-bottom: 0;
position: relative;
z-index: 2;
}
.subskills-rail {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: calc(100% - 1px);
z-index: 3;
display: inline-flex;
justify-content: center;
align-items: center;
width: auto;
max-width: 100%;
padding: 3px 5px;
background: rgba(0, 0, 0, 0.38);
border: 1px solid rgba(255, 255, 255, 0.1);
border-top: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0 0 10px 10px;
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.22);
-webkit-backdrop-filter: blur(2px);
backdrop-filter: blur(2px);
overflow: hidden;
transition: opacity 0.14s ease, transform 0.14s ease;
opacity: 1;
}
.subskills-rail::before {
content: "";
position: absolute;
top: -6px;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(to bottom,
rgba(0, 0, 0, 0.2),
rgba(0, 0, 0, 0));
pointer-events: none;
}
.subskills-rail.collapsed {
opacity: 0;
pointer-events: none;
transform: translate(-50%, -6px);
}
.subskills-rail.hidden {
visibility: hidden;
}
.subskills-spacer {
height: 0;
transition: height 0.2s ease;
}
.subskills-rail .subicon-bar {
display: inline-flex;
align-items: center;
gap: 0;
overflow-x: auto;
/* Firefox */
scrollbar-width: thin;
scrollbar-color: #ababab transparent;
}
.subskills-rail .subicon-bar::-webkit-scrollbar {
height: 6px;
}
.subskills-rail .subicon-bar::-webkit-scrollbar-thumb {
background: #151515;
border-radius: 3px;
}
.subskills-rail .subicon {
width: 42px;
height: 42px;
border-radius: 6px;
position: relative;
overflow: hidden;
flex: 0 0 auto;
cursor: pointer;
isolation: isolate;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform: translateZ(0);
}
.subskills-rail .subicon+.subicon {
margin-left: 4px;
}
.subskills-rail .subicon img {
width: 100%;
height: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
display: block;
border-radius: inherit;
}
.subskills-rail .subicon::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 2px var(--icon-idle, #cfcfcf);
pointer-events: none;
z-index: 2;
transition: box-shadow 0.12s ease;
}
.subskills-rail .subicon:hover::after {
box-shadow: inset 0 0 0 2px #e6e6e6;
}
.subskills-rail .subicon.active::after {
box-shadow: inset 0 0 0 2px var(--icon-active, #ffd95a);
}
.video-container .skill-video {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
object-fit: cover;
background: #000;
border-radius: 10px;
}
@media (max-width: 900px) {
.subskills-rail {
position: static;
transform: none;
margin-top: -2px;
border-top: 0;
border-radius: 0 0 10px 10px;
}
.subskills-spacer {
height: 0 !important;
}
}
.skills-rail-wrap {
position: relative;
display: block;
width: max-content;
margin: 0 auto;
}
/* Subskills com arma disponível - borda vermelha quando inativa */
.character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after {
box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important;
}
/* Subskill com arma ATIVA - laranja/coral vibrante + brilho forte */
.character-box .top-rail.skills .subicon.has-weapon-available.active {
position: relative;
}
.character-box .top-rail.skills .subicon.has-weapon-available.active::after {
box-shadow: none !important;
background: linear-gradient(135deg,
#ff5722 0%,
#ff7043 20%,
#ff8a65 40%,
#ff7043 60%,
#ff5722 80%,
#ff3d00 100%) !important;
background-size: 300% 300% !important;
animation: weapon-icon-border-scan 3s linear infinite !important;
-webkit-mask: linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0) !important;
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
-webkit-mask-composite: xor !important;
mask-composite: exclude !important;
padding: 2px !important;
filter: drop-shadow(0 0 4px rgba(255, 87, 34, 0.8)) drop-shadow(0 0 8px rgba(255, 87, 34, 0.6)) !important;
}
.character-box .top-rail.skills .subicon.has-weapon-available.active::before {
box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.95),
0 0 40px 16px rgba(255, 87, 34, 0.75),
0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6) !important;
opacity: 1 !important;
}
/* Modo arma ON - efeito animado nos subicons */
.character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available {
position: relative;
}
.character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::after {
box-shadow: none !important;
background: linear-gradient(90deg,
rgba(255, 80, 80, 0.9) 0%,
rgba(255, 120, 60, 1) 25%,
rgba(255, 80, 80, 0.9) 50%,
rgba(255, 120, 60, 1) 75%,
rgba(255, 80, 80, 0.9) 100%) !important;
background-size: 400% 100% !important;
animation: weapon-subicon-border-scan 4s linear infinite !important;
-webkit-mask: linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0) !important;
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
-webkit-mask-composite: xor !important;
mask-composite: exclude !important;
padding: 2px !important;
}
.character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available::before {
content: "";
position: absolute;
inset: -4px;
border-radius: calc(6px + 4px);
pointer-events: none;
z-index: 1;
box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
0 0 32px 12px rgba(255, 80, 80, 0.6),
0 0 48px 18px rgba(255, 100, 60, 0.4) !important;
opacity: 1 !important;
animation: weapon-subicon-pulse 3s ease-in-out infinite !important;
}
/* Subskill ativa com arma - mais intenso */
.character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::after {
background: linear-gradient(135deg,
#ff3d00 0%,
#ff5722 15%,
#ff7043 30%,
#ff8a65 45%,
#ff7043 60%,
#ff5722 75%,
#ff3d00 90%,
#ff5722 100%) !important;
background-size: 400% 400% !important;
animation: weapon-icon-border-scan 2.5s linear infinite !important;
filter: drop-shadow(0 0 6px rgba(255, 87, 34, 1)) drop-shadow(0 0 12px rgba(255, 87, 34, 0.8)) drop-shadow(0 0 18px rgba(255, 120, 50, 0.6)) !important;
}
.character-box .top-rail.skills.weapon-mode-on .subicon.has-weapon-available.active::before {
box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
0 0 56px 24px rgba(255, 87, 34, 0.85),
0 0 80px 32px rgba(255, 140, 60, 0.6), 0 0 0 5px rgba(255, 87, 34, 0.75) !important;
animation: weapon-subicon-pulse-active 2.5s ease-in-out infinite !important;
}
@keyframes weapon-icon-border-scan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 400% 0%;
}
}
@keyframes weapon-subicon-border-scan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 400% 0%;
}
}
@keyframes weapon-subicon-pulse {
0%,
100% {
opacity: 0.95;
box-shadow: 0 0 16px 6px rgba(255, 80, 80, 0.85),
0 0 32px 12px rgba(255, 80, 80, 0.6),
0 0 48px 18px rgba(255, 100, 60, 0.4);
}
50% {
opacity: 1;
box-shadow: 0 0 24px 8px rgba(255, 80, 80, 1),
0 0 48px 16px rgba(255, 80, 80, 0.75),
0 0 64px 24px rgba(255, 120, 60, 0.5);
}
}
@keyframes weapon-subicon-pulse-active {
0%,
100% {
opacity: 0.95;
box-shadow: 0 0 20px 8px rgba(255, 87, 34, 0.9),
0 0 40px 16px rgba(255, 87, 34, 0.7),
0 0 60px 24px rgba(255, 120, 50, 0.5), 0 0 0 4px rgba(255, 87, 34, 0.6);
}
50% {
opacity: 1;
box-shadow: 0 0 28px 12px rgba(255, 87, 34, 1),
0 0 56px 24px rgba(255, 87, 34, 0.85),
0 0 80px 32px rgba(255, 140, 60, 0.6), 0 0 0 5px rgba(255, 87, 34, 0.75);
}
}
</style>