Mudanças entre as edições de "Widget:Character.Skills"
Ir para navegação
Ir para pesquisar
m |
m |
||
| (51 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
| Linha 1: | Linha 1: | ||
<!-- | <!-- MAIN SKILLS SYSTEM --> | ||
<script> | <script> | ||
(function () { | (function () { | ||
const $ = (s, root = document) => root.querySelector(s); | const $ = (s, root = document) => root.querySelector(s); | ||
const $$ = (s, root = document) => Array.from(root.querySelectorAll(s)); | const $$ = (s, root = document) => Array.from(root.querySelectorAll(s)); | ||
const ensureRemoved = sel => { Array.from(document.querySelectorAll(sel)).forEach(n => n.remove()); }; | const ensureRemoved = sel => { | ||
const onceFlag = (el, key) => { if (!el) return false; if (el.dataset[key]) return false; el.dataset[key] = '1'; return true; }; | Array.from(document.querySelectorAll(sel)).forEach(n => n.remove()); | ||
}; | |||
const onceFlag = (el, key) => { | |||
if (!el) return false; | |||
if (el.dataset[key]) return false; | |||
el.dataset[key] = '1'; | |||
return true; | |||
}; | |||
const addOnce = (el, ev, fn) => { | const addOnce = (el, ev, fn) => { | ||
if (!el) return; | if (!el) return; | ||
| Linha 18: | Linha 20: | ||
el.setAttribute(attr, '1'); | el.setAttribute(attr, '1'); | ||
}; | }; | ||
const FLAG_ICON_FILES = { | const FLAG_ICON_FILES = { | ||
aggro: 'Enemyaggro-icon.png', | aggro: 'Enemyaggro-icon.png', bridge: 'Bridgemaker-icon.png', wall: 'Destroywall-icon.png', quickcast: 'Quickcast-icon.png' | ||
}; | }; | ||
const subBarTemplateCache = window.__skillSubBarTemplateCache || (window.__skillSubBarTemplateCache = new Map()); | const subBarTemplateCache = window.__skillSubBarTemplateCache || (window.__skillSubBarTemplateCache = new Map()); | ||
| Linha 31: | Linha 28: | ||
const flagRowCache = window.__skillFlagRowCache || (window.__skillFlagRowCache = new Map()); | const flagRowCache = window.__skillFlagRowCache || (window.__skillFlagRowCache = new Map()); | ||
const flagIconURLCache = window.__skillFlagIconURLCache || (window.__skillFlagIconURLCache = new Map()); | const flagIconURLCache = window.__skillFlagIconURLCache || (window.__skillFlagIconURLCache = new Map()); | ||
function filePathURL(fileName) { | function filePathURL(fileName) { | ||
const f = encodeURIComponent((fileName || 'Nada.png').replace(/^Arquivo:|^File:/, '')); | const f = encodeURIComponent((fileName || 'Nada.png').replace(/^Arquivo:|^File:/, '')); | ||
const base = (window.mw && mw.util && typeof mw.util.wikiScript === 'function') | const base = (window.mw && mw.util && typeof mw.util.wikiScript === 'function') ? mw.util.wikiScript() : (window.mw && window.mw.config ? (mw.config.get('wgScript') || '/index.php') : '/index.php'); | ||
return `${base}?title=Especial:FilePath/${f}`; | return `${base}?title=Especial:FilePath/${f}`; | ||
} | } function slugify(s) { | ||
if (!s) return ''; | if (!s) return ''; | ||
return String(s) | return String(s).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\w\s-]/g, '').replace(/[\s:/\-]+/g, '-').replace(/^-+|-+$/g, '').replace(/-+/g, '-'); | ||
} window.__skillSlugify = slugify; | |||
} | |||
function getLangKey() { | function getLangKey() { | ||
const skillsRoot = document.getElementById('skills'); | const skillsRoot = document.getElementById('skills'); | ||
const raw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase(); | const raw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase(); | ||
return raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt'); | return raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt'); | ||
} | } function chooseDescFrom(obj) { | ||
const lang = getLangKey(); | const lang = getLangKey(); | ||
const pack = obj.desc_i18n || { pt: obj.descPt, en: obj.descEn, es: obj.descEs, pl: obj.descPl }; | // Aceita tanto desc_i18n quanto desc para compatibilidade | ||
return (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) | const pack = obj.desc_i18n || obj.desc || { | ||
} | pt: obj.descPt, en: obj.descEn, es: obj.descEs, pl: obj.descPl | ||
}; | |||
return (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || ''; | |||
} function renderSubAttributesFromObj(s, L) { | |||
const chip = (label, val) => (val ? `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${val}</span></div>` : ''); | const chip = (label, val) => (val ? `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${val}</span></div>` : ''); | ||
const pve = (s.powerpve || '').toString().trim(); | const pve = (s.powerpve || '').toString().trim(); | ||
| Linha 70: | Linha 53: | ||
const en = (s.energy || '').toString().trim(); | const en = (s.energy || '').toString().trim(); | ||
const cd = (s.cooldown || '').toString().trim(); | const cd = (s.cooldown || '').toString().trim(); | ||
const rows = [ | const rows = [cd ? chip(L.cooldown, cd) : '', en ? chip((en.startsWith('-') ? L.energy_cost : L.energy_gain), en.startsWith('-') ? en.replace(/^-/, '') : en.replace(/^\+?/, '')) : '', pve ? chip(L.power, pve) : '', pvp ? chip(L.power_pvp, pvp) : '',].filter(Boolean); | ||
return rows.length ? `<div class="attr-list">${rows.join('')}</div>` : ''; | return rows.length ? `<div class="attr-list">${rows.join('')}</div>` : ''; | ||
} | } function getFlagIconURL(key) { | ||
if (!FLAG_ICON_FILES[key]) return ''; | if (!FLAG_ICON_FILES[key]) return ''; | ||
if (!flagIconURLCache.has(key)) { | if (!flagIconURLCache.has(key)) { | ||
flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key])); | flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key])); | ||
} | } return flagIconURLCache.get(key); | ||
} function renderFlagsRow(flags) { | |||
} | |||
const arr = (flags || []).filter(Boolean); | const arr = (flags || []).filter(Boolean); | ||
if (!arr.length) return ''; | if (!arr.length) return ''; | ||
| Linha 92: | Linha 66: | ||
if (flagRowCache.has(cacheKey)) { | if (flagRowCache.has(cacheKey)) { | ||
return flagRowCache.get(cacheKey); | return flagRowCache.get(cacheKey); | ||
} | } const items = arr.map(k => { | ||
const url = getFlagIconURL(k); | const url = getFlagIconURL(k); | ||
return url ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">` : ''; | return url ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">` : ''; | ||
| Linha 100: | Linha 73: | ||
if (html) flagRowCache.set(cacheKey, html); | if (html) flagRowCache.set(cacheKey, html); | ||
return html; | return html; | ||
} | } function applyFlagTooltips(container) { | ||
const skillsRoot = document.getElementById('skills'); | const skillsRoot = document.getElementById('skills'); | ||
if (!skillsRoot) return; | if (!skillsRoot) return; | ||
let pack = {}; | let pack = { | ||
}; | |||
try { | try { | ||
pack = JSON.parse(skillsRoot.dataset.i18nFlags || '{}'); | pack = JSON.parse(skillsRoot.dataset.i18nFlags || '{}'); | ||
} catch (e) { | } catch (e) { | ||
const lang = getLangKey(); | } const lang = getLangKey(); | ||
const dict = pack[lang] || pack.pt || {}; | const dict = pack[lang] || pack.pt || { | ||
}; | |||
const flags = container.querySelectorAll('.skill-flags .skill-flag[data-flag]'); | const flags = container.querySelectorAll('.skill-flags .skill-flag[data-flag]'); | ||
const tooltip = window.__globalSkillTooltip; | const tooltip = window.__globalSkillTooltip; | ||
if (!tooltip) return; | if (!tooltip) return; | ||
flags.forEach(el => { | flags.forEach(el => { | ||
const key = el.getAttribute('data-flag'); | const key = el.getAttribute('data-flag'); | ||
const tip = (dict && dict[key]) || ''; | const tip = (dict && dict[key]) || ''; | ||
if (!tip) return; | if (!tip) return; | ||
if (el.dataset.flagTipWired) return; | if (el.dataset.flagTipWired) return; | ||
el.dataset.flagTipWired = '1'; | el.dataset.flagTipWired = '1'; | ||
el.setAttribute('aria-label', tip); | el.setAttribute('aria-label', tip); | ||
if (el.hasAttribute('title')) el.removeAttribute('title'); | if (el.hasAttribute('title')) el.removeAttribute('title'); | ||
el.addEventListener('mouseenter', () => { | el.addEventListener('mouseenter', () => { | ||
const tipEl = document.querySelector('.skill-tooltip'); | const tipEl = document.querySelector('.skill-tooltip'); | ||
| Linha 150: | Linha 117: | ||
} | } | ||
// ============= | // ====== Skill/Subskill inheritance helpers ====== | ||
const mainSkillsMeta = { | |||
byIndex: new Map(), | |||
byName: new Map(), | |||
ready: false | |||
}; | |||
function normalizeFileURL(raw, fallback = '') { | function normalizeFileURL(raw, fallback = '') { | ||
if (!raw) return fallback; | if (!raw) return fallback; | ||
| Linha 159: | Linha 130: | ||
if (/^(https?:)?\/\//i.test(val) || val.startsWith('data:') || val.includes('Especial:FilePath/')) { | if (/^(https?:)?\/\//i.test(val) || val.startsWith('data:') || val.includes('Especial:FilePath/')) { | ||
return val; | return val; | ||
} return filePathURL(val); | |||
} | |||
function extractFileNameFromURL(url) { | |||
if (!url) return ''; | |||
const match = String(url).match(/(?:FilePath\/)([^&?]+)/i); | |||
return match ? decodeURIComponent(match[1]) : ''; | |||
} | |||
function parseAttrString(raw) { | |||
const parts = (raw || '').split(',').map(v => v.trim()); | |||
const safe = idx => { | |||
const val = parts[idx] || ''; | |||
return (val && val !== '-') ? val : ''; | |||
}; | |||
return { | |||
powerpve: safe(0), | |||
powerpvp: safe(1), | |||
energy: safe(2), | |||
cooldown: safe(3) | |||
}; | |||
} | |||
function hasText(value) { | |||
return typeof value === 'string' ? value.trim() !== '' : value !== undefined && value !== null; | |||
} | |||
function pickFilled(current, fallback) { | |||
if (current === 0 || current === '0') return current; | |||
if (!hasText(current)) return fallback; | |||
return current; | |||
} | |||
function buildMainSkillsMeta(nodes) { | |||
if (mainSkillsMeta.ready) { | |||
return mainSkillsMeta; | |||
} | } | ||
return | (nodes || []).forEach(icon => { | ||
const index = (icon.dataset.index || '').trim(); | |||
if (!index) return; | |||
const name = (icon.dataset.nome || icon.dataset.name || '').trim(); | |||
const attrs = parseAttrString(icon.dataset.atr || ''); | |||
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]) : ''; | |||
} | |||
let videoFile = (icon.dataset.videoFile || '').trim(); | |||
if (!videoFile) { | |||
videoFile = extractFileNameFromURL(icon.dataset.video || ''); | |||
} | |||
const meta = { | |||
index, | |||
name, | |||
icon: iconFile || 'Nada.png', | |||
level: icon.dataset.level || '', | |||
video: videoFile || '', | |||
powerpve: attrs.powerpve || '', | |||
powerpvp: attrs.powerpvp || '', | |||
energy: attrs.energy || '', | |||
cooldown: attrs.cooldown || '', | |||
desc: icon.dataset.desc || '', | |||
descPt: icon.dataset.descPt || '', | |||
descEn: icon.dataset.descEn || '', | |||
descEs: icon.dataset.descEs || '', | |||
descPl: icon.dataset.descPl || '' | |||
}; | |||
mainSkillsMeta.byIndex.set(index, meta); | |||
mainSkillsMeta.byIndex.set(parseInt(index, 10), meta); | |||
if (name) { | |||
mainSkillsMeta.byName.set(name, meta); | |||
} | |||
}); | |||
mainSkillsMeta.ready = true; | |||
return mainSkillsMeta; | |||
} | |||
function inheritSubskillFromMain(sub, meta) { | |||
if (!sub || !meta) return sub; | |||
// Suporta refS (novo) e refM (legado) | |||
const refS = ((sub.refS || sub.S || sub.s || '') + '').trim(); | |||
const refIndex = ((sub.refM || sub.M || sub.m || '') + '').trim(); | |||
let name = (sub.name || sub.n || '').trim(); | |||
let main = null; | |||
// Primeiro tenta por refS | |||
if (refS) { | |||
main = meta.byIndex.get(refS) || meta.byIndex.get(parseInt(refS, 10)); | |||
} | |||
// Depois por refM | |||
if (!main && refIndex) { | |||
main = meta.byIndex.get(refIndex) || meta.byIndex.get(parseInt(refIndex, 10)); | |||
} | |||
// Por último pelo nome | |||
if (!main && name) { | |||
main = meta.byName.get(name); | |||
} | |||
if (!main) { | |||
return sub; | |||
} | |||
const hydrated = { ...sub }; | |||
if (!name && main.name) { | |||
name = main.name; | |||
} | |||
hydrated.name = name || hydrated.name || main.name || ''; | |||
hydrated.icon = pickFilled(hydrated.icon, main.icon || 'Nada.png'); | |||
hydrated.level = pickFilled(hydrated.level, main.level || ''); | |||
hydrated.video = pickFilled(hydrated.video, main.video || ''); | |||
hydrated.powerpve = pickFilled(hydrated.powerpve, main.powerpve || ''); | |||
hydrated.powerpvp = pickFilled(hydrated.powerpvp, main.powerpvp || ''); | |||
hydrated.energy = pickFilled(hydrated.energy, main.energy || ''); | |||
hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || ''); | |||
if (!hasText(hydrated.descPt) && hasText(main.descPt)) hydrated.descPt = main.descPt; | |||
if (!hasText(hydrated.descEn) && hasText(main.descEn)) hydrated.descEn = main.descEn; | |||
if (!hasText(hydrated.descEs) && hasText(main.descEs)) hydrated.descEs = main.descEs; | |||
if (!hasText(hydrated.descPl) && hasText(main.descPl)) hydrated.descPl = main.descPl; | |||
if (!hasText(hydrated.desc) && hasText(main.desc)) hydrated.desc = main.desc; | |||
if (!hydrated.desc_i18n && (hydrated.descPt || hydrated.descEn || hydrated.descEs || hydrated.descPl)) { | |||
hydrated.desc_i18n = { | |||
pt: hydrated.descPt || '', | |||
en: hydrated.descEn || '', | |||
es: hydrated.descEs || '', | |||
pl: hydrated.descPl || '' | |||
}; | |||
} | |||
return hydrated; | |||
} | |||
function inheritSubskillTree(subs, meta) { | |||
if (!Array.isArray(subs)) return []; | |||
return subs.map(sub => { | |||
const hydrated = inheritSubskillFromMain(sub, meta); | |||
if (Array.isArray(hydrated.subs)) { | |||
hydrated.subs = inheritSubskillTree(hydrated.subs, meta); | |||
} | |||
return hydrated; | |||
}); | |||
} | } | ||
| Linha 167: | Linha 279: | ||
subs.forEach(sub => { | subs.forEach(sub => { | ||
const iconURL = normalizeFileURL(sub.icon || 'Nada.png', filePathURL('Nada.png')); | const iconURL = normalizeFileURL(sub.icon || 'Nada.png', filePathURL('Nada.png')); | ||
if (iconURL) iconsSet.add(iconURL); | if (iconURL && iconURL !== filePathURL('Nada.png')) iconsSet.add(iconURL); | ||
if (sub.video) { | // Vídeo normal | ||
if (sub.video && sub.video.trim() !== '' && sub.video !== 'Nada.png' && !sub.video.toLowerCase().includes('nada.png')) { | |||
const videoURL = normalizeFileURL(sub.video); | const videoURL = normalizeFileURL(sub.video); | ||
if (videoURL) videosSet.add(videoURL); | if (videoURL) videosSet.add(videoURL); | ||
} | |||
// Vídeo de weapon | |||
if (sub.weapon && typeof sub.weapon === 'object' && sub.weapon.video && sub.weapon.video.trim() !== '' && sub.weapon.video !== 'Nada.png' && !sub.weapon.video.toLowerCase().includes('nada.png')) { | |||
const weaponVideoURL = normalizeFileURL(sub.weapon.video); | |||
if (weaponVideoURL) videosSet.add(weaponVideoURL); | |||
} | } | ||
if (Array.isArray(sub.flags)) { | if (Array.isArray(sub.flags)) { | ||
| Linha 177: | Linha 295: | ||
if (url) flagsSet.add(url); | if (url) flagsSet.add(url); | ||
}); | }); | ||
} | } if (Array.isArray(sub.subs)) { | ||
collectAssetsFromSubs(sub.subs, iconsSet, videosSet, flagsSet); | collectAssetsFromSubs(sub.subs, iconsSet, videosSet, flagsSet); | ||
} | } | ||
}); | }); | ||
} | } function buildAssetManifest() { | ||
if (window.__skillAssetManifest && window.__skillAssetManifest.ready) { | if (window.__skillAssetManifest && window.__skillAssetManifest.ready) { | ||
return window.__skillAssetManifest; | return window.__skillAssetManifest; | ||
} | } const iconsSet = new Set(); | ||
const videosSet = new Set(); | const videosSet = new Set(); | ||
const flagsSet = new Set(); | const flagsSet = new Set(); | ||
iconItems.forEach(el => { | iconItems.forEach(el => { | ||
const img = el.querySelector('img'); | const img = el.querySelector('img'); | ||
if (img && img.src) { | if (img && img.src) { | ||
iconsSet.add(img.src); | iconsSet.add(img.src); | ||
} else if (el.dataset. | } else if (el.dataset.iconFile) { | ||
const iconURL = normalizeFileURL(el.dataset.iconFile); | |||
if (iconURL) iconsSet.add(iconURL); | |||
} | } | ||
const videoRaw = (el.dataset.video || '').trim(); | // Vídeo normal da skill | ||
if (videoRaw) { | const videoRaw = (el.dataset.videoFile || el.dataset.video || '').trim(); | ||
videosSet.add(normalizeFileURL( | if (videoRaw && videoRaw !== 'Nada.png' && !videoRaw.toLowerCase().includes('nada.png')) { | ||
const videoURL = normalizeFileURL(videoRaw); | |||
if (videoURL) videosSet.add(videoURL); | |||
} | |||
// Vídeo de weapon da skill | |||
if (el.dataset.weapon) { | |||
try { | |||
const weaponData = JSON.parse(el.dataset.weapon); | |||
if (weaponData && weaponData.video && weaponData.video.trim() !== '' && weaponData.video !== 'Nada.png' && !weaponData.video.toLowerCase().includes('nada.png')) { | |||
const weaponVideoURL = normalizeFileURL(weaponData.video); | |||
if (weaponVideoURL) videosSet.add(weaponVideoURL); | |||
} | |||
} catch (e) { | |||
} | |||
} | } | ||
if (el.dataset.flags) { | if (el.dataset.flags) { | ||
| Linha 210: | Linha 337: | ||
if (url) flagsSet.add(url); | if (url) flagsSet.add(url); | ||
}); | }); | ||
} catch (e) { } | } catch (e) { | ||
} | } | ||
} if (el.dataset.subs) { | |||
try { | try { | ||
const subs = JSON.parse(el.dataset.subs); | const subs = JSON.parse(el.dataset.subs); | ||
collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet); | collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet); | ||
} catch (e) { } | } catch (e) { | ||
} | |||
} | } | ||
}); | }); | ||
Object.keys(FLAG_ICON_FILES).forEach(flagKey => { | Object.keys(FLAG_ICON_FILES).forEach(flagKey => { | ||
const url = getFlagIconURL(flagKey); | const url = getFlagIconURL(flagKey); | ||
if (url) flagsSet.add(url); | if (url) flagsSet.add(url); | ||
}); | }); | ||
const manifest = { | const manifest = { | ||
icons: iconsSet, | icons: iconsSet, videos: videosSet, flags: flagsSet, ready: true | ||
}; | }; | ||
window.__skillAssetManifest = manifest; | window.__skillAssetManifest = manifest; | ||
return manifest; | return manifest; | ||
} | } function preloadImagesFromSet(set) { | ||
if (!set || !set.size) return; | if (!set || !set.size) return; | ||
set.forEach(url => { | set.forEach(url => { | ||
| Linha 246: | Linha 367: | ||
imagePreloadCache.set(url, img); | imagePreloadCache.set(url, img); | ||
}); | }); | ||
} | } function preloadVideosFromSet(set) { | ||
if (!set || !set.size) return; | if (!set || !set.size) return; | ||
const head = document.head || document.getElementsByTagName('head')[0]; | const head = document.head || document.getElementsByTagName('head')[0]; | ||
| Linha 261: | Linha 380: | ||
videoPreloadCache.add(url); | videoPreloadCache.add(url); | ||
}); | }); | ||
} | } const subskillVideosCache = new Map(); | ||
window.__subskillVideosCache = subskillVideosCache; | window.__subskillVideosCache = subskillVideosCache; | ||
let assetManifest = null; | let assetManifest = null; | ||
const skillsTab = $('#skills'); | const skillsTab = $('#skills'); | ||
const skinsTab = $('#skins'); | const skinsTab = $('#skins'); | ||
ensureRemoved('.top-rail'); | ensureRemoved('.top-rail'); | ||
ensureRemoved('.content-card'); | ensureRemoved('.content-card'); | ||
| Linha 286: | Linha 393: | ||
} | } | ||
}); | }); | ||
if (skillsTab) { | if (skillsTab) { | ||
const iconBar = skillsTab.querySelector('.icon-bar'); | const iconBar = skillsTab.querySelector('.icon-bar'); | ||
| Linha 295: | Linha 400: | ||
rail.appendChild(iconBar); | rail.appendChild(iconBar); | ||
skillsTab.prepend(rail); | skillsTab.prepend(rail); | ||
} | } const details = skillsTab.querySelector('.skills-details'); | ||
const videoContainer = skillsTab.querySelector('.video-container'); | const videoContainer = skillsTab.querySelector('.video-container'); | ||
const card = document.createElement('div'); | const card = document.createElement('div'); | ||
| Linha 304: | Linha 407: | ||
if (videoContainer) card.appendChild(videoContainer); | if (videoContainer) card.appendChild(videoContainer); | ||
skillsTab.appendChild(card); | skillsTab.appendChild(card); | ||
} | } if (skinsTab) { | ||
const wrapper = skinsTab.querySelector('.skins-carousel-wrapper'); | const wrapper = skinsTab.querySelector('.skins-carousel-wrapper'); | ||
const rail = document.createElement('div'); | const rail = document.createElement('div'); | ||
| Linha 324: | Linha 424: | ||
skinsTab.prepend(rail); | skinsTab.prepend(rail); | ||
} | } | ||
} const iconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null; | |||
const skillsTopRail = iconsBar ? iconsBar.closest('.top-rail.skills') : null; | |||
const iconItems = iconsBar ? Array.from(iconsBar.querySelectorAll('.skill-icon')) : []; | |||
buildMainSkillsMeta(iconItems); | |||
// Verifica se há weapon em skills principais OU em subskills | |||
function checkHasAnyWeapon() { | |||
// Verifica skills principais | |||
if (iconItems.some(el => !!el.dataset.weapon)) { | |||
return true; | |||
} | |||
// Verifica subskills | |||
for (const el of iconItems) { | |||
const subsRaw = el.getAttribute('data-subs'); | |||
if (!subsRaw) continue; | |||
try { | |||
const subs = JSON.parse(subsRaw); | |||
if (Array.isArray(subs) && subs.some(s => s && s.weapon)) { | |||
return true; | |||
} | |||
} catch (e) { } | |||
} | |||
return false; | |||
} | } | ||
const hasWeaponSkillAvailable = checkHasAnyWeapon(); | |||
let weaponToggleBtn = null; | |||
const | |||
if (!assetManifest) { | if (!assetManifest) { | ||
assetManifest = buildAssetManifest(); | assetManifest = buildAssetManifest(); | ||
| Linha 336: | Linha 454: | ||
preloadImagesFromSet(assetManifest.flags); | preloadImagesFromSet(assetManifest.flags); | ||
preloadVideosFromSet(assetManifest.videos); | preloadVideosFromSet(assetManifest.videos); | ||
} | } const descBox = $('#skills') ? $('.desc-box', $('#skills')) : null; | ||
const videoBox = $('#skills') ? $('.video-container', $('#skills')) : null; | const videoBox = $('#skills') ? $('.video-container', $('#skills')) : null; | ||
const videosCache = new Map(); | const videosCache = new Map(); | ||
const nestedVideoElByIcon = new WeakMap(); | const nestedVideoElByIcon = new WeakMap(); | ||
const barStack = []; | const barStack = []; | ||
window.__barStack = barStack; | window.__barStack = barStack; | ||
let initialBarSnapshot = null; | let initialBarSnapshot = null; | ||
let totalVideos = 0, loadedVideos = 0, autoplay = false; | let totalVideos = 0, loadedVideos = 0, autoplay = false; | ||
window.__lastActiveSkillIcon = null; | window.__lastActiveSkillIcon = null; | ||
let userHasInteracted = false; | let userHasInteracted = false; | ||
let globalWeaponEnabled = false; | |||
try { | |||
if (localStorage.getItem('glaWeaponEnabled') === '1') { | |||
globalWeaponEnabled = true; | |||
} | |||
} catch (err) { | |||
} | |||
const weaponStateListeners = new Set(); | |||
let showWeaponPopupFn = null; | |||
let popupShouldOpen = false; | |||
function attachWeaponPopupFn(fn) { | |||
if (typeof fn !== 'function') return; | |||
showWeaponPopupFn = fn; | |||
if (popupShouldOpen) { | |||
popupShouldOpen = false; | |||
try { | |||
showWeaponPopupFn(); | |||
} catch (err) { | |||
} | |||
} | |||
} | |||
attachWeaponPopupFn(window.__glaWeaponShowPopup); | |||
function requestWeaponPopupDisplay() { | |||
try { | |||
if (localStorage.getItem('glaWeaponPopupDismissed') === '1') return; | |||
} catch (err) { | |||
} | |||
if (typeof showWeaponPopupFn === 'function') { | |||
showWeaponPopupFn(); | |||
return; | |||
} | |||
popupShouldOpen = true; | |||
} | |||
function onWeaponStateChange(fn) { | |||
if (typeof fn !== 'function') return; | |||
weaponStateListeners.add(fn); | |||
} | |||
function syncWeaponButtonState(enabled) { | |||
if (!weaponToggleBtn || !weaponToggleBtn.isConnected) return; | |||
// Usa .weapon-active em vez de .active para não conflitar com skills | |||
weaponToggleBtn.classList.toggle('weapon-active', !!enabled); | |||
weaponToggleBtn.classList.remove('active'); // Garante que .active nunca seja aplicado | |||
weaponToggleBtn.setAttribute('aria-pressed', enabled ? 'true' : 'false'); | |||
weaponToggleBtn.setAttribute('aria-label', enabled ? 'Desativar Arma Especial' : 'Ativar Arma Especial'); | |||
} | |||
function syncWeaponRailState(enabled) { | |||
if (skillsTopRail) { | |||
skillsTopRail.classList.toggle('weapon-mode-on', !!enabled); | |||
} | |||
} | |||
function notifyWeaponStateListeners(enabled) { | |||
weaponStateListeners.forEach(listener => { | |||
try { | |||
listener(enabled); | |||
} catch (err) { | |||
} | |||
}); | |||
} | |||
let pendingWeaponState = null; | |||
window.addEventListener('weapon:ready', (ev) => { | |||
if (ev && ev.detail && ev.detail.showPopup) { | |||
attachWeaponPopupFn(ev.detail.showPopup); | |||
} | |||
if (pendingWeaponState === null) return; | |||
if (typeof window.__applyWeaponState === 'function') { | |||
const target = pendingWeaponState; | |||
pendingWeaponState = null; | |||
window.__applyWeaponState(target); | |||
} | |||
}); | |||
window.__setGlobalWeaponEnabled = (enabled) => { | |||
globalWeaponEnabled = enabled; | |||
notifyWeaponStateListeners(enabled); | |||
}; | |||
function requestWeaponState(targetState) { | |||
if (typeof window.__applyWeaponState === 'function') { | |||
pendingWeaponState = null; | |||
window.__applyWeaponState(targetState); | |||
return; | |||
} | |||
pendingWeaponState = targetState; | |||
} | |||
onWeaponStateChange(syncWeaponButtonState); | |||
function reapplyWeaponClassesToBar() { | |||
if (!globalWeaponEnabled) return; | |||
// SISTEMA UNIFICADO: Aplica em skills E subskills | |||
iconsBar.querySelectorAll('.skill-icon[data-weapon], .subicon[data-weapon]').forEach(el => { | |||
if (!el.classList.contains('has-weapon-available')) { | |||
el.classList.add('has-weapon-available'); | |||
} | |||
if (!el.querySelector('.weapon-indicator')) { | |||
const ind = document.createElement('div'); | |||
ind.className = 'weapon-indicator'; | |||
el.appendChild(ind); | |||
} | |||
}); | |||
} | |||
function setupWeaponBarToggle(shouldShow) { | |||
if (!shouldShow || !iconsBar) return; | |||
if (iconsBar.querySelector('.weapon-bar-toggle')) return; | |||
const btn = document.createElement('button'); | |||
btn.type = 'button'; | |||
btn.className = 'skill-icon weapon-bar-toggle'; | |||
btn.dataset.weaponToggle = '1'; | |||
btn.dataset.nome = 'Arma Especial'; | |||
btn.setAttribute('aria-pressed', 'false'); | |||
btn.setAttribute('aria-label', 'Arma Especial'); | |||
btn.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="currentColor" d="M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58a.5.5 0 00.12-.62l-1.92-3.32a.5.5 0 00-.61-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.37-2.48a.55.55 0 00-.55-.5h-3.82a.55.55 0 00-.55.5l-.37 2.48c-.59.24-1.12.56-1.62.94l-2.39-.96a.5.5 0 00-.61.22L3.13 8.5a.5.5 0 00.12.62l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.5.5 0 00-.12.62l1.92 3.32a.5.5 0 00.61.22l2.39-.96c.5.38 1.03.7 1.62.94l.37 2.48a.55.55 0 00.55.5h3.82a.55.55 0 00.55-.5l.37-2.48c.59-.24 1.12-.56 1.62-.94l2.39.96a.5.5 0 00.61-.22l1.92-3.32a.5.5 0 00-.12-.62zM12 15.6a3.6 3.6 0 110-7.2 3.6 3.6 0 010 7.2z"></path></svg>'; | |||
iconsBar.appendChild(btn); | |||
weaponToggleBtn = btn; | |||
syncWeaponButtonState(globalWeaponEnabled); | |||
btn.addEventListener('click', () => { | |||
const nextState = !globalWeaponEnabled; | |||
if (nextState) { | |||
requestWeaponPopupDisplay(); | |||
} | |||
requestWeaponState(nextState); | |||
}); | |||
// Wire tooltip for weapon toggle | |||
const tooltip = window.__globalSkillTooltip; | |||
if (tooltip) { | |||
btn.addEventListener('mouseenter', () => { | |||
const tip = document.querySelector('.skill-tooltip'); | |||
if (tip) tip.classList.add('weapon-tooltip'); | |||
tooltip.show(btn, globalWeaponEnabled ? 'Desativar Arma Especial' : 'Ativar Arma Especial'); | |||
}); | |||
btn.addEventListener('mouseleave', () => { | |||
const tip = document.querySelector('.skill-tooltip'); | |||
if (tip) tip.classList.remove('weapon-tooltip'); | |||
tooltip.hide(); | |||
}); | |||
} | |||
reapplyWeaponClassesToBar(); | |||
} | |||
onWeaponStateChange(syncWeaponRailState); | |||
syncWeaponRailState(globalWeaponEnabled); | |||
setupWeaponBarToggle(hasWeaponSkillAvailable); | |||
(function injectWeaponStyles() { | (function injectWeaponStyles() { | ||
if (document.getElementById('weapon-toggle-styles')) return; | if (document.getElementById('weapon-toggle-styles')) return; | ||
| Linha 365: | Linha 606: | ||
style.id = 'weapon-toggle-styles'; | style.id = 'weapon-toggle-styles'; | ||
style.textContent = ` | style.textContent = ` | ||
/* Animação da borda nos ícones */ | |||
@keyframes weapon-icon-border-scan { | |||
0% { background-position: 0% 0%; } | |||
100% { background-position: 400% 0%; } | |||
} | } | ||
.weapon-toggle-bar. | @keyframes weapon-icon-pulse { | ||
0%, 100% { | |||
opacity: 0.5; | |||
box-shadow: 0 0 8px rgba(255, 80, 80, 0.25); | |||
} | |||
50% { | |||
opacity: 1; | |||
box-shadow: 0 0 16px rgba(255, 80, 80, 0.45); | |||
} | |||
} | |||
/* Skills com arma disponível - borda vermelha quando inativa */ | |||
.skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::after { | |||
box-shadow: inset 0 0 0 var(--icon-ring-w) rgba(220, 70, 70, 0.8) !important; | |||
} | |||
/* Skill com arma ATIVA - laranja/coral vibrante */ | |||
.skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::after { | |||
box-shadow: inset 0 0 0 var(--icon-ring-w) #FF7043 !important; | |||
} | |||
.skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before { | |||
box-shadow: 0 0 12px 3px rgba(255, 112, 67, 0.35), 0 0 0 4px rgba(255, 112, 67, 0.25) !important; | |||
} | |||
/* Modo arma ON - efeito animado nos ícones */ | |||
.top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle) { | |||
position: relative; | |||
} | |||
.top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle)::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-icon-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: var(--icon-ring-w) !important; | |||
} | } | ||
.weapon- | .top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle)::before { | ||
animation: weapon-icon-pulse 3s ease-in-out infinite !important; | |||
} | } | ||
.weapon-toggle | /* Skill ativa com arma - mais intenso */ | ||
background: rgba(255, 255, 255, | .top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::after { | ||
background: linear-gradient(90deg, | |||
rgba(255, 87, 34, 1) 0%, | |||
rgba(255, 140, 60, 1) 25%, | |||
rgba(255, 87, 34, 1) 50%, | |||
rgba(255, 140, 60, 1) 75%, | |||
rgba(255, 87, 34, 1) 100% | |||
) !important; | |||
} | } | ||
.weapon-toggle | .top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before { | ||
box-shadow: 0 0 14px 4px rgba(255, 87, 34, 0.4), 0 0 0 4px rgba(255, 87, 34, 0.3) !important; | |||
animation: weapon-icon-pulse 2.5s ease-in-out infinite !important; | |||
} | } | ||
.weapon- | .skill-icon .weapon-indicator { | ||
display: none; | |||
} | } | ||
. | /* Variáveis de cor vermelha para skill ativa com arma equipada */ | ||
.skill-icon.weapon-equipped { | |||
--icon-active: #FF6B6B; | |||
--icon-active-ring: rgba(255, 100, 100, 0.6); | |||
--icon-active-glow: rgba(255, 90, 90, 0.45); | |||
} | } | ||
/* Badge | /* Badge de arma no canto inferior direito */ | ||
.skill-icon.weapon- | .skill-icon .weapon-badge { | ||
position: absolute; | position: absolute; | ||
bottom: | bottom: 3px; | ||
right: | right: 3px; | ||
width: | width: 16px; | ||
height: | height: 16px; | ||
background: var(--weapon-badge-url) center/contain no-repeat; | background: var(--weapon-badge-url) center/contain no-repeat; | ||
filter: drop-shadow(0 1px 2px rgba(0,0,0,0. | filter: drop-shadow(0 1px 2px rgba(0,0,0,0.6)); | ||
pointer-events: none; | pointer-events: none; | ||
z-index: | z-index: 10; | ||
border-radius: 3px; | |||
display: none; | |||
} | } | ||
.skill-icon.weapon-equipped { | .skill-icon.weapon-equipped .weapon-badge { | ||
display: block; | |||
} | } | ||
`; | `.replace(/\s+/g, ' ').trim(); | ||
document.head.appendChild(style); | document.head.appendChild(style); | ||
})(); | })(); | ||
function applyWeaponBadge(el, weaponData, equipped) { | |||
// Encontrar ou criar o badge | |||
let badge = el.querySelector('.weapon-badge'); | |||
if (!badge) { | |||
badge = document.createElement('div'); | |||
badge.className = 'weapon-badge'; | |||
el.appendChild(badge); | |||
} | |||
if (equipped && weaponData) { | |||
if (equipped) { | |||
el.classList.add('weapon-equipped'); | el.classList.add('weapon-equipped'); | ||
el.style.setProperty('--weapon-badge-url', `url('${filePathURL(weaponData.icon || 'Nada.png')}')`); | el.style.setProperty('--weapon-badge-url', `url('${filePathURL(weaponData.icon || 'Nada.png')}')`); | ||
| Linha 502: | Linha 715: | ||
el.style.removeProperty('--weapon-badge-url'); | el.style.removeProperty('--weapon-badge-url'); | ||
} | } | ||
} | } function getWeaponKey(el) { | ||
return (el.dataset.index || '') + ':' + (el.dataset.nome || el.dataset.name || ''); | |||
} function isWeaponModeOn() { | |||
try { | |||
return localStorage.getItem('glaWeaponEnabled') === '1'; | |||
} catch (e) { | |||
return false; | |||
} | |||
} function getWeaponDataForIcon(iconEl) { | |||
if (!iconEl || !iconEl.dataset.weapon) return null; | |||
try { | |||
return JSON.parse(iconEl.dataset.weapon); | |||
} catch (e) { | |||
return null; | |||
} | |||
} function getEffectiveSkillVideoFromIcon(iconEl) { | |||
const weaponOn = globalWeaponEnabled; | |||
const weaponData = getWeaponDataForIcon(iconEl); | |||
const baseVideoFile = (iconEl.dataset.videoFile || '').trim(); | |||
const baseVideoURL = (iconEl.dataset.video || '').trim(); | |||
console.log('[Skills DEBUG]', { | |||
skillName: iconEl.dataset.nome || iconEl.dataset.name, | |||
weaponOn, | |||
hasWeaponData: !!weaponData, | |||
weaponData: weaponData, | |||
baseVideoFile, | |||
baseVideoURL | |||
}); | |||
if (weaponOn && weaponData) { | |||
console.log('[Skills] checking weapon video', { | |||
skillName: iconEl.dataset.nome || iconEl.dataset.name, | |||
function createVideoElement(videoURL, extraAttrs = {}) { | hasVideo: !!(weaponData.video), | ||
videoValue: weaponData.video, | |||
videoTrimmed: weaponData.video ? weaponData.video.trim() : '' | |||
}); | |||
if (weaponData.video && weaponData.video.trim() !== '') { | |||
console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video); | |||
return weaponData.video.trim(); | |||
} | |||
} | |||
const result = baseVideoFile || baseVideoURL || ''; | |||
console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result); | |||
return result; | |||
} function createVideoElement(videoURL, extraAttrs = { | |||
}) { | |||
const v = document.createElement('video'); | const v = document.createElement('video'); | ||
v.className = 'skill-video'; | v.className = 'skill-video'; | ||
v.setAttribute('controls', ''); | v.setAttribute('controls', ''); | ||
v.setAttribute('preload', ' | v.setAttribute('preload', 'auto'); | ||
v.setAttribute('playsinline', ''); | v.setAttribute('playsinline', ''); | ||
v.style.display = 'none'; | v.style.display = 'none'; | ||
| Linha 518: | Linha 772: | ||
v.style.aspectRatio = '16/9'; | v.style.aspectRatio = '16/9'; | ||
v.style.objectFit = 'cover'; | v.style.objectFit = 'cover'; | ||
Object.keys(extraAttrs).forEach(k => { | Object.keys(extraAttrs).forEach(k => { | ||
v.dataset[k] = extraAttrs[k]; | v.dataset[k] = extraAttrs[k]; | ||
}); | }); | ||
// Detectar formato do vídeo pela extensão | |||
const ext = (videoURL.split('.').pop() || '').toLowerCase().split('?')[0]; | |||
const mimeTypes = { | |||
'mp4': 'video/mp4', | |||
'm4v': 'video/mp4', | |||
'webm': 'video/webm', | |||
'ogv': 'video/ogg', | |||
'ogg': 'video/ogg', | |||
'mov': 'video/quicktime' | |||
}; | |||
const mimeType = mimeTypes[ext] || 'video/mp4'; | |||
const src = document.createElement('source'); | const src = document.createElement('source'); | ||
src.src = videoURL; | src.src = videoURL; | ||
src.type = | src.type = mimeType; | ||
v.appendChild(src); | v.appendChild(src); | ||
// Fallback para Safari/iOS mais antigos | |||
v.setAttribute('webkit-playsinline', ''); | |||
v.setAttribute('x-webkit-airplay', 'allow'); | |||
return v; | return v; | ||
} | } function precreateSubskillVideos() { | ||
if (!videoBox) return; | if (!videoBox) return; | ||
iconItems.forEach(parentIcon => { | iconItems.forEach(parentIcon => { | ||
| Linha 542: | Linha 807: | ||
const parentIdx = parentIcon.dataset.index || ''; | const parentIdx = parentIcon.dataset.index || ''; | ||
subs.forEach(s => { | subs.forEach(s => { | ||
const subName = (s.name || s.n || '').trim(); | const subName = (s.name || s.n || '').trim(); | ||
const key = `sub:${parentIdx}:${subName}`; | // Vídeo normal da subskill | ||
if (s.video && s.video.trim() !== '' && s.video !== 'Nada.png' && !s.video.toLowerCase().includes('nada.png')) { | |||
const key = `sub:${parentIdx}:${subName}`; | |||
if (subskillVideosCache.has(key)) return; | |||
if (! | const videoURL = normalizeFileURL(s.video); | ||
if (videoURL && videoURL.trim() !== '') { | |||
const v = createVideoElement(videoURL, { | |||
sub: '1', parentIndex: parentIdx, subName: subName | |||
}); | |||
v.setAttribute('preload', 'auto'); | |||
videoBox.appendChild(v); | |||
subskillVideosCache.set(key, v); | |||
// Força carregamento apenas se ainda não estiver carregando | |||
if (v.readyState === 0 || v.readyState === 1) { | |||
v.load(); | |||
} | |||
} | |||
} | |||
// Vídeo de weapon da subskill | |||
if (s.weapon && typeof s.weapon === 'object' && s.weapon.video && s.weapon.video.trim() !== '' && s.weapon.video !== 'Nada.png' && !s.weapon.video.toLowerCase().includes('nada.png')) { | |||
const weaponKey = `sub:${parentIdx}:${subName}:weapon`; | |||
if (subskillVideosCache.has(weaponKey)) return; | |||
const weaponVideoURL = normalizeFileURL(s.weapon.video); | |||
if (weaponVideoURL && weaponVideoURL.trim() !== '') { | |||
const v = createVideoElement(weaponVideoURL, { | |||
sub: '1', parentIndex: parentIdx, subName: subName, weapon: '1' | |||
}); | |||
v.setAttribute('preload', 'auto'); | |||
videoBox.appendChild(v); | |||
subskillVideosCache.set(weaponKey, v); | |||
// Força carregamento apenas se ainda não estiver carregando | |||
if (v.readyState === 0 || v.readyState === 1) { | |||
v.load(); | |||
} | |||
} | |||
} | |||
}); | }); | ||
} catch (e) { | } catch (e) { | ||
} | } | ||
}); | }); | ||
} | } setTimeout(precreateSubskillVideos, 100); | ||
if (iconItems.length && videoBox) { | if (iconItems.length && videoBox) { | ||
iconItems.forEach(el => { | iconItems.forEach(el => { | ||
const idx = el.dataset.index || ''; | const idx = el.dataset.index || ''; | ||
if (!src | // Vídeo normal | ||
const src = (el.dataset.videoFile || el.dataset.video || '').trim(); | |||
if (src && src !== 'Nada.png' && !src.toLowerCase().includes('nada.png') && !videosCache.has(idx)) { | |||
totalVideos++; | |||
const videoURL = normalizeFileURL(src); | |||
if (videoURL) { | |||
const v = createVideoElement(videoURL, { | |||
index: idx | |||
}); | |||
v.setAttribute('preload', 'auto'); | |||
v.style.maxWidth = '100%'; | |||
v.addEventListener('canplaythrough', () => { | |||
loadedVideos++; | |||
if (!userHasInteracted && loadedVideos === 1) { | |||
try { | |||
v.pause(); | |||
v.currentTime = 0; | |||
} catch (e) { | |||
} | |||
} if (loadedVideos === totalVideos) autoplay = true; | |||
}, { | |||
once: true | |||
}); | |||
v.addEventListener('error', () => { | |||
loadedVideos++; | |||
if (loadedVideos === totalVideos) autoplay = true; | |||
}, { | |||
once: true | |||
}); | |||
videoBox.appendChild(v); | |||
videosCache.set(idx, v); | |||
// Força carregamento | |||
v.load(); | |||
} | |||
} | |||
// Vídeo de weapon | |||
if (el.dataset.weapon) { | |||
try { | |||
const weaponData = JSON.parse(el.dataset.weapon); | |||
if (weaponData && weaponData.video && weaponData.video.trim() !== '' && weaponData.video !== 'Nada.png' && !weaponData.video.toLowerCase().includes('nada.png')) { | |||
const weaponKey = `weapon:${idx}:${(el.dataset.nome || el.dataset.name || '').trim()}`; | |||
if (!videosCache.has(weaponKey)) { | |||
totalVideos++; | |||
const weaponVideoURL = normalizeFileURL(weaponData.video); | |||
if (weaponVideoURL) { | |||
const v = createVideoElement(weaponVideoURL, { | |||
index: idx, weapon: '1' | |||
}); | |||
v.setAttribute('preload', 'auto'); | |||
v.style.maxWidth = '100%'; | |||
v.addEventListener('canplaythrough', () => { | |||
loadedVideos++; | |||
if (loadedVideos === totalVideos) autoplay = true; | |||
}, { | |||
once: true | |||
}); | |||
v.addEventListener('error', () => { | |||
loadedVideos++; | |||
if (loadedVideos === totalVideos) autoplay = true; | |||
}, { | |||
once: true | |||
}); | |||
videoBox.appendChild(v); | |||
videosCache.set(weaponKey, v); | |||
// Força carregamento | |||
v.load(); | |||
} | |||
} | |||
} | |||
} catch (e) { | |||
} | } | ||
} | |||
} | |||
}); | }); | ||
} | } function wireTooltipsForNewIcons() { | ||
const tip = document.querySelector('.skill-tooltip'); | const tip = document.querySelector('.skill-tooltip'); | ||
if (!tip) return; | if (!tip) return; | ||
let lockUntil2 = 0; | let lockUntil2 = 0; | ||
Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => { | Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => { | ||
if (icon.dataset.weaponToggle === '1' || icon.classList.contains('weapon-bar-toggle')) return; | |||
if (icon.dataset.tipwired) return; | if (icon.dataset.tipwired) return; | ||
icon.dataset.tipwired = '1'; | icon.dataset.tipwired = '1'; | ||
| Linha 615: | Linha 937: | ||
if (icon.hasAttribute('title')) icon.removeAttribute('title'); | if (icon.hasAttribute('title')) icon.removeAttribute('title'); | ||
const img = icon.querySelector('img'); | const img = icon.querySelector('img'); | ||
if (img) { const imgAlt = img.getAttribute('alt') || ''; const imgTitle = img.getAttribute('title') || ''; if (!label && (imgAlt || imgTitle)) icon.setAttribute('aria-label', imgAlt || imgTitle); img.setAttribute('alt', ''); if (img.hasAttribute('title')) img.removeAttribute('title'); | if (img) { | ||
const measureAndPos = (el) => { | const imgAlt = img.getAttribute('alt') || ''; | ||
const imgTitle = img.getAttribute('title') || ''; | |||
if (!label && (imgAlt || imgTitle)) icon.setAttribute('aria-label', imgAlt || imgTitle); | |||
img.setAttribute('alt', ''); | |||
if (img.hasAttribute('title')) img.removeAttribute('title'); | |||
} const measureAndPos = (el) => { | |||
if (!el || tip.getAttribute('aria-hidden') === 'true') return; | if (!el || tip.getAttribute('aria-hidden') === 'true') return; | ||
tip.style.left = '0px'; tip.style.top = '0px'; | tip.style.left = '0px'; | ||
const rect = el.getBoundingClientRect(); const tr = tip.getBoundingClientRect(); | tip.style.top = '0px'; | ||
const rect = el.getBoundingClientRect(); | |||
const tr = tip.getBoundingClientRect(); | |||
let left = Math.round(rect.left + (rect.width - tr.width) / 2); | let left = Math.round(rect.left + (rect.width - tr.width) / 2); | ||
left = Math.max(8, Math.min(left, window.innerWidth - tr.width - 8)); | left = Math.max(8, Math.min(left, window.innerWidth - tr.width - 8)); | ||
| Linha 625: | Linha 954: | ||
let top = coarse ? Math.round(rect.bottom + 10) : Math.round(rect.top - tr.height - 8); | let top = coarse ? Math.round(rect.bottom + 10) : Math.round(rect.top - tr.height - 8); | ||
if (top < 8) top = Math.round(rect.bottom + 10); | if (top < 8) top = Math.round(rect.bottom + 10); | ||
tip.style.left = left + 'px'; tip.style.top = top + 'px'; | tip.style.left = left + 'px'; | ||
tip.style.top = top + 'px'; | |||
}; | |||
const show = (el, text) => { | |||
tip.textContent = text || ''; | |||
tip.setAttribute('aria-hidden', 'false'); | |||
measureAndPos(el); | |||
tip.style.opacity = '1'; | |||
}; | |||
const hide = () => { | |||
tip.setAttribute('aria-hidden', 'true'); | |||
tip.style.opacity = '0'; | |||
tip.style.left = '-9999px'; | |||
tip.style.top = '-9999px'; | |||
}; | }; | ||
icon.addEventListener('mouseenter', () => show(icon, (icon.dataset.nome || icon.dataset.name || ''))); | icon.addEventListener('mouseenter', () => show(icon, (icon.dataset.nome || icon.dataset.name || ''))); | ||
icon.addEventListener('mousemove', () => { if (performance.now() >= lockUntil2) measureAndPos(icon); }); | icon.addEventListener('mousemove', () => { | ||
icon.addEventListener('click', () => { lockUntil2 = performance.now() + 240; measureAndPos(icon); }); | if (performance.now() >= lockUntil2) measureAndPos(icon); | ||
}); | |||
icon.addEventListener('click', () => { | |||
lockUntil2 = performance.now() + 240; | |||
measureAndPos(icon); | |||
}); | |||
icon.addEventListener('mouseleave', hide); | icon.addEventListener('mouseleave', hide); | ||
}); | }); | ||
} | } function showVideoForIcon(el) { | ||
userHasInteracted = true; | userHasInteracted = true; | ||
if (!videoBox) return; | |||
const effectiveVideo = getEffectiveSkillVideoFromIcon(el); | |||
if ( | if (!effectiveVideo || effectiveVideo.trim() === '') { | ||
videoBox.style.display = 'none'; | |||
return; | |||
} | |||
const videoURL = normalizeFileURL(effectiveVideo); | |||
if (!videoURL || videoURL.trim() === '') { | |||
videoBox.style.display = 'none'; | |||
return; | |||
} | } | ||
Array.from(videoBox.querySelectorAll('video.skill-video')).forEach(v => { | |||
try { | |||
v.pause(); | |||
} catch (e) { | |||
} | |||
v.style.display = 'none'; | |||
}); | |||
if (window.__subskills) window.__subskills.hideAll?.(videoBox); | if (window.__subskills) window.__subskills.hideAll?.(videoBox); | ||
const hasIdx = !!el.dataset.index; | const hasIdx = !!el.dataset.index; | ||
const | const weaponOn = globalWeaponEnabled; | ||
const weaponData = getWeaponDataForIcon(el); | |||
const isWeaponVideo = weaponOn && weaponData && weaponData.video && weaponData.video.trim() !== ''; | |||
console.log('[Skills] showVideoForIcon chamado', { | |||
if (hasIdx && videosCache.has(el.dataset.index)) { | skillName: el.dataset.nome || el.dataset.name, | ||
weaponOn, | |||
isWeaponVideo, | |||
effectiveVideo: getEffectiveSkillVideoFromIcon(el) | |||
}); | |||
const videoKey = isWeaponVideo ? `weapon:${getWeaponKey(el)}` : (el.dataset.index || ''); | |||
if (hasIdx && !isWeaponVideo && videosCache.has(el.dataset.index)) { | |||
const v = videosCache.get(el.dataset.index); | const v = videosCache.get(el.dataset.index); | ||
videoBox.style.display = 'block'; | videoBox.style.display = 'block'; | ||
v.style.display = 'block'; | v.style.display = 'block'; | ||
try { v.currentTime = 0; } catch (e) { } | try { | ||
v.currentTime = 0; | |||
} catch (e) { | |||
} | |||
const suppress = document.body.dataset.suppressSkillPlay === '1'; | const suppress = document.body.dataset.suppressSkillPlay === '1'; | ||
if (!suppress) { v.play().catch(() => { }); } else { try { v.pause(); } catch (e) { } } | if (!suppress) { | ||
v.play().catch(() => { | |||
}); | |||
} else { | |||
try { | |||
v.pause(); | |||
} catch (e) { | |||
} | |||
} | |||
return; | return; | ||
} | } | ||
let v = null; | |||
if (isWeaponVideo) { | |||
v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`); | |||
} else { | |||
if ( | v = nestedVideoElByIcon.get(el); | ||
} | } | ||
if (!v) { | if (!v) { | ||
v = createVideoElement( | v = createVideoElement(videoURL, isWeaponVideo ? { | ||
videoBox.appendChild(v); | weaponKey: videoKey | ||
nestedVideoElByIcon.set(el, v); | } : {}); | ||
if (isWeaponVideo) { | |||
videoBox.appendChild(v); | |||
} else { | |||
videoBox.appendChild(v); | |||
nestedVideoElByIcon.set(el, v); | |||
} | |||
} else { | |||
const src = v.querySelector('source'); | |||
if (src && src.src !== videoURL) { | |||
src.src = videoURL; | |||
v.load(); | |||
} | |||
} | } | ||
videoBox.style.display = 'block'; | videoBox.style.display = 'block'; | ||
v.style.display = 'block'; | v.style.display = 'block'; | ||
try { v.currentTime = 0; } catch (e) { } | try { | ||
v.currentTime = 0; | |||
} catch (e) { | |||
} | |||
const suppress = document.body.dataset.suppressSkillPlay === '1'; | const suppress = document.body.dataset.suppressSkillPlay === '1'; | ||
if (!suppress) { v.play().catch(() => { }); } else { try { v.pause(); } catch (e) { | if (!suppress) { | ||
v.play().catch(() => { | |||
}); | |||
} else { | |||
try { | |||
v.pause(); | |||
} catch (e) { | |||
} | |||
} | |||
} function activateSkill(el, options = { | |||
const { openSubs = true } = options; | }) { | ||
const { | |||
openSubs = true | |||
} = options; | |||
const tip = document.querySelector('.skill-tooltip'); | const tip = document.querySelector('.skill-tooltip'); | ||
if (tip) { | if (tip) { | ||
| Linha 710: | Linha 1 082: | ||
tip.style.left = '-9999px'; | tip.style.left = '-9999px'; | ||
tip.style.top = '-9999px'; | tip.style.top = '-9999px'; | ||
} | } const skillsRoot = document.getElementById('skills'); | ||
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : { | |||
}; | |||
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {}; | const L = i18nMap[getLangKey()] || i18nMap.pt || { | ||
const L = i18nMap[getLangKey()] || i18nMap.pt || { cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível' }; | cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível' | ||
}; | |||
const name = el.dataset.nome || el.dataset.name || ''; | const name = el.dataset.nome || el.dataset.name || ''; | ||
const level = (el.dataset.level || '').trim(); | const level = (el.dataset.level || '').trim(); | ||
let weaponData = null; | let weaponData = null; | ||
if (el.dataset.weapon) { | if (el.dataset.weapon) { | ||
try { | try { | ||
weaponData = JSON.parse(el.dataset.weapon); | weaponData = JSON.parse(el.dataset.weapon); | ||
} catch (e) { weaponData = null; } | } catch (e) { | ||
} | weaponData = null; | ||
} | |||
const weaponEquipped = hasWeapon && | } const hasWeapon = !!weaponData; | ||
const weaponEquipped = hasWeapon && globalWeaponEnabled; | |||
const lang = getLangKey(); | const lang = getLangKey(); | ||
const baseDescPack = { | const baseDescPack = { | ||
pt: el.dataset.descPt || '', | pt: el.dataset.descPt || '', en: el.dataset.descEn || '', es: el.dataset.descEs || '', pl: el.dataset.descPl || '' | ||
}; | }; | ||
const baseDesc = baseDescPack[lang] || baseDescPack.pt || baseDescPack.en || baseDescPack.es || baseDescPack.pl || el.dataset.desc || ''; | const baseDesc = baseDescPack[lang] || baseDescPack.pt || baseDescPack.en || baseDescPack.es || baseDescPack.pl || el.dataset.desc || ''; | ||
// Aceita tanto desc_i18n quanto desc para compatibilidade | |||
// | |||
let weaponDescPack = {}; | let weaponDescPack = {}; | ||
if (weaponData | if (weaponData) { | ||
if (weaponData.desc_i18n) { | |||
weaponDescPack = weaponData.desc_i18n; | |||
weaponDescPack = { | } else if (weaponData.desc) { | ||
weaponDescPack = weaponData.desc; | |||
} else { | |||
weaponDescPack = { | |||
pt: weaponData.descPt || '', en: weaponData.descEn || '', es: weaponData.descEs || '', pl: weaponData.descPl || '' | |||
} | }; | ||
} | |||
} | } | ||
const weaponDesc = weaponDescPack[lang] || weaponDescPack.pt || weaponDescPack.en || weaponDescPack.es || weaponDescPack.pl || ''; | const weaponDesc = weaponDescPack[lang] || weaponDescPack.pt || weaponDescPack.en || weaponDescPack.es || weaponDescPack.pl || ''; | ||
const chosenDesc = (weaponEquipped && weaponDesc) ? weaponDesc : baseDesc; | const chosenDesc = (weaponEquipped && weaponDesc) ? weaponDesc : baseDesc; | ||
const descHtml = chosenDesc.replace(/'''(.*?)'''/g, '<b>$1</b>'); | const descHtml = chosenDesc.replace(/'''(.*?)'''/g, '<b>$1</b>'); | ||
let attrsHTML = ''; | let attrsHTML = ''; | ||
if (weaponEquipped && weaponData) { | if (weaponEquipped && weaponData) { | ||
// | // Usa valores do weapon, mas só se existirem (não herda da skill base) | ||
const wPve = (weaponData.powerpve | const wPve = (weaponData.powerpve !== undefined && weaponData.powerpve !== null && weaponData.powerpve !== '') ? weaponData.powerpve.toString().trim() : ''; | ||
const wPvp = (weaponData.powerpvp | const wPvp = (weaponData.powerpvp !== undefined && weaponData.powerpvp !== null && weaponData.powerpvp !== '') ? weaponData.powerpvp.toString().trim() : ''; | ||
const wEnergy = (weaponData.energy | const wEnergy = (weaponData.energy !== undefined && weaponData.energy !== null && weaponData.energy !== '') ? weaponData.energy.toString().trim() : ''; | ||
const wCd = (weaponData.cooldown | const wCd = (weaponData.cooldown !== undefined && weaponData.cooldown !== null && weaponData.cooldown !== '') ? weaponData.cooldown.toString().trim() : ''; | ||
const weaponAttrs = [wPve, wPvp, wEnergy, wCd].join(','); | const weaponAttrs = [wPve, wPvp, wEnergy, wCd].join(','); | ||
console.log('[Skills] weaponAttrs string:', weaponAttrs, { wPve, wPvp, wEnergy, wCd, weaponData }); | |||
attrsHTML = renderAttributes(weaponAttrs); | attrsHTML = renderAttributes(weaponAttrs); | ||
} else { | } else { | ||
attrsHTML = el.dataset.atr ? renderAttributes(el.dataset.atr) : (el.dataset.subattrs ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L) : ''); | attrsHTML = el.dataset.atr ? renderAttributes(el.dataset.atr) : (el.dataset.subattrs ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L) : ''); | ||
} | } let flagsHTML = ''; | ||
if (el.dataset.flags) { | if (el.dataset.flags) { | ||
try { | try { | ||
const flags = JSON.parse(el.dataset.flags); | const flags = JSON.parse(el.dataset.flags); | ||
flagsHTML = renderFlagsRow(flags); | flagsHTML = renderFlagsRow(flags); | ||
} catch (e) { | } catch (e) { | ||
} | } | ||
} | } if (descBox) { | ||
descBox.innerHTML = `<div class="skill-title"><h3>${name}</h3></div>${level ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>` : ''}${attrsHTML}<div class="desc">${descHtml}</div>`; | |||
if (videoBox) { | } if (hasWeapon) { | ||
applyWeaponBadge(el, weaponData, weaponEquipped); | |||
} if (videoBox) { | |||
const oldFlags = videoBox.querySelector('.skill-flags'); | const oldFlags = videoBox.querySelector('.skill-flags'); | ||
if (oldFlags) oldFlags.remove(); | if (oldFlags) oldFlags.remove(); | ||
if (flagsHTML) { | if (flagsHTML) { | ||
videoBox.insertAdjacentHTML('beforeend', flagsHTML); | videoBox.insertAdjacentHTML('beforeend', flagsHTML); | ||
applyFlagTooltips(videoBox); | applyFlagTooltips(videoBox); | ||
} | } | ||
} | } const currIcons = Array.from(iconsBar.querySelectorAll('.skill-icon')); | ||
currIcons.forEach(i => i.classList.remove('active')); | currIcons.forEach(i => i.classList.remove('active')); | ||
el.classList.add('active'); | el.classList.add('active'); | ||
if (!autoplay && loadedVideos > 0) autoplay = true; | if (!autoplay && loadedVideos > 0) autoplay = true; | ||
window.__lastActiveSkillIcon = el; | window.__lastActiveSkillIcon = el; | ||
// Lógica de vídeo: usa função centralizada que já considera weapon | |||
showVideoForIcon(el); | |||
const subsRaw = el.dataset.subs || el.getAttribute('data-subs'); | const subsRaw = el.dataset.subs || el.getAttribute('data-subs'); | ||
const isBack = el.dataset.back === 'true' || el.getAttribute('data-back') === 'true' || el.dataset.back === 'yes' || el.getAttribute('data-back') === 'yes' || el.dataset.back === '1' || el.getAttribute('data-back') === '1'; | const isBack = el.dataset.back === 'true' || el.getAttribute('data-back') === 'true' || el.dataset.back === 'yes' || el.getAttribute('data-back') === 'yes' || el.dataset.back === '1' || el.getAttribute('data-back') === '1'; | ||
if (isBack && barStack.length) { | if (isBack && barStack.length) { | ||
const prev = barStack.pop(); | const prev = barStack.pop(); | ||
| Linha 872: | Linha 1 165: | ||
if (btn) btn.style.display = barStack.length ? 'block' : 'none'; | if (btn) btn.style.display = barStack.length ? 'block' : 'none'; | ||
return; | return; | ||
} | } if (openSubs && subsRaw && subsRaw.trim() !== '') { | ||
if (barStack.length && barStack[barStack.length - 1].parentIcon === el) return; | if (barStack.length && barStack[barStack.length - 1].parentIcon === el) return; | ||
try { | try { | ||
const subs = JSON.parse(subsRaw); | const subs = JSON.parse(subsRaw); | ||
pushSubBarFrom(subs, el); | pushSubBarFrom(subs, el); | ||
} catch { | } catch { | ||
} | |||
} | } | ||
} | } function wireClicksForCurrentBar() { | ||
const currIcons = Array.from(iconsBar.querySelectorAll('.skill-icon')); | const currIcons = Array.from(iconsBar.querySelectorAll('.skill-icon')); | ||
currIcons.forEach(el => { | currIcons.forEach(el => { | ||
if (el.dataset.weaponToggle === '1' || el.classList.contains('weapon-bar-toggle')) return; | |||
if (el.dataset.wired) return; | if (el.dataset.wired) return; | ||
el.dataset.wired = '1'; | el.dataset.wired = '1'; | ||
| Linha 895: | Linha 1 182: | ||
el.setAttribute('aria-label', label); | el.setAttribute('aria-label', label); | ||
if (el.hasAttribute('title')) el.removeAttribute('title'); | if (el.hasAttribute('title')) el.removeAttribute('title'); | ||
const img = el.querySelector('img'); if (img) { img.setAttribute('alt', ''); if (img.hasAttribute('title')) img.removeAttribute('title'); | const img = el.querySelector('img'); | ||
el.addEventListener('click', () => { | if (img) { | ||
activateSkill(el, { openSubs: true }); | img.setAttribute('alt', ''); | ||
if (img.hasAttribute('title')) img.removeAttribute('title'); | |||
} el.addEventListener('click', () => { | |||
activateSkill(el, { | |||
openSubs: true | |||
}); | |||
}); | }); | ||
}); | }); | ||
wireTooltipsForNewIcons(); | wireTooltipsForNewIcons(); | ||
} | } function animateIconsBarEntrance() { | ||
Array.from(iconsBar.children).forEach((c, i) => { | Array.from(iconsBar.children).forEach((c, i) => { | ||
c.style.opacity = '0'; | c.style.opacity = '0'; | ||
| Linha 916: | Linha 1 205: | ||
}); | }); | ||
}); | }); | ||
} | } function snapshotCurrentBarItemsFromDOM() { | ||
return Array.from(iconsBar.querySelectorAll('.skill-icon')).filter(el => el.dataset.weaponToggle !== '1').map(el => { | |||
return Array.from(iconsBar.querySelectorAll('.skill-icon')).map(el => { | |||
const img = el.querySelector('img'); | const img = el.querySelector('img'); | ||
const iconURL = img ? img.src : ''; | const iconURL = img ? img.src : ''; | ||
const subsRaw = el.dataset.subs || el.getAttribute('data-subs') || ''; | const subsRaw = el.dataset.subs || el.getAttribute('data-subs') || ''; | ||
let subs = null; try { subs = subsRaw ? JSON.parse(subsRaw) : null; } catch { subs = null; | let subs = null; | ||
const subattrsRaw = el.dataset.subattrs || ''; | try { | ||
subs = subsRaw ? JSON.parse(subsRaw) : null; | |||
} catch { | |||
subs = null; | |||
} const subattrsRaw = el.dataset.subattrs || ''; | |||
let flags = null; | let flags = null; | ||
if (el.dataset.flags) { | if (el.dataset.flags) { | ||
try { | try { | ||
flags = JSON.parse(el.dataset.flags); | flags = JSON.parse(el.dataset.flags); | ||
} catch (e) { } | } catch (e) { | ||
} | } | ||
return { | } let weapon = null; | ||
name: el.dataset.nome || el.dataset.name || '', | if (el.dataset.weapon) { | ||
try { | |||
weapon = JSON.parse(el.dataset.weapon); | |||
} catch (e) { | |||
} | |||
} return { | |||
name: el.dataset.nome || el.dataset.name || '', index: el.dataset.index || '', level: el.dataset.level || '', desc: el.dataset.desc || '', descPt: el.dataset.descPt || '', descEn: el.dataset.descEn || '', descEs: el.dataset.descEs || '', descPl: el.dataset.descPl || '', attrs: el.dataset.atr || el.dataset.attrs || '', video: el.dataset.video || '', iconURL, subs, subattrsStr: subattrsRaw, flags: flags, weapon: weapon | |||
}; | }; | ||
}); | }); | ||
} | } function ensureBackButton() { | ||
const rail = iconsBar.closest('.top-rail.skills'); | |||
if (!rail) return null; | |||
const rail = iconsBar.closest('.top-rail.skills'); if (!rail) return null; | |||
let wrap = rail.parentElement; | let wrap = rail.parentElement; | ||
if (!wrap || !wrap.classList || !wrap.classList.contains('skills-rail-wrap')) { | if (!wrap || !wrap.classList || !wrap.classList.contains('skills-rail-wrap')) { | ||
| Linha 958: | Linha 1 240: | ||
const newWrap = document.createElement('div'); | const newWrap = document.createElement('div'); | ||
newWrap.className = 'skills-rail-wrap'; | newWrap.className = 'skills-rail-wrap'; | ||
parentNode.insertBefore(newWrap, rail); | parentNode.insertBefore(newWrap, rail); | ||
newWrap.appendChild(rail); | newWrap.appendChild(rail); | ||
wrap = newWrap; | wrap = newWrap; | ||
} | } let backWrap = wrap.querySelector('.skills-back-wrapper'); | ||
if (!backWrap) { | if (!backWrap) { | ||
backWrap = document.createElement('div'); | backWrap = document.createElement('div'); | ||
| Linha 972: | Linha 1 251: | ||
btnInner.type = 'button'; | btnInner.type = 'button'; | ||
btnInner.setAttribute('aria-label', 'Voltar'); | btnInner.setAttribute('aria-label', 'Voltar'); | ||
btnInner.innerHTML = '<svg class="back-chevron" width="100%" height="100%" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" preserveAspectRatio="xMidYMid meet"><path d="M10 2L4 16L10 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 2L14 16L20 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M30 2L24 16L30 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'; | btnInner.innerHTML = '<svg class="back-chevron" width="100%" height="100%" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" preserveAspectRatio="xMidYMid meet"><path d="M10 2L4 16L10 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 2L14 16L20 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M30 2L24 16L30 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'; | ||
backWrap.appendChild(btnInner); | backWrap.appendChild(btnInner); | ||
| Linha 984: | Linha 1 262: | ||
if (!barStack.length) btnInner.classList.remove('peek'); | if (!barStack.length) btnInner.classList.remove('peek'); | ||
}); | }); | ||
} | } backWrap.style.display = barStack.length ? 'block' : 'none'; | ||
wrap.classList.toggle('has-sub-bar', barStack.length > 0); | wrap.classList.toggle('has-sub-bar', barStack.length > 0); | ||
const btnInner = backWrap.querySelector('.skills-back'); | const btnInner = backWrap.querySelector('.skills-back'); | ||
return btnInner; | return btnInner; | ||
} | } function renderBarFromItems(items) { | ||
const tip = document.querySelector('.skill-tooltip'); | const tip = document.querySelector('.skill-tooltip'); | ||
if (tip) { | if (tip) { | ||
| Linha 1 001: | Linha 1 273: | ||
tip.style.left = '-9999px'; | tip.style.left = '-9999px'; | ||
tip.style.top = '-9999px'; | tip.style.top = '-9999px'; | ||
} | } iconsBar.innerHTML = ''; | ||
items.forEach((it, idx) => { | items.forEach((it, idx) => { | ||
const node = document.createElement('div'); node.className = 'skill-icon'; | const node = document.createElement('div'); | ||
node.className = 'skill-icon'; | |||
node.dataset.nome = it.name || ''; | node.dataset.nome = it.name || ''; | ||
if (it.index) node.dataset.index = it.index; | if (it.index) node.dataset.index = it.index; | ||
if (it.level) node.dataset.level = it.level; | if (it.level) node.dataset.level = it.level; | ||
| Linha 1 019: | Linha 1 290: | ||
if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr; | if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr; | ||
if (it.flags) node.dataset.flags = JSON.stringify(it.flags); | if (it.flags) node.dataset.flags = JSON.stringify(it.flags); | ||
if (it.weapon) node.dataset.weapon = JSON.stringify(it.weapon); | |||
if (!it.index) node.dataset.nested = '1'; | if (!it.index) node.dataset.nested = '1'; | ||
const img = document.createElement('img'); img.alt = ''; img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : ''); node.appendChild(img); | const img = document.createElement('img'); | ||
img.alt = ''; | |||
img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : ''); | |||
node.appendChild(img); | |||
iconsBar.appendChild(node); | iconsBar.appendChild(node); | ||
}); | }); | ||
animateIconsBarEntrance(); | animateIconsBarEntrance(); | ||
wireClicksForCurrentBar(); const b = ensureBackButton(); if (b) b.classList.add('peek'); | wireClicksForCurrentBar(); | ||
} | setupWeaponBarToggle(hasWeaponSkillAvailable); | ||
const b = ensureBackButton(); | |||
if (b) b.classList.add('peek'); | |||
} function pushSubBarFrom(subs, parentIconEl) { | |||
const tip = document.querySelector('.skill-tooltip'); | const tip = document.querySelector('.skill-tooltip'); | ||
if (tip) { | if (tip) { | ||
| Linha 1 036: | Linha 1 310: | ||
tip.style.left = '-9999px'; | tip.style.left = '-9999px'; | ||
tip.style.top = '-9999px'; | tip.style.top = '-9999px'; | ||
} | } const parentNameSnapshot = parentIconEl ? (parentIconEl.dataset.nome || parentIconEl.dataset.name || '') : ''; | ||
const parentIndexSnapshot = parentIconEl ? (parentIconEl.dataset.index || '') : ''; | const parentIndexSnapshot = parentIconEl ? (parentIconEl.dataset.index || '') : ''; | ||
barStack.push({ | barStack.push({ | ||
items: snapshotCurrentBarItemsFromDOM(), | items: snapshotCurrentBarItemsFromDOM(), parentIcon: parentIconEl, parentName: parentNameSnapshot, parentIndex: parentIndexSnapshot | ||
}); | }); | ||
ensureBackButton(); | ensureBackButton(); | ||
| Linha 1 057: | Linha 1 326: | ||
const slug = slugify(parentIconEl.dataset.nome || parentIconEl.dataset.name || ''); | const slug = slugify(parentIconEl.dataset.nome || parentIconEl.dataset.name || ''); | ||
if (slug) cacheKey = `slug:${slug}`; | if (slug) cacheKey = `slug:${slug}`; | ||
} | } if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey; | ||
} | } | ||
} | } if (cacheKey) { | ||
const cached = subBarTemplateCache.get(cacheKey); | const cached = subBarTemplateCache.get(cacheKey); | ||
if (cached && cached.lang === langKey) { | if (cached && cached.lang === langKey) { | ||
| Linha 1 068: | Linha 1 335: | ||
iconsBar.appendChild(clone); | iconsBar.appendChild(clone); | ||
animateIconsBarEntrance(); | animateIconsBarEntrance(); | ||
wireClicksForCurrentBar(); const cachedBtn = ensureBackButton(); if (cachedBtn) cachedBtn.classList.add('peek'); | wireClicksForCurrentBar(); | ||
setupWeaponBarToggle(hasWeaponSkillAvailable); | |||
const cachedBtn = ensureBackButton(); | |||
if (cachedBtn) cachedBtn.classList.add('peek'); | |||
return; | return; | ||
} | } | ||
} | } const skillsRoot = document.getElementById('skills'); | ||
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : { | |||
}; | |||
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {}; | const L = i18nMap[getLangKey()] || i18nMap.pt || { | ||
const L = i18nMap[getLangKey()] || i18nMap.pt || { cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível' }; | cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível' | ||
const items = ( | }; | ||
const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta); | |||
const items = (hydratedSubs || []).filter(s => { | |||
// Filtra só se não tem nada útil | |||
const hasName = (s.name || s.n || '').trim() !== ''; | |||
const hasIcon = (s.icon || '').trim() !== '' && s.icon !== 'Nada.png'; | |||
const hasRef = (s.refS || s.refM || '').toString().trim() !== ''; | |||
return hasName || hasIcon || hasRef; | |||
}).map(s => { | |||
const name = (s.name || s.n || '').trim(); | const name = (s.name || s.n || '').trim(); | ||
const desc = chooseDescFrom(s).replace(/'''(.*?)'''/g, '<b>$1</b>'); | const desc = chooseDescFrom(s).replace(/'''(.*?)'''/g, '<b>$1</b>'); | ||
const attrsHTML = renderSubAttributesFromObj(s, L); | const attrsHTML = renderSubAttributesFromObj(s, L); | ||
return { | return { | ||
name, | name, level: (s.level || '').toString().trim(), desc, descPt: (s.descPt || (s.desc_i18n && s.desc_i18n.pt) || ''), descEn: (s.descEn || (s.desc_i18n && s.desc_i18n.en) || ''), descEs: (s.descEs || (s.desc_i18n && s.desc_i18n.es) || ''), descPl: (s.descPl || (s.desc_i18n && s.desc_i18n.pl) || ''), attrs: '', icon: (s.icon || 'Nada.png'), iconURL: filePathURL(s.icon || 'Nada.png'), video: s.video ? filePathURL(s.video) : '', subs: Array.isArray(s.subs) ? s.subs : null, subattrs: s, flags: Array.isArray(s.flags) ? s.flags : null, back: (s.back === true || s.back === 'true' || s.back === 'yes' || s.back === '1') ? 'true' : null, weapon: s.weapon || null | ||
}; | }; | ||
}); | }); | ||
const fragment = document.createDocumentFragment(); | const fragment = document.createDocumentFragment(); | ||
items.forEach((it, iIdx) => { | items.forEach((it, iIdx) => { | ||
const node = document.createElement('div'); node.className = 'skill-icon'; node.dataset.nested = '1'; | const node = document.createElement('div'); | ||
node.className = 'skill-icon'; | |||
node.dataset.nested = '1'; | |||
node.dataset.nome = it.name || ''; | node.dataset.nome = it.name || ''; | ||
node.dataset.parentIndex = parentIndexSnapshot; | node.dataset.parentIndex = parentIndexSnapshot; | ||
node.dataset.subName = it.name || ''; | node.dataset.subName = it.name || ''; | ||
const subSlug = slugify(it.name || ''); | const subSlug = slugify(it.name || ''); | ||
if (subSlug) node.dataset.slug = subSlug; | if (subSlug) node.dataset.slug = subSlug; | ||
| Linha 1 122: | Linha 1 383: | ||
if (it.flags) node.dataset.flags = JSON.stringify(it.flags); | if (it.flags) node.dataset.flags = JSON.stringify(it.flags); | ||
if (it.back) node.dataset.back = it.back; | if (it.back) node.dataset.back = it.back; | ||
const img = document.createElement('img'); img.alt = ''; img.src = it.iconURL; node.appendChild(img); | if (it.weapon) { | ||
try { | |||
node.dataset.weapon = JSON.stringify(it.weapon); | |||
} catch (e) { | |||
console.error('[Skills] Erro ao serializar weapon de subskill', it.name, e); | |||
} | |||
} | |||
const img = document.createElement('img'); | |||
img.alt = ''; | |||
img.src = it.iconURL; | |||
node.appendChild(img); | |||
fragment.appendChild(node); | fragment.appendChild(node); | ||
}); | }); | ||
| Linha 1 129: | Linha 1 400: | ||
iconsBar.appendChild(fragment); | iconsBar.appendChild(fragment); | ||
animateIconsBarEntrance(); | animateIconsBarEntrance(); | ||
wireClicksForCurrentBar(); const b2 = ensureBackButton(); if (b2) b2.classList.add('peek'); | wireClicksForCurrentBar(); | ||
setupWeaponBarToggle(hasWeaponSkillAvailable); | |||
const b2 = ensureBackButton(); | |||
if (b2) b2.classList.add('peek'); | |||
if (cacheKey) { | if (cacheKey) { | ||
subBarTemplateCache.set(cacheKey, { | subBarTemplateCache.set(cacheKey, { | ||
template: templateClone, | template: templateClone, lang: langKey | ||
}); | }); | ||
} | } | ||
} | } window.addEventListener('gla:langChanged', () => { | ||
subBarTemplateCache.clear(); | subBarTemplateCache.clear(); | ||
const skillsRoot = document.getElementById('skills'); | const skillsRoot = document.getElementById('skills'); | ||
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {}; | const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : { | ||
}; | |||
const lang = getLangKey(); | const lang = getLangKey(); | ||
Array.from(iconsBar.querySelectorAll('.skill-icon')).forEach(icon => { | Array.from(iconsBar.querySelectorAll('.skill-icon')).forEach(icon => { | ||
const pack = { | const pack = { | ||
pt: icon.dataset.descPt || '', | pt: icon.dataset.descPt || '', en: icon.dataset.descEn || '', es: icon.dataset.descEs || '', pl: icon.dataset.descPl || '' | ||
}; | }; | ||
const chosen = (pack[lang] || pack.pt || pack.en || pack.es || pack.pl || icon.dataset.desc || '').trim(); | const chosen = (pack[lang] || pack.pt || pack.en || pack.es || pack.pl || icon.dataset.desc || '').trim(); | ||
if (chosen) icon.dataset.desc = chosen; | if (chosen) icon.dataset.desc = chosen; | ||
}); | }); | ||
barStack.forEach(frame => { | barStack.forEach(frame => { | ||
(frame.items || []).forEach(it => { | (frame.items || []).forEach(it => { | ||
const pack = { pt: it.descPt, en: it.descEn, es: it.descEs, pl: it.descPl }; | const pack = { | ||
pt: it.descPt, en: it.descEn, es: it.descEs, pl: it.descPl | |||
}; | |||
const chosen = (pack[lang] || pack.pt || pack.en || pack.es || pack.pl || it.desc || ''); | const chosen = (pack[lang] || pack.pt || pack.en || pack.es || pack.pl || it.desc || ''); | ||
it.desc = chosen; | it.desc = chosen; | ||
}); | }); | ||
}); | }); | ||
if (descBox) { | if (descBox) { | ||
applyFlagTooltips(descBox); | applyFlagTooltips(descBox); | ||
} | } const activeIcon = window.__lastActiveSkillIcon; | ||
if (activeIcon && activeIcon.dataset.weapon) { | if (activeIcon && activeIcon.dataset.weapon) { | ||
activateSkill(activeIcon, { openSubs: false }); | activateSkill(activeIcon, { | ||
openSubs: false | |||
}); | |||
} | } | ||
}); | }); | ||
wireClicksForCurrentBar(); | |||
const b0 = ensureBackButton(); | |||
if (b0) { | |||
b0.classList.add('peek'); | |||
b0.style.alignSelf = 'stretch'; | |||
} (function initSkillTooltip() { | |||
(function initSkillTooltip() { | |||
if (document.querySelector('.skill-tooltip')) return; | if (document.querySelector('.skill-tooltip')) return; | ||
const tip = document.createElement('div'); | const tip = document.createElement('div'); | ||
| Linha 1 187: | Linha 1 452: | ||
tip.setAttribute('aria-hidden', 'true'); | tip.setAttribute('aria-hidden', 'true'); | ||
document.body.appendChild(tip); | document.body.appendChild(tip); | ||
const lockUntilRef = { | |||
const lockUntilRef = { value: 0 }; | value: 0 | ||
}; | |||
function measureAndPos(el) { | function measureAndPos(el) { | ||
if (!el || tip.getAttribute('aria-hidden') === 'true') return; | if (!el || tip.getAttribute('aria-hidden') === 'true') return; | ||
| Linha 1 203: | Linha 1 468: | ||
tip.style.left = left + 'px'; | tip.style.left = left + 'px'; | ||
tip.style.top = top + 'px'; | tip.style.top = top + 'px'; | ||
} | } function show(el, text) { | ||
tip.textContent = text || ''; | tip.textContent = text || ''; | ||
tip.setAttribute('aria-hidden', 'false'); | tip.setAttribute('aria-hidden', 'false'); | ||
measureAndPos(el); | measureAndPos(el); | ||
tip.style.opacity = '1'; | tip.style.opacity = '1'; | ||
} | } function hide() { | ||
tip.setAttribute('aria-hidden', 'true'); | tip.setAttribute('aria-hidden', 'true'); | ||
tip.style.opacity = '0'; | tip.style.opacity = '0'; | ||
tip.style.left = '-9999px'; | tip.style.left = '-9999px'; | ||
tip.style.top = '-9999px'; | tip.style.top = '-9999px'; | ||
} | } window.__globalSkillTooltip = { | ||
show, hide, measureAndPos, lockUntil: lockUntilRef | |||
show, | |||
}; | }; | ||
Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => { | Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => { | ||
if (icon.dataset.weaponToggle === '1' || icon.classList.contains('weapon-bar-toggle')) return; | |||
if (icon.dataset.tipwired) return; | if (icon.dataset.tipwired) return; | ||
icon.dataset.tipwired = '1'; | icon.dataset.tipwired = '1'; | ||
| Linha 1 233: | Linha 1 488: | ||
if (label && !icon.hasAttribute('aria-label')) icon.setAttribute('aria-label', label); | if (label && !icon.hasAttribute('aria-label')) icon.setAttribute('aria-label', label); | ||
if (icon.hasAttribute('title')) icon.removeAttribute('title'); | if (icon.hasAttribute('title')) icon.removeAttribute('title'); | ||
const img = icon.querySelector('img'); | const img = icon.querySelector('img'); | ||
if (img) { | if (img) { | ||
| Linha 1 241: | Linha 1 495: | ||
img.setAttribute('alt', ''); | img.setAttribute('alt', ''); | ||
if (img.hasAttribute('title')) img.removeAttribute('title'); | if (img.hasAttribute('title')) img.removeAttribute('title'); | ||
} | } icon.addEventListener('mouseenter', () => show(icon, label)); | ||
icon.addEventListener('mousemove', () => { | |||
if (performance.now() >= lockUntilRef.value) measureAndPos(icon); | |||
icon.addEventListener('mousemove', () => { if (performance.now() >= lockUntilRef.value) measureAndPos(icon); }); | }); | ||
icon.addEventListener('click', () => { lockUntilRef.value = performance.now() + 240; measureAndPos(icon); }); | icon.addEventListener('click', () => { | ||
lockUntilRef.value = performance.now() + 240; | |||
measureAndPos(icon); | |||
}); | |||
icon.addEventListener('mouseleave', hide); | icon.addEventListener('mouseleave', hide); | ||
}); | }); | ||
Array.from(document.querySelectorAll('.subskills-rail .subicon')).forEach(sub => { | Array.from(document.querySelectorAll('.subskills-rail .subicon')).forEach(sub => { | ||
if (sub.dataset.tipwired) return; | if (sub.dataset.tipwired) return; | ||
| Linha 1 257: | Linha 1 512: | ||
if (sub.hasAttribute('title')) sub.removeAttribute('title'); | if (sub.hasAttribute('title')) sub.removeAttribute('title'); | ||
sub.addEventListener('mouseenter', () => show(sub, label)); | sub.addEventListener('mouseenter', () => show(sub, label)); | ||
sub.addEventListener('mousemove', () => { if (performance.now() >= lockUntilRef.value) measureAndPos(sub); }); | sub.addEventListener('mousemove', () => { | ||
sub.addEventListener('click', () => { lockUntilRef.value = performance.now() + 240; measureAndPos(sub); }); | if (performance.now() >= lockUntilRef.value) measureAndPos(sub); | ||
}); | |||
sub.addEventListener('click', () => { | |||
lockUntilRef.value = performance.now() + 240; | |||
measureAndPos(sub); | |||
}); | |||
sub.addEventListener('mouseleave', hide); | sub.addEventListener('mouseleave', hide); | ||
}); | }); | ||
window.addEventListener('scroll', () => { | window.addEventListener('scroll', () => { | ||
const visible = document.querySelector('.skill-tooltip[aria-hidden="false"]'); | const visible = document.querySelector('.skill-tooltip[aria-hidden="false"]'); | ||
if (!visible) return; | if (!visible) return; | ||
const target = document.querySelector('.subskills-rail .subicon:hover') | const target = document.querySelector('.subskills-rail .subicon:hover') || document.querySelector('.subskills-rail .subicon.active') || document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active'); | ||
measureAndPos(target); | measureAndPos(target); | ||
}, true); | }, true); | ||
window.addEventListener('resize', () => { | window.addEventListener('resize', () => { | ||
const target = document.querySelector('.subskills-rail .subicon:hover') | const target = document.querySelector('.subskills-rail .subicon:hover') || document.querySelector('.subskills-rail .subicon.active') || document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active'); | ||
measureAndPos(target); | measureAndPos(target); | ||
}); | }); | ||
})(); | })(); | ||
(function initTabs() { | (function initTabs() { | ||
const tabs = Array.from(document.querySelectorAll('.tab-btn')); | const tabs = Array.from(document.querySelectorAll('.tab-btn')); | ||
| Linha 1 288: | Linha 1 537: | ||
const contents = Array.from(document.querySelectorAll('.tab-content')); | const contents = Array.from(document.querySelectorAll('.tab-content')); | ||
const characterBox = document.querySelector('.character-box'); | const characterBox = document.querySelector('.character-box'); | ||
let wrapper = characterBox.querySelector('.tabs-height-wrapper'); | let wrapper = characterBox.querySelector('.tabs-height-wrapper'); | ||
if (!wrapper) { | if (!wrapper) { | ||
wrapper = document.createElement('div'); | wrapper = document.createElement('div'); | ||
wrapper.className = 'tabs-height-wrapper'; | wrapper.className = 'tabs-height-wrapper'; | ||
contents.forEach(c => { | contents.forEach(c => { | ||
wrapper.appendChild(c); | wrapper.appendChild(c); | ||
}); | }); | ||
const tabsElement = characterBox.querySelector('.character-tabs'); | const tabsElement = characterBox.querySelector('.character-tabs'); | ||
if (tabsElement && tabsElement.nextSibling) { | if (tabsElement && tabsElement.nextSibling) { | ||
| Linha 1 307: | Linha 1 550: | ||
characterBox.appendChild(wrapper); | characterBox.appendChild(wrapper); | ||
} | } | ||
} | } async function smoothHeightTransition(fromTab, toTab) { | ||
if (!wrapper) return Promise.resolve(); | if (!wrapper) return Promise.resolve(); | ||
const scrollY = window.scrollY; | const scrollY = window.scrollY; | ||
const currentHeight = wrapper.getBoundingClientRect().height; | const currentHeight = wrapper.getBoundingClientRect().height; | ||
await new Promise((resolve) => { | await new Promise((resolve) => { | ||
const videoContainers = toTab.querySelectorAll('.video-container'); | const videoContainers = toTab.querySelectorAll('.video-container'); | ||
const contentCard = toTab.querySelector('.content-card'); | const contentCard = toTab.querySelector('.content-card'); | ||
if (videoContainers.length === 0) { | if (videoContainers.length === 0) { | ||
requestAnimationFrame(() => { | requestAnimationFrame(() => { | ||
requestAnimationFrame(() => { | requestAnimationFrame(() => { | ||
| Linha 1 334: | Linha 1 564: | ||
}); | }); | ||
return; | return; | ||
} | } let lastHeight = 0; | ||
let stableCount = 0; | let stableCount = 0; | ||
const checksNeeded = 3; | const checksNeeded = 3; | ||
let totalChecks = 0; | let totalChecks = 0; | ||
const maxChecks = 15; | const maxChecks = 15; | ||
function checkStability() { | function checkStability() { | ||
totalChecks++; | totalChecks++; | ||
const currentTabHeight = toTab.scrollHeight; | const currentTabHeight = toTab.scrollHeight; | ||
if (Math.abs(currentTabHeight - lastHeight) < 5) { | if (Math.abs(currentTabHeight - lastHeight) < 5) { | ||
stableCount++; | stableCount++; | ||
} else { | } else { | ||
stableCount = 0; | stableCount = 0; | ||
} | } lastHeight = currentTabHeight; | ||
if (stableCount >= checksNeeded || totalChecks >= maxChecks) { | if (stableCount >= checksNeeded || totalChecks >= maxChecks) { | ||
resolve(); | resolve(); | ||
} else { | } else { | ||
setTimeout(checkStability, 50); | setTimeout(checkStability, 50); | ||
} | } | ||
} | } setTimeout(checkStability, 50); | ||
}); | }); | ||
const nextHeight = toTab.getBoundingClientRect().height; | const nextHeight = toTab.getBoundingClientRect().height; | ||
const finalHeight = Math.max(nextHeight, 100); | const finalHeight = Math.max(nextHeight, 100); | ||
if (Math.abs(finalHeight - currentHeight) < 30) { | if (Math.abs(finalHeight - currentHeight) < 30) { | ||
wrapper.style.height = ''; | wrapper.style.height = ''; | ||
return Promise.resolve(); | return Promise.resolve(); | ||
} | } wrapper.style.overflow = 'hidden'; | ||
wrapper.style.height = currentHeight + 'px'; | wrapper.style.height = currentHeight + 'px'; | ||
wrapper.offsetHeight; | wrapper.offsetHeight; | ||
wrapper.style.transition = 'height 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; | wrapper.style.transition = 'height 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; | ||
requestAnimationFrame(() => { | requestAnimationFrame(() => { | ||
wrapper.style.height = finalHeight + 'px'; | wrapper.style.height = finalHeight + 'px'; | ||
}); | }); | ||
return new Promise(resolve => { | return new Promise(resolve => { | ||
setTimeout(() => { | setTimeout(() => { | ||
| Linha 1 407: | Linha 1 604: | ||
}, 320); | }, 320); | ||
}); | }); | ||
} | } tabs.forEach(btn => { | ||
if (btn.dataset.wiredTab) return; | if (btn.dataset.wiredTab) return; | ||
btn.dataset.wiredTab = '1'; | btn.dataset.wiredTab = '1'; | ||
| Linha 1 416: | Linha 1 611: | ||
const currentActive = contents.find(c => c.classList.contains('active')); | const currentActive = contents.find(c => c.classList.contains('active')); | ||
const nextActive = contents.find(c => c.id === target); | const nextActive = contents.find(c => c.id === target); | ||
if (currentActive === nextActive) return; | |||
if (currentActive === nextActive) return; | |||
document.body.classList.add('transitioning-tabs'); | document.body.classList.add('transitioning-tabs'); | ||
if (currentActive) { | if (currentActive) { | ||
currentActive.style.opacity = '0'; | currentActive.style.opacity = '0'; | ||
currentActive.style.transform = 'translateY(-8px)'; | currentActive.style.transform = 'translateY(-8px)'; | ||
} | } setTimeout(async () => { | ||
contents.forEach(c => { | contents.forEach(c => { | ||
if (c !== nextActive) { | if (c !== nextActive) { | ||
| Linha 1 437: | Linha 1 623: | ||
} | } | ||
}); | }); | ||
tabs.forEach(b => b.classList.toggle('active', b === btn)); | tabs.forEach(b => b.classList.toggle('active', b === btn)); | ||
if (nextActive) { | if (nextActive) { | ||
nextActive.classList.add('active'); | nextActive.classList.add('active'); | ||
| Linha 1 447: | Linha 1 629: | ||
nextActive.style.opacity = '0'; | nextActive.style.opacity = '0'; | ||
nextActive.style.visibility = 'hidden'; | nextActive.style.visibility = 'hidden'; | ||
nextActive.offsetHeight; | nextActive.offsetHeight; | ||
try { | try { | ||
if (target === 'skills') { | if (target === 'skills') { | ||
| Linha 1 461: | Linha 1 638: | ||
const toClick = activeIcon || firstIcon; | const toClick = activeIcon || firstIcon; | ||
if (toClick) { | if (toClick) { | ||
const had = document.body.dataset.suppressSkillPlay; | const had = document.body.dataset.suppressSkillPlay; | ||
document.body.dataset.suppressSkillPlay = '1'; | document.body.dataset.suppressSkillPlay = '1'; | ||
toClick.click(); | toClick.click(); | ||
if (had) document.body.dataset.suppressSkillPlay = had; | if (had) document.body.dataset.suppressSkillPlay = had; | ||
} | } | ||
} | } | ||
} | } | ||
} catch (e) { | } catch (e) { | ||
} | } | ||
} if (currentActive && nextActive) { | |||
await smoothHeightTransition(currentActive, nextActive); | await smoothHeightTransition(currentActive, nextActive); | ||
} | } if (nextActive) { | ||
nextActive.style.visibility = ''; | nextActive.style.visibility = ''; | ||
nextActive.style.transform = 'translateY(12px)'; | nextActive.style.transform = 'translateY(12px)'; | ||
requestAnimationFrame(() => { | requestAnimationFrame(() => { | ||
nextActive.style.opacity = '1'; | nextActive.style.opacity = '1'; | ||
nextActive.style.transform = 'translateY(0)'; | nextActive.style.transform = 'translateY(0)'; | ||
setTimeout(() => { | setTimeout(() => { | ||
nextActive.style.opacity = ''; | nextActive.style.opacity = ''; | ||
nextActive.style.transform = ''; | nextActive.style.transform = ''; | ||
document.body.classList.remove('transitioning-tabs'); | document.body.classList.remove('transitioning-tabs'); | ||
try { | |||
try { delete document.body.dataset.suppressSkillPlay; } catch { } | delete document.body.dataset.suppressSkillPlay; | ||
} catch { | |||
} | |||
}, 300); | }, 300); | ||
}); | }); | ||
} | } | ||
}, 120); | }, 120); | ||
setTimeout(() => { | setTimeout(() => { | ||
syncDescHeight(); | syncDescHeight(); | ||
if (target === 'skins') { | if (target === 'skins') { | ||
videosCache.forEach(v => { | |||
videosCache.forEach(v => { try { v.pause(); } catch (e) { } v.style.display = 'none'; }); | try { | ||
v.pause(); | |||
} catch (e) { | |||
} v.style.display = 'none'; | |||
}); | |||
if (videoBox) { | if (videoBox) { | ||
videoBox.querySelectorAll('video.skill-video').forEach(v => { | videoBox.querySelectorAll('video.skill-video').forEach(v => { | ||
try { v.pause(); } catch (e) { | try { | ||
v.style.display = 'none'; | v.pause(); | ||
} catch (e) { | |||
} v.style.display = 'none'; | |||
}); | }); | ||
} if (window.__subskills) window.__subskills.hideAll?.(videoBox); | |||
if (videoBox && placeholder) { | |||
placeholder.style.display = 'none'; | |||
placeholder.classList.add('fade-out'); | |||
} | } | ||
} else { | } else { | ||
const activeIcon = document.querySelector('.icon-bar .skill-icon.active'); | const activeIcon = document.querySelector('.icon-bar .skill-icon.active'); | ||
if (activeIcon) activeIcon.click(); | if (activeIcon) activeIcon.click(); | ||
} | } | ||
}, 450); | }, 450); | ||
}); | }); | ||
}); | }); | ||
})(); | })(); | ||
(function initSkinsArrows() { | (function initSkinsArrows() { | ||
const carousel = $('.skins-carousel'); | const carousel = $('.skins-carousel'); | ||
| Linha 1 546: | Linha 1 714: | ||
wrapper.classList.toggle('has-right', hasRight); | wrapper.classList.toggle('has-right', hasRight); | ||
carousel.style.justifyContent = (!hasLeft && !hasRight) ? 'center' : ''; | carousel.style.justifyContent = (!hasLeft && !hasRight) ? 'center' : ''; | ||
} | } function go(dir) { | ||
const max = carousel.scrollWidth - carousel.clientWidth; | const max = carousel.scrollWidth - carousel.clientWidth; | ||
const next = dir < 0 | const next = dir < 0 ? Math.max(0, carousel.scrollLeft - scrollAmt()) : Math.min(max, carousel.scrollLeft + scrollAmt()); | ||
carousel.scrollTo({ | |||
left: next, behavior: 'smooth' | |||
carousel.scrollTo({ left: next, behavior: 'smooth' }); | }); | ||
} | } left.addEventListener('click', () => go(-1)); | ||
right.addEventListener('click', () => go(1)); | right.addEventListener('click', () => go(1)); | ||
carousel.addEventListener('scroll', setState); | carousel.addEventListener('scroll', setState); | ||
| Linha 1 560: | Linha 1 726: | ||
setState(); | setState(); | ||
})(); | })(); | ||
function renderAttributes(str) { | function renderAttributes(str) { | ||
const skillsRoot = document.getElementById('skills'); | const skillsRoot = document.getElementById('skills'); | ||
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {}; | const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : { | ||
}; | |||
const langRaw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase(); | const langRaw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase(); | ||
const langKey = i18nMap[langRaw] ? langRaw : (i18nMap[langRaw.split('-')[0]] ? langRaw.split('-')[0] : 'pt'); | const langKey = i18nMap[langRaw] ? langRaw : (i18nMap[langRaw.split('-')[0]] ? langRaw.split('-')[0] : 'pt'); | ||
const L = i18nMap[langKey] || i18nMap.pt || { | const L = i18nMap[langKey] || i18nMap.pt || { | ||
cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', | cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível' | ||
}; | }; | ||
const vals = (str || '').split(',').map(v => v.trim()); | const vals = (str || '').split(',').map(v => v.trim()); | ||
const pve = | // Processa valores, tratando strings vazias como NaN | ||
const pvp = | // IMPORTANTE: ordem fixa é [powerpve, powerpvp, energy, cooldown] | ||
const ene = | const pve = (vals[0] && vals[0] !== '') ? parseFloat(vals[0]) : NaN; | ||
const cd = | const pvp = (vals[1] && vals[1] !== '') ? parseFloat(vals[1]) : NaN; | ||
const ene = (vals[2] && vals[2] !== '') ? parseFloat(vals[2]) : NaN; | |||
const cd = (vals[3] && vals[3] !== '') ? parseFloat(vals[3]) : NaN; | |||
// Debug: log se houver problema na ordem | |||
if (str && str.includes(',') && !isNaN(cd) && !isNaN(pvp) && cd === pvp) { | |||
console.warn('[Skills] renderAttributes: possível problema na ordem dos atributos', { str, vals, pve, pvp, ene, cd }); | |||
} | |||
const rows = []; | const rows = []; | ||
// Ordem de exibição: cooldown, energy, power, power_pvp | |||
if (!isNaN(cd)) rows.push([L.cooldown, cd]); | if (!isNaN(cd)) rows.push([L.cooldown, cd]); | ||
if (!isNaN(ene) && ene !== 0) { | if (!isNaN(ene) && ene !== 0) { | ||
| Linha 1 588: | Linha 1 755: | ||
if (!isNaN(pve)) rows.push([L.power, pve]); | if (!isNaN(pve)) rows.push([L.power, pve]); | ||
if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]); | if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]); | ||
// Debug: log se houver valores suspeitos | |||
if (str && str.includes(',')) { | |||
console.log('[Skills] renderAttributes processed', { | |||
str, | |||
vals: vals.slice(0, 4), | |||
parsed: { pve, pvp, ene, cd }, | |||
rows: rows.map(r => r[0]) | |||
}); | |||
} | |||
if (!rows.length) return ''; | if (!rows.length) return ''; | ||
const html = rows.map(([label, value]) => ` | const html = rows.map(([label, value]) => `<div class="attr-row"><span class="attr-label">${label}</span><span class="attr-value">${value}</span></div>`).join(''); | ||
return `<div class="attr-list">${html}</div>`; | return `<div class="attr-list">${html}</div>`; | ||
} | } function syncDescHeight() { | ||
} window.addEventListener('resize', syncDescHeight); | |||
} | |||
if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox); | if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox); | ||
iconItems.forEach(el => { | iconItems.forEach(el => { | ||
const wired = !!el.dataset._sync_wired; | const wired = !!el.dataset._sync_wired; | ||
| Linha 1 616: | Linha 1 778: | ||
}); | }); | ||
}); | }); | ||
if (iconsBar) addOnce(iconsBar, 'wheel', (e) => { | if (iconsBar) addOnce(iconsBar, 'wheel', (e) => { | ||
if (e.deltaY) { | if (e.deltaY) { | ||
| Linha 1 623: | Linha 1 784: | ||
} | } | ||
}); | }); | ||
wireClicksForCurrentBar(); | wireClicksForCurrentBar(); | ||
if (iconItems.length) { | if (iconItems.length) { | ||
const first = iconItems[0]; | const first = iconItems[0]; | ||
if (first) { | if (first) { | ||
activateSkill(first, { openSubs: false }); | activateSkill(first, { | ||
openSubs: false | |||
}); | |||
} | } | ||
} | } setTimeout(() => { | ||
Array.from(document.querySelectorAll('.skill-icon')).forEach(el => { | Array.from(document.querySelectorAll('.skill-icon')).forEach(el => { | ||
}); | }); | ||
videosCache.forEach((v, idx) => { | videosCache.forEach((v, idx) => { | ||
const src = v.querySelector('source') ? v.querySelector('source').src : v.src; | const src = v.querySelector('source') ? v.querySelector('source').src : v.src; | ||
v.addEventListener('error', (ev) => { | v.addEventListener('error', (ev) => { | ||
}); | }); | ||
v.addEventListener('loadedmetadata', () => { | v.addEventListener('loadedmetadata', () => { | ||
}); | }); | ||
}); | }); | ||
Edição atual tal como às 17h47min de 5 de dezembro de 2025
<script>
(function () {
const $ = (s, root = document) => root.querySelector(s);
const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
const ensureRemoved = sel => {
Array.from(document.querySelectorAll(sel)).forEach(n => n.remove());
};
const onceFlag = (el, key) => {
if (!el) return false;
if (el.dataset[key]) return false;
el.dataset[key] = '1';
return true;
};
const addOnce = (el, ev, fn) => {
if (!el) return;
const attr = `data-wired-${ev}`;
if (el.hasAttribute(attr)) return;
el.addEventListener(ev, fn);
el.setAttribute(attr, '1');
};
const FLAG_ICON_FILES = {
aggro: 'Enemyaggro-icon.png', bridge: 'Bridgemaker-icon.png', wall: 'Destroywall-icon.png', quickcast: 'Quickcast-icon.png'
};
const subBarTemplateCache = window.__skillSubBarTemplateCache || (window.__skillSubBarTemplateCache = new Map());
const imagePreloadCache = window.__skillImagePreloadCache || (window.__skillImagePreloadCache = new Map());
const videoPreloadCache = window.__skillVideoPreloadCache || (window.__skillVideoPreloadCache = new Set());
const flagRowCache = window.__skillFlagRowCache || (window.__skillFlagRowCache = new Map());
const flagIconURLCache = window.__skillFlagIconURLCache || (window.__skillFlagIconURLCache = new Map());
function filePathURL(fileName) {
const f = encodeURIComponent((fileName || 'Nada.png').replace(/^Arquivo:|^File:/, ));
const base = (window.mw && mw.util && typeof mw.util.wikiScript === 'function') ? mw.util.wikiScript() : (window.mw && window.mw.config ? (mw.config.get('wgScript') || '/index.php') : '/index.php');
return `${base}?title=Especial:FilePath/${f}`;
} function slugify(s) {
if (!s) return ;
return String(s).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ).replace(/[^\w\s-]/g, ).replace(/[\s:/\-]+/g, '-').replace(/^-+|-+$/g, ).replace(/-+/g, '-');
} window.__skillSlugify = slugify;
function getLangKey() {
const skillsRoot = document.getElementById('skills');
const raw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase();
return raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
} function chooseDescFrom(obj) {
const lang = getLangKey();
// Aceita tanto desc_i18n quanto desc para compatibilidade
const pack = obj.desc_i18n || obj.desc || {
pt: obj.descPt, en: obj.descEn, es: obj.descEs, pl: obj.descPl
};
return (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || ;
} function renderSubAttributesFromObj(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 getFlagIconURL(key) {
if (!FLAG_ICON_FILES[key]) return ;
if (!flagIconURLCache.has(key)) {
flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key]));
} return flagIconURLCache.get(key);
} function renderFlagsRow(flags) {
const arr = (flags || []).filter(Boolean);
if (!arr.length) return ;
const cacheKey = arr.join('|');
if (flagRowCache.has(cacheKey)) {
return flagRowCache.get(cacheKey);
} const items = arr.map(k => {
const url = getFlagIconURL(k);
return url ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">` : ;
}).join();
const html = items ? `
${items}
` : ;
if (html) flagRowCache.set(cacheKey, html);
return html;
} function applyFlagTooltips(container) {
const skillsRoot = document.getElementById('skills');
if (!skillsRoot) return;
let pack = {
};
try {
pack = JSON.parse(skillsRoot.dataset.i18nFlags || '{}');
} catch (e) {
} const lang = getLangKey();
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();
});
});
}
// ====== Skill/Subskill inheritance helpers ======
const mainSkillsMeta = {
byIndex: new Map(),
byName: new Map(),
ready: false
};
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 extractFileNameFromURL(url) {
if (!url) return ;
const match = String(url).match(/(?:FilePath\/)([^&?]+)/i);
return match ? decodeURIComponent(match[1]) : ;
}
function parseAttrString(raw) {
const parts = (raw || ).split(',').map(v => v.trim());
const safe = idx => {
const val = parts[idx] || ;
return (val && val !== '-') ? val : ;
};
return {
powerpve: safe(0),
powerpvp: safe(1),
energy: safe(2),
cooldown: safe(3)
};
}
function hasText(value) {
return typeof value === 'string' ? value.trim() !== : value !== undefined && value !== null;
}
function pickFilled(current, fallback) {
if (current === 0 || current === '0') return current;
if (!hasText(current)) return fallback;
return current;
}
function buildMainSkillsMeta(nodes) {
if (mainSkillsMeta.ready) {
return mainSkillsMeta;
}
(nodes || []).forEach(icon => {
const index = (icon.dataset.index || ).trim();
if (!index) return;
const name = (icon.dataset.nome || icon.dataset.name || ).trim();
const attrs = parseAttrString(icon.dataset.atr || );
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]) : ;
}
let videoFile = (icon.dataset.videoFile || ).trim();
if (!videoFile) {
videoFile = extractFileNameFromURL(icon.dataset.video || );
}
const meta = {
index,
name,
icon: iconFile || 'Nada.png',
level: icon.dataset.level || ,
video: videoFile || ,
powerpve: attrs.powerpve || ,
powerpvp: attrs.powerpvp || ,
energy: attrs.energy || ,
cooldown: attrs.cooldown || ,
desc: icon.dataset.desc || ,
descPt: icon.dataset.descPt || ,
descEn: icon.dataset.descEn || ,
descEs: icon.dataset.descEs || ,
descPl: icon.dataset.descPl ||
};
mainSkillsMeta.byIndex.set(index, meta);
mainSkillsMeta.byIndex.set(parseInt(index, 10), meta);
if (name) {
mainSkillsMeta.byName.set(name, meta);
}
});
mainSkillsMeta.ready = true;
return mainSkillsMeta;
}
function inheritSubskillFromMain(sub, meta) {
if (!sub || !meta) return sub;
// Suporta refS (novo) e refM (legado)
const refS = ((sub.refS || sub.S || sub.s || ) + ).trim();
const refIndex = ((sub.refM || sub.M || sub.m || ) + ).trim();
let name = (sub.name || sub.n || ).trim();
let main = null;
// Primeiro tenta por refS
if (refS) {
main = meta.byIndex.get(refS) || meta.byIndex.get(parseInt(refS, 10));
}
// Depois por refM
if (!main && refIndex) {
main = meta.byIndex.get(refIndex) || meta.byIndex.get(parseInt(refIndex, 10));
}
// Por último pelo nome
if (!main && name) {
main = meta.byName.get(name);
}
if (!main) {
return sub;
}
const hydrated = { ...sub };
if (!name && main.name) {
name = main.name;
}
hydrated.name = name || hydrated.name || main.name || ;
hydrated.icon = pickFilled(hydrated.icon, main.icon || 'Nada.png');
hydrated.level = pickFilled(hydrated.level, main.level || );
hydrated.video = pickFilled(hydrated.video, main.video || );
hydrated.powerpve = pickFilled(hydrated.powerpve, main.powerpve || );
hydrated.powerpvp = pickFilled(hydrated.powerpvp, main.powerpvp || );
hydrated.energy = pickFilled(hydrated.energy, main.energy || );
hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || );
if (!hasText(hydrated.descPt) && hasText(main.descPt)) hydrated.descPt = main.descPt;
if (!hasText(hydrated.descEn) && hasText(main.descEn)) hydrated.descEn = main.descEn;
if (!hasText(hydrated.descEs) && hasText(main.descEs)) hydrated.descEs = main.descEs;
if (!hasText(hydrated.descPl) && hasText(main.descPl)) hydrated.descPl = main.descPl;
if (!hasText(hydrated.desc) && hasText(main.desc)) hydrated.desc = main.desc;
if (!hydrated.desc_i18n && (hydrated.descPt || hydrated.descEn || hydrated.descEs || hydrated.descPl)) {
hydrated.desc_i18n = {
pt: hydrated.descPt || ,
en: hydrated.descEn || ,
es: hydrated.descEs || ,
pl: hydrated.descPl ||
};
}
return hydrated;
}
function inheritSubskillTree(subs, meta) {
if (!Array.isArray(subs)) return [];
return subs.map(sub => {
const hydrated = inheritSubskillFromMain(sub, meta);
if (Array.isArray(hydrated.subs)) {
hydrated.subs = inheritSubskillTree(hydrated.subs, meta);
}
return hydrated;
});
}
function collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet) {
if (!Array.isArray(subs)) return;
subs.forEach(sub => {
const iconURL = normalizeFileURL(sub.icon || 'Nada.png', filePathURL('Nada.png'));
if (iconURL && iconURL !== filePathURL('Nada.png')) iconsSet.add(iconURL);
// Vídeo normal
if (sub.video && sub.video.trim() !== && sub.video !== 'Nada.png' && !sub.video.toLowerCase().includes('nada.png')) {
const videoURL = normalizeFileURL(sub.video);
if (videoURL) videosSet.add(videoURL);
}
// Vídeo de weapon
if (sub.weapon && typeof sub.weapon === 'object' && sub.weapon.video && sub.weapon.video.trim() !== && sub.weapon.video !== 'Nada.png' && !sub.weapon.video.toLowerCase().includes('nada.png')) {
const weaponVideoURL = normalizeFileURL(sub.weapon.video);
if (weaponVideoURL) videosSet.add(weaponVideoURL);
}
if (Array.isArray(sub.flags)) {
sub.flags.forEach(flagKey => {
const url = getFlagIconURL(flagKey);
if (url) flagsSet.add(url);
});
} if (Array.isArray(sub.subs)) {
collectAssetsFromSubs(sub.subs, iconsSet, videosSet, flagsSet);
}
});
} function buildAssetManifest() {
if (window.__skillAssetManifest && window.__skillAssetManifest.ready) {
return window.__skillAssetManifest;
} const iconsSet = new Set();
const videosSet = new Set();
const flagsSet = new Set();
iconItems.forEach(el => {
const img = el.querySelector('img');
if (img && img.src) {
iconsSet.add(img.src);
} else if (el.dataset.iconFile) {
const iconURL = normalizeFileURL(el.dataset.iconFile);
if (iconURL) iconsSet.add(iconURL);
}
// Vídeo normal da skill
const videoRaw = (el.dataset.videoFile || el.dataset.video || ).trim();
if (videoRaw && videoRaw !== 'Nada.png' && !videoRaw.toLowerCase().includes('nada.png')) {
const videoURL = normalizeFileURL(videoRaw);
if (videoURL) videosSet.add(videoURL);
}
// Vídeo de weapon da skill
if (el.dataset.weapon) {
try {
const weaponData = JSON.parse(el.dataset.weapon);
if (weaponData && weaponData.video && weaponData.video.trim() !== && weaponData.video !== 'Nada.png' && !weaponData.video.toLowerCase().includes('nada.png')) {
const weaponVideoURL = normalizeFileURL(weaponData.video);
if (weaponVideoURL) videosSet.add(weaponVideoURL);
}
} catch (e) {
}
}
if (el.dataset.flags) {
try {
const parsedFlags = JSON.parse(el.dataset.flags);
(parsedFlags || []).forEach(flagKey => {
const url = getFlagIconURL(flagKey);
if (url) flagsSet.add(url);
});
} catch (e) {
}
} if (el.dataset.subs) {
try {
const subs = JSON.parse(el.dataset.subs);
collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet);
} catch (e) {
}
}
});
Object.keys(FLAG_ICON_FILES).forEach(flagKey => {
const url = getFlagIconURL(flagKey);
if (url) flagsSet.add(url);
});
const manifest = {
icons: iconsSet, videos: videosSet, flags: flagsSet, ready: true
};
window.__skillAssetManifest = manifest;
return manifest;
} function preloadImagesFromSet(set) {
if (!set || !set.size) return;
set.forEach(url => {
if (!url || imagePreloadCache.has(url)) return;
const img = new Image();
img.decoding = 'async';
img.loading = 'eager';
img.referrerPolicy = 'same-origin';
img.src = url;
imagePreloadCache.set(url, img);
});
} function preloadVideosFromSet(set) {
if (!set || !set.size) return;
const head = document.head || document.getElementsByTagName('head')[0];
set.forEach(url => {
if (!url || videoPreloadCache.has(url)) return;
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'video';
link.href = url;
link.crossOrigin = 'anonymous';
head.appendChild(link);
videoPreloadCache.add(url);
});
} const subskillVideosCache = new Map();
window.__subskillVideosCache = subskillVideosCache;
let assetManifest = null;
const skillsTab = $('#skills');
const skinsTab = $('#skins');
ensureRemoved('.top-rail');
ensureRemoved('.content-card');
ensureRemoved('.video-placeholder');
Array.from(document.querySelectorAll('.card-skins-title, .card-skins .card-skins-title, .cardskins-title, .rail-title')).forEach(t => {
if ((t.textContent || ).trim().toLowerCase().includes('skins')) {
t.remove();
}
});
if (skillsTab) {
const iconBar = skillsTab.querySelector('.icon-bar');
if (iconBar) {
const rail = document.createElement('div');
rail.className = 'top-rail skills';
rail.appendChild(iconBar);
skillsTab.prepend(rail);
} const details = skillsTab.querySelector('.skills-details');
const videoContainer = skillsTab.querySelector('.video-container');
const card = document.createElement('div');
card.className = 'content-card skills-grid';
if (details) card.appendChild(details);
if (videoContainer) card.appendChild(videoContainer);
skillsTab.appendChild(card);
} if (skinsTab) {
const wrapper = skinsTab.querySelector('.skins-carousel-wrapper');
const rail = document.createElement('div');
rail.className = 'top-rail skins';
const title = document.createElement('div');
title.className = 'rail-title';
title.textContent = 'Skins & Spotlights';
rail.appendChild(title);
if (wrapper) {
const card = document.createElement('div');
card.className = 'content-card';
card.appendChild(wrapper);
skinsTab.prepend(rail);
skinsTab.appendChild(card);
} else {
skinsTab.prepend(rail);
}
} const iconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
const skillsTopRail = iconsBar ? iconsBar.closest('.top-rail.skills') : null;
const iconItems = iconsBar ? Array.from(iconsBar.querySelectorAll('.skill-icon')) : [];
buildMainSkillsMeta(iconItems);
// Verifica se há weapon em skills principais OU em subskills
function checkHasAnyWeapon() {
// Verifica skills principais
if (iconItems.some(el => !!el.dataset.weapon)) {
return true;
}
// Verifica subskills
for (const el of iconItems) {
const subsRaw = el.getAttribute('data-subs');
if (!subsRaw) continue;
try {
const subs = JSON.parse(subsRaw);
if (Array.isArray(subs) && subs.some(s => s && s.weapon)) {
return true;
}
} catch (e) { }
}
return false;
}
const hasWeaponSkillAvailable = checkHasAnyWeapon();
let weaponToggleBtn = null;
if (!assetManifest) {
assetManifest = buildAssetManifest();
preloadImagesFromSet(assetManifest.icons);
preloadImagesFromSet(assetManifest.flags);
preloadVideosFromSet(assetManifest.videos);
} const descBox = $('#skills') ? $('.desc-box', $('#skills')) : null;
const videoBox = $('#skills') ? $('.video-container', $('#skills')) : null;
const videosCache = new Map();
const nestedVideoElByIcon = new WeakMap();
const barStack = [];
window.__barStack = barStack;
let initialBarSnapshot = null;
let totalVideos = 0, loadedVideos = 0, autoplay = false;
window.__lastActiveSkillIcon = null;
let userHasInteracted = false;
let globalWeaponEnabled = false;
try {
if (localStorage.getItem('glaWeaponEnabled') === '1') {
globalWeaponEnabled = true;
}
} catch (err) {
}
const weaponStateListeners = new Set();
let showWeaponPopupFn = null;
let popupShouldOpen = false;
function attachWeaponPopupFn(fn) {
if (typeof fn !== 'function') return;
showWeaponPopupFn = fn;
if (popupShouldOpen) {
popupShouldOpen = false;
try {
showWeaponPopupFn();
} catch (err) {
}
}
}
attachWeaponPopupFn(window.__glaWeaponShowPopup);
function requestWeaponPopupDisplay() {
try {
if (localStorage.getItem('glaWeaponPopupDismissed') === '1') return;
} catch (err) {
}
if (typeof showWeaponPopupFn === 'function') {
showWeaponPopupFn();
return;
}
popupShouldOpen = true;
}
function onWeaponStateChange(fn) {
if (typeof fn !== 'function') return;
weaponStateListeners.add(fn);
}
function syncWeaponButtonState(enabled) {
if (!weaponToggleBtn || !weaponToggleBtn.isConnected) return;
// Usa .weapon-active em vez de .active para não conflitar com skills
weaponToggleBtn.classList.toggle('weapon-active', !!enabled);
weaponToggleBtn.classList.remove('active'); // Garante que .active nunca seja aplicado
weaponToggleBtn.setAttribute('aria-pressed', enabled ? 'true' : 'false');
weaponToggleBtn.setAttribute('aria-label', enabled ? 'Desativar Arma Especial' : 'Ativar Arma Especial');
}
function syncWeaponRailState(enabled) {
if (skillsTopRail) {
skillsTopRail.classList.toggle('weapon-mode-on', !!enabled);
}
}
function notifyWeaponStateListeners(enabled) {
weaponStateListeners.forEach(listener => {
try {
listener(enabled);
} catch (err) {
}
});
}
let pendingWeaponState = null;
window.addEventListener('weapon:ready', (ev) => {
if (ev && ev.detail && ev.detail.showPopup) {
attachWeaponPopupFn(ev.detail.showPopup);
}
if (pendingWeaponState === null) return;
if (typeof window.__applyWeaponState === 'function') {
const target = pendingWeaponState;
pendingWeaponState = null;
window.__applyWeaponState(target);
}
});
window.__setGlobalWeaponEnabled = (enabled) => {
globalWeaponEnabled = enabled;
notifyWeaponStateListeners(enabled);
};
function requestWeaponState(targetState) {
if (typeof window.__applyWeaponState === 'function') {
pendingWeaponState = null;
window.__applyWeaponState(targetState);
return;
}
pendingWeaponState = targetState;
}
onWeaponStateChange(syncWeaponButtonState);
function reapplyWeaponClassesToBar() {
if (!globalWeaponEnabled) return;
// SISTEMA UNIFICADO: Aplica em skills E subskills
iconsBar.querySelectorAll('.skill-icon[data-weapon], .subicon[data-weapon]').forEach(el => {
if (!el.classList.contains('has-weapon-available')) {
el.classList.add('has-weapon-available');
}
if (!el.querySelector('.weapon-indicator')) {
const ind = document.createElement('div');
ind.className = 'weapon-indicator';
el.appendChild(ind);
}
});
}
function setupWeaponBarToggle(shouldShow) {
if (!shouldShow || !iconsBar) return;
if (iconsBar.querySelector('.weapon-bar-toggle')) return;
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'skill-icon weapon-bar-toggle';
btn.dataset.weaponToggle = '1';
btn.dataset.nome = 'Arma Especial';
btn.setAttribute('aria-pressed', 'false');
btn.setAttribute('aria-label', 'Arma Especial');
btn.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="currentColor" d="M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58a.5.5 0 00.12-.62l-1.92-3.32a.5.5 0 00-.61-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.37-2.48a.55.55 0 00-.55-.5h-3.82a.55.55 0 00-.55.5l-.37 2.48c-.59.24-1.12.56-1.62.94l-2.39-.96a.5.5 0 00-.61.22L3.13 8.5a.5.5 0 00.12.62l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.5.5 0 00-.12.62l1.92 3.32a.5.5 0 00.61.22l2.39-.96c.5.38 1.03.7 1.62.94l.37 2.48a.55.55 0 00.55.5h3.82a.55.55 0 00.55-.5l.37-2.48c.59-.24 1.12-.56 1.62-.94l2.39.96a.5.5 0 00.61-.22l1.92-3.32a.5.5 0 00-.12-.62zM12 15.6a3.6 3.6 0 110-7.2 3.6 3.6 0 010 7.2z"></path></svg>';
iconsBar.appendChild(btn);
weaponToggleBtn = btn;
syncWeaponButtonState(globalWeaponEnabled);
btn.addEventListener('click', () => {
const nextState = !globalWeaponEnabled;
if (nextState) {
requestWeaponPopupDisplay();
}
requestWeaponState(nextState);
});
// Wire tooltip for weapon toggle
const tooltip = window.__globalSkillTooltip;
if (tooltip) {
btn.addEventListener('mouseenter', () => {
const tip = document.querySelector('.skill-tooltip');
if (tip) tip.classList.add('weapon-tooltip');
tooltip.show(btn, globalWeaponEnabled ? 'Desativar Arma Especial' : 'Ativar Arma Especial');
});
btn.addEventListener('mouseleave', () => {
const tip = document.querySelector('.skill-tooltip');
if (tip) tip.classList.remove('weapon-tooltip');
tooltip.hide();
});
}
reapplyWeaponClassesToBar();
}
onWeaponStateChange(syncWeaponRailState);
syncWeaponRailState(globalWeaponEnabled);
setupWeaponBarToggle(hasWeaponSkillAvailable);
(function injectWeaponStyles() {
if (document.getElementById('weapon-toggle-styles')) return;
const style = document.createElement('style');
style.id = 'weapon-toggle-styles';
style.textContent = `
/* Animação da borda nos ícones */
@keyframes weapon-icon-border-scan {
0% { background-position: 0% 0%; }
100% { background-position: 400% 0%; }
}
@keyframes weapon-icon-pulse {
0%, 100% {
opacity: 0.5;
box-shadow: 0 0 8px rgba(255, 80, 80, 0.25);
}
50% {
opacity: 1;
box-shadow: 0 0 16px rgba(255, 80, 80, 0.45);
}
}
/* Skills com arma disponível - borda vermelha quando inativa */
.skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::after {
box-shadow: inset 0 0 0 var(--icon-ring-w) rgba(220, 70, 70, 0.8) !important;
}
/* Skill com arma ATIVA - laranja/coral vibrante */
.skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::after {
box-shadow: inset 0 0 0 var(--icon-ring-w) #FF7043 !important;
}
.skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before {
box-shadow: 0 0 12px 3px rgba(255, 112, 67, 0.35), 0 0 0 4px rgba(255, 112, 67, 0.25) !important;
}
/* Modo arma ON - efeito animado nos ícones */
.top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
position: relative;
}
.top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle)::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-icon-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: var(--icon-ring-w) !important;
}
.top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle)::before {
animation: weapon-icon-pulse 3s ease-in-out infinite !important;
}
/* Skill ativa com arma - mais intenso */
.top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::after {
background: linear-gradient(90deg,
rgba(255, 87, 34, 1) 0%,
rgba(255, 140, 60, 1) 25%,
rgba(255, 87, 34, 1) 50%,
rgba(255, 140, 60, 1) 75%,
rgba(255, 87, 34, 1) 100%
) !important;
}
.top-rail.skills.weapon-mode-on .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before {
box-shadow: 0 0 14px 4px rgba(255, 87, 34, 0.4), 0 0 0 4px rgba(255, 87, 34, 0.3) !important;
animation: weapon-icon-pulse 2.5s ease-in-out infinite !important;
}
.skill-icon .weapon-indicator {
display: none;
}
/* Variáveis de cor vermelha para skill ativa com arma equipada */
.skill-icon.weapon-equipped {
--icon-active: #FF6B6B;
--icon-active-ring: rgba(255, 100, 100, 0.6);
--icon-active-glow: rgba(255, 90, 90, 0.45);
}
/* Badge de arma no canto inferior direito */
.skill-icon .weapon-badge {
position: absolute;
bottom: 3px;
right: 3px;
width: 16px;
height: 16px;
background: var(--weapon-badge-url) center/contain no-repeat;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.6));
pointer-events: none;
z-index: 10;
border-radius: 3px;
display: none;
}
.skill-icon.weapon-equipped .weapon-badge {
display: block;
}
`.replace(/\s+/g, ' ').trim();
document.head.appendChild(style);
})();
function applyWeaponBadge(el, weaponData, equipped) {
// Encontrar ou criar o badge
let badge = el.querySelector('.weapon-badge');
if (!badge) {
badge = document.createElement('div');
badge.className = 'weapon-badge';
el.appendChild(badge);
}
if (equipped && weaponData) {
el.classList.add('weapon-equipped');
el.style.setProperty('--weapon-badge-url', `url('${filePathURL(weaponData.icon || 'Nada.png')}')`);
} else {
el.classList.remove('weapon-equipped');
el.style.removeProperty('--weapon-badge-url');
}
} function getWeaponKey(el) {
return (el.dataset.index || ) + ':' + (el.dataset.nome || el.dataset.name || );
} function isWeaponModeOn() {
try {
return localStorage.getItem('glaWeaponEnabled') === '1';
} catch (e) {
return false;
}
} function getWeaponDataForIcon(iconEl) {
if (!iconEl || !iconEl.dataset.weapon) return null;
try {
return JSON.parse(iconEl.dataset.weapon);
} catch (e) {
return null;
}
} function getEffectiveSkillVideoFromIcon(iconEl) {
const weaponOn = globalWeaponEnabled;
const weaponData = getWeaponDataForIcon(iconEl);
const baseVideoFile = (iconEl.dataset.videoFile || ).trim();
const baseVideoURL = (iconEl.dataset.video || ).trim();
console.log('[Skills DEBUG]', {
skillName: iconEl.dataset.nome || iconEl.dataset.name,
weaponOn,
hasWeaponData: !!weaponData,
weaponData: weaponData,
baseVideoFile,
baseVideoURL
});
if (weaponOn && weaponData) {
console.log('[Skills] checking weapon video', {
skillName: iconEl.dataset.nome || iconEl.dataset.name,
hasVideo: !!(weaponData.video),
videoValue: weaponData.video,
videoTrimmed: weaponData.video ? weaponData.video.trim() :
});
if (weaponData.video && weaponData.video.trim() !== ) {
console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
return weaponData.video.trim();
}
}
const result = baseVideoFile || baseVideoURL || ;
console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
return result;
} function createVideoElement(videoURL, extraAttrs = {
}) {
const v = document.createElement('video');
v.className = 'skill-video';
v.setAttribute('controls', );
v.setAttribute('preload', 'auto');
v.setAttribute('playsinline', );
v.style.display = 'none';
v.style.width = '100%';
v.style.height = 'auto';
v.style.aspectRatio = '16/9';
v.style.objectFit = 'cover';
Object.keys(extraAttrs).forEach(k => {
v.dataset[k] = extraAttrs[k];
});
// Detectar formato do vídeo pela extensão
const ext = (videoURL.split('.').pop() || ).toLowerCase().split('?')[0];
const mimeTypes = {
'mp4': 'video/mp4',
'm4v': 'video/mp4',
'webm': 'video/webm',
'ogv': 'video/ogg',
'ogg': 'video/ogg',
'mov': 'video/quicktime'
};
const mimeType = mimeTypes[ext] || 'video/mp4';
const src = document.createElement('source');
src.src = videoURL;
src.type = mimeType;
v.appendChild(src);
// Fallback para Safari/iOS mais antigos
v.setAttribute('webkit-playsinline', );
v.setAttribute('x-webkit-airplay', 'allow');
return v;
} function precreateSubskillVideos() {
if (!videoBox) return;
iconItems.forEach(parentIcon => {
const subsRaw = parentIcon.dataset.subs || parentIcon.getAttribute('data-subs');
if (!subsRaw) return;
try {
const subs = JSON.parse(subsRaw);
if (!Array.isArray(subs)) return;
const parentIdx = parentIcon.dataset.index || ;
subs.forEach(s => {
const subName = (s.name || s.n || ).trim();
// Vídeo normal da subskill
if (s.video && s.video.trim() !== && s.video !== 'Nada.png' && !s.video.toLowerCase().includes('nada.png')) {
const key = `sub:${parentIdx}:${subName}`;
if (subskillVideosCache.has(key)) return;
const videoURL = normalizeFileURL(s.video);
if (videoURL && videoURL.trim() !== ) {
const v = createVideoElement(videoURL, {
sub: '1', parentIndex: parentIdx, subName: subName
});
v.setAttribute('preload', 'auto');
videoBox.appendChild(v);
subskillVideosCache.set(key, v);
// Força carregamento apenas se ainda não estiver carregando
if (v.readyState === 0 || v.readyState === 1) {
v.load();
}
}
}
// Vídeo de weapon da subskill
if (s.weapon && typeof s.weapon === 'object' && s.weapon.video && s.weapon.video.trim() !== && s.weapon.video !== 'Nada.png' && !s.weapon.video.toLowerCase().includes('nada.png')) {
const weaponKey = `sub:${parentIdx}:${subName}:weapon`;
if (subskillVideosCache.has(weaponKey)) return;
const weaponVideoURL = normalizeFileURL(s.weapon.video);
if (weaponVideoURL && weaponVideoURL.trim() !== ) {
const v = createVideoElement(weaponVideoURL, {
sub: '1', parentIndex: parentIdx, subName: subName, weapon: '1'
});
v.setAttribute('preload', 'auto');
videoBox.appendChild(v);
subskillVideosCache.set(weaponKey, v);
// Força carregamento apenas se ainda não estiver carregando
if (v.readyState === 0 || v.readyState === 1) {
v.load();
}
}
}
});
} catch (e) {
}
});
} setTimeout(precreateSubskillVideos, 100);
if (iconItems.length && videoBox) {
iconItems.forEach(el => {
const idx = el.dataset.index || ;
// Vídeo normal
const src = (el.dataset.videoFile || el.dataset.video || ).trim();
if (src && src !== 'Nada.png' && !src.toLowerCase().includes('nada.png') && !videosCache.has(idx)) {
totalVideos++;
const videoURL = normalizeFileURL(src);
if (videoURL) {
const v = createVideoElement(videoURL, {
index: idx
});
v.setAttribute('preload', 'auto');
v.style.maxWidth = '100%';
v.addEventListener('canplaythrough', () => {
loadedVideos++;
if (!userHasInteracted && loadedVideos === 1) {
try {
v.pause();
v.currentTime = 0;
} catch (e) {
}
} if (loadedVideos === totalVideos) autoplay = true;
}, {
once: true
});
v.addEventListener('error', () => {
loadedVideos++;
if (loadedVideos === totalVideos) autoplay = true;
}, {
once: true
});
videoBox.appendChild(v);
videosCache.set(idx, v);
// Força carregamento
v.load();
}
}
// Vídeo de weapon
if (el.dataset.weapon) {
try {
const weaponData = JSON.parse(el.dataset.weapon);
if (weaponData && weaponData.video && weaponData.video.trim() !== && weaponData.video !== 'Nada.png' && !weaponData.video.toLowerCase().includes('nada.png')) {
const weaponKey = `weapon:${idx}:${(el.dataset.nome || el.dataset.name || ).trim()}`;
if (!videosCache.has(weaponKey)) {
totalVideos++;
const weaponVideoURL = normalizeFileURL(weaponData.video);
if (weaponVideoURL) {
const v = createVideoElement(weaponVideoURL, {
index: idx, weapon: '1'
});
v.setAttribute('preload', 'auto');
v.style.maxWidth = '100%';
v.addEventListener('canplaythrough', () => {
loadedVideos++;
if (loadedVideos === totalVideos) autoplay = true;
}, {
once: true
});
v.addEventListener('error', () => {
loadedVideos++;
if (loadedVideos === totalVideos) autoplay = true;
}, {
once: true
});
videoBox.appendChild(v);
videosCache.set(weaponKey, v);
// Força carregamento
v.load();
}
}
}
} catch (e) {
}
}
});
} function wireTooltipsForNewIcons() {
const tip = document.querySelector('.skill-tooltip');
if (!tip) return;
let lockUntil2 = 0;
Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => {
if (icon.dataset.weaponToggle === '1' || icon.classList.contains('weapon-bar-toggle')) return;
if (icon.dataset.tipwired) return;
icon.dataset.tipwired = '1';
const label = icon.dataset.nome || icon.dataset.name || icon.title || ;
if (label && !icon.hasAttribute('aria-label')) icon.setAttribute('aria-label', label);
if (icon.hasAttribute('title')) icon.removeAttribute('title');
const img = icon.querySelector('img');
if (img) {
const imgAlt = img.getAttribute('alt') || ;
const imgTitle = img.getAttribute('title') || ;
if (!label && (imgAlt || imgTitle)) icon.setAttribute('aria-label', imgAlt || imgTitle);
img.setAttribute('alt', );
if (img.hasAttribute('title')) img.removeAttribute('title');
} const measureAndPos = (el) => {
if (!el || tip.getAttribute('aria-hidden') === 'true') return;
tip.style.left = '0px';
tip.style.top = '0px';
const rect = el.getBoundingClientRect();
const tr = tip.getBoundingClientRect();
let left = Math.round(rect.left + (rect.width - tr.width) / 2);
left = Math.max(8, Math.min(left, window.innerWidth - tr.width - 8));
const coarse = (window.matchMedia && matchMedia('(pointer: coarse)').matches) || (window.innerWidth <= 600);
let top = coarse ? Math.round(rect.bottom + 10) : Math.round(rect.top - tr.height - 8);
if (top < 8) top = Math.round(rect.bottom + 10);
tip.style.left = left + 'px';
tip.style.top = top + 'px';
};
const show = (el, text) => {
tip.textContent = text || ;
tip.setAttribute('aria-hidden', 'false');
measureAndPos(el);
tip.style.opacity = '1';
};
const hide = () => {
tip.setAttribute('aria-hidden', 'true');
tip.style.opacity = '0';
tip.style.left = '-9999px';
tip.style.top = '-9999px';
};
icon.addEventListener('mouseenter', () => show(icon, (icon.dataset.nome || icon.dataset.name || )));
icon.addEventListener('mousemove', () => {
if (performance.now() >= lockUntil2) measureAndPos(icon);
});
icon.addEventListener('click', () => {
lockUntil2 = performance.now() + 240;
measureAndPos(icon);
});
icon.addEventListener('mouseleave', hide);
});
} function showVideoForIcon(el) {
userHasInteracted = true;
if (!videoBox) return;
const effectiveVideo = getEffectiveSkillVideoFromIcon(el);
if (!effectiveVideo || effectiveVideo.trim() === ) {
videoBox.style.display = 'none';
return;
}
const videoURL = normalizeFileURL(effectiveVideo);
if (!videoURL || videoURL.trim() === ) {
videoBox.style.display = 'none';
return;
}
Array.from(videoBox.querySelectorAll('video.skill-video')).forEach(v => {
try {
v.pause();
} catch (e) {
}
v.style.display = 'none';
});
if (window.__subskills) window.__subskills.hideAll?.(videoBox);
const hasIdx = !!el.dataset.index;
const weaponOn = globalWeaponEnabled;
const weaponData = getWeaponDataForIcon(el);
const isWeaponVideo = weaponOn && weaponData && weaponData.video && weaponData.video.trim() !== ;
console.log('[Skills] showVideoForIcon chamado', {
skillName: el.dataset.nome || el.dataset.name,
weaponOn,
isWeaponVideo,
effectiveVideo: getEffectiveSkillVideoFromIcon(el)
});
const videoKey = isWeaponVideo ? `weapon:${getWeaponKey(el)}` : (el.dataset.index || );
if (hasIdx && !isWeaponVideo && videosCache.has(el.dataset.index)) {
const v = videosCache.get(el.dataset.index);
videoBox.style.display = 'block';
v.style.display = 'block';
try {
v.currentTime = 0;
} catch (e) {
}
const suppress = document.body.dataset.suppressSkillPlay === '1';
if (!suppress) {
v.play().catch(() => {
});
} else {
try {
v.pause();
} catch (e) {
}
}
return;
}
let v = null;
if (isWeaponVideo) {
v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`);
} else {
v = nestedVideoElByIcon.get(el);
}
if (!v) {
v = createVideoElement(videoURL, isWeaponVideo ? {
weaponKey: videoKey
} : {});
if (isWeaponVideo) {
videoBox.appendChild(v);
} else {
videoBox.appendChild(v);
nestedVideoElByIcon.set(el, v);
}
} else {
const src = v.querySelector('source');
if (src && src.src !== videoURL) {
src.src = videoURL;
v.load();
}
}
videoBox.style.display = 'block';
v.style.display = 'block';
try {
v.currentTime = 0;
} catch (e) {
}
const suppress = document.body.dataset.suppressSkillPlay === '1';
if (!suppress) {
v.play().catch(() => {
});
} else {
try {
v.pause();
} catch (e) {
}
}
} function activateSkill(el, options = {
}) {
const {
openSubs = true
} = options;
const tip = document.querySelector('.skill-tooltip');
if (tip) {
tip.setAttribute('aria-hidden', 'true');
tip.style.opacity = '0';
tip.style.left = '-9999px';
tip.style.top = '-9999px';
} const skillsRoot = document.getElementById('skills');
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {
};
const L = i18nMap[getLangKey()] || i18nMap.pt || {
cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível'
};
const name = el.dataset.nome || el.dataset.name || ;
const level = (el.dataset.level || ).trim();
let weaponData = null;
if (el.dataset.weapon) {
try {
weaponData = JSON.parse(el.dataset.weapon);
} catch (e) {
weaponData = null;
}
} const hasWeapon = !!weaponData;
const weaponEquipped = hasWeapon && globalWeaponEnabled;
const lang = getLangKey();
const baseDescPack = {
pt: el.dataset.descPt || , en: el.dataset.descEn || , es: el.dataset.descEs || , pl: el.dataset.descPl ||
};
const baseDesc = baseDescPack[lang] || baseDescPack.pt || baseDescPack.en || baseDescPack.es || baseDescPack.pl || el.dataset.desc || ;
// Aceita tanto desc_i18n quanto desc para compatibilidade
let weaponDescPack = {};
if (weaponData) {
if (weaponData.desc_i18n) {
weaponDescPack = weaponData.desc_i18n;
} else if (weaponData.desc) {
weaponDescPack = weaponData.desc;
} else {
weaponDescPack = {
pt: weaponData.descPt || , en: weaponData.descEn || , es: weaponData.descEs || , pl: weaponData.descPl ||
};
}
}
const weaponDesc = weaponDescPack[lang] || weaponDescPack.pt || weaponDescPack.en || weaponDescPack.es || weaponDescPack.pl || ;
const chosenDesc = (weaponEquipped && weaponDesc) ? weaponDesc : baseDesc;
const descHtml = chosenDesc.replace(/(.*?)/g, '$1');
let attrsHTML = ;
if (weaponEquipped && weaponData) {
// Usa valores do weapon, mas só se existirem (não herda da skill base)
const wPve = (weaponData.powerpve !== undefined && weaponData.powerpve !== null && weaponData.powerpve !== ) ? weaponData.powerpve.toString().trim() : ;
const wPvp = (weaponData.powerpvp !== undefined && weaponData.powerpvp !== null && weaponData.powerpvp !== ) ? weaponData.powerpvp.toString().trim() : ;
const wEnergy = (weaponData.energy !== undefined && weaponData.energy !== null && weaponData.energy !== ) ? weaponData.energy.toString().trim() : ;
const wCd = (weaponData.cooldown !== undefined && weaponData.cooldown !== null && weaponData.cooldown !== ) ? weaponData.cooldown.toString().trim() : ;
const weaponAttrs = [wPve, wPvp, wEnergy, wCd].join(',');
console.log('[Skills] weaponAttrs string:', weaponAttrs, { wPve, wPvp, wEnergy, wCd, weaponData });
attrsHTML = renderAttributes(weaponAttrs);
} else {
attrsHTML = el.dataset.atr ? renderAttributes(el.dataset.atr) : (el.dataset.subattrs ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L) : );
} let flagsHTML = ;
if (el.dataset.flags) {
try {
const flags = JSON.parse(el.dataset.flags);
flagsHTML = renderFlagsRow(flags);
} catch (e) {
}
} if (descBox) {
descBox.innerHTML = `
${name}
${level ? `
${L.level} ${level}
` : }${attrsHTML}
${descHtml}
`;
} if (hasWeapon) {
applyWeaponBadge(el, weaponData, weaponEquipped);
} if (videoBox) {
const oldFlags = videoBox.querySelector('.skill-flags');
if (oldFlags) oldFlags.remove();
if (flagsHTML) {
videoBox.insertAdjacentHTML('beforeend', flagsHTML);
applyFlagTooltips(videoBox);
}
} const currIcons = Array.from(iconsBar.querySelectorAll('.skill-icon'));
currIcons.forEach(i => i.classList.remove('active'));
el.classList.add('active');
if (!autoplay && loadedVideos > 0) autoplay = true;
window.__lastActiveSkillIcon = el;
// Lógica de vídeo: usa função centralizada que já considera weapon
showVideoForIcon(el);
const subsRaw = el.dataset.subs || el.getAttribute('data-subs');
const isBack = el.dataset.back === 'true' || el.getAttribute('data-back') === 'true' || el.dataset.back === 'yes' || el.getAttribute('data-back') === 'yes' || el.dataset.back === '1' || el.getAttribute('data-back') === '1';
if (isBack && barStack.length) {
const prev = barStack.pop();
renderBarFromItems(prev.items);
const btn = document.querySelector('.skills-back-wrapper');
if (btn) btn.style.display = barStack.length ? 'block' : 'none';
return;
} if (openSubs && subsRaw && subsRaw.trim() !== ) {
if (barStack.length && barStack[barStack.length - 1].parentIcon === el) return;
try {
const subs = JSON.parse(subsRaw);
pushSubBarFrom(subs, el);
} catch {
}
}
} function wireClicksForCurrentBar() {
const currIcons = Array.from(iconsBar.querySelectorAll('.skill-icon'));
currIcons.forEach(el => {
if (el.dataset.weaponToggle === '1' || el.classList.contains('weapon-bar-toggle')) return;
if (el.dataset.wired) return;
el.dataset.wired = '1';
const label = el.dataset.nome || el.dataset.name || ;
el.setAttribute('aria-label', label);
if (el.hasAttribute('title')) el.removeAttribute('title');
const img = el.querySelector('img');
if (img) {
img.setAttribute('alt', );
if (img.hasAttribute('title')) img.removeAttribute('title');
} el.addEventListener('click', () => {
activateSkill(el, {
openSubs: true
});
});
});
wireTooltipsForNewIcons();
} function animateIconsBarEntrance() {
Array.from(iconsBar.children).forEach((c, i) => {
c.style.opacity = '0';
c.style.transform = 'translateY(6px)';
requestAnimationFrame(() => {
setTimeout(() => {
c.style.transition = 'opacity .18s ease, transform .18s ease';
c.style.opacity = '1';
c.style.transform = 'translateY(0)';
}, i * 24);
});
});
} function snapshotCurrentBarItemsFromDOM() {
return Array.from(iconsBar.querySelectorAll('.skill-icon')).filter(el => el.dataset.weaponToggle !== '1').map(el => {
const img = el.querySelector('img');
const iconURL = img ? img.src : ;
const subsRaw = el.dataset.subs || el.getAttribute('data-subs') || ;
let subs = null;
try {
subs = subsRaw ? JSON.parse(subsRaw) : null;
} catch {
subs = null;
} const subattrsRaw = el.dataset.subattrs || ;
let flags = null;
if (el.dataset.flags) {
try {
flags = JSON.parse(el.dataset.flags);
} catch (e) {
}
} let weapon = null;
if (el.dataset.weapon) {
try {
weapon = JSON.parse(el.dataset.weapon);
} catch (e) {
}
} return {
name: el.dataset.nome || el.dataset.name || , index: el.dataset.index || , level: el.dataset.level || , desc: el.dataset.desc || , descPt: el.dataset.descPt || , descEn: el.dataset.descEn || , descEs: el.dataset.descEs || , descPl: el.dataset.descPl || , attrs: el.dataset.atr || el.dataset.attrs || , video: el.dataset.video || , iconURL, subs, subattrsStr: subattrsRaw, flags: flags, weapon: weapon
};
});
} function ensureBackButton() {
const rail = iconsBar.closest('.top-rail.skills');
if (!rail) return null;
let wrap = rail.parentElement;
if (!wrap || !wrap.classList || !wrap.classList.contains('skills-rail-wrap')) {
const parentNode = rail.parentNode;
const newWrap = document.createElement('div');
newWrap.className = 'skills-rail-wrap';
parentNode.insertBefore(newWrap, rail);
newWrap.appendChild(rail);
wrap = newWrap;
} let backWrap = wrap.querySelector('.skills-back-wrapper');
if (!backWrap) {
backWrap = document.createElement('div');
backWrap.className = 'skills-back-wrapper';
const btnInner = document.createElement('button');
btnInner.className = 'skills-back';
btnInner.type = 'button';
btnInner.setAttribute('aria-label', 'Voltar');
btnInner.innerHTML = '<svg class="back-chevron" width="100%" height="100%" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" preserveAspectRatio="xMidYMid meet"><path d="M10 2L4 16L10 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 2L14 16L20 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M30 2L24 16L30 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
backWrap.appendChild(btnInner);
wrap.insertBefore(backWrap, rail);
btnInner.addEventListener('click', () => {
if (!barStack.length) return;
const prev = barStack.pop();
renderBarFromItems(prev.items);
backWrap.style.display = barStack.length ? 'block' : 'none';
wrap.classList.toggle('has-sub-bar', barStack.length > 0);
if (!barStack.length) btnInner.classList.remove('peek');
});
} backWrap.style.display = barStack.length ? 'block' : 'none';
wrap.classList.toggle('has-sub-bar', barStack.length > 0);
const btnInner = backWrap.querySelector('.skills-back');
return btnInner;
} function renderBarFromItems(items) {
const tip = document.querySelector('.skill-tooltip');
if (tip) {
tip.setAttribute('aria-hidden', 'true');
tip.style.opacity = '0';
tip.style.left = '-9999px';
tip.style.top = '-9999px';
} iconsBar.innerHTML = ;
items.forEach((it, idx) => {
const node = document.createElement('div');
node.className = 'skill-icon';
node.dataset.nome = it.name || ;
if (it.index) node.dataset.index = it.index;
if (it.level) node.dataset.level = it.level;
if (it.desc) node.dataset.desc = it.desc;
if (it.descPt) node.dataset.descPt = it.descPt;
if (it.descEn) node.dataset.descEn = it.descEn;
if (it.descEs) node.dataset.descEs = it.descEs;
if (it.descPl) node.dataset.descPl = it.descPl;
if (it.attrs) node.dataset.atr = it.attrs;
if (it.video) node.dataset.video = it.video;
if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr;
if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
if (it.weapon) node.dataset.weapon = JSON.stringify(it.weapon);
if (!it.index) node.dataset.nested = '1';
const img = document.createElement('img');
img.alt = ;
img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : );
node.appendChild(img);
iconsBar.appendChild(node);
});
animateIconsBarEntrance();
wireClicksForCurrentBar();
setupWeaponBarToggle(hasWeaponSkillAvailable);
const b = ensureBackButton();
if (b) b.classList.add('peek');
} function pushSubBarFrom(subs, parentIconEl) {
const tip = document.querySelector('.skill-tooltip');
if (tip) {
tip.setAttribute('aria-hidden', 'true');
tip.style.opacity = '0';
tip.style.left = '-9999px';
tip.style.top = '-9999px';
} const parentNameSnapshot = parentIconEl ? (parentIconEl.dataset.nome || parentIconEl.dataset.name || ) : ;
const parentIndexSnapshot = parentIconEl ? (parentIconEl.dataset.index || ) : ;
barStack.push({
items: snapshotCurrentBarItemsFromDOM(), parentIcon: parentIconEl, parentName: parentNameSnapshot, parentIndex: parentIndexSnapshot
});
ensureBackButton();
const langKey = getLangKey();
let cacheKey = null;
if (parentIconEl) {
cacheKey = parentIconEl.dataset.subCacheKey || null;
if (!cacheKey) {
if (parentIconEl.dataset.index) {
cacheKey = `idx:${parentIconEl.dataset.index}`;
} else {
const slug = slugify(parentIconEl.dataset.nome || parentIconEl.dataset.name || );
if (slug) cacheKey = `slug:${slug}`;
} if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey;
}
} if (cacheKey) {
const cached = subBarTemplateCache.get(cacheKey);
if (cached && cached.lang === langKey) {
iconsBar.innerHTML = ;
const clone = cached.template.cloneNode(true);
iconsBar.appendChild(clone);
animateIconsBarEntrance();
wireClicksForCurrentBar();
setupWeaponBarToggle(hasWeaponSkillAvailable);
const cachedBtn = ensureBackButton();
if (cachedBtn) cachedBtn.classList.add('peek');
return;
}
} const skillsRoot = document.getElementById('skills');
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {
};
const L = i18nMap[getLangKey()] || i18nMap.pt || {
cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível'
};
const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
const items = (hydratedSubs || []).filter(s => {
// Filtra só se não tem nada útil
const hasName = (s.name || s.n || ).trim() !== ;
const hasIcon = (s.icon || ).trim() !== && s.icon !== 'Nada.png';
const hasRef = (s.refS || s.refM || ).toString().trim() !== ;
return hasName || hasIcon || hasRef;
}).map(s => {
const name = (s.name || s.n || ).trim();
const desc = chooseDescFrom(s).replace(/(.*?)/g, '$1');
const attrsHTML = renderSubAttributesFromObj(s, L);
return {
name, level: (s.level || ).toString().trim(), desc, descPt: (s.descPt || (s.desc_i18n && s.desc_i18n.pt) || ), descEn: (s.descEn || (s.desc_i18n && s.desc_i18n.en) || ), descEs: (s.descEs || (s.desc_i18n && s.desc_i18n.es) || ), descPl: (s.descPl || (s.desc_i18n && s.desc_i18n.pl) || ), attrs: , icon: (s.icon || 'Nada.png'), iconURL: filePathURL(s.icon || 'Nada.png'), video: s.video ? filePathURL(s.video) : , subs: Array.isArray(s.subs) ? s.subs : null, subattrs: s, flags: Array.isArray(s.flags) ? s.flags : null, back: (s.back === true || s.back === 'true' || s.back === 'yes' || s.back === '1') ? 'true' : null, weapon: s.weapon || null
};
});
const fragment = document.createDocumentFragment();
items.forEach((it, iIdx) => {
const node = document.createElement('div');
node.className = 'skill-icon';
node.dataset.nested = '1';
node.dataset.nome = it.name || ;
node.dataset.parentIndex = parentIndexSnapshot;
node.dataset.subName = it.name || ;
const subSlug = slugify(it.name || );
if (subSlug) node.dataset.slug = subSlug;
if (it.level) node.dataset.level = it.level;
if (it.desc) node.dataset.desc = it.desc;
if (it.descPt) node.dataset.descPt = it.descPt;
if (it.descEn) node.dataset.descEn = it.descEn;
if (it.descEs) node.dataset.descEs = it.descEs;
if (it.descPl) node.dataset.descPl = it.descPl;
if (it.video) node.dataset.video = it.video;
if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
if (it.subattrs) node.dataset.subattrs = JSON.stringify(it.subattrs);
if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
if (it.back) node.dataset.back = it.back;
if (it.weapon) {
try {
node.dataset.weapon = JSON.stringify(it.weapon);
} catch (e) {
console.error('[Skills] Erro ao serializar weapon de subskill', it.name, e);
}
}
const img = document.createElement('img');
img.alt = ;
img.src = it.iconURL;
node.appendChild(img);
fragment.appendChild(node);
});
const templateClone = fragment.cloneNode(true);
iconsBar.innerHTML = ;
iconsBar.appendChild(fragment);
animateIconsBarEntrance();
wireClicksForCurrentBar();
setupWeaponBarToggle(hasWeaponSkillAvailable);
const b2 = ensureBackButton();
if (b2) b2.classList.add('peek');
if (cacheKey) {
subBarTemplateCache.set(cacheKey, {
template: templateClone, lang: langKey
});
}
} window.addEventListener('gla:langChanged', () => {
subBarTemplateCache.clear();
const skillsRoot = document.getElementById('skills');
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {
};
const lang = getLangKey();
Array.from(iconsBar.querySelectorAll('.skill-icon')).forEach(icon => {
const pack = {
pt: icon.dataset.descPt || , en: icon.dataset.descEn || , es: icon.dataset.descEs || , pl: icon.dataset.descPl ||
};
const chosen = (pack[lang] || pack.pt || pack.en || pack.es || pack.pl || icon.dataset.desc || ).trim();
if (chosen) icon.dataset.desc = chosen;
});
barStack.forEach(frame => {
(frame.items || []).forEach(it => {
const pack = {
pt: it.descPt, en: it.descEn, es: it.descEs, pl: it.descPl
};
const chosen = (pack[lang] || pack.pt || pack.en || pack.es || pack.pl || it.desc || );
it.desc = chosen;
});
});
if (descBox) {
applyFlagTooltips(descBox);
} const activeIcon = window.__lastActiveSkillIcon;
if (activeIcon && activeIcon.dataset.weapon) {
activateSkill(activeIcon, {
openSubs: false
});
}
});
wireClicksForCurrentBar();
const b0 = ensureBackButton();
if (b0) {
b0.classList.add('peek');
b0.style.alignSelf = 'stretch';
} (function initSkillTooltip() {
if (document.querySelector('.skill-tooltip')) return;
const tip = document.createElement('div');
tip.className = 'skill-tooltip';
tip.setAttribute('role', 'tooltip');
tip.setAttribute('aria-hidden', 'true');
document.body.appendChild(tip);
const lockUntilRef = {
value: 0
};
function measureAndPos(el) {
if (!el || tip.getAttribute('aria-hidden') === 'true') return;
tip.style.left = '0px';
tip.style.top = '0px';
const rect = el.getBoundingClientRect();
const tr = tip.getBoundingClientRect();
let left = Math.round(rect.left + (rect.width - tr.width) / 2);
left = Math.max(8, Math.min(left, window.innerWidth - tr.width - 8));
const coarse = (window.matchMedia && matchMedia('(pointer: coarse)').matches) || (window.innerWidth <= 600);
let top = coarse ? Math.round(rect.bottom + 10) : Math.round(rect.top - tr.height - 8);
if (top < 8) top = Math.round(rect.bottom + 10);
tip.style.left = left + 'px';
tip.style.top = top + 'px';
} function show(el, text) {
tip.textContent = text || ;
tip.setAttribute('aria-hidden', 'false');
measureAndPos(el);
tip.style.opacity = '1';
} function hide() {
tip.setAttribute('aria-hidden', 'true');
tip.style.opacity = '0';
tip.style.left = '-9999px';
tip.style.top = '-9999px';
} window.__globalSkillTooltip = {
show, hide, measureAndPos, lockUntil: lockUntilRef
};
Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => {
if (icon.dataset.weaponToggle === '1' || icon.classList.contains('weapon-bar-toggle')) return;
if (icon.dataset.tipwired) return;
icon.dataset.tipwired = '1';
const label = icon.dataset.nome || icon.dataset.name || icon.title || ;
if (label && !icon.hasAttribute('aria-label')) icon.setAttribute('aria-label', label);
if (icon.hasAttribute('title')) icon.removeAttribute('title');
const img = icon.querySelector('img');
if (img) {
const imgAlt = img.getAttribute('alt') || ;
const imgTitle = img.getAttribute('title') || ;
if (!label && (imgAlt || imgTitle)) icon.setAttribute('aria-label', imgAlt || imgTitle);
img.setAttribute('alt', );
if (img.hasAttribute('title')) img.removeAttribute('title');
} icon.addEventListener('mouseenter', () => show(icon, label));
icon.addEventListener('mousemove', () => {
if (performance.now() >= lockUntilRef.value) measureAndPos(icon);
});
icon.addEventListener('click', () => {
lockUntilRef.value = performance.now() + 240;
measureAndPos(icon);
});
icon.addEventListener('mouseleave', hide);
});
Array.from(document.querySelectorAll('.subskills-rail .subicon')).forEach(sub => {
if (sub.dataset.tipwired) return;
sub.dataset.tipwired = '1';
const label = sub.getAttribute('title') || sub.getAttribute('aria-label') || ;
if (label && !sub.hasAttribute('aria-label')) sub.setAttribute('aria-label', label);
if (sub.hasAttribute('title')) sub.removeAttribute('title');
sub.addEventListener('mouseenter', () => show(sub, label));
sub.addEventListener('mousemove', () => {
if (performance.now() >= lockUntilRef.value) measureAndPos(sub);
});
sub.addEventListener('click', () => {
lockUntilRef.value = performance.now() + 240;
measureAndPos(sub);
});
sub.addEventListener('mouseleave', hide);
});
window.addEventListener('scroll', () => {
const visible = document.querySelector('.skill-tooltip[aria-hidden="false"]');
if (!visible) return;
const target = document.querySelector('.subskills-rail .subicon:hover') || document.querySelector('.subskills-rail .subicon.active') || document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active');
measureAndPos(target);
}, true);
window.addEventListener('resize', () => {
const target = document.querySelector('.subskills-rail .subicon:hover') || document.querySelector('.subskills-rail .subicon.active') || document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active');
measureAndPos(target);
});
})();
(function initTabs() {
const tabs = Array.from(document.querySelectorAll('.tab-btn'));
if (!tabs.length) return;
const contents = Array.from(document.querySelectorAll('.tab-content'));
const characterBox = document.querySelector('.character-box');
let wrapper = characterBox.querySelector('.tabs-height-wrapper');
if (!wrapper) {
wrapper = document.createElement('div');
wrapper.className = 'tabs-height-wrapper';
contents.forEach(c => {
wrapper.appendChild(c);
});
const tabsElement = characterBox.querySelector('.character-tabs');
if (tabsElement && tabsElement.nextSibling) {
characterBox.insertBefore(wrapper, tabsElement.nextSibling);
} else {
characterBox.appendChild(wrapper);
}
} async function smoothHeightTransition(fromTab, toTab) {
if (!wrapper) return Promise.resolve();
const scrollY = window.scrollY;
const currentHeight = wrapper.getBoundingClientRect().height;
await new Promise((resolve) => {
const videoContainers = toTab.querySelectorAll('.video-container');
const contentCard = toTab.querySelector('.content-card');
if (videoContainers.length === 0) {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => resolve());
});
});
return;
} let lastHeight = 0;
let stableCount = 0;
const checksNeeded = 3;
let totalChecks = 0;
const maxChecks = 15;
function checkStability() {
totalChecks++;
const currentTabHeight = toTab.scrollHeight;
if (Math.abs(currentTabHeight - lastHeight) < 5) {
stableCount++;
} else {
stableCount = 0;
} lastHeight = currentTabHeight;
if (stableCount >= checksNeeded || totalChecks >= maxChecks) {
resolve();
} else {
setTimeout(checkStability, 50);
}
} setTimeout(checkStability, 50);
});
const nextHeight = toTab.getBoundingClientRect().height;
const finalHeight = Math.max(nextHeight, 100);
if (Math.abs(finalHeight - currentHeight) < 30) {
wrapper.style.height = ;
return Promise.resolve();
} wrapper.style.overflow = 'hidden';
wrapper.style.height = currentHeight + 'px';
wrapper.offsetHeight;
wrapper.style.transition = 'height 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
requestAnimationFrame(() => {
wrapper.style.height = finalHeight + 'px';
});
return new Promise(resolve => {
setTimeout(() => {
wrapper.style.height = ;
wrapper.style.transition = ;
wrapper.style.overflow = ;
resolve();
}, 320);
});
} tabs.forEach(btn => {
if (btn.dataset.wiredTab) return;
btn.dataset.wiredTab = '1';
btn.addEventListener('click', () => {
const target = btn.getAttribute('data-tab');
const currentActive = contents.find(c => c.classList.contains('active'));
const nextActive = contents.find(c => c.id === target);
if (currentActive === nextActive) return;
document.body.classList.add('transitioning-tabs');
if (currentActive) {
currentActive.style.opacity = '0';
currentActive.style.transform = 'translateY(-8px)';
} setTimeout(async () => {
contents.forEach(c => {
if (c !== nextActive) {
c.style.display = 'none';
c.classList.remove('active');
}
});
tabs.forEach(b => b.classList.toggle('active', b === btn));
if (nextActive) {
nextActive.classList.add('active');
nextActive.style.display = 'block';
nextActive.style.opacity = '0';
nextActive.style.visibility = 'hidden';
nextActive.offsetHeight;
try {
if (target === 'skills') {
const tabEl = document.getElementById(target);
if (tabEl) {
const activeIcon = tabEl.querySelector('.icon-bar .skill-icon.active');
const firstIcon = tabEl.querySelector('.icon-bar .skill-icon');
const toClick = activeIcon || firstIcon;
if (toClick) {
const had = document.body.dataset.suppressSkillPlay;
document.body.dataset.suppressSkillPlay = '1';
toClick.click();
if (had) document.body.dataset.suppressSkillPlay = had;
}
}
}
} catch (e) {
}
} if (currentActive && nextActive) {
await smoothHeightTransition(currentActive, nextActive);
} if (nextActive) {
nextActive.style.visibility = ;
nextActive.style.transform = 'translateY(12px)';
requestAnimationFrame(() => {
nextActive.style.opacity = '1';
nextActive.style.transform = 'translateY(0)';
setTimeout(() => {
nextActive.style.opacity = ;
nextActive.style.transform = ;
document.body.classList.remove('transitioning-tabs');
try {
delete document.body.dataset.suppressSkillPlay;
} catch {
}
}, 300);
});
}
}, 120);
setTimeout(() => {
syncDescHeight();
if (target === 'skins') {
videosCache.forEach(v => {
try {
v.pause();
} catch (e) {
} v.style.display = 'none';
});
if (videoBox) {
videoBox.querySelectorAll('video.skill-video').forEach(v => {
try {
v.pause();
} catch (e) {
} v.style.display = 'none';
});
} if (window.__subskills) window.__subskills.hideAll?.(videoBox);
if (videoBox && placeholder) {
placeholder.style.display = 'none';
placeholder.classList.add('fade-out');
}
} else {
const activeIcon = document.querySelector('.icon-bar .skill-icon.active');
if (activeIcon) activeIcon.click();
}
}, 450);
});
});
})();
(function initSkinsArrows() {
const carousel = $('.skins-carousel');
const wrapper = $('.skins-carousel-wrapper');
const left = $('.skins-arrow.left');
const right = $('.skins-arrow.right');
if (!carousel || !left || !right || !wrapper) return;
if (wrapper.dataset.wired) return;
wrapper.dataset.wired = '1';
const scrollAmt = () => Math.round(carousel.clientWidth * 0.6);
function setState() {
const max = carousel.scrollWidth - carousel.clientWidth;
const x = carousel.scrollLeft;
const hasLeft = x > 5, hasRight = x < max - 5;
left.style.display = hasLeft ? 'inline-block' : 'none';
right.style.display = hasRight ? 'inline-block' : 'none';
wrapper.classList.toggle('has-left', hasLeft);
wrapper.classList.toggle('has-right', hasRight);
carousel.style.justifyContent = (!hasLeft && !hasRight) ? 'center' : ;
} function go(dir) {
const max = carousel.scrollWidth - carousel.clientWidth;
const next = dir < 0 ? Math.max(0, carousel.scrollLeft - scrollAmt()) : Math.min(max, carousel.scrollLeft + scrollAmt());
carousel.scrollTo({
left: next, behavior: 'smooth'
});
} left.addEventListener('click', () => go(-1));
right.addEventListener('click', () => go(1));
carousel.addEventListener('scroll', setState);
new ResizeObserver(setState).observe(carousel);
setState();
})();
function renderAttributes(str) {
const skillsRoot = document.getElementById('skills');
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {
};
const langRaw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase();
const langKey = i18nMap[langRaw] ? langRaw : (i18nMap[langRaw.split('-')[0]] ? langRaw.split('-')[0] : 'pt');
const L = i18nMap[langKey] || i18nMap.pt || {
cooldown: 'Recarga', energy_gain: 'Ganho de energia', energy_cost: 'Custo de energia', power: 'Poder', power_pvp: 'Poder PvP', level: 'Nível'
};
const vals = (str || ).split(',').map(v => v.trim());
// Processa valores, tratando strings vazias como NaN
// IMPORTANTE: ordem fixa é [powerpve, powerpvp, energy, cooldown]
const pve = (vals[0] && vals[0] !== ) ? parseFloat(vals[0]) : NaN;
const pvp = (vals[1] && vals[1] !== ) ? parseFloat(vals[1]) : NaN;
const ene = (vals[2] && vals[2] !== ) ? parseFloat(vals[2]) : NaN;
const cd = (vals[3] && vals[3] !== ) ? parseFloat(vals[3]) : NaN;
// Debug: log se houver problema na ordem
if (str && str.includes(',') && !isNaN(cd) && !isNaN(pvp) && cd === pvp) {
console.warn('[Skills] renderAttributes: possível problema na ordem dos atributos', { str, vals, pve, pvp, ene, cd });
}
const rows = [];
// Ordem de exibição: cooldown, energy, power, power_pvp
if (!isNaN(cd)) rows.push([L.cooldown, cd]);
if (!isNaN(ene) && ene !== 0) {
const label = ene > 0 ? L.energy_gain : L.energy_cost;
rows.push([label, Math.abs(ene)]);
}
if (!isNaN(pve)) rows.push([L.power, pve]);
if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]);
// Debug: log se houver valores suspeitos
if (str && str.includes(',')) {
console.log('[Skills] renderAttributes processed', {
str,
vals: vals.slice(0, 4),
parsed: { pve, pvp, ene, cd },
rows: rows.map(r => r[0])
});
}
if (!rows.length) return ;
const html = rows.map(([label, value]) => `
${label}${value}
`).join(); return `
${html}
`;
} function syncDescHeight() {
} window.addEventListener('resize', syncDescHeight);
if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox);
iconItems.forEach(el => {
const wired = !!el.dataset._sync_wired;
if (wired) return;
el.dataset._sync_wired = '1';
el.addEventListener('click', () => {
Promise.resolve().then(syncDescHeight);
});
});
if (iconsBar) addOnce(iconsBar, 'wheel', (e) => {
if (e.deltaY) {
e.preventDefault();
iconsBar.scrollLeft += e.deltaY;
}
});
wireClicksForCurrentBar();
if (iconItems.length) {
const first = iconItems[0];
if (first) {
activateSkill(first, {
openSubs: false
});
}
} setTimeout(() => {
Array.from(document.querySelectorAll('.skill-icon')).forEach(el => {
});
videosCache.forEach((v, idx) => {
const src = v.querySelector('source') ? v.querySelector('source').src : v.src;
v.addEventListener('error', (ev) => {
});
v.addEventListener('loadedmetadata', () => {
});
});
}, 600);
})();
</script>