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

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
 
(37 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
Linha 1 827: Linha 1 827:
             0%, 100% { opacity: 0.7; }
             0%, 100% { opacity: 0.7; }
             50% { opacity: 1; }
             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 ===== */
         /* ===== Í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) */
         /* 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) {
         .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 */
             /* Remove transform scale quando toggle está desativado */
Linha 1 878: Linha 1 894:
             /* SEM transição no background/padding/mask para evitar "flash" durante a mudança */
             /* SEM transição no background/padding/mask para evitar "flash" durante a mudança */
             transition: box-shadow 0.15s ease !important;
             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;
         }
         }


Linha 1 949: Linha 1 971:
         }
         }


         /* ========== 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)::after {
            position: relative;
             pointer-events: none !important;
        }
             box-shadow: none !important;
        .character-box .top-rail.skills .icon-bar .skill-icon {
             background: linear-gradient(90deg,  
            z-index: 2;
                 #FF6D00 0%,  
        }
                 #FF9800 50%,  
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active) {
                 #FF6D00 100%) !important;
            border-color: transparent !important;
             background-size: 200% 100% !important;
            outline: 2px solid rgba(210, 60, 60, 0.95) !important;
             animation: weapon-border-flow 2.8s linear infinite !important;
            outline-offset: -2px;
             -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
            animation: effect-child-outline 1.6s ease-in-out infinite !important;
             mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0) !important;
        }
             -webkit-mask-composite: xor !important;
        .character-box .top-rail.skills .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle):not(.active)::after {
             mask-composite: exclude !important;
            box-shadow:
             padding: 2px !important;
                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)::before {
         .character-box .top-rail.skills.effect-mode-on .icon-bar .skill-icon.effect-active:not(.weapon-bar-toggle).active::before {
             pointer-events: none !important;
             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 2.2s ease-in-out infinite !important;
             animation: weapon-glow-breathe 1.4s ease-in-out infinite !important;
             box-shadow:  
             box-shadow:  
                 0 0 8px 0 rgba(255, 140, 0, 0.45),
                 0 0 10px 0 rgba(220, 220, 220, 0.5),
                 0 0 14px 0 rgba(255, 167, 38, 0.35),
                 0 0 16px 0 rgba(190, 190, 190, 0.4),
                 0 0 20px 0 rgba(255, 140, 0, 0.25) !important;
                 0 0 22px 0 rgba(220, 220, 220, 0.3) !important;
             opacity: 1 !important;
             opacity: 1 !important;
         }
         }
Linha 2 022: Linha 2 199:
       videos: new Map(),
       videos: new Map(),
       expiresAt: 0,
       expiresAt: 0,
      sourceIcon: null,
       timer: null,
       timer: null,
     };
     };
    let effectLinesLayer = null;
    let effectLinesRAF = null;
    let effectLinesCleanupTimer = null;
    let effectLinesScrollWrap = null;
    let effectLinesBound = false;
    let effectLinesLastState = null;
     function getSkillNameFromIcon(iconEl) {
     function getSkillNameFromIcon(iconEl) {
       return (iconEl?.dataset?.nome || iconEl?.dataset?.name || "").trim();
       return (iconEl?.dataset?.nome || iconEl?.dataset?.name || "").trim();
Linha 2 059: Linha 2 243:
       }
       }
       return { skills, timeMs, videos };
       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() {
     function applyEffectClasses() {
Linha 2 072: Linha 2 502:
             isActive && name && effectState.skills.has(name);
             isActive && name && effectState.skills.has(name);
           icon.classList.toggle("effect-active", !!should);
           icon.classList.toggle("effect-active", !!should);
          if (!should) {
            icon.style.removeProperty("--effect-strength");
          }
         });
         });
      if (isActive) {
        bindEffectLinesEvents();
        scheduleEffectLinesUpdate();
      }
     }
     }
     function clearEffectState() {
     function clearEffectState() {
Linha 2 082: Linha 2 519:
         activeName && effectState.skills.has(activeName);
         activeName && effectState.skills.has(activeName);


      animateEffectLinesReturn();
       if (effectState.timer) {
       if (effectState.timer) {
         clearTimeout(effectState.timer);
         clearTimeout(effectState.timer);
Linha 2 089: Linha 2 527:
       effectState.videos.clear();
       effectState.videos.clear();
       effectState.expiresAt = 0;
       effectState.expiresAt = 0;
      effectState.sourceIcon = null;
       applyEffectClasses();
       applyEffectClasses();


Linha 2 103: Linha 2 542:
       effectState.videos = normalized.videos;
       effectState.videos = normalized.videos;
       effectState.expiresAt = Date.now() + normalized.timeMs;
       effectState.expiresAt = Date.now() + normalized.timeMs;
      effectState.sourceIcon = iconEl;


       if (effectState.timer) clearTimeout(effectState.timer);
       if (effectState.timer) clearTimeout(effectState.timer);

Edição atual tal como às 02h47min de 25 de janeiro 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 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;
     // Determina próxima forma
     // Se currentForm é null, detecta qual forma está atualmente visível no DOM
     if (currentForm === null) {
       currentForm = detectCurrentForm();
     }
     // 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);
     if (currentIdx === -1) return; // Forma não encontrada
     const nextIdx = (currentIdx + 1) % orderedFormNames.length;
     const nextForm = orderedFormNames[nextIdx];
     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 !== "" &&
       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) { }
     }
     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 !== "" &&
       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) { }
     }
     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) {
     if (!FLAG_ICON_FILES[key]) return "";
     if (!flagIconURLCache.has(key)) {
       flagIconURLCache.set(key, filePathURL(FLAG_ICON_FILES[key]));
     }
     return flagIconURLCache.get(key);
   }
   function renderFlagsRow(flags) {
     const arr = (flags || []).filter(Boolean);
     if (!arr.length) return "";
     const cacheKey = arr.join("|");
     if (flagRowCache.has(cacheKey)) {
       return flagRowCache.get(cacheKey);
     }
     const items = arr
       .map((k) => {
         const url = getFlagIconURL(k);
         return url
           ? `<img class="skill-flag" data-flag="${k}" alt="" src="${url}">`
           : "";
       })
       .join("");
     const html = items

 ? `

${items}

`

       : "";
     if (html) flagRowCache.set(cacheKey, html);
     return html;
   }
   function applyFlagTooltips(container) {
     const skillsRoot = document.getElementById("skills");
     if (!skillsRoot) return;
     let pack = {};
     try {
       pack = JSON.parse(skillsRoot.dataset.i18nFlags || "{}");
     } catch (e) { }
     const lang = getLangKey();
     const dict = pack[lang] || pack.pt || {};
     const flags = container.querySelectorAll(
       ".skill-flags .skill-flag[data-flag]"
     );
     const tooltip = window.__globalSkillTooltip;
     if (!tooltip) return;
     flags.forEach((el) => {
       const key = el.getAttribute("data-flag");
       const tip = (dict && dict[key]) || "";
       if (!tip) return;
       if (el.dataset.flagTipWired) return;
       el.dataset.flagTipWired = "1";
       el.setAttribute("aria-label", tip);
       if (el.hasAttribute("title")) el.removeAttribute("title");
       el.addEventListener("mouseenter", () => {
         const tipEl = document.querySelector(".skill-tooltip");
         if (tipEl) tipEl.classList.add("flag-tooltip");
         tooltip.show(el, tip);
       });
       el.addEventListener("mousemove", () => {
         if (performance.now() >= tooltip.lockUntil.value) {
           tooltip.measureAndPos(el);
         }
       });
       el.addEventListener("click", () => {
         tooltip.lockUntil.value = performance.now() + 240;
         tooltip.measureAndPos(el);
       });
       el.addEventListener("mouseleave", () => {
         const tipEl = document.querySelector(".skill-tooltip");
         if (tipEl) tipEl.classList.remove("flag-tooltip");
         tooltip.hide();
       });
     });
   }
   // ====== Skill/Subskill inheritance helpers ======
   const mainSkillsMeta = {
     byIndex: new Map(),
     byName: new Map(),
     ready: false,
   };
   function normalizeFileURL(raw, fallback = "") {
     if (!raw) return fallback;
     const val = String(raw).trim();
     if (!val) return fallback;
     if (
       /^(https?:)?\/\//i.test(val) ||
       val.startsWith("data:") ||
       val.includes("Especial:FilePath/")
     ) {
       return val;
     }
     return filePathURL(val);
   }
   function extractFileNameFromURL(url) {
     if (!url) return "";
     const match = String(url).match(/(?:FilePath\/)([^&?]+)/i);
     return match ? decodeURIComponent(match[1]) : "";
   }
   function parseAttrString(raw) {
     const parts = (raw || "").split(",").map((v) => v.trim());
     const safe = (idx) => {
       const val = parts[idx] || "";
       return val && val !== "-" ? val : "";
     };
     return {
       powerpve: safe(0),
       powerpvp: safe(1),
       energy: safe(2),
       cooldown: safe(3),
     };
   }
   function hasText(value) {
     return typeof value === "string"
       ? value.trim() !== ""
       : value !== undefined && value !== null;
   }
   function pickFilled(current, fallback) {
     if (current === 0 || current === "0") return current;
     if (!hasText(current)) return fallback;
     return current;
   }
   function buildMainSkillsMeta(nodes) {
     if (mainSkillsMeta.ready) {
       return mainSkillsMeta;
     }
     (nodes || []).forEach((icon) => {
       const index = (icon.dataset.index || "").trim();
       if (!index) return;
       const name = (icon.dataset.nome || icon.dataset.name || "").trim();
       const attrs = parseAttrString(icon.dataset.atr || "");
       let iconFile = (icon.dataset.iconFile || "").trim();
       if (!iconFile) {
         const imgSrc = icon.querySelector("img")?.src || "";
         const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
         iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : "";
       }
       let videoFile = (icon.dataset.videoFile || "").trim();
       if (!videoFile) {
         videoFile = extractFileNameFromURL(icon.dataset.video || "");
       }
       const meta = {
         index,
         name,
         icon: iconFile || "",
         level: icon.dataset.level || "",
         video: videoFile || "",
         powerpve: attrs.powerpve || "",
         powerpvp: attrs.powerpvp || "",
         energy: attrs.energy || "",
         cooldown: attrs.cooldown || "",
         desc: icon.dataset.desc || "",
         descPt: icon.dataset.descPt || "",
         descEn: icon.dataset.descEn || "",
         descEs: icon.dataset.descEs || "",
         descPl: icon.dataset.descPl || "",
       };
       mainSkillsMeta.byIndex.set(index, meta);
       mainSkillsMeta.byIndex.set(parseInt(index, 10), meta);
       if (name) {
         mainSkillsMeta.byName.set(name, meta);
       }
     });
     mainSkillsMeta.ready = true;
     return mainSkillsMeta;
   }
   function inheritSubskillFromMain(sub, meta) {
     if (!sub || !meta) return sub;
     // 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 || "");
     // 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;
   const videoBox = $("#skills") ? $(".video-container", $("#skills")) : null;
   const videosCache = new Map();
   const nestedVideoElByIcon = new WeakMap();
   const barStack = [];
   window.__barStack = barStack;
   let initialBarSnapshot = null;
   let totalVideos = 0,
     loadedVideos = 0,
     autoplay = false;
   window.__lastActiveSkillIcon = null;
   let userHasInteracted = false;
   let globalWeaponEnabled = false;
   try {
     if (localStorage.getItem("glaWeaponEnabled") === "1") {
       globalWeaponEnabled = true;
     }
   } catch (err) { }
   const weaponStateListeners = new Set();
   let showWeaponPopupFn = null;
   let popupShouldOpen = false;
   function attachWeaponPopupFn(fn) {
     if (typeof fn !== "function") return;
     showWeaponPopupFn = fn;
     if (popupShouldOpen) {
       popupShouldOpen = false;
       try {
         showWeaponPopupFn();
       } catch (err) { }
     }
   }
   attachWeaponPopupFn(window.__glaWeaponShowPopup);
   function requestWeaponPopupDisplay() {
     try {
       if (localStorage.getItem("glaWeaponPopupDismissed") === "1") return;
     } catch (err) { }
     if (typeof showWeaponPopupFn === "function") {
       showWeaponPopupFn();
       return;
     }
     popupShouldOpen = true;
   }
   function onWeaponStateChange(fn) {
     if (typeof fn !== "function") return;
     weaponStateListeners.add(fn);
   }
   function syncWeaponButtonState(enabled) {
     if (!weaponToggleBtn || !weaponToggleBtn.isConnected) return;
     // Usa .weapon-active em vez de .active para não conflitar com skills
     weaponToggleBtn.classList.toggle("weapon-active", !!enabled);
     weaponToggleBtn.classList.remove("active"); // Garante que .active nunca seja aplicado
     weaponToggleBtn.setAttribute("aria-pressed", enabled ? "true" : "false");
     weaponToggleBtn.setAttribute(
       "aria-label",
       enabled ? "Desativar Arma Especial" : "Ativar Arma Especial"
     );
   }
   function syncWeaponRailState(enabled) {
     if (skillsTopRail) {
       skillsTopRail.classList.toggle("weapon-mode-on", !!enabled);
     }
   }
   function notifyWeaponStateListeners(enabled) {
     weaponStateListeners.forEach((listener) => {
       try {
         listener(enabled);
       } catch (err) { }
     });
   }
   let pendingWeaponState = null;
   window.addEventListener("weapon:ready", (ev) => {
     if (ev && ev.detail && ev.detail.showPopup) {
       attachWeaponPopupFn(ev.detail.showPopup);
     }
     if (pendingWeaponState === null) return;
     if (typeof window.__applyWeaponState === "function") {
       const target = pendingWeaponState;
       pendingWeaponState = null;
       window.__applyWeaponState(target);
     }
   });
   window.__setGlobalWeaponEnabled = (enabled) => {
     globalWeaponEnabled = enabled;
     notifyWeaponStateListeners(enabled);
   };
   function requestWeaponState(targetState) {
     if (typeof window.__applyWeaponState === "function") {
       pendingWeaponState = null;
       window.__applyWeaponState(targetState);
       return;
     }
     pendingWeaponState = targetState;
   }
   onWeaponStateChange(syncWeaponButtonState);
   function reapplyWeaponClassesToBar() {
     // 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);
       }
     }
     // 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 = ""
   ) {
     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 (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)
               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)
       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)
               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)
       if (Array.isArray(s.subs) && s.subs.length > 0) {
         createdCount += preloadSubskillVideosRecursively(
           s.subs,
           parentIdx,
           currentPath
         );
       }
     });
     return createdCount;
   }
   function precreateSubskillVideos() {
     if (!videoBox) 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() {
     if (!videoBox || !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
       // Prioriza data-video-file (nome do arquivo), depois data-video (pode ser URL completa)
       let src = (el.dataset.videoFile || "").trim();
       if (!src) {
         // Se não tem videoFile, tenta extrair do video (pode ser URL completa)
         const videoAttr = (el.dataset.video || "").trim();
         if (videoAttr) {
           // Se já é uma URL completa, usa direto; senão normaliza
           if (videoAttr.includes("/") || videoAttr.startsWith("http")) {
             src = videoAttr;
           } else {
             src = 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,
               }
             );
             videoBox.appendChild(v);
             videosCache.set(idx, v);
             nestedVideoElByIcon.set(el, v);
             // Força carregamento imediatamente (apenas uma vez)
             v.load();
           }
         }
       }
       // 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,
                 }
               );
               videoBox.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;
     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);
       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;
     }
     // 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;
           }
         }
       } 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;
     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);
     }
     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)
     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) {
       // Atualiza o data-video-file do ícone com o vídeo correto da transição baseado na forma atual
       try {
         const formsJSON = skillsRoot.dataset.forms || "{}";
         if (formsJSON && formsJSON !== "{}") {
           const tempFormsData = JSON.parse(formsJSON);
           const formNames = Object.keys(tempFormsData);
           // Busca vídeo de transição na skill Change Form
           const formVideosRaw =
             el.dataset.formVideos || el.getAttribute("data-form-videos");
           if (formVideosRaw) {
             const videos = JSON.parse(formVideosRaw);
             // Se currentForm é null, detecta qual forma está atualmente visível no DOM
             let formForVideo = currentForm;
             if (!formForVideo) {
               // Lê formsData se ainda não foi carregado
               if (Object.keys(formsData).length === 0) {
                 try {
                   formsData = JSON.parse(formsJSON);
                 } catch (e) {
                   formsData = {};
                 }
               }
               formForVideo = detectCurrentForm();
               // Se ainda não conseguiu detectar, usa a primeira como fallback
               if (!formForVideo && formNames.length > 0) {
                 formForVideo = formNames[0];
               }
             }
             const transitionVideo = formForVideo
               ? videos[formForVideo] || ""
               : "";
             if (transitionVideo && transitionVideo.trim() !== "") {
               // Atualiza temporariamente o data-video-file (o sistema usa isso)
               el.dataset.videoFile = transitionVideo;
             }
           }
         }
       } catch (e) {
         console.error("[Forms] Erro ao processar vídeo de transição:", e);
       }
       // Usa o sistema normal de vídeo (mesmo que skills normais)
       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 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]) =>

`

${rowLabel}${rowValue}

`

       )
       .join("");

return `

${html}

`;

   }
   function syncDescHeight() { }
   window.addEventListener("resize", syncDescHeight);
   if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox);
   iconItems.forEach((el) => {
     const wired = !!el.dataset._sync_wired;
     if (wired) return;
     el.dataset._sync_wired = "1";
     el.addEventListener("click", () => {
       Promise.resolve().then(syncDescHeight);
     });
   });
   if (iconsBar) {
     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()
   }
   wireClicksForCurrentBar();
   if (iconItems.length) {
     const 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>