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

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
 
(36 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
Linha 382: Linha 382:


       return null;
       return null;
    }
    function getNextFormName() {
      const formNames = Object.keys(formsData);
      if (formNames.length === 0) return null;
      let cur = currentForm;
      if (cur === null) {
        cur = detectCurrentForm();
      }
      if (!cur && formNames.length > 0) {
        cur = formNames[0];
      }
      let orderedFormNames = [];
      if (cur === "Brain Point" && formNames.length === 3 &&
        formNames.includes("Kung Fu Point") && formNames.includes("Heavy Point")) {
        orderedFormNames = ["Brain Point", "Kung Fu Point", "Heavy Point"];
      } else if (cur === "Kung Fu Point" && formNames.length === 3 &&
        formNames.includes("Heavy Point") && formNames.includes("Brain Point")) {
        orderedFormNames = ["Kung Fu Point", "Heavy Point", "Brain Point"];
      } else if (cur === "Heavy Point" && formNames.length === 3 &&
        formNames.includes("Brain Point") && formNames.includes("Kung Fu Point")) {
        orderedFormNames = ["Heavy Point", "Brain Point", "Kung Fu Point"];
      }
      if (orderedFormNames.length === 0) {
        orderedFormNames = [...formNames].sort();
        if (cur) {
          const idx = orderedFormNames.indexOf(cur);
          if (idx !== -1) {
            orderedFormNames = [
              ...orderedFormNames.slice(idx),
              ...orderedFormNames.slice(0, idx),
            ];
          }
        }
      }
      const idx = orderedFormNames.indexOf(cur);
      if (idx === -1) return null;
      return orderedFormNames[(idx + 1) % orderedFormNames.length];
     }
     }


Linha 441: Linha 483:
       if (formNames.length === 0) return;
       if (formNames.length === 0) return;


      // Determina próxima forma
      // Se currentForm é null, detecta qual forma está atualmente visível no DOM
       if (currentForm === null) {
       if (currentForm === null) {
         currentForm = detectCurrentForm();
         currentForm = detectCurrentForm();
      }
        if (!currentForm && formNames.length > 0) {
 
           currentForm = formNames[0];
      // Se ainda não conseguiu detectar, usa a primeira forma como fallback
      if (!currentForm && formNames.length > 0) {
        currentForm = formNames[0];
      }
 
      // Cria ordem circular fixa baseada na forma atual detectada
      // Se detectamos "Brain Point", ordem é: Brain → Kung Fu → Heavy → Brain
      // Se detectamos "Kung Fu Point", ordem é: Kung Fu → Heavy → Brain → Kung Fu
      // Se detectamos "Heavy Point", ordem é: Heavy → Brain → Kung Fu → Heavy
      let orderedFormNames = [];
      if (currentForm === "Brain Point" && formNames.length === 3) {
        // Ordem conhecida: Brain → Kung Fu → Heavy
        if (
           formNames.includes("Kung Fu Point") &&
          formNames.includes("Heavy Point")
        ) {
          orderedFormNames = ["Brain Point", "Kung Fu Point", "Heavy Point"];
        }
      } else if (currentForm === "Kung Fu Point" && formNames.length === 3) {
        // Ordem conhecida: Kung Fu → Heavy → Brain
        if (
          formNames.includes("Heavy Point") &&
          formNames.includes("Brain Point")
        ) {
          orderedFormNames = ["Kung Fu Point", "Heavy Point", "Brain Point"];
        }
      } else if (currentForm === "Heavy Point" && formNames.length === 3) {
        // Ordem conhecida: Heavy → Brain → Kung Fu
        if (
          formNames.includes("Brain Point") &&
          formNames.includes("Kung Fu Point")
        ) {
          orderedFormNames = ["Heavy Point", "Brain Point", "Kung Fu Point"];
        }
      }
 
      // Se não conseguiu criar ordem conhecida, usa ordem alfabética como fallback
      if (orderedFormNames.length === 0) {
        orderedFormNames = [...formNames].sort();
        // Se sabemos a forma atual, reorganiza para começar por ela
        if (currentForm) {
          const currentIdx = orderedFormNames.indexOf(currentForm);
          if (currentIdx !== -1) {
            orderedFormNames = [
              ...orderedFormNames.slice(currentIdx),
              ...orderedFormNames.slice(0, currentIdx),
            ];
          }
         }
         }
       }
       }


       const currentIdx = orderedFormNames.indexOf(currentForm);
       const nextForm = getNextFormName();
       if (currentIdx === -1) return; // Forma não encontrada
       if (!nextForm) return;
 
      const nextIdx = (currentIdx + 1) % orderedFormNames.length;
      const nextForm = orderedFormNames[nextIdx];


       currentForm = nextForm;
       currentForm = nextForm;
Linha 835: Linha 824:
         skill.level &&
         skill.level &&
         skill.level !== "" &&
         skill.level !== "" &&
         skill.level.toUpperCase() !== "NIVEL"
         String(skill.level).toUpperCase() !== "NIVEL"
       ) {
       ) {
         iconWrap.setAttribute("data-level", skill.level);
         iconWrap.setAttribute("data-level", skill.level);
Linha 877: Linha 866:
           iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
           iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
         } catch (e) { }
         } catch (e) { }
      }
      if (skill.formSwitch) {
        iconWrap.setAttribute("data-form-switch", skill.formSwitch);
      }
      if (skill.formVideos && typeof skill.formVideos === "string" && skill.formVideos.trim() !== "") {
        iconWrap.setAttribute("data-form-videos", skill.formVideos);
       }
       }


Linha 915: Linha 911:
         skill.level &&
         skill.level &&
         skill.level !== "" &&
         skill.level !== "" &&
         skill.level.toUpperCase() !== "NIVEL"
         String(skill.level).toUpperCase() !== "NIVEL"
       ) {
       ) {
         iconWrap.setAttribute("data-level", skill.level);
         iconWrap.setAttribute("data-level", skill.level);
Linha 957: Linha 953:
           iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
           iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
         } catch (e) { }
         } catch (e) { }
      }
      if (skill.formSwitch) {
        iconWrap.setAttribute("data-form-switch", skill.formSwitch);
      }
      if (skill.formVideos && typeof skill.formVideos === "string" && skill.formVideos.trim() !== "") {
        iconWrap.setAttribute("data-form-videos", skill.formVideos);
       }
       }


Linha 1 057: Linha 1 060:
     }
     }
     function getFlagIconURL(key) {
     function getFlagIconURL(key) {
       if (!FLAG_ICON_FILES[key]) return "";
      const keyNorm = String(key || "").trim().toLowerCase();
       if (!flagIconURLCache.has(key)) {
       if (!keyNorm || !FLAG_ICON_FILES[keyNorm]) return "";
         flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key]));
       if (!flagIconURLCache.has(keyNorm)) {
         flagIconURLCache.set(keyNorm, filePathURL(FLAG_ICON_FILES[keyNorm]));
       }
       }
       return flagIconURLCache.get(key);
       return flagIconURLCache.get(keyNorm);
     }
     }
     function renderFlagsRow(flags) {
     function renderFlagsRow(flags) {
       const arr = (flags || []).filter(Boolean);
       const arr = (flags || []).filter(Boolean).map((k) => String(k || "").trim().toLowerCase());
       if (!arr.length) return "";
      const arrFiltered = arr.filter(Boolean);
       const cacheKey = arr.join("|");
       if (!arrFiltered.length) return "";
       const cacheKey = arrFiltered.join("|");
       if (flagRowCache.has(cacheKey)) {
       if (flagRowCache.has(cacheKey)) {
         return flagRowCache.get(cacheKey);
         return flagRowCache.get(cacheKey);
       }
       }
       const items = arr
       const items = arrFiltered
         .map((k) => {
         .map((k) => {
           const url = getFlagIconURL(k);
           const url = getFlagIconURL(k);
Linha 1 199: Linha 1 204:
         if (!videoFile) {
         if (!videoFile) {
           videoFile = extractFileNameFromURL(icon.dataset.video || "");
           videoFile = extractFileNameFromURL(icon.dataset.video || "");
        }
        let flags = null;
        if (icon.dataset.flags) {
          try {
            flags = JSON.parse(icon.dataset.flags);
            if (!Array.isArray(flags) || flags.length === 0) flags = null;
          } catch (e) { flags = null; }
         }
         }
         const meta = {
         const meta = {
Linha 1 215: Linha 1 227:
           descEs: icon.dataset.descEs || "",
           descEs: icon.dataset.descEs || "",
           descPl: icon.dataset.descPl || "",
           descPl: icon.dataset.descPl || "",
          flags: flags || null,
         };
         };
         mainSkillsMeta.byIndex.set(index, meta);
         mainSkillsMeta.byIndex.set(index, meta);
Linha 1 280: Linha 1 293:
       hydrated.energy = pickFilled(hydrated.energy, main.energy || "");
       hydrated.energy = pickFilled(hydrated.energy, main.energy || "");
       hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || "");
       hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || "");
      // Flags: herda da skill principal se a subskill não tiver
      if (
        (!hydrated.flags || !Array.isArray(hydrated.flags) || hydrated.flags.length === 0) &&
        main.flags && Array.isArray(main.flags) && main.flags.length > 0
      ) {
        hydrated.flags = main.flags;
      }


       // Descrição: sempre vem da subskill, nunca herda
       // Descrição: sempre vem da subskill, nunca herda
Linha 1 647: Linha 1 668:
     // Busca descBox e videoBox após a estrutura estar organizada
     // Busca descBox e videoBox após a estrutura estar organizada
     const descBox = $("#skills") ? $(".desc-box", $("#skills")) : null;
     const descBox = $("#skills") ? $(".desc-box", $("#skills")) : null;
     const videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
     let videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
    // Busca dinâmica do videoBox (pode não existir no momento da inicialização)
    function getVideoBox() {
      if (videoBox && videoBox.isConnected) return videoBox;
      const skillsRoot = document.getElementById("skills");
      if (!skillsRoot) return null;
      videoBox = skillsRoot.querySelector(".video-container") ||
        skillsRoot.querySelector(".skills-container .video-container") ||
        skillsRoot.querySelector(".content-card .video-container");
      return videoBox;
    }
     const videosCache = new Map();
     const videosCache = new Map();
     const nestedVideoElByIcon = new WeakMap();
     const nestedVideoElByIcon = new WeakMap();
Linha 1 971: Linha 2 002:
         }
         }


         /* ========== ESTILOS DE EFFECT TEMPORARIO ========== */
         .character-box .top-rail.skills .icon-bar {
         .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle) {
            position: relative;
             overflow: visible !important;
        }
             contain: none !important;
        .character-box .top-rail.skills .icon-bar .skill-icon {
            z-index: 2;
        }
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active) {
            border-color: transparent !important;
            outline: 2px solid rgba(210, 60, 60, 0.95) !important;
            outline-offset: -2px;
            animation: effect-child-outline 1.6s ease-in-out infinite !important;
        }
         .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
            box-shadow:
                inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                0 0 6px rgba(255, 60, 60, 0.45),
                0 0 10px rgba(120, 20, 20, 0.6) !important;
            animation: effect-child-ring 1.6s ease-in-out infinite !important;
            opacity: 1 !important;
        }
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
             inset: 0 !important;
            border-radius: inherit !important;
             z-index: 1 !important;
            opacity: 1 !important;
            box-shadow:
                0 0 8px rgba(255, 60, 60, 0.35),
                0 0 14px rgba(120, 20, 20, 0.45);
            animation: effect-child-glow 1.6s ease-in-out infinite !important;
         }
         }
         .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
         .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
            pointer-events: none !important;
             box-shadow:
             box-shadow: none !important;
                inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
            background: conic-gradient(from var(--effect-spin),
                 0 0 6px rgba(255, 60, 60, 0.45),
                #d92a21 0deg,
                0 0 10px rgba(120, 20, 20, 0.6) !important;
                #d92a21 140deg,
             animation: effect-child-ring 1.6s ease-in-out infinite !important;
                #000000 190deg,
             opacity: 1 !important;
                #d92a21 320deg,
                 #d92a21 360deg) !important;
            background-size: 100% 100% !important;
            will-change: background;
            animation: effect-border-spin 6.5s linear infinite !important;
            -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
            mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
             -webkit-mask-composite: xor !important;
            mask-composite: exclude !important;
             padding: calc(2.5px * var(--effect-strength, 1)) !important;
         }
         }
         .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
         .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
            pointer-events: none !important;
             inset: 0 !important;
             inset: 0 !important;
             border-radius: inherit !important;
             border-radius: inherit !important;
             z-index: 1 !important;
             z-index: 1 !important;
            animation: weapon-glow-breathe 2s ease-in-out infinite !important;
            box-shadow:
                0 0 calc(10px * var(--effect-strength, 1)) 0 #d92a21,
                0 0 calc(16px * var(--effect-strength, 1)) 2px #000000,
                0 0 calc(22px * var(--effect-strength, 1)) 4px #d92a21 !important;
             opacity: 1 !important;
             opacity: 1 !important;
            box-shadow:
                0 0 8px rgba(255, 60, 60, 0.35),
                0 0 14px rgba(120, 20, 20, 0.45);
            animation: effect-child-glow 1.6s ease-in-out infinite !important;
         }
         }
 
         @keyframes effect-child-ring {
         .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle).active::after {
             0% {
             pointer-events: none !important;
                box-shadow:
            box-shadow: none !important;
                    inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
            background: conic-gradient(from var(--effect-spin),
                    0 0 6px rgba(255, 60, 60, 0.45),
                #d92a21 0deg,
                    0 0 10px rgba(120, 20, 20, 0.6);
                #d92a21 150deg,
             }
                #000000 200deg,
            50% {
                #d92a21 330deg,
                box-shadow:
                #d92a21 360deg) !important;
                    inset 0 0 0 var(--icon-ring-w) rgba(120, 20, 20, 0.95),
             background-size: 100% 100% !important;
                    0 0 9px rgba(255, 70, 70, 0.65),
            will-change: background;
                    0 0 14px rgba(20, 0, 0, 0.8);
            animation: effect-border-spin 4.5s linear infinite !important;
             }
            -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
             100% {
            mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
                box-shadow:
             -webkit-mask-composite: xor !important;
                    inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
             mask-composite: exclude !important;
                    0 0 6px rgba(255, 60, 60, 0.45),
            padding: calc(2.5px * var(--effect-strength, 1)) !important;
                    0 0 10px rgba(120, 20, 20, 0.6);
            }
         }
         }
 
         @keyframes effect-child-outline {
        /* ========== POPUP EFFECT (igual weapon toggle) ========== */
             0% {
         .effect-modal {
                outline-color: rgba(210, 60, 60, 0.95);
             position: absolute;
             }
             inset: 0;
             50% {
             z-index: 100;
                outline-color: rgba(120, 20, 20, 0.95);
             display: flex;
             }
             align-items: center;
             100% {
            justify-content: center;
                outline-color: rgba(210, 60, 60, 0.95);
             pointer-events: none;
             }
         }
         }
         .effect-modal.show {
         @keyframes effect-child-glow {
             pointer-events: all;
            0% {
                box-shadow:
                    0 0 8px rgba(255, 60, 60, 0.35),
                    0 0 14px rgba(120, 20, 20, 0.45);
            }
            50% {
                box-shadow:
                    0 0 12px rgba(255, 70, 70, 0.55),
                    0 0 18px rgba(20, 0, 0, 0.75);
            }
             100% {
                box-shadow:
                    0 0 8px rgba(255, 60, 60, 0.35),
                    0 0 14px rgba(120, 20, 20, 0.45);
            }
         }
         }
         .effect-modal-overlay {
         .character-box .top-rail.skills .effect-lines-layer {
             position: absolute;
             position: absolute;
             inset: 0;
             left: 0;
             background: rgba(0, 0, 0, 0.65);
             top: 0;
             -webkit-backdrop-filter: blur(4px);
             width: 100%;
             backdrop-filter: blur(4px);
            height: 100%;
             opacity: 0;
             pointer-events: none;
             transition: opacity 0.15s ease;
             z-index: 0;
             overflow: visible;
         }
         }
         .effect-modal.show .effect-modal-overlay {
         .character-box .top-rail.skills .effect-lines-layer .effect-line {
             opacity: 1;
             fill: none;
            stroke-linecap: round;
            stroke-linejoin: round;
         }
         }
         .effect-modal-content {
         .character-box .top-rail.skills .effect-lines-layer .effect-line-glow {
            position: relative;
             stroke: rgba(120, 20, 20, 0.7);
            z-index: 1;
             stroke-width: 5;
            transform: scale(0.96);
             filter: drop-shadow(0 0 6px rgba(255, 60, 60, 0.45));
            background: linear-gradient(145deg, #141414, #0b0b0b);
             animation: none;
             border: 1px solid rgba(46, 154, 254, 0.25);
             border-radius: 14px;
            max-width: 420px;
             width: 90%;
            opacity: 0;
            transition: transform 0.18s ease 0.08s, opacity 0.15s ease 0.08s;
             overflow: hidden;
         }
         }
         .effect-modal.show .effect-modal-content {
         .character-box .top-rail.skills .effect-lines-layer .effect-line-core {
             transform: scale(1);
             stroke: rgba(210, 60, 60, 0.95);
             opacity: 1;
             stroke-width: 2.2;
            animation: none;
         }
         }
         .effect-modal-header {
         .character-box .top-rail.skills .effect-lines-layer .effect-line.effect-line-returning {
             display: flex;
             animation: effect-line-return var(--effect-return-duration, 1.1s) ease forwards;
            align-items: center;
             animation-delay: var(--effect-return-delay, 0s);
            justify-content: space-between;
            padding: 16px 20px;
            border-bottom: 1px solid rgba(46, 154, 254, 0.12);
             background: linear-gradient(90deg, rgba(46, 154, 254, 0.08), transparent);
         }
         }
         .effect-modal-header h3 {
         @keyframes effect-line-pulse {
             margin: 0;
             0% {
            font-size: 16px;
                stroke: rgba(210, 60, 60, 0.95);
             font-weight: 600;
                stroke-width: 2.1;
             color: #fff;
             }
            50% {
                stroke: rgba(120, 20, 20, 0.95);
                stroke-width: 2.7;
             }
            100% {
                stroke: rgba(220, 70, 70, 0.95);
                stroke-width: 2.1;
            }
         }
         }
         .effect-modal-close {
         @keyframes effect-line-glow {
             background: transparent;
             0% {
            border: 1px solid rgba(255, 255, 255, 0.1);
                stroke: rgba(140, 25, 25, 0.65);
            color: rgba(255, 255, 255, 0.5);
                filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
             font-size: 18px;
             }
             cursor: pointer;
             50% {
            line-height: 1;
                stroke: rgba(20, 0, 0, 0.85);
             width: 28px;
                filter: drop-shadow(0 0 9px rgba(255, 70, 70, 0.65));
             height: 28px;
             }
            border-radius: 6px;
             100% {
            display: inline-flex;
                stroke: rgba(140, 25, 25, 0.65);
            align-items: center;
                filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
             justify-content: center;
             }
         }
         }
         .effect-modal-close:hover {
         @keyframes effect-line-return {
             color: #fff;
             0% {
            border-color: rgba(255, 255, 255, 0.2);
                stroke-dasharray: var(--effect-line-length, 0) var(--effect-line-length, 0);
        }
                stroke-dashoffset: 0;
        .effect-modal-body {
                opacity: 1;
            padding: 16px 20px;
            font-size: 13px;
            line-height: 1.4;
            color: rgba(255, 255, 255, 0.85);
        }
        .effect-modal-body p {
            margin: 0 0 10px;
        }
        .effect-modal-body p:last-child {
            margin-bottom: 0;
        }
        .effect-modal-body strong {
            color: #2e9afe;
        }
        .effect-modal-footer {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 14px 20px;
            border-top: 1px solid rgba(46, 154, 254, 0.12);
            background: rgba(0, 0, 0, 0.2);
            gap: 12px;
        }
        .effect-modal-checkbox {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            font-size: 12px;
            line-height: 1.2;
            color: rgba(255, 255, 255, 0.6);
            cursor: pointer;
            transition: color 0.15s;
        }
        .effect-modal-checkbox:hover {
            color: rgba(255, 255, 255, 0.8);
        }
        .effect-modal-checkbox input[type="checkbox"] {
            accent-color: #2e9afe;
            margin: 0;
            width: 14px;
            height: 14px;
            flex-shrink: 0;
            transform: translateY(1px);
        }
        .effect-modal-btn {
            background: #2e9afe;
            border: none;
            color: #fff;
            padding: 10px 24px;
            border-radius: 6px;
            font-weight: 600;
            font-size: 13px;
            line-height: 1;
            cursor: pointer;
            transition: background 0.15s;
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }
        .effect-modal-btn:hover {
            background: #3aa4ff;
        }
        .effect-modal-btn:active {
            background: #1f7fd1;
        }
        @media (max-width: 600px) {
            .effect-modal-content {
                width: 92%;
                max-width: none;
             }
             }
             .effect-modal-header,
             100% {
            .effect-modal-body,
                stroke-dasharray: 0 var(--effect-line-length, 0);
            .effect-modal-footer {
                stroke-dashoffset: calc(var(--effect-line-length, 0) * -1);
                 padding: 14px 16px;
                 opacity: 0;
             }
             }
            .effect-modal-footer {
        }
                flex-direction: column;
        @media (prefers-reduced-motion: reduce) {
                gap: 12px;
            .character-box .top-rail.skills .effect-lines-layer .effect-line {
            }
                 animation: none;
            .effect-modal-btn {
                 width: 100%;
             }
             }
         }
         }
Linha 2 193: Linha 2 181:
             animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
             animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
             box-shadow:  
             box-shadow:  
                 0 0 calc(12px * var(--effect-strength, 1)) 0 #d92a21,
                 0 0 10px 0 rgba(220, 220, 220, 0.5),
                 0 0 calc(20px * var(--effect-strength, 1)) 4px #000000,
                 0 0 16px 0 rgba(190, 190, 190, 0.4),
                 0 0 calc(28px * var(--effect-strength, 1)) 6px #d92a21 !important;
                 0 0 22px 0 rgba(220, 220, 220, 0.3) !important;
             opacity: 1 !important;
             opacity: 1 !important;
         }
         }
Linha 2 241: Linha 2 229:
       skills: new Set(),
       skills: new Set(),
       videos: new Map(),
       videos: new Map(),
      startedAt: 0,
      durationMs: 0,
       expiresAt: 0,
       expiresAt: 0,
      sourceIcon: null,
       timer: null,
       timer: null,
      fadeTimer: null,
      strength: 1,
     };
     };
     const EFFECT_POPUP_KEY = "glaEffectPopupDismissed";
     let effectLinesLayer = null;
     let effectPopupShown = false;
     let effectLinesRAF = null;
     let effectModalListenersBound = false;
     let effectLinesCleanupTimer = null;
     const effectPopupTexts = {
     let effectLinesScrollWrap = null;
      pt: {
    let effectLinesBound = false;
        title: "Efeitos de Habilidades",
     let effectLinesLastState = null;
        body1:
     function getSkillNameFromIcon(iconEl) {
          "Algumas habilidades possuem efeitos que modificam outras habilidades existentes na mesma barra de habilidades.",
       return (iconEl?.dataset?.nome || iconEl?.dataset?.name || "").trim();
        body2:
          "Os ícones dessas habilidades ficam destacados temporariamente com um <strong>efeito diferente</strong> após clicar na habilidade que as ativa.",
        dontShow: "Não mostrar novamente",
        ok: "Entendi",
      },
      en: {
        title: "Skill Effects",
        body1:
          "Some abilities have effects that modify other abilities in the same skill bar.",
        body2:
          "The icons of those abilities are temporarily highlighted with a <strong>different effect</strong> after you click the ability that activates them.",
        dontShow: "Don't show again",
        ok: "Got it",
      },
      es: {
        title: "Efectos de Habilidades",
        body1:
          "Algunas habilidades tienen efectos que modifican otras habilidades en la misma barra.",
        body2:
          "Los iconos de esas habilidades se destacan temporalmente con un <strong>efecto diferente</strong> después de clicar la habilidad que las activa.",
        dontShow: "No mostrar de nuevo",
        ok: "Entendido",
      },
      pl: {
        title: "Efekty Umiejętności",
        body1:
          "Niektóre umiejętności mają efekty, które modyfikują inne umiejętności na tym samym pasku.",
        body2:
          "Ikony tych umiejętności są tymczasowo podświetlane <strong>innym efektem</strong> po kliknięciu umiejętności, która je aktywuje.",
        dontShow: "Nie pokazuj ponownie",
        ok: "Rozumiem",
      },
     };
     function updateEffectModalTexts(modal) {
       if (!modal) return;
      const lang = getLangKey();
      const t = effectPopupTexts[lang] || effectPopupTexts.pt;
      const title = modal.querySelector(".effect-modal-header h3");
      if (title) title.textContent = t.title;
      const bodyParas = modal.querySelectorAll(".effect-modal-body p");
      if (bodyParas[0]) bodyParas[0].innerHTML = t.body1;
      if (bodyParas[1]) bodyParas[1].innerHTML = t.body2;
      const checkbox = modal.querySelector(".effect-modal-checkbox span");
      if (checkbox) checkbox.textContent = t.dontShow;
      const btn = modal.querySelector(".effect-modal-btn");
      if (btn) btn.textContent = t.ok;
     }
     }
    function bindEffectModalEvents() {
     function getEffectVideoKey(iconEl, videoValue) {
      if (effectModalListenersBound) return;
      effectModalListenersBound = true;
      document.addEventListener("click", (ev) => {
        if (
          ev.target.closest(".effect-modal-close") ||
          ev.target.closest(".effect-modal-btn")
        ) {
          const checkbox = document.getElementById("effect-dont-show");
          if (checkbox && checkbox.checked) {
            try {
              localStorage.setItem(EFFECT_POPUP_KEY, "1");
            } catch (e) { }
          }
          hideEffectPopup();
        }
        if (ev.target.classList.contains("effect-modal-overlay")) {
          hideEffectPopup();
        }
      });
    }
    function ensureEffectModal() {
      let modal = document.getElementById("effect-info-modal");
      if (modal) {
        updateEffectModalTexts(modal);
        return modal;
      }
      const container =
        document.querySelector(".character-box") ||
        document.querySelector("#mw-content-text") ||
        document.body;
      modal = document.createElement("div");
      modal.id = "effect-info-modal";
      modal.className = "effect-modal";
      modal.innerHTML = `
            <div class="effect-modal-overlay"></div>
            <div class="effect-modal-content">
                <div class="effect-modal-header">
                    <h3></h3>
                    <button class="effect-modal-close" type="button" aria-label="Fechar">&times;</button>
                </div>
                <div class="effect-modal-body">
                    <p></p>
                    <p></p>
                </div>
                <div class="effect-modal-footer">
                    <label class="effect-modal-checkbox">
                        <input type="checkbox" id="effect-dont-show">
                        <span></span>
                    </label>
                    <button class="effect-modal-btn" type="button"></button>
                </div>
            </div>
        `;
      container.appendChild(modal);
      updateEffectModalTexts(modal);
      bindEffectModalEvents();
      return modal;
    }
    function showEffectPopup() {
      const modal = ensureEffectModal();
      if (modal) {
        updateEffectModalTexts(modal);
        void modal.offsetHeight;
        modal.classList.add("show");
      }
    }
    function hideEffectPopup() {
      const modal = document.getElementById("effect-info-modal");
      if (modal) modal.classList.remove("show");
    }
    function maybeShowEffectPopup() {
      if (effectPopupShown) return;
      let dismissed = false;
      try {
        dismissed = localStorage.getItem(EFFECT_POPUP_KEY) === "1";
      } catch (e) { }
      if (dismissed) {
        effectPopupShown = true;
        return;
      }
      showEffectPopup();
      effectPopupShown = true;
    }
    window.addEventListener("gla:langChanged", () => {
      const modal = document.getElementById("effect-info-modal");
      if (modal) updateEffectModalTexts(modal);
    });
    function getSkillNameFromIcon(iconEl) {
      return (iconEl?.dataset?.nome || iconEl?.dataset?.name || "").trim();
    }
     function getEffectVideoKey(iconEl, videoValue) {
       if (!iconEl) return "";
       if (!iconEl) return "";
       const baseIndex = iconEl.dataset.index || "";
       const baseIndex = iconEl.dataset.index || "";
Linha 2 427: Linha 2 275:
       return { skills, timeMs, videos };
       return { skills, timeMs, videos };
     }
     }
     function applyEffectClasses() {
     function handleEffectLinesScroll() {
       const isActive = effectState.expiresAt > Date.now();
       scheduleEffectLinesUpdate();
    }
    function bindEffectLinesEvents() {
      if (!effectLinesBound) {
        window.addEventListener("resize", scheduleEffectLinesUpdate);
        window.addEventListener("scroll", scheduleEffectLinesUpdate, {
          passive: true,
        });
        effectLinesBound = true;
      }
       const rail = document.querySelector(".top-rail.skills");
       const rail = document.querySelector(".top-rail.skills");
       if (rail) rail.classList.toggle("effect-mode-on", isActive);
       const scrollWrap = rail ? rail.querySelector(".icon-scroll-x") : null;
       document
       if (scrollWrap && scrollWrap !== effectLinesScrollWrap) {
        .querySelectorAll(".icon-bar .skill-icon[data-index]")
         if (effectLinesScrollWrap) {
         .forEach((icon) => {
           effectLinesScrollWrap.removeEventListener(
           if (icon.classList.contains("weapon-bar-toggle")) return;
            "scroll",
           const name = getSkillNameFromIcon(icon);
            handleEffectLinesScroll
          const should =
           );
            isActive && name && effectState.skills.has(name);
        }
          icon.classList.toggle("effect-active", !!should);
        scrollWrap.addEventListener("scroll", handleEffectLinesScroll, {
           if (should) {
           passive: true,
            const strength =
        });
              typeof effectState.strength === "number"
        effectLinesScrollWrap = scrollWrap;
                ? Math.max(0, Math.min(1, effectState.strength))
      }
                : 1;
    }
            icon.style.setProperty("--effect-strength", strength.toFixed(3));
    function scheduleEffectLinesUpdate() {
          } else {
      if (effectLinesRAF) return;
            icon.style.removeProperty("--effect-strength");
      effectLinesRAF = requestAnimationFrame(() => {
          }
        effectLinesRAF = null;
        });
        updateEffectLines();
      });
     }
     }
     function clearEffectState() {
     function getEffectLinesLayer() {
       const activeIcon = document.querySelector(
       const rail = document.querySelector(".top-rail.skills");
        ".icon-bar .skill-icon.active"
       if (!rail) return null;
       );
       const iconBar = rail.querySelector(".icon-bar");
       const activeName = getSkillNameFromIcon(activeIcon);
       if (!iconBar) return null;
       const wasAffected =
      if (effectLinesLayer && effectLinesLayer.isConnected) {
        activeName && effectState.skills.has(activeName);
        return effectLinesLayer;
 
      }
       if (effectState.timer) {
      const existing = iconBar.querySelector(".effect-lines-layer");
         clearTimeout(effectState.timer);
       if (existing) {
         effectState.timer = null;
         effectLinesLayer = existing;
         return effectLinesLayer;
       }
       }
       if (effectState.fadeTimer) {
      const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
         clearInterval(effectState.fadeTimer);
      svg.classList.add("effect-lines-layer");
         effectState.fadeTimer = null;
      svg.setAttribute("aria-hidden", "true");
      iconBar.insertBefore(svg, iconBar.firstChild);
      effectLinesLayer = svg;
      return effectLinesLayer;
    }
    function updateEffectLines() {
       if (!effectState.sourceIcon || !effectState.sourceIcon.isConnected) {
         clearEffectLines();
         return;
       }
       }
       effectState.skills.clear();
       if (effectState.expiresAt <= Date.now()) {
      effectState.videos.clear();
         clearEffectLines();
      effectState.startedAt = 0;
        return;
      effectState.durationMs = 0;
      effectState.strength = 1;
      effectState.expiresAt = 0;
      applyEffectClasses();
 
      if (wasAffected && activeIcon) {
         activeIcon.dispatchEvent(new Event("click", { bubbles: true }));
       }
       }
    }
      const layer = getEffectLinesLayer();
    function activateEffectFromIcon(iconEl) {
      if (!layer) return;
       const effectRaw = getCachedJSON(iconEl, "effect");
       const rail = layer.closest(".top-rail.skills");
       const normalized = normalizeEffectData(effectRaw);
      if (!rail) return;
       if (!normalized) return;
       const iconBar = rail.querySelector(".icon-bar");
       if (!iconBar) return;


       maybeShowEffectPopup();
       bindEffectLinesEvents();


       effectState.skills = new Set(normalized.skills);
       const barRect = iconBar.getBoundingClientRect();
       effectState.videos = normalized.videos;
      const width = Math.max(1, Math.round(iconBar.scrollWidth || barRect.width));
       effectState.startedAt = Date.now();
       const height = Math.max(1, Math.round(iconBar.clientHeight || barRect.height));
       effectState.durationMs = normalized.timeMs;
      layer.style.left = "0px";
       effectState.strength = 1;
       layer.style.top = "0px";
       effectState.expiresAt = Date.now() + normalized.timeMs;
      layer.setAttribute("width", String(width));
       layer.setAttribute("height", String(height));
       layer.setAttribute("viewBox", `0 0 ${width} ${height}`);
       layer.classList.remove("effect-lines-returning");


       if (effectState.timer) clearTimeout(effectState.timer);
       const srcRect = effectState.sourceIcon.getBoundingClientRect();
       effectState.timer = setTimeout(() => {
       const startX =
         clearEffectState();
         srcRect.left + srcRect.width / 2 - barRect.left + iconBar.scrollLeft;
       }, normalized.timeMs + 5);
       const startY = srcRect.top + srcRect.height / 2 - barRect.top;


       if (effectState.fadeTimer) clearInterval(effectState.fadeTimer);
       const allIcons = Array.from(
       effectState.fadeTimer = setInterval(() => {
        document.querySelectorAll(".icon-bar .skill-icon[data-index]")
        if (effectState.expiresAt <= Date.now()) return;
       ).filter((icon) => !icon.classList.contains("weapon-bar-toggle"));
        const remaining = effectState.expiresAt - Date.now();
      const targets = allIcons.filter((icon) => {
         const ratio =
         if (icon === effectState.sourceIcon) return false;
          effectState.durationMs > 0
         const name = getSkillNameFromIcon(icon);
            ? remaining / effectState.durationMs
         return name && effectState.skills.has(name);
            : 0;
       });
         effectState.strength = Math.max(0, Math.min(1, ratio));
         applyEffectClasses();
       }, 120);


      applyEffectClasses();
       if (!targets.length) {
    }
        clearEffectLines();
    function getEffectVideoForIcon(iconEl) {
        return;
       if (!iconEl) return "";
       }
      if (effectState.expiresAt <= Date.now()) return "";
      const name = getSkillNameFromIcon(iconEl);
      if (!name || !effectState.skills.has(name)) return "";
       if (effectState.videos.has(name)) return effectState.videos.get(name) || "";
      return "";
    }
    function getEffectiveSkillVideoFromIcon(iconEl) {
      const weaponOn = globalWeaponEnabled;
      const weaponData = getWeaponDataForIcon(iconEl);
      const baseVideoFile = (iconEl.dataset.videoFile || "").trim();
      const baseVideoURL = (iconEl.dataset.video || "").trim();


       // console.log('[Skills DEBUG]', {
       const frag = document.createDocumentFragment();
      //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
      const baselinePadding = 10;
       //    weaponOn,
      const baselineExtra = 12;
      //    hasWeaponData: !!weaponData,
      const baselineY = Math.max(
      //    weaponData: weaponData,
        startY,
       //    baseVideoFile,
        height - baselinePadding + baselineExtra
       //    baseVideoURL
      );
       // });
      const targetPoints = targets.map((target) => {
        const tgtRect = target.getBoundingClientRect();
        const endX =
          tgtRect.left + tgtRect.width / 2 - barRect.left + iconBar.scrollLeft;
        const endY = tgtRect.top + tgtRect.height / 2 - barRect.top;
        return { x: endX, y: endY };
      });
      const xs = targetPoints.map((p) => p.x);
      const minX = Math.min(startX, ...xs);
      const maxX = Math.max(startX, ...xs);
       effectLinesLastState = {
        startX,
        startY,
        baselineY,
        targets: targetPoints.map((p) => ({
          x: p.x,
          y: p.y,
          dist: Math.hypot(p.x - startX, p.y - startY),
        })),
      };
       const dParts = [
        `M ${startX} ${startY}`,
        `L ${startX} ${baselineY}`,
        `L ${minX} ${baselineY}`,
        `L ${maxX} ${baselineY}`,
       ];
      targetPoints.forEach((p) => {
        dParts.push(`M ${p.x} ${baselineY} L ${p.x} ${p.y}`);
       });
      const d = dParts.join(" ");


       const effectVideo = getEffectVideoForIcon(iconEl);
       const glow = document.createElementNS(
       if (effectVideo && effectVideo.trim() !== "") {
        "http://www.w3.org/2000/svg",
        return effectVideo.trim();
        "path"
      }
      );
       glow.setAttribute("d", d);
      glow.classList.add("effect-line", "effect-line-glow");


       if (weaponOn && weaponData) {
       const core = document.createElementNS(
        // console.log('[Skills] checking weapon video', {
         "http://www.w3.org/2000/svg",
         //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
         "path"
        //     hasVideo: !!(weaponData.video),
      );
         //    videoValue: weaponData.video,
      core.setAttribute("d", d);
        //    videoTrimmed: weaponData.video ? weaponData.video.trim() : ''
      core.classList.add("effect-line", "effect-line-core");
        // });
        if (weaponData.video && weaponData.video.trim() !== "") {
          // console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
          return weaponData.video.trim();
        }
      }


       // Sistema genérico de swap: verifica character_videos se houver personagem ativo
       frag.appendChild(glow);
      if (activeCharacter !== null && iconEl.dataset.characterVideos) {
      frag.appendChild(core);
        try {
          const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
          const characterVideo = characterVideos[activeCharacter];
          if (characterVideo && characterVideo.trim() !== "") {
            return characterVideo.trim();
          }
        } catch (e) {
          console.warn("[Swap] Erro ao processar character_videos:", e);
        }
      }


       // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
       requestAnimationFrame(() => {
      const result = baseVideoFile || baseVideoURL || "";
        const length = Math.max(1, Math.round(core.getTotalLength()));
      // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
        [core, glow].forEach((path) => {
       return result;
          path.style.setProperty("--effect-line-length", `${length}`);
          path.style.strokeDasharray = `${length}`;
          path.style.strokeDashoffset = "0";
        });
      });
       layer.replaceChildren(frag);
     }
     }
     function createVideoElement(videoURL, extraAttrs = {}) {
     function animateEffectLinesReturn() {
       // Se o vídeo já falhou antes, não cria novo elemento
       const layer =
      if (failedVideosCache.has(videoURL)) {
        (effectLinesLayer && effectLinesLayer.isConnected
         return null;
          ? effectLinesLayer
          : document.querySelector(".effect-lines-layer")) || null;
      if (!layer || !effectLinesLastState) {
        clearEffectLines();
         return;
       }
       }
      const { startX, startY, baselineY, targets } = effectLinesLastState;
      const sortedTargets = [...targets].sort((a, b) => b.dist - a.dist);
      const returnDuration = 1.1;
      const returnStagger = 0.22;
      const frag = document.createDocumentFragment();
      sortedTargets.forEach((target, idx) => {
        const d = [
          `M ${startX} ${startY}`,
          `L ${startX} ${baselineY}`,
          `L ${target.x} ${baselineY}`,
          `L ${target.x} ${target.y}`,
        ].join(" ");


      const v = document.createElement("video");
        const glow = document.createElementNS(
      v.className = "skill-video";
          "http://www.w3.org/2000/svg",
      v.setAttribute("controls", "");
          "path"
      v.setAttribute("preload", "auto"); // Mudado de 'metadata' para 'auto' para carregar tudo imediatamente
        );
      v.setAttribute("playsinline", "");
        glow.setAttribute("d", d);
      v.style.display = "none";
        glow.classList.add(
      v.style.width = "100%";
          "effect-line",
      v.style.height = "auto";
          "effect-line-glow",
      v.style.aspectRatio = "16/9";
          "effect-line-returning"
      v.style.objectFit = "cover";
        );
      Object.keys(extraAttrs).forEach((k) => {
        glow.style.setProperty("--effect-return-duration", `${returnDuration}s`);
        v.dataset[k] = extraAttrs[k];
        glow.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);
 
        const core = document.createElementNS(
          "http://www.w3.org/2000/svg",
          "path"
        );
        core.setAttribute("d", d);
        core.classList.add(
          "effect-line",
          "effect-line-core",
          "effect-line-returning"
        );
        core.style.setProperty("--effect-return-duration", `${returnDuration}s`);
        core.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);
 
        frag.appendChild(glow);
        frag.appendChild(core);
       });
       });
       // Detectar formato do vídeo pela extensão
       layer.replaceChildren(frag);
      const ext = (videoURL.split(".").pop() || "").toLowerCase().split("?")[0];
      const mimeTypes = {
        mp4: "video/mp4",
        m4v: "video/mp4",
        webm: "video/webm",
        ogv: "video/ogg",
        ogg: "video/ogg",
        mov: "video/quicktime",
      };
      const mimeType = mimeTypes[ext] || "video/mp4";


       const src = document.createElement("source");
       requestAnimationFrame(() => {
       src.src = videoURL;
        const paths = Array.from(layer.querySelectorAll("path.effect-line"));
       src.type = mimeType;
        paths.forEach((path) => {
       v.appendChild(src);
          const length = Math.max(1, Math.round(path.getTotalLength()));
 
          path.style.setProperty("--effect-line-length", `${length}`);
       // Fallback para Safari/iOS mais antigos
          path.style.strokeDasharray = `${length} ${length}`;
       v.setAttribute("webkit-playsinline", "");
          path.style.strokeDashoffset = "0";
       v.setAttribute("x-webkit-airplay", "allow");
        });
      });
      if (effectLinesCleanupTimer) {
        clearTimeout(effectLinesCleanupTimer);
      }
      effectLinesCleanupTimer = setTimeout(() => {
        clearEffectLines();
       }, (returnDuration + returnStagger * Math.max(0, targets.length - 1)) * 1000 + 120);
    }
    function clearEffectLines() {
      if (effectLinesCleanupTimer) {
        clearTimeout(effectLinesCleanupTimer);
        effectLinesCleanupTimer = null;
       }
      const layer =
        (effectLinesLayer && effectLinesLayer.isConnected
          ? effectLinesLayer
          : document.querySelector(".effect-lines-layer")) || null;
       if (layer) layer.remove();
      effectLinesLayer = null;
    }
    function applyEffectClasses() {
       const isActive = effectState.expiresAt > Date.now();
       const rail = document.querySelector(".top-rail.skills");
      if (rail) rail.classList.toggle("effect-mode-on", isActive);
       document
        .querySelectorAll(".icon-bar .skill-icon[data-index]")
        .forEach((icon) => {
          if (icon.classList.contains("weapon-bar-toggle")) return;
          const name = getSkillNameFromIcon(icon);
          const should =
            isActive && name && effectState.skills.has(name);
          icon.classList.toggle("effect-active", !!should);
          if (!should) {
            icon.style.removeProperty("--effect-strength");
          }
        });
      if (isActive) {
        bindEffectLinesEvents();
        scheduleEffectLinesUpdate();
      }
    }
    function clearEffectState() {
      const activeIcon = document.querySelector(
        ".icon-bar .skill-icon.active"
      );
      const activeName = getSkillNameFromIcon(activeIcon);
      const wasAffected =
        activeName && effectState.skills.has(activeName);


       // Tratamento silencioso de erros - marca como falhado e não tenta mais
       animateEffectLinesReturn();
      let errorHandled = false;
       if (effectState.timer) {
       v.addEventListener(
        clearTimeout(effectState.timer);
        "error",
        effectState.timer = null;
        (e) => {
      }
          if (errorHandled) return;
      effectState.skills.clear();
          errorHandled = true;
      effectState.videos.clear();
          // Marca o vídeo como falhado para não tentar carregar novamente
      effectState.expiresAt = 0;
          failedVideosCache.add(videoURL);
      effectState.sourceIcon = null;
          // Remove o vídeo do DOM se estiver lá
       applyEffectClasses();
          if (v.parentNode) {
            v.parentNode.removeChild(v);
          }
          // Avisa apenas uma vez sobre vídeos faltantes
          if (!missingVideosReported.has(videoURL)) {
            missingVideosReported.add(videoURL);
            // Extrai nome do arquivo da URL para o aviso
            const fileName =
              videoURL.split("/").pop().split("?")[0] || videoURL;
            console.info(
              `[Skills] Vídeo não encontrado na wiki: ${decodeURIComponent(
                fileName
              )}. Este aviso aparecerá apenas uma vez.`
            );
          }
        },
        { once: true }
       );


       return v;
       if (wasAffected && activeIcon) {
        activeIcon.dispatchEvent(new Event("click", { bubbles: true }));
      }
     }
     }
    function activateEffectFromIcon(iconEl) {
      const effectRaw = getCachedJSON(iconEl, "effect");
      const normalized = normalizeEffectData(effectRaw);
      if (!normalized) return;


    // Função recursiva para carregar TODOS os vídeos de subskills (incluindo sub-subskills)
       effectState.skills = new Set(normalized.skills);
    function preloadSubskillVideosRecursively(
       effectState.videos = normalized.videos;
       subs,
       effectState.expiresAt = Date.now() + normalized.timeMs;
      parentIdx,
      effectState.sourceIcon = iconEl;
      parentPath = ""
    ) {
      if (!videoBox || !Array.isArray(subs)) return 0;
       let createdCount = 0;
       subs.forEach((s) => {
        const subName = (s.name || s.n || "").trim();
        const currentPath = parentPath ? `${parentPath}:${subName}` : subName;


        // Vídeo normal da subskill
      if (effectState.timer) clearTimeout(effectState.timer);
        if (s.video && s.video.trim() !== "") {
      effectState.timer = setTimeout(() => {
          const key = `sub:${parentIdx}:${currentPath}`;
        clearEffectState();
          if (!subskillVideosCache.has(key)) {
      }, normalized.timeMs + 5);
            const videoURL = normalizeFileURL(s.video);
            if (
              videoURL &&
              videoURL.trim() !== "" &&
              !failedVideosCache.has(videoURL)
            ) {
              const v = createVideoElement(videoURL, {
                sub: "1",
                parentIndex: parentIdx,
                subName: currentPath,
                cacheKey: key,
              });
              if (v) {
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                videoBox.appendChild(v);
                subskillVideosCache.set(key, v);
                createdCount++;
                // FORÇA carregamento imediatamente (apenas uma vez)
                v.load();
              }
            }
          }
        }


        // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
      applyEffectClasses();
        if (
    }
          s.weapon &&
    function getEffectVideoForIcon(iconEl) {
          typeof s.weapon === "object" &&
      if (!iconEl) return "";
          s.weapon.video &&
      if (effectState.expiresAt <= Date.now()) return "";
          s.weapon.video.trim() !== ""
      const name = getSkillNameFromIcon(iconEl);
        ) {
      if (!name || !effectState.skills.has(name)) return "";
          const weaponKey = `sub:${parentIdx}:${currentPath}:weapon`;
      if (effectState.videos.has(name)) return effectState.videos.get(name) || "";
          if (!subskillVideosCache.has(weaponKey)) {
      return "";
            const weaponVideoURL = normalizeFileURL(s.weapon.video);
    }
            if (
    function getEffectiveSkillVideoFromIcon(iconEl) {
              weaponVideoURL &&
      const weaponOn = globalWeaponEnabled;
              weaponVideoURL.trim() !== "" &&
      const weaponData = getWeaponDataForIcon(iconEl);
              !failedVideosCache.has(weaponVideoURL)
      const baseVideoFile = (iconEl.dataset.videoFile || "").trim();
            ) {
      const baseVideoURL = (iconEl.dataset.video || "").trim();
              const v = createVideoElement(weaponVideoURL, {
                sub: "1",
                parentIndex: parentIdx,
                subName: currentPath,
                weapon: "1",
                cacheKey: weaponKey,
              });
              if (v) {
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                videoBox.appendChild(v);
                subskillVideosCache.set(weaponKey, v);
                createdCount++;
                // FORÇA carregamento imediatamente (apenas uma vez)
                v.load();
              }
            }
          }
        }


        // RECURSÃO: processa sub-subskills (SEMPRE processa, mesmo se o vídeo já estiver no cache)
      // console.log('[Skills DEBUG]', {
        if (Array.isArray(s.subs) && s.subs.length > 0) {
      //    skillName: iconEl.dataset.nome || iconEl.dataset.name,
          createdCount += preloadSubskillVideosRecursively(
      //    weaponOn,
            s.subs,
      //    hasWeaponData: !!weaponData,
            parentIdx,
      //    weaponData: weaponData,
            currentPath
      //    baseVideoFile,
          );
      //    baseVideoURL
        }
       // });
       });
      return createdCount;
    }


    function precreateSubskillVideos() {
      const effectVideo = getEffectVideoForIcon(iconEl);
       if (!videoBox) return;
       if (effectVideo && effectVideo.trim() !== "") {
      let createdCount = 0;
         return effectVideo.trim();
      iconItems.forEach((parentIcon) => {
       }
        const subs = getCachedJSON(parentIcon, "subs");
         if (!Array.isArray(subs)) return;
        const parentIdx = parentIcon.dataset.index || "";
        createdCount += preloadSubskillVideosRecursively(subs, parentIdx);
       });
    }


     // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
      if (weaponOn && weaponData) {
     function preloadAllVideosRecursively() {
        // console.log('[Skills] checking weapon video', {
      if (!videoBox || !iconItems.length) return;
        //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
        //     hasVideo: !!(weaponData.video),
        //    videoValue: weaponData.video,
        //     videoTrimmed: weaponData.video ? weaponData.video.trim() : ''
        // });
        if (weaponData.video && weaponData.video.trim() !== "") {
          // console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
          return weaponData.video.trim();
        }
      }


       // 1. Carregar vídeos de skills principais
       // Sistema genérico de swap: verifica character_videos se houver personagem ativo
       iconItems.forEach((el) => {
       if (activeCharacter !== null && iconEl.dataset.characterVideos) {
         const idx = el.dataset.index || "";
         try {
         if (!idx) return;
          const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
          const characterVideo = characterVideos[activeCharacter];
          if (characterVideo && characterVideo.trim() !== "") {
            return characterVideo.trim();
          }
         } catch (e) {
          console.warn("[Swap] Erro ao processar character_videos:", e);
        }
      }


        // Vídeo normal da skill principal
      // form_switch (Change Form): usa vídeo da PRÓXIMA forma (transição atual → próxima)
        // Prioriza data-video-file (nome do arquivo), depois data-video (pode ser URL completa)
      const isFormSwitch =
         let src = (el.dataset.videoFile || "").trim();
         iconEl.dataset.formSwitch === "true" ||
        if (!src) {
        iconEl.getAttribute("data-form-switch") === "true";
          // Se não tem videoFile, tenta extrair do video (pode ser URL completa)
      if (isFormSwitch) {
           const videoAttr = (el.dataset.video || "").trim();
        const formVideosRaw =
          if (videoAttr) {
           iconEl.dataset.formVideos || iconEl.getAttribute("data-form-videos");
            // Se já é uma URL completa, usa direto; senão normaliza
        if (formVideosRaw) {
             if (videoAttr.includes("/") || videoAttr.startsWith("http")) {
          try {
              src = videoAttr;
             const videos = JSON.parse(formVideosRaw);
             } else {
             let formVideo = "";
              src = videoAttr;
             const nextForm = typeof getNextFormName === "function" ? getNextFormName() : null;
             }
            if (nextForm) {
          }
              formVideo = videos[nextForm] || "";
        }
             }
        if (src && !videosCache.has(idx)) {
             // Fallback: se nextForm for null (formsData ainda não carregado), usa primeiro vídeo disponível
          const videoURL = normalizeFileURL(src);
            if ((!formVideo || formVideo.trim() === "") && Object.keys(videos).length > 0) {
          if (videoURL && !failedVideosCache.has(videoURL)) {
              const firstKey = Object.keys(videos)[0];
            const v = createVideoElement(videoURL, {
               formVideo = videos[firstKey] || "";
              index: idx,
            }
             });
            if (formVideo && formVideo.trim() !== "") {
             if (v) {
               return formVideo.trim();
              // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
              totalVideos++;
              v.style.maxWidth = "100%";
              v.addEventListener(
                "canplaythrough",
                () => {
                  loadedVideos++;
                  if (!userHasInteracted && loadedVideos === 1) {
                    try {
                      v.pause();
                      v.currentTime = 0;
                    } catch (e) { }
                  }
                  if (loadedVideos === totalVideos) autoplay = true;
                },
                {
                  once: true,
                }
              );
               v.addEventListener(
                "error",
                () => {
                  loadedVideos++;
                  if (loadedVideos === totalVideos) autoplay = true;
                },
                {
                  once: true,
                }
              );
              videoBox.appendChild(v);
              videosCache.set(idx, v);
              nestedVideoElByIcon.set(el, v);
              // Força carregamento imediatamente (apenas uma vez)
               v.load();
             }
             }
          } catch (e) {
            console.warn("[Forms] Erro ao parsear form_videos em getEffectiveSkillVideoFromIcon:", e);
           }
           }
         }
         }
      }
      // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
      const result = baseVideoFile || baseVideoURL || "";
      // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
      return result;
    }
    function createVideoElement(videoURL, extraAttrs = {}) {
      // Se o vídeo já falhou antes, não cria novo elemento
      if (failedVideosCache.has(videoURL)) {
        return null;
      }


        // Vídeo de weapon (sempre carrega, independente do toggle)
      const v = document.createElement("video");
        const weaponData = getCachedJSON(el, "weapon");
      v.className = "skill-video";
        if (
      v.setAttribute("controls", "");
          weaponData &&
      v.setAttribute("preload", "auto"); // Mudado de 'metadata' para 'auto' para carregar tudo imediatamente
          weaponData.video &&
      v.setAttribute("playsinline", "");
          weaponData.video.trim() !== ""
      v.style.display = "none";
        ) {
      v.style.width = "100%";
          const weaponKey = `weapon:${idx}:${(
      v.style.height = "auto";
            el.dataset.nome ||
      v.style.aspectRatio = "16/9";
            el.dataset.name ||
      v.style.objectFit = "cover";
            ""
      Object.keys(extraAttrs).forEach((k) => {
          ).trim()}`;
        v.dataset[k] = extraAttrs[k];
          if (!videosCache.has(weaponKey)) {
      });
            const weaponVideoURL = normalizeFileURL(weaponData.video);
      // Detectar formato do vídeo pela extensão
            if (weaponVideoURL && !failedVideosCache.has(weaponVideoURL)) {
      const ext = (videoURL.split(".").pop() || "").toLowerCase().split("?")[0];
              const v = createVideoElement(weaponVideoURL, {
      const mimeTypes = {
                index: idx,
        mp4: "video/mp4",
                weapon: "1",
        m4v: "video/mp4",
              });
        webm: "video/webm",
              if (v) {
        ogv: "video/ogg",
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
        ogg: "video/ogg",
                totalVideos++;
         mov: "video/quicktime",
                v.style.maxWidth = "100%";
      };
                v.addEventListener(
       const mimeType = mimeTypes[ext] || "video/mp4";
                  "canplaythrough",
                  () => {
                    loadedVideos++;
                    if (loadedVideos === totalVideos) autoplay = true;
                  },
                  {
                    once: true,
                  }
                );
                v.addEventListener(
                  "error",
                  () => {
                    loadedVideos++;
                    if (loadedVideos === totalVideos) autoplay = true;
                  },
                  {
                    once: true,
                  }
                );
                videoBox.appendChild(v);
                videosCache.set(weaponKey, v);
                // Força carregamento imediatamente (apenas uma vez)
                v.load();
              }
            }
          }
         }
       });


       // 2. Carregar vídeos de subskills (recursivamente)
       const src = document.createElement("source");
       precreateSubskillVideos();
      src.src = videoURL;
    }
       src.type = mimeType;
      v.appendChild(src);


    // Função para pré-carregar TODOS os ícones recursivamente
      // Fallback para Safari/iOS mais antigos
    function preloadAllIconsRecursively() {
      v.setAttribute("webkit-playsinline", "");
       const iconCache = new Set();
       v.setAttribute("x-webkit-airplay", "allow");


       // Função recursiva para processar subskills
       // Tratamento silencioso de erros - marca como falhado e não tenta mais
       function processSubskillsRecursively(subs) {
      let errorHandled = false;
         if (!Array.isArray(subs)) return;
       v.addEventListener(
         subs.forEach((s) => {
         "error",
           const icon = (s.icon || "").trim();
         (e) => {
           if (icon) {
           if (errorHandled) return;
            const iconURL = normalizeFileURL(icon);
           errorHandled = true;
            if (iconURL && !iconCache.has(iconURL)) {
          // Marca o vídeo como falhado para não tentar carregar novamente
              iconCache.add(iconURL);
          failedVideosCache.add(videoURL);
              const img = new Image();
          // Remove o vídeo do DOM se estiver lá
              img.decoding = "async";
          if (v.parentNode) {
              img.loading = "eager";
            v.parentNode.removeChild(v);
              img.referrerPolicy = "same-origin";
              img.src = iconURL;
              imagePreloadCache.set(iconURL, img);
            }
           }
           }
           // Recursão para sub-subskills
           // Avisa apenas uma vez sobre vídeos faltantes
           if (Array.isArray(s.subs)) {
           if (!missingVideosReported.has(videoURL)) {
             processSubskillsRecursively(s.subs);
             missingVideosReported.add(videoURL);
          }
            // Extrai nome do arquivo da URL para o aviso
        });
            const fileName =
      }
              videoURL.split("/").pop().split("?")[0] || videoURL;
 
             console.info(
      // Carregar ícones de skills principais
              `[Skills] Vídeo não encontrado na wiki: ${decodeURIComponent(
      iconItems.forEach((el) => {
                fileName
        const img = el.querySelector("img");
              )}. Este aviso aparecerá apenas uma vez.`
        if (img && img.src) {
             );
          const iconURL = img.src;
          if (iconURL && !iconCache.has(iconURL)) {
             iconCache.add(iconURL);
            // Já está no DOM, mas força pré-carregamento
            const preloadImg = new Image();
            preloadImg.decoding = "async";
            preloadImg.loading = "eager";
            preloadImg.referrerPolicy = "same-origin";
            preloadImg.src = iconURL;
             imagePreloadCache.set(iconURL, preloadImg);
           }
           }
         }
         },
        { once: true }
      );


        // Carregar ícones de subskills recursivamente
       return v;
        const subs = getCachedJSON(el, "subs");
        if (Array.isArray(subs)) {
          processSubskillsRecursively(subs);
        }
       });
     }
     }


     // Função principal que carrega TUDO imediatamente
     // Função recursiva para carregar TODOS os vídeos de subskills (incluindo sub-subskills)
     function preloadAllAssets() {
     function preloadSubskillVideosRecursively(
       // Carregar TUDO imediatamente - sem lazy loading
      subs,
       // 1. Carregar TODOS os vídeos (principais, subskills, weapon, sub-subskills)
      parentIdx,
       preloadAllVideosRecursively();
      parentPath = ""
    ) {
       const vb = getVideoBox();
       if (!vb || !Array.isArray(subs)) return 0;
      let createdCount = 0;
       subs.forEach((s) => {
        const subName = (s.name || s.n || "").trim();
        const currentPath = parentPath ? `${parentPath}:${subName}` : subName;


      // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
        // Vídeo normal da subskill
      preloadAllIconsRecursively();
        if (s.video && s.video.trim() !== "") {
    }
          const key = `sub:${parentIdx}:${currentPath}`;
          if (!subskillVideosCache.has(key)) {
            const videoURL = normalizeFileURL(s.video);
            if (
              videoURL &&
              videoURL.trim() !== "" &&
              !failedVideosCache.has(videoURL)
            ) {
              const v = createVideoElement(videoURL, {
                sub: "1",
                parentIndex: parentIdx,
                subName: currentPath,
                cacheKey: key,
              });
              if (v) {
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                vb.appendChild(v);
                subskillVideosCache.set(key, v);
                createdCount++;
                // FORÇA carregamento imediatamente (apenas uma vez)
                v.load();
              }
            }
          }
        }


    // Executa pré-carregamento imediatamente
        // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
    preloadAllAssets();
        if (
    function wireTooltipsForNewIcons() {
          s.weapon &&
      const tip = document.querySelector(".skill-tooltip");
           typeof s.weapon === "object" &&
      if (!tip) return;
          s.weapon.video &&
      let lockUntil2 = 0;
           s.weapon.video.trim() !== ""
      Array.from(document.querySelectorAll(".icon-bar .skill-icon")).forEach(
        ) {
        (icon) => {
           const weaponKey = `sub:${parentIdx}:${currentPath}:weapon`;
           if (
           if (!subskillVideosCache.has(weaponKey)) {
            icon.dataset.weaponToggle === "1" ||
             const weaponVideoURL = normalizeFileURL(s.weapon.video);
            icon.classList.contains("weapon-bar-toggle")
            if (
           )
              weaponVideoURL &&
            return;
              weaponVideoURL.trim() !== "" &&
          if (icon.dataset.tipwired) return;
              !failedVideosCache.has(weaponVideoURL)
          icon.dataset.tipwired = "1";
            ) {
           const label =
              const v = createVideoElement(weaponVideoURL, {
            icon.dataset.nome || icon.dataset.name || icon.title || "";
                sub: "1",
           if (label && !icon.hasAttribute("aria-label"))
                parentIndex: parentIdx,
             icon.setAttribute("aria-label", label);
                subName: currentPath,
          if (icon.hasAttribute("title")) icon.removeAttribute("title");
                weapon: "1",
          const img = icon.querySelector("img");
                cacheKey: weaponKey,
          if (img) {
              });
            const imgAlt = img.getAttribute("alt") || "";
              if (v) {
            const imgTitle = img.getAttribute("title") || "";
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
            if (!label && (imgAlt || imgTitle))
                vb.appendChild(v);
              icon.setAttribute("aria-label", imgAlt || imgTitle);
                subskillVideosCache.set(weaponKey, v);
            img.setAttribute("alt", "");
                createdCount++;
            if (img.hasAttribute("title")) img.removeAttribute("title");
                // FORÇA carregamento imediatamente (apenas uma vez)
                v.load();
              }
            }
           }
           }
          const measureAndPos = (el) => {
        }
            if (!el || tip.getAttribute("aria-hidden") === "true") return;
 
            tip.style.left = "0px";
        // RECURSÃO: processa sub-subskills (SEMPRE processa, mesmo se o vídeo já estiver no cache)
            tip.style.top = "0px";
        if (Array.isArray(s.subs) && s.subs.length > 0) {
            const rect = el.getBoundingClientRect();
          createdCount += preloadSubskillVideosRecursively(
            const tr = tip.getBoundingClientRect();
             s.subs,
             let left = Math.round(rect.left + (rect.width - tr.width) / 2);
             parentIdx,
             left = Math.max(
            currentPath
              8,
          );
              Math.min(left, window.innerWidth - tr.width - 8)
        }
            );
      });
            const coarse =
      return createdCount;
              (window.matchMedia && matchMedia("(pointer: coarse)").matches) ||
    }
              window.innerWidth <= 600;
 
            let top = coarse
    function precreateSubskillVideos() {
              ? Math.round(rect.bottom + 10)
      if (!getVideoBox()) return;
              : Math.round(rect.top - tr.height - 8);
      let createdCount = 0;
            if (top < 8) top = Math.round(rect.bottom + 10);
      iconItems.forEach((parentIcon) => {
            tip.style.left = left + "px";
        const subs = getCachedJSON(parentIcon, "subs");
            tip.style.top = top + "px";
        if (!Array.isArray(subs)) return;
          };
        const parentIdx = parentIcon.dataset.index || "";
          const show = (el, text) => {
        createdCount += preloadSubskillVideosRecursively(subs, parentIdx);
            tip.textContent = text || "";
      });
            tip.setAttribute("aria-hidden", "false");
            measureAndPos(el);
            tip.style.opacity = "1";
          };
          const hide = () => {
            tip.setAttribute("aria-hidden", "true");
            tip.style.opacity = "0";
            tip.style.left = "-9999px";
            tip.style.top = "-9999px";
          };
          icon.addEventListener("mouseenter", () =>
            show(icon, icon.dataset.nome || icon.dataset.name || "")
          );
          icon.addEventListener("mousemove", () => {
            if (performance.now() >= lockUntil2) measureAndPos(icon);
          });
          icon.addEventListener("click", () => {
            lockUntil2 = performance.now() + 240;
            measureAndPos(icon);
          });
          icon.addEventListener("mouseleave", hide);
        }
      );
     }
     }
     function showVideoForIcon(el) {
 
       userHasInteracted = true;
    // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
       if (!videoBox) return;
     function preloadAllVideosRecursively() {
       const effectiveVideo = getEffectiveSkillVideoFromIcon(el);
       const vb = getVideoBox();
      if (!effectiveVideo || effectiveVideo.trim() === "") {
       if (!vb || !iconItems.length) return;
         videoBox.style.display = "none";
 
         return;
      // 1. Carregar vídeos de skills principais
      }
       iconItems.forEach((el) => {
      const videoURL = normalizeFileURL(effectiveVideo);
        const idx = el.dataset.index || "";
      if (!videoURL || videoURL.trim() === "") {
        if (!idx) return;
        videoBox.style.display = "none";
 
         return;
        // Vídeo normal da skill principal
      }
        // form_switch com form_videos: NÃO pré-carrega o vídeo base (ex: Change Form-Video.mp4) pois pode não existir
      Array.from(videoBox.querySelectorAll("video.skill-video")).forEach(
        const isFormSwitchPreload =
        (v) => {
          el.dataset.formSwitch === "true" ||
          try {
          el.getAttribute("data-form-switch") === "true";
            v.pause();
         const hasFormVideos =
          } catch (e) { }
          (el.dataset.formVideos || el.getAttribute("data-form-videos") || "").trim() !== "";
          v.style.display = "none";
         const skipBaseVideo = isFormSwitchPreload && hasFormVideos;
 
        let src = "";
        if (!skipBaseVideo) {
          src = (el.dataset.videoFile || "").trim();
          if (!src) {
            const videoAttr = (el.dataset.video || "").trim();
            if (videoAttr) {
              src = videoAttr.includes("/") || videoAttr.startsWith("http")
                ? videoAttr
                : videoAttr;
            }
          }
         }
        if (src && !videosCache.has(idx)) {
          const videoURL = normalizeFileURL(src);
          if (videoURL && !failedVideosCache.has(videoURL)) {
            const v = createVideoElement(videoURL, {
              index: idx,
            });
            if (v) {
              // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
              totalVideos++;
              v.style.maxWidth = "100%";
              v.addEventListener(
                "canplaythrough",
                () => {
                  loadedVideos++;
                  if (!userHasInteracted && loadedVideos === 1) {
                    try {
                      v.pause();
                      v.currentTime = 0;
                    } catch (e) { }
                  }
                  if (loadedVideos === totalVideos) autoplay = true;
                },
                {
                  once: true,
                }
              );
              v.addEventListener(
                "error",
                () => {
                  loadedVideos++;
                  if (loadedVideos === totalVideos) autoplay = true;
                },
                {
                  once: true,
                }
              );
              vb.appendChild(v);
              videosCache.set(idx, v);
              nestedVideoElByIcon.set(el, v);
              // Força carregamento imediatamente (apenas uma vez)
              v.load();
            }
          }
         }
         }
      );
      if (window.__subskills) window.__subskills.hideAll?.(videoBox);
      const hasIdx = !!el.dataset.index;
      const weaponOn = globalWeaponEnabled;
      const weaponData = getWeaponDataForIcon(el);
      const isWeaponVideo =
        weaponOn &&
        weaponData &&
        weaponData.video &&
        weaponData.video.trim() !== "";
      const effectVideo = getEffectVideoForIcon(el);
      const isEffectVideo = !!(effectVideo && effectVideo.trim() !== "");
      const effectKey = isEffectVideo
        ? getEffectVideoKey(el, effectiveVideo)
        : "";
      // console.log('[Skills] showVideoForIcon chamado', {
      //    skillName: el.dataset.nome || el.dataset.name,
      //    weaponOn,
      //    isWeaponVideo,
      //    effectiveVideo: getEffectiveSkillVideoFromIcon(el)
      // });
      const videoKey = isWeaponVideo
        ? `weapon:${getWeaponKey(el)}`
        : isEffectVideo
          ? effectKey
          : el.dataset.index || "";
      const isSubskill = !hasIdx || el.dataset.nested === "1";
      const parentIdx = el.dataset.parentIndex || "";
      const subName =
        el.dataset.subName || el.dataset.nome || el.dataset.name || "";


      if (
         // form_switch: pré-carrega TODOS os vídeos de form_videos para transições
         hasIdx &&
         const isFormSwitch =
         !isWeaponVideo &&
          el.dataset.formSwitch === "true" ||
        !isEffectVideo &&
          el.getAttribute("data-form-switch") === "true";
        videosCache.has(el.dataset.index)
        if (isFormSwitch) {
      ) {
          const formVideosRaw =
        const v = videosCache.get(el.dataset.index);
            el.dataset.formVideos || el.getAttribute("data-form-videos");
        videoBox.style.display = "block";
          if (formVideosRaw) {
        v.style.display = "block";
            try {
        try {
              const videos = JSON.parse(formVideosRaw);
          v.currentTime = 0;
              Object.keys(videos).forEach((formName) => {
        } catch (e) { }
                const videoFile = videos[formName];
        const suppress = document.body.dataset.suppressSkillPlay === "1";
                if (videoFile && videoFile.trim() !== "") {
        if (!suppress) {
                  const videoKey = idx + ":" + videoFile;
          v.play().catch(() => { });
                  if (!videosCache.has(videoKey)) {
        } else {
                    const videoURL = normalizeFileURL(videoFile);
          try {
                    if (videoURL && !failedVideosCache.has(videoURL)) {
            v.pause();
                      const v = createVideoElement(videoURL, { index: idx });
          } catch (e) { }
                      if (v) {
        }
                        totalVideos++;
        return;
                        v.addEventListener(
      }
                          "canplaythrough",
      // Para form_switch, permite criação dinâmica de vídeos (transições de forma)
                          () => {
      // Vídeos normais devem estar pré-carregados
                            loadedVideos++;
      const isFormSwitch =
                            if (loadedVideos === totalVideos) autoplay = true;
        el.dataset.formSwitch === "true" ||
                          },
        el.getAttribute("data-form-switch") === "true";
                          { once: true }
      let v = null;
                        );
      if (isWeaponVideo) {
                        v.addEventListener(
        const weaponKeyFull = `weapon:${getWeaponKey(el)}`;
                          "error",
        v = videosCache.get(weaponKeyFull);
                          () => {
        if (!v && isSubskill && parentIdx && subName) {
                            loadedVideos++;
          // Tenta buscar vídeo de weapon de subskill
                            if (loadedVideos === totalVideos) autoplay = true;
           // O cache usa o formato: sub:${parentIdx}:${path}:weapon onde path NÃO inclui o nome da skill principal
                          },
          // O subName agora já está no formato correto (sem nome da skill principal)
                          { once: true }
                        );
                        vb.appendChild(v);
                        videosCache.set(videoKey, v);
                        v.load();
                      }
                    }
                  }
                }
              });
            } catch (e) {
              console.warn("[Forms] Erro ao pré-carregar form_videos:", e);
            }
           }
        }


          // Tenta buscar diretamente com o subName
        // Vídeo de weapon (sempre carrega, independente do toggle)
          let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
        const weaponData = getCachedJSON(el, "weapon");
          v = subskillVideosCache.get(subWeaponKey);
        if (
 
          weaponData &&
           // Se não encontrou, tenta buscar recursivamente
           weaponData.video &&
           if (!v) {
           weaponData.video.trim() !== ""
            const basePattern = `sub:${parentIdx}:`;
        ) {
             const weaponSuffix = ":weapon";
          const weaponKey = `weapon:${idx}:${(
             const searchName = subName.split(":").pop();
             el.dataset.nome ||
            for (const [key, video] of subskillVideosCache.entries()) {
             el.dataset.name ||
              if (key.startsWith(basePattern) && key.endsWith(weaponSuffix)) {
            ""
                 const pathInKey = key.substring(
          ).trim()}`;
                   basePattern.length,
          if (!videosCache.has(weaponKey)) {
                   key.length - weaponSuffix.length
            const weaponVideoURL = normalizeFileURL(weaponData.video);
            if (weaponVideoURL && !failedVideosCache.has(weaponVideoURL)) {
              const v = createVideoElement(weaponVideoURL, {
                index: idx,
                 weapon: "1",
              });
              if (v) {
                // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
                totalVideos++;
                v.style.maxWidth = "100%";
                v.addEventListener(
                  "canplaythrough",
                  () => {
                    loadedVideos++;
                    if (loadedVideos === totalVideos) autoplay = true;
                   },
                  {
                    once: true,
                   }
                 );
                 );
                 // Tenta match exato, ou se termina com o nome da skill
                 v.addEventListener(
                if (
                  "error",
                  pathInKey === subName ||
                  () => {
                   pathInKey.endsWith(`:${searchName}`) ||
                    loadedVideos++;
                   pathInKey === searchName
                    if (loadedVideos === totalVideos) autoplay = true;
                 ) {
                  },
                  v = video;
                   {
                  break;
                    once: true,
                 }
                   }
                 );
                vb.appendChild(v);
                videosCache.set(weaponKey, v);
                 // Força carregamento imediatamente (apenas uma vez)
                v.load();
               }
               }
             }
             }
           }
           }
         }
         }
        if (!v) {
       });
          // Tenta buscar pelo padrão antigo também
          v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`);
        }
       } else if (isEffectVideo && effectKey) {
        v = videosCache.get(effectKey);
      } else {
        if (isSubskill && parentIdx && subName) {
          // Busca vídeo de subskill no cache correto
          // O cache usa o formato: sub:${parentIdx}:${path} onde path NÃO inclui o nome da skill principal
          // O subName agora já está no formato correto (sem nome da skill principal)


          // Tenta buscar diretamente com o subName
      // 2. Carregar vídeos de subskills (recursivamente)
          let subKey = `sub:${parentIdx}:${subName}`;
      precreateSubskillVideos();
          v = subskillVideosCache.get(subKey);
    }


          // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
    // Função para pré-carregar TODOS os ícones recursivamente
          // Isso é útil caso haja alguma diferença no formato do caminho
    function preloadAllIconsRecursively() {
          if (!v) {
      const iconCache = new Set();
            const basePattern = `sub:${parentIdx}:`;
            const searchName = subName.split(":").pop(); // Último segmento do caminho
            // Tenta também buscar apenas pelo último segmento (útil para sub-subskills)
            const lastSegmentKey = `sub:${parentIdx}:${searchName}`;
            v = subskillVideosCache.get(lastSegmentKey);


            // Se ainda não encontrou, faz busca mais ampla
      // Função recursiva para processar subskills
            if (!v) {
      function processSubskillsRecursively(subs) {
              for (const [key, video] of subskillVideosCache.entries()) {
        if (!Array.isArray(subs)) return;
                if (key.startsWith(basePattern)) {
        subs.forEach((s) => {
                  const pathInKey = key.substring(basePattern.length);
          const icon = (s.icon || "").trim();
                  // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
          if (icon) {
                  if (
            const iconURL = normalizeFileURL(icon);
                    pathInKey === subName ||
            if (iconURL && !iconCache.has(iconURL)) {
                    pathInKey.endsWith(`:${searchName}`) ||
              iconCache.add(iconURL);
                    pathInKey === searchName ||
              const img = new Image();
                    (subName.includes(":") && pathInKey.includes(subName)) ||
              img.decoding = "async";
                    (subName.includes(":") &&
              img.loading = "eager";
                      pathInKey.endsWith(
              img.referrerPolicy = "same-origin";
                        subName.split(":").slice(-2).join(":")
              img.src = iconURL;
                      ))
               imagePreloadCache.set(iconURL, img);
                  ) {
                    v = video;
                    break;
                  }
                }
               }
             }
             }
           }
           }
        } else {
          // Recursão para sub-subskills
           v = videosCache.get(el.dataset.index);
           if (Array.isArray(s.subs)) {
          if (!v) {
             processSubskillsRecursively(s.subs);
             v = nestedVideoElByIcon.get(el);
           }
           }
         }
         });
       }
       }


       // Se vídeo não foi encontrado no cache, verifica se falhou antes
       // Carregar ícones de skills principais
       if (!v) {
       iconItems.forEach((el) => {
         // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
         const img = el.querySelector("img");
        if (failedVideosCache.has(videoURL)) {
         if (img && img.src) {
          videoBox.style.display = "none";
           const iconURL = img.src;
          return;
           if (iconURL && !iconCache.has(iconURL)) {
        }
             iconCache.add(iconURL);
        // Para effect, cria vídeo dinamicamente (vídeo alternativo por tempo)
             // Já está no DOM, mas força pré-carregamento
         if (isEffectVideo && videoURL) {
            const preloadImg = new Image();
           const baseIndex = el.dataset.index || "";
            preloadImg.decoding = "async";
           const effectCacheKey =
            preloadImg.loading = "eager";
            effectKey || `effect:${effectiveVideo || videoURL}`;
             preloadImg.referrerPolicy = "same-origin";
          v = videosCache.get(effectCacheKey);
            preloadImg.src = iconURL;
          if (!v) {
             imagePreloadCache.set(iconURL, preloadImg);
             v = createVideoElement(videoURL, {
              index: baseIndex,
              effect: "1",
            });
             if (v) {
              videoBox.appendChild(v);
              videosCache.set(effectCacheKey, v);
              v.load();
             } else {
              videoBox.style.display = "none";
              return;
             }
           }
           }
         }
         }
         // Para form_switch, cria vídeo dinamicamente (transições de forma são dinâmicas)
 
         // Usa uma chave única que inclui o nome do arquivo do vídeo para garantir que cada vídeo diferente seja cacheado separadamente
         // Carregar ícones de subskills recursivamente
        else if (isFormSwitch && videoURL) {
         const subs = getCachedJSON(el, "subs");
          const baseIndex = el.dataset.index || "";
        if (Array.isArray(subs)) {
          const videoFileName = effectiveVideo || "";
          processSubskillsRecursively(subs);
          // Chave única: index + nome do arquivo do vídeo
          const videoKey = baseIndex + ":" + videoFileName;
          v = videosCache.get(videoKey);
          if (!v) {
            v = createVideoElement(videoURL, { index: baseIndex });
            if (v) {
              videoBox.appendChild(v);
              videosCache.set(videoKey, v);
              v.load();
            } else {
              videoBox.style.display = "none";
              return;
            }
          }
        } else {
          // Vídeos normais devem estar pré-carregados
          videoBox.style.display = "none";
          return;
         }
         }
       }
       });
      videoBox.style.display = "block";
      v.style.display = "block";
      try {
        v.currentTime = 0;
      } catch (e) { }
      const suppress = document.body.dataset.suppressSkillPlay === "1";
      if (!suppress) {
        v.play().catch(() => { });
      } else {
        try {
          v.pause();
        } catch (e) { }
      }
     }
     }
     function activateSkill(el, options = {}) {
 
       const { openSubs = true } = options;
    // Função principal que carrega TUDO imediatamente
     function preloadAllAssets() {
      // Carregar TUDO imediatamente - sem lazy loading
      // 1. Carregar TODOS os vídeos (principais, subskills, weapon, sub-subskills)
      preloadAllVideosRecursively();
 
      // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
       preloadAllIconsRecursively();
    }
 
    // Executa pré-carregamento imediatamente
    preloadAllAssets();
    function wireTooltipsForNewIcons() {
       const tip = document.querySelector(".skill-tooltip");
       const tip = document.querySelector(".skill-tooltip");
       if (tip) {
       if (!tip) return;
        tip.setAttribute("aria-hidden", "true");
      let lockUntil2 = 0;
         tip.style.opacity = "0";
      Array.from(document.querySelectorAll(".icon-bar .skill-icon")).forEach(
        tip.style.left = "-9999px";
         (icon) => {
        tip.style.top = "-9999px";
          if (
      }
            icon.dataset.weaponToggle === "1" ||
      const skillsRoot = document.getElementById("skills");
            icon.classList.contains("weapon-bar-toggle")
      const i18nMap = skillsRoot
          )
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
            return;
        : {};
          if (icon.dataset.tipwired) return;
      const L = i18nMap[getLangKey()] ||
          icon.dataset.tipwired = "1";
        i18nMap.pt || {
          const label =
        cooldown: "Recarga",
            icon.dataset.nome || icon.dataset.name || icon.title || "";
        energy_gain: "Ganho de energia",
          if (label && !icon.hasAttribute("aria-label"))
        energy_cost: "Custo de energia",
            icon.setAttribute("aria-label", label);
        power: "Poder",
          if (icon.hasAttribute("title")) icon.removeAttribute("title");
        power_pvp: "Poder PvP",
           const img = icon.querySelector("img");
        level: "Nível",
           if (img) {
      };
             const imgAlt = img.getAttribute("alt") || "";
      const name = el.dataset.nome || el.dataset.name || "";
             const imgTitle = img.getAttribute("title") || "";
      if (el.dataset.effect) {
            if (!label && (imgAlt || imgTitle))
        activateEffectFromIcon(el);
              icon.setAttribute("aria-label", imgAlt || imgTitle);
      }
            img.setAttribute("alt", "");
      let weaponData = null;
             if (img.hasAttribute("title")) img.removeAttribute("title");
      if (el.dataset.weapon) {
        try {
           const parsed = JSON.parse(el.dataset.weapon);
          // Só considera weapon válido se for um objeto não vazio
           if (
             parsed &&
             typeof parsed === "object" &&
            Object.keys(parsed).length > 0
          ) {
             weaponData = parsed;
           }
           }
        } catch (e) {
          const measureAndPos = (el) => {
          weaponData = null;
            if (!el || tip.getAttribute("aria-hidden") === "true") return;
        }
            tip.style.left = "0px";
      }
            tip.style.top = "0px";
      const hasWeapon = !!weaponData;
            const rect = el.getBoundingClientRect();
      const weaponEquipped = hasWeapon && globalWeaponEnabled;
            const tr = tip.getBoundingClientRect();
      // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
            let left = Math.round(rect.left + (rect.width - tr.width) / 2);
      let level = (el.dataset.level || "").trim();
            left = Math.max(
      if (weaponEquipped && weaponData) {
              8,
        // Verifica se weapon tem level definido (pode ser número ou string)
              Math.min(left, window.innerWidth - tr.width - 8)
        const weaponLevel = weaponData.level;
            );
        if (
            const coarse =
          weaponLevel !== undefined &&
              (window.matchMedia && matchMedia("(pointer: coarse)").matches) ||
          weaponLevel !== null &&
              window.innerWidth <= 600;
          weaponLevel !== ""
            let top = coarse
        ) {
              ? Math.round(rect.bottom + 10)
          level = weaponLevel.toString().trim();
              : Math.round(rect.top - tr.height - 8);
        }
            if (top < 8) top = Math.round(rect.bottom + 10);
      }
            tip.style.left = left + "px";
      const lang = getLangKey();
            tip.style.top = top + "px";
      const baseDescPack = {
          };
        pt: el.dataset.descPt || "",
          const show = (el, text) => {
        en: el.dataset.descEn || "",
            tip.textContent = text || "";
        es: el.dataset.descEs || "",
            tip.setAttribute("aria-hidden", "false");
        pl: el.dataset.descPl || "",
            measureAndPos(el);
      };
            tip.style.opacity = "1";
      const baseDesc =
          };
        baseDescPack[lang] ||
           const hide = () => {
        baseDescPack.pt ||
             tip.setAttribute("aria-hidden", "true");
        baseDescPack.en ||
             tip.style.opacity = "0";
        baseDescPack.es ||
             tip.style.left = "-9999px";
        baseDescPack.pl ||
             tip.style.top = "-9999px";
        el.dataset.desc ||
        "";
      // Aceita tanto desc_i18n quanto desc para compatibilidade
      let weaponDescPack = {};
      if (weaponData) {
        if (weaponData.desc_i18n) {
          weaponDescPack = weaponData.desc_i18n;
        } else if (weaponData.desc) {
          weaponDescPack = weaponData.desc;
        } else {
           weaponDescPack = {
             pt: weaponData.descPt || "",
             en: weaponData.descEn || "",
             es: weaponData.descEs || "",
             pl: weaponData.descPl || "",
           };
           };
          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);
         }
         }
       }
       );
       const weaponDesc =
    }
        weaponDescPack[lang] ||
    function showVideoForIcon(el) {
        weaponDescPack.pt ||
      userHasInteracted = true;
        weaponDescPack.en ||
       const videoBox = getVideoBox();
         weaponDescPack.es ||
      if (!videoBox) return;
        weaponDescPack.pl ||
      const effectiveVideo = getEffectiveSkillVideoFromIcon(el);
         "";
      if (!effectiveVideo || effectiveVideo.trim() === "") {
       const chosenDesc = weaponEquipped && weaponDesc ? weaponDesc : baseDesc;
         videoBox.style.display = "none";
       const descHtml = chosenDesc.replace(/'''(.*?)'''/g, "<b>$1</b>");
         return;
      let attrsHTML = "";
      }
       if (weaponEquipped && weaponData) {
       const videoURL = normalizeFileURL(effectiveVideo);
        // Faz merge: usa atributos da skill base e substitui apenas os que existem no weapon
       if (!videoURL || videoURL.trim() === "") {
        // Parse dos atributos da skill base (formato: "pve, pvp, energy, cooldown" ou "pve, pvp, energy, cooldown")
        videoBox.style.display = "none";
        const baseAttrsStr = el.dataset.atr || "";
        return;
         // Suporta tanto ", " quanto "," como separador
      }
        const baseAttrs = baseAttrsStr.split(/\s*,\s*/).map((a) => a.trim());
       Array.from(videoBox.querySelectorAll("video.skill-video")).forEach(
        const basePve =
        (v) => {
          baseAttrs[0] && baseAttrs[0] !== "-" ? baseAttrs[0] : "";
          try {
         const basePvp =
            v.pause();
          baseAttrs[1] && baseAttrs[1] !== "-" ? baseAttrs[1] : "";
          } catch (e) { }
        const baseEnergy =
          v.style.display = "none";
          baseAttrs[2] && baseAttrs[2] !== "-" ? baseAttrs[2] : "";
         }
        const baseCd = baseAttrs[3] && baseAttrs[3] !== "-" ? baseAttrs[3] : "";
      );
      if (window.__subskills) window.__subskills.hideAll?.(videoBox);
      const hasIdx = !!el.dataset.index;
      const weaponOn = globalWeaponEnabled;
      const weaponData = getWeaponDataForIcon(el);
      const isWeaponVideo =
        weaponOn &&
         weaponData &&
        weaponData.video &&
        weaponData.video.trim() !== "";
      const effectVideo = getEffectVideoForIcon(el);
      const isEffectVideo = !!(effectVideo && effectVideo.trim() !== "");
      const effectKey = isEffectVideo
        ? getEffectVideoKey(el, effectiveVideo)
        : "";


        // Valores do weapon (substituem os da skill base se existirem)
      // console.log('[Skills] showVideoForIcon chamado', {
        const wPve =
      //    skillName: el.dataset.nome || el.dataset.name,
          weaponData.powerpve !== undefined &&
      //    weaponOn,
            weaponData.powerpve !== null &&
      //    isWeaponVideo,
            weaponData.powerpve !== ""
      //    effectiveVideo: getEffectiveSkillVideoFromIcon(el)
            ? weaponData.powerpve.toString().trim()
      // });
            : basePve;
      const videoKey = isWeaponVideo
         const wPvp =
        ? `weapon:${getWeaponKey(el)}`
           weaponData.powerpvp !== undefined &&
         : isEffectVideo
            weaponData.powerpvp !== null &&
           ? effectKey
            weaponData.powerpvp !== ""
          : el.dataset.index || "";
            ? weaponData.powerpvp.toString().trim()
      const isSubskill = !hasIdx || el.dataset.nested === "1";
            : basePvp;
      const parentIdx = el.dataset.parentIndex || "";
        const wEnergy =
      const subName =
          weaponData.energy !== undefined &&
        el.dataset.subName || el.dataset.nome || el.dataset.name || "";
            weaponData.energy !== null &&
            weaponData.energy !== ""
            ? weaponData.energy.toString().trim()
            : baseEnergy;
        const wCd =
          weaponData.cooldown !== undefined &&
            weaponData.cooldown !== null &&
            weaponData.cooldown !== ""
            ? weaponData.cooldown.toString().trim()
            : baseCd;


         // Monta string de atributos mesclados
      if (
         const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(",");
        hasIdx &&
         attrsHTML = renderAttributes(mergedAttrs);
        !isWeaponVideo &&
      } else {
        !isEffectVideo &&
        attrsHTML = el.dataset.atr
        videosCache.has(el.dataset.index)
           ? renderAttributes(el.dataset.atr)
      ) {
           : el.dataset.subattrs
        const v = videosCache.get(el.dataset.index);
            ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L)
         // Verifica se o vídeo cacheado corresponde ao vídeo atual da skill
            : "";
        // (ao trocar de form, o índice é reutilizado mas o vídeo muda)
         const cachedSrc = v.querySelector("source")?.getAttribute("src") || "";
        const currentVideoFile = (el.dataset.videoFile || "").trim();
        const currentVideoURL = currentVideoFile ? normalizeFileURL(currentVideoFile) : "";
        const decodeCached = decodeURIComponent(cachedSrc.split("/").pop().split("?")[0] || "");
        const decodeTarget = decodeURIComponent((currentVideoURL || currentVideoFile).split("/").pop().split("?")[0] || "");
         const srcMismatch = decodeTarget && decodeCached && decodeCached !== decodeTarget;
        if (!srcMismatch) {
          videoBox.style.display = "block";
          v.style.display = "block";
          try {
            v.currentTime = 0;
          } catch (e) { }
          const suppress = document.body.dataset.suppressSkillPlay === "1";
           if (!suppress) {
            v.play().catch(() => { });
          } else {
            try {
              v.pause();
            } catch (e) { }
           }
          return;
        }
        // Vídeo não corresponde - remove do cache e do DOM
        v.style.display = "none";
        try { v.pause(); } catch (e) { }
        if (v.parentNode) v.parentNode.removeChild(v);
        videosCache.delete(el.dataset.index);
       }
       }
       let flagsHTML = "";
       // Para form_switch, permite criação dinâmica de vídeos (transições de forma)
       // Debug: verifica se é uma skill do Urouge (verifica pela URL da página ou pelo contexto)
       // Vídeos normais devem estar pré-carregados
       const isUrougePage =
       const isFormSwitch =
         window.location.href &&
         el.dataset.formSwitch === "true" ||
         window.location.href.toLowerCase().includes("urouge");
         el.getAttribute("data-form-switch") === "true";
       if (el.dataset.flags) {
      let v = null;
         try {
       if (isWeaponVideo) {
          const flags = JSON.parse(el.dataset.flags);
         const weaponKeyFull = `weapon:${getWeaponKey(el)}`;
          if (flags && Array.isArray(flags) && flags.length > 0) {
        v = videosCache.get(weaponKeyFull);
            flagsHTML = renderFlagsRow(flags);
        if (!v && isSubskill && parentIdx && subName) {
            // Debug para Urouge
          // Tenta buscar vídeo de weapon de subskill
            if (isUrougePage) {
          // O cache usa o formato: sub:${parentIdx}:${path}:weapon onde path NÃO inclui o nome da skill principal
              console.log("[Skills] Urouge - Flags processadas:", {
          // O subName agora já está no formato correto (sem nome da skill principal)
                skill: name || el.dataset.nome,
 
                 flags: flags,
          // Tenta buscar diretamente com o subName
                htmlLength: flagsHTML ? flagsHTML.length : 0,
          let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
                 hasHTML: !!flagsHTML,
          v = subskillVideosCache.get(subWeaponKey);
              });
 
            }
          // Se não encontrou, tenta buscar recursivamente
          } else {
          if (!v) {
            if (isUrougePage) {
            const basePattern = `sub:${parentIdx}:`;
              console.warn("[Skills] Urouge - Flags inválidas:", {
            const weaponSuffix = ":weapon";
                skill: name || el.dataset.nome,
            const searchName = subName.split(":").pop();
                 flags: flags,
            for (const [key, video] of subskillVideosCache.entries()) {
                 rawData: el.dataset.flags,
              if (key.startsWith(basePattern) && key.endsWith(weaponSuffix)) {
               });
                 const pathInKey = key.substring(
                  basePattern.length,
                  key.length - weaponSuffix.length
                 );
                // Tenta match exato, ou se termina com o nome da skill
                if (
                  pathInKey === subName ||
                  pathInKey.endsWith(`:${searchName}`) ||
                  pathInKey === searchName
                 ) {
                  v = video;
                  break;
                 }
               }
             }
             }
          }
        } catch (e) {
          console.warn(
            "[Skills] Erro ao processar flags:",
            e,
            "flags data:",
            el.dataset.flags,
            "skill:",
            name || el.dataset.nome
          );
          if (isUrougePage) {
            console.error(
              "[Skills] Urouge - Erro ao processar flags:",
              e,
              "element:",
              el
            );
           }
           }
         }
         }
        if (!v) {
          // Tenta buscar pelo padrão antigo também
          v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`);
        }
      } else if (isEffectVideo && effectKey) {
        v = videosCache.get(effectKey);
       } else {
       } else {
        // Debug: verifica se deveria ter flags mas não tem (apenas para Urouge)
         if (isSubskill && parentIdx && subName) {
         if (isUrougePage) {
           // Busca vídeo de subskill no cache correto
           console.warn("[Skills] Urouge - Skill sem data-flags:", {
           // O cache usa o formato: sub:${parentIdx}:${path} onde path NÃO inclui o nome da skill principal
            skill: name || el.dataset.nome,
           // O subName agora já está no formato correto (sem nome da skill principal)
            element: el,
            allAttributes: Array.from(el.attributes).map(
              (attr) => attr.name + "=" + attr.value
            ),
          });
        }
      }
      if (descBox) {
        descBox.innerHTML = `<div class="skill-title"><h3>${name}</h3></div>${level
           ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>`
          : ""
          }${attrsHTML}<div class="desc">${descHtml}</div>`;
      }
      if (hasWeapon) {
        applyWeaponBadge(el, weaponData, weaponEquipped);
      }
      if (videoBox) {
        const oldFlags = videoBox.querySelector(".skill-flags");
        if (oldFlags) oldFlags.remove();
        if (flagsHTML) {
           videoBox.insertAdjacentHTML("beforeend", flagsHTML);
          applyFlagTooltips(videoBox);
        }
      }
      const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
      currIcons.forEach((i) => i.classList.remove("active"));
      const subsRaw = el.dataset.subs || el.getAttribute("data-subs");
      const isFormSwitch =
        el.dataset.formSwitch === "true" ||
        el.getAttribute("data-form-switch") === "true";
      const isSwap =
        el.dataset.swap === "true" || el.getAttribute("data-swap") === "true";


      // Se for skill de swap, troca personagem (não marca como ativo, não processa como back)
          // Tenta buscar diretamente com o subName
      if (isSwap && !isFormSwitch) {
          let subKey = `sub:${parentIdx}:${subName}`;
        handleSwapCharacter(el);
          v = subskillVideosCache.get(subKey);
        // Não marca como ativo (similar ao form_switch)
        return;
      }


      // Não marca como ativo se for form_switch (Change Form)
          // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
      if (!isFormSwitch) {
          // Isso é útil caso haja alguma diferença no formato do caminho
        el.classList.add("active");
          if (!v) {
        if (!autoplay && loadedVideos > 0) autoplay = true;
            const basePattern = `sub:${parentIdx}:`;
        window.__lastActiveSkillIcon = el;
            const searchName = subName.split(":").pop(); // Último segmento do caminho
        // Lógica de vídeo: usa função centralizada que já considera weapon
            // Tenta também buscar apenas pelo último segmento (útil para sub-subskills)
        showVideoForIcon(el);
            const lastSegmentKey = `sub:${parentIdx}:${searchName}`;
      }
            v = subskillVideosCache.get(lastSegmentKey);
      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";


      // Se for form_switch, alterna forma (não processa como back)
            // Se ainda não encontrou, faz busca mais ampla
      if (isFormSwitch) {
            if (!v) {
        // Atualiza o data-video-file do ícone com o vídeo correto da transição baseado na forma atual
              for (const [key, video] of subskillVideosCache.entries()) {
         try {
                if (key.startsWith(basePattern)) {
           const formsJSON = skillsRoot.dataset.forms || "{}";
                  const pathInKey = key.substring(basePattern.length);
           if (formsJSON && formsJSON !== "{}") {
                  // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
            const tempFormsData = JSON.parse(formsJSON);
                  if (
             const formNames = Object.keys(tempFormsData);
                    pathInKey === subName ||
 
                    pathInKey.endsWith(`:${searchName}`) ||
            // Busca vídeo de transição na skill Change Form
                    pathInKey === searchName ||
             const formVideosRaw =
                    (subName.includes(":") && pathInKey.includes(subName)) ||
              el.dataset.formVideos || el.getAttribute("data-form-videos");
                    (subName.includes(":") &&
             if (formVideosRaw) {
                      pathInKey.endsWith(
              const videos = JSON.parse(formVideosRaw);
                        subName.split(":").slice(-2).join(":")
              // Se currentForm é null, detecta qual forma está atualmente visível no DOM
                      ))
               let formForVideo = currentForm;
                  ) {
               if (!formForVideo) {
                    v = video;
                // Lê formsData se ainda não foi carregado
                    break;
                if (Object.keys(formsData).length === 0) {
                  }
                  try {
                }
                    formsData = JSON.parse(formsJSON);
              }
                  } catch (e) {
            }
                    formsData = {};
          }
                  }
        } else {
                }
          v = videosCache.get(el.dataset.index);
                formForVideo = detectCurrentForm();
          if (!v) {
                // Se ainda não conseguiu detectar, usa a primeira como fallback
            v = nestedVideoElByIcon.get(el);
                if (!formForVideo && formNames.length > 0) {
          }
                  formForVideo = formNames[0];
        }
                }
      }
              }
 
              const transitionVideo = formForVideo
      // Se vídeo não foi encontrado no cache, verifica se falhou antes
                ? videos[formForVideo] || ""
      if (!v) {
                : "";
        // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
              if (transitionVideo && transitionVideo.trim() !== "") {
        if (failedVideosCache.has(videoURL)) {
                // Atualiza temporariamente o data-video-file (o sistema usa isso)
          videoBox.style.display = "none";
                el.dataset.videoFile = transitionVideo;
          return;
              }
        }
             }
        // Para effect, cria vídeo dinamicamente (vídeo alternativo por tempo)
         if (isEffectVideo && videoURL) {
           const baseIndex = el.dataset.index || "";
          const effectCacheKey =
            effectKey || `effect:${effectiveVideo || videoURL}`;
          v = videosCache.get(effectCacheKey);
          if (!v) {
            v = createVideoElement(videoURL, {
              index: baseIndex,
              effect: "1",
            });
            if (v) {
              videoBox.appendChild(v);
              videosCache.set(effectCacheKey, v);
              v.load();
            } else {
              videoBox.style.display = "none";
              return;
            }
           }
        }
        // Para form_switch, cria vídeo dinamicamente (transições de forma são dinâmicas)
        // Usa uma chave única que inclui o nome do arquivo do vídeo para garantir que cada vídeo diferente seja cacheado separadamente
        else if (isFormSwitch && videoURL) {
          const baseIndex = el.dataset.index || "";
          const videoFileName = effectiveVideo || "";
          // Chave única: index + nome do arquivo do vídeo
          const videoKey = baseIndex + ":" + videoFileName;
          v = videosCache.get(videoKey);
          if (!v) {
            v = createVideoElement(videoURL, { index: baseIndex });
            if (v) {
              videoBox.appendChild(v);
              videosCache.set(videoKey, v);
              v.load();
            } else {
              videoBox.style.display = "none";
              return;
            }
          }
        }
        // Vídeos de form skills ou outros que não estão no cache - cria dinamicamente
        else if (videoURL) {
          const idx = el.dataset.index || "";
          v = createVideoElement(videoURL, { index: idx });
          if (v) {
            videoBox.appendChild(v);
            if (idx) videosCache.set(idx, v);
            v.load();
          } else {
            videoBox.style.display = "none";
            return;
          }
        } else {
          videoBox.style.display = "none";
          return;
        }
      }
      videoBox.style.display = "block";
      v.style.display = "block";
      try {
        v.currentTime = 0;
      } catch (e) { }
      const suppress = document.body.dataset.suppressSkillPlay === "1";
      if (!suppress) {
        v.play().catch(() => { });
      } else {
        try {
          v.pause();
        } catch (e) { }
      }
    }
    function activateSkill(el, options = {}) {
      const { openSubs = true } = options;
      const tip = document.querySelector(".skill-tooltip");
      if (tip) {
        tip.setAttribute("aria-hidden", "true");
        tip.style.opacity = "0";
        tip.style.left = "-9999px";
        tip.style.top = "-9999px";
      }
      const skillsRoot = document.getElementById("skills");
      const i18nMap = skillsRoot
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
        : {};
      const L = i18nMap[getLangKey()] ||
        i18nMap.pt || {
        cooldown: "Recarga",
        energy_gain: "Ganho de energia",
        energy_cost: "Custo de energia",
        power: "Poder",
        power_pvp: "Poder PvP",
        level: "Nível",
      };
      const name = el.dataset.nome || el.dataset.name || "";
      if (el.dataset.effect) {
        activateEffectFromIcon(el);
      }
      let weaponData = null;
      if (el.dataset.weapon) {
        try {
          const parsed = JSON.parse(el.dataset.weapon);
          // Só considera weapon válido se for um objeto não vazio
          if (
             parsed &&
            typeof parsed === "object" &&
            Object.keys(parsed).length > 0
          ) {
            weaponData = parsed;
          }
        } catch (e) {
          weaponData = null;
        }
      }
      const hasWeapon = !!weaponData;
      const weaponEquipped = hasWeapon && globalWeaponEnabled;
      // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
      let level = (el.dataset.level || "").trim();
      if (weaponEquipped && weaponData) {
        // Verifica se weapon tem level definido (pode ser número ou string)
        const weaponLevel = weaponData.level;
        if (
          weaponLevel !== undefined &&
          weaponLevel !== null &&
          weaponLevel !== ""
        ) {
          level = weaponLevel.toString().trim();
        }
      }
      const lang = getLangKey();
      const baseDescPack = {
        pt: el.dataset.descPt || "",
        en: el.dataset.descEn || "",
        es: el.dataset.descEs || "",
        pl: el.dataset.descPl || "",
      };
      const baseDesc =
        baseDescPack[lang] ||
        baseDescPack.pt ||
        baseDescPack.en ||
        baseDescPack.es ||
        baseDescPack.pl ||
        el.dataset.desc ||
        "";
      // Aceita tanto desc_i18n quanto desc para compatibilidade
      let weaponDescPack = {};
      if (weaponData) {
        if (weaponData.desc_i18n) {
          weaponDescPack = weaponData.desc_i18n;
        } else if (weaponData.desc) {
          weaponDescPack = weaponData.desc;
        } else {
          weaponDescPack = {
            pt: weaponData.descPt || "",
            en: weaponData.descEn || "",
            es: weaponData.descEs || "",
             pl: weaponData.descPl || "",
          };
        }
      }
      const weaponDesc =
        weaponDescPack[lang] ||
        weaponDescPack.pt ||
        weaponDescPack.en ||
        weaponDescPack.es ||
        weaponDescPack.pl ||
        "";
      const chosenDesc = weaponEquipped && weaponDesc ? weaponDesc : baseDesc;
      const descHtml = chosenDesc.replace(/'''(.*?)'''/g, "<b>$1</b>");
      let attrsHTML = "";
      if (weaponEquipped && weaponData) {
        // Faz merge: usa atributos da skill base e substitui apenas os que existem no weapon
        // Parse dos atributos da skill base (formato: "pve, pvp, energy, cooldown" ou "pve, pvp, energy, cooldown")
        const baseAttrsStr = el.dataset.atr || "";
        // Suporta tanto ", " quanto "," como separador
        const baseAttrs = baseAttrsStr.split(/\s*,\s*/).map((a) => a.trim());
        const basePve =
          baseAttrs[0] && baseAttrs[0] !== "-" ? baseAttrs[0] : "";
        const basePvp =
          baseAttrs[1] && baseAttrs[1] !== "-" ? baseAttrs[1] : "";
        const baseEnergy =
          baseAttrs[2] && baseAttrs[2] !== "-" ? baseAttrs[2] : "";
        const baseCd = baseAttrs[3] && baseAttrs[3] !== "-" ? baseAttrs[3] : "";
 
        // Valores do weapon (substituem os da skill base se existirem)
        const wPve =
          weaponData.powerpve !== undefined &&
            weaponData.powerpve !== null &&
            weaponData.powerpve !== ""
            ? weaponData.powerpve.toString().trim()
            : basePve;
        const wPvp =
          weaponData.powerpvp !== undefined &&
            weaponData.powerpvp !== null &&
            weaponData.powerpvp !== ""
            ? weaponData.powerpvp.toString().trim()
            : basePvp;
        const wEnergy =
          weaponData.energy !== undefined &&
            weaponData.energy !== null &&
            weaponData.energy !== ""
            ? weaponData.energy.toString().trim()
            : baseEnergy;
        const wCd =
          weaponData.cooldown !== undefined &&
            weaponData.cooldown !== null &&
            weaponData.cooldown !== ""
            ? weaponData.cooldown.toString().trim()
             : baseCd;
 
        // Monta string de atributos mesclados
        const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(",");
        attrsHTML = renderAttributes(mergedAttrs);
      } else {
        attrsHTML = el.dataset.atr
          ? renderAttributes(el.dataset.atr)
          : el.dataset.subattrs
            ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L)
            : "";
      }
      let flagsHTML = "";
      // Debug: verifica se é uma skill do Urouge (verifica pela URL da página ou pelo contexto)
      const isUrougePage =
        window.location.href &&
        window.location.href.toLowerCase().includes("urouge");
      if (el.dataset.flags) {
        try {
          const flags = JSON.parse(el.dataset.flags);
          if (flags && Array.isArray(flags) && flags.length > 0) {
            flagsHTML = renderFlagsRow(flags);
            // Debug para Urouge
            if (isUrougePage) {
              console.log("[Skills] Urouge - Flags processadas:", {
                skill: name || el.dataset.nome,
                flags: flags,
                htmlLength: flagsHTML ? flagsHTML.length : 0,
                hasHTML: !!flagsHTML,
               });
            }
          } else {
            if (isUrougePage) {
              console.warn("[Skills] Urouge - Flags inválidas:", {
                skill: name || el.dataset.nome,
                flags: flags,
                rawData: el.dataset.flags,
               });
            }
          }
        } catch (e) {
          console.warn(
            "[Skills] Erro ao processar flags:",
            e,
            "flags data:",
            el.dataset.flags,
            "skill:",
            name || el.dataset.nome
          );
          if (isUrougePage) {
            console.error(
              "[Skills] Urouge - Erro ao processar flags:",
              e,
              "element:",
              el
            );
          }
        }
      } else {
        // Debug: verifica se deveria ter flags mas não tem (apenas para Urouge)
        if (isUrougePage) {
          console.warn("[Skills] Urouge - Skill sem data-flags:", {
            skill: name || el.dataset.nome,
            element: el,
            allAttributes: Array.from(el.attributes).map(
              (attr) => attr.name + "=" + attr.value
            ),
          });
        }
      }
      if (descBox) {
        descBox.innerHTML = `<div class="skill-title"><h3>${name}</h3></div>${level
          ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>`
          : ""
          }${attrsHTML}<div class="desc">${descHtml}</div>`;
      }
      if (hasWeapon) {
        applyWeaponBadge(el, weaponData, weaponEquipped);
      }
      const vb = getVideoBox();
      if (vb) {
        const oldFlags = vb.querySelector(".skill-flags");
        if (oldFlags) oldFlags.remove();
        if (flagsHTML) {
          vb.insertAdjacentHTML("beforeend", flagsHTML);
          applyFlagTooltips(vb);
        }
      }
      const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
      currIcons.forEach((i) => i.classList.remove("active"));
      const subsRaw = el.dataset.subs || el.getAttribute("data-subs");
      const isFormSwitch =
        el.dataset.formSwitch === "true" ||
        el.getAttribute("data-form-switch") === "true";
      const isSwap =
        el.dataset.swap === "true" || el.getAttribute("data-swap") === "true";
 
      // Se for skill de swap, troca personagem (não marca como ativo, não processa como back)
      if (isSwap && !isFormSwitch) {
        handleSwapCharacter(el);
        // Não marca como ativo (similar ao form_switch)
        return;
      }
 
      // Não marca como ativo se for form_switch (Change Form)
      if (!isFormSwitch) {
        el.classList.add("active");
        if (!autoplay && loadedVideos > 0) autoplay = true;
        window.__lastActiveSkillIcon = el;
        // Lógica de vídeo: usa função centralizada que já considera weapon
        showVideoForIcon(el);
      }
      const 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";
 
      // Se for form_switch, alterna forma (não processa como back)
      if (isFormSwitch) {
        try {
          const formsJSON = skillsRoot.dataset.forms || "{}";
          if (formsJSON && formsJSON !== "{}") {
            if (Object.keys(formsData).length === 0) {
              formsData = JSON.parse(formsJSON);
            }
          }
 
          // Inicializa currentForm se necessário
          if (currentForm === null) {
            currentForm = detectCurrentForm();
            const formNames = Object.keys(formsData);
            if (!currentForm && formNames.length > 0) {
              currentForm = formNames[0];
            }
          }
 
          // Calcula a próxima forma ANTES de trocar
          const nextForm = getNextFormName();
 
          // Vídeo de transição: usa o vídeo da PRÓXIMA forma
          const formVideosRaw =
            el.dataset.formVideos || el.getAttribute("data-form-videos");
          if (formVideosRaw && nextForm) {
            const videos = JSON.parse(formVideosRaw);
            const transitionVideo = videos[nextForm] || "";
            if (transitionVideo && transitionVideo.trim() !== "") {
              el.dataset.videoFile = transitionVideo;
            }
          }
        } catch (e) {
          console.error("[Forms] Erro ao processar transição:", e);
        }
 
        showVideoForIcon(el);
        switchForm();
        return;
      }
 
      if (isBack && !isFormSwitch && barStack.length) {
        const prev = barStack.pop();
        // Restaura currentForm se estava salvo no snapshot
        if (prev.currentForm !== undefined) {
          currentForm = prev.currentForm;
        }
        renderBarFromItems(prev);
        const btn = document.querySelector(".skills-back-wrapper");
        if (btn) btn.style.display = barStack.length ? "block" : "none";
        return;
      }
      if (openSubs && subsRaw && subsRaw.trim() !== "") {
        if (barStack.length && barStack[barStack.length - 1].parentIcon === el)
          return;
        try {
          const subs = JSON.parse(subsRaw);
          pushSubBarFrom(subs, el);
        } catch { }
      }
    }
    function wireClicksForCurrentBar() {
      const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
      currIcons.forEach((el) => {
        if (
          el.dataset.weaponToggle === "1" ||
          el.classList.contains("weapon-bar-toggle")
        )
          return;
        if (el.dataset.wired) return;
        el.dataset.wired = "1";
        const label = el.dataset.nome || el.dataset.name || "";
        el.setAttribute("aria-label", label);
        if (el.hasAttribute("title")) el.removeAttribute("title");
        const img = el.querySelector("img");
        if (img) {
          img.setAttribute("alt", "");
          if (img.hasAttribute("title")) img.removeAttribute("title");
        }
        el.addEventListener("click", () => {
          activateSkill(el, {
            openSubs: true,
          });
        });
      });
      wireTooltipsForNewIcons();
      applyEffectClasses();
    }
    function animateIconsBarEntrance() {
      Array.from(iconsBar.children).forEach((c, i) => {
        c.style.opacity = "0";
        c.style.transform = "translateY(6px)";
        requestAnimationFrame(() => {
          setTimeout(() => {
            c.style.transition = "opacity .18s ease, transform .18s ease";
            c.style.opacity = "1";
            c.style.transform = "translateY(0)";
          }, i * 24);
        });
      });
    }
    function snapshotCurrentBarItemsFromDOM() {
      const items = Array.from(iconsBar.querySelectorAll(".skill-icon"))
        .filter((el) => el.dataset.weaponToggle !== "1")
        .map((el) => {
          const img = el.querySelector("img");
          const iconURL = img ? img.src : "";
          const subsRaw = el.dataset.subs || el.getAttribute("data-subs") || "";
          let subs = null;
          try {
             subs = subsRaw ? JSON.parse(subsRaw) : null;
          } catch {
            subs = null;
           }
           }
        } catch (e) {
           const subattrsRaw = el.dataset.subattrs || "";
           console.error("[Forms] Erro ao processar vídeo de transição:", e);
          let flags = null;
        }
          if (el.dataset.flags) {
 
            try {
        // Usa o sistema normal de vídeo (mesmo que skills normais)
              flags = JSON.parse(el.dataset.flags);
        showVideoForIcon(el);
            } catch (e) { }
        switchForm();
          }
        return;
          let weapon = null;
      }
          if (el.dataset.weapon) {
 
            try {
      if (isBack && !isFormSwitch && barStack.length) {
              weapon = JSON.parse(el.dataset.weapon);
        const prev = barStack.pop();
            } catch (e) { }
        // Restaura currentForm se estava salvo no snapshot
          }
        if (prev.currentForm !== undefined) {
          let effect = null;
          currentForm = prev.currentForm;
          if (el.dataset.effect) {
        }
            try {
        renderBarFromItems(prev);
              effect = JSON.parse(el.dataset.effect);
        const btn = document.querySelector(".skills-back-wrapper");
            } catch (e) { }
        if (btn) btn.style.display = barStack.length ? "block" : "none";
          }
        return;
          // Preserva data-form-videos para poder restaurar depois
      }
          const formVideos =
      if (openSubs && subsRaw && subsRaw.trim() !== "") {
            el.dataset.formVideos || el.getAttribute("data-form-videos") || "";
        if (barStack.length && barStack[barStack.length - 1].parentIcon === el)
          return {
          return;
            name: el.dataset.nome || el.dataset.name || "",
        try {
            index: el.dataset.index || "",
          const subs = JSON.parse(subsRaw);
            level: el.dataset.level || "",
           pushSubBarFrom(subs, el);
            desc: el.dataset.desc || "",
         } catch { }
            descPt: el.dataset.descPt || "",
       }
            descEn: el.dataset.descEn || "",
            descEs: el.dataset.descEs || "",
            descPl: el.dataset.descPl || "",
            attrs: el.dataset.atr || el.dataset.attrs || "",
            video: el.dataset.video || "",
            iconURL,
            subs,
            subattrsStr: subattrsRaw,
            flags: flags,
            weapon: weapon,
            effect: effect,
            nested: el.dataset.nested || "",
            subName: el.dataset.subName || "",
            parentIndex: el.dataset.parentIndex || "",
            formSwitch:
              el.dataset.formSwitch ||
              el.getAttribute("data-form-switch") ||
              "",
            formVideos: formVideos,
           };
         });
      // Retorna objeto com items e currentForm para poder restaurar depois
       return { items, currentForm: currentForm };
     }
     }
     function wireClicksForCurrentBar() {
     function ensureBackButton() {
       const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
       const rail = iconsBar.closest(".top-rail.skills");
       currIcons.forEach((el) => {
       if (!rail) return null;
        if (
      let wrap = rail.parentElement;
          el.dataset.weaponToggle === "1" ||
      if (
          el.classList.contains("weapon-bar-toggle")
        !wrap ||
        )
        !wrap.classList ||
          return;
        !wrap.classList.contains("skills-rail-wrap")
         if (el.dataset.wired) return;
      ) {
         el.dataset.wired = "1";
        const parentNode = rail.parentNode;
         const label = el.dataset.nome || el.dataset.name || "";
         const newWrap = document.createElement("div");
         el.setAttribute("aria-label", label);
         newWrap.className = "skills-rail-wrap";
        if (el.hasAttribute("title")) el.removeAttribute("title");
         parentNode.insertBefore(newWrap, rail);
         const img = el.querySelector("img");
        newWrap.appendChild(rail);
         if (img) {
         wrap = newWrap;
          img.setAttribute("alt", "");
      }
           if (img.hasAttribute("title")) img.removeAttribute("title");
      let backWrap = wrap.querySelector(".skills-back-wrapper");
         }
      if (!backWrap) {
         el.addEventListener("click", () => {
        backWrap = document.createElement("div");
           activateSkill(el, {
        backWrap.className = "skills-back-wrapper";
            openSubs: true,
         const btnInner = document.createElement("button");
           });
         btnInner.className = "skills-back";
        btnInner.type = "button";
        btnInner.setAttribute("aria-label", "Voltar");
        btnInner.innerHTML =
           '<svg class="back-chevron" width="100%" height="100%" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" preserveAspectRatio="xMidYMid meet"><path d="M10 2L4 16L10 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 2L14 16L20 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M30 2L24 16L30 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
        backWrap.appendChild(btnInner);
         wrap.insertBefore(backWrap, rail);
         btnInner.addEventListener("click", () => {
           if (!barStack.length) return;
          const prev = barStack.pop();
          renderBarFromItems(prev);
          backWrap.style.display = barStack.length ? "block" : "none";
          wrap.classList.toggle("has-sub-bar", barStack.length > 0);
           if (!barStack.length) btnInner.classList.remove("peek");
         });
         });
       });
       }
       wireTooltipsForNewIcons();
      backWrap.style.display = barStack.length ? "block" : "none";
       applyEffectClasses();
       wrap.classList.toggle("has-sub-bar", barStack.length > 0);
    }
       const btnInner = backWrap.querySelector(".skills-back");
    function animateIconsBarEntrance() {
 
      Array.from(iconsBar.children).forEach((c, i) => {
      // Calcula a posição do botão baseado na posição real do top-rail
         c.style.opacity = "0";
      function updateBackButtonPosition() {
        c.style.transform = "translateY(6px)";
        if (!rail || !backWrap) return;
        const railRect = rail.getBoundingClientRect();
        const wrapRect = wrap.getBoundingClientRect();
        // Calcula a posição relativa do rail dentro do wrap
        const railLeft = railRect.left - wrapRect.left;
        // Posiciona o wrapper na borda esquerda do rail
        // O botão interno usa translateX(-97%) para ficar atrás da barra
         backWrap.style.left = railLeft + "px";
      }
 
      // Atualiza a posição quando necessário
      if (backWrap.style.display !== "none") {
        // Usa requestAnimationFrame para garantir que o DOM foi atualizado
         requestAnimationFrame(() => {
         requestAnimationFrame(() => {
           setTimeout(() => {
           updateBackButtonPosition();
            c.style.transition = "opacity .18s ease, transform .18s ease";
            c.style.opacity = "1";
            c.style.transform = "translateY(0)";
          }, i * 24);
         });
         });
       });
 
        // Recalcula em resize e scroll
        if (!backWrap.dataset.positionWired) {
          backWrap.dataset.positionWired = "1";
          const updateOnResize = () => {
            if (backWrap.style.display !== "none") {
              updateBackButtonPosition();
            }
          };
          window.addEventListener("resize", updateOnResize);
          const observer = new ResizeObserver(() => {
            if (backWrap.style.display !== "none") {
              updateBackButtonPosition();
            }
          });
          if (rail) observer.observe(rail);
          observer.observe(wrap);
        }
       }
 
      return btnInner;
     }
     }
     function snapshotCurrentBarItemsFromDOM() {
     function renderBarFromItems(itemsOrSnapshot) {
       const items = Array.from(iconsBar.querySelectorAll(".skill-icon"))
       // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
         .filter((el) => el.dataset.weaponToggle !== "1")
      let items, savedCurrentForm;
        .map((el) => {
      if (Array.isArray(itemsOrSnapshot)) {
          const img = el.querySelector("img");
        items = itemsOrSnapshot;
          const iconURL = img ? img.src : "";
        savedCurrentForm = null;
          const subsRaw = el.dataset.subs || el.getAttribute("data-subs") || "";
      } else if (itemsOrSnapshot && itemsOrSnapshot.items) {
          let subs = null;
        items = itemsOrSnapshot.items;
          try {
        savedCurrentForm = itemsOrSnapshot.currentForm;
            subs = subsRaw ? JSON.parse(subsRaw) : null;
      } else {
          } catch {
        items = [];
            subs = null;
        savedCurrentForm = null;
          }
      }
          const subattrsRaw = el.dataset.subattrs || "";
      // Restaura currentForm se estava salvo
          let flags = null;
      if (savedCurrentForm !== undefined && savedCurrentForm !== null) {
          if (el.dataset.flags) {
        currentForm = savedCurrentForm;
            try {
      }
              flags = JSON.parse(el.dataset.flags);
      const tip = document.querySelector(".skill-tooltip");
            } catch (e) { }
      if (tip) {
           }
         tip.setAttribute("aria-hidden", "true");
           let weapon = null;
        tip.style.opacity = "0";
           if (el.dataset.weapon) {
        tip.style.left = "-9999px";
            try {
        tip.style.top = "-9999px";
              weapon = JSON.parse(el.dataset.weapon);
      }
            } catch (e) { }
      iconsBar.innerHTML = "";
          }
      items.forEach((it, idx) => {
          let effect = null;
        const node = document.createElement("div");
          if (el.dataset.effect) {
        node.className = "skill-icon";
            try {
        node.dataset.nome = it.name || "";
              effect = JSON.parse(el.dataset.effect);
        if (it.index) node.dataset.index = it.index;
            } catch (e) { }
        if (it.level) node.dataset.level = it.level;
          }
        if (it.desc) node.dataset.desc = it.desc;
          // Preserva data-form-videos para poder restaurar depois
        if (it.descPt) node.dataset.descPt = it.descPt;
          const formVideos =
        if (it.descEn) node.dataset.descEn = it.descEn;
            el.dataset.formVideos || el.getAttribute("data-form-videos") || "";
        if (it.descEs) node.dataset.descEs = it.descEs;
          return {
        if (it.descPl) node.dataset.descPl = it.descPl;
            name: el.dataset.nome || el.dataset.name || "",
        if (it.attrs) node.dataset.atr = it.attrs;
            index: el.dataset.index || "",
        if (it.video) node.dataset.video = it.video;
            level: el.dataset.level || "",
        if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
            desc: el.dataset.desc || "",
        if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr;
            descPt: el.dataset.descPt || "",
        if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
            descEn: el.dataset.descEn || "",
        if (
            descEs: el.dataset.descEs || "",
           it.weapon &&
            descPl: el.dataset.descPl || "",
           typeof it.weapon === "object" &&
            attrs: el.dataset.atr || el.dataset.attrs || "",
           Object.keys(it.weapon).length > 0
            video: el.dataset.video || "",
        ) {
            iconURL,
          node.dataset.weapon = JSON.stringify(it.weapon);
            subs,
        }
            subattrsStr: subattrsRaw,
        if (it.effect && typeof it.effect === "object") {
            flags: flags,
          try {
            weapon: weapon,
            node.dataset.effect = JSON.stringify(it.effect);
            effect: effect,
          } catch (e) { }
            nested: el.dataset.nested || "",
        }
            subName: el.dataset.subName || "",
        // Restaura informações de subskill importantes para busca de vídeos
            parentIndex: el.dataset.parentIndex || "",
        if (it.nested) node.dataset.nested = it.nested;
            formSwitch:
        if (it.subName) node.dataset.subName = it.subName;
              el.dataset.formSwitch ||
        if (it.parentIndex) node.dataset.parentIndex = it.parentIndex;
              el.getAttribute("data-form-switch") ||
        if (!it.index && !it.nested) node.dataset.nested = "1";
              "",
        // Restaura formSwitch (Change Form)
            formVideos: formVideos,
        if (it.formSwitch) {
          };
          node.dataset.formSwitch = it.formSwitch;
         });
          node.setAttribute("data-form-switch", it.formSwitch);
      // Retorna objeto com items e currentForm para poder restaurar depois
         }
      return { items, currentForm: currentForm };
        // Restaura formVideos (vídeos de transição de forma)
    }
        if (it.formVideos) {
    function ensureBackButton() {
          node.dataset.formVideos = it.formVideos;
      const rail = iconsBar.closest(".top-rail.skills");
          node.setAttribute("data-form-videos", it.formVideos);
      if (!rail) return null;
        }
      let wrap = rail.parentElement;
        const img = document.createElement("img");
      if (
        img.alt = "";
         !wrap ||
         img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : "");
        !wrap.classList ||
         img.decoding = "async";
        !wrap.classList.contains("skills-rail-wrap")
         img.loading = "lazy";
      ) {
         img.width = 50;
         const parentNode = rail.parentNode;
        img.height = 50;
         const newWrap = document.createElement("div");
         node.appendChild(img);
         newWrap.className = "skills-rail-wrap";
         iconsBar.appendChild(node);
         parentNode.insertBefore(newWrap, rail);
      });
         newWrap.appendChild(rail);
      animateIconsBarEntrance();
        wrap = newWrap;
      wireClicksForCurrentBar();
       }
       // Remove qualquer toggle antigo que possa aparecer
       let backWrap = wrap.querySelector(".skills-back-wrapper");
       const oldToggle = iconsBar.querySelector(".weapon-bar-toggle");
       if (!backWrap) {
       if (oldToggle) oldToggle.remove();
        backWrap = document.createElement("div");
      // Reaplica classes de weapon após renderizar barra
        backWrap.className = "skills-back-wrapper";
      reapplyWeaponClassesToBar();
         const btnInner = document.createElement("button");
      const b = ensureBackButton();
        btnInner.className = "skills-back";
      if (b) b.classList.add("peek");
         btnInner.type = "button";
      // Atualiza a posição do botão back após a barra ser renderizada
        btnInner.setAttribute("aria-label", "Voltar");
      requestAnimationFrame(() => {
        btnInner.innerHTML =
         const backWrap = document.querySelector(".skills-back-wrapper");
           '<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>';
         if (backWrap && backWrap.style.display !== "none") {
         backWrap.appendChild(btnInner);
          const rail = iconsBar.closest(".top-rail.skills");
        wrap.insertBefore(backWrap, rail);
          const wrap = rail ? rail.parentElement : null;
        btnInner.addEventListener("click", () => {
           if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
          if (!barStack.length) return;
            const railRect = rail.getBoundingClientRect();
          const prev = barStack.pop();
            const wrapRect = wrap.getBoundingClientRect();
          renderBarFromItems(prev);
            const railLeft = railRect.left - wrapRect.left;
          backWrap.style.display = barStack.length ? "block" : "none";
            // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
          wrap.classList.toggle("has-sub-bar", barStack.length > 0);
            backWrap.style.left = railLeft + "px";
          if (!barStack.length) btnInner.classList.remove("peek");
          }
        });
         }
      });
    }
    function pushSubBarFrom(subs, parentIconEl) {
      const tip = document.querySelector(".skill-tooltip");
      if (tip) {
        tip.setAttribute("aria-hidden", "true");
        tip.style.opacity = "0";
        tip.style.left = "-9999px";
        tip.style.top = "-9999px";
       }
       }
       backWrap.style.display = barStack.length ? "block" : "none";
       const parentNameSnapshot = parentIconEl
       wrap.classList.toggle("has-sub-bar", barStack.length > 0);
        ? parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
       const btnInner = backWrap.querySelector(".skills-back");
        : "";
 
       // Para subskills, usa parentIndex; para skills principais, usa index
       // Calcula a posição do botão baseado na posição real do top-rail
      const parentIndexSnapshot = parentIconEl
       function updateBackButtonPosition() {
        ? parentIconEl.dataset.nested === "1"
         if (!rail || !backWrap) return;
          ? parentIconEl.dataset.parentIndex || ""
        const railRect = rail.getBoundingClientRect();
          : parentIconEl.dataset.index || ""
        const wrapRect = wrap.getBoundingClientRect();
        : "";
        // Calcula a posição relativa do rail dentro do wrap
       const snapshot = snapshotCurrentBarItemsFromDOM();
        const railLeft = railRect.left - wrapRect.left;
      barStack.push({
        // Posiciona o wrapper na borda esquerda do rail
        items: snapshot.items,
        // O botão interno usa translateX(-97%) para ficar atrás da barra
        currentForm: snapshot.currentForm,
        backWrap.style.left = railLeft + "px";
        parentIcon: parentIconEl,
        parentName: parentNameSnapshot,
        parentIndex: parentIndexSnapshot,
      });
      ensureBackButton();
      const langKey = getLangKey();
       let cacheKey = null;
       if (parentIconEl) {
        cacheKey = parentIconEl.dataset.subCacheKey || null;
         if (!cacheKey) {
          if (parentIconEl.dataset.index) {
            cacheKey = `idx:${parentIconEl.dataset.index}`;
          } else {
            const slug = slugify(
              parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
            );
            if (slug) cacheKey = `slug:${slug}`;
          }
          if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey;
        }
       }
       }
 
       if (cacheKey) {
      // Atualiza a posição quando necessário
         const cached = subBarTemplateCache.get(cacheKey);
       if (backWrap.style.display !== "none") {
         if (cached && cached.lang === langKey) {
         // Usa requestAnimationFrame para garantir que o DOM foi atualizado
           iconsBar.innerHTML = "";
        requestAnimationFrame(() => {
           const clone = cached.template.cloneNode(true);
          updateBackButtonPosition();
          iconsBar.appendChild(clone);
        });
          animateIconsBarEntrance();
 
          wireClicksForCurrentBar();
        // Recalcula em resize e scroll
           // Remove qualquer toggle antigo que possa aparecer
         if (!backWrap.dataset.positionWired) {
           const oldToggle2 = iconsBar.querySelector(".weapon-bar-toggle");
           backWrap.dataset.positionWired = "1";
           if (oldToggle2) oldToggle2.remove();
           const updateOnResize = () => {
          // Reaplica classes de weapon após renderizar do cache
            if (backWrap.style.display !== "none") {
          reapplyWeaponClassesToBar();
              updateBackButtonPosition();
           const cachedBtn = ensureBackButton();
            }
           if (cachedBtn) cachedBtn.classList.add("peek");
           };
           return;
           window.addEventListener("resize", updateOnResize);
           const observer = new ResizeObserver(() => {
            if (backWrap.style.display !== "none") {
              updateBackButtonPosition();
            }
           });
           if (rail) observer.observe(rail);
           observer.observe(wrap);
         }
         }
       }
       }
 
       const skillsRoot = document.getElementById("skills");
       return btnInner;
      const i18nMap = skillsRoot
    }
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
    function renderBarFromItems(itemsOrSnapshot) {
        : {};
       // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
       const L = i18nMap[getLangKey()] ||
      let items, savedCurrentForm;
        i18nMap.pt || {
      if (Array.isArray(itemsOrSnapshot)) {
        cooldown: "Recarga",
         items = itemsOrSnapshot;
        energy_gain: "Ganho de energia",
         savedCurrentForm = null;
        energy_cost: "Custo de energia",
       } else if (itemsOrSnapshot && itemsOrSnapshot.items) {
        power: "Poder",
        items = itemsOrSnapshot.items;
         power_pvp: "Poder PvP",
        savedCurrentForm = itemsOrSnapshot.currentForm;
         level: "Nível",
       } else {
       };
        items = [];
      const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
         savedCurrentForm = null;
       const items = (hydratedSubs || [])
      }
         .filter((s) => {
      // Restaura currentForm se estava salvo
          // Filtra só se não tem nada útil
      if (savedCurrentForm !== undefined && savedCurrentForm !== null) {
          const hasName = (s.name || s.n || "").trim() !== "";
        currentForm = savedCurrentForm;
          const hasIcon = (s.icon || "").trim() !== "";
      }
          const hasRef = (s.refS || s.refM || "").toString().trim() !== "";
      const tip = document.querySelector(".skill-tooltip");
          return hasName || hasIcon || hasRef;
      if (tip) {
        })
        tip.setAttribute("aria-hidden", "true");
        .map((s) => {
        tip.style.opacity = "0";
          const name = (s.name || s.n || "").trim();
        tip.style.left = "-9999px";
          const desc = chooseDescFrom(s).replace(/'''(.*?)'''/g, "<b>$1</b>");
        tip.style.top = "-9999px";
          const attrsHTML = renderSubAttributesFromObj(s, L);
      }
          return {
       iconsBar.innerHTML = "";
            name,
       items.forEach((it, idx) => {
            level: (s.level || "").toString().trim(),
            desc,
            descPt: s.descPt || (s.desc_i18n && s.desc_i18n.pt) || "",
            descEn: s.descEn || (s.desc_i18n && s.desc_i18n.en) || "",
            descEs: s.descEs || (s.desc_i18n && s.desc_i18n.es) || "",
            descPl: s.descPl || (s.desc_i18n && s.desc_i18n.pl) || "",
            attrs: "",
            icon: s.icon || "",
            iconURL: s.icon ? filePathURL(s.icon) : "",
            video: s.video ? filePathURL(s.video) : "",
            subs: Array.isArray(s.subs) ? s.subs : null,
            subattrs: s,
            flags: Array.isArray(s.flags) ? s.flags : null,
            back:
              s.back === true ||
                s.back === "true" ||
                s.back === "yes" ||
                s.back === "1"
                ? "true"
                : null,
            weapon: s.weapon || null,
          };
        });
       const fragment = document.createDocumentFragment();
       items.forEach((it, iIdx) => {
         const node = document.createElement("div");
         const node = document.createElement("div");
         node.className = "skill-icon";
         node.className = "skill-icon";
        node.dataset.nested = "1";
         node.dataset.nome = it.name || "";
         node.dataset.nome = it.name || "";
         if (it.index) node.dataset.index = it.index;
        node.dataset.parentIndex = parentIndexSnapshot;
        // Constrói o caminho completo para sub-subskills
        // IMPORTANTE: O cache NÃO inclui o nome da skill principal no caminho
        // Formato do cache: "SubskillName" ou "SubskillName:SubSubskillName" (sem o nome da skill principal)
        let fullPath = it.name || "";
         if (parentIconEl) {
          // Se o pai é uma subskill (tem nested), adiciona o nome do pai ao caminho
          if (parentIconEl.dataset.nested === "1") {
            // Se o pai tem subName, usa ele (já está no formato correto, sem nome da skill principal)
            if (
              parentIconEl.dataset.subName &&
              parentIconEl.dataset.subName.trim() !== ""
            ) {
              fullPath = `${parentIconEl.dataset.subName}:${it.name || ""}`;
            } else {
              // Se o pai não tem subName, é a primeira subskill, então usa apenas o nome do pai
              const parentName = (
                parentIconEl.dataset.nome ||
                parentIconEl.dataset.name ||
                ""
              ).trim();
              fullPath = `${parentName}:${it.name || ""}`;
            }
          }
          // Se o pai é uma skill principal (não é nested), o caminho é apenas o nome da subskill atual
          // (já está definido como it.name acima)
        }
        node.dataset.subName = fullPath;
        const subSlug = slugify(it.name || "");
        if (subSlug) node.dataset.slug = subSlug;
         if (it.level) node.dataset.level = it.level;
         if (it.level) node.dataset.level = it.level;
         if (it.desc) node.dataset.desc = it.desc;
         if (it.desc) node.dataset.desc = it.desc;
Linha 3 790: Linha 4 188:
         if (it.descEs) node.dataset.descEs = it.descEs;
         if (it.descEs) node.dataset.descEs = it.descEs;
         if (it.descPl) node.dataset.descPl = it.descPl;
         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.video) node.dataset.video = it.video;
         if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
         if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
         if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr;
         if (it.subattrs) node.dataset.subattrs = JSON.stringify(it.subattrs);
         if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
         if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
        if (it.back) node.dataset.back = it.back;
         if (
         if (
           it.weapon &&
           it.weapon &&
Linha 3 800: Linha 4 198:
           Object.keys(it.weapon).length > 0
           Object.keys(it.weapon).length > 0
         ) {
         ) {
          node.dataset.weapon = JSON.stringify(it.weapon);
        }
        if (it.effect && typeof it.effect === "object") {
           try {
           try {
             node.dataset.effect = JSON.stringify(it.effect);
             node.dataset.weapon = JSON.stringify(it.weapon);
           } catch (e) { }
           } catch (e) {
        }
            console.error(
        // Restaura informações de subskill importantes para busca de vídeos
              "[Skills] Erro ao serializar weapon de subskill",
        if (it.nested) node.dataset.nested = it.nested;
              it.name,
        if (it.subName) node.dataset.subName = it.subName;
              e
        if (it.parentIndex) node.dataset.parentIndex = it.parentIndex;
            );
        if (!it.index && !it.nested) node.dataset.nested = "1";
           }
        // Restaura formSwitch (Change Form)
        if (it.formSwitch) {
          node.dataset.formSwitch = it.formSwitch;
          node.setAttribute("data-form-switch", it.formSwitch);
        }
        // Restaura formVideos (vídeos de transição de forma)
        if (it.formVideos) {
          node.dataset.formVideos = it.formVideos;
           node.setAttribute("data-form-videos", it.formVideos);
         }
         }
         const img = document.createElement("img");
         const img = document.createElement("img");
         img.alt = "";
         img.alt = "";
         img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : "");
         img.src = it.iconURL;
         img.decoding = "async";
         img.decoding = "async";
         img.loading = "lazy";
         img.loading = "lazy";
Linha 3 830: Linha 4 216:
         img.height = 50;
         img.height = 50;
         node.appendChild(img);
         node.appendChild(img);
         iconsBar.appendChild(node);
         fragment.appendChild(node);
       });
       });
      const templateClone = fragment.cloneNode(true);
      iconsBar.innerHTML = "";
      iconsBar.appendChild(fragment);
       animateIconsBarEntrance();
       animateIconsBarEntrance();
       wireClicksForCurrentBar();
       wireClicksForCurrentBar();
       // Remove qualquer toggle antigo que possa aparecer
       // Remove qualquer toggle antigo que possa aparecer
       const oldToggle = iconsBar.querySelector(".weapon-bar-toggle");
       const oldToggle3 = iconsBar.querySelector(".weapon-bar-toggle");
       if (oldToggle) oldToggle.remove();
       if (oldToggle3) oldToggle3.remove();
       // Reaplica classes de weapon após renderizar barra
       // Reaplica classes de weapon após renderizar subskills
       reapplyWeaponClassesToBar();
       reapplyWeaponClassesToBar();
       const b = ensureBackButton();
       const b2 = ensureBackButton();
       if (b) b.classList.add("peek");
       if (b2) b2.classList.add("peek");
       // Atualiza a posição do botão back após a barra ser renderizada
       // Atualiza a posição do botão back após subskills serem renderizadas
       requestAnimationFrame(() => {
       requestAnimationFrame(() => {
         const backWrap = document.querySelector(".skills-back-wrapper");
         const backWrap = document.querySelector(".skills-back-wrapper");
Linha 3 856: Linha 4 245:
         }
         }
       });
       });
    }
       if (cacheKey) {
    function pushSubBarFrom(subs, parentIconEl) {
         subBarTemplateCache.set(cacheKey, {
      const tip = document.querySelector(".skill-tooltip");
          template: templateClone,
       if (tip) {
          lang: langKey,
         tip.setAttribute("aria-hidden", "true");
         });
        tip.style.opacity = "0";
        tip.style.left = "-9999px";
         tip.style.top = "-9999px";
       }
       }
       const parentNameSnapshot = parentIconEl
    }
         ? parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
    window.addEventListener("gla:langChanged", () => {
         : "";
      subBarTemplateCache.clear();
       // Para subskills, usa parentIndex; para skills principais, usa index
       const skillsRoot = document.getElementById("skills");
       const parentIndexSnapshot = parentIconEl
      const i18nMap = skillsRoot
        ? parentIconEl.dataset.nested === "1"
         ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
           ? parentIconEl.dataset.parentIndex || ""
         : {};
           : parentIconEl.dataset.index || ""
       const lang = getLangKey();
        : "";
       Array.from(iconsBar.querySelectorAll(".skill-icon")).forEach((icon) => {
      const snapshot = snapshotCurrentBarItemsFromDOM();
        const pack = {
      barStack.push({
          pt: icon.dataset.descPt || "",
        items: snapshot.items,
           en: icon.dataset.descEn || "",
        currentForm: snapshot.currentForm,
           es: icon.dataset.descEs || "",
        parentIcon: parentIconEl,
          pl: icon.dataset.descPl || "",
         parentName: parentNameSnapshot,
        };
         parentIndex: parentIndexSnapshot,
        const chosen = (
          pack[lang] ||
          pack.pt ||
          pack.en ||
          pack.es ||
          pack.pl ||
          icon.dataset.desc ||
          ""
         ).trim();
         if (chosen) icon.dataset.desc = chosen;
       });
       });
       ensureBackButton();
       barStack.forEach((frame) => {
      const langKey = getLangKey();
         (frame.items || []).forEach((it) => {
      let cacheKey = null;
           const pack = {
      if (parentIconEl) {
            pt: it.descPt,
         cacheKey = parentIconEl.dataset.subCacheKey || null;
            en: it.descEn,
        if (!cacheKey) {
             es: it.descEs,
           if (parentIconEl.dataset.index) {
            pl: it.descPl,
             cacheKey = `idx:${parentIconEl.dataset.index}`;
          };
           } else {
           const chosen =
             const slug = slugify(
            pack[lang] ||
              parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
             pack.pt ||
            );
            pack.en ||
            if (slug) cacheKey = `slug:${slug}`;
            pack.es ||
          }
            pack.pl ||
          if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey;
            it.desc ||
         }
            "";
          it.desc = chosen;
        });
      });
      if (descBox) {
         applyFlagTooltips(descBox);
       }
       }
       if (cacheKey) {
       const activeIcon = window.__lastActiveSkillIcon;
        const cached = subBarTemplateCache.get(cacheKey);
      if (activeIcon && activeIcon.dataset.weapon) {
        if (cached && cached.lang === langKey) {
        activateSkill(activeIcon, {
          iconsBar.innerHTML = "";
           openSubs: false,
          const clone = cached.template.cloneNode(true);
        });
          iconsBar.appendChild(clone);
          animateIconsBarEntrance();
          wireClicksForCurrentBar();
          // Remove qualquer toggle antigo que possa aparecer
          const oldToggle2 = iconsBar.querySelector(".weapon-bar-toggle");
          if (oldToggle2) oldToggle2.remove();
           // Reaplica classes de weapon após renderizar do cache
          reapplyWeaponClassesToBar();
          const cachedBtn = ensureBackButton();
          if (cachedBtn) cachedBtn.classList.add("peek");
          return;
        }
       }
       }
      const skillsRoot = document.getElementById("skills");
    });
      const i18nMap = skillsRoot
    wireClicksForCurrentBar();
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
 
        : {};
    // Inicializa o sistema de swap de personagens (genérico)
      const L = i18nMap[getLangKey()] ||
    // Aguarda um pouco para garantir que todos os ícones foram renderizados
        i18nMap.pt || {
    setTimeout(() => {
        cooldown: "Recarga",
       initializeActiveCharacter();
        energy_gain: "Ganho de energia",
    }, 100);
        energy_cost: "Custo de energia",
 
        power: "Poder",
    const b0 = ensureBackButton();
        power_pvp: "Poder PvP",
    if (b0) {
        level: "Nível",
      b0.classList.add("peek");
       };
      b0.style.alignSelf = "stretch";
      const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
    }
      const items = (hydratedSubs || [])
 
        .filter((s) => {
    // Move inicialização de tooltip para requestIdleCallback (não crítico)
          // Filtra só se não tem nada útil
    if ("requestIdleCallback" in window) {
          const hasName = (s.name || s.n || "").trim() !== "";
      requestIdleCallback(
          const hasIcon = (s.icon || "").trim() !== "";
         () => {
          const hasRef = (s.refS || s.refM || "").toString().trim() !== "";
           (function initSkillTooltip() {
          return hasName || hasIcon || hasRef;
            if (document.querySelector(".skill-tooltip")) return;
        })
            const tip = document.createElement("div");
         .map((s) => {
             tip.className = "skill-tooltip";
           const name = (s.name || s.n || "").trim();
             tip.setAttribute("role", "tooltip");
          const desc = chooseDescFrom(s).replace(/'''(.*?)'''/g, "<b>$1</b>");
             tip.setAttribute("aria-hidden", "true");
          const attrsHTML = renderSubAttributesFromObj(s, L);
             document.body.appendChild(tip);
          return {
             const lockUntilRef = {
            name,
              value: 0,
             level: (s.level || "").toString().trim(),
             };
            desc,
             function measureAndPos(el) {
             descPt: s.descPt || (s.desc_i18n && s.desc_i18n.pt) || "",
               if (!el || tip.getAttribute("aria-hidden") === "true") return;
            descEn: s.descEn || (s.desc_i18n && s.desc_i18n.en) || "",
              tip.style.left = "0px";
            descEs: s.descEs || (s.desc_i18n && s.desc_i18n.es) || "",
              tip.style.top = "0px";
             descPl: s.descPl || (s.desc_i18n && s.desc_i18n.pl) || "",
              const rect = el.getBoundingClientRect();
            attrs: "",
              const tr = tip.getBoundingClientRect();
             icon: s.icon || "",
              let left = Math.round(rect.left + (rect.width - tr.width) / 2);
            iconURL: s.icon ? filePathURL(s.icon) : "",
              left = Math.max(
             video: s.video ? filePathURL(s.video) : "",
                8,
            subs: Array.isArray(s.subs) ? s.subs : null,
                Math.min(left, window.innerWidth - tr.width - 8)
             subattrs: s,
              );
             flags: Array.isArray(s.flags) ? s.flags : null,
              const coarse =
            back:
                (window.matchMedia &&
               s.back === true ||
                  matchMedia("(pointer: coarse)").matches) ||
                s.back === "true" ||
                window.innerWidth <= 600;
                s.back === "yes" ||
               let top = coarse
                s.back === "1"
                 ? Math.round(rect.bottom + 10)
                ? "true"
                 : Math.round(rect.top - tr.height - 8);
                : null,
               if (top < 8) top = Math.round(rect.bottom + 10);
            weapon: s.weapon || null,
               tip.style.left = left + "px";
          };
              tip.style.top = top + "px";
        });
      const fragment = document.createDocumentFragment();
      items.forEach((it, iIdx) => {
        const node = document.createElement("div");
        node.className = "skill-icon";
        node.dataset.nested = "1";
        node.dataset.nome = it.name || "";
        node.dataset.parentIndex = parentIndexSnapshot;
        // Constrói o caminho completo para sub-subskills
        // IMPORTANTE: O cache NÃO inclui o nome da skill principal no caminho
        // Formato do cache: "SubskillName" ou "SubskillName:SubSubskillName" (sem o nome da skill principal)
        let fullPath = it.name || "";
        if (parentIconEl) {
          // Se o pai é uma subskill (tem nested), adiciona o nome do pai ao caminho
          if (parentIconEl.dataset.nested === "1") {
            // Se o pai tem subName, usa ele (já está no formato correto, sem nome da skill principal)
            if (
              parentIconEl.dataset.subName &&
              parentIconEl.dataset.subName.trim() !== ""
            ) {
              fullPath = `${parentIconEl.dataset.subName}:${it.name || ""}`;
            } else {
               // Se o pai não tem subName, é a primeira subskill, então usa apenas o nome do pai
              const parentName = (
                 parentIconEl.dataset.nome ||
                 parentIconEl.dataset.name ||
                ""
               ).trim();
               fullPath = `${parentName}:${it.name || ""}`;
             }
             }
          }
            function show(el, text) {
          // Se o pai é uma skill principal (não é nested), o caminho é apenas o nome da subskill atual
              tip.textContent = text || "";
          // (já está definido como it.name acima)
              tip.setAttribute("aria-hidden", "false");
        }
              measureAndPos(el);
        node.dataset.subName = fullPath;
              tip.style.opacity = "1";
        const subSlug = slugify(it.name || "");
            }
        if (subSlug) node.dataset.slug = subSlug;
            function hide() {
        if (it.level) node.dataset.level = it.level;
              tip.setAttribute("aria-hidden", "true");
        if (it.desc) node.dataset.desc = it.desc;
              tip.style.opacity = "0";
        if (it.descPt) node.dataset.descPt = it.descPt;
              tip.style.left = "-9999px";
        if (it.descEn) node.dataset.descEn = it.descEn;
              tip.style.top = "-9999px";
        if (it.descEs) node.dataset.descEs = it.descEs;
            }
        if (it.descPl) node.dataset.descPl = it.descPl;
            window.__globalSkillTooltip = {
        if (it.video) node.dataset.video = it.video;
              show,
        if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
              hide,
        if (it.subattrs) node.dataset.subattrs = JSON.stringify(it.subattrs);
              measureAndPos,
        if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
              lockUntil: lockUntilRef,
        if (it.back) node.dataset.back = it.back;
            };
        if (
            Array.from(
          it.weapon &&
              document.querySelectorAll(".icon-bar .skill-icon")
          typeof it.weapon === "object" &&
            ).forEach((icon) => {
          Object.keys(it.weapon).length > 0
              if (
        ) {
                icon.dataset.weaponToggle === "1" ||
          try {
                icon.classList.contains("weapon-bar-toggle")
            node.dataset.weapon = JSON.stringify(it.weapon);
              )
          } catch (e) {
                return;
            console.error(
              if (icon.dataset.tipwired) return;
               "[Skills] Erro ao serializar weapon de subskill",
              icon.dataset.tipwired = "1";
               it.name,
              const label =
              e
                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 = document.createElement("img");
              const img = icon.querySelector("img");
        img.alt = "";
              if (img) {
        img.src = it.iconURL;
                const imgAlt = img.getAttribute("alt") || "";
        img.decoding = "async";
                const imgTitle = img.getAttribute("title") || "";
        img.loading = "lazy";
                if (!label && (imgAlt || imgTitle))
        img.width = 50;
                  icon.setAttribute("aria-label", imgAlt || imgTitle);
        img.height = 50;
                img.setAttribute("alt", "");
        node.appendChild(img);
                if (img.hasAttribute("title")) img.removeAttribute("title");
        fragment.appendChild(node);
              }
      });
              icon.addEventListener("mouseenter", () => show(icon, label));
      const templateClone = fragment.cloneNode(true);
              icon.addEventListener("mousemove", () => {
      iconsBar.innerHTML = "";
                if (performance.now() >= lockUntilRef.value)
      iconsBar.appendChild(fragment);
                  measureAndPos(icon);
      animateIconsBarEntrance();
              });
      wireClicksForCurrentBar();
              icon.addEventListener("click", () => {
      // Remove qualquer toggle antigo que possa aparecer
                lockUntilRef.value = performance.now() + 240;
      const oldToggle3 = iconsBar.querySelector(".weapon-bar-toggle");
                measureAndPos(icon);
      if (oldToggle3) oldToggle3.remove();
               });
      // Reaplica classes de weapon após renderizar subskills
               icon.addEventListener("mouseleave", hide);
      reapplyWeaponClassesToBar();
             });
      const b2 = ensureBackButton();
            Array.from(
      if (b2) b2.classList.add("peek");
              document.querySelectorAll(".subskills-rail .subicon")
      // Atualiza a posição do botão back após subskills serem renderizadas
            ).forEach((sub) => {
      requestAnimationFrame(() => {
              if (sub.dataset.tipwired) return;
        const backWrap = document.querySelector(".skills-back-wrapper");
              sub.dataset.tipwired = "1";
        if (backWrap && backWrap.style.display !== "none") {
              const label =
          const rail = iconsBar.closest(".top-rail.skills");
                sub.getAttribute("title") ||
          const wrap = rail ? rail.parentElement : null;
                sub.getAttribute("aria-label") ||
          if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
                "";
             const railRect = rail.getBoundingClientRect();
              if (label && !sub.hasAttribute("aria-label"))
             const wrapRect = wrap.getBoundingClientRect();
                sub.setAttribute("aria-label", label);
            const railLeft = railRect.left - wrapRect.left;
              if (sub.hasAttribute("title")) sub.removeAttribute("title");
            // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
              sub.addEventListener("mouseenter", () => show(sub, label));
            backWrap.style.left = railLeft + "px";
              sub.addEventListener("mousemove", () => {
          }
                if (performance.now() >= lockUntilRef.value) measureAndPos(sub);
        }
              });
      });
              sub.addEventListener("click", () => {
      if (cacheKey) {
                lockUntilRef.value = performance.now() + 240;
         subBarTemplateCache.set(cacheKey, {
                measureAndPos(sub);
          template: templateClone,
              });
          lang: langKey,
              sub.addEventListener("mouseleave", hide);
        });
            });
      }
            window.addEventListener(
    }
              "scroll",
    window.addEventListener("gla:langChanged", () => {
              () => {
      subBarTemplateCache.clear();
                const visible = document.querySelector(
      const skillsRoot = document.getElementById("skills");
                  '.skill-tooltip[aria-hidden="false"]'
      const i18nMap = skillsRoot
                );
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
                if (!visible) return;
        : {};
                const target =
      const lang = getLangKey();
                  document.querySelector(".subskills-rail .subicon:hover") ||
      Array.from(iconsBar.querySelectorAll(".skill-icon")).forEach((icon) => {
                  document.querySelector(".subskills-rail .subicon.active") ||
        const pack = {
                  document.querySelector(".icon-bar .skill-icon:hover") ||
          pt: icon.dataset.descPt || "",
                  document.querySelector(".icon-bar .skill-icon.active");
          en: icon.dataset.descEn || "",
                measureAndPos(target);
          es: icon.dataset.descEs || "",
              },
          pl: icon.dataset.descPl || "",
              true
        };
             );
        const chosen = (
             window.addEventListener("resize", () => {
          pack[lang] ||
              const target =
          pack.pt ||
                document.querySelector(".subskills-rail .subicon:hover") ||
          pack.en ||
                document.querySelector(".subskills-rail .subicon.active") ||
          pack.es ||
                document.querySelector(".icon-bar .skill-icon:hover") ||
          pack.pl ||
                document.querySelector(".icon-bar .skill-icon.active");
          icon.dataset.desc ||
              measureAndPos(target);
          ""
            });
        ).trim();
          })();
        if (chosen) icon.dataset.desc = chosen;
         },
      });
        { timeout: 2000 }
      barStack.forEach((frame) => {
      );
        (frame.items || []).forEach((it) => {
    } else {
          const pack = {
      // Fallback para navegadores sem requestIdleCallback
             pt: it.descPt,
      setTimeout(() => {
             en: it.descEn,
        (function initSkillTooltip() {
            es: it.descEs,
          if (document.querySelector(".skill-tooltip")) return;
            pl: it.descPl,
          const tip = document.createElement("div");
          tip.className = "skill-tooltip";
          tip.setAttribute("role", "tooltip");
          tip.setAttribute("aria-hidden", "true");
          document.body.appendChild(tip);
          window.__globalSkillTooltip = {
            show: (el, text) => {
              if (!el || !text) return;
              tip.textContent = text;
              tip.setAttribute("aria-hidden", "false");
              measureAndPos(el);
            },
            hide: () => {
              tip.setAttribute("aria-hidden", "true");
              tip.style.left = "-9999px";
              tip.style.top = "-9999px";
            },
            measureAndPos: (el) => {
              if (!el || tip.getAttribute("aria-hidden") === "true") return;
              const rect = el.getBoundingClientRect();
              const tipRect = tip.getBoundingClientRect();
              let left = rect.left + rect.width / 2 - tipRect.width / 2;
              let top = rect.top - tipRect.height - 8;
              if (left < 8) left = 8;
              if (left + tipRect.width > window.innerWidth - 8)
                left = window.innerWidth - tipRect.width - 8;
              if (top < 8) top = rect.bottom + 8;
              tip.style.left = left + "px";
              tip.style.top = top + "px";
             },
             lockUntil: { value: 0 },
           };
           };
           const chosen =
           const { measureAndPos } = window.__globalSkillTooltip;
            pack[lang] ||
          window.addEventListener(
            pack.pt ||
             "scroll",
             pack.en ||
             () => {
             pack.es ||
              const visible = document.querySelector(
            pack.pl ||
                '.skill-tooltip[aria-hidden="false"]'
            it.desc ||
              );
            "";
              if (!visible) return;
          it.desc = chosen;
              const target =
        });
                document.querySelector(".subskills-rail .subicon:hover") ||
      });
                document.querySelector(".subskills-rail .subicon.active") ||
      if (descBox) {
                document.querySelector(".icon-bar .skill-icon:hover") ||
        applyFlagTooltips(descBox);
                document.querySelector(".icon-bar .skill-icon.active");
      }
              measureAndPos(target);
      const activeIcon = window.__lastActiveSkillIcon;
            },
      if (activeIcon && activeIcon.dataset.weapon) {
            true
        activateSkill(activeIcon, {
          );
          openSubs: false,
          window.addEventListener("resize", () => {
        });
            const target =
      }
              document.querySelector(".subskills-rail .subicon:hover") ||
    });
              document.querySelector(".subskills-rail .subicon.active") ||
    wireClicksForCurrentBar();
              document.querySelector(".icon-bar .skill-icon:hover") ||
 
              document.querySelector(".icon-bar .skill-icon.active");
    // Inicializa o sistema de swap de personagens (genérico)
            measureAndPos(target);
    // Aguarda um pouco para garantir que todos os ícones foram renderizados
          });
    setTimeout(() => {
        })();
      initializeActiveCharacter();
       }, 100);
    }, 100);
 
    const b0 = ensureBackButton();
    if (b0) {
      b0.classList.add("peek");
       b0.style.alignSelf = "stretch";
     }
     }


     // Move inicialização de tooltip para requestIdleCallback (não crítico)
     (function initTabs() {
    if ("requestIdleCallback" in window) {
      const tabs = Array.from(document.querySelectorAll(".tab-btn"));
       requestIdleCallback(
       if (!tabs.length) return;
        () => {
      const contents = Array.from(document.querySelectorAll(".tab-content"));
          (function initSkillTooltip() {
      const characterBox = document.querySelector(".character-box");
            if (document.querySelector(".skill-tooltip")) return;
      let wrapper = characterBox.querySelector(".tabs-height-wrapper");
            const tip = document.createElement("div");
      if (!wrapper) {
            tip.className = "skill-tooltip";
        wrapper = document.createElement("div");
            tip.setAttribute("role", "tooltip");
        wrapper.className = "tabs-height-wrapper";
            tip.setAttribute("aria-hidden", "true");
        contents.forEach((c) => {
            document.body.appendChild(tip);
          wrapper.appendChild(c);
            const lockUntilRef = {
        });
              value: 0,
        const tabsElement = characterBox.querySelector(".character-tabs");
            };
        if (tabsElement && tabsElement.nextSibling) {
            function measureAndPos(el) {
          characterBox.insertBefore(wrapper, tabsElement.nextSibling);
              if (!el || tip.getAttribute("aria-hidden") === "true") return;
        } else {
              tip.style.left = "0px";
          characterBox.appendChild(wrapper);
              tip.style.top = "0px";
        }
              const rect = el.getBoundingClientRect();
      }
              const tr = tip.getBoundingClientRect();
      async function smoothHeightTransition(fromTab, toTab) {
              let left = Math.round(rect.left + (rect.width - tr.width) / 2);
        if (!wrapper) return Promise.resolve();
              left = Math.max(
        const scrollY = window.scrollY;
                8,
        const currentHeight = wrapper.getBoundingClientRect().height;
                Math.min(left, window.innerWidth - tr.width - 8)
        await new Promise((resolve) => {
               );
          const videoContainers = toTab.querySelectorAll(".video-container");
              const coarse =
          const contentCard = toTab.querySelector(".content-card");
                 (window.matchMedia &&
          if (videoContainers.length === 0) {
                  matchMedia("(pointer: coarse)").matches) ||
            requestAnimationFrame(() => {
                window.innerWidth <= 600;
               requestAnimationFrame(() => {
              let top = coarse
                 requestAnimationFrame(() => resolve());
                ? Math.round(rect.bottom + 10)
              });
                : Math.round(rect.top - tr.height - 8);
            });
              if (top < 8) top = Math.round(rect.bottom + 10);
            return;
               tip.style.left = left + "px";
          }
               tip.style.top = top + "px";
          let lastHeight = 0;
          let stableCount = 0;
          const checksNeeded = 3;
          let totalChecks = 0;
          const maxChecks = 15;
          function checkStability() {
            totalChecks++;
            const currentTabHeight = toTab.scrollHeight;
            if (Math.abs(currentTabHeight - lastHeight) < 5) {
               stableCount++;
            } else {
               stableCount = 0;
             }
             }
             function show(el, text) {
             lastHeight = currentTabHeight;
              tip.textContent = text || "";
            if (stableCount >= checksNeeded || totalChecks >= maxChecks) {
               tip.setAttribute("aria-hidden", "false");
               resolve();
               measureAndPos(el);
            } else {
              tip.style.opacity = "1";
               setTimeout(checkStability, 50);
             }
             }
            function hide() {
          }
              tip.setAttribute("aria-hidden", "true");
          setTimeout(checkStability, 50);
              tip.style.opacity = "0";
        });
              tip.style.left = "-9999px";
        const nextHeight = toTab.getBoundingClientRect().height;
              tip.style.top = "-9999px";
        const finalHeight = Math.max(nextHeight, 100);
            }
        if (Math.abs(finalHeight - currentHeight) < 30) {
            window.__globalSkillTooltip = {
          wrapper.style.height = "";
              show,
          return Promise.resolve();
              hide,
        }
              measureAndPos,
        wrapper.style.overflow = "hidden";
              lockUntil: lockUntilRef,
        wrapper.style.height = currentHeight + "px";
            };
        wrapper.offsetHeight;
            Array.from(
        wrapper.style.transition = "height 0.3s cubic-bezier(0.4, 0, 0.2, 1)";
              document.querySelectorAll(".icon-bar .skill-icon")
        requestAnimationFrame(() => {
            ).forEach((icon) => {
          wrapper.style.height = finalHeight + "px";
              if (
        });
                icon.dataset.weaponToggle === "1" ||
        return new Promise((resolve) => {
                icon.classList.contains("weapon-bar-toggle")
          setTimeout(() => {
              )
            wrapper.style.height = "";
                return;
            wrapper.style.transition = "";
              if (icon.dataset.tipwired) return;
            wrapper.style.overflow = "";
              icon.dataset.tipwired = "1";
            resolve();
              const label =
          }, 320);
                icon.dataset.nome || icon.dataset.name || icon.title || "";
        });
              if (label && !icon.hasAttribute("aria-label"))
      }
                icon.setAttribute("aria-label", label);
      tabs.forEach((btn) => {
              if (icon.hasAttribute("title")) icon.removeAttribute("title");
        if (btn.dataset.wiredTab) return;
              const img = icon.querySelector("img");
        btn.dataset.wiredTab = "1";
              if (img) {
        btn.addEventListener("click", () => {
                const imgAlt = img.getAttribute("alt") || "";
          const target = btn.getAttribute("data-tab");
                const imgTitle = img.getAttribute("title") || "";
          const currentActive = contents.find((c) =>
                if (!label && (imgAlt || imgTitle))
            c.classList.contains("active")
                  icon.setAttribute("aria-label", imgAlt || imgTitle);
          );
                 img.setAttribute("alt", "");
          const nextActive = contents.find((c) => c.id === target);
                 if (img.hasAttribute("title")) img.removeAttribute("title");
          if (currentActive === nextActive) return;
          document.body.classList.add("transitioning-tabs");
          if (currentActive) {
            currentActive.style.opacity = "0";
            currentActive.style.transform = "translateY(-8px)";
          }
          setTimeout(async () => {
            contents.forEach((c) => {
              if (c !== nextActive) {
                 c.style.display = "none";
                 c.classList.remove("active");
               }
               }
              icon.addEventListener("mouseenter", () => show(icon, label));
              icon.addEventListener("mousemove", () => {
                if (performance.now() >= lockUntilRef.value)
                  measureAndPos(icon);
              });
              icon.addEventListener("click", () => {
                lockUntilRef.value = performance.now() + 240;
                measureAndPos(icon);
              });
              icon.addEventListener("mouseleave", hide);
             });
             });
             Array.from(
             tabs.forEach((b) => b.classList.toggle("active", b === btn));
              document.querySelectorAll(".subskills-rail .subicon")
             if (nextActive) {
             ).forEach((sub) => {
               nextActive.classList.add("active");
               if (sub.dataset.tipwired) return;
               nextActive.style.display = "block";
               sub.dataset.tipwired = "1";
               nextActive.style.opacity = "0";
               const label =
               nextActive.style.visibility = "hidden";
                sub.getAttribute("title") ||
               nextActive.offsetHeight;
                sub.getAttribute("aria-label") ||
               try {
                "";
                 if (target === "skills") {
               if (label && !sub.hasAttribute("aria-label"))
                  const tabEl = document.getElementById(target);
                sub.setAttribute("aria-label", label);
                  if (tabEl) {
               if (sub.hasAttribute("title")) sub.removeAttribute("title");
                    const allIcons = tabEl.querySelectorAll(
               sub.addEventListener("mouseenter", () => show(sub, label));
                      ".icon-bar .skill-icon"
              sub.addEventListener("mousemove", () => {
                    );
                 if (performance.now() >= lockUntilRef.value) measureAndPos(sub);
                    const activeIcon = tabEl.querySelector(
              });
                      ".icon-bar .skill-icon.active"
              sub.addEventListener("click", () => {
                    );
                lockUntilRef.value = performance.now() + 240;
                    const firstIcon = tabEl.querySelector(
                measureAndPos(sub);
                      ".icon-bar .skill-icon"
              });
                    );
              sub.addEventListener("mouseleave", hide);
                    const isFormSwitch = (el) =>
            });
                      el?.dataset?.formSwitch === "true" ||
            window.addEventListener(
                      el?.getAttribute?.("data-form-switch") === "true";
              "scroll",
                    let toClick = activeIcon && !isFormSwitch(activeIcon)
              () => {
                      ? activeIcon
                const visible = document.querySelector(
                      : null;
                  '.skill-tooltip[aria-hidden="false"]'
                    if (!toClick)
                );
                      for (const icon of allIcons || [])
                if (!visible) return;
                        if (!isFormSwitch(icon)) {
                const target =
                          toClick = icon;
                  document.querySelector(".subskills-rail .subicon:hover") ||
                          break;
                  document.querySelector(".subskills-rail .subicon.active") ||
                        }
                  document.querySelector(".icon-bar .skill-icon:hover") ||
                    if (!toClick) toClick = firstIcon;
                  document.querySelector(".icon-bar .skill-icon.active");
                    if (toClick) {
                measureAndPos(target);
                      const had = document.body.dataset.suppressSkillPlay;
              },
                      document.body.dataset.suppressSkillPlay = "1";
              true
                      toClick.click();
            );
                      if (had) document.body.dataset.suppressSkillPlay = had;
            window.addEventListener("resize", () => {
                    }
              const target =
                  }
                document.querySelector(".subskills-rail .subicon:hover") ||
                }
                document.querySelector(".subskills-rail .subicon.active") ||
               } catch (e) { }
                document.querySelector(".icon-bar .skill-icon:hover") ||
             }
                document.querySelector(".icon-bar .skill-icon.active");
            if (currentActive && nextActive) {
               measureAndPos(target);
              await smoothHeightTransition(currentActive, nextActive);
             });
            }
          })();
            if (nextActive) {
        },
              nextActive.style.visibility = "";
        { timeout: 2000 }
              nextActive.style.transform = "translateY(12px)";
      );
              requestAnimationFrame(() => {
    } else {
                nextActive.style.opacity = "1";
      // Fallback para navegadores sem requestIdleCallback
                nextActive.style.transform = "translateY(0)";
      setTimeout(() => {
                setTimeout(() => {
        (function initSkillTooltip() {
                  nextActive.style.opacity = "";
          if (document.querySelector(".skill-tooltip")) return;
                  nextActive.style.transform = "";
          const tip = document.createElement("div");
                  document.body.classList.remove("transitioning-tabs");
          tip.className = "skill-tooltip";
                  try {
          tip.setAttribute("role", "tooltip");
                    delete document.body.dataset.suppressSkillPlay;
          tip.setAttribute("aria-hidden", "true");
                  } catch { }
          document.body.appendChild(tip);
                }, 300);
           window.__globalSkillTooltip = {
              });
            show: (el, text) => {
            }
              if (!el || !text) return;
           }, 120);
              tip.textContent = text;
          setTimeout(() => {
              tip.setAttribute("aria-hidden", "false");
            syncDescHeight();
               measureAndPos(el);
            if (target === "skins") {
            },
               videosCache.forEach((v) => {
            hide: () => {
                try {
              tip.setAttribute("aria-hidden", "true");
                  v.pause();
              tip.style.left = "-9999px";
                } catch (e) { }
               tip.style.top = "-9999px";
                v.style.display = "none";
            },
               });
            measureAndPos: (el) => {
              const vb = getVideoBox();
               if (!el || tip.getAttribute("aria-hidden") === "true") return;
               if (vb) {
              const rect = el.getBoundingClientRect();
                vb.querySelectorAll("video.skill-video").forEach((v) => {
              const tipRect = tip.getBoundingClientRect();
                  try {
              let left = rect.left + rect.width / 2 - tipRect.width / 2;
                    v.pause();
              let top = rect.top - tipRect.height - 8;
                  } catch (e) { }
               if (left < 8) left = 8;
                  v.style.display = "none";
               if (left + tipRect.width > window.innerWidth - 8)
                });
                left = window.innerWidth - tipRect.width - 8;
               }
               if (top < 8) top = rect.bottom + 8;
               if (window.__subskills) window.__subskills.hideAll?.(getVideoBox());
               tip.style.left = left + "px";
              const placeholder = getVideoBox()?.querySelector(".video-placeholder");
              tip.style.top = top + "px";
               const vb2 = getVideoBox();
            },
               if (vb2 && placeholder) {
             lockUntil: { value: 0 },
                placeholder.style.display = "none";
          };
                placeholder.classList.add("fade-out");
          const { measureAndPos } = window.__globalSkillTooltip;
              }
          window.addEventListener(
             } else {
            "scroll",
               const activeIcon = document.querySelector(
            () => {
                 ".icon-bar .skill-icon.active"
               const visible = document.querySelector(
                 '.skill-tooltip[aria-hidden="false"]'
               );
               );
               if (!visible) return;
               const isFormSwitch = (el) =>
              const target =
                 el?.dataset?.formSwitch === "true" ||
                 document.querySelector(".subskills-rail .subicon:hover") ||
                 el?.getAttribute?.("data-form-switch") === "true";
                 document.querySelector(".subskills-rail .subicon.active") ||
              if (activeIcon && !isFormSwitch(activeIcon)) activeIcon.click();
                 document.querySelector(".icon-bar .skill-icon:hover") ||
              else {
                 document.querySelector(".icon-bar .skill-icon.active");
                 const first = document.querySelector(
               measureAndPos(target);
                  ".icon-bar .skill-icon:not([data-form-switch='true'])"
            },
                );
            true
                 if (first) first.click();
          );
               }
          window.addEventListener("resize", () => {
            }
            const target =
          }, 450);
              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");
    (function initSkinsArrows() {
            measureAndPos(target);
      const carousel = $(".skins-carousel");
          });
      const wrapper = $(".skins-carousel-wrapper");
        })();
      const left = $(".skins-arrow.left");
       }, 100);
      const right = $(".skins-arrow.right");
    }
      if (!carousel || !left || !right || !wrapper) return;
 
      if (wrapper.dataset.wired) return;
    (function initTabs() {
      wrapper.dataset.wired = "1";
      const tabs = Array.from(document.querySelectorAll(".tab-btn"));
      const scrollAmt = () => Math.round(carousel.clientWidth * 0.6);
      if (!tabs.length) return;
       function setState() {
      const contents = Array.from(document.querySelectorAll(".tab-content"));
        const max = carousel.scrollWidth - carousel.clientWidth;
      const characterBox = document.querySelector(".character-box");
        const x = carousel.scrollLeft;
      let wrapper = characterBox.querySelector(".tabs-height-wrapper");
        const hasLeft = x > 5,
      if (!wrapper) {
          hasRight = x < max - 5;
         wrapper = document.createElement("div");
        left.style.display = hasLeft ? "inline-block" : "none";
         wrapper.className = "tabs-height-wrapper";
        right.style.display = hasRight ? "inline-block" : "none";
         contents.forEach((c) => {
        wrapper.classList.toggle("has-left", hasLeft);
          wrapper.appendChild(c);
         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",
         });
         });
        const tabsElement = characterBox.querySelector(".character-tabs");
        if (tabsElement && tabsElement.nextSibling) {
          characterBox.insertBefore(wrapper, tabsElement.nextSibling);
        } else {
          characterBox.appendChild(wrapper);
        }
       }
       }
       async function smoothHeightTransition(fromTab, toTab) {
       left.addEventListener("click", () => go(-1));
        if (!wrapper) return Promise.resolve();
      right.addEventListener("click", () => go(1));
        const scrollY = window.scrollY;
      carousel.addEventListener("scroll", setState);
        const currentHeight = wrapper.getBoundingClientRect().height;
      new ResizeObserver(setState).observe(carousel);
        await new Promise((resolve) => {
      setState();
          const videoContainers = toTab.querySelectorAll(".video-container");
    })();
          const contentCard = toTab.querySelector(".content-card");
    function renderAttributes(str) {
          if (videoContainers.length === 0) {
      const skillsRoot = document.getElementById("skills");
            requestAnimationFrame(() => {
      const i18nMap = skillsRoot
              requestAnimationFrame(() => {
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
                requestAnimationFrame(() => resolve());
        : {};
              });
      const langRaw = (
            });
        document.documentElement.lang ||
            return;
        skillsRoot?.dataset.i18nDefault ||
           }
        "pt"
          let lastHeight = 0;
      ).toLowerCase();
           let stableCount = 0;
      const langKey = i18nMap[langRaw]
          const checksNeeded = 3;
        ? langRaw
          let totalChecks = 0;
        : i18nMap[langRaw.split("-")[0]]
          const maxChecks = 15;
           ? langRaw.split("-")[0]
          function checkStability() {
           : "pt";
            totalChecks++;
      const L = i18nMap[langKey] ||
            const currentTabHeight = toTab.scrollHeight;
        i18nMap.pt || {
            if (Math.abs(currentTabHeight - lastHeight) < 5) {
        cooldown: "Recarga",
              stableCount++;
        energy_gain: "Ganho de energia",
            } else {
        energy_cost: "Custo de energia",
              stableCount = 0;
        power: "Poder",
            }
        power_pvp: "Poder PvP",
            lastHeight = currentTabHeight;
        level: "Nível",
            if (stableCount >= checksNeeded || totalChecks >= maxChecks) {
      };
              resolve();
      const vals = (str || "").split(",").map((v) => v.trim());
            } else {
      // Processa valores, tratando strings vazias e "-" como NaN
              setTimeout(checkStability, 50);
      // IMPORTANTE: ordem fixa é [powerpve, powerpvp, energy, cooldown]
            }
      const parseValue = (val) => {
          }
        if (!val || val === "" || val === "-") return NaN;
          setTimeout(checkStability, 50);
        const parsed = parseFloat(val);
        });
        return isNaN(parsed) ? NaN : parsed;
        const nextHeight = toTab.getBoundingClientRect().height;
      };
        const finalHeight = Math.max(nextHeight, 100);
      const pve = parseValue(vals[0]);
        if (Math.abs(finalHeight - currentHeight) < 30) {
      const pvp = parseValue(vals[1]);
           wrapper.style.height = "";
      const ene = parseValue(vals[2]);
           return Promise.resolve();
      const cd = parseValue(vals[3]);
        }
      // Debug: log se houver problema na ordem
        wrapper.style.overflow = "hidden";
      if (str && str.includes(",") && !isNaN(cd) && !isNaN(pvp) && cd === pvp) {
        wrapper.style.height = currentHeight + "px";
        console.warn(
        wrapper.offsetHeight;
           "[Skills] renderAttributes: possível problema na ordem dos atributos",
        wrapper.style.transition = "height 0.3s cubic-bezier(0.4, 0, 0.2, 1)";
           { str, vals, pve, pvp, ene, cd }
        requestAnimationFrame(() => {
        );
          wrapper.style.height = finalHeight + "px";
      }
        });
      const rows = [];
        return new Promise((resolve) => {
      // Ordem de exibição: cooldown, energy, power, power_pvp
          setTimeout(() => {
      if (!isNaN(cd)) rows.push([L.cooldown, cd]);
            wrapper.style.height = "";
      if (!isNaN(ene) && ene !== 0) {
            wrapper.style.transition = "";
        const label = ene > 0 ? L.energy_gain : L.energy_cost;
            wrapper.style.overflow = "";
        rows.push([label, Math.abs(ene)]);
            resolve();
          }, 320);
        });
       }
       }
       tabs.forEach((btn) => {
       if (!isNaN(pve)) rows.push([L.power, pve]);
        if (btn.dataset.wiredTab) return;
      if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]);
         btn.dataset.wiredTab = "1";
      // Debug: log se houver valores suspeitos (desabilitado para performance)
         btn.addEventListener("click", () => {
      // if (str && str.includes(',')) {
          const target = btn.getAttribute("data-tab");
      //    console.log('[Skills] renderAttributes processed', {
          const currentActive = contents.find((c) =>
      //        str,
            c.classList.contains("active")
      //        vals: vals.slice(0, 4),
          );
      //        parsed: { pve, pvp, ene, cd },
          const nextActive = contents.find((c) => c.id === target);
      //        rows: rows.map(r => r[0])
          if (currentActive === nextActive) return;
      //    });
          document.body.classList.add("transitioning-tabs");
      // }
           if (currentActive) {
      if (!rows.length) return "";
             currentActive.style.opacity = "0";
      const html = rows
             currentActive.style.transform = "translateY(-8px)";
         .map(
          ([rowLabel, rowValue]) =>
            `<div class="attr-row"><span class="attr-label">${rowLabel}</span><span class="attr-value">${rowValue}</span></div>`
        )
         .join("");
      return `<div class="attr-list">${html}</div>`;
    }
    function syncDescHeight() { }
    window.addEventListener("resize", syncDescHeight);
    const vbInit = getVideoBox();
    if (vbInit) new ResizeObserver(syncDescHeight).observe(vbInit);
    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) {
      const scrollWrapper =
        iconsBar.closest(".icon-scroll-x") || iconsBar.parentElement;
      const scrollContainer =
        scrollWrapper && scrollWrapper.classList.contains("icon-scroll-x")
          ? scrollWrapper
          : iconsBar;
      addOnce(
        scrollContainer,
        "wheel",
        (e) => {
           if (e.deltaY) {
             e.preventDefault();
             scrollContainer.scrollLeft += e.deltaY;
           }
           }
          setTimeout(async () => {
        },
             contents.forEach((c) => {
        { passive: false }
               if (c !== nextActive) {
      ); // passive: false necessário porque usamos preventDefault()
                c.style.display = "none";
    }
                c.classList.remove("active");
    (function maybeInjectFormSkillsOnLoad() {
      try {
        const skillsRoot = document.getElementById("skills");
        if (skillsRoot && iconsBar && iconItems.length > 0) {
          const formsJSON = skillsRoot.dataset.forms || "{}";
          if (formsJSON && formsJSON !== "{}") {
             formsData = JSON.parse(formsJSON);
            const firstIcon = iconItems[0];
            const isFormSwitch =
              firstIcon?.dataset?.formSwitch === "true" ||
              firstIcon?.getAttribute?.("data-form-switch") === "true";
            if (
              formsData &&
              Object.keys(formsData).length > 0 &&
              isFormSwitch
            ) {
              const firstFormName = Object.keys(formsData)[0];
              const firstFormData = formsData[firstFormName];
               if (firstFormData) {
                updateSkillsBarForForm(firstFormName, firstFormData, firstIcon);
                setTimeout(() => {
                  wireClicksForCurrentBar();
                  const freshIcons = Array.from(
                    iconsBar.querySelectorAll(".skill-icon")
                  );
                  const isFS = (el) =>
                    el?.dataset?.formSwitch === "true" ||
                    el?.getAttribute?.("data-form-switch") === "true";
                  const firstNonSwitch = freshIcons.find((el) => !isFS(el));
                  if (firstNonSwitch)
                    activateSkill(firstNonSwitch, { openSubs: false });
                }, 200);
                return;
               }
               }
            });
            tabs.forEach((b) => b.classList.toggle("active", b === btn));
            if (nextActive) {
              nextActive.classList.add("active");
              nextActive.style.display = "block";
              nextActive.style.opacity = "0";
              nextActive.style.visibility = "hidden";
              nextActive.offsetHeight;
              try {
                if (target === "skills") {
                  const tabEl = document.getElementById(target);
                  if (tabEl) {
                    const activeIcon = tabEl.querySelector(
                      ".icon-bar .skill-icon.active"
                    );
                    const firstIcon = tabEl.querySelector(
                      ".icon-bar .skill-icon"
                    );
                    const toClick = activeIcon || firstIcon;
                    if (toClick) {
                      const had = document.body.dataset.suppressSkillPlay;
                      document.body.dataset.suppressSkillPlay = "1";
                      toClick.click();
                      if (had) document.body.dataset.suppressSkillPlay = had;
                    }
                  }
                }
              } catch (e) { }
             }
             }
            if (currentActive && nextActive) {
              await smoothHeightTransition(currentActive, nextActive);
            }
            if (nextActive) {
              nextActive.style.visibility = "";
              nextActive.style.transform = "translateY(12px)";
              requestAnimationFrame(() => {
                nextActive.style.opacity = "1";
                nextActive.style.transform = "translateY(0)";
                setTimeout(() => {
                  nextActive.style.opacity = "";
                  nextActive.style.transform = "";
                  document.body.classList.remove("transitioning-tabs");
                  try {
                    delete document.body.dataset.suppressSkillPlay;
                  } catch { }
                }, 300);
              });
            }
          }, 120);
          setTimeout(() => {
            syncDescHeight();
            if (target === "skins") {
              videosCache.forEach((v) => {
                try {
                  v.pause();
                } catch (e) { }
                v.style.display = "none";
              });
              if (videoBox) {
                videoBox.querySelectorAll("video.skill-video").forEach((v) => {
                  try {
                    v.pause();
                  } catch (e) { }
                  v.style.display = "none";
                });
              }
              if (window.__subskills) window.__subskills.hideAll?.(videoBox);
              const placeholder = videoBox?.querySelector(".video-placeholder");
              if (videoBox && placeholder) {
                placeholder.style.display = "none";
                placeholder.classList.add("fade-out");
              }
            } else {
              const activeIcon = document.querySelector(
                ".icon-bar .skill-icon.active"
              );
              if (activeIcon) activeIcon.click();
            }
          }, 450);
        });
      });
    })();
    (function initSkinsArrows() {
      const carousel = $(".skins-carousel");
      const wrapper = $(".skins-carousel-wrapper");
      const left = $(".skins-arrow.left");
      const right = $(".skins-arrow.right");
      if (!carousel || !left || !right || !wrapper) return;
      if (wrapper.dataset.wired) return;
      wrapper.dataset.wired = "1";
      const scrollAmt = () => Math.round(carousel.clientWidth * 0.6);
      function setState() {
        const max = carousel.scrollWidth - carousel.clientWidth;
        const x = carousel.scrollLeft;
        const hasLeft = x > 5,
          hasRight = x < max - 5;
        left.style.display = hasLeft ? "inline-block" : "none";
        right.style.display = hasRight ? "inline-block" : "none";
        wrapper.classList.toggle("has-left", hasLeft);
        wrapper.classList.toggle("has-right", hasRight);
        carousel.style.justifyContent = !hasLeft && !hasRight ? "center" : "";
      }
      function go(dir) {
        const max = carousel.scrollWidth - carousel.clientWidth;
        const next =
          dir < 0
            ? Math.max(0, carousel.scrollLeft - scrollAmt())
            : Math.min(max, carousel.scrollLeft + scrollAmt());
        carousel.scrollTo({
          left: next,
          behavior: "smooth",
        });
      }
      left.addEventListener("click", () => go(-1));
      right.addEventListener("click", () => go(1));
      carousel.addEventListener("scroll", setState);
      new ResizeObserver(setState).observe(carousel);
      setState();
    })();
    function renderAttributes(str) {
      const skillsRoot = document.getElementById("skills");
      const i18nMap = skillsRoot
        ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
        : {};
      const langRaw = (
        document.documentElement.lang ||
        skillsRoot?.dataset.i18nDefault ||
        "pt"
      ).toLowerCase();
      const langKey = i18nMap[langRaw]
        ? langRaw
        : i18nMap[langRaw.split("-")[0]]
          ? langRaw.split("-")[0]
          : "pt";
      const L = i18nMap[langKey] ||
        i18nMap.pt || {
        cooldown: "Recarga",
        energy_gain: "Ganho de energia",
        energy_cost: "Custo de energia",
        power: "Poder",
        power_pvp: "Poder PvP",
        level: "Nível",
      };
      const vals = (str || "").split(",").map((v) => v.trim());
      // Processa valores, tratando strings vazias e "-" como NaN
      // IMPORTANTE: ordem fixa é [powerpve, powerpvp, energy, cooldown]
      const parseValue = (val) => {
        if (!val || val === "" || val === "-") return NaN;
        const parsed = parseFloat(val);
        return isNaN(parsed) ? NaN : parsed;
      };
      const pve = parseValue(vals[0]);
      const pvp = parseValue(vals[1]);
      const ene = parseValue(vals[2]);
      const cd = parseValue(vals[3]);
      // Debug: log se houver problema na ordem
      if (str && str.includes(",") && !isNaN(cd) && !isNaN(pvp) && cd === pvp) {
        console.warn(
          "[Skills] renderAttributes: possível problema na ordem dos atributos",
          { str, vals, pve, pvp, ene, cd }
        );
      }
      const rows = [];
      // Ordem de exibição: cooldown, energy, power, power_pvp
      if (!isNaN(cd)) rows.push([L.cooldown, cd]);
      if (!isNaN(ene) && ene !== 0) {
        const label = ene > 0 ? L.energy_gain : L.energy_cost;
        rows.push([label, Math.abs(ene)]);
      }
      if (!isNaN(pve)) rows.push([L.power, pve]);
      if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]);
      // Debug: log se houver valores suspeitos (desabilitado para performance)
      // if (str && str.includes(',')) {
      //    console.log('[Skills] renderAttributes processed', {
      //        str,
      //        vals: vals.slice(0, 4),
      //        parsed: { pve, pvp, ene, cd },
      //        rows: rows.map(r => r[0])
      //    });
      // }
      if (!rows.length) return "";
      const html = rows
        .map(
          ([rowLabel, rowValue]) =>
            `<div class="attr-row"><span class="attr-label">${rowLabel}</span><span class="attr-value">${rowValue}</span></div>`
        )
        .join("");
      return `<div class="attr-list">${html}</div>`;
    }
    function syncDescHeight() { }
    window.addEventListener("resize", syncDescHeight);
    if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox);
    iconItems.forEach((el) => {
      const wired = !!el.dataset._sync_wired;
      if (wired) return;
      el.dataset._sync_wired = "1";
      el.addEventListener("click", () => {
        Promise.resolve().then(syncDescHeight);
      });
    });
    if (iconsBar) {
      const scrollWrapper =
        iconsBar.closest(".icon-scroll-x") || iconsBar.parentElement;
      const scrollContainer =
        scrollWrapper && scrollWrapper.classList.contains("icon-scroll-x")
          ? scrollWrapper
          : iconsBar;
      addOnce(
        scrollContainer,
        "wheel",
        (e) => {
          if (e.deltaY) {
            e.preventDefault();
            scrollContainer.scrollLeft += e.deltaY;
           }
           }
         },
         }
        { passive: false }
      } catch (e) {
      ); // passive: false necessário porque usamos preventDefault()
        console.error("[Forms] init inject error", e);
    }
      }
    wireClicksForCurrentBar();
      wireClicksForCurrentBar();
    if (iconItems.length) {
      if (iconItems.length) {
      const first = iconItems[0];
        const isFormSwitch = (el) =>
      if (first) {
          el?.dataset?.formSwitch === "true" ||
        activateSkill(first, {
          el?.getAttribute?.("data-form-switch") === "true";
          openSubs: false,
        let first = null;
        });
        for (const el of iconItems) {
          if (!isFormSwitch(el)) {
            first = el;
            break;
          }
        }
        if (!first) first = iconItems[0];
        if (first) {
          activateSkill(first, {
            openSubs: false,
          });
        }
       }
       }
     }
     })();


     // Aplica lazy loading em imagens fora do viewport inicial
     // Aplica lazy loading em imagens fora do viewport inicial

Edição atual tal como às 02h37min de 22 de fevereiro de 2026

<script>

 (function () {
   const $ = (s, root = document) => root.querySelector(s);
   const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
   const ensureRemoved = (sel) => {
     Array.from(document.querySelectorAll(sel)).forEach((n) => n.remove());
   };
   const onceFlag = (el, key) => {
     if (!el) return false;
     if (el.dataset[key]) return false;
     el.dataset[key] = "1";
     return true;
   };
   const addOnce = (el, ev, fn, options = {}) => {
     if (!el) return;
     const attr = `data-wired-${ev}`;
     if (el.hasAttribute(attr)) return;
     el.addEventListener(ev, fn, options);
     el.setAttribute(attr, "1");
   };
   const FLAG_ICON_FILES = {
     aggro: "Enemyaggro-icon.png",
     bridge: "Bridgemaker-icon.png",
     wall: "Destroywall-icon.png",
     quickcast: "Quickcast-icon.png",
     wallpass: "Passthroughwall-icon.png",
   };
   const subBarTemplateCache =
     window.__skillSubBarTemplateCache ||
     (window.__skillSubBarTemplateCache = new Map());
   const imagePreloadCache =
     window.__skillImagePreloadCache ||
     (window.__skillImagePreloadCache = new Map());
   const videoPreloadCache =
     window.__skillVideoPreloadCache ||
     (window.__skillVideoPreloadCache = new Set());
   const flagRowCache =
     window.__skillFlagRowCache || (window.__skillFlagRowCache = new Map());
   const flagIconURLCache =
     window.__skillFlagIconURLCache ||
     (window.__skillFlagIconURLCache = new Map());
   // Sistema de múltiplas formas (genérico)
   let currentForm = null; // null = primeira forma, depois nome da forma atual
   let formsData = {};
   let fixedSkills = []; // Skills que sempre aparecem (Change Form, Guard Point, etc.)
   // Sistema genérico de swap de personagens (ex: Buchi & Sham, futuros personagens)
   let activeCharacter = null; // null = personagem padrão, depois nome do personagem ativo (ex: "Buchi", "Sham")
   function showFormTransitionVideo(changeFormIconEl) {
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return;
     // Busca videoBox
     let videoBox = skillsRoot.querySelector(".video-container");
     if (!videoBox) {
       const skillsContainer = skillsRoot.querySelector(".skills-container");
       if (skillsContainer) {
         videoBox = skillsContainer.querySelector(".video-container");
       }
     }
     if (!videoBox) return;
     try {
       // Lê dados de forms para determinar forma atual e próxima
       const formsJSON = skillsRoot.dataset.forms || "{}";
       if (formsJSON && formsJSON !== "{}") {
         const tempFormsData = JSON.parse(formsJSON);
         const formNames = Object.keys(tempFormsData);
         // Determina forma atual e próxima
         const currentIdx = currentForm ? formNames.indexOf(currentForm) : -1;
         const nextIdx = (currentIdx + 1) % formNames.length;
         const nextForm = formNames[nextIdx];
         // Busca vídeo de transição na skill Change Form
         // form_videos[forma_atual] = "video.mp4" (vídeo da transição atual → próxima)
         const formVideosRaw =
           changeFormIconEl.dataset.formVideos ||
           changeFormIconEl.getAttribute("data-form-videos");
         if (formVideosRaw) {
           try {
             const videos = JSON.parse(formVideosRaw);
             const transitionVideo = videos[currentForm] || "";
             if (transitionVideo && transitionVideo.trim() !== "") {
               const videoURL = filePathURL(transitionVideo);
               if (videoURL) {
                 // Busca ou cria elemento de vídeo para esta transição
                 const videoKey = `form_transition:${currentForm}:${nextForm}`;
                 let v = videosCache.get(videoKey);
                 if (!v) {
                   // Cria novo elemento de vídeo
                   v = document.createElement("video");
                   v.className = "skill-video";
                   v.src = videoURL;
                   v.preload = "auto";
                   v.controls = false;
                   v.muted = false;
                   v.loop = false;
                   v.playsInline = true;
                   videoBox.appendChild(v);
                   videosCache.set(videoKey, v);
                 }
                 // Mostra e reproduz o vídeo
                 Array.from(
                   videoBox.querySelectorAll("video.skill-video")
                 ).forEach((vid) => {
                   try {
                     vid.pause();
                   } catch (e) { }
                   vid.style.display = "none";
                 });
                 videoBox.style.display = "block";
                 v.style.display = "block";
                 try {
                   v.currentTime = 0;
                   v.play().catch(() => { });
                 } catch (e) { }
               }
             }
           } catch (e) {
             console.error("[Forms] Erro ao parsear form_videos:", e);
           }
         }
       }
     } catch (e) {
       console.error("[Forms] Erro ao processar vídeo de transição:", e);
     }
   }
   // Sistema genérico de swap de personagens (ex: Buchi & Sham, futuros personagens)
   // Detecta quais personagens estão disponíveis baseado nas skills
   function detectAvailableCharacters() {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return [];
     const characters = new Set();
     Array.from(
       iconBar.querySelectorAll(".skill-icon[data-only-character]")
     ).forEach((icon) => {
       const character = (icon.dataset.onlyCharacter || "").trim();
       if (character) {
         characters.add(character);
       }
     });
     return Array.from(characters);
   }
   // Inicializa o personagem padrão (deve ser chamado quando a página carrega)
   function initializeActiveCharacter() {
     const availableCharacters = detectAvailableCharacters();
     if (availableCharacters.length > 0 && activeCharacter === null) {
       // Inicializa com o primeiro personagem disponível (padrão)
       activeCharacter = availableCharacters[0];
       // Aplica o estado inicial (habilita/desabilita skills)
       applyCharacterSwapState();
     }
   }
   // Aplica o estado de swap nas skills (habilita/desabilita e atualiza vídeos)
   function applyCharacterSwapState() {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return;
     // Atualiza todas as skills na barra
     Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       (icon) => {
         const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
         const hasCharacterVideos =
           icon.dataset.characterVideos &&
           icon.dataset.characterVideos.trim() !== "";
         const baseVideoFile = icon.dataset.videoFile || "";
         const baseVideoURL = icon.dataset.video || "";
         // Salva o vídeo original se ainda não foi salvo (apenas para skills compartilhadas com character_videos)
         if (
           hasCharacterVideos &&
           !onlyCharacter &&
           !icon.dataset.originalVideoFile
         ) {
           icon.dataset.originalVideoFile =
             baseVideoFile || baseVideoURL || "";
         }
         // Desabilita/habilita skills baseado no personagem ativo
         if (onlyCharacter) {
           // Skill específica de um personagem
           if (onlyCharacter === activeCharacter) {
             // Skill do personagem ativo → habilitar
             icon.style.opacity = "1";
             icon.style.filter = "";
             icon.style.pointerEvents = "";
             icon.classList.remove("disabled-skill");
           } else {
             // Skill de outro personagem → desabilitar (escuras)
             icon.style.opacity = "0.3";
             icon.style.filter = "grayscale(100%)";
             icon.style.pointerEvents = "none";
             icon.classList.add("disabled-skill");
           }
         } else {
           // Skill compartilhada → sempre habilitada
           icon.style.opacity = "1";
           icon.style.filter = "";
           icon.style.pointerEvents = "";
           icon.classList.remove("disabled-skill");
           // Atualiza vídeo se houver character_videos e personagem ativo
           if (hasCharacterVideos && activeCharacter) {
             try {
               const characterVideos = JSON.parse(
                 icon.dataset.characterVideos
               );
               const characterVideo = characterVideos[activeCharacter];
               if (characterVideo && characterVideo.trim() !== "") {
                 icon.dataset.videoFile = characterVideo;
                 icon.dataset.video =
                   filePathURL(characterVideo) || characterVideo;
               }
             } catch (e) {
               console.error("[Swap] Erro ao processar character_videos:", e);
             }
           } else if (hasCharacterVideos && activeCharacter === null) {
             // Restaura vídeo original quando volta ao personagem padrão (null)
             const originalVideo = icon.dataset.originalVideoFile || "";
             if (originalVideo) {
               icon.dataset.videoFile = originalVideo;
               icon.dataset.video =
                 filePathURL(originalVideo) || originalVideo;
             }
           }
         }
       }
     );
   }
   // Troca entre personagens (genérico)
   function handleSwapCharacter(swapIconEl) {
     if (!swapIconEl) return;
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return;
     const availableCharacters = detectAvailableCharacters();
     if (availableCharacters.length === 0) {
       // Se não há personagens específicos, não faz nada (não há swap)
       return;
     }
     // Se activeCharacter é null, inicializa com o primeiro personagem disponível
     if (activeCharacter === null) {
       activeCharacter = availableCharacters[0];
     } else {
       // Encontra o índice do personagem atual e avança para o próximo
       const currentIndex = availableCharacters.indexOf(activeCharacter);
       const nextIndex = (currentIndex + 1) % availableCharacters.length;
       activeCharacter = availableCharacters[nextIndex];
     }
     // Aplica o novo estado
     applyCharacterSwapState();
     // Atualiza todas as skills na barra
     Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       (icon) => {
         const onlyCharacter = (icon.dataset.onlyCharacter || "").trim();
         const hasCharacterVideos =
           icon.dataset.characterVideos &&
           icon.dataset.characterVideos.trim() !== "";
         const baseVideoFile = icon.dataset.videoFile || "";
         const baseVideoURL = icon.dataset.video || "";
         // Salva o vídeo original se ainda não foi salvo
         if (hasCharacterVideos && !icon.dataset.originalVideoFile) {
           icon.dataset.originalVideoFile =
             baseVideoFile || baseVideoURL || "";
         }
         // Desabilita/habilita skills baseado no personagem ativo
         if (onlyCharacter) {
           // Skill específica de um personagem
           if (onlyCharacter === activeCharacter) {
             // Skill do personagem ativo → habilitar
             icon.style.opacity = "1";
             icon.style.filter = "";
             icon.style.pointerEvents = "";
             icon.classList.remove("disabled-skill");
           } else {
             // Skill de outro personagem → desabilitar (escuras)
             icon.style.opacity = "0.3";
             icon.style.filter = "grayscale(100%)";
             icon.style.pointerEvents = "none";
             icon.classList.add("disabled-skill");
           }
         } else {
           // Skill compartilhada → sempre habilitada
           icon.style.opacity = "1";
           icon.style.filter = "";
           icon.style.pointerEvents = "";
           icon.classList.remove("disabled-skill");
           // Atualiza vídeo se houver character_videos e personagem ativo
           if (hasCharacterVideos && activeCharacter) {
             try {
               const characterVideos = JSON.parse(
                 icon.dataset.characterVideos
               );
               const characterVideo = characterVideos[activeCharacter];
               if (characterVideo && characterVideo.trim() !== "") {
                 icon.dataset.videoFile = characterVideo;
                 icon.dataset.video =
                   filePathURL(characterVideo) || characterVideo;
               }
             } catch (e) {
               console.error("[Swap] Erro ao processar character_videos:", e);
             }
           } else if (hasCharacterVideos && activeCharacter === null) {
             // Restaura vídeo original quando volta ao personagem padrão (null)
             const originalVideo = icon.dataset.originalVideoFile || "";
             if (originalVideo) {
               icon.dataset.videoFile = originalVideo;
               icon.dataset.video =
                 filePathURL(originalVideo) || originalVideo;
             }
           }
         }
       }
     );
   }
   // Detecta qual forma está atualmente visível no DOM
   function detectCurrentForm() {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar || !formsData || Object.keys(formsData).length === 0)
       return null;
     // Coleta todas as skills de form que estão visíveis no DOM
     const allFormSkillNames = new Set();
     Object.values(formsData).forEach((form) => {
       if (form.order && Array.isArray(form.order)) {
         form.order.forEach((skillName) => allFormSkillNames.add(skillName));
       }
     });
     const visibleFormSkillNames = new Set();
     Array.from(iconBar.querySelectorAll(".skill-icon[data-index]")).forEach(
       (icon) => {
         const name = (icon.dataset.nome || "").trim();
         if (name && allFormSkillNames.has(name)) {
           visibleFormSkillNames.add(name);
         }
       }
     );
     // Compara com cada forma para ver qual corresponde
     for (const [formName, formData] of Object.entries(formsData)) {
       if (formData.order && Array.isArray(formData.order)) {
         const formSkillSet = new Set(formData.order);
         // Verifica se todas as skills desta forma estão visíveis
         let allMatch = true;
         for (const skillName of formData.order) {
           if (!visibleFormSkillNames.has(skillName)) {
             allMatch = false;
             break;
           }
         }
         if (
           allMatch &&
           formData.order.length === visibleFormSkillNames.size
         ) {
           return formName;
         }
       }
     }
     return null;
   }
   function getNextFormName() {
     const formNames = Object.keys(formsData);
     if (formNames.length === 0) return null;
     let cur = currentForm;
     if (cur === null) {
       cur = detectCurrentForm();
     }
     if (!cur && formNames.length > 0) {
       cur = formNames[0];
     }
     let orderedFormNames = [];
     if (cur === "Brain Point" && formNames.length === 3 &&
       formNames.includes("Kung Fu Point") && formNames.includes("Heavy Point")) {
       orderedFormNames = ["Brain Point", "Kung Fu Point", "Heavy Point"];
     } else if (cur === "Kung Fu Point" && formNames.length === 3 &&
       formNames.includes("Heavy Point") && formNames.includes("Brain Point")) {
       orderedFormNames = ["Kung Fu Point", "Heavy Point", "Brain Point"];
     } else if (cur === "Heavy Point" && formNames.length === 3 &&
       formNames.includes("Brain Point") && formNames.includes("Kung Fu Point")) {
       orderedFormNames = ["Heavy Point", "Brain Point", "Kung Fu Point"];
     }
     if (orderedFormNames.length === 0) {
       orderedFormNames = [...formNames].sort();
       if (cur) {
         const idx = orderedFormNames.indexOf(cur);
         if (idx !== -1) {
           orderedFormNames = [
             ...orderedFormNames.slice(idx),
             ...orderedFormNames.slice(0, idx),
           ];
         }
       }
     }
     const idx = orderedFormNames.indexOf(cur);
     if (idx === -1) return null;
     return orderedFormNames[(idx + 1) % orderedFormNames.length];
   }
   function switchForm() {
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return;
     // Lê dados de forms do atributo data-forms
     try {
       const formsJSON = skillsRoot.dataset.forms || "{}";
       if (formsJSON && formsJSON !== "{}") {
         formsData = JSON.parse(formsJSON);
       }
     } catch (e) {
       console.error("[Forms] Erro ao parsear forms:", e);
       return;
     }
     if (!formsData || Object.keys(formsData).length === 0) {
       return; // Não tem forms, não faz nada
     }
     // Identifica skills fixas (sempre presentes)
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar) return;
     // Busca a skill com form_switch dinamicamente (genérico)
     const changeFormIcon = Array.from(
       iconBar.querySelectorAll(".skill-icon[data-index]")
     ).find(
       (icon) =>
         icon.dataset.formSwitch === "true" ||
         icon.getAttribute("data-form-switch") === "true"
     );
     if (changeFormIcon) {
       changeFormIcon.classList.add("active");
     }
     // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
     const allFormSkillNames = new Set();
     Object.values(formsData).forEach((form) => {
       if (form.order && Array.isArray(form.order)) {
         form.order.forEach((skillName) => allFormSkillNames.add(skillName));
       }
     });
     // Skills fixas = todas as skills na barra que não estão em nenhuma forms
     fixedSkills = Array.from(
       iconBar.querySelectorAll(".skill-icon[data-index]")
     )
       .filter((icon) => {
         const name = (icon.dataset.nome || "").trim();
         return name && !allFormSkillNames.has(name);
       })
       .map((icon) => snapshotIconData(icon));
     // Obtém lista de formas disponíveis
     const formNames = Object.keys(formsData);
     if (formNames.length === 0) return;
     if (currentForm === null) {
       currentForm = detectCurrentForm();
       if (!currentForm && formNames.length > 0) {
         currentForm = formNames[0];
       }
     }
     const nextForm = getNextFormName();
     if (!nextForm) return;
     currentForm = nextForm;
     // Atualiza barra de skills (que vai remover o active depois da animação)
     updateSkillsBarForForm(nextForm, formsData[nextForm], changeFormIcon);
   }
   function snapshotIconData(icon) {
     const img = icon.querySelector("img");
     const iconURL = img ? img.src : "";
     const subsRaw = icon.dataset.subs || icon.getAttribute("data-subs") || "";
     let subs = null;
     try {
       subs = subsRaw ? JSON.parse(subsRaw) : null;
     } catch {
       subs = null;
     }
     let flags = null;
     if (icon.dataset.flags) {
       try {
         flags = JSON.parse(icon.dataset.flags);
       } catch (e) { }
     }
     let weapon = null;
     if (icon.dataset.weapon) {
       try {
         weapon = JSON.parse(icon.dataset.weapon);
       } catch (e) { }
     }
     return {
       name: icon.dataset.nome || icon.dataset.name || "",
       index: icon.dataset.index || "",
       level: icon.dataset.level || "",
       desc: icon.dataset.desc || "",
       descPt: icon.dataset.descPt || "",
       descEn: icon.dataset.descEn || "",
       descEs: icon.dataset.descEs || "",
       descPl: icon.dataset.descPl || "",
       attrs: icon.dataset.atr || icon.dataset.attrs || "",
       video: icon.dataset.video || "",
       iconURL,
       iconFile: icon.dataset.iconFile || "",
       subs,
       flags,
       weapon,
       formSwitch: icon.dataset.formSwitch || "",
     };
   }
   function updateSkillsBarForForm(formName, formData, changeFormIconEl) {
     const iconBar = document.querySelector(".icon-bar");
     if (!iconBar || !formData || !formData.skills) return;
     // Determina skills fixas dinamicamente: todas que não estão em nenhuma forms
     const allFormSkillNames = new Set();
     Object.values(formsData).forEach((form) => {
       if (form.order && Array.isArray(form.order)) {
         form.order.forEach((skillName) => allFormSkillNames.add(skillName));
       }
     });
     // Remove skills de forma antigas (que não são fixas) com animação de saída
     const existingIcons = Array.from(
       iconBar.querySelectorAll(".skill-icon[data-index]")
     );
     const iconsToRemove = [];
     existingIcons.forEach((icon) => {
       const name = (icon.dataset.nome || "").trim();
       if (name && allFormSkillNames.has(name)) {
         iconsToRemove.push(icon);
       }
     });
     // Anima saída das skills antigas
     iconsToRemove.forEach((icon) => {
       icon.style.transition = "opacity .15s ease, transform .15s ease";
       icon.style.opacity = "0";
       icon.style.transform = "translateY(-6px)";
     });
     // Encontra a skill com form_switch dinamicamente (genérico)
     const changeFormIcon = Array.from(
       iconBar.querySelectorAll(".skill-icon[data-index]")
     ).find(
       (icon) =>
         icon.dataset.formSwitch === "true" ||
         icon.getAttribute("data-form-switch") === "true"
     );
     // Encontra a próxima skill fixa depois do form_switch dinamicamente
     let nextFixedSkillIcon = null;
     if (changeFormIcon) {
       const allIcons = Array.from(
         iconBar.querySelectorAll(".skill-icon[data-index]")
       ).sort((a, b) => {
         return (
           parseInt(a.dataset.index || "0", 10) -
           parseInt(b.dataset.index || "0", 10)
         );
       });
       const changeFormIndex = allIcons.indexOf(changeFormIcon);
       const allFormSkillNames = new Set();
       Object.values(formsData).forEach((form) => {
         if (form.order && Array.isArray(form.order)) {
           form.order.forEach((skillName) => allFormSkillNames.add(skillName));
         }
       });
       // Procura a próxima skill fixa (que não está em forms)
       for (let i = changeFormIndex + 1; i < allIcons.length; i++) {
         const icon = allIcons[i];
         const name = (icon.dataset.nome || "").trim();
         if (name && !allFormSkillNames.has(name)) {
           nextFixedSkillIcon = icon;
           break;
         }
       }
     }
     if (!changeFormIcon || !nextFixedSkillIcon) {
       console.warn(
         "[Forms] Skill com form_switch ou próxima skill fixa não encontrada"
       );
       return;
     }
     const formSkills = formData.skills || [];
     const formOrder = formData.order || [];
     // Primeira skill da forma (vai ANTES do Change Form)
     const firstSkillName = formOrder[0];
     const firstSkillData = formSkills.find((s) => s.name === firstSkillName);
     // Terceira skill da forma (vai DEPOIS do Change Form, ANTES do Guard Point)
     const thirdSkillName = formOrder[1]; // Segunda na ordem = terceira skill (índice 1)
     const thirdSkillData = formSkills.find((s) => s.name === thirdSkillName);
     // Quinta skill da forma (vai DEPOIS do Guard Point)
     const fifthSkillName = formOrder[2]; // Terceira na ordem = quinta skill (índice 2)
     const fifthSkillData = formSkills.find((s) => s.name === fifthSkillName);
     // Cria fragments para inserir
     const firstFragment = document.createDocumentFragment();
     const thirdFragment = document.createDocumentFragment();
     const fifthFragment = document.createDocumentFragment();
     if (firstSkillData) {
       const iconElement = createSkillIconElement(firstSkillData, 1);
       firstFragment.appendChild(iconElement);
     }
     if (thirdSkillData) {
       const changeFormIndex = parseInt(
         changeFormIcon.dataset.index || "2",
         10
       );
       const iconElement = createSkillIconElement(
         thirdSkillData,
         changeFormIndex + 1
       );
       thirdFragment.appendChild(iconElement);
     }
     if (fifthSkillData) {
       const nextFixedSkillIndex = parseInt(
         nextFixedSkillIcon.dataset.index || "4",
         10
       );
       const iconElement = createSkillIconElement(
         fifthSkillData,
         nextFixedSkillIndex + 1
       );
       fifthFragment.appendChild(iconElement);
     }
     // Remove os ícones antigos do DOM após animação
     setTimeout(() => {
       iconsToRemove.forEach((icon) => {
         if (icon.parentNode) {
           icon.remove();
         }
       });
       // Insere a primeira skill ANTES da skill com form_switch
       if (firstFragment.hasChildNodes()) {
         iconBar.insertBefore(firstFragment, changeFormIcon);
       }
       // Insere a terceira skill DEPOIS da skill com form_switch, ANTES da próxima skill fixa
       if (thirdFragment.hasChildNodes()) {
         iconBar.insertBefore(thirdFragment, nextFixedSkillIcon);
       }
       // Insere a quinta skill DEPOIS da próxima skill fixa
       if (fifthFragment.hasChildNodes()) {
         if (nextFixedSkillIcon.nextSibling) {
           iconBar.insertBefore(fifthFragment, nextFixedSkillIcon.nextSibling);
         } else {
           iconBar.appendChild(fifthFragment);
         }
       }
       // Anima entrada das novas skills (similar a animateIconsBarEntrance)
       const newIconsInBar = Array.from(
         iconBar.querySelectorAll(".skill-icon[data-index]")
       ).filter((icon) => {
         const name = (icon.dataset.nome || "").trim();
         return name && allFormSkillNames.has(name);
       });
       newIconsInBar.forEach((icon, idx) => {
         icon.style.opacity = "0";
         icon.style.transform = "translateY(6px)";
         requestAnimationFrame(() => {
           setTimeout(() => {
             icon.style.transition = "opacity .18s ease, transform .18s ease";
             icon.style.opacity = "1";
             icon.style.transform = "translateY(0)";
           }, idx * 24);
         });
       });
       // Remove slots de descrição das skills de forma antigas e cria novos
       const descBox = document.querySelector(".skills-details .desc-box");
       if (descBox) {
         // Encontra os slots de descrição dos elementos fixos
         const changeFormIndex = parseInt(
           changeFormIcon.dataset.index || "1",
           10
         );
         const nextFixedSkillIndex = parseInt(
           nextFixedSkillIcon.dataset.index || "1",
           10
         );
         const changeFormDescSlot = descBox.querySelector(
           `.skill-desc[data-index="${changeFormIndex}"]`
         );
         const nextFixedSkillDescSlot = descBox.querySelector(
           `.skill-desc[data-index="${nextFixedSkillIndex}"]`
         );
         // Cria slot para primeira skill (antes do Change Form)
         if (firstSkillData && changeFormDescSlot) {
           const descSlot = document.createElement("div");
           descSlot.className = "skill-desc";
           descSlot.setAttribute("data-index", changeFormIndex);
           descBox.insertBefore(descSlot, changeFormDescSlot);
         }
         // Cria slot para terceira skill (depois da skill com form_switch, antes da próxima skill fixa)
         if (thirdSkillData && nextFixedSkillDescSlot) {
           const descSlot = document.createElement("div");
           descSlot.className = "skill-desc";
           descSlot.setAttribute("data-index", nextFixedSkillIndex);
           descBox.insertBefore(descSlot, nextFixedSkillDescSlot);
         }
         // Cria slot para quinta skill (depois da próxima skill fixa)
         if (fifthSkillData && nextFixedSkillDescSlot) {
           const descSlot = document.createElement("div");
           descSlot.className = "skill-desc";
           descSlot.setAttribute("data-index", nextFixedSkillIndex + 1);
           if (nextFixedSkillDescSlot.nextSibling) {
             descBox.insertBefore(
               descSlot,
               nextFixedSkillDescSlot.nextSibling
             );
           } else {
             descBox.appendChild(descSlot);
           }
         }
       }
       // Re-numera todas as skills na ordem do DOM
       const allIcons = Array.from(
         iconBar.querySelectorAll(".skill-icon[data-index]")
       );
       let currentIndex = 1;
       allIcons.forEach((icon) => {
         const oldIndex = icon.dataset.index;
         icon.setAttribute("data-index", currentIndex);
         // Atualiza slot de descrição
         if (descBox && oldIndex) {
           const descSlot = descBox.querySelector(
             `.skill-desc[data-index="${oldIndex}"]`
           );
           if (descSlot) {
             descSlot.setAttribute("data-index", currentIndex);
           }
         }
         currentIndex++;
       });
       // Re-wire eventos
       wireClicksForCurrentBar();
       wireTooltipsForNewIcons();
       // Remove active do Change Form após animação completar
       setTimeout(() => {
         if (changeFormIconEl) {
           changeFormIconEl.classList.remove("active");
         }
       }, newIconsInBar.length * 24 + 180);
     }, 150);
   }
   function createSkillIconElement(skill, index) {
     const iconWrap = document.createElement("div");
     iconWrap.className = "skill-icon";
     iconWrap.setAttribute("data-index", index);
     iconWrap.setAttribute("data-nome", skill.name || "");
     iconWrap.setAttribute("data-desc", "");
     iconWrap.setAttribute(
       "data-atr",
       makeAttrString(
         skill.powerpve,
         skill.powerpvp,
         skill.energy,
         skill.cooldown
       )
     );
     iconWrap.setAttribute(
       "data-video",
       skill.video ? filePathURL(skill.video) : ""
     );
     iconWrap.setAttribute("data-video-preload", "auto");
     iconWrap.setAttribute("data-icon-file", skill.icon || "");
     iconWrap.setAttribute("data-video-file", skill.video || "");
     if (
       skill.level &&
       skill.level !== "" &&
       String(skill.level).toUpperCase() !== "NIVEL"
     ) {
       iconWrap.setAttribute("data-level", skill.level);
     }
     if (skill.desc_i18n) {
       if (skill.desc_i18n.pt)
         iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
       if (skill.desc_i18n.en)
         iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
       if (skill.desc_i18n.es)
         iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
       if (skill.desc_i18n.pl)
         iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
     }
     if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
       iconWrap.setAttribute("data-subs", JSON.stringify(skill.subs));
     }
     if (
       skill.suborder &&
       Array.isArray(skill.suborder) &&
       skill.suborder.length > 0
     ) {
       iconWrap.setAttribute("data-suborder", JSON.stringify(skill.suborder));
     }
     if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
       iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
     }
     if (
       skill.weapon &&
       typeof skill.weapon === "object" &&
       Object.keys(skill.weapon).length > 0
     ) {
       iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
     }
     if (skill.effect && typeof skill.effect === "object") {
       try {
         iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
       } catch (e) { }
     }
     if (skill.formSwitch) {
       iconWrap.setAttribute("data-form-switch", skill.formSwitch);
     }
     if (skill.formVideos && typeof skill.formVideos === "string" && skill.formVideos.trim() !== "") {
       iconWrap.setAttribute("data-form-videos", skill.formVideos);
     }
     const img = document.createElement("img");
     img.className = "skill-icon-img";
     img.src = filePathURL(skill.icon || "");
     img.alt = "";
     iconWrap.appendChild(img);
     return iconWrap;
   }
   function createSkillIcon(skill, index, container) {
     // Cria ícone similar ao que é gerado pelo servidor
     const iconWrap = document.createElement("div");
     iconWrap.className = "skill-icon";
     iconWrap.setAttribute("data-index", index);
     iconWrap.setAttribute("data-nome", skill.name || "");
     iconWrap.setAttribute("data-desc", "");
     iconWrap.setAttribute(
       "data-atr",
       makeAttrString(
         skill.powerpve,
         skill.powerpvp,
         skill.energy,
         skill.cooldown
       )
     );
     iconWrap.setAttribute(
       "data-video",
       skill.video ? filePathURL(skill.video) : ""
     );
     iconWrap.setAttribute("data-video-preload", "auto");
     iconWrap.setAttribute("data-icon-file", skill.icon || "");
     iconWrap.setAttribute("data-video-file", skill.video || "");
     if (
       skill.level &&
       skill.level !== "" &&
       String(skill.level).toUpperCase() !== "NIVEL"
     ) {
       iconWrap.setAttribute("data-level", skill.level);
     }
     if (skill.desc_i18n) {
       if (skill.desc_i18n.pt)
         iconWrap.setAttribute("data-desc-pt", skill.desc_i18n.pt);
       if (skill.desc_i18n.en)
         iconWrap.setAttribute("data-desc-en", skill.desc_i18n.en);
       if (skill.desc_i18n.es)
         iconWrap.setAttribute("data-desc-es", skill.desc_i18n.es);
       if (skill.desc_i18n.pl)
         iconWrap.setAttribute("data-desc-pl", skill.desc_i18n.pl);
     }
     if (skill.subs && Array.isArray(skill.subs) && skill.subs.length > 0) {
       iconWrap.setAttribute("data-subs", JSON.stringify(skill.subs));
     }
     if (
       skill.suborder &&
       Array.isArray(skill.suborder) &&
       skill.suborder.length > 0
     ) {
       iconWrap.setAttribute("data-suborder", JSON.stringify(skill.suborder));
     }
     if (skill.flags && Array.isArray(skill.flags) && skill.flags.length > 0) {
       iconWrap.setAttribute("data-flags", JSON.stringify(skill.flags));
     }
     if (
       skill.weapon &&
       typeof skill.weapon === "object" &&
       Object.keys(skill.weapon).length > 0
     ) {
       iconWrap.setAttribute("data-weapon", JSON.stringify(skill.weapon));
     }
     if (skill.effect && typeof skill.effect === "object") {
       try {
         iconWrap.setAttribute("data-effect", JSON.stringify(skill.effect));
       } catch (e) { }
     }
     if (skill.formSwitch) {
       iconWrap.setAttribute("data-form-switch", skill.formSwitch);
     }
     if (skill.formVideos && typeof skill.formVideos === "string" && skill.formVideos.trim() !== "") {
       iconWrap.setAttribute("data-form-videos", skill.formVideos);
     }
     const img = document.createElement("img");
     img.className = "skill-icon-img";
     img.src = filePathURL(skill.icon || "");
     img.alt = "";
     iconWrap.appendChild(img);
     container.appendChild(iconWrap);
   }
   function makeAttrString(pve, pvp, energy, cd) {
     const parts = [
       pve !== undefined && pve !== null && pve !== "" ? String(pve) : "-",
       pvp !== undefined && pvp !== null && pvp !== "" ? String(pvp) : "-",
       energy !== undefined && energy !== null && energy !== ""
         ? String(energy)
         : "-",
       cd !== undefined && cd !== null && cd !== "" ? String(cd) : "-",
     ];
     return parts.join(", ");
   }
   function filePathURL(fileName) {
     // Evita requisições para valores vazios
     if (!fileName || fileName.trim() === "") {
       return "";
     }
     const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ""));
     const base =
       window.mw && mw.util && typeof mw.util.wikiScript === "function"
         ? mw.util.wikiScript()
         : window.mw && window.mw.config
           ? mw.config.get("wgScript") || "/index.php"
           : "/index.php";
     // Garante HTTPS para evitar Mixed Content
     let url = `${base}?title=Especial:FilePath/${f}`;
     if (window.location.protocol === "https:" && url.startsWith("http://")) {
       url = url.replace("http://", "https://");
     }
     return url;
   }
   function slugify(s) {
     if (!s) return "";
     return String(s)
       .toLowerCase()
       .normalize("NFD")
       .replace(/[\u0300-\u036f]/g, "")
       .replace(/[^\w\s-]/g, "")
       .replace(/[\s:/\-]+/g, "-")
       .replace(/^-+|-+$/g, "")
       .replace(/-+/g, "-");
   }
   window.__skillSlugify = slugify;
   function getLangKey() {
     const skillsRoot = document.getElementById("skills");
     const raw = (
       document.documentElement.lang ||
       skillsRoot?.dataset.i18nDefault ||
       "pt"
     ).toLowerCase();
     return raw === "pt-br" ? "pt" : raw.split("-")[0] || "pt";
   }
   function chooseDescFrom(obj) {
     const lang = getLangKey();
     // Aceita tanto desc_i18n quanto desc para compatibilidade
     const pack = obj.desc_i18n ||
       obj.desc || {
       pt: obj.descPt,
       en: obj.descEn,
       es: obj.descEs,
       pl: obj.descPl,
     };
     return (
       (pack && (pack[lang] || pack.pt || pack.en || pack.es || pack.pl)) || ""
     );
   }
   function renderSubAttributesFromObj(s, L) {
     const chip = (label, val) =>
       val

 ? `

${label}${val}

`

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

return rows.length ? `

${rows.join("")}

` : "";

   }
   function getFlagIconURL(key) {
     const keyNorm = String(key || "").trim().toLowerCase();
     if (!keyNorm || !FLAG_ICON_FILES[keyNorm]) return "";
     if (!flagIconURLCache.has(keyNorm)) {
       flagIconURLCache.set(keyNorm, filePathURL(FLAG_ICON_FILES[keyNorm]));
     }
     return flagIconURLCache.get(keyNorm);
   }
   function renderFlagsRow(flags) {
     const arr = (flags || []).filter(Boolean).map((k) => String(k || "").trim().toLowerCase());
     const arrFiltered = arr.filter(Boolean);
     if (!arrFiltered.length) return "";
     const cacheKey = arrFiltered.join("|");
     if (flagRowCache.has(cacheKey)) {
       return flagRowCache.get(cacheKey);
     }
     const items = arrFiltered
       .map((k) => {
         const url = getFlagIconURL(k);
         return url
           ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">`
           : "";
       })
       .join("");
     const html = items

 ? `

${items}

`

       : "";
     if (html) flagRowCache.set(cacheKey, html);
     return html;
   }
   function applyFlagTooltips(container) {
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return;
     let pack = {};
     try {
       pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
     } catch (e) { }
     const lang = getLangKey();
     const dict = pack[lang] || pack.pt || {};
     const flags = container.querySelectorAll(
       ".skill-flags .skill-flag[data-flag]"
     );
     const tooltip = window.__globalSkillTooltip;
     if (!tooltip) return;
     flags.forEach((el) => {
       const key = el.getAttribute("data-flag");
       const tip = (dict && dict[key]) || "";
       if (!tip) return;
       if (el.dataset.flagTipWired) return;
       el.dataset.flagTipWired = "1";
       el.setAttribute("aria-label", tip);
       if (el.hasAttribute("title")) el.removeAttribute("title");
       el.addEventListener("mouseenter", () => {
         const tipEl = document.querySelector(".skill-tooltip");
         if (tipEl) tipEl.classList.add("flag-tooltip");
         tooltip.show(el, tip);
       });
       el.addEventListener("mousemove", () => {
         if (performance.now() >= tooltip.lockUntil.value) {
           tooltip.measureAndPos(el);
         }
       });
       el.addEventListener("click", () => {
         tooltip.lockUntil.value = performance.now() + 240;
         tooltip.measureAndPos(el);
       });
       el.addEventListener("mouseleave", () => {
         const tipEl = document.querySelector(".skill-tooltip");
         if (tipEl) tipEl.classList.remove("flag-tooltip");
         tooltip.hide();
       });
     });
   }
   // ====== Skill/Subskill inheritance helpers ======
   const mainSkillsMeta = {
     byIndex: new Map(),
     byName: new Map(),
     ready: false,
   };
   function normalizeFileURL(raw, fallback = "") {
     if (!raw) return fallback;
     const val = String(raw).trim();
     if (!val) return fallback;
     if (
       /^(https?:)?\/\//i.test(val) ||
       val.startsWith("data:") ||
       val.includes("Especial:FilePath/")
     ) {
       return val;
     }
     return filePathURL(val);
   }
   function extractFileNameFromURL(url) {
     if (!url) return "";
     const match = String(url).match(/(?:FilePath\/)([^&?]+)/i);
     return match ? decodeURIComponent(match[1]) : "";
   }
   function parseAttrString(raw) {
     const parts = (raw || "").split(",").map((v) => v.trim());
     const safe = (idx) => {
       const val = parts[idx] || "";
       return val && val !== "-" ? val : "";
     };
     return {
       powerpve: safe(0),
       powerpvp: safe(1),
       energy: safe(2),
       cooldown: safe(3),
     };
   }
   function hasText(value) {
     return typeof value === "string"
       ? value.trim() !== ""
       : value !== undefined && value !== null;
   }
   function pickFilled(current, fallback) {
     if (current === 0 || current === "0") return current;
     if (!hasText(current)) return fallback;
     return current;
   }
   function buildMainSkillsMeta(nodes) {
     if (mainSkillsMeta.ready) {
       return mainSkillsMeta;
     }
     (nodes || []).forEach((icon) => {
       const index = (icon.dataset.index || "").trim();
       if (!index) return;
       const name = (icon.dataset.nome || icon.dataset.name || "").trim();
       const attrs = parseAttrString(icon.dataset.atr || "");
       let iconFile = (icon.dataset.iconFile || "").trim();
       if (!iconFile) {
         const imgSrc = icon.querySelector("img")?.src || "";
         const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
         iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : "";
       }
       let videoFile = (icon.dataset.videoFile || "").trim();
       if (!videoFile) {
         videoFile = extractFileNameFromURL(icon.dataset.video || "");
       }
       let flags = null;
       if (icon.dataset.flags) {
         try {
           flags = JSON.parse(icon.dataset.flags);
           if (!Array.isArray(flags) || flags.length === 0) flags = null;
         } catch (e) { flags = null; }
       }
       const meta = {
         index,
         name,
         icon: iconFile || "",
         level: icon.dataset.level || "",
         video: videoFile || "",
         powerpve: attrs.powerpve || "",
         powerpvp: attrs.powerpvp || "",
         energy: attrs.energy || "",
         cooldown: attrs.cooldown || "",
         desc: icon.dataset.desc || "",
         descPt: icon.dataset.descPt || "",
         descEn: icon.dataset.descEn || "",
         descEs: icon.dataset.descEs || "",
         descPl: icon.dataset.descPl || "",
         flags: flags || null,
       };
       mainSkillsMeta.byIndex.set(index, meta);
       mainSkillsMeta.byIndex.set(parseInt(index, 10), meta);
       if (name) {
         mainSkillsMeta.byName.set(name, meta);
       }
     });
     mainSkillsMeta.ready = true;
     return mainSkillsMeta;
   }
   function inheritSubskillFromMain(sub, meta) {
     if (!sub || !meta) return sub;
     // Verifica se herança está desabilitada
     const shouldInherit = !(
       sub.inherit === false ||
       sub.inherit === "no" ||
       sub.inherit === "false"
     );
     // Suporta refS (novo) e refM (legado)
     const refS = ((sub.refS || sub.S || sub.s || "") + "").trim();
     const refIndex = ((sub.refM || sub.M || sub.m || "") + "").trim();
     let name = (sub.name || sub.n || "").trim();
     let main = null;
     // Se herança está desabilitada, não busca a skill principal
     if (!shouldInherit) {
       return { ...sub };
     }
     // Primeiro tenta por refS
     if (refS) {
       main = meta.byIndex.get(refS) || meta.byIndex.get(parseInt(refS, 10));
     }
     // Depois por refM
     if (!main && refIndex) {
       main =
         meta.byIndex.get(refIndex) ||
         meta.byIndex.get(parseInt(refIndex, 10));
     }
     // Por último pelo nome
     if (!main && name) {
       main = meta.byName.get(name);
     }
     if (!main) {
       return sub;
     }
     const hydrated = { ...sub };
     if (!name && main.name) {
       name = main.name;
     }
     hydrated.name = name || hydrated.name || main.name || "";
     hydrated.icon = pickFilled(hydrated.icon, main.icon || "");
     hydrated.level = pickFilled(hydrated.level, main.level || "");
     // Vídeo NUNCA é herdado da skill principal
     const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, "video");
     hydrated.video = hasOwnVideo ? sub.video || "" : "";
     hydrated.powerpve = pickFilled(hydrated.powerpve, main.powerpve || "");
     hydrated.powerpvp = pickFilled(hydrated.powerpvp, main.powerpvp || "");
     hydrated.energy = pickFilled(hydrated.energy, main.energy || "");
     hydrated.cooldown = pickFilled(hydrated.cooldown, main.cooldown || "");
     // Flags: herda da skill principal se a subskill não tiver
     if (
       (!hydrated.flags || !Array.isArray(hydrated.flags) || hydrated.flags.length === 0) &&
       main.flags && Array.isArray(main.flags) && main.flags.length > 0
     ) {
       hydrated.flags = main.flags;
     }
     // Descrição: sempre vem da subskill, nunca herda
     // PROTEÇÃO TOTAL: Remove qualquer descrição que possa ter sido copiada do main
     if (
       !sub.desc &&
       !sub.descPt &&
       !sub.descEn &&
       !sub.descEs &&
       !sub.descPl &&
       !sub.desc_i18n
     ) {
       hydrated.desc = undefined;
       hydrated.descPt = undefined;
       hydrated.descEn = undefined;
       hydrated.descEs = undefined;
       hydrated.descPl = undefined;
       hydrated.desc_i18n = undefined;
     } else {
       // Se subskill tem descrição, normaliza para desc_i18n
       if (
         !hydrated.desc_i18n &&
         (hydrated.descPt ||
           hydrated.descEn ||
           hydrated.descEs ||
           hydrated.descPl)
       ) {
         hydrated.desc_i18n = {
           pt: hydrated.descPt || "",
           en: hydrated.descEn || "",
           es: hydrated.descEs || "",
           pl: hydrated.descPl || "",
         };
       }
     }
     return hydrated;
   }
   function inheritSubskillTree(subs, meta) {
     if (!Array.isArray(subs)) return [];
     return subs.map((sub) => {
       const hydrated = inheritSubskillFromMain(sub, meta);
       if (Array.isArray(hydrated.subs)) {
         hydrated.subs = inheritSubskillTree(hydrated.subs, meta);
       }
       return hydrated;
     });
   }
   function collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet) {
     if (!Array.isArray(subs)) return;
     subs.forEach((sub) => {
       const iconURL = normalizeFileURL(sub.icon || "", "");
       if (iconURL && iconURL !== "") iconsSet.add(iconURL);
       // Vídeo normal
       if (sub.video && sub.video.trim() !== "") {
         const videoURL = normalizeFileURL(sub.video);
         if (videoURL) videosSet.add(videoURL);
       }
       // Vídeo de weapon
       if (
         sub.weapon &&
         typeof sub.weapon === "object" &&
         sub.weapon.video &&
         sub.weapon.video.trim() !== ""
       ) {
         const weaponVideoURL = normalizeFileURL(sub.weapon.video);
         if (weaponVideoURL) videosSet.add(weaponVideoURL);
       }
       if (Array.isArray(sub.flags)) {
         sub.flags.forEach((flagKey) => {
           const url = getFlagIconURL(flagKey);
           if (url) flagsSet.add(url);
         });
       }
       if (Array.isArray(sub.subs)) {
         collectAssetsFromSubs(sub.subs, iconsSet, videosSet, flagsSet);
       }
     });
   }
   function buildAssetManifest() {
     if (window.__skillAssetManifest && window.__skillAssetManifest.ready) {
       return window.__skillAssetManifest;
     }
     const iconsSet = new Set();
     const videosSet = new Set();
     const flagsSet = new Set();
     iconItems.forEach((el) => {
       const img = el.querySelector("img");
       if (img && img.src) {
         iconsSet.add(img.src);
       } else if (el.dataset.iconFile) {
         const iconURL = normalizeFileURL(el.dataset.iconFile);
         if (iconURL) iconsSet.add(iconURL);
       }
       // Vídeo normal da skill
       const videoRaw = (
         el.dataset.videoFile ||
         el.dataset.video ||
         ""
       ).trim();
       if (videoRaw) {
         const videoURL = normalizeFileURL(videoRaw);
         if (videoURL) videosSet.add(videoURL);
       }
       // Vídeo de weapon da skill
       if (el.dataset.weapon) {
         try {
           const weaponData = JSON.parse(el.dataset.weapon);
           if (
             weaponData &&
             weaponData.video &&
             weaponData.video.trim() !== ""
           ) {
             const weaponVideoURL = normalizeFileURL(weaponData.video);
             if (weaponVideoURL) videosSet.add(weaponVideoURL);
           }
         } catch (e) { }
       }
       if (el.dataset.flags) {
         try {
           const parsedFlags = JSON.parse(el.dataset.flags);
           (parsedFlags || []).forEach((flagKey) => {
             const url = getFlagIconURL(flagKey);
             if (url) flagsSet.add(url);
           });
         } catch (e) { }
       }
       if (el.dataset.subs) {
         try {
           const subs = JSON.parse(el.dataset.subs);
           collectAssetsFromSubs(subs, iconsSet, videosSet, flagsSet);
         } catch (e) { }
       }
     });
     Object.keys(FLAG_ICON_FILES).forEach((flagKey) => {
       const url = getFlagIconURL(flagKey);
       if (url) flagsSet.add(url);
     });
     const manifest = {
       icons: iconsSet,
       videos: videosSet,
       flags: flagsSet,
       ready: true,
     };
     window.__skillAssetManifest = manifest;
     return manifest;
   }
   const subskillVideosCache = new Map();
   window.__subskillVideosCache = subskillVideosCache;
   // Cache de vídeos que falharam (404) para evitar tentativas repetidas
   const failedVideosCache = new Set();
   const missingVideosReported = new Set(); // Para avisar apenas uma vez sobre vídeos faltantes
   // Cache de parsing JSON para evitar re-parsear os mesmos dados
   const jsonParseCache = new WeakMap();
   function getCachedJSON(el, key) {
     if (!el) return null;
     const cache = jsonParseCache.get(el) || {};
     if (cache[key] !== undefined) return cache[key];
     const raw = el.dataset[key] || el.getAttribute(`data-${key}`);
     if (!raw) {
       cache[key] = null;
       jsonParseCache.set(el, cache);
       return null;
     }
     try {
       const parsed = JSON.parse(raw);
       cache[key] = parsed;
       jsonParseCache.set(el, cache);
       return parsed;
     } catch (e) {
       cache[key] = null;
       jsonParseCache.set(el, cache);
       return null;
     }
   }
   let assetManifest = null;
   const skillsTab = $("#skills");
   const skinsTab = $("#skins");
   ensureRemoved(".top-rail");
   // NÃO remove .content-card aqui - será gerenciado abaixo
   // ensureRemoved('.content-card'); // REMOVIDO - pode estar removendo skills-container
   ensureRemoved(".video-placeholder");
   Array.from(
     document.querySelectorAll(
       ".card-skins-title, .card-skins .card-skins-title, .cardskins-title, .rail-title"
     )
   ).forEach((t) => {
     if ((t.textContent || "").trim().toLowerCase().includes("skins")) {
       t.remove();
     }
   });
   if (skillsTab) {
     const iconBar = skillsTab.querySelector(".icon-bar");
     if (iconBar) {
       const rail = document.createElement("div");
       rail.className = "top-rail skills";
       // Criar wrapper de scroll para permitir glow sem clipping
       if (
         !iconBar.parentElement ||
         !iconBar.parentElement.classList.contains("icon-scroll-x")
       ) {
         const scrollWrapper = document.createElement("div");
         scrollWrapper.className = "icon-scroll-x";
         scrollWrapper.appendChild(iconBar);
         rail.appendChild(scrollWrapper);
       } else {
         rail.appendChild(iconBar.parentElement);
       }
       skillsTab.prepend(rail);
     }
     // Busca skills-container criado pelo Lua
     const skillsContainer = skillsTab.querySelector(".skills-container");
     // Busca skills-details e video-container (podem estar dentro de skills-container ou soltos)
     let details = skillsTab.querySelector(".skills-details");
     if (!details && skillsContainer) {
       details = skillsContainer.querySelector(".skills-details");
     }
     if (!details) {
       details = document.createElement("div");
       details.className = "skills-details";
     }
     // Busca ou cria desc-box dentro de details
     let descBoxEl = details.querySelector(".desc-box");
     if (!descBoxEl) {
       descBoxEl = document.createElement("div");
       descBoxEl.className = "desc-box";
       details.appendChild(descBoxEl);
     }
     // Busca video-container
     let videoContainer = skillsTab.querySelector(".video-container");
     if (!videoContainer && skillsContainer) {
       videoContainer = skillsContainer.querySelector(".video-container");
     }
     if (!videoContainer) {
       videoContainer = document.createElement("div");
       videoContainer.className = "video-container";
     }
     // Cria ou atualiza content-card skills-grid (estrutura esperada pelo CSS)
     let card = skillsTab.querySelector(".content-card.skills-grid");
     if (!card) {
       card = document.createElement("div");
       card.className = "content-card skills-grid";
       skillsTab.appendChild(card);
     }
     // Move details e videoContainer para dentro do card (estrutura correta)
     // Remove skills-container se existir (não é necessário para o layout)
     if (skillsContainer && skillsContainer.parentNode) {
       // Move os elementos filhos de skills-container para o card
       if (details.parentNode === skillsContainer) {
         skillsContainer.removeChild(details);
       }
       if (videoContainer.parentNode === skillsContainer) {
         skillsContainer.removeChild(videoContainer);
       }
       // Remove skills-container se estiver vazio
       if (skillsContainer.children.length === 0) {
         skillsContainer.remove();
       }
     }
     // Garante que details e videoContainer estão no card na ordem correta
     // skills-details deve vir ANTES de video-container para o grid funcionar
     if (details.parentNode !== card) {
       // Se videoContainer já está no card, insere details antes dele
       if (videoContainer.parentNode === card) {
         card.insertBefore(details, videoContainer);
       } else {
         card.appendChild(details);
       }
     }
     if (videoContainer.parentNode !== card) {
       card.appendChild(videoContainer);
     }
     // Garante ordem: details primeiro, videoContainer depois
     if (details.parentNode === card && videoContainer.parentNode === card) {
       if (details.nextSibling !== videoContainer) {
         card.insertBefore(details, videoContainer);
       }
     }
   }
   // Função para obter iconsBar de forma segura (com retry)
   function getIconsBar() {
     const skillsEl = document.getElementById("skills");
     if (!skillsEl) return null;
     return skillsEl.querySelector(".icon-bar");
   }
   const iconsBar = getIconsBar();
   const skillsTopRail = iconsBar
     ? iconsBar.closest(".top-rail.skills")
     : null;
   const iconItems = iconsBar
     ? Array.from(iconsBar.querySelectorAll(".skill-icon"))
     : [];
   buildMainSkillsMeta(iconItems);
   // Verifica se há weapon em skills principais OU em subskills
   function checkHasAnyWeapon() {
     // Verifica skills principais
     const hasMainWeapon = iconItems.some((el) => {
       const weapon = el.dataset.weapon;
       return weapon && weapon.trim() !== "" && weapon !== "{}";
     });
     if (hasMainWeapon) {
       return true;
     }
     // Verifica subskills
     for (const el of iconItems) {
       const subsRaw = el.getAttribute("data-subs");
       if (!subsRaw) continue;
       try {
         const subs = JSON.parse(subsRaw);
         if (
           Array.isArray(subs) &&
           subs.some(
             (s) =>
               s &&
               s.weapon &&
               typeof s.weapon === "object" &&
               Object.keys(s.weapon).length > 0
           )
         ) {
           return true;
         }
       } catch (e) { }
     }
     return false;
   }
   const hasWeaponSkillAvailable = checkHasAnyWeapon();
   let weaponToggleBtn = null;
   if (!assetManifest) {
     assetManifest = buildAssetManifest();
     // Pré-carrega apenas ícones e flags críticos
     if (assetManifest.icons && assetManifest.icons.size) {
       assetManifest.icons.forEach((url) => {
         if (!url || imagePreloadCache.has(url)) return;
         const img = new Image();
         img.decoding = "async";
         img.loading = "eager";
         img.referrerPolicy = "same-origin";
         img.src = url;
         imagePreloadCache.set(url, img);
       });
     }
     if (assetManifest.flags && assetManifest.flags.size) {
       assetManifest.flags.forEach((url) => {
         if (!url || imagePreloadCache.has(url)) return;
         const img = new Image();
         img.decoding = "async";
         img.loading = "eager";
         img.referrerPolicy = "same-origin";
         img.src = url;
         imagePreloadCache.set(url, img);
       });
     }
   }
   // Busca descBox e videoBox após a estrutura estar organizada
   const descBox = $("#skills") ? $(".desc-box", $("#skills")) : null;
   let videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
   // Busca dinâmica do videoBox (pode não existir no momento da inicialização)
   function getVideoBox() {
     if (videoBox && videoBox.isConnected) return videoBox;
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return null;
     videoBox = skillsRoot.querySelector(".video-container") ||
       skillsRoot.querySelector(".skills-container .video-container") ||
       skillsRoot.querySelector(".content-card .video-container");
     return videoBox;
   }
   const videosCache = new Map();
   const nestedVideoElByIcon = new WeakMap();
   const barStack = [];
   window.__barStack = barStack;
   let initialBarSnapshot = null;
   let totalVideos = 0,
     loadedVideos = 0,
     autoplay = false;
   window.__lastActiveSkillIcon = null;
   let userHasInteracted = false;
   let globalWeaponEnabled = false;
   try {
     if (localStorage.getItem("glaWeaponEnabled") === "1") {
       globalWeaponEnabled = true;
     }
   } catch (err) { }
   const weaponStateListeners = new Set();
   let showWeaponPopupFn = null;
   let popupShouldOpen = false;
   function attachWeaponPopupFn(fn) {
     if (typeof fn !== "function") return;
     showWeaponPopupFn = fn;
     if (popupShouldOpen) {
       popupShouldOpen = false;
       try {
         showWeaponPopupFn();
       } catch (err) { }
     }
   }
   attachWeaponPopupFn(window.__glaWeaponShowPopup);
   function requestWeaponPopupDisplay() {
     try {
       if (localStorage.getItem("glaWeaponPopupDismissed") === "1") return;
     } catch (err) { }
     if (typeof showWeaponPopupFn === "function") {
       showWeaponPopupFn();
       return;
     }
     popupShouldOpen = true;
   }
   function onWeaponStateChange(fn) {
     if (typeof fn !== "function") return;
     weaponStateListeners.add(fn);
   }
   function syncWeaponButtonState(enabled) {
     if (!weaponToggleBtn || !weaponToggleBtn.isConnected) return;
     // Usa .weapon-active em vez de .active para não conflitar com skills
     weaponToggleBtn.classList.toggle("weapon-active", !!enabled);
     weaponToggleBtn.classList.remove("active"); // Garante que .active nunca seja aplicado
     weaponToggleBtn.setAttribute("aria-pressed", enabled ? "true" : "false");
     weaponToggleBtn.setAttribute(
       "aria-label",
       enabled ? "Desativar Arma Especial" : "Ativar Arma Especial"
     );
   }
   function syncWeaponRailState(enabled) {
     if (skillsTopRail) {
       skillsTopRail.classList.toggle("weapon-mode-on", !!enabled);
     }
   }
   function notifyWeaponStateListeners(enabled) {
     weaponStateListeners.forEach((listener) => {
       try {
         listener(enabled);
       } catch (err) { }
     });
   }
   let pendingWeaponState = null;
   window.addEventListener("weapon:ready", (ev) => {
     if (ev && ev.detail && ev.detail.showPopup) {
       attachWeaponPopupFn(ev.detail.showPopup);
     }
     if (pendingWeaponState === null) return;
     if (typeof window.__applyWeaponState === "function") {
       const target = pendingWeaponState;
       pendingWeaponState = null;
       window.__applyWeaponState(target);
     }
   });
   window.__setGlobalWeaponEnabled = (enabled) => {
     globalWeaponEnabled = enabled;
     notifyWeaponStateListeners(enabled);
   };
   function requestWeaponState(targetState) {
     if (typeof window.__applyWeaponState === "function") {
       pendingWeaponState = null;
       window.__applyWeaponState(targetState);
       return;
     }
     pendingWeaponState = targetState;
   }
   onWeaponStateChange(syncWeaponButtonState);
   function reapplyWeaponClassesToBar() {
     // SISTEMA UNIFICADO: Aplica em skills E subskills (sempre, não só quando weapon está ativo)
     // Obtém iconsBar dinamicamente (pode não estar pronto ainda quando há flags)
     const currentIconsBar = getIconsBar();
     if (!currentIconsBar) {
       return;
     }
     // Busca em skills principais (dentro de iconsBar)
     currentIconsBar
       .querySelectorAll(".skill-icon[data-weapon]")
       .forEach((el) => {
         const weapon = el.dataset.weapon;
         if (weapon && weapon.trim() !== "" && weapon !== "{}") {
           try {
             const weaponObj = JSON.parse(weapon);
             if (
               weaponObj &&
               typeof weaponObj === "object" &&
               Object.keys(weaponObj).length > 0
             ) {
               if (!el.classList.contains("has-weapon-available")) {
                 el.classList.add("has-weapon-available");
               }
               if (!el.querySelector(".weapon-indicator")) {
                 const ind = document.createElement("div");
                 ind.className = "weapon-indicator";
                 el.appendChild(ind);
               }
             }
           } catch (e) {
             // Se não for JSON válido, não adiciona a classe
           }
         }
       });
     // Busca em subskills (dentro de subskills-rail)
     document
       .querySelectorAll(".subskills-rail .subicon[data-weapon]")
       .forEach((el) => {
         const weapon = el.dataset.weapon;
         if (weapon && weapon.trim() !== "" && weapon !== "{}") {
           try {
             const weaponObj = JSON.parse(weapon);
             if (
               weaponObj &&
               typeof weaponObj === "object" &&
               Object.keys(weaponObj).length > 0
             ) {
               if (!el.classList.contains("has-weapon-available")) {
                 el.classList.add("has-weapon-available");
               }
             }
           } catch (e) {
             // Se não for JSON válido, não adiciona a classe
           }
         }
       });
   }
   // Aplica classes de weapon imediatamente ao carregar
   reapplyWeaponClassesToBar();
   // REMOVIDO: setupWeaponBarToggle - O toggle de weapon agora é criado apenas pelo C.WeaponToggle.html no header
   // Não cria mais botão na barra de skills - apenas aplica classes visuais
   onWeaponStateChange(syncWeaponRailState);
   // Reaplica classes quando o estado do weapon muda (para garantir que funcione mesmo quando toggle é ativado fora da barra)
   onWeaponStateChange(() => {
     // Usa setTimeout para garantir que o DOM foi atualizado
     setTimeout(() => {
       reapplyWeaponClassesToBar();
     }, 50);
   });
   syncWeaponRailState(globalWeaponEnabled);
   (function injectWeaponStyles() {
     if (document.getElementById("weapon-toggle-styles")) return;
     const style = document.createElement("style");
     style.id = "weapon-toggle-styles";
     style.textContent = `
       /* ========== ESTILOS DE WEAPON - NOVO SISTEMA ========== */
       
       /* Animação simples para borda */
       @keyframes weapon-border-flow {
           0% { background-position: 0% 0%; }
           100% { background-position: 200% 0%; }
       }
       /* Animação sutil para brilho */
       @keyframes weapon-glow-breathe {
           0%, 100% { opacity: 0.7; }
           50% { opacity: 1; }
       }
       /* Animação do gradiente (sem girar a borda) */
       @property --effect-spin {
           syntax: "<angle>";
           inherits: false;
           initial-value: 0deg;
       }
       @keyframes effect-border-spin {
           from { --effect-spin: 0deg; }
           to { --effect-spin: 360deg; }
       }
       /* ===== ÍCONE COM ARMA - Só mostra efeitos quando weapon-mode-on está ativo ===== */
       /* Quando NÃO está em weapon-mode-on, os ícones com arma devem ser normais (mesma borda padrão) */
       .character-box .top-rail.skills .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
           /* Remove borda sólida base que aparece na transição do toggle */
           border-color: transparent !important;
           box-shadow: none !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle) {
           /* Remove transform scale quando toggle está desativado */
           transform: none !important;
           transition: transform 0.15s ease !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle)::after {
           /* Reseta COMPLETAMENTE para borda padrão quando toggle está desativado */
           /* Remove background, padding e mask INSTANTANEAMENTE (sem transição) */
           background: none !important;
           background-size: unset !important;
           padding: 0 !important;
           -webkit-mask: none !important;
           mask: none !important;
           mask-composite: unset !important;
           -webkit-mask-composite: unset !important;
           animation: none !important;
           /* Apenas box-shadow tem transição suave */
           box-shadow: inset 0 0 0 var(--icon-ring-w) var(--icon-idle) !important;
           /* SEM transição no background/padding/mask para evitar "flash" durante a mudança */
           transition: box-shadow 0.15s ease !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle)::before {
           /* Remove efeitos de glow quando toggle está desativado - com transição suave */
           opacity: 0 !important;
           transition: opacity 0.15s ease !important;
       }
       /* Quando ativo mas toggle desativado, usa borda padrão de ativo */
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active {
           /* Mantém o zoom padrão mesmo quando toggle está desativado */
           transform: scale(1.10) translateZ(0) !important;
           transition: transform 0.15s ease !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::after {
           /* Reseta COMPLETAMENTE para borda padrão de ativo */
           /* Remove background, padding e mask INSTANTANEAMENTE (sem transição) */
           background: none !important;
           background-size: unset !important;
           padding: 0 !important;
           -webkit-mask: none !important;
           mask: none !important;
           mask-composite: unset !important;
           -webkit-mask-composite: unset !important;
           animation: none !important;
           /* Apenas box-shadow tem transição suave */
           box-shadow: inset 0 0 0 var(--icon-ring-w) var(--icon-active) !important;
           /* SEM transição no background/padding/mask para evitar "flash" durante a mudança */
           transition: box-shadow 0.15s ease !important;
       }
       .character-box .top-rail.skills:not(.weapon-mode-on) .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before {
           /* Sem glow amarelo no modo normal para skills com weapon */
           opacity: 0 !important;
           box-shadow: none !important;
           transition: opacity 0.15s ease !important;
       }
       /* ===== MODO WEAPON ON - INATIVO ===== */
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::after {
           pointer-events: none !important;
           box-shadow: none !important;
           background: linear-gradient(90deg, 
               #FF3333 0%, 
               #FF0000 50%, 
               #FF3333 100%) !important;
           background-size: 200% 100% !important;
           animation: weapon-border-flow 3s linear infinite !important;
           -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
           mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
           -webkit-mask-composite: xor !important;
           mask-composite: exclude !important;
           padding: 2px !important;
           /* SEM transição para permitir remoção instantânea quando toggle é desativado */
           transition: none !important;
       }
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle):not(.active)::before {
           pointer-events: none !important;
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           animation: weapon-glow-breathe 2s ease-in-out infinite !important;
           box-shadow: 
               0 0 8px 0 rgba(255, 0, 0, 0.4),
               0 0 12px 0 rgba(255, 51, 51, 0.3),
               0 0 16px 0 rgba(255, 0, 0, 0.2) !important;
           opacity: 1 !important;
       }
       /* ===== MODO WEAPON ON - ATIVO ===== */
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active {
           transform: scale(1.10) !important;
           z-index: 5 !important;
       }
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::after {
           pointer-events: none !important;
           box-shadow: none !important;
           background: linear-gradient(90deg, 
               #FFEB3B 0%, 
               #FFD700 50%, 
               #FFEB3B 100%) !important;
           background-size: 200% 100% !important;
           animation: weapon-border-flow 2s linear infinite !important;
           -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
           mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
           -webkit-mask-composite: xor !important;
           mask-composite: exclude !important;
           padding: 2px !important;
           /* SEM transição para permitir remoção instantânea quando toggle é desativado */
           transition: none !important;
       }
       .character-box .top-rail.skills.weapon-mode-on .icon-bar .skill-icon.has-weapon-available:not(.weapon-bar-toggle).active::before {
           pointer-events: none !important;
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           animation: weapon-glow-breathe 1.5s ease-in-out infinite !important;
           box-shadow: 
               0 0 10px 0 rgba(255, 215, 0, 0.5),
               0 0 16px 0 rgba(255, 235, 59, 0.4),
               0 0 22px 0 rgba(255, 215, 0, 0.3) !important;
           opacity: 1 !important;
       }
       .character-box .top-rail.skills .icon-bar {
           position: relative;
       }
       .character-box .top-rail.skills .icon-bar .skill-icon {
           z-index: 2;
       }
       .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active) {
           border-color: transparent !important;
           outline: 2px solid rgba(210, 60, 60, 0.95) !important;
           outline-offset: -2px;
           animation: effect-child-outline 1.6s ease-in-out infinite !important;
       }
       .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
           box-shadow:
               inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
               0 0 6px rgba(255, 60, 60, 0.45),
               0 0 10px rgba(120, 20, 20, 0.6) !important;
           animation: effect-child-ring 1.6s ease-in-out infinite !important;
           opacity: 1 !important;
       }
       .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           opacity: 1 !important;
           box-shadow:
               0 0 8px rgba(255, 60, 60, 0.35),
               0 0 14px rgba(120, 20, 20, 0.45);
           animation: effect-child-glow 1.6s ease-in-out infinite !important;
       }
       .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
           box-shadow:
               inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
               0 0 6px rgba(255, 60, 60, 0.45),
               0 0 10px rgba(120, 20, 20, 0.6) !important;
           animation: effect-child-ring 1.6s ease-in-out infinite !important;
           opacity: 1 !important;
       }
       .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::before {
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           opacity: 1 !important;
           box-shadow:
               0 0 8px rgba(255, 60, 60, 0.35),
               0 0 14px rgba(120, 20, 20, 0.45);
           animation: effect-child-glow 1.6s ease-in-out infinite !important;
       }
       @keyframes effect-child-ring {
           0% {
               box-shadow:
                   inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                   0 0 6px rgba(255, 60, 60, 0.45),
                   0 0 10px rgba(120, 20, 20, 0.6);
           }
           50% {
               box-shadow:
                   inset 0 0 0 var(--icon-ring-w) rgba(120, 20, 20, 0.95),
                   0 0 9px rgba(255, 70, 70, 0.65),
                   0 0 14px rgba(20, 0, 0, 0.8);
           }
           100% {
               box-shadow:
                   inset 0 0 0 var(--icon-ring-w) rgba(210, 60, 60, 0.95),
                   0 0 6px rgba(255, 60, 60, 0.45),
                   0 0 10px rgba(120, 20, 20, 0.6);
           }
       }
       @keyframes effect-child-outline {
           0% {
               outline-color: rgba(210, 60, 60, 0.95);
           }
           50% {
               outline-color: rgba(120, 20, 20, 0.95);
           }
           100% {
               outline-color: rgba(210, 60, 60, 0.95);
           }
       }
       @keyframes effect-child-glow {
           0% {
               box-shadow:
                   0 0 8px rgba(255, 60, 60, 0.35),
                   0 0 14px rgba(120, 20, 20, 0.45);
           }
           50% {
               box-shadow:
                   0 0 12px rgba(255, 70, 70, 0.55),
                   0 0 18px rgba(20, 0, 0, 0.75);
           }
           100% {
               box-shadow:
                   0 0 8px rgba(255, 60, 60, 0.35),
                   0 0 14px rgba(120, 20, 20, 0.45);
           }
       }
       .character-box .top-rail.skills .effect-lines-layer {
           position: absolute;
           left: 0;
           top: 0;
           width: 100%;
           height: 100%;
           pointer-events: none;
           z-index: 0;
           overflow: visible;
       }
       .character-box .top-rail.skills .effect-lines-layer .effect-line {
           fill: none;
           stroke-linecap: round;
           stroke-linejoin: round;
       }
       .character-box .top-rail.skills .effect-lines-layer .effect-line-glow {
           stroke: rgba(120, 20, 20, 0.7);
           stroke-width: 5;
           filter: drop-shadow(0 0 6px rgba(255, 60, 60, 0.45));
           animation: none;
       }
       .character-box .top-rail.skills .effect-lines-layer .effect-line-core {
           stroke: rgba(210, 60, 60, 0.95);
           stroke-width: 2.2;
           animation: none;
       }
       .character-box .top-rail.skills .effect-lines-layer .effect-line.effect-line-returning {
           animation: effect-line-return var(--effect-return-duration, 1.1s) ease forwards;
           animation-delay: var(--effect-return-delay, 0s);
       }
       @keyframes effect-line-pulse {
           0% {
               stroke: rgba(210, 60, 60, 0.95);
               stroke-width: 2.1;
           }
           50% {
               stroke: rgba(120, 20, 20, 0.95);
               stroke-width: 2.7;
           }
           100% {
               stroke: rgba(220, 70, 70, 0.95);
               stroke-width: 2.1;
           }
       }
       @keyframes effect-line-glow {
           0% {
               stroke: rgba(140, 25, 25, 0.65);
               filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
           }
           50% {
               stroke: rgba(20, 0, 0, 0.85);
               filter: drop-shadow(0 0 9px rgba(255, 70, 70, 0.65));
           }
           100% {
               stroke: rgba(140, 25, 25, 0.65);
               filter: drop-shadow(0 0 5px rgba(255, 80, 80, 0.35));
           }
       }
       @keyframes effect-line-return {
           0% {
               stroke-dasharray: var(--effect-line-length, 0) var(--effect-line-length, 0);
               stroke-dashoffset: 0;
               opacity: 1;
           }
           100% {
               stroke-dasharray: 0 var(--effect-line-length, 0);
               stroke-dashoffset: calc(var(--effect-line-length, 0) * -1);
               opacity: 0;
           }
       }
       @media (prefers-reduced-motion: reduce) {
           .character-box .top-rail.skills .effect-lines-layer .effect-line {
               animation: none;
           }
       }
       .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle).active::before {
           pointer-events: none !important;
           inset: 0 !important;
           border-radius: inherit !important;
           z-index: 1 !important;
           animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
           box-shadow: 
               0 0 10px 0 rgba(220, 220, 220, 0.5),
               0 0 16px 0 rgba(190, 190, 190, 0.4),
               0 0 22px 0 rgba(220, 220, 220, 0.3) !important;
           opacity: 1 !important;
       }
       /* ========== ESTILOS DE SWAP DE PERSONAGENS (Sistema Genérico) ========== */
       /* Skills desabilitadas quando o personagem ativo não pode usá-las */
       .character-box .top-rail.skills .icon-bar .skill-icon.disabled-skill {
           opacity: 0.3 !important;
           filter: grayscale(100%) !important;
           cursor: not-allowed !important;
           pointer-events: none !important;
           transition: opacity 0.2s ease, filter 0.2s ease !important;
       }
           `;
     document.head.appendChild(style);
   })();
   function applyWeaponBadge(el, weaponData, equipped) {
     // Apenas gerencia a classe weapon-equipped (badge visual removido)
     if (equipped && weaponData) {
       el.classList.add("weapon-equipped");
     } else {
       el.classList.remove("weapon-equipped");
     }
   }
   function getWeaponKey(el) {
     return (
       (el.dataset.index || "") +
       ":" +
       (el.dataset.nome || el.dataset.name || "")
     );
   }
   function isWeaponModeOn() {
     try {
       return localStorage.getItem("glaWeaponEnabled") === "1";
     } catch (e) {
       return false;
     }
   }
   function getWeaponDataForIcon(iconEl) {
     if (!iconEl || !iconEl.dataset.weapon) return null;
     // Usa cache de parsing JSON
     return getCachedJSON(iconEl, "weapon");
   }
   const effectState = {
     skills: new Set(),
     videos: new Map(),
     expiresAt: 0,
     sourceIcon: null,
     timer: null,
   };
   let effectLinesLayer = null;
   let effectLinesRAF = null;
   let effectLinesCleanupTimer = null;
   let effectLinesScrollWrap = null;
   let effectLinesBound = false;
   let effectLinesLastState = null;
   function getSkillNameFromIcon(iconEl) {
     return (iconEl?.dataset?.nome || iconEl?.dataset?.name || "").trim();
   }
   function getEffectVideoKey(iconEl, videoValue) {
     if (!iconEl) return "";
     const baseIndex = iconEl.dataset.index || "";
     const subName =
       iconEl.dataset.subName ||
       iconEl.dataset.nome ||
       iconEl.dataset.name ||
       "";
     const namePart = baseIndex || subName || "";
     const videoPart = String(videoValue || "").trim();
     if (!namePart || !videoPart) return "";
     return `effect:${namePart}:${videoPart}`;
   }
   function normalizeEffectData(raw) {
     if (!raw || typeof raw !== "object") return null;
     const rawSkills = raw.skills;
     const timeValue = Number(raw.time ?? raw.duration ?? 0);
     const timeMs = Number.isFinite(timeValue) ? timeValue * 1000 : 0;
     if (!timeMs || timeMs <= 0) return null;
     const skills = Array.isArray(rawSkills)
       ? rawSkills.map((s) => String(s || "").trim()).filter(Boolean)
       : [];
     if (!skills.length) return null;
     const videos = new Map();
     if (raw.videos && typeof raw.videos === "object") {
       Object.keys(raw.videos).forEach((k) => {
         const key = String(k || "").trim();
         const val = String(raw.videos[k] || "").trim();
         if (key && val) videos.set(key, val);
       });
     }
     return { skills, timeMs, videos };
   }
   function handleEffectLinesScroll() {
     scheduleEffectLinesUpdate();
   }
   function bindEffectLinesEvents() {
     if (!effectLinesBound) {
       window.addEventListener("resize", scheduleEffectLinesUpdate);
       window.addEventListener("scroll", scheduleEffectLinesUpdate, {
         passive: true,
       });
       effectLinesBound = true;
     }
     const rail = document.querySelector(".top-rail.skills");
     const scrollWrap = rail ? rail.querySelector(".icon-scroll-x") : null;
     if (scrollWrap && scrollWrap !== effectLinesScrollWrap) {
       if (effectLinesScrollWrap) {
         effectLinesScrollWrap.removeEventListener(
           "scroll",
           handleEffectLinesScroll
         );
       }
       scrollWrap.addEventListener("scroll", handleEffectLinesScroll, {
         passive: true,
       });
       effectLinesScrollWrap = scrollWrap;
     }
   }
   function scheduleEffectLinesUpdate() {
     if (effectLinesRAF) return;
     effectLinesRAF = requestAnimationFrame(() => {
       effectLinesRAF = null;
       updateEffectLines();
     });
   }
   function getEffectLinesLayer() {
     const rail = document.querySelector(".top-rail.skills");
     if (!rail) return null;
     const iconBar = rail.querySelector(".icon-bar");
     if (!iconBar) return null;
     if (effectLinesLayer && effectLinesLayer.isConnected) {
       return effectLinesLayer;
     }
     const existing = iconBar.querySelector(".effect-lines-layer");
     if (existing) {
       effectLinesLayer = existing;
       return effectLinesLayer;
     }
     const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
     svg.classList.add("effect-lines-layer");
     svg.setAttribute("aria-hidden", "true");
     iconBar.insertBefore(svg, iconBar.firstChild);
     effectLinesLayer = svg;
     return effectLinesLayer;
   }
   function updateEffectLines() {
     if (!effectState.sourceIcon || !effectState.sourceIcon.isConnected) {
       clearEffectLines();
       return;
     }
     if (effectState.expiresAt <= Date.now()) {
       clearEffectLines();
       return;
     }
     const layer = getEffectLinesLayer();
     if (!layer) return;
     const rail = layer.closest(".top-rail.skills");
     if (!rail) return;
     const iconBar = rail.querySelector(".icon-bar");
     if (!iconBar) return;
     bindEffectLinesEvents();
     const barRect = iconBar.getBoundingClientRect();
     const width = Math.max(1, Math.round(iconBar.scrollWidth || barRect.width));
     const height = Math.max(1, Math.round(iconBar.clientHeight || barRect.height));
     layer.style.left = "0px";
     layer.style.top = "0px";
     layer.setAttribute("width", String(width));
     layer.setAttribute("height", String(height));
     layer.setAttribute("viewBox", `0 0 ${width} ${height}`);
     layer.classList.remove("effect-lines-returning");
     const srcRect = effectState.sourceIcon.getBoundingClientRect();
     const startX =
       srcRect.left + srcRect.width / 2 - barRect.left + iconBar.scrollLeft;
     const startY = srcRect.top + srcRect.height / 2 - barRect.top;
     const allIcons = Array.from(
       document.querySelectorAll(".icon-bar .skill-icon[data-index]")
     ).filter((icon) => !icon.classList.contains("weapon-bar-toggle"));
     const targets = allIcons.filter((icon) => {
       if (icon === effectState.sourceIcon) return false;
       const name = getSkillNameFromIcon(icon);
       return name && effectState.skills.has(name);
     });
     if (!targets.length) {
       clearEffectLines();
       return;
     }
     const frag = document.createDocumentFragment();
     const baselinePadding = 10;
     const baselineExtra = 12;
     const baselineY = Math.max(
       startY,
       height - baselinePadding + baselineExtra
     );
     const targetPoints = targets.map((target) => {
       const tgtRect = target.getBoundingClientRect();
       const endX =
         tgtRect.left + tgtRect.width / 2 - barRect.left + iconBar.scrollLeft;
       const endY = tgtRect.top + tgtRect.height / 2 - barRect.top;
       return { x: endX, y: endY };
     });
     const xs = targetPoints.map((p) => p.x);
     const minX = Math.min(startX, ...xs);
     const maxX = Math.max(startX, ...xs);
     effectLinesLastState = {
       startX,
       startY,
       baselineY,
       targets: targetPoints.map((p) => ({
         x: p.x,
         y: p.y,
         dist: Math.hypot(p.x - startX, p.y - startY),
       })),
     };
     const dParts = [
       `M ${startX} ${startY}`,
       `L ${startX} ${baselineY}`,
       `L ${minX} ${baselineY}`,
       `L ${maxX} ${baselineY}`,
     ];
     targetPoints.forEach((p) => {
       dParts.push(`M ${p.x} ${baselineY} L ${p.x} ${p.y}`);
     });
     const d = dParts.join(" ");
     const glow = document.createElementNS(
       "http://www.w3.org/2000/svg",
       "path"
     );
     glow.setAttribute("d", d);
     glow.classList.add("effect-line", "effect-line-glow");
     const core = document.createElementNS(
       "http://www.w3.org/2000/svg",
       "path"
     );
     core.setAttribute("d", d);
     core.classList.add("effect-line", "effect-line-core");
     frag.appendChild(glow);
     frag.appendChild(core);
     requestAnimationFrame(() => {
       const length = Math.max(1, Math.round(core.getTotalLength()));
       [core, glow].forEach((path) => {
         path.style.setProperty("--effect-line-length", `${length}`);
         path.style.strokeDasharray = `${length}`;
         path.style.strokeDashoffset = "0";
       });
     });
     layer.replaceChildren(frag);
   }
   function animateEffectLinesReturn() {
     const layer =
       (effectLinesLayer && effectLinesLayer.isConnected
         ? effectLinesLayer
         : document.querySelector(".effect-lines-layer")) || null;
     if (!layer || !effectLinesLastState) {
       clearEffectLines();
       return;
     }
     const { startX, startY, baselineY, targets } = effectLinesLastState;
     const sortedTargets = [...targets].sort((a, b) => b.dist - a.dist);
     const returnDuration = 1.1;
     const returnStagger = 0.22;
     const frag = document.createDocumentFragment();
     sortedTargets.forEach((target, idx) => {
       const d = [
         `M ${startX} ${startY}`,
         `L ${startX} ${baselineY}`,
         `L ${target.x} ${baselineY}`,
         `L ${target.x} ${target.y}`,
       ].join(" ");
       const glow = document.createElementNS(
         "http://www.w3.org/2000/svg",
         "path"
       );
       glow.setAttribute("d", d);
       glow.classList.add(
         "effect-line",
         "effect-line-glow",
         "effect-line-returning"
       );
       glow.style.setProperty("--effect-return-duration", `${returnDuration}s`);
       glow.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);
       const core = document.createElementNS(
         "http://www.w3.org/2000/svg",
         "path"
       );
       core.setAttribute("d", d);
       core.classList.add(
         "effect-line",
         "effect-line-core",
         "effect-line-returning"
       );
       core.style.setProperty("--effect-return-duration", `${returnDuration}s`);
       core.style.setProperty("--effect-return-delay", `${idx * returnStagger}s`);
       frag.appendChild(glow);
       frag.appendChild(core);
     });
     layer.replaceChildren(frag);
     requestAnimationFrame(() => {
       const paths = Array.from(layer.querySelectorAll("path.effect-line"));
       paths.forEach((path) => {
         const length = Math.max(1, Math.round(path.getTotalLength()));
         path.style.setProperty("--effect-line-length", `${length}`);
         path.style.strokeDasharray = `${length} ${length}`;
         path.style.strokeDashoffset = "0";
       });
     });
     if (effectLinesCleanupTimer) {
       clearTimeout(effectLinesCleanupTimer);
     }
     effectLinesCleanupTimer = setTimeout(() => {
       clearEffectLines();
     }, (returnDuration + returnStagger * Math.max(0, targets.length - 1)) * 1000 + 120);
   }
   function clearEffectLines() {
     if (effectLinesCleanupTimer) {
       clearTimeout(effectLinesCleanupTimer);
       effectLinesCleanupTimer = null;
     }
     const layer =
       (effectLinesLayer && effectLinesLayer.isConnected
         ? effectLinesLayer
         : document.querySelector(".effect-lines-layer")) || null;
     if (layer) layer.remove();
     effectLinesLayer = null;
   }
   function applyEffectClasses() {
     const isActive = effectState.expiresAt > Date.now();
     const rail = document.querySelector(".top-rail.skills");
     if (rail) rail.classList.toggle("effect-mode-on", isActive);
     document
       .querySelectorAll(".icon-bar .skill-icon[data-index]")
       .forEach((icon) => {
         if (icon.classList.contains("weapon-bar-toggle")) return;
         const name = getSkillNameFromIcon(icon);
         const should =
           isActive && name && effectState.skills.has(name);
         icon.classList.toggle("effect-active", !!should);
         if (!should) {
           icon.style.removeProperty("--effect-strength");
         }
       });
     if (isActive) {
       bindEffectLinesEvents();
       scheduleEffectLinesUpdate();
     }
   }
   function clearEffectState() {
     const activeIcon = document.querySelector(
       ".icon-bar .skill-icon.active"
     );
     const activeName = getSkillNameFromIcon(activeIcon);
     const wasAffected =
       activeName && effectState.skills.has(activeName);
     animateEffectLinesReturn();
     if (effectState.timer) {
       clearTimeout(effectState.timer);
       effectState.timer = null;
     }
     effectState.skills.clear();
     effectState.videos.clear();
     effectState.expiresAt = 0;
     effectState.sourceIcon = null;
     applyEffectClasses();
     if (wasAffected && activeIcon) {
       activeIcon.dispatchEvent(new Event("click", { bubbles: true }));
     }
   }
   function activateEffectFromIcon(iconEl) {
     const effectRaw = getCachedJSON(iconEl, "effect");
     const normalized = normalizeEffectData(effectRaw);
     if (!normalized) return;
     effectState.skills = new Set(normalized.skills);
     effectState.videos = normalized.videos;
     effectState.expiresAt = Date.now() + normalized.timeMs;
     effectState.sourceIcon = iconEl;
     if (effectState.timer) clearTimeout(effectState.timer);
     effectState.timer = setTimeout(() => {
       clearEffectState();
     }, normalized.timeMs + 5);
     applyEffectClasses();
   }
   function getEffectVideoForIcon(iconEl) {
     if (!iconEl) return "";
     if (effectState.expiresAt <= Date.now()) return "";
     const name = getSkillNameFromIcon(iconEl);
     if (!name || !effectState.skills.has(name)) return "";
     if (effectState.videos.has(name)) return effectState.videos.get(name) || "";
     return "";
   }
   function getEffectiveSkillVideoFromIcon(iconEl) {
     const weaponOn = globalWeaponEnabled;
     const weaponData = getWeaponDataForIcon(iconEl);
     const baseVideoFile = (iconEl.dataset.videoFile || "").trim();
     const baseVideoURL = (iconEl.dataset.video || "").trim();
     // console.log('[Skills DEBUG]', {
     //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
     //     weaponOn,
     //     hasWeaponData: !!weaponData,
     //     weaponData: weaponData,
     //     baseVideoFile,
     //     baseVideoURL
     // });
     const effectVideo = getEffectVideoForIcon(iconEl);
     if (effectVideo && effectVideo.trim() !== "") {
       return effectVideo.trim();
     }
     if (weaponOn && weaponData) {
       // console.log('[Skills] checking weapon video', {
       //     skillName: iconEl.dataset.nome || iconEl.dataset.name,
       //     hasVideo: !!(weaponData.video),
       //     videoValue: weaponData.video,
       //     videoTrimmed: weaponData.video ? weaponData.video.trim() : 
       // });
       if (weaponData.video && weaponData.video.trim() !== "") {
         // console.log('[Skills] video escolhido (weapon)', iconEl.dataset.nome || iconEl.dataset.name, weaponData.video);
         return weaponData.video.trim();
       }
     }
     // Sistema genérico de swap: verifica character_videos se houver personagem ativo
     if (activeCharacter !== null && iconEl.dataset.characterVideos) {
       try {
         const characterVideos = JSON.parse(iconEl.dataset.characterVideos);
         const characterVideo = characterVideos[activeCharacter];
         if (characterVideo && characterVideo.trim() !== "") {
           return characterVideo.trim();
         }
       } catch (e) {
         console.warn("[Swap] Erro ao processar character_videos:", e);
       }
     }
     // form_switch (Change Form): usa vídeo da PRÓXIMA forma (transição atual → próxima)
     const isFormSwitch =
       iconEl.dataset.formSwitch === "true" ||
       iconEl.getAttribute("data-form-switch") === "true";
     if (isFormSwitch) {
       const formVideosRaw =
         iconEl.dataset.formVideos || iconEl.getAttribute("data-form-videos");
       if (formVideosRaw) {
         try {
           const videos = JSON.parse(formVideosRaw);
           let formVideo = "";
           const nextForm = typeof getNextFormName === "function" ? getNextFormName() : null;
           if (nextForm) {
             formVideo = videos[nextForm] || "";
           }
           // Fallback: se nextForm for null (formsData ainda não carregado), usa primeiro vídeo disponível
           if ((!formVideo || formVideo.trim() === "") && Object.keys(videos).length > 0) {
             const firstKey = Object.keys(videos)[0];
             formVideo = videos[firstKey] || "";
           }
           if (formVideo && formVideo.trim() !== "") {
             return formVideo.trim();
           }
         } catch (e) {
           console.warn("[Forms] Erro ao parsear form_videos em getEffectiveSkillVideoFromIcon:", e);
         }
       }
     }
     // Prioriza videoFile (nome do arquivo), mas se estiver vazio, usa video (pode ser URL completa)
     const result = baseVideoFile || baseVideoURL || "";
     // console.log('[Skills] video escolhido (base)', iconEl.dataset.nome || iconEl.dataset.name, result);
     return result;
   }
   function createVideoElement(videoURL, extraAttrs = {}) {
     // Se o vídeo já falhou antes, não cria novo elemento
     if (failedVideosCache.has(videoURL)) {
       return null;
     }
     const v = document.createElement("video");
     v.className = "skill-video";
     v.setAttribute("controls", "");
     v.setAttribute("preload", "auto"); // Mudado de 'metadata' para 'auto' para carregar tudo imediatamente
     v.setAttribute("playsinline", "");
     v.style.display = "none";
     v.style.width = "100%";
     v.style.height = "auto";
     v.style.aspectRatio = "16/9";
     v.style.objectFit = "cover";
     Object.keys(extraAttrs).forEach((k) => {
       v.dataset[k] = extraAttrs[k];
     });
     // Detectar formato do vídeo pela extensão
     const ext = (videoURL.split(".").pop() || "").toLowerCase().split("?")[0];
     const mimeTypes = {
       mp4: "video/mp4",
       m4v: "video/mp4",
       webm: "video/webm",
       ogv: "video/ogg",
       ogg: "video/ogg",
       mov: "video/quicktime",
     };
     const mimeType = mimeTypes[ext] || "video/mp4";
     const src = document.createElement("source");
     src.src = videoURL;
     src.type = mimeType;
     v.appendChild(src);
     // Fallback para Safari/iOS mais antigos
     v.setAttribute("webkit-playsinline", "");
     v.setAttribute("x-webkit-airplay", "allow");
     // Tratamento silencioso de erros - marca como falhado e não tenta mais
     let errorHandled = false;
     v.addEventListener(
       "error",
       (e) => {
         if (errorHandled) return;
         errorHandled = true;
         // Marca o vídeo como falhado para não tentar carregar novamente
         failedVideosCache.add(videoURL);
         // Remove o vídeo do DOM se estiver lá
         if (v.parentNode) {
           v.parentNode.removeChild(v);
         }
         // Avisa apenas uma vez sobre vídeos faltantes
         if (!missingVideosReported.has(videoURL)) {
           missingVideosReported.add(videoURL);
           // Extrai nome do arquivo da URL para o aviso
           const fileName =
             videoURL.split("/").pop().split("?")[0] || videoURL;
           console.info(
             `[Skills] Vídeo não encontrado na wiki: ${decodeURIComponent(
               fileName
             )}. Este aviso aparecerá apenas uma vez.`
           );
         }
       },
       { once: true }
     );
     return v;
   }
   // Função recursiva para carregar TODOS os vídeos de subskills (incluindo sub-subskills)
   function preloadSubskillVideosRecursively(
     subs,
     parentIdx,
     parentPath = ""
   ) {
     const vb = getVideoBox();
     if (!vb || !Array.isArray(subs)) return 0;
     let createdCount = 0;
     subs.forEach((s) => {
       const subName = (s.name || s.n || "").trim();
       const currentPath = parentPath ? `${parentPath}:${subName}` : subName;
       // Vídeo normal da subskill
       if (s.video && s.video.trim() !== "") {
         const key = `sub:${parentIdx}:${currentPath}`;
         if (!subskillVideosCache.has(key)) {
           const videoURL = normalizeFileURL(s.video);
           if (
             videoURL &&
             videoURL.trim() !== "" &&
             !failedVideosCache.has(videoURL)
           ) {
             const v = createVideoElement(videoURL, {
               sub: "1",
               parentIndex: parentIdx,
               subName: currentPath,
               cacheKey: key,
             });
             if (v) {
               // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
               vb.appendChild(v);
               subskillVideosCache.set(key, v);
               createdCount++;
               // FORÇA carregamento imediatamente (apenas uma vez)
               v.load();
             }
           }
         }
       }
       // Vídeo de weapon da subskill (sempre carrega, independente do toggle)
       if (
         s.weapon &&
         typeof s.weapon === "object" &&
         s.weapon.video &&
         s.weapon.video.trim() !== ""
       ) {
         const weaponKey = `sub:${parentIdx}:${currentPath}:weapon`;
         if (!subskillVideosCache.has(weaponKey)) {
           const weaponVideoURL = normalizeFileURL(s.weapon.video);
           if (
             weaponVideoURL &&
             weaponVideoURL.trim() !== "" &&
             !failedVideosCache.has(weaponVideoURL)
           ) {
             const v = createVideoElement(weaponVideoURL, {
               sub: "1",
               parentIndex: parentIdx,
               subName: currentPath,
               weapon: "1",
               cacheKey: weaponKey,
             });
             if (v) {
               // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
               vb.appendChild(v);
               subskillVideosCache.set(weaponKey, v);
               createdCount++;
               // FORÇA carregamento imediatamente (apenas uma vez)
               v.load();
             }
           }
         }
       }
       // RECURSÃO: processa sub-subskills (SEMPRE processa, mesmo se o vídeo já estiver no cache)
       if (Array.isArray(s.subs) && s.subs.length > 0) {
         createdCount += preloadSubskillVideosRecursively(
           s.subs,
           parentIdx,
           currentPath
         );
       }
     });
     return createdCount;
   }
   function precreateSubskillVideos() {
     if (!getVideoBox()) return;
     let createdCount = 0;
     iconItems.forEach((parentIcon) => {
       const subs = getCachedJSON(parentIcon, "subs");
       if (!Array.isArray(subs)) return;
       const parentIdx = parentIcon.dataset.index || "";
       createdCount += preloadSubskillVideosRecursively(subs, parentIdx);
     });
   }
   // Função para pré-carregar TODOS os vídeos recursivamente (principais, subskills, weapon, sub-subskills)
   function preloadAllVideosRecursively() {
     const vb = getVideoBox();
     if (!vb || !iconItems.length) return;
     // 1. Carregar vídeos de skills principais
     iconItems.forEach((el) => {
       const idx = el.dataset.index || "";
       if (!idx) return;
       // Vídeo normal da skill principal
       // form_switch com form_videos: NÃO pré-carrega o vídeo base (ex: Change Form-Video.mp4) pois pode não existir
       const isFormSwitchPreload =
         el.dataset.formSwitch === "true" ||
         el.getAttribute("data-form-switch") === "true";
       const hasFormVideos =
         (el.dataset.formVideos || el.getAttribute("data-form-videos") || "").trim() !== "";
       const skipBaseVideo = isFormSwitchPreload && hasFormVideos;
       let src = "";
       if (!skipBaseVideo) {
         src = (el.dataset.videoFile || "").trim();
         if (!src) {
           const videoAttr = (el.dataset.video || "").trim();
           if (videoAttr) {
             src = videoAttr.includes("/") || videoAttr.startsWith("http")
               ? videoAttr
               : videoAttr;
           }
         }
       }
       if (src && !videosCache.has(idx)) {
         const videoURL = normalizeFileURL(src);
         if (videoURL && !failedVideosCache.has(videoURL)) {
           const v = createVideoElement(videoURL, {
             index: idx,
           });
           if (v) {
             // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
             totalVideos++;
             v.style.maxWidth = "100%";
             v.addEventListener(
               "canplaythrough",
               () => {
                 loadedVideos++;
                 if (!userHasInteracted && loadedVideos === 1) {
                   try {
                     v.pause();
                     v.currentTime = 0;
                   } catch (e) { }
                 }
                 if (loadedVideos === totalVideos) autoplay = true;
               },
               {
                 once: true,
               }
             );
             v.addEventListener(
               "error",
               () => {
                 loadedVideos++;
                 if (loadedVideos === totalVideos) autoplay = true;
               },
               {
                 once: true,
               }
             );
             vb.appendChild(v);
             videosCache.set(idx, v);
             nestedVideoElByIcon.set(el, v);
             // Força carregamento imediatamente (apenas uma vez)
             v.load();
           }
         }
       }
       // form_switch: pré-carrega TODOS os vídeos de form_videos para transições
       const isFormSwitch =
         el.dataset.formSwitch === "true" ||
         el.getAttribute("data-form-switch") === "true";
       if (isFormSwitch) {
         const formVideosRaw =
           el.dataset.formVideos || el.getAttribute("data-form-videos");
         if (formVideosRaw) {
           try {
             const videos = JSON.parse(formVideosRaw);
             Object.keys(videos).forEach((formName) => {
               const videoFile = videos[formName];
               if (videoFile && videoFile.trim() !== "") {
                 const videoKey = idx + ":" + videoFile;
                 if (!videosCache.has(videoKey)) {
                   const videoURL = normalizeFileURL(videoFile);
                   if (videoURL && !failedVideosCache.has(videoURL)) {
                     const v = createVideoElement(videoURL, { index: idx });
                     if (v) {
                       totalVideos++;
                       v.addEventListener(
                         "canplaythrough",
                         () => {
                           loadedVideos++;
                           if (loadedVideos === totalVideos) autoplay = true;
                         },
                         { once: true }
                       );
                       v.addEventListener(
                         "error",
                         () => {
                           loadedVideos++;
                           if (loadedVideos === totalVideos) autoplay = true;
                         },
                         { once: true }
                       );
                       vb.appendChild(v);
                       videosCache.set(videoKey, v);
                       v.load();
                     }
                   }
                 }
               }
             });
           } catch (e) {
             console.warn("[Forms] Erro ao pré-carregar form_videos:", e);
           }
         }
       }
       // Vídeo de weapon (sempre carrega, independente do toggle)
       const weaponData = getCachedJSON(el, "weapon");
       if (
         weaponData &&
         weaponData.video &&
         weaponData.video.trim() !== ""
       ) {
         const weaponKey = `weapon:${idx}:${(
           el.dataset.nome ||
           el.dataset.name ||
           ""
         ).trim()}`;
         if (!videosCache.has(weaponKey)) {
           const weaponVideoURL = normalizeFileURL(weaponData.video);
           if (weaponVideoURL && !failedVideosCache.has(weaponVideoURL)) {
             const v = createVideoElement(weaponVideoURL, {
               index: idx,
               weapon: "1",
             });
             if (v) {
               // Só adiciona se o vídeo foi criado (não estava no cache de falhas)
               totalVideos++;
               v.style.maxWidth = "100%";
               v.addEventListener(
                 "canplaythrough",
                 () => {
                   loadedVideos++;
                   if (loadedVideos === totalVideos) autoplay = true;
                 },
                 {
                   once: true,
                 }
               );
               v.addEventListener(
                 "error",
                 () => {
                   loadedVideos++;
                   if (loadedVideos === totalVideos) autoplay = true;
                 },
                 {
                   once: true,
                 }
               );
               vb.appendChild(v);
               videosCache.set(weaponKey, v);
               // Força carregamento imediatamente (apenas uma vez)
               v.load();
             }
           }
         }
       }
     });
     // 2. Carregar vídeos de subskills (recursivamente)
     precreateSubskillVideos();
   }
   // Função para pré-carregar TODOS os ícones recursivamente
   function preloadAllIconsRecursively() {
     const iconCache = new Set();
     // Função recursiva para processar subskills
     function processSubskillsRecursively(subs) {
       if (!Array.isArray(subs)) return;
       subs.forEach((s) => {
         const icon = (s.icon || "").trim();
         if (icon) {
           const iconURL = normalizeFileURL(icon);
           if (iconURL && !iconCache.has(iconURL)) {
             iconCache.add(iconURL);
             const img = new Image();
             img.decoding = "async";
             img.loading = "eager";
             img.referrerPolicy = "same-origin";
             img.src = iconURL;
             imagePreloadCache.set(iconURL, img);
           }
         }
         // Recursão para sub-subskills
         if (Array.isArray(s.subs)) {
           processSubskillsRecursively(s.subs);
         }
       });
     }
     // Carregar ícones de skills principais
     iconItems.forEach((el) => {
       const img = el.querySelector("img");
       if (img && img.src) {
         const iconURL = img.src;
         if (iconURL && !iconCache.has(iconURL)) {
           iconCache.add(iconURL);
           // Já está no DOM, mas força pré-carregamento
           const preloadImg = new Image();
           preloadImg.decoding = "async";
           preloadImg.loading = "eager";
           preloadImg.referrerPolicy = "same-origin";
           preloadImg.src = iconURL;
           imagePreloadCache.set(iconURL, preloadImg);
         }
       }
       // Carregar ícones de subskills recursivamente
       const subs = getCachedJSON(el, "subs");
       if (Array.isArray(subs)) {
         processSubskillsRecursively(subs);
       }
     });
   }
   // Função principal que carrega TUDO imediatamente
   function preloadAllAssets() {
     // Carregar TUDO imediatamente - sem lazy loading
     // 1. Carregar TODOS os vídeos (principais, subskills, weapon, sub-subskills)
     preloadAllVideosRecursively();
     // 2. Carregar TODOS os ícones (principais, subskills, sub-subskills)
     preloadAllIconsRecursively();
   }
   // Executa pré-carregamento imediatamente
   preloadAllAssets();
   function wireTooltipsForNewIcons() {
     const tip = document.querySelector(".skill-tooltip");
     if (!tip) return;
     let lockUntil2 = 0;
     Array.from(document.querySelectorAll(".icon-bar .skill-icon")).forEach(
       (icon) => {
         if (
           icon.dataset.weaponToggle === "1" ||
           icon.classList.contains("weapon-bar-toggle")
         )
           return;
         if (icon.dataset.tipwired) return;
         icon.dataset.tipwired = "1";
         const label =
           icon.dataset.nome || icon.dataset.name || icon.title || "";
         if (label && !icon.hasAttribute("aria-label"))
           icon.setAttribute("aria-label", label);
         if (icon.hasAttribute("title")) icon.removeAttribute("title");
         const img = icon.querySelector("img");
         if (img) {
           const imgAlt = img.getAttribute("alt") || "";
           const imgTitle = img.getAttribute("title") || "";
           if (!label && (imgAlt || imgTitle))
             icon.setAttribute("aria-label", imgAlt || imgTitle);
           img.setAttribute("alt", "");
           if (img.hasAttribute("title")) img.removeAttribute("title");
         }
         const measureAndPos = (el) => {
           if (!el || tip.getAttribute("aria-hidden") === "true") return;
           tip.style.left = "0px";
           tip.style.top = "0px";
           const rect = el.getBoundingClientRect();
           const tr = tip.getBoundingClientRect();
           let left = Math.round(rect.left + (rect.width - tr.width) / 2);
           left = Math.max(
             8,
             Math.min(left, window.innerWidth - tr.width - 8)
           );
           const coarse =
             (window.matchMedia && matchMedia("(pointer: coarse)").matches) ||
             window.innerWidth <= 600;
           let top = coarse
             ? Math.round(rect.bottom + 10)
             : Math.round(rect.top - tr.height - 8);
           if (top < 8) top = Math.round(rect.bottom + 10);
           tip.style.left = left + "px";
           tip.style.top = top + "px";
         };
         const show = (el, text) => {
           tip.textContent = text || "";
           tip.setAttribute("aria-hidden", "false");
           measureAndPos(el);
           tip.style.opacity = "1";
         };
         const hide = () => {
           tip.setAttribute("aria-hidden", "true");
           tip.style.opacity = "0";
           tip.style.left = "-9999px";
           tip.style.top = "-9999px";
         };
         icon.addEventListener("mouseenter", () =>
           show(icon, icon.dataset.nome || icon.dataset.name || "")
         );
         icon.addEventListener("mousemove", () => {
           if (performance.now() >= lockUntil2) measureAndPos(icon);
         });
         icon.addEventListener("click", () => {
           lockUntil2 = performance.now() + 240;
           measureAndPos(icon);
         });
         icon.addEventListener("mouseleave", hide);
       }
     );
   }
   function showVideoForIcon(el) {
     userHasInteracted = true;
     const videoBox = getVideoBox();
     if (!videoBox) return;
     const effectiveVideo = getEffectiveSkillVideoFromIcon(el);
     if (!effectiveVideo || effectiveVideo.trim() === "") {
       videoBox.style.display = "none";
       return;
     }
     const videoURL = normalizeFileURL(effectiveVideo);
     if (!videoURL || videoURL.trim() === "") {
       videoBox.style.display = "none";
       return;
     }
     Array.from(videoBox.querySelectorAll("video.skill-video")).forEach(
       (v) => {
         try {
           v.pause();
         } catch (e) { }
         v.style.display = "none";
       }
     );
     if (window.__subskills) window.__subskills.hideAll?.(videoBox);
     const hasIdx = !!el.dataset.index;
     const weaponOn = globalWeaponEnabled;
     const weaponData = getWeaponDataForIcon(el);
     const isWeaponVideo =
       weaponOn &&
       weaponData &&
       weaponData.video &&
       weaponData.video.trim() !== "";
     const effectVideo = getEffectVideoForIcon(el);
     const isEffectVideo = !!(effectVideo && effectVideo.trim() !== "");
     const effectKey = isEffectVideo
       ? getEffectVideoKey(el, effectiveVideo)
       : "";
     // console.log('[Skills] showVideoForIcon chamado', {
     //     skillName: el.dataset.nome || el.dataset.name,
     //     weaponOn,
     //     isWeaponVideo,
     //     effectiveVideo: getEffectiveSkillVideoFromIcon(el)
     // });
     const videoKey = isWeaponVideo
       ? `weapon:${getWeaponKey(el)}`
       : isEffectVideo
         ? effectKey
         : el.dataset.index || "";
     const isSubskill = !hasIdx || el.dataset.nested === "1";
     const parentIdx = el.dataset.parentIndex || "";
     const subName =
       el.dataset.subName || el.dataset.nome || el.dataset.name || "";
     if (
       hasIdx &&
       !isWeaponVideo &&
       !isEffectVideo &&
       videosCache.has(el.dataset.index)
     ) {
       const v = videosCache.get(el.dataset.index);
       // Verifica se o vídeo cacheado corresponde ao vídeo atual da skill
       // (ao trocar de form, o índice é reutilizado mas o vídeo muda)
       const cachedSrc = v.querySelector("source")?.getAttribute("src") || "";
       const currentVideoFile = (el.dataset.videoFile || "").trim();
       const currentVideoURL = currentVideoFile ? normalizeFileURL(currentVideoFile) : "";
       const decodeCached = decodeURIComponent(cachedSrc.split("/").pop().split("?")[0] || "");
       const decodeTarget = decodeURIComponent((currentVideoURL || currentVideoFile).split("/").pop().split("?")[0] || "");
       const srcMismatch = decodeTarget && decodeCached && decodeCached !== decodeTarget;
       if (!srcMismatch) {
         videoBox.style.display = "block";
         v.style.display = "block";
         try {
           v.currentTime = 0;
         } catch (e) { }
         const suppress = document.body.dataset.suppressSkillPlay === "1";
         if (!suppress) {
           v.play().catch(() => { });
         } else {
           try {
             v.pause();
           } catch (e) { }
         }
         return;
       }
       // Vídeo não corresponde - remove do cache e do DOM
       v.style.display = "none";
       try { v.pause(); } catch (e) { }
       if (v.parentNode) v.parentNode.removeChild(v);
       videosCache.delete(el.dataset.index);
     }
     // Para form_switch, permite criação dinâmica de vídeos (transições de forma)
     // Vídeos normais devem estar pré-carregados
     const isFormSwitch =
       el.dataset.formSwitch === "true" ||
       el.getAttribute("data-form-switch") === "true";
     let v = null;
     if (isWeaponVideo) {
       const weaponKeyFull = `weapon:${getWeaponKey(el)}`;
       v = videosCache.get(weaponKeyFull);
       if (!v && isSubskill && parentIdx && subName) {
         // Tenta buscar vídeo de weapon de subskill
         // O cache usa o formato: sub:${parentIdx}:${path}:weapon onde path NÃO inclui o nome da skill principal
         // O subName agora já está no formato correto (sem nome da skill principal)
         // Tenta buscar diretamente com o subName
         let subWeaponKey = `sub:${parentIdx}:${subName}:weapon`;
         v = subskillVideosCache.get(subWeaponKey);
         // Se não encontrou, tenta buscar recursivamente
         if (!v) {
           const basePattern = `sub:${parentIdx}:`;
           const weaponSuffix = ":weapon";
           const searchName = subName.split(":").pop();
           for (const [key, video] of subskillVideosCache.entries()) {
             if (key.startsWith(basePattern) && key.endsWith(weaponSuffix)) {
               const pathInKey = key.substring(
                 basePattern.length,
                 key.length - weaponSuffix.length
               );
               // Tenta match exato, ou se termina com o nome da skill
               if (
                 pathInKey === subName ||
                 pathInKey.endsWith(`:${searchName}`) ||
                 pathInKey === searchName
               ) {
                 v = video;
                 break;
               }
             }
           }
         }
       }
       if (!v) {
         // Tenta buscar pelo padrão antigo também
         v = videoBox.querySelector(`video[data-weapon-key="${videoKey}"]`);
       }
     } else if (isEffectVideo && effectKey) {
       v = videosCache.get(effectKey);
     } else {
       if (isSubskill && parentIdx && subName) {
         // Busca vídeo de subskill no cache correto
         // O cache usa o formato: sub:${parentIdx}:${path} onde path NÃO inclui o nome da skill principal
         // O subName agora já está no formato correto (sem nome da skill principal)
         // Tenta buscar diretamente com o subName
         let subKey = `sub:${parentIdx}:${subName}`;
         v = subskillVideosCache.get(subKey);
         // Se não encontrou, tenta buscar recursivamente todas as chaves que começam com o padrão
         // Isso é útil caso haja alguma diferença no formato do caminho
         if (!v) {
           const basePattern = `sub:${parentIdx}:`;
           const searchName = subName.split(":").pop(); // Último segmento do caminho
           // Tenta também buscar apenas pelo último segmento (útil para sub-subskills)
           const lastSegmentKey = `sub:${parentIdx}:${searchName}`;
           v = subskillVideosCache.get(lastSegmentKey);
           // Se ainda não encontrou, faz busca mais ampla
           if (!v) {
             for (const [key, video] of subskillVideosCache.entries()) {
               if (key.startsWith(basePattern)) {
                 const pathInKey = key.substring(basePattern.length);
                 // Tenta match exato, ou se termina com o nome da skill, ou se contém o caminho completo
                 if (
                   pathInKey === subName ||
                   pathInKey.endsWith(`:${searchName}`) ||
                   pathInKey === searchName ||
                   (subName.includes(":") && pathInKey.includes(subName)) ||
                   (subName.includes(":") &&
                     pathInKey.endsWith(
                       subName.split(":").slice(-2).join(":")
                     ))
                 ) {
                   v = video;
                   break;
                 }
               }
             }
           }
         }
       } else {
         v = videosCache.get(el.dataset.index);
         if (!v) {
           v = nestedVideoElByIcon.get(el);
         }
       }
     }
     // Se vídeo não foi encontrado no cache, verifica se falhou antes
     if (!v) {
       // Se o vídeo já foi marcado como falhado, não mostra aviso repetido
       if (failedVideosCache.has(videoURL)) {
         videoBox.style.display = "none";
         return;
       }
       // Para effect, cria vídeo dinamicamente (vídeo alternativo por tempo)
       if (isEffectVideo && videoURL) {
         const baseIndex = el.dataset.index || "";
         const effectCacheKey =
           effectKey || `effect:${effectiveVideo || videoURL}`;
         v = videosCache.get(effectCacheKey);
         if (!v) {
           v = createVideoElement(videoURL, {
             index: baseIndex,
             effect: "1",
           });
           if (v) {
             videoBox.appendChild(v);
             videosCache.set(effectCacheKey, v);
             v.load();
           } else {
             videoBox.style.display = "none";
             return;
           }
         }
       }
       // Para form_switch, cria vídeo dinamicamente (transições de forma são dinâmicas)
       // Usa uma chave única que inclui o nome do arquivo do vídeo para garantir que cada vídeo diferente seja cacheado separadamente
       else if (isFormSwitch && videoURL) {
         const baseIndex = el.dataset.index || "";
         const videoFileName = effectiveVideo || "";
         // Chave única: index + nome do arquivo do vídeo
         const videoKey = baseIndex + ":" + videoFileName;
         v = videosCache.get(videoKey);
         if (!v) {
           v = createVideoElement(videoURL, { index: baseIndex });
           if (v) {
             videoBox.appendChild(v);
             videosCache.set(videoKey, v);
             v.load();
           } else {
             videoBox.style.display = "none";
             return;
           }
         }
       }
       // Vídeos de form skills ou outros que não estão no cache - cria dinamicamente
       else if (videoURL) {
         const idx = el.dataset.index || "";
         v = createVideoElement(videoURL, { index: idx });
         if (v) {
           videoBox.appendChild(v);
           if (idx) videosCache.set(idx, v);
           v.load();
         } else {
           videoBox.style.display = "none";
           return;
         }
       } else {
         videoBox.style.display = "none";
         return;
       }
     }
     videoBox.style.display = "block";
     v.style.display = "block";
     try {
       v.currentTime = 0;
     } catch (e) { }
     const suppress = document.body.dataset.suppressSkillPlay === "1";
     if (!suppress) {
       v.play().catch(() => { });
     } else {
       try {
         v.pause();
       } catch (e) { }
     }
   }
   function activateSkill(el, options = {}) {
     const { openSubs = true } = options;
     const tip = document.querySelector(".skill-tooltip");
     if (tip) {
       tip.setAttribute("aria-hidden", "true");
       tip.style.opacity = "0";
       tip.style.left = "-9999px";
       tip.style.top = "-9999px";
     }
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const L = i18nMap[getLangKey()] ||
       i18nMap.pt || {
       cooldown: "Recarga",
       energy_gain: "Ganho de energia",
       energy_cost: "Custo de energia",
       power: "Poder",
       power_pvp: "Poder PvP",
       level: "Nível",
     };
     const name = el.dataset.nome || el.dataset.name || "";
     if (el.dataset.effect) {
       activateEffectFromIcon(el);
     }
     let weaponData = null;
     if (el.dataset.weapon) {
       try {
         const parsed = JSON.parse(el.dataset.weapon);
         // Só considera weapon válido se for um objeto não vazio
         if (
           parsed &&
           typeof parsed === "object" &&
           Object.keys(parsed).length > 0
         ) {
           weaponData = parsed;
         }
       } catch (e) {
         weaponData = null;
       }
     }
     const hasWeapon = !!weaponData;
     const weaponEquipped = hasWeapon && globalWeaponEnabled;
     // Level: usa o level da weapon se estiver ativa e tiver level, senão usa o level da skill base
     let level = (el.dataset.level || "").trim();
     if (weaponEquipped && weaponData) {
       // Verifica se weapon tem level definido (pode ser número ou string)
       const weaponLevel = weaponData.level;
       if (
         weaponLevel !== undefined &&
         weaponLevel !== null &&
         weaponLevel !== ""
       ) {
         level = weaponLevel.toString().trim();
       }
     }
     const lang = getLangKey();
     const baseDescPack = {
       pt: el.dataset.descPt || "",
       en: el.dataset.descEn || "",
       es: el.dataset.descEs || "",
       pl: el.dataset.descPl || "",
     };
     const baseDesc =
       baseDescPack[lang] ||
       baseDescPack.pt ||
       baseDescPack.en ||
       baseDescPack.es ||
       baseDescPack.pl ||
       el.dataset.desc ||
       "";
     // Aceita tanto desc_i18n quanto desc para compatibilidade
     let weaponDescPack = {};
     if (weaponData) {
       if (weaponData.desc_i18n) {
         weaponDescPack = weaponData.desc_i18n;
       } else if (weaponData.desc) {
         weaponDescPack = weaponData.desc;
       } else {
         weaponDescPack = {
           pt: weaponData.descPt || "",
           en: weaponData.descEn || "",
           es: weaponData.descEs || "",
           pl: weaponData.descPl || "",
         };
       }
     }
     const weaponDesc =
       weaponDescPack[lang] ||
       weaponDescPack.pt ||
       weaponDescPack.en ||
       weaponDescPack.es ||
       weaponDescPack.pl ||
       "";
     const chosenDesc = weaponEquipped && weaponDesc ? weaponDesc : baseDesc;
     const descHtml = chosenDesc.replace(/(.*?)/g, "$1");
     let attrsHTML = "";
     if (weaponEquipped && weaponData) {
       // Faz merge: usa atributos da skill base e substitui apenas os que existem no weapon
       // Parse dos atributos da skill base (formato: "pve, pvp, energy, cooldown" ou "pve, pvp, energy, cooldown")
       const baseAttrsStr = el.dataset.atr || "";
       // Suporta tanto ", " quanto "," como separador
       const baseAttrs = baseAttrsStr.split(/\s*,\s*/).map((a) => a.trim());
       const basePve =
         baseAttrs[0] && baseAttrs[0] !== "-" ? baseAttrs[0] : "";
       const basePvp =
         baseAttrs[1] && baseAttrs[1] !== "-" ? baseAttrs[1] : "";
       const baseEnergy =
         baseAttrs[2] && baseAttrs[2] !== "-" ? baseAttrs[2] : "";
       const baseCd = baseAttrs[3] && baseAttrs[3] !== "-" ? baseAttrs[3] : "";
       // Valores do weapon (substituem os da skill base se existirem)
       const wPve =
         weaponData.powerpve !== undefined &&
           weaponData.powerpve !== null &&
           weaponData.powerpve !== ""
           ? weaponData.powerpve.toString().trim()
           : basePve;
       const wPvp =
         weaponData.powerpvp !== undefined &&
           weaponData.powerpvp !== null &&
           weaponData.powerpvp !== ""
           ? weaponData.powerpvp.toString().trim()
           : basePvp;
       const wEnergy =
         weaponData.energy !== undefined &&
           weaponData.energy !== null &&
           weaponData.energy !== ""
           ? weaponData.energy.toString().trim()
           : baseEnergy;
       const wCd =
         weaponData.cooldown !== undefined &&
           weaponData.cooldown !== null &&
           weaponData.cooldown !== ""
           ? weaponData.cooldown.toString().trim()
           : baseCd;
       // Monta string de atributos mesclados
       const mergedAttrs = [wPve, wPvp, wEnergy, wCd].join(",");
       attrsHTML = renderAttributes(mergedAttrs);
     } else {
       attrsHTML = el.dataset.atr
         ? renderAttributes(el.dataset.atr)
         : el.dataset.subattrs
           ? renderSubAttributesFromObj(JSON.parse(el.dataset.subattrs), L)
           : "";
     }
     let flagsHTML = "";
     // Debug: verifica se é uma skill do Urouge (verifica pela URL da página ou pelo contexto)
     const isUrougePage =
       window.location.href &&
       window.location.href.toLowerCase().includes("urouge");
     if (el.dataset.flags) {
       try {
         const flags = JSON.parse(el.dataset.flags);
         if (flags && Array.isArray(flags) && flags.length > 0) {
           flagsHTML = renderFlagsRow(flags);
           // Debug para Urouge
           if (isUrougePage) {
             console.log("[Skills] Urouge - Flags processadas:", {
               skill: name || el.dataset.nome,
               flags: flags,
               htmlLength: flagsHTML ? flagsHTML.length : 0,
               hasHTML: !!flagsHTML,
             });
           }
         } else {
           if (isUrougePage) {
             console.warn("[Skills] Urouge - Flags inválidas:", {
               skill: name || el.dataset.nome,
               flags: flags,
               rawData: el.dataset.flags,
             });
           }
         }
       } catch (e) {
         console.warn(
           "[Skills] Erro ao processar flags:",
           e,
           "flags data:",
           el.dataset.flags,
           "skill:",
           name || el.dataset.nome
         );
         if (isUrougePage) {
           console.error(
             "[Skills] Urouge - Erro ao processar flags:",
             e,
             "element:",
             el
           );
         }
       }
     } else {
       // Debug: verifica se deveria ter flags mas não tem (apenas para Urouge)
       if (isUrougePage) {
         console.warn("[Skills] Urouge - Skill sem data-flags:", {
           skill: name || el.dataset.nome,
           element: el,
           allAttributes: Array.from(el.attributes).map(
             (attr) => attr.name + "=" + attr.value
           ),
         });
       }
     }
     if (descBox) {

descBox.innerHTML = `

${name}

${level  ? `

${L.level} ${level}

`

         : ""

}${attrsHTML}

${descHtml}

`;

     }
     if (hasWeapon) {
       applyWeaponBadge(el, weaponData, weaponEquipped);
     }
     const vb = getVideoBox();
     if (vb) {
       const oldFlags = vb.querySelector(".skill-flags");
       if (oldFlags) oldFlags.remove();
       if (flagsHTML) {
         vb.insertAdjacentHTML("beforeend", flagsHTML);
         applyFlagTooltips(vb);
       }
     }
     const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
     currIcons.forEach((i) => i.classList.remove("active"));
     const subsRaw = el.dataset.subs || el.getAttribute("data-subs");
     const isFormSwitch =
       el.dataset.formSwitch === "true" ||
       el.getAttribute("data-form-switch") === "true";
     const isSwap =
       el.dataset.swap === "true" || el.getAttribute("data-swap") === "true";
     // Se for skill de swap, troca personagem (não marca como ativo, não processa como back)
     if (isSwap && !isFormSwitch) {
       handleSwapCharacter(el);
       // Não marca como ativo (similar ao form_switch)
       return;
     }
     // Não marca como ativo se for form_switch (Change Form)
     if (!isFormSwitch) {
       el.classList.add("active");
       if (!autoplay && loadedVideos > 0) autoplay = true;
       window.__lastActiveSkillIcon = el;
       // Lógica de vídeo: usa função centralizada que já considera weapon
       showVideoForIcon(el);
     }
     const 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";
     // Se for form_switch, alterna forma (não processa como back)
     if (isFormSwitch) {
       try {
         const formsJSON = skillsRoot.dataset.forms || "{}";
         if (formsJSON && formsJSON !== "{}") {
           if (Object.keys(formsData).length === 0) {
             formsData = JSON.parse(formsJSON);
           }
         }
         // Inicializa currentForm se necessário
         if (currentForm === null) {
           currentForm = detectCurrentForm();
           const formNames = Object.keys(formsData);
           if (!currentForm && formNames.length > 0) {
             currentForm = formNames[0];
           }
         }
         // Calcula a próxima forma ANTES de trocar
         const nextForm = getNextFormName();
         // Vídeo de transição: usa o vídeo da PRÓXIMA forma
         const formVideosRaw =
           el.dataset.formVideos || el.getAttribute("data-form-videos");
         if (formVideosRaw && nextForm) {
           const videos = JSON.parse(formVideosRaw);
           const transitionVideo = videos[nextForm] || "";
           if (transitionVideo && transitionVideo.trim() !== "") {
             el.dataset.videoFile = transitionVideo;
           }
         }
       } catch (e) {
         console.error("[Forms] Erro ao processar transição:", e);
       }
       showVideoForIcon(el);
       switchForm();
       return;
     }
     if (isBack && !isFormSwitch && barStack.length) {
       const prev = barStack.pop();
       // Restaura currentForm se estava salvo no snapshot
       if (prev.currentForm !== undefined) {
         currentForm = prev.currentForm;
       }
       renderBarFromItems(prev);
       const btn = document.querySelector(".skills-back-wrapper");
       if (btn) btn.style.display = barStack.length ? "block" : "none";
       return;
     }
     if (openSubs && subsRaw && subsRaw.trim() !== "") {
       if (barStack.length && barStack[barStack.length - 1].parentIcon === el)
         return;
       try {
         const subs = JSON.parse(subsRaw);
         pushSubBarFrom(subs, el);
       } catch { }
     }
   }
   function wireClicksForCurrentBar() {
     const currIcons = Array.from(iconsBar.querySelectorAll(".skill-icon"));
     currIcons.forEach((el) => {
       if (
         el.dataset.weaponToggle === "1" ||
         el.classList.contains("weapon-bar-toggle")
       )
         return;
       if (el.dataset.wired) return;
       el.dataset.wired = "1";
       const label = el.dataset.nome || el.dataset.name || "";
       el.setAttribute("aria-label", label);
       if (el.hasAttribute("title")) el.removeAttribute("title");
       const img = el.querySelector("img");
       if (img) {
         img.setAttribute("alt", "");
         if (img.hasAttribute("title")) img.removeAttribute("title");
       }
       el.addEventListener("click", () => {
         activateSkill(el, {
           openSubs: true,
         });
       });
     });
     wireTooltipsForNewIcons();
     applyEffectClasses();
   }
   function animateIconsBarEntrance() {
     Array.from(iconsBar.children).forEach((c, i) => {
       c.style.opacity = "0";
       c.style.transform = "translateY(6px)";
       requestAnimationFrame(() => {
         setTimeout(() => {
           c.style.transition = "opacity .18s ease, transform .18s ease";
           c.style.opacity = "1";
           c.style.transform = "translateY(0)";
         }, i * 24);
       });
     });
   }
   function snapshotCurrentBarItemsFromDOM() {
     const items = Array.from(iconsBar.querySelectorAll(".skill-icon"))
       .filter((el) => el.dataset.weaponToggle !== "1")
       .map((el) => {
         const img = el.querySelector("img");
         const iconURL = img ? img.src : "";
         const subsRaw = el.dataset.subs || el.getAttribute("data-subs") || "";
         let subs = null;
         try {
           subs = subsRaw ? JSON.parse(subsRaw) : null;
         } catch {
           subs = null;
         }
         const subattrsRaw = el.dataset.subattrs || "";
         let flags = null;
         if (el.dataset.flags) {
           try {
             flags = JSON.parse(el.dataset.flags);
           } catch (e) { }
         }
         let weapon = null;
         if (el.dataset.weapon) {
           try {
             weapon = JSON.parse(el.dataset.weapon);
           } catch (e) { }
         }
         let effect = null;
         if (el.dataset.effect) {
           try {
             effect = JSON.parse(el.dataset.effect);
           } catch (e) { }
         }
         // Preserva data-form-videos para poder restaurar depois
         const formVideos =
           el.dataset.formVideos || el.getAttribute("data-form-videos") || "";
         return {
           name: el.dataset.nome || el.dataset.name || "",
           index: el.dataset.index || "",
           level: el.dataset.level || "",
           desc: el.dataset.desc || "",
           descPt: el.dataset.descPt || "",
           descEn: el.dataset.descEn || "",
           descEs: el.dataset.descEs || "",
           descPl: el.dataset.descPl || "",
           attrs: el.dataset.atr || el.dataset.attrs || "",
           video: el.dataset.video || "",
           iconURL,
           subs,
           subattrsStr: subattrsRaw,
           flags: flags,
           weapon: weapon,
           effect: effect,
           nested: el.dataset.nested || "",
           subName: el.dataset.subName || "",
           parentIndex: el.dataset.parentIndex || "",
           formSwitch:
             el.dataset.formSwitch ||
             el.getAttribute("data-form-switch") ||
             "",
           formVideos: formVideos,
         };
       });
     // Retorna objeto com items e currentForm para poder restaurar depois
     return { items, currentForm: currentForm };
   }
   function ensureBackButton() {
     const rail = iconsBar.closest(".top-rail.skills");
     if (!rail) return null;
     let wrap = rail.parentElement;
     if (
       !wrap ||
       !wrap.classList ||
       !wrap.classList.contains("skills-rail-wrap")
     ) {
       const parentNode = rail.parentNode;
       const newWrap = document.createElement("div");
       newWrap.className = "skills-rail-wrap";
       parentNode.insertBefore(newWrap, rail);
       newWrap.appendChild(rail);
       wrap = newWrap;
     }
     let backWrap = wrap.querySelector(".skills-back-wrapper");
     if (!backWrap) {
       backWrap = document.createElement("div");
       backWrap.className = "skills-back-wrapper";
       const btnInner = document.createElement("button");
       btnInner.className = "skills-back";
       btnInner.type = "button";
       btnInner.setAttribute("aria-label", "Voltar");
       btnInner.innerHTML =
         '<svg class="back-chevron" width="100%" height="100%" viewBox="0 0 36 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" preserveAspectRatio="xMidYMid meet"><path d="M10 2L4 16L10 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 2L14 16L20 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M30 2L24 16L30 30" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
       backWrap.appendChild(btnInner);
       wrap.insertBefore(backWrap, rail);
       btnInner.addEventListener("click", () => {
         if (!barStack.length) return;
         const prev = barStack.pop();
         renderBarFromItems(prev);
         backWrap.style.display = barStack.length ? "block" : "none";
         wrap.classList.toggle("has-sub-bar", barStack.length > 0);
         if (!barStack.length) btnInner.classList.remove("peek");
       });
     }
     backWrap.style.display = barStack.length ? "block" : "none";
     wrap.classList.toggle("has-sub-bar", barStack.length > 0);
     const btnInner = backWrap.querySelector(".skills-back");
     // Calcula a posição do botão baseado na posição real do top-rail
     function updateBackButtonPosition() {
       if (!rail || !backWrap) return;
       const railRect = rail.getBoundingClientRect();
       const wrapRect = wrap.getBoundingClientRect();
       // Calcula a posição relativa do rail dentro do wrap
       const railLeft = railRect.left - wrapRect.left;
       // Posiciona o wrapper na borda esquerda do rail
       // O botão interno usa translateX(-97%) para ficar atrás da barra
       backWrap.style.left = railLeft + "px";
     }
     // Atualiza a posição quando necessário
     if (backWrap.style.display !== "none") {
       // Usa requestAnimationFrame para garantir que o DOM foi atualizado
       requestAnimationFrame(() => {
         updateBackButtonPosition();
       });
       // Recalcula em resize e scroll
       if (!backWrap.dataset.positionWired) {
         backWrap.dataset.positionWired = "1";
         const updateOnResize = () => {
           if (backWrap.style.display !== "none") {
             updateBackButtonPosition();
           }
         };
         window.addEventListener("resize", updateOnResize);
         const observer = new ResizeObserver(() => {
           if (backWrap.style.display !== "none") {
             updateBackButtonPosition();
           }
         });
         if (rail) observer.observe(rail);
         observer.observe(wrap);
       }
     }
     return btnInner;
   }
   function renderBarFromItems(itemsOrSnapshot) {
     // Suporta tanto o formato antigo (array) quanto o novo (objeto com items e currentForm)
     let items, savedCurrentForm;
     if (Array.isArray(itemsOrSnapshot)) {
       items = itemsOrSnapshot;
       savedCurrentForm = null;
     } else if (itemsOrSnapshot && itemsOrSnapshot.items) {
       items = itemsOrSnapshot.items;
       savedCurrentForm = itemsOrSnapshot.currentForm;
     } else {
       items = [];
       savedCurrentForm = null;
     }
     // Restaura currentForm se estava salvo
     if (savedCurrentForm !== undefined && savedCurrentForm !== null) {
       currentForm = savedCurrentForm;
     }
     const tip = document.querySelector(".skill-tooltip");
     if (tip) {
       tip.setAttribute("aria-hidden", "true");
       tip.style.opacity = "0";
       tip.style.left = "-9999px";
       tip.style.top = "-9999px";
     }
     iconsBar.innerHTML = "";
     items.forEach((it, idx) => {
       const node = document.createElement("div");
       node.className = "skill-icon";
       node.dataset.nome = it.name || "";
       if (it.index) node.dataset.index = it.index;
       if (it.level) node.dataset.level = it.level;
       if (it.desc) node.dataset.desc = it.desc;
       if (it.descPt) node.dataset.descPt = it.descPt;
       if (it.descEn) node.dataset.descEn = it.descEn;
       if (it.descEs) node.dataset.descEs = it.descEs;
       if (it.descPl) node.dataset.descPl = it.descPl;
       if (it.attrs) node.dataset.atr = it.attrs;
       if (it.video) node.dataset.video = it.video;
       if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
       if (it.subattrsStr) node.dataset.subattrs = it.subattrsStr;
       if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
       if (
         it.weapon &&
         typeof it.weapon === "object" &&
         Object.keys(it.weapon).length > 0
       ) {
         node.dataset.weapon = JSON.stringify(it.weapon);
       }
       if (it.effect && typeof it.effect === "object") {
         try {
           node.dataset.effect = JSON.stringify(it.effect);
         } catch (e) { }
       }
       // Restaura informações de subskill importantes para busca de vídeos
       if (it.nested) node.dataset.nested = it.nested;
       if (it.subName) node.dataset.subName = it.subName;
       if (it.parentIndex) node.dataset.parentIndex = it.parentIndex;
       if (!it.index && !it.nested) node.dataset.nested = "1";
       // Restaura formSwitch (Change Form)
       if (it.formSwitch) {
         node.dataset.formSwitch = it.formSwitch;
         node.setAttribute("data-form-switch", it.formSwitch);
       }
       // Restaura formVideos (vídeos de transição de forma)
       if (it.formVideos) {
         node.dataset.formVideos = it.formVideos;
         node.setAttribute("data-form-videos", it.formVideos);
       }
       const img = document.createElement("img");
       img.alt = "";
       img.src = it.iconURL || (it.icon ? filePathURL(it.icon) : "");
       img.decoding = "async";
       img.loading = "lazy";
       img.width = 50;
       img.height = 50;
       node.appendChild(img);
       iconsBar.appendChild(node);
     });
     animateIconsBarEntrance();
     wireClicksForCurrentBar();
     // Remove qualquer toggle antigo que possa aparecer
     const oldToggle = iconsBar.querySelector(".weapon-bar-toggle");
     if (oldToggle) oldToggle.remove();
     // Reaplica classes de weapon após renderizar barra
     reapplyWeaponClassesToBar();
     const b = ensureBackButton();
     if (b) b.classList.add("peek");
     // Atualiza a posição do botão back após a barra ser renderizada
     requestAnimationFrame(() => {
       const backWrap = document.querySelector(".skills-back-wrapper");
       if (backWrap && backWrap.style.display !== "none") {
         const rail = iconsBar.closest(".top-rail.skills");
         const wrap = rail ? rail.parentElement : null;
         if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
           const railRect = rail.getBoundingClientRect();
           const wrapRect = wrap.getBoundingClientRect();
           const railLeft = railRect.left - wrapRect.left;
           // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
           backWrap.style.left = railLeft + "px";
         }
       }
     });
   }
   function pushSubBarFrom(subs, parentIconEl) {
     const tip = document.querySelector(".skill-tooltip");
     if (tip) {
       tip.setAttribute("aria-hidden", "true");
       tip.style.opacity = "0";
       tip.style.left = "-9999px";
       tip.style.top = "-9999px";
     }
     const parentNameSnapshot = parentIconEl
       ? parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
       : "";
     // Para subskills, usa parentIndex; para skills principais, usa index
     const parentIndexSnapshot = parentIconEl
       ? parentIconEl.dataset.nested === "1"
         ? parentIconEl.dataset.parentIndex || ""
         : parentIconEl.dataset.index || ""
       : "";
     const snapshot = snapshotCurrentBarItemsFromDOM();
     barStack.push({
       items: snapshot.items,
       currentForm: snapshot.currentForm,
       parentIcon: parentIconEl,
       parentName: parentNameSnapshot,
       parentIndex: parentIndexSnapshot,
     });
     ensureBackButton();
     const langKey = getLangKey();
     let cacheKey = null;
     if (parentIconEl) {
       cacheKey = parentIconEl.dataset.subCacheKey || null;
       if (!cacheKey) {
         if (parentIconEl.dataset.index) {
           cacheKey = `idx:${parentIconEl.dataset.index}`;
         } else {
           const slug = slugify(
             parentIconEl.dataset.nome || parentIconEl.dataset.name || ""
           );
           if (slug) cacheKey = `slug:${slug}`;
         }
         if (cacheKey) parentIconEl.dataset.subCacheKey = cacheKey;
       }
     }
     if (cacheKey) {
       const cached = subBarTemplateCache.get(cacheKey);
       if (cached && cached.lang === langKey) {
         iconsBar.innerHTML = "";
         const clone = cached.template.cloneNode(true);
         iconsBar.appendChild(clone);
         animateIconsBarEntrance();
         wireClicksForCurrentBar();
         // Remove qualquer toggle antigo que possa aparecer
         const oldToggle2 = iconsBar.querySelector(".weapon-bar-toggle");
         if (oldToggle2) oldToggle2.remove();
         // Reaplica classes de weapon após renderizar do cache
         reapplyWeaponClassesToBar();
         const cachedBtn = ensureBackButton();
         if (cachedBtn) cachedBtn.classList.add("peek");
         return;
       }
     }
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const L = i18nMap[getLangKey()] ||
       i18nMap.pt || {
       cooldown: "Recarga",
       energy_gain: "Ganho de energia",
       energy_cost: "Custo de energia",
       power: "Poder",
       power_pvp: "Poder PvP",
       level: "Nível",
     };
     const hydratedSubs = inheritSubskillTree(subs, mainSkillsMeta);
     const items = (hydratedSubs || [])
       .filter((s) => {
         // Filtra só se não tem nada útil
         const hasName = (s.name || s.n || "").trim() !== "";
         const hasIcon = (s.icon || "").trim() !== "";
         const hasRef = (s.refS || s.refM || "").toString().trim() !== "";
         return hasName || hasIcon || hasRef;
       })
       .map((s) => {
         const name = (s.name || s.n || "").trim();
         const desc = chooseDescFrom(s).replace(/(.*?)/g, "$1");
         const attrsHTML = renderSubAttributesFromObj(s, L);
         return {
           name,
           level: (s.level || "").toString().trim(),
           desc,
           descPt: s.descPt || (s.desc_i18n && s.desc_i18n.pt) || "",
           descEn: s.descEn || (s.desc_i18n && s.desc_i18n.en) || "",
           descEs: s.descEs || (s.desc_i18n && s.desc_i18n.es) || "",
           descPl: s.descPl || (s.desc_i18n && s.desc_i18n.pl) || "",
           attrs: "",
           icon: s.icon || "",
           iconURL: s.icon ? filePathURL(s.icon) : "",
           video: s.video ? filePathURL(s.video) : "",
           subs: Array.isArray(s.subs) ? s.subs : null,
           subattrs: s,
           flags: Array.isArray(s.flags) ? s.flags : null,
           back:
             s.back === true ||
               s.back === "true" ||
               s.back === "yes" ||
               s.back === "1"
               ? "true"
               : null,
           weapon: s.weapon || null,
         };
       });
     const fragment = document.createDocumentFragment();
     items.forEach((it, iIdx) => {
       const node = document.createElement("div");
       node.className = "skill-icon";
       node.dataset.nested = "1";
       node.dataset.nome = it.name || "";
       node.dataset.parentIndex = parentIndexSnapshot;
       // Constrói o caminho completo para sub-subskills
       // IMPORTANTE: O cache NÃO inclui o nome da skill principal no caminho
       // Formato do cache: "SubskillName" ou "SubskillName:SubSubskillName" (sem o nome da skill principal)
       let fullPath = it.name || "";
       if (parentIconEl) {
         // Se o pai é uma subskill (tem nested), adiciona o nome do pai ao caminho
         if (parentIconEl.dataset.nested === "1") {
           // Se o pai tem subName, usa ele (já está no formato correto, sem nome da skill principal)
           if (
             parentIconEl.dataset.subName &&
             parentIconEl.dataset.subName.trim() !== ""
           ) {
             fullPath = `${parentIconEl.dataset.subName}:${it.name || ""}`;
           } else {
             // Se o pai não tem subName, é a primeira subskill, então usa apenas o nome do pai
             const parentName = (
               parentIconEl.dataset.nome ||
               parentIconEl.dataset.name ||
               ""
             ).trim();
             fullPath = `${parentName}:${it.name || ""}`;
           }
         }
         // Se o pai é uma skill principal (não é nested), o caminho é apenas o nome da subskill atual
         // (já está definido como it.name acima)
       }
       node.dataset.subName = fullPath;
       const subSlug = slugify(it.name || "");
       if (subSlug) node.dataset.slug = subSlug;
       if (it.level) node.dataset.level = it.level;
       if (it.desc) node.dataset.desc = it.desc;
       if (it.descPt) node.dataset.descPt = it.descPt;
       if (it.descEn) node.dataset.descEn = it.descEn;
       if (it.descEs) node.dataset.descEs = it.descEs;
       if (it.descPl) node.dataset.descPl = it.descPl;
       if (it.video) node.dataset.video = it.video;
       if (it.subs) node.dataset.subs = JSON.stringify(it.subs);
       if (it.subattrs) node.dataset.subattrs = JSON.stringify(it.subattrs);
       if (it.flags) node.dataset.flags = JSON.stringify(it.flags);
       if (it.back) node.dataset.back = it.back;
       if (
         it.weapon &&
         typeof it.weapon === "object" &&
         Object.keys(it.weapon).length > 0
       ) {
         try {
           node.dataset.weapon = JSON.stringify(it.weapon);
         } catch (e) {
           console.error(
             "[Skills] Erro ao serializar weapon de subskill",
             it.name,
             e
           );
         }
       }
       const img = document.createElement("img");
       img.alt = "";
       img.src = it.iconURL;
       img.decoding = "async";
       img.loading = "lazy";
       img.width = 50;
       img.height = 50;
       node.appendChild(img);
       fragment.appendChild(node);
     });
     const templateClone = fragment.cloneNode(true);
     iconsBar.innerHTML = "";
     iconsBar.appendChild(fragment);
     animateIconsBarEntrance();
     wireClicksForCurrentBar();
     // Remove qualquer toggle antigo que possa aparecer
     const oldToggle3 = iconsBar.querySelector(".weapon-bar-toggle");
     if (oldToggle3) oldToggle3.remove();
     // Reaplica classes de weapon após renderizar subskills
     reapplyWeaponClassesToBar();
     const b2 = ensureBackButton();
     if (b2) b2.classList.add("peek");
     // Atualiza a posição do botão back após subskills serem renderizadas
     requestAnimationFrame(() => {
       const backWrap = document.querySelector(".skills-back-wrapper");
       if (backWrap && backWrap.style.display !== "none") {
         const rail = iconsBar.closest(".top-rail.skills");
         const wrap = rail ? rail.parentElement : null;
         if (rail && wrap && wrap.classList.contains("skills-rail-wrap")) {
           const railRect = rail.getBoundingClientRect();
           const wrapRect = wrap.getBoundingClientRect();
           const railLeft = railRect.left - wrapRect.left;
           // Posiciona na borda esquerda do rail (botão fica atrás com translateX)
           backWrap.style.left = railLeft + "px";
         }
       }
     });
     if (cacheKey) {
       subBarTemplateCache.set(cacheKey, {
         template: templateClone,
         lang: langKey,
       });
     }
   }
   window.addEventListener("gla:langChanged", () => {
     subBarTemplateCache.clear();
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const lang = getLangKey();
     Array.from(iconsBar.querySelectorAll(".skill-icon")).forEach((icon) => {
       const pack = {
         pt: icon.dataset.descPt || "",
         en: icon.dataset.descEn || "",
         es: icon.dataset.descEs || "",
         pl: icon.dataset.descPl || "",
       };
       const chosen = (
         pack[lang] ||
         pack.pt ||
         pack.en ||
         pack.es ||
         pack.pl ||
         icon.dataset.desc ||
         ""
       ).trim();
       if (chosen) icon.dataset.desc = chosen;
     });
     barStack.forEach((frame) => {
       (frame.items || []).forEach((it) => {
         const pack = {
           pt: it.descPt,
           en: it.descEn,
           es: it.descEs,
           pl: it.descPl,
         };
         const chosen =
           pack[lang] ||
           pack.pt ||
           pack.en ||
           pack.es ||
           pack.pl ||
           it.desc ||
           "";
         it.desc = chosen;
       });
     });
     if (descBox) {
       applyFlagTooltips(descBox);
     }
     const activeIcon = window.__lastActiveSkillIcon;
     if (activeIcon && activeIcon.dataset.weapon) {
       activateSkill(activeIcon, {
         openSubs: false,
       });
     }
   });
   wireClicksForCurrentBar();
   // Inicializa o sistema de swap de personagens (genérico)
   // Aguarda um pouco para garantir que todos os ícones foram renderizados
   setTimeout(() => {
     initializeActiveCharacter();
   }, 100);
   const b0 = ensureBackButton();
   if (b0) {
     b0.classList.add("peek");
     b0.style.alignSelf = "stretch";
   }
   // Move inicialização de tooltip para requestIdleCallback (não crítico)
   if ("requestIdleCallback" in window) {
     requestIdleCallback(
       () => {
         (function initSkillTooltip() {
           if (document.querySelector(".skill-tooltip")) return;
           const tip = document.createElement("div");
           tip.className = "skill-tooltip";
           tip.setAttribute("role", "tooltip");
           tip.setAttribute("aria-hidden", "true");
           document.body.appendChild(tip);
           const lockUntilRef = {
             value: 0,
           };
           function measureAndPos(el) {
             if (!el || tip.getAttribute("aria-hidden") === "true") return;
             tip.style.left = "0px";
             tip.style.top = "0px";
             const rect = el.getBoundingClientRect();
             const tr = tip.getBoundingClientRect();
             let left = Math.round(rect.left + (rect.width - tr.width) / 2);
             left = Math.max(
               8,
               Math.min(left, window.innerWidth - tr.width - 8)
             );
             const coarse =
               (window.matchMedia &&
                 matchMedia("(pointer: coarse)").matches) ||
               window.innerWidth <= 600;
             let top = coarse
               ? Math.round(rect.bottom + 10)
               : Math.round(rect.top - tr.height - 8);
             if (top < 8) top = Math.round(rect.bottom + 10);
             tip.style.left = left + "px";
             tip.style.top = top + "px";
           }
           function show(el, text) {
             tip.textContent = text || "";
             tip.setAttribute("aria-hidden", "false");
             measureAndPos(el);
             tip.style.opacity = "1";
           }
           function hide() {
             tip.setAttribute("aria-hidden", "true");
             tip.style.opacity = "0";
             tip.style.left = "-9999px";
             tip.style.top = "-9999px";
           }
           window.__globalSkillTooltip = {
             show,
             hide,
             measureAndPos,
             lockUntil: lockUntilRef,
           };
           Array.from(
             document.querySelectorAll(".icon-bar .skill-icon")
           ).forEach((icon) => {
             if (
               icon.dataset.weaponToggle === "1" ||
               icon.classList.contains("weapon-bar-toggle")
             )
               return;
             if (icon.dataset.tipwired) return;
             icon.dataset.tipwired = "1";
             const label =
               icon.dataset.nome || icon.dataset.name || icon.title || "";
             if (label && !icon.hasAttribute("aria-label"))
               icon.setAttribute("aria-label", label);
             if (icon.hasAttribute("title")) icon.removeAttribute("title");
             const img = icon.querySelector("img");
             if (img) {
               const imgAlt = img.getAttribute("alt") || "";
               const imgTitle = img.getAttribute("title") || "";
               if (!label && (imgAlt || imgTitle))
                 icon.setAttribute("aria-label", imgAlt || imgTitle);
               img.setAttribute("alt", "");
               if (img.hasAttribute("title")) img.removeAttribute("title");
             }
             icon.addEventListener("mouseenter", () => show(icon, label));
             icon.addEventListener("mousemove", () => {
               if (performance.now() >= lockUntilRef.value)
                 measureAndPos(icon);
             });
             icon.addEventListener("click", () => {
               lockUntilRef.value = performance.now() + 240;
               measureAndPos(icon);
             });
             icon.addEventListener("mouseleave", hide);
           });
           Array.from(
             document.querySelectorAll(".subskills-rail .subicon")
           ).forEach((sub) => {
             if (sub.dataset.tipwired) return;
             sub.dataset.tipwired = "1";
             const label =
               sub.getAttribute("title") ||
               sub.getAttribute("aria-label") ||
               "";
             if (label && !sub.hasAttribute("aria-label"))
               sub.setAttribute("aria-label", label);
             if (sub.hasAttribute("title")) sub.removeAttribute("title");
             sub.addEventListener("mouseenter", () => show(sub, label));
             sub.addEventListener("mousemove", () => {
               if (performance.now() >= lockUntilRef.value) measureAndPos(sub);
             });
             sub.addEventListener("click", () => {
               lockUntilRef.value = performance.now() + 240;
               measureAndPos(sub);
             });
             sub.addEventListener("mouseleave", hide);
           });
           window.addEventListener(
             "scroll",
             () => {
               const visible = document.querySelector(
                 '.skill-tooltip[aria-hidden="false"]'
               );
               if (!visible) return;
               const target =
                 document.querySelector(".subskills-rail .subicon:hover") ||
                 document.querySelector(".subskills-rail .subicon.active") ||
                 document.querySelector(".icon-bar .skill-icon:hover") ||
                 document.querySelector(".icon-bar .skill-icon.active");
               measureAndPos(target);
             },
             true
           );
           window.addEventListener("resize", () => {
             const target =
               document.querySelector(".subskills-rail .subicon:hover") ||
               document.querySelector(".subskills-rail .subicon.active") ||
               document.querySelector(".icon-bar .skill-icon:hover") ||
               document.querySelector(".icon-bar .skill-icon.active");
             measureAndPos(target);
           });
         })();
       },
       { timeout: 2000 }
     );
   } else {
     // Fallback para navegadores sem requestIdleCallback
     setTimeout(() => {
       (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);
         window.__globalSkillTooltip = {
           show: (el, text) => {
             if (!el || !text) return;
             tip.textContent = text;
             tip.setAttribute("aria-hidden", "false");
             measureAndPos(el);
           },
           hide: () => {
             tip.setAttribute("aria-hidden", "true");
             tip.style.left = "-9999px";
             tip.style.top = "-9999px";
           },
           measureAndPos: (el) => {
             if (!el || tip.getAttribute("aria-hidden") === "true") return;
             const rect = el.getBoundingClientRect();
             const tipRect = tip.getBoundingClientRect();
             let left = rect.left + rect.width / 2 - tipRect.width / 2;
             let top = rect.top - tipRect.height - 8;
             if (left < 8) left = 8;
             if (left + tipRect.width > window.innerWidth - 8)
               left = window.innerWidth - tipRect.width - 8;
             if (top < 8) top = rect.bottom + 8;
             tip.style.left = left + "px";
             tip.style.top = top + "px";
           },
           lockUntil: { value: 0 },
         };
         const { measureAndPos } = window.__globalSkillTooltip;
         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);
         });
       })();
     }, 100);
   }
   (function initTabs() {
     const tabs = Array.from(document.querySelectorAll(".tab-btn"));
     if (!tabs.length) return;
     const contents = Array.from(document.querySelectorAll(".tab-content"));
     const characterBox = document.querySelector(".character-box");
     let wrapper = characterBox.querySelector(".tabs-height-wrapper");
     if (!wrapper) {
       wrapper = document.createElement("div");
       wrapper.className = "tabs-height-wrapper";
       contents.forEach((c) => {
         wrapper.appendChild(c);
       });
       const tabsElement = characterBox.querySelector(".character-tabs");
       if (tabsElement && tabsElement.nextSibling) {
         characterBox.insertBefore(wrapper, tabsElement.nextSibling);
       } else {
         characterBox.appendChild(wrapper);
       }
     }
     async function smoothHeightTransition(fromTab, toTab) {
       if (!wrapper) return Promise.resolve();
       const scrollY = window.scrollY;
       const currentHeight = wrapper.getBoundingClientRect().height;
       await new Promise((resolve) => {
         const videoContainers = toTab.querySelectorAll(".video-container");
         const contentCard = toTab.querySelector(".content-card");
         if (videoContainers.length === 0) {
           requestAnimationFrame(() => {
             requestAnimationFrame(() => {
               requestAnimationFrame(() => resolve());
             });
           });
           return;
         }
         let lastHeight = 0;
         let stableCount = 0;
         const checksNeeded = 3;
         let totalChecks = 0;
         const maxChecks = 15;
         function checkStability() {
           totalChecks++;
           const currentTabHeight = toTab.scrollHeight;
           if (Math.abs(currentTabHeight - lastHeight) < 5) {
             stableCount++;
           } else {
             stableCount = 0;
           }
           lastHeight = currentTabHeight;
           if (stableCount >= checksNeeded || totalChecks >= maxChecks) {
             resolve();
           } else {
             setTimeout(checkStability, 50);
           }
         }
         setTimeout(checkStability, 50);
       });
       const nextHeight = toTab.getBoundingClientRect().height;
       const finalHeight = Math.max(nextHeight, 100);
       if (Math.abs(finalHeight - currentHeight) < 30) {
         wrapper.style.height = "";
         return Promise.resolve();
       }
       wrapper.style.overflow = "hidden";
       wrapper.style.height = currentHeight + "px";
       wrapper.offsetHeight;
       wrapper.style.transition = "height 0.3s cubic-bezier(0.4, 0, 0.2, 1)";
       requestAnimationFrame(() => {
         wrapper.style.height = finalHeight + "px";
       });
       return new Promise((resolve) => {
         setTimeout(() => {
           wrapper.style.height = "";
           wrapper.style.transition = "";
           wrapper.style.overflow = "";
           resolve();
         }, 320);
       });
     }
     tabs.forEach((btn) => {
       if (btn.dataset.wiredTab) return;
       btn.dataset.wiredTab = "1";
       btn.addEventListener("click", () => {
         const target = btn.getAttribute("data-tab");
         const currentActive = contents.find((c) =>
           c.classList.contains("active")
         );
         const nextActive = contents.find((c) => c.id === target);
         if (currentActive === nextActive) return;
         document.body.classList.add("transitioning-tabs");
         if (currentActive) {
           currentActive.style.opacity = "0";
           currentActive.style.transform = "translateY(-8px)";
         }
         setTimeout(async () => {
           contents.forEach((c) => {
             if (c !== nextActive) {
               c.style.display = "none";
               c.classList.remove("active");
             }
           });
           tabs.forEach((b) => b.classList.toggle("active", b === btn));
           if (nextActive) {
             nextActive.classList.add("active");
             nextActive.style.display = "block";
             nextActive.style.opacity = "0";
             nextActive.style.visibility = "hidden";
             nextActive.offsetHeight;
             try {
               if (target === "skills") {
                 const tabEl = document.getElementById(target);
                 if (tabEl) {
                   const allIcons = tabEl.querySelectorAll(
                     ".icon-bar .skill-icon"
                   );
                   const activeIcon = tabEl.querySelector(
                     ".icon-bar .skill-icon.active"
                   );
                   const firstIcon = tabEl.querySelector(
                     ".icon-bar .skill-icon"
                   );
                   const isFormSwitch = (el) =>
                     el?.dataset?.formSwitch === "true" ||
                     el?.getAttribute?.("data-form-switch") === "true";
                   let toClick = activeIcon && !isFormSwitch(activeIcon)
                     ? activeIcon
                     : null;
                   if (!toClick)
                     for (const icon of allIcons || [])
                       if (!isFormSwitch(icon)) {
                         toClick = icon;
                         break;
                       }
                   if (!toClick) toClick = firstIcon;
                   if (toClick) {
                     const had = document.body.dataset.suppressSkillPlay;
                     document.body.dataset.suppressSkillPlay = "1";
                     toClick.click();
                     if (had) document.body.dataset.suppressSkillPlay = had;
                   }
                 }
               }
             } catch (e) { }
           }
           if (currentActive && nextActive) {
             await smoothHeightTransition(currentActive, nextActive);
           }
           if (nextActive) {
             nextActive.style.visibility = "";
             nextActive.style.transform = "translateY(12px)";
             requestAnimationFrame(() => {
               nextActive.style.opacity = "1";
               nextActive.style.transform = "translateY(0)";
               setTimeout(() => {
                 nextActive.style.opacity = "";
                 nextActive.style.transform = "";
                 document.body.classList.remove("transitioning-tabs");
                 try {
                   delete document.body.dataset.suppressSkillPlay;
                 } catch { }
               }, 300);
             });
           }
         }, 120);
         setTimeout(() => {
           syncDescHeight();
           if (target === "skins") {
             videosCache.forEach((v) => {
               try {
                 v.pause();
               } catch (e) { }
               v.style.display = "none";
             });
             const vb = getVideoBox();
             if (vb) {
               vb.querySelectorAll("video.skill-video").forEach((v) => {
                 try {
                   v.pause();
                 } catch (e) { }
                 v.style.display = "none";
               });
             }
             if (window.__subskills) window.__subskills.hideAll?.(getVideoBox());
             const placeholder = getVideoBox()?.querySelector(".video-placeholder");
             const vb2 = getVideoBox();
             if (vb2 && placeholder) {
               placeholder.style.display = "none";
               placeholder.classList.add("fade-out");
             }
           } else {
             const activeIcon = document.querySelector(
               ".icon-bar .skill-icon.active"
             );
             const isFormSwitch = (el) =>
               el?.dataset?.formSwitch === "true" ||
               el?.getAttribute?.("data-form-switch") === "true";
             if (activeIcon && !isFormSwitch(activeIcon)) activeIcon.click();
             else {
               const first = document.querySelector(
                 ".icon-bar .skill-icon:not([data-form-switch='true'])"
               );
               if (first) first.click();
             }
           }
         }, 450);
       });
     });
   })();
   (function initSkinsArrows() {
     const carousel = $(".skins-carousel");
     const wrapper = $(".skins-carousel-wrapper");
     const left = $(".skins-arrow.left");
     const right = $(".skins-arrow.right");
     if (!carousel || !left || !right || !wrapper) return;
     if (wrapper.dataset.wired) return;
     wrapper.dataset.wired = "1";
     const scrollAmt = () => Math.round(carousel.clientWidth * 0.6);
     function setState() {
       const max = carousel.scrollWidth - carousel.clientWidth;
       const x = carousel.scrollLeft;
       const hasLeft = x > 5,
         hasRight = x < max - 5;
       left.style.display = hasLeft ? "inline-block" : "none";
       right.style.display = hasRight ? "inline-block" : "none";
       wrapper.classList.toggle("has-left", hasLeft);
       wrapper.classList.toggle("has-right", hasRight);
       carousel.style.justifyContent = !hasLeft && !hasRight ? "center" : "";
     }
     function go(dir) {
       const max = carousel.scrollWidth - carousel.clientWidth;
       const next =
         dir < 0
           ? Math.max(0, carousel.scrollLeft - scrollAmt())
           : Math.min(max, carousel.scrollLeft + scrollAmt());
       carousel.scrollTo({
         left: next,
         behavior: "smooth",
       });
     }
     left.addEventListener("click", () => go(-1));
     right.addEventListener("click", () => go(1));
     carousel.addEventListener("scroll", setState);
     new ResizeObserver(setState).observe(carousel);
     setState();
   })();
   function renderAttributes(str) {
     const skillsRoot = document.getElementById("skills");
     const i18nMap = skillsRoot
       ? JSON.parse(skillsRoot.dataset.i18nAttrs || "{}")
       : {};
     const langRaw = (
       document.documentElement.lang ||
       skillsRoot?.dataset.i18nDefault ||
       "pt"
     ).toLowerCase();
     const langKey = i18nMap[langRaw]
       ? langRaw
       : i18nMap[langRaw.split("-")[0]]
         ? langRaw.split("-")[0]
         : "pt";
     const L = i18nMap[langKey] ||
       i18nMap.pt || {
       cooldown: "Recarga",
       energy_gain: "Ganho de energia",
       energy_cost: "Custo de energia",
       power: "Poder",
       power_pvp: "Poder PvP",
       level: "Nível",
     };
     const vals = (str || "").split(",").map((v) => v.trim());
     // Processa valores, tratando strings vazias e "-" como NaN
     // IMPORTANTE: ordem fixa é [powerpve, powerpvp, energy, cooldown]
     const parseValue = (val) => {
       if (!val || val === "" || val === "-") return NaN;
       const parsed = parseFloat(val);
       return isNaN(parsed) ? NaN : parsed;
     };
     const pve = parseValue(vals[0]);
     const pvp = parseValue(vals[1]);
     const ene = parseValue(vals[2]);
     const cd = parseValue(vals[3]);
     // Debug: log se houver problema na ordem
     if (str && str.includes(",") && !isNaN(cd) && !isNaN(pvp) && cd === pvp) {
       console.warn(
         "[Skills] renderAttributes: possível problema na ordem dos atributos",
         { str, vals, pve, pvp, ene, cd }
       );
     }
     const rows = [];
     // Ordem de exibição: cooldown, energy, power, power_pvp
     if (!isNaN(cd)) rows.push([L.cooldown, cd]);
     if (!isNaN(ene) && ene !== 0) {
       const label = ene > 0 ? L.energy_gain : L.energy_cost;
       rows.push([label, Math.abs(ene)]);
     }
     if (!isNaN(pve)) rows.push([L.power, pve]);
     if (!isNaN(pvp)) rows.push([L.power_pvp, pvp]);
     // Debug: log se houver valores suspeitos (desabilitado para performance)
     // if (str && str.includes(',')) {
     //     console.log('[Skills] renderAttributes processed', {
     //         str,
     //         vals: vals.slice(0, 4),
     //         parsed: { pve, pvp, ene, cd },
     //         rows: rows.map(r => r[0])
     //     });
     // }
     if (!rows.length) return "";
     const html = rows
       .map(
         ([rowLabel, rowValue]) =>

`

${rowLabel}${rowValue}

`

       )
       .join("");

return `

${html}

`;

   }
   function syncDescHeight() { }
   window.addEventListener("resize", syncDescHeight);
   const vbInit = getVideoBox();
   if (vbInit) new ResizeObserver(syncDescHeight).observe(vbInit);
   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) {
     const scrollWrapper =
       iconsBar.closest(".icon-scroll-x") || iconsBar.parentElement;
     const scrollContainer =
       scrollWrapper && scrollWrapper.classList.contains("icon-scroll-x")
         ? scrollWrapper
         : iconsBar;
     addOnce(
       scrollContainer,
       "wheel",
       (e) => {
         if (e.deltaY) {
           e.preventDefault();
           scrollContainer.scrollLeft += e.deltaY;
         }
       },
       { passive: false }
     ); // passive: false necessário porque usamos preventDefault()
   }
   (function maybeInjectFormSkillsOnLoad() {
     try {
       const skillsRoot = document.getElementById("skills");
       if (skillsRoot && iconsBar && iconItems.length > 0) {
         const formsJSON = skillsRoot.dataset.forms || "{}";
         if (formsJSON && formsJSON !== "{}") {
           formsData = JSON.parse(formsJSON);
           const firstIcon = iconItems[0];
           const isFormSwitch =
             firstIcon?.dataset?.formSwitch === "true" ||
             firstIcon?.getAttribute?.("data-form-switch") === "true";
           if (
             formsData &&
             Object.keys(formsData).length > 0 &&
             isFormSwitch
           ) {
             const firstFormName = Object.keys(formsData)[0];
             const firstFormData = formsData[firstFormName];
             if (firstFormData) {
               updateSkillsBarForForm(firstFormName, firstFormData, firstIcon);
               setTimeout(() => {
                 wireClicksForCurrentBar();
                 const freshIcons = Array.from(
                   iconsBar.querySelectorAll(".skill-icon")
                 );
                 const isFS = (el) =>
                   el?.dataset?.formSwitch === "true" ||
                   el?.getAttribute?.("data-form-switch") === "true";
                 const firstNonSwitch = freshIcons.find((el) => !isFS(el));
                 if (firstNonSwitch)
                   activateSkill(firstNonSwitch, { openSubs: false });
               }, 200);
               return;
             }
           }
         }
       }
     } catch (e) {
       console.error("[Forms] init inject error", e);
     }
     wireClicksForCurrentBar();
     if (iconItems.length) {
       const isFormSwitch = (el) =>
         el?.dataset?.formSwitch === "true" ||
         el?.getAttribute?.("data-form-switch") === "true";
       let first = null;
       for (const el of iconItems) {
         if (!isFormSwitch(el)) {
           first = el;
           break;
         }
       }
       if (!first) first = iconItems[0];
       if (first) {
         activateSkill(first, {
           openSubs: false,
         });
       }
     }
   })();
   // Aplica lazy loading em imagens fora do viewport inicial
   (function applyLazyLoading() {
     if ("IntersectionObserver" in window) {
       const imageObserver = new IntersectionObserver((entries, observer) => {
         entries.forEach((entry) => {
           if (entry.isIntersecting) {
             const img = entry.target;
             if (img.tagName === "IMG" && !img.hasAttribute("loading")) {
               img.loading = "lazy";
             }
             observer.unobserve(img);
           }
         });
       });
       // Observa imagens que não estão no viewport inicial
       document
         .querySelectorAll("img:not(.topbar-icon):not([loading])")
         .forEach((img) => {
           const rect = img.getBoundingClientRect();
           if (rect.bottom > window.innerHeight || rect.top < 0) {
             imageObserver.observe(img);
           }
         });
     }
   })();
   setTimeout(() => {
     Array.from(document.querySelectorAll(".skill-icon")).forEach((el) => { });
     videosCache.forEach((v, idx) => {
       const src = v.querySelector("source")
         ? v.querySelector("source").src
         : v.src;
       v.addEventListener("error", (ev) => { });
       v.addEventListener("loadedmetadata", () => { });
     });
   }, 600);
 })();

</script>