Mudanças entre as edições de "Widget:Character.Skills"

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
Linha 39: Linha 39:
                 .replace(/-+/g, '-'); // Remove múltiplos hífens consecutivos
                 .replace(/-+/g, '-'); // Remove múltiplos hífens consecutivos
         }
         }
       
        // Compact slugify: cria slug mais curto para subskills (usa apenas palavras-chave importantes)
        function compactSlugify(s) {
            if (!s) return '';
            const fullSlug = slugify(s);
            // Para nomes muito longos, tenta criar versão mais curta
            // Remove palavras comuns como "no", "mi", "model", "modelo"
            const short = fullSlug
                .replace(/\b(no|mi|model|modelo|de|do|da|dos|das|the|a|an|and|or)\b/g, '')
                .replace(/-+/g, '-')
                .replace(/^-+|-+$/g, '');
            // Se ficou muito curto (menos de 3 caracteres), usa o slug completo
            return short.length >= 3 ? short : fullSlug;
        }
        // Expose compactSlugify globally for subskills widget
        window.__compactSlugify = compactSlugify;
         // Expor slugify globalmente para Subskills usar
         // Expor slugify globalmente para Subskills usar
         window.__skillSlugify = slugify;
         window.__skillSlugify = slugify;
Linha 444: Linha 460:
                      
                      
                     if (isNested) {
                     if (isNested) {
                         // We're in a sub-bar, update hash as #parent/sub
                         // We're in a sub-bar, update hash as #M{index}/subskill
                         let parentName = '';
                         let parentIndex = null;
                         if (window.__barStack && window.__barStack.length > 0) {
                         if (window.__barStack && window.__barStack.length > 0) {
                             const lastFrame = window.__barStack[window.__barStack.length - 1];
                             const lastFrame = window.__barStack[window.__barStack.length - 1];
                             if (lastFrame && lastFrame.parentIcon) {
                             if (lastFrame && lastFrame.parentIcon) {
                                 parentName = lastFrame.parentIcon.dataset.nome || lastFrame.parentIcon.dataset.name || '';
                                 parentIndex = lastFrame.parentIcon.dataset.index || null;
                            } else if (lastFrame && lastFrame.items && lastFrame.items.length > 0) {
                                // Fallback: use first item from previous bar
                                parentName = lastFrame.items[0].name || '';
                             }
                             }
                         }
                         }
                         // Fallback: try to find from main icon bar
                         // Fallback: try to find from main icon bar
                         if (!parentName) {
                         if (!parentIndex) {
                             const mainIconBar = document.querySelector('#skills .icon-bar');
                             const mainIconBar = document.querySelector('#skills .icon-bar');
                             const mainSkills = mainIconBar ? Array.from(mainIconBar.querySelectorAll('.skill-icon:not([data-nested="1"])')) : [];
                             const mainSkills = mainIconBar ? Array.from(mainIconBar.querySelectorAll('.skill-icon:not([data-nested="1"])')) : [];
Linha 470: Linha 483:
                                         });
                                         });
                                         if (hasThisSub) {
                                         if (hasThisSub) {
                                             parentName = mainSkill.dataset.nome || mainSkill.dataset.name || '';
                                             parentIndex = mainSkill.dataset.index || null;
                                             break;
                                             break;
                                         }
                                         }
Linha 477: Linha 490:
                             }
                             }
                         }
                         }
                         const parentSlug = slugify(parentName);
                        // Use compact slug for subskill to make URL shorter
                         if (parentSlug && skillSlug) {
                         const subSlug = compactSlugify(name);
                             const newHash = '#' + parentSlug + '/' + skillSlug;
                         if (parentIndex && subSlug) {
                            // Use format: #M{index}/subskill (more compact than full parent name)
                             const newHash = '#M' + parentIndex + '/' + subSlug;
                             if (window.location.hash !== newHash) {
                             if (window.location.hash !== newHash) {
                                 history.replaceState(null, '', window.location.pathname + window.location.search + newHash);
                                 history.replaceState(null, '', window.location.pathname + window.location.search + newHash);
Linha 781: Linha 796:
             if (!hash) return;
             if (!hash) return;


             const parts = hash.split('/');
            // Support formats:
            const skillSlug = parts[0];
            // - #M{index}/subskill (new compact format for subskills)
             const subSlug = parts[1];
            // - #parent/sub (old format with parent name)
            // - #parent-sub (old compact format)
            // - #skill (main skill)
            let skillSlug = hash;
            let subSlug = null;
            let isMFormat = false;
            let parentIndex = null;
           
            // Check for M{index}/subskill format first (new compact format)
             const mFormatMatch = hash.match(/^M(\d+)\/(.+)$/);
            if (mFormatMatch) {
                parentIndex = mFormatMatch[1];
                subSlug = mFormatMatch[2];
                isMFormat = true;
                // Find parent skill by index
                const currentIconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
                if (currentIconsBar) {
                    const parentSkill = currentIconsBar.querySelector(`.skill-icon:not([data-nested="1"])[data-index="${parentIndex}"]`);
                    if (parentSkill) {
                        const parentName = parentSkill.dataset.nome || parentSkill.dataset.name || '';
                        skillSlug = slugify(parentName);
                    }
                }
            } else if (hash.includes('/')) {
                // Old format: #parent/sub
                const parts = hash.split('/');
                skillSlug = parts[0];
                subSlug = parts[1];
             } else if (hash.includes('-')) {
                // Old compact format: #parent-sub
                // Try to find the parent skill first to determine where to split
                const currentIconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
                if (currentIconsBar) {
                    const allSkills = Array.from(currentIconsBar.querySelectorAll('.skill-icon:not([data-nested="1"])'));
                    // Try to find longest matching parent slug
                    let bestMatch = null;
                    let bestLength = 0;
                    for (const skill of allSkills) {
                        const name = skill.dataset.nome || skill.dataset.name || '';
                        const parentSlug = slugify(name);
                        if (hash.startsWith(parentSlug + '-') && parentSlug.length > bestLength) {
                            bestMatch = parentSlug;
                            bestLength = parentSlug.length;
                        }
                    }
                    if (bestMatch) {
                        skillSlug = bestMatch;
                        subSlug = hash.substring(bestLength + 1); // +1 for the hyphen
                    }
                }
            }


             // Get iconsBar fresh each time (may change when navigating)
             // Get iconsBar fresh each time (may change when navigating)
Linha 820: Linha 885:
             // If we have a subSlug, we need parent skill from main bar (not nested)
             // If we have a subSlug, we need parent skill from main bar (not nested)
             if (subSlug) {
             if (subSlug) {
                // If we're using M{index} format, find parent by index directly
                if (isMFormat && parentIndex) {
                    const parentByIndex = currentIconsBar.querySelector(`.skill-icon:not([data-nested="1"])[data-index="${parentIndex}"]`);
                    if (parentByIndex) {
                        targetSkill = parentByIndex;
                    }
                }
                 // We're looking for a subskill, so we need the parent skill from main bar
                 // We're looking for a subskill, so we need the parent skill from main bar
                 // If targetSkill is nested, we need to go back to main bar first
                 // If targetSkill is nested, we need to go back to main bar first
Linha 941: Linha 1 013:
                 if (nestedIcons.length > 0) {
                 if (nestedIcons.length > 0) {
                     for (const nestedIcon of nestedIcons) {
                     for (const nestedIcon of nestedIcons) {
                        const nestedName = nestedIcon.dataset.nome || nestedIcon.dataset.name || '';
                         // Try data-slug first (more reliable)
                         // Try data-slug first (more reliable)
                         if (nestedIcon.dataset.slug === subSlug) {
                         if (nestedIcon.dataset.slug === subSlug) {
Linha 950: Linha 1 023:
                             return;
                             return;
                         }
                         }
                         // Fallback to name
                         // Try full slug match
                        const nestedName = nestedIcon.dataset.nome || nestedIcon.dataset.name || '';
                         const nestedSlugMatch = slugify(nestedName);
                         const nestedSlugMatch = slugify(nestedName);
                         if (nestedSlugMatch === subSlug) {
                         if (nestedSlugMatch === subSlug) {
                            if (!nestedIcon.dataset.wired) {
                                wireClicksForCurrentBar();
                            }
                            activateSkill(nestedIcon, { updateHash: false, openSubs: false });
                            window.__applyingHash = false;
                            return;
                        }
                        // Try compact slug match (for new format)
                        const nestedCompactSlug = compactSlugify(nestedName);
                        if (nestedCompactSlug === subSlug) {
                             if (!nestedIcon.dataset.wired) {
                             if (!nestedIcon.dataset.wired) {
                                 wireClicksForCurrentBar();
                                 wireClicksForCurrentBar();

Edição das 14h15min de 24 de novembro de 2025

<script>

   (function () {
       // ===========================
       // Utility Functions
       // ===========================
       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');
       };
       // Extra helpers
       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}`;
       }
       // Slugify: converte nome para slug (minúsculas, sem acentos, espaços/:/'/' viram -, remove múltiplos -)
       function slugify(s) {
           if (!s) return ;
           return String(s)
               .toLowerCase()
               .normalize('NFD')
               .replace(/[\u0300-\u036f]/g, ) // Remove acentos
               .replace(/[^\w\s-]/g, ) // Remove caracteres especiais exceto hífen
               .replace(/[\s:/\-]+/g, '-') // Espaços, dois-pontos, barras viram hífen
               .replace(/^-+|-+$/g, ) // Remove hífens do início/fim
               .replace(/-+/g, '-'); // Remove múltiplos hífens consecutivos
       }
       
       // Compact slugify: cria slug mais curto para subskills (usa apenas palavras-chave importantes)
       function compactSlugify(s) {
           if (!s) return ;
           const fullSlug = slugify(s);
           // Para nomes muito longos, tenta criar versão mais curta
           // Remove palavras comuns como "no", "mi", "model", "modelo"
           const short = fullSlug
               .replace(/\b(no|mi|model|modelo|de|do|da|dos|das|the|a|an|and|or)\b/g, )
               .replace(/-+/g, '-')
               .replace(/^-+|-+$/g, );
           // Se ficou muito curto (menos de 3 caracteres), usa o slug completo
           return short.length >= 3 ? short : fullSlug;
       }
       // Expose compactSlugify globally for subskills widget
       window.__compactSlugify = compactSlugify;
       // Expor slugify globalmente para Subskills usar
       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();
           const pack = obj.desc_i18n || { pt: obj.descPt, en: obj.descEn, es: obj.descEs, pl: obj.descPl };
           return (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || (obj.desc || );
       }
       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 renderFlagsRow(flags) {
           const map = {
               aggro: 'Enemyaggro-icon.png',
               bridge: 'Bridgemaker-icon.png',
               wall: 'Destroywall-icon.png',
               quickcast: 'Quickcast-icon.png'
           };
           const arr = (flags || []).filter(Boolean);
           if (!arr.length) return ;
           const items = arr.map(k => `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(map[k])}">`).join();

return `

${items}

`;

       }
       function applyFlagTooltips(container) {
           const skillsRoot = document.getElementById('skills');
           if (!skillsRoot) return;
           let pack = {};
           try {
               pack = JSON.parse(skillsRoot.dataset.i18nFlags || '{}');
           } catch (e) { }
           const 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; // Tooltip system not ready yet
           flags.forEach(el => {
               const key = el.getAttribute('data-flag');
               const tip = (dict && dict[key]) || ;
               if (!tip) return;
               // Remove old listeners if any
               if (el.dataset.flagTipWired) return;
               el.dataset.flagTipWired = '1';
               // Set aria-label for accessibility
               el.setAttribute('aria-label', tip);
               if (el.hasAttribute('title')) el.removeAttribute('title');
               // Wire tooltip events using the same system as skill icons
               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();
               });
           });
       }
       // ===========================
       // DOM Setup & Initialization
       // ===========================
       const skillsTab = $('#skills');
       const skinsTab = $('#skins');
       const weaponTab = $('#weapon');
       // Clean up existing elements
       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();
           }
       });
       // Setup skills tab structure
       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);
       }
       // Setup weapon tab structure (mirror skills)
       if (weaponTab) {
           const iconBarW = weaponTab.querySelector('.icon-bar');
           if (iconBarW) {
               const railW = document.createElement('div');
               railW.className = 'top-rail weapon';
               railW.appendChild(iconBarW);
               weaponTab.prepend(railW);
           }
           const detailsW = weaponTab.querySelector('.skills-details');
           const videoContainerW = weaponTab.querySelector('.video-container');
           const cardW = document.createElement('div');
           cardW.className = 'content-card skills-grid';
           if (detailsW) cardW.appendChild(detailsW);
           if (videoContainerW) cardW.appendChild(videoContainerW);
           weaponTab.appendChild(cardW);
       }
       // Setup skins tab structure
       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);
           }
       }
       // ===========================
       // Video Management
       // ===========================
       const iconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
       const iconItems = iconsBar ? Array.from(iconsBar.querySelectorAll('.skill-icon')) : [];
       const descBox = $('#skills') ? $('.desc-box', $('#skills')) : null;
       const videoBox = $('#skills') ? $('.video-container', $('#skills')) : null;
       const videosCache = new Map();
       const nestedVideoElByIcon = new WeakMap();
       const barStack = [];
       // Expose barStack globally for subskill hash updates
       window.__barStack = barStack;
       let initialBarSnapshot = null;
       let totalVideos = 0, loadedVideos = 0, autoplay = false;
       // Track last clicked skill/subskill for language changes
       window.__lastActiveSkillIcon = null;
       // Placeholder management - REMOVED: no placeholder logo in videos
       // ===========================
       // Video Loading & Caching
       // ===========================
       if (iconItems.length && videoBox) {
           iconItems.forEach(el => {
               const src = (el.dataset.video || ).trim();
               const idx = el.dataset.index || ;
               if (!src || videosCache.has(idx)) return;
               totalVideos++;
               const v = document.createElement('video');
               v.className = 'skill-video';
               v.setAttribute('controls', );
               v.setAttribute('preload', 'auto');
               v.setAttribute('playsinline', );
               v.style.display = 'none';
               v.dataset.index = idx;
               v.style.width = '100%';
               v.style.maxWidth = '100%';
               v.style.height = 'auto';
               v.style.aspectRatio = '16/9';
               v.style.objectFit = 'cover';
               const source = document.createElement('source');
               source.src = src;
               source.type = 'video/webm';
               v.appendChild(source);
               v.addEventListener('canplay', () => {
                   loadedVideos++;
                   if (loadedVideos === 1) { try { v.pause(); v.currentTime = 0; } catch (e) { } }
                   if (loadedVideos === totalVideos) autoplay = true;
               });
               v.addEventListener('error', () => {
                   loadedVideos++;
                   if (loadedVideos === totalVideos) autoplay = true;
               });
               videoBox.appendChild(v);
               videosCache.set(idx, v);
           });
       }


       // ===========================
       // Skill Bar Wiring (root and nested)
       // ===========================
       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.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) {
           // Hide all existing videos
           if (videoBox) {
               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 hasVideo = !!(el.dataset.video && el.dataset.video.trim() !== );
           if (!videoBox || !hasVideo) {
               if (videoBox) { videoBox.style.display = 'none'; }
               return;
           }
           if (hasIdx && 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) { if (autoplay) v.play().catch(() => { }); } else { try { v.pause(); } catch (e) { } }
               return;
           }
           // Nested or custom icon video
           let v = nestedVideoElByIcon.get(el);
           if (!v) {
               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';
               const src = document.createElement('source');
               src.src = el.dataset.video; src.type = 'video/webm';
               v.appendChild(src);
               videoBox.appendChild(v);
               nestedVideoElByIcon.set(el, v);
           }
           videoBox.style.display = 'block';
           v.style.display = 'block';
           try { v.currentTime = 0; } catch (e) { }
           const suppress = document.body.dataset.suppressSkillPlay === '1';
           if (!suppress) { if (autoplay) v.play().catch(() => { }); } else { try { v.pause(); } catch (e) { } }
       }
       /**
        * Activate a skill icon: show description, video, update UI state
        * @param {HTMLElement} el - The skill icon element
        * @param {Object} options - Options object
        * @param {boolean} options.updateHash - Whether to update URL hash (default: true)
        * @param {boolean} options.openSubs - Whether to open sub-bar if skill has subskills (default: true)
        */
       function activateSkill(el, options = {}) {
           const { updateHash = true, openSubs = true } = options;
           // Hide tooltip immediately when activating (prevents it from jumping to corner when bar changes)
           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();
           // Pick description from current language (respects language changes)
           const lang = getLangKey();
           const descPack = {
               pt: el.dataset.descPt || ,
               en: el.dataset.descEn || ,
               es: el.dataset.descEs || ,
               pl: el.dataset.descPl || 
           };
           const chosenDesc = descPack[lang] || descPack.pt || descPack.en || descPack.es || descPack.pl || el.dataset.desc || ;
           const descHtml = chosenDesc.replace(/(.*?)/g, '$1');
           const 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}
       ${flagsHTML}`;
               if (flagsHTML) {
                   applyFlagTooltips(descBox);
               }
           }
           // Active state
           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;
           // Track for language changes
           window.__lastActiveSkillIcon = el;
           // Video
           showVideoForIcon(el);
           // Handle subskills and back navigation
           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';
               // Update hash when going back
               if (updateHash) {
                   if (prev.items && prev.items.length > 0) {
                       const prevName = prev.items[0].name || ;
                       const prevSlug = slugify(prevName);
                       if (prevSlug) {
                           const newHash = '#' + prevSlug;
                           history.replaceState(null, , window.location.pathname + window.location.search + newHash);
                       }
                   } else {
                       history.replaceState(null, , window.location.pathname + window.location.search);
                   }
               }
               return;
           }
           // Update URL hash with skill slug
           // Also skip if applying hash programmatically
           if (updateHash && !window.__applyingHash) {
               const skillSlug = slugify(name);
               if (skillSlug) {
                   // Check if we're in a sub-bar (nested skill)
                   const isNested = el.dataset.nested === '1';
                   
                   if (isNested) {
                       // We're in a sub-bar, update hash as #M{index}/subskill
                       let parentIndex = null;
                       if (window.__barStack && window.__barStack.length > 0) {
                           const lastFrame = window.__barStack[window.__barStack.length - 1];
                           if (lastFrame && lastFrame.parentIcon) {
                               parentIndex = lastFrame.parentIcon.dataset.index || null;
                           }
                       }
                       // Fallback: try to find from main icon bar
                       if (!parentIndex) {
                           const mainIconBar = document.querySelector('#skills .icon-bar');
                           const mainSkills = mainIconBar ? Array.from(mainIconBar.querySelectorAll('.skill-icon:not([data-nested="1"])')) : [];
                           // Find the skill that has this sub-bar
                           for (const mainSkill of mainSkills) {
                               const mainSubs = mainSkill.dataset.subs || ;
                               if (mainSubs) {
                                   try {
                                       const subs = JSON.parse(mainSubs);
                                       const hasThisSub = subs.some(s => {
                                           const subName = s.name || s.n || ;
                                           return slugify(subName) === skillSlug;
                                       });
                                       if (hasThisSub) {
                                           parentIndex = mainSkill.dataset.index || null;
                                           break;
                                       }
                                   } catch (e) { }
                               }
                           }
                       }
                       // Use compact slug for subskill to make URL shorter
                       const subSlug = compactSlugify(name);
                       if (parentIndex && subSlug) {
                           // Use format: #M{index}/subskill (more compact than full parent name)
                           const newHash = '#M' + parentIndex + '/' + subSlug;
                           if (window.location.hash !== newHash) {
                               history.replaceState(null, , window.location.pathname + window.location.search + newHash);
                           }
                       }
                   } else {
                       // We're in main bar, update hash as #skill
                       const currentHash = window.location.hash.slice(1);
                       const hasSubskillInHash = currentHash.includes('/') && currentHash.startsWith(skillSlug + '/');
                       // Only update hash if:
                       // 1. Skill has no subskills, OR
                       // 2. Skill has subskills but hash doesn't already contain a subskill of this skill
                       if (!subsRaw || subsRaw.trim() ===  || !hasSubskillInHash) {
                           const newHash = '#' + skillSlug;
                           if (window.location.hash !== newHash) {
                               history.replaceState(null, , window.location.pathname + window.location.search + newHash);
                           }
                       }
                   }
               }
           }
           // Open sub-bar if skill has subskills and openSubs is true
           if (openSubs && subsRaw && subsRaw.trim() !== ) {
               // Avoid re-opening same sub-bar if already opened by this icon (prevents flicker on language change)
               if (barStack.length && barStack[barStack.length - 1].parentIcon === el) return;
               try {
                   const subs = JSON.parse(subsRaw);
                   pushSubBarFrom(subs, el);
               } catch { /* no-op */ }
           }
       }
       function wireClicksForCurrentBar() {
           const currIcons = Array.from(iconsBar.querySelectorAll('.skill-icon'));
           currIcons.forEach(el => {
               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, { updateHash: true, openSubs: true });
               });
           });
           // Removido: badges de +/− (abrir/voltar via clique direto no ícone)
           wireTooltipsForNewIcons();
       }
       function snapshotCurrentBarItemsFromDOM() {
           return Array.from(iconsBar.querySelectorAll('.skill-icon')).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) { }
               }
               return {
                   name: el.dataset.nome || el.dataset.name || ,
                   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
               };
           });
       }
       function ensureBackButton() {
           const rail = iconsBar.closest('.top-rail.skills'); if (!rail) return null;
           // Wrap rail in a dedicated container if not already wrapped
           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';
               // keep layout
               parentNode.insertBefore(newWrap, rail);
               newWrap.appendChild(rail);
               wrap = newWrap;
           }
           // Ensure back-wrapper exists as sibling layered behind
           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');
               // Use SVG double chevron for a crisper, larger icon
               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');
                   // Update hash when going back
                   if (prev.items && prev.items.length > 0) {
                       const prevName = prev.items[0].name || ;
                       const prevSlug = slugify(prevName);
                       if (prevSlug) {
                           const newHash = '#' + prevSlug;
                           history.replaceState(null, , window.location.pathname + window.location.search + newHash);
                       }
                   } else {
                       history.replaceState(null, , window.location.pathname + window.location.search);
                   }
               });
           }
           // Always show back button when there's a sub-bar (user can always go back)
           backWrap.style.display = barStack.length ? 'block' : 'none';
           // Toggle class to show/hide mask
           wrap.classList.toggle('has-sub-bar', barStack.length > 0);
           const btnInner = backWrap.querySelector('.skills-back');
           return btnInner;
       }
       function renderBarFromItems(items) {
           // Hide tooltip when bar changes (prevents it from jumping to corner)
           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.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);
               // mark nested icons (no dataset.index)
               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);
           });
           // Animação de entrada (igual quando entra em subskills)
           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);
               });
           });
           wireClicksForCurrentBar(); const b = ensureBackButton(); if (b) b.classList.add('peek');
       }
       function pushSubBarFrom(subs, parentIconEl) {
           // Hide tooltip when opening sub-bar (prevents it from jumping to corner)
           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';
           }
           // Save current (store parentIcon to avoid re-opening same sub-bar on language change)
           barStack.push({ items: snapshotCurrentBarItemsFromDOM(), parentIcon: parentIconEl }); ensureBackButton();
           // Build next items from JSON
           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 items = (subs || []).map(s => {
               const name = (s.name || s.n || ).trim();
               const desc = chooseDescFrom(s).replace(/(.*?)/g, '$1');
               const attrsHTML = renderSubAttributesFromObj(s, L);
               // store sub-attrs object JSON to re-render attributes later
               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) || ),
                   // keep raw obj for attrs
                   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
               };
           });
           // Render and attach subattrs object via data-subattrs
           iconsBar.innerHTML = ;
           items.forEach((it, iIdx) => {
               const node = document.createElement('div'); node.className = 'skill-icon'; node.dataset.nested = '1';
               node.dataset.nome = it.name || ;
               // Store slug for hash matching
               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;
               const img = document.createElement('img'); img.alt = ; img.src = it.iconURL; node.appendChild(img);
               iconsBar.appendChild(node);
           });
           // pequena animação de entrada
           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);
               });
           });
           wireClicksForCurrentBar(); const b2 = ensureBackButton(); if (b2) b2.classList.add('peek');
       }
       // Reage à troca de idioma (emitida pelo char translator)
       window.addEventListener('gla:langChanged', () => {
           const skillsRoot = document.getElementById('skills');
           const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {};
           const lang = getLangKey();
           // Atualiza dataset.desc dos ícones da barra atual (skills)
           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;
           });
           // Atualiza descrições salvas no stack (para futuras barras ao voltar)
           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;
               });
           });
           // Atualiza dataset.desc dos ícones da weapon tab (se existir)
           const weaponTab = document.getElementById('weapon');
           if (weaponTab) {
               const weaponIconBar = weaponTab.querySelector('.icon-bar');
               if (weaponIconBar) {
                   Array.from(weaponIconBar.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;
                   });
               }
           }
           // Atualiza tooltips dos flags na descrição atual
           if (descBox) {
               applyFlagTooltips(descBox);
           }
       });
       // Wire initial (root) bar and add + badges
       wireClicksForCurrentBar(); const b0 = ensureBackButton(); if (b0) { b0.classList.add('peek'); b0.style.alignSelf = 'stretch'; }
       // ===========================
       // Hash-based deep linking
       // ===========================
       function applyHashSelection() {
           const hash = window.location.hash.slice(1); // Remove #
           if (!hash) return;
           // Support formats:
           // - #M{index}/subskill (new compact format for subskills)
           // - #parent/sub (old format with parent name)
           // - #parent-sub (old compact format)
           // - #skill (main skill)
           let skillSlug = hash;
           let subSlug = null;
           let isMFormat = false;
           let parentIndex = null;
           
           // Check for M{index}/subskill format first (new compact format)
           const mFormatMatch = hash.match(/^M(\d+)\/(.+)$/);
           if (mFormatMatch) {
               parentIndex = mFormatMatch[1];
               subSlug = mFormatMatch[2];
               isMFormat = true;
               // Find parent skill by index
               const currentIconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
               if (currentIconsBar) {
                   const parentSkill = currentIconsBar.querySelector(`.skill-icon:not([data-nested="1"])[data-index="${parentIndex}"]`);
                   if (parentSkill) {
                       const parentName = parentSkill.dataset.nome || parentSkill.dataset.name || ;
                       skillSlug = slugify(parentName);
                   }
               }
           } else if (hash.includes('/')) {
               // Old format: #parent/sub
               const parts = hash.split('/');
               skillSlug = parts[0];
               subSlug = parts[1];
           } else if (hash.includes('-')) {
               // Old compact format: #parent-sub
               // Try to find the parent skill first to determine where to split
               const currentIconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
               if (currentIconsBar) {
                   const allSkills = Array.from(currentIconsBar.querySelectorAll('.skill-icon:not([data-nested="1"])'));
                   // Try to find longest matching parent slug
                   let bestMatch = null;
                   let bestLength = 0;
                   for (const skill of allSkills) {
                       const name = skill.dataset.nome || skill.dataset.name || ;
                       const parentSlug = slugify(name);
                       if (hash.startsWith(parentSlug + '-') && parentSlug.length > bestLength) {
                           bestMatch = parentSlug;
                           bestLength = parentSlug.length;
                       }
                   }
                   if (bestMatch) {
                       skillSlug = bestMatch;
                       subSlug = hash.substring(bestLength + 1); // +1 for the hyphen
                   }
               }
           }
           // Get iconsBar fresh each time (may change when navigating)
           const currentIconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
           if (!currentIconsBar) {
               // Retry if iconsBar not ready
               setTimeout(() => {
                   applyHashSelection();
               }, 100);
               return;
           }
           // Find skill by slug
           // Strategy:
           // 1. If hash has format #parent/sub, we're looking for a subskill
           //    - First find parent skill in main bar
           //    - Then open sub-bar and find subskill
           // 2. If hash is just #skill, prefer main bar skill, but if not found, check sub-bar
           const allSkills = Array.from(currentIconsBar.querySelectorAll('.skill-icon'));
           let targetSkill = null;
           let targetSkillNested = null;
           
           for (const skill of allSkills) {
               const name = skill.dataset.nome || skill.dataset.name || ;
               const slugMatch = slugify(name) === skillSlug;
               if (slugMatch) {
                   const isNested = skill.dataset.nested === '1';
                   if (isNested) {
                       targetSkillNested = skill;
                   } else {
                       targetSkill = skill;
                   }
               }
           }
           
           // If we have a subSlug, we need parent skill from main bar (not nested)
           if (subSlug) {
               // If we're using M{index} format, find parent by index directly
               if (isMFormat && parentIndex) {
                   const parentByIndex = currentIconsBar.querySelector(`.skill-icon:not([data-nested="1"])[data-index="${parentIndex}"]`);
                   if (parentByIndex) {
                       targetSkill = parentByIndex;
                   }
               }
               // We're looking for a subskill, so we need the parent skill from main bar
               // If targetSkill is nested, we need to go back to main bar first
               if (!targetSkill && targetSkillNested) {
                   // Skill found in sub-bar, but we need it from main bar
                   // This means we need to navigate back to main bar first
                   // But wait - if we're looking for #parent/sub, the parent should be in main bar
                   // So this shouldn't happen. Let's handle it anyway.
                   targetSkill = null; // Force search in main bar
               }
           } else {
               // No subSlug - looking for main skill
               // Prefer main bar skill, but if not found, use nested as fallback
               if (!targetSkill && targetSkillNested) {
                   targetSkill = targetSkillNested;
               }
           }
           if (!targetSkill) {
               // Retry if skill not found yet (may still be loading)
               setTimeout(() => {
                   applyHashSelection();
               }, 200);
               return;
           }
           // Set flag to suppress hash updates during programmatic activation
           window.__applyingHash = true;
           // If there's a subskill, we need to activate the parent skill first and wait for sub-bar
           if (subSlug) {
               // Check if targetSkill is already nested (we're in a sub-bar)
               const isNested = targetSkill.dataset.nested === '1';
               
               if (isNested) {
                   // We're already in a sub-bar, just activate the nested skill directly
                   if (!targetSkill.dataset.wired) {
                       wireClicksForCurrentBar();
                   }
                   activateSkill(targetSkill, { updateHash: false, openSubs: false });
                   window.__applyingHash = false;
                   return;
               }
               
               // Check if skill has subskills
               const hasSubs = targetSkill.dataset.subs && targetSkill.dataset.subs.trim() !== ;
               if (!hasSubs) {
                   // Skill doesn't have subskills, just activate it
                   if (!targetSkill.dataset.wired) {
                       wireClicksForCurrentBar();
                   }
                   activateSkill(targetSkill, { updateHash: false, openSubs: false });
                   window.__applyingHash = false;
                   return;
               }
               // Ensure skill is wired before activating
               if (!targetSkill.dataset.wired) {
                   wireClicksForCurrentBar();
               }
               // Activate parent skill with openSubs=true to open sub-bar, but updateHash=false
               activateSkill(targetSkill, { updateHash: false, openSubs: true });
               // Wait for sub-bar to open after activating parent skill
               setTimeout(() => {
                   tryFindSubskillAfterParentClick(subSlug);
               }, 500);
           } else {
               // No subskill, just activate the skill
               // But check if we're looking for a skill that exists in sub-bar
               // If targetSkill is nested, we need to go back to main bar first
               const isNested = targetSkill.dataset.nested === '1';
               if (isNested) {
                   // We're in a sub-bar but hash points to main bar skill
                   // Need to go back to main bar first
                   if (barStack.length > 0) {
                       const prev = barStack.pop();
                       renderBarFromItems(prev.items);
                       const btn = document.querySelector('.skills-back-wrapper');
                       if (btn) btn.style.display = barStack.length ? 'block' : 'none';
                       // Now find the skill in main bar
                       setTimeout(() => {
                           const mainBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
                           if (mainBar) {
                               const mainSkills = Array.from(mainBar.querySelectorAll('.skill-icon:not([data-nested="1"])'));
                               for (const skill of mainSkills) {
                                   const name = skill.dataset.nome || skill.dataset.name || ;
                                   if (slugify(name) === skillSlug) {
                                       if (!skill.dataset.wired) {
                                           wireClicksForCurrentBar();
                                       }
                                       activateSkill(skill, { updateHash: false, openSubs: false });
                                       break;
                                   }
                               }
                           }
                           window.__applyingHash = false;
                       }, 300);
                       return;
                   }
               }
               
               if (!targetSkill.dataset.wired) {
                   wireClicksForCurrentBar();
               }
               activateSkill(targetSkill, { updateHash: false, openSubs: false });
               window.__applyingHash = false;
           }
       }
       function tryFindSubskillAfterParentClick(subSlug) {
           let retries = 0;
           const maxRetries = 25;
           const tryFindSubskill = () => {
               // Check both subskills-rail (old system) and icon-bar with nested skills (replace main bar)
               const subRail = document.querySelector('.subskills-rail');
               const subBar = subRail ? subRail.querySelector('.subicon-bar') : null;
               const currentIconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
               const nestedIcons = currentIconsBar ? Array.from(currentIconsBar.querySelectorAll('.skill-icon[data-nested="1"]')) : [];
               // Try nested icons first (replace main bar system)
               if (nestedIcons.length > 0) {
                   for (const nestedIcon of nestedIcons) {
                       const nestedName = nestedIcon.dataset.nome || nestedIcon.dataset.name || ;
                       // Try data-slug first (more reliable)
                       if (nestedIcon.dataset.slug === subSlug) {
                           if (!nestedIcon.dataset.wired) {
                               wireClicksForCurrentBar();
                           }
                           activateSkill(nestedIcon, { updateHash: false, openSubs: false });
                           window.__applyingHash = false;
                           return;
                       }
                       // Try full slug match
                       const nestedSlugMatch = slugify(nestedName);
                       if (nestedSlugMatch === subSlug) {
                           if (!nestedIcon.dataset.wired) {
                               wireClicksForCurrentBar();
                           }
                           activateSkill(nestedIcon, { updateHash: false, openSubs: false });
                           window.__applyingHash = false;
                           return;
                       }
                       // Try compact slug match (for new format)
                       const nestedCompactSlug = compactSlugify(nestedName);
                       if (nestedCompactSlug === subSlug) {
                           if (!nestedIcon.dataset.wired) {
                               wireClicksForCurrentBar();
                           }
                           activateSkill(nestedIcon, { updateHash: false, openSubs: false });
                           window.__applyingHash = false;
                           return;
                       }
                   }
               }
               // Fallback to old subskills-rail system
               if (subRail && subBar && !subRail.classList.contains('hidden') && !subRail.classList.contains('collapsed')) {
                   const subIcons = subBar.querySelectorAll('.subicon');
                   if (subIcons.length > 0) {
                       for (const subIcon of subIcons) {
                           // Try data-slug first (more reliable)
                           if (subIcon.dataset.slug === subSlug) {
                               subIcon.click();
                               window.__applyingHash = false;
                               return;
                           }
                           // Fallback to title/aria-label
                           const subName = subIcon.title || subIcon.getAttribute('aria-label') || ;
                           const subSlugMatch = slugify(subName);
                           if (subSlugMatch === subSlug) {
                               subIcon.click();
                               window.__applyingHash = false;
                               return;
                           }
                       }
                   }
               }
               // If not found, retry
               retries++;
               if (retries < maxRetries) {
                   setTimeout(tryFindSubskill, 200);
               } else {
                   window.__applyingHash = false;
               }
           };
           // Start trying after a small delay
           setTimeout(tryFindSubskill, 100);
       }
       // Apply hash on initial load (wait for DOM and elements to be ready)
       let hashSelectionAttempted = false;
       function initHashSelection() {
           if (!window.location.hash) return;
           // Get iconsBar fresh each time
           const currentIconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
           const iconCount = currentIconsBar ? currentIconsBar.querySelectorAll('.skill-icon').length : 0;
           // Wait for iconsBar to be available and have icons
           if (!currentIconsBar || iconCount === 0) {
               if (!hashSelectionAttempted) {
                   setTimeout(initHashSelection, 150);
               }
               return;
           }
           hashSelectionAttempted = true;
           // Small delay to ensure everything is wired
           setTimeout(() => {
               applyHashSelection();
           }, 400);
       }
       // Start hash selection - multiple strategies to ensure it runs
       function startHashSelection() {
           if (document.readyState === 'loading') {
               document.addEventListener('DOMContentLoaded', () => {
                   setTimeout(initHashSelection, 300);
               });
           } else {
               setTimeout(initHashSelection, 300);
           }
       }
       // Start immediately if hash exists
       if (window.location.hash) {
           startHashSelection();
       }
       // Also try on window load as fallback
       window.addEventListener('load', () => {
           if (window.location.hash) {
               hashSelectionAttempted = false;
               setTimeout(initHashSelection, 500);
           }
       });
       // One more fallback: try after a longer delay
       if (window.location.hash) {
           setTimeout(() => {
               if (!document.querySelector('.skill-icon.active')) {
                   hashSelectionAttempted = false;
                   initHashSelection();
               }
           }, 1000);
       }
       // MutationObserver fallback: watch for skill icons being added
       if (window.location.hash) {
           const observer = new MutationObserver((mutations) => {
               const hasIcons = document.querySelectorAll('#skills .icon-bar .skill-icon').length > 0;
               if (hasIcons && !hashSelectionAttempted) {
                   hashSelectionAttempted = true;
                   setTimeout(() => {
                       applyHashSelection();
                   }, 200);
               }
           });
           const skillsContainer = document.getElementById('skills');
           if (skillsContainer) {
               observer.observe(skillsContainer, {
                   childList: true,
                   subtree: true
               });
               // Stop observing after 5 seconds
               setTimeout(() => {
                   observer.disconnect();
               }, 5000);
           }
       }
       // Expose function globally for manual triggering if needed
       window.__applySkillHash = function () {
           hashSelectionAttempted = false;
           initHashSelection();
       };
       // Listen for hash changes (back/forward navigation)
       window.addEventListener('hashchange', () => {
           applyHashSelection();
       });
       // ===========================
       // Tooltip System
       // ===========================
       (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';
           }
           // Export to global for subskills to use
           window.__globalSkillTooltip = {
               show,
               hide,
               measureAndPos,
               lockUntil: lockUntilRef
           };
           Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => {
               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);
           });
           // Also wire subskill icons present at load (kept in sync when sub-rail opens)
           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);
           });
       })();
       // ===========================
       // Tab System (com transição suave de altura)
       // ===========================
       (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');
           // Cria o wrapper UMA VEZ no início
           let wrapper = characterBox.querySelector('.tabs-height-wrapper');
           if (!wrapper) {
               wrapper = document.createElement('div');
               wrapper.className = 'tabs-height-wrapper';
               // Move os conteúdos das abas para dentro do wrapper
               contents.forEach(c => {
                   wrapper.appendChild(c);
               });
               // Encontra onde inserir o wrapper (após as tabs)
               const tabsElement = characterBox.querySelector('.character-tabs');
               if (tabsElement && tabsElement.nextSibling) {
                   characterBox.insertBefore(wrapper, tabsElement.nextSibling);
               } else {
                   characterBox.appendChild(wrapper);
               }
           }
           // Função para animar a altura suavemente (retorna Promise)
           // NOVA ESTRATÉGIA: aba já está visível mas invisível (opacity:0, visibility:hidden)
           async function smoothHeightTransition(fromTab, toTab) {
               if (!wrapper) return Promise.resolve();
               // Salva o scroll atual
               const scrollY = window.scrollY;
               // Mede a altura ATUAL
               const currentHeight = wrapper.getBoundingClientRect().height;
               // A aba toTab JÁ está display:block mas invisível
               // Aguarda ela renderizar COMPLETAMENTE na posição real
               await new Promise((resolve) => {
                   const videoContainers = toTab.querySelectorAll('.video-container');
                   const contentCard = toTab.querySelector('.content-card');
                   if (videoContainers.length === 0) {
                       // Sem vídeos, aguarda 3 frames
                       requestAnimationFrame(() => {
                           requestAnimationFrame(() => {
                               requestAnimationFrame(() => resolve());
                           });
                       });
                       return;
                   }
                   // COM vídeos: aguarda até que TUDO esteja renderizado
                   // Faz polling da altura até estabilizar
                   let lastHeight = 0;
                   let stableCount = 0;
                   const checksNeeded = 3; // Altura precisa ficar estável por 3 checks
                   let totalChecks = 0;
                   const maxChecks = 15; // Máximo 15 checks (750ms)
                   function checkStability() {
                       totalChecks++;
                       // Mede altura atual da aba
                       const currentTabHeight = toTab.scrollHeight;
                       // Verifica se estabilizou
                       if (Math.abs(currentTabHeight - lastHeight) < 5) {
                           stableCount++;
                       } else {
                           stableCount = 0; // Resetar se mudou
                       }
                       lastHeight = currentTabHeight;
                       // Se estável por N checks ou atingiu máximo, resolve
                       if (stableCount >= checksNeeded || totalChecks >= maxChecks) {
                           resolve();
                       } else {
                           // Continua checando a cada 50ms
                           setTimeout(checkStability, 50);
                       }
                   }
                   // Inicia checagem após um pequeno delay
                   setTimeout(checkStability, 50);
               });
               // AGORA mede a altura REAL (após tudo renderizado)
               const nextHeight = toTab.getBoundingClientRect().height;
               const finalHeight = Math.max(nextHeight, 100);
               // Se alturas são similares (< 30px diferença), não anima
               if (Math.abs(finalHeight - currentHeight) < 30) {
                   wrapper.style.height = ;
                   return Promise.resolve();
               }
               // Define altura inicial fixa
               wrapper.style.overflow = 'hidden';
               wrapper.style.height = currentHeight + 'px';
               // Força reflow
               wrapper.offsetHeight;
               // Define transição
               wrapper.style.transition = 'height 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
               // Anima para a altura FINAL (já medida corretamente)
               requestAnimationFrame(() => {
                   wrapper.style.height = finalHeight + 'px';
                   // Não altere a posição do scroll do usuário
               });
               // Limpa após transição
               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; // Já está na aba
                   // Previne scroll durante a transição
                   document.body.classList.add('transitioning-tabs');
                   // Desativa a aba atual (fade out)
                   if (currentActive) {
                       currentActive.style.opacity = '0';
                       currentActive.style.transform = 'translateY(-8px)';
                   }
                   // Delay para fade out completar
                   setTimeout(async () => {
                       // Remove display de TODAS as abas inativas
                       contents.forEach(c => {
                           if (c !== nextActive) {
                               c.style.display = 'none';
                               c.classList.remove('active');
                           }
                       });
                       // Ativa os botões
                       tabs.forEach(b => b.classList.toggle('active', b === btn));
                       // MOSTRA a nova aba INVISÍVEL na posição real
                       if (nextActive) {
                           nextActive.classList.add('active');
                           nextActive.style.display = 'block';
                           nextActive.style.opacity = '0';
                           nextActive.style.visibility = 'hidden';
                           // Força renderização completa ANTES de medir
                           nextActive.offsetHeight;
                           // Pré-carrega/ativa conteúdo padrão da aba (ex.: vídeo) ANTES da medição
                           // Assim a altura final já considera o player visível
                           try {
                               if (target === 'weapon' || 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) {
                                           // Evita autoplay durante preparação
                                           const had = document.body.dataset.suppressSkillPlay;
                                           document.body.dataset.suppressSkillPlay = '1';
                                           toClick.click();
                                           // Mantém a flag até o fim da transição (limpamos mais abaixo)
                                           if (had) document.body.dataset.suppressSkillPlay = had;
                                       }
                                   }
                               }
                           } catch (e) { /* no-op */ }
                       }
                       // AGORA anima altura (com a aba já renderizada na posição correta)
                       if (currentActive && nextActive) {
                           await smoothHeightTransition(currentActive, nextActive);
                       }
                       // Após altura estar correta, MOSTRA a nova aba
                       if (nextActive) {
                           nextActive.style.visibility = ;
                           nextActive.style.transform = 'translateY(12px)';
                           // Anima entrada
                           requestAnimationFrame(() => {
                               nextActive.style.opacity = '1';
                               nextActive.style.transform = 'translateY(0)';
                               // Limpa estilos inline e classe de transição
                               setTimeout(() => {
                                   nextActive.style.opacity = ;
                                   nextActive.style.transform = ;
                                   document.body.classList.remove('transitioning-tabs');
                                   // Libera flag de autoplay suprimido (se aplicada acima)
                                   try { delete document.body.dataset.suppressSkillPlay; } catch { }
                               }, 300);
                           });
                       }
                   }, 120);
                   // Executa ações após a transição completa
                   setTimeout(() => {
                       syncDescHeight();
                       if (target === 'skins') {
                           // Pause all cached main skill videos
                           videosCache.forEach(v => { try { v.pause(); } catch (e) { } v.style.display = 'none'; });
                           // Pause all nested/subskill videos
                           if (videoBox) {
                               videoBox.querySelectorAll('video.skill-video').forEach(v => {
                                   try { v.pause(); } catch (e) { }
                                   v.style.display = 'none';
                               });
                           }
                           // Hide subskill videos via API
                           if (window.__subskills) window.__subskills.hideAll?.(videoBox);
                           if (videoBox && placeholder) { placeholder.style.display = 'none'; placeholder.classList.add('fade-out'); }
                           // Pause weapon videos when entering skins
                           const weaponTabEl = document.getElementById('weapon');
                           if (weaponTabEl) {
                               const wvb = weaponTabEl.querySelector('.video-container');
                               if (wvb) wvb.querySelectorAll('video.skill-video').forEach(v => { try { v.pause(); } catch (e) { } v.style.display = 'none'; });
                           }
                       } else if (target === 'weapon') {
                           // Initialize or refresh weapon tab
                           const weaponTab = document.getElementById('weapon');
                           if (weaponTab) {
                               const activeWeaponIcon = weaponTab.querySelector('.icon-bar .skill-icon.active');
                               const toClick = activeWeaponIcon || weaponTab.querySelector('.icon-bar .skill-icon');
                               if (toClick) toClick.click();
                               // Pause skills videos when entering weapon
                               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'; });
                           }
                       } else {
                           const activeIcon = document.querySelector('.icon-bar .skill-icon.active');
                           if (activeIcon) activeIcon.click();
                           // Pause weapon videos when returning to skills
                           const weaponTabEl = document.getElementById('weapon');
                           if (weaponTabEl) {
                               const wvb = weaponTabEl.querySelector('.video-container');
                               if (wvb) wvb.querySelectorAll('video.skill-video').forEach(v => { try { v.pause(); } catch (e) { } v.style.display = 'none'; });
                           }
                       }
                   }, 450); // Após transição completar (120ms + 300ms + buffer)
               });
           });
       })();
       // ===========================
       // Skins Navigation
       // ===========================
       (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();
       })();
       // ===========================
       // Utility Functions
       // ===========================
       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());
           const pve = parseInt(vals[0], 10);
           const pvp = parseInt(vals[1], 10);
           const ene = parseInt(vals[2], 10);
           const cd = parseInt(vals[3], 10);
           const rows = [];
           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]);
           if (!rows.length) return ;
           const html = rows.map(([label, value]) => `
     ${label}
     ${value}

`).join(); return `

${html}

`;

       }
       function syncDescHeight() {
           // no-op on purpose
       }
       // ===========================
       // Event Listeners & Initialization
       // ===========================
       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;
           }
       });
       // Initialize first skill (only if no hash in URL)
       // Wire clicks first so activateSkill is available
       wireClicksForCurrentBar();
       
       if (iconItems.length) {
           // Skip auto-activation if there's a hash (let hash selection handle it)
           if (!window.location.hash || window.location.hash.length <= 1) {
               const first = iconItems[0];
               if (first) {
                   // Set flag to prevent hash updates during initial activation
                   window.__applyingHash = true;
                   // Activate first skill without updating hash and without opening sub-bar
                   activateSkill(first, { updateHash: false, openSubs: false });
                   // Clear flag after a short delay to allow normal clicks to update hash
                   setTimeout(() => {
                       window.__applyingHash = false;
                   }, 100);
               }
           }
       }
       // ===========================
       // Weapon Tab Setup (if exists)
       // ===========================
       (function initWeaponTab() {
           const weaponTab = document.getElementById('weapon');
           if (!weaponTab) return;
           const weaponIconBar = weaponTab.querySelector('.icon-bar');
           const weaponIconItems = weaponIconBar ? Array.from(weaponIconBar.querySelectorAll('.skill-icon')) : [];
           const weaponDescBox = weaponTab.querySelector('.desc-box');
           const weaponVideoBox = weaponTab.querySelector('.video-container');
           if (!weaponIconBar || !weaponIconItems.length) return;
           const weaponVideosCache = new Map();
           let weaponTotalVideos = 0, weaponLoadedVideos = 0, weaponAutoplay = false;
           // Load weapon videos
           weaponIconItems.forEach(el => {
               const src = (el.dataset.video || ).trim();
               const idx = el.dataset.index || ;
               if (!src || weaponVideosCache.has(idx)) return;
               weaponTotalVideos++;
               const v = document.createElement('video');
               v.className = 'skill-video';
               v.setAttribute('controls', );
               v.setAttribute('preload', 'auto');
               v.setAttribute('playsinline', );
               v.style.display = 'none';
               v.dataset.index = idx;
               v.style.width = '100%';
               v.style.maxWidth = '100%';
               v.style.height = 'auto';
               v.style.aspectRatio = '16/9';
               v.style.objectFit = 'cover';
               const source = document.createElement('source');
               source.src = src;
               source.type = 'video/webm';
               v.appendChild(source);
               v.addEventListener('canplay', () => {
                   weaponLoadedVideos++;
                   if (weaponLoadedVideos === weaponTotalVideos) weaponAutoplay = true;
               });
               v.addEventListener('error', () => {
                   weaponLoadedVideos++;
                   if (weaponLoadedVideos === weaponTotalVideos) weaponAutoplay = true;
               });
               weaponVideoBox.appendChild(v);
               weaponVideosCache.set(idx, v);
           });
           // Wire clicks for weapon icons
           weaponIconItems.forEach(el => {
               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', () => {
                   const skillsRoot = document.getElementById('skills');
                   const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {};
                   const lang = getLangKey();
                   const L = i18nMap[lang] || i18nMap.pt || {
                       cooldown: 'Recarga',
                       energy_gain: 'Ganho de energia',
                       energy_cost: 'Custo de energia',
                       power: 'Poder',
                       power_pvp: 'Poder PvP',
                       level: 'Nível'
                   };
                   const name = el.dataset.nome || el.dataset.name || ;
                   const level = (el.dataset.level || ).trim();
                   const descPack = {
                       pt: el.dataset.descPt || ,
                       en: el.dataset.descEn || ,
                       es: el.dataset.descEs || ,
                       pl: el.dataset.descPl || 
                   };
                   const chosenDesc = descPack[lang] || descPack.pt || descPack.en || descPack.es || descPack.pl || el.dataset.desc || ;
                   const descHtml = chosenDesc.replace(/(.*?)/g, '$1');
                   const attrsHTML = el.dataset.atr ? renderAttributes(el.dataset.atr) : ;
                   if (weaponDescBox) {
                       weaponDescBox.innerHTML = `

${name}

${level ? `

${L.level} ${level}

` : }

       ${attrsHTML}
${descHtml}

`;

                   }
                   weaponIconItems.forEach(i => i.classList.remove('active'));
                   el.classList.add('active');
                   if (!weaponAutoplay && weaponLoadedVideos > 0) weaponAutoplay = true;
                   window.__lastActiveSkillIcon = el;
                   // Show video
                   if (weaponVideoBox) {
                       Array.from(weaponVideoBox.querySelectorAll('video.skill-video')).forEach(v => {
                           try { v.pause(); } catch (e) { }
                           v.style.display = 'none';
                       });
                   }
                   const hasIdx = !!el.dataset.index;
                   const hasVideo = !!(el.dataset.video && el.dataset.video.trim() !== );
                   if (!weaponVideoBox || !hasVideo) {
                       if (weaponVideoBox) weaponVideoBox.style.display = 'none';
                       return;
                   }
                   if (hasIdx && weaponVideosCache.has(el.dataset.index)) {
                       const v = weaponVideosCache.get(el.dataset.index);
                       weaponVideoBox.style.display = 'block';
                       v.style.display = 'block';
                       try { v.currentTime = 0; } catch (e) { }
                       const suppress = document.body.dataset.suppressSkillPlay === '1';
                       if (!suppress && weaponAutoplay) v.play().catch(() => { });
                   }
               });
           });
           // Wire tooltips for weapon icons
           const tip = document.querySelector('.skill-tooltip');
           if (tip) {
               weaponIconItems.forEach(icon => {
                   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 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, label));
                   icon.addEventListener('mousemove', () => measureAndPos(icon));
                   icon.addEventListener('click', () => { window.__globalSkillTooltip.lockUntil.value = performance.now() + 240; measureAndPos(icon); });
                   icon.addEventListener('mouseleave', hide);
               });
           }
           // Do not pre-mark active here; activation handled on tab enter
       })();
       // Debug logging
       setTimeout(() => {
           Array.from(document.querySelectorAll('.skill-icon')).forEach(el => {
               console.log('icon', el.dataset.index, 'data-video=', el.dataset.video);
           });
           videosCache.forEach((v, idx) => {
               const src = v.querySelector('source') ? v.querySelector('source').src : v.src;
               console.log('video element', idx, 'src=', src, 'readyState=', v.readyState);
               v.addEventListener('error', (ev) => {
                   console.error('VIDEO ERROR idx=', idx, 'src=', src, 'error=', v.error);
               });
               v.addEventListener('loadedmetadata', () => {
                   console.log('loadedmetadata idx=', idx, 'dimensions=', v.videoWidth, 'x', v.videoHeight);
               });
           });
       }, 600);
   })();

</script>