Widget:Teste

De Wiki Gla
Revisão de 14h44min de 31 de agosto de 2025 por Visno (discussão | contribs)
Ir para navegação Ir para pesquisar

<script>

   (function () {
       const $ = (s, root = document) => root.querySelector(s);
       const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
       // Idempotent helpers
       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; };
       // Basic DOM nodes
       const skillsTab = $('#skills');
       const skinsTab = $('#skins');
       // Clean up legacy nodes/titles/placeholders before building
       ensureRemoved('.top-rail');
       ensureRemoved('.content-card');
       ensureRemoved('.video-placeholder');
       // remove old duplicated skins titles (text match)
       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')) {
               // keep only if it's inside a future top-rail.skins we will create; remove for now
               t.remove();
           }
       });
       // Build top-rail for skills (only icon-bar, centered)
       if (skillsTab) {
           const iconBar = skillsTab.querySelector('.icon-bar');
           if (iconBar) {
               const rail = document.createElement('div');
               rail.className = 'top-rail skills';
               // center icon bar inside rail
               rail.appendChild(iconBar);
               skillsTab.prepend(rail);
           }
       }
       // Build top-rail for skins (single title + keep carousel wrapper)
       if (skinsTab) {
           const wrapper = skinsTab.querySelector('.skins-carousel-wrapper');
           const rail = document.createElement('div');
           rail.className = 'top-rail skins';
           const title = document.createElement('div');
           title.className = 'rail-title';
           title.textContent = 'Skins & Spotlights';
           rail.appendChild(title);
           if (wrapper) {
               const card = document.createElement('div');
               card.className = 'content-card';
               card.appendChild(wrapper);
               skinsTab.prepend(rail);
               skinsTab.appendChild(card);
           } else {
               skinsTab.prepend(rail);
           }
       }
       // Create content-card for skills (description + video) and move nodes in
       if (skillsTab) {
           const details = skillsTab.querySelector('.skills-details');
           const videoContainer = skillsTab.querySelector('.video-container');
           const card = document.createElement('div');
           card.className = 'content-card skills-grid';
           if (details) card.appendChild(details);
           if (videoContainer) card.appendChild(videoContainer);
           skillsTab.appendChild(card);
       }
       // ----- Elements after build
       const iconsBar = $('#skills') ? $('.icon-bar', $('#skills')) : null;
       const iconItems = iconsBar ? Array.from(iconsBar.querySelectorAll('.skill-icon')) : [];
       const descBox = $('#skills') ? $('.desc-box', $('#skills')) : null;
       const videoBox = $('#skills') ? $('.video-container', $('#skills')) : null;
       // Video cache and state
       const videosCache = new Map();
       let totalVideos = 0, loadedVideos = 0, autoplay = false;
       // Single placeholder node: create once at page load and never re-create later.
       let placeholder = videoBox ? videoBox.querySelector('.video-placeholder') : null;
       let placeholderCreatedOnLoad = false;
       // true once the single placeholder was used/consumed (only show for the first active video)
       let placeholderConsumed = false;
       if (!placeholder && videoBox) {
           placeholder = document.createElement('div');
           placeholder.className = 'video-placeholder';
           placeholder.innerHTML = '<img src="/images/d/d5/Icon_gla.png" alt="Carregando...">';
           videoBox.appendChild(placeholder);
           placeholderCreatedOnLoad = true;
       } else if (placeholder) {
           // If a placeholder already exists in DOM (legacy), mark it as created on load.
           placeholderCreatedOnLoad = true;
       }
       if (!placeholder) placeholderConsumed = true; // nothing to show
       // Robust hide/show: fade-out hides the single placeholder but keeps it in DOM
       // so it can be reused without duplication. Removal (DOM delete) is avoided.
       const removePlaceholderSmooth = () => {
           if (!placeholder) return;
           if (placeholder.classList.contains('fade-out')) return;
           placeholder.classList.add('fade-out');
           // when transition completes, keep node but hide it so showPlaceholder can reuse.
           const onEnd = () => {
               try { placeholder.style.display = 'none'; } catch (e) { /* ignore */ }
               placeholder.removeEventListener('transitionend', onEnd);
           };
           placeholder.addEventListener('transitionend', onEnd, { once: true });
           // fallback ensure it's hidden
           setTimeout(() => { try { placeholder.style.display = 'none'; } catch (e) {} }, 600);
       };
       // showPlaceholder: shows the single placeholder created at load (no new nodes).
       const showPlaceholder = () => {
           if (!videoBox) return;
           if (!placeholder || !placeholderCreatedOnLoad) return;
           if (placeholderConsumed) return; // never show again after first use
           placeholder.classList.remove('fade-out');
           placeholder.style.display = 'flex';
           // force reflow to ensure transition apply on hide later
           void placeholder.offsetWidth;
       };
       // Utility: attach listener only once per element
       const addOnce = (el, ev, fn) => {
           if (!el) return;
           const attr = `data-wired-${ev}`;
           if (el.hasAttribute(attr)) return;
           el.addEventListener(ev, fn);
           el.setAttribute(attr, '1');
       };
       // Preload videos (idempotent: skip if present in map)
       if (iconItems.length && videoBox) {
           iconItems.forEach(el => {
               const src = (el.dataset.video || ).trim();
               const idx = el.dataset.index || ;
               if (!src || videosCache.has(idx)) return;
               totalVideos++;
               const v = document.createElement('video');
               v.className = 'skill-video';
               v.setAttribute('controls', );
               v.setAttribute('preload', 'auto');
               v.setAttribute('playsinline', );
               v.style.display = 'none';
               v.dataset.index = idx;
               // prefer consistent sizing: width 100% inside video container, and use 16:9 to avoid layout jumps
               v.style.width = '100%';
               v.style.maxWidth = '100%';
               v.style.height = 'auto';
               v.style.aspectRatio = '16/9';
               v.style.objectFit = 'cover';
               const source = document.createElement('source');
               source.src = src;
               source.type = 'video/webm';
               v.appendChild(source);
               v.addEventListener('canplay', () => {
                   loadedVideos++;
                   if (loadedVideos === 1) { try { v.pause(); v.currentTime = 0; } catch (e) { } }
                   const active = $('.skill-icon.active', iconsBar);
                   if (active && active.dataset.index === idx) {
                       // remove placeholder for the active video (only once)
                       if (!placeholderConsumed) {
                           setTimeout(() => { removePlaceholderSmooth(); placeholderConsumed = true; }, 180);
                       }
                   }
                   if (loadedVideos === totalVideos) autoplay = true;
               });
               v.addEventListener('error', () => {
                   loadedVideos++;
                   removePlaceholderSmooth();
                   if (loadedVideos === totalVideos) autoplay = true;
               });
               videoBox.appendChild(v);
               videosCache.set(idx, v);
           });
       }
       if (totalVideos === 0 && placeholder) {
           // no videos: hide placeholder but keep the node for idempotence
           placeholder.style.display = 'none';
           placeholder.classList.add('fade-out');
       }
       // Attach skill icon click handlers once
       iconItems.forEach(el => {
           if (el.dataset.wired) return;
           el.dataset.wired = '1';
           const name = el.dataset.nome || el.dataset.name || ;
           const desc = (el.dataset.desc || ).replace(/(.*?)/g, '$1');
           const attrs = el.dataset.atr || el.dataset.attrs || ;
           const idx = el.dataset.index || ;
           const hasVideo = !!(el.dataset.video && el.dataset.video.trim() !== );
           el.title = name;
           el.addEventListener('click', () => {
               // update description area
               if (descBox) {
                   descBox.innerHTML = `

${name}

 ${renderAttributes(attrs)}
${desc}

`;

               }
               // switch videos: hide all, show selected (if any)
               videosCache.forEach(v => { try { v.pause(); } catch (e) { } v.style.display = 'none'; });
               if (videoBox) {
                   if (hasVideo && videosCache.has(idx)) {
                       const v = videosCache.get(idx);
                       // ensure video container visible and video fills container consistently
                       videoBox.style.display = 'block';
                       v.style.display = 'block';
                       try { v.currentTime = 0; } catch (e) {}
                       // show the (single) placeholder until this video can play
                       if (v.readyState >= 3) {
                           if (!placeholderConsumed) { removePlaceholderSmooth(); placeholderConsumed = true; }
                       } else {
                           // show placeholder only if it hasn't been used yet
                           if (!placeholderConsumed) {
                               showPlaceholder();
                               const canplayHandler = () => { removePlaceholderSmooth(); placeholderConsumed = true; v.removeEventListener('canplay', canplayHandler); };
                               v.addEventListener('canplay', canplayHandler);
                           }
                       }
                       if (autoplay) v.play().catch(() => { });
                   } else {
                       // no video for this skill: hide video area and hide placeholder
                       videoBox.style.display = 'none';
                       if (placeholder) {
                           placeholder.style.display = 'none';
                           placeholder.classList.add('fade-out');
                       }
                   }
               }
               // active state
               iconItems.forEach(i => i.classList.remove('active'));
               el.classList.add('active');
               if (!autoplay && loadedVideos > 0) autoplay = true;
           });
       });
       // Tooltip: single global tooltip for skill icons (idempotent)
       (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);
           function measureAndPos(el) {
               if (!el || tip.getAttribute('aria-hidden') === 'true') return;
               // reset to measure
               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));
               let top = Math.round(rect.top - tr.height - 8);
               if (top < 8) top = Math.round(rect.bottom + 8);
               tip.style.left = left + 'px';
               tip.style.top = top + 'px';
           }
           function show(el, text) {
               tip.textContent = text || ;
               tip.setAttribute('aria-hidden', 'false');
               tip.style.opacity = '1';
               measureAndPos(el);
           }
           function hide() {
               tip.setAttribute('aria-hidden', 'true');
               tip.style.opacity = '0';
           }
           // attach once to each icon
           Array.from(document.querySelectorAll('.icon-bar .skill-icon')).forEach(icon => {
               if (icon.dataset.tipwired) return;
               icon.dataset.tipwired = '1';
               const label = icon.dataset.nome || icon.dataset.name || icon.title || ;
               icon.addEventListener('mouseenter', () => show(icon, label));
               icon.addEventListener('mousemove', () => measureAndPos(icon));
               icon.addEventListener('mouseleave', hide);
           });
           // keep positioned while scrolling/resizing
           window.addEventListener('scroll', () => {
               const visible = document.querySelector('.skill-tooltip[aria-hidden="false"]');
               if (!visible) return;
               const hover = document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active');
               measureAndPos(hover);
           }, true);
           window.addEventListener('resize', () => {
               const hover = document.querySelector('.icon-bar .skill-icon:hover') || document.querySelector('.icon-bar .skill-icon.active');
               measureAndPos(hover);
           });
       })();
       // Select first skill by default (dispatch click once)
       if (iconItems.length) {
           const first = iconItems[0];
           if (first) {
               // ensure default active visual & trigger behavior
               if (!first.classList.contains('active')) first.classList.add('active');
               // trigger click after microtask so videos are ready to be shown
               setTimeout(() => first.click(), 0);
           }
       }
       // Description height is fixed via CSS (max-height). Keep this function as no-op
       // to avoid dynamic inline max-height adjustments that cause inconsistent behaviour.
       function syncDescHeight() {
           return;
       }
       // keep in sync on resize and when videos change layout
       window.addEventListener('resize', syncDescHeight);
       if (videoBox) new ResizeObserver(syncDescHeight).observe(videoBox);
       // call sync after each icon click (desc is rebuilt there)
       // patch: wrap existing click wiring to call syncDescHeight after updating DOM
       iconItems.forEach(el => {
           const wired = !!el.dataset._sync_wired;
           if (wired) return;
           el.dataset._sync_wired = '1';
           // find existing click handler we attached earlier and chain sync
           el.addEventListener('click', () => {
               // run in next microtask so DOM update from earlier handler is settled
               Promise.resolve().then(syncDescHeight);
           });
       });
       // horizontal scroll on wheel (attach once)
       if (iconsBar) addOnce(iconsBar, 'wheel', (e) => {
           if (e.deltaY) {
               e.preventDefault();
               iconsBar.scrollLeft += e.deltaY;
           }
       });
       // skins carousel arrows init (idempotent)
       (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();
       })();
       // Render attributes function (keeps empty rows above visible ones)
       function renderAttributes(str) {
           const vals = (str || ).split(',').map(v => v.trim());
           const pve = parseInt(vals[0], 10);
           const pvp = parseInt(vals[1], 10);
           const ene = parseInt(vals[2], 10);
           const cd = parseInt(vals[3], 10);
           const recargaVal = isNaN(cd) ? '-' : cd;
           const energiaLabel = isNaN(ene) ? 'Energia' : (ene >= 0 ? 'Ganho de energia' : 'Custo de energia');
           const energiaVal = isNaN(ene) ? '-' : Math.abs(ene);
           const poderVal = isNaN(pve) ? '-' : pve;
           const poderPvpVal = isNaN(pvp) ? '-' : pvp;
           const rows = [
               ['Recarga', recargaVal],
               [energiaLabel, energiaVal],
               ['Poder', poderVal],
               ['Poder PvP', poderPvpVal],
           ];
           // Only render actual attributes. Do not emit invisible placeholders
           const visible = rows.filter(([, v]) => v !== '-');
           if (visible.length === 0) return ; // nothing to show -> no reserved space
           const visibleHtml = visible.map(([label, value]) => `
                   ${label}:
                   ${value}
           `).join();

return `

${visibleHtml}

`;

       }
       // language-aware tab label for Skills
       (function localizeSkillsTab() {
           const lang = (document.documentElement.lang || navigator.language || ).toLowerCase();
           const code = lang.split('-')[0];
           const map = {
               en: 'Skills',
               'pt': 'Habilidades',
               'pt-br': 'Habilidades',
               'es': 'Habilidades',
               pl: 'Umiejętności'
           };
           // prefer full match (pt-br) then base code
           const key = map[lang] ? lang : (map[code] ? code : 'en');
           const label = map[key] || map.en;
           // try well-known selector first, fallback to searching for english-like text
           let btn = document.querySelector('.character-tabs .tab-btn[data-tab="skills"]') ||
                     Array.from(document.querySelectorAll('.character-tabs .tab-btn')).find(b => /skill|habil|umiej/i.test(b.textContent || ));
           if (btn) btn.textContent = label;
       })();
       // DEBUG: log video sources and errors
       setTimeout(() => {
           console.log('DEBUG: totalVideos=', totalVideos);
           Array.from(document.querySelectorAll('.skill-icon')).forEach(el => {
               console.log('icon', el.dataset.index, 'data-video=', el.dataset.video);
           });
           videosCache.forEach((v, idx) => {
               const src = v.querySelector('source') ? v.querySelector('source').src : v.src;
               console.log('video element', idx, 'src=', src, 'readyState=', v.readyState);
               v.addEventListener('error', (ev) => {
                   console.error('VIDEO ERROR idx=', idx, 'src=', src, 'error=', v.error);
               });
               v.addEventListener('loadedmetadata', () => {
                   console.log('loadedmetadata idx=', idx, 'dimensions=', v.videoWidth, 'x', v.videoHeight);
               });
           });
       }, 600);
   })();

</script>

<style>

   /* ===========================
  Base
  =========================== */
   img {
       pointer-events: none;
       user-select: none;
   }

   /* video baseline; specific skill video sizing overridden below */
   video { max-height: none; }

   .mw-body {
       padding: unset !important;
   }

   /* precisa de !important p/ MediaWiki */
   .mw-body-content {
       line-height: 1.5;
   }

   .mw-body-content p {
       display: none;
   }

   /* ===========================
  Banner
  =========================== */
   /* Hide the original banner element (we'll use the image as the template background)
      and keep the selector so existing markup doesn't break. */
   .banner { display: none !important; }
   /* Use the banner image as the background for the template container and add
      a subtle dark overlay via ::before to improve text contrast. */
   .character-box {
       /* ...existing properties... */
       background-image: url(https://i.imgur.com/RktmgO8.png);
       background-position: center top;
       background-repeat: no-repeat;
       background-size: cover;
       position: relative; /* ensure positioning context for ::before */
       z-index: 1; /* base layer for the box content */
   }
   /* overlay sits above the background image but below the content; darker bottom-to-top gradient */
   .character-box::before {
       content: "";
       position: absolute;
       inset: 0;
       pointer-events: none;
       background: linear-gradient(to bottom, rgba(0,0,0,.45), rgba(0,0,0,.60));
       z-index: 0; /* overlay: below content (content kept at z-index:1) */
   }
   /* ===========================
  Character topbar
  =========================== */
   .character-box {
       color: #000;
       font-family: 'Noto Sans', sans-serif;
       width: 100%;
       margin: auto;
       position: relative;
       user-select: none;
   }
   .character-box p {
       display: unset;
   }
   .character-topbar {
       display: flex;
       flex-direction: column;
       align-items: flex-start;
       padding: 8px 20px 0;
   z-index: 1; /* topbar above overlay */
   }
   .character-name-box {
       display: flex;
       align-items: center;
       gap: 14px;
   }
   .topbar-icon {
       margin-top: 8px;
       width: 100px;
       height: 100px;
       object-fit: none;
   }
   .character-name {
       color: #fff;
       font-size: 56px;
       font-family: 'Orbitron', sans-serif;
       font-weight: 900;
       text-shadow: 0 0 6px #000, 0 0 9px #000;
   }
   .topbar-description {
       display: none;
   }
   /* ===========================
  Header / Artwork
  =========================== */
   .character-header {
       position: relative;
       overflow: hidden; /* artwork must not 'vazar' */
       display: flex;
       gap: 10px;
       flex-direction: column;
       z-index: 1; /* header/topbar layer */
   }
   /* artwork removed from layout (server still accepts param but we ignore rendering).
      ensure it doesn't reserve space if present in legacy markup */
   .character-art { display: none !important; width: 0 !important; height: 0 !important; overflow: hidden !important; }
   /* Class / tier chips */
   .class-tags {
       display: flex;
       gap: 9px;
       flex-wrap: wrap;
       margin-left: .28rem;
   }
   .class-tag {
       background: #353420;
       color: #fff;
       outline: 2px solid #000;
       padding: 1px 6px;
       border-radius: 4px;
       font-size: .9em;
       font-weight: 700;
       box-shadow: 0 0 2px rgb(0 0 0 / 70%);
   }
   .character-info .tier,
   .character-info .class-tag {
       font-size: 18px;
       color: #bbb;
   }
   /* ===========================
  Tabs
  =========================== */
   .character-tabs {
       margin: 4px 0 4px 8px;
       display: flex;
       gap: 12px;
   }
   .tab-btn {
       padding: 5px 20px;
       background: #333;
       color: #fff;
       border: 2px solid transparent;
       border-radius: 8px;
       font-size: 20px;
       cursor: pointer;
       font-weight: 600;
       line-height: 1;
       transition: background .15s, border-color .15s;
   }
   .tab-btn.active {
       background: #156bc7;
       border-color: #156bc7;
   }
   .tab-content {
       display: none;
       padding: 0 8px 8px;
       position: relative;
   z-index: 2; /* content layer */
   }
   .tab-content.active {
       display: block;
   }
   /* ===========================
  Skills 
  =========================== */
   .skills-container {
       display: flex;
       gap: 20px;
   }
   .skills-details {
       flex: 1;
       display: flex;
       flex-direction: column;
       gap: 10px;
       width: auto; /* let grid control widths */
       justify-content: center;
   }
   .icon-bar {
       display: flex;
       flex-wrap: nowrap;
       gap: 10px;
       width: 100%;
       overflow-x: auto;
       overflow-y: hidden;
       padding: 6px 6px; /* centraliza melhor verticalmente */
       margin-bottom: 6px;
       scrollbar-width: thin;
       scrollbar-color: #ababab transparent;
       scroll-behavior: smooth;
       justify-content: flex-start;
       align-items: center; /* centraliza os ícones verticalmente dentro da barra */
       position: relative;
       z-index: 4;
   }
   .icon-bar::-webkit-scrollbar {
       height: 6px;
   }
   .icon-bar::-webkit-scrollbar-track {
       background: transparent;
   }
   .icon-bar::-webkit-scrollbar-thumb {
       background: #151515;
       border-radius: 3px;
   }
   :root {
       --icon-size: 39px;
       --icon-radius: 8px;
       /* ring width and colors (icons-only overrides) */
       --icon-ring-w: 2px;
       --icon-idle: #bbb;
       --icon-active: #FFD257;               /* amarelo agradável */
       --icon-active-ring: rgba(255,210,87,.45);
       --icon-active-glow: rgba(255,210,87,.35);
   }
   /* ensure each icon is vertically centered inside the bar */
   .icon-bar .skill-icon {
       /* keep original sizing and alignment but ensure perfect clipping */
       width: var(--icon-size);
       height: var(--icon-size);
       position: relative;
       flex: 0 0 auto;
       border-radius: var(--icon-radius);
       overflow: hidden;        /* hard clip the image */
       contain: paint;          /* reduce subpixel leakage */
       margin-top: 0;
       display: flex;
       align-items: center;
       justify-content: center;
       -webkit-tap-highlight-color: transparent;
       background-clip: padding-box;
   }
   .icon-bar .skill-icon img {
       display: block;
       width: 100%;
       height: 100%;
       object-fit: cover;
       border-radius: inherit;
       clip-path: inset(0 round var(--icon-radius));
       -webkit-clip-path: inset(0 round var(--icon-radius));
       will-change: transform;
       backface-visibility: hidden;
       transform: translateZ(0);
       transition: transform .12s ease;
   }
   /* internal ring implemented via inset box-shadow to avoid border layout changes */
   .icon-bar .skill-icon::after {
       content: "";
       position: absolute;
       inset: 0;
       border-radius: inherit;
       /* internal ring keeps constant thickness between states */
       box-shadow: inset 0 0 0 var(--icon-ring-w) var(--icon-idle);
       pointer-events: none;
       z-index: 2;
       transition: box-shadow .14s ease;
   }
   /* subtle hover color change without changing ring width */
   .icon-bar .skill-icon:hover::after {
       box-shadow: inset 0 0 0 var(--icon-ring-w) #e0e0e0;
   }
   /* separate pseudo for glow so ring thickness does not change */
   .icon-bar .skill-icon::before {
       content: "";
       position: absolute;
       inset: 0;
       border-radius: inherit;
       pointer-events: none;
       z-index: 1;
       box-shadow: none;
       opacity: 0;
       transition: opacity .14s ease, box-shadow .14s ease;
   }
   .icon-bar .skill-icon.active::after {
       box-shadow: inset 0 0 0 var(--icon-ring-w) var(--icon-active);
   }
   .icon-bar .skill-icon.active::before {
       /* glow only (does not change inner ring thickness) */
       box-shadow: 0 0 10px 2px var(--icon-active-glow), 0 0 0 4px var(--icon-active-ring);
       opacity: 1;
   }
   /* smooth zoom on the image only (keeps ring aligned) */
   .icon-bar .skill-icon.active img {
       transform: scale(1.10);
   }
   @media (prefers-reduced-motion: reduce) {
       .icon-bar .skill-icon {
           transition: none;
       }
   }
   /* Title description */
   .skill-title {
       margin: 0 0 12px;
       display: flex;
       justify-content: center;
       align-items: center;
   }
   .skill-title h3 {
       margin: 0;
       width: 100%;
       text-align: center;
       font-size: 1.6em;
       color: #fff;
   }
   /* Description */
   /* Description box: remove own card background/shadow so it doesn't stack
      on top of the outer .content-card. The .content-card remains the main
      visual container. Keep padding and readable colors. */
   .desc-box {
       padding: 12px 18px; /* breathing room */
       background: transparent;
       border-radius: 6px;
       position: relative;
       box-shadow: none;
       color: #fff;
       transition: all .3s ease;
       z-index: 2;
       overflow: visible;
       text-shadow: none;
       /* allow description area to grow but not force whole card height */
       height: auto;
       min-height: 0;
   }
   .desc-box h3 {
       font-size: 2.7em;
       margin: 0;
       text-align: center;
       padding-top: 0;
   }
   .desc {
       font-size: 1.32em !important;
       line-height: 1.7 !important;
       letter-spacing: .01em;
       /* Fixed max height so long descriptions always scroll consistently */
       max-height: 100%;
       /* keep scrollbar gutter stable to avoid layout shift of background/content */
       overflow-y: auto;
       scrollbar-gutter: stable;
       margin-top: 10px;
       padding-right: 8px;
       color: #f1efe9;
       /* allow long words/URLs to wrap instead of pushing layout */
       overflow-wrap: anywhere;
       word-break: break-word;
       white-space: normal;
   }
   .desc b,
   .desc strong {
       font-weight: 700;
       color: #fff;
   }
   .desc::-webkit-scrollbar {
       width: 7px;
       height: 7px;
   }
   .desc::-webkit-scrollbar-thumb {
       background: #156bc7;
       border-radius: 10px;
   }
   .desc::-webkit-scrollbar-track {
       background: #151515a8;
       border-radius: 10px;
   }
   /* Attributes list */
   .attrs,
   .attr-list {
       display: block;
       margin: 6px 0 12px;
   }
   .desc-box .attrs,
   .desc-box .attr-list,
   .desc-box .attrs *,
   .desc-box .attr-list * {
       text-shadow: none;
       font-family: 'Noto Sans', sans-serif;
   }
   .attrs__row,
   .attr-row {
       display: flex;
       align-items: baseline;
       gap: 8px;
       min-height: 22px;
       line-height: 1.2;
   }
   .attrs__row--empty,
   .attr-row.is-empty {
       display: none;
   }
   .attrs__label,
   .attr-label {
       font-weight: 700;
       color: #f0c87b;
       font-size: .98rem;
       white-space: nowrap;
       margin: 0;
   }
   .attrs__value,
   .attr-value {
       color: #fff;
       font-weight: 800;
       /* equal size with label for consistent centering */
       font-size: .98rem;
       letter-spacing: .01em;
       margin: 0;
   }
   /* ensure rows center both label & value (value vertically centered on label) */
   .attr-row {
       align-items: center;
   }
   /* remove fixed skill pane height and let content decide height */
   :root { --skill-pane-height: unset; }
   .skills-container { align-items: flex-start; }
   /* Video container: allow the video to display at its original size
      while keeping it responsive (max-width:100%). Remove extra inner
      shadows so the .content-card is the primary card. Center content. */
   .video-container {
        position: relative;
        width: 100%;
        max-width: 100%;
        background: transparent;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 10px;
        box-shadow: none;
        overflow: hidden; /* rounded corners */
        padding: 0;
        z-index: 2;
    }

    /* ensure every skill video has consistent visible size to avoid tiny thumbnails;
       videos fill the video column (responsive) using 16:9 to avoid layout jumps */
   .video-container>video {
       /* Preencher a coluna do vídeo; mostrar conteúdo inteiro sem recorte */
       width: 100%;
       max-width: 100%;
       height: auto;
       aspect-ratio: 16 / 9;
       object-fit: contain;
       object-position: center;
       z-index: 2;
       display: block;
       border-radius: 6px;
       background: #000;
   }
   /* Center the placeholder/logo while videos load. Keep it as an overlay
      so it doesn't shift layout. */
   .video-placeholder {
       position: absolute;
       inset: 0;
       z-index: 6;
       display: flex;
       align-items: center;
       justify-content: center;
       pointer-events: none;
       opacity: 1;
       transition: opacity .28s ease;
       background: linear-gradient(rgba(0,0,0,0.0), rgba(0,0,0,0.0)); /* transparent overlay but keeps stacking context */
   }
   .video-placeholder img {
       max-width: 160px;
       width: auto;
       height: auto;
       opacity: 0.98;
       display: block;
   }
   /* fade-out helper used by JS removePlaceholder() */
   .video-placeholder.fade-out {
       opacity: 0;
   }
   @media (max-width:1100px) {
       .top-rail {
           flex-direction: column;
           align-items: stretch;
       }
       .top-rail .icon-bar {
           order: 2;
           width: 100%;
           flex-wrap: wrap;
       }
       .content-card.skills-grid { grid-template-columns: 1fr; }
   }
   @media (max-aspect-ratio: 3/4) {
        .character-header .character-art {
            display: none;
        }
       .video-container {
           width: 80%;
           border-radius: 3%;
           margin-top: 2%;
           align-self: center;
       }
   }


   /* Tiers */
   .tier-bronze .topbar-icon,
   .tier-bronze .tier {
       outline: 2px solid #7b4e2f;
   }
   .tier-silver .topbar-icon,
   .tier-silver .tier {
       outline: 2px solid #d6d2d2;
   }
   .tier-gold .topbar-icon,
   .tier-gold .tier {
       outline: 2px solid #fcd300;
   }
   .tier-diamond .topbar-icon,
   .tier-diamond .tier {
       outline: 2px solid #60dae2;
   }
   /* Top rail: created dynamically by JS; styles for tabs header */
   .top-rail {
       display:flex;
       align-items:center;
       justify-content:center;
       width:max-content;
       max-width:96vw;
       margin:8px auto;
       padding:8px 12px;
       background:rgba(0,0,0,.55);
       border: 2px solid rgba(255,255,255,.08);
       border-radius: 12px;
       box-shadow: 0 4px 12px rgba(0,0,0,.25);
       -webkit-backdrop-filter:blur(2px);
       backdrop-filter:blur(2px);
   }
   /* hide title by default (skills rail won't show it) */
   .top-rail .rail-title { display:none; }
   /* skins variant shows the title at left */
   .top-rail.skins .rail-title {
       display:block;
       font-weight:800;
       font-size:clamp(20px,2.2vw,28px);
       color:#fff;
       margin-right:auto;
   }
   /* center icons and keep scroll behavior if overflow */
   .top-rail .icon-bar {
       width:auto;
       justify-content:center;
       margin:0;
       overflow-x:auto; /* preserve horizontal scroll */
   }
   /* Card sizing: larger but constrained to viewport; padding included via box-sizing */
   .content-card {
       width: min(1600px, 96vw);
       max-width: 96vw;
       margin: 10px auto;
       background: #26211C;
       border-radius: 12px;
       box-shadow: 0 8px 24px rgba(0,0,0,.30);
       padding: 18px;
       z-index: 2; /* above overlay */
   }

    /* layout specific for skills card: larger grid */
    /* Make the video column wider so the video has more space.
       minmax garante largura mínima para o vídeo e permite expansão até 70% do card. */
    .content-card.skills-grid {
        display: grid;
       /* user requested layout for testing: description column wider, video a bit smaller */
       grid-template-columns: minmax(320px, 60%) minmax(320px, 45%);
        gap: 16px; /* menor gap para evitar overflow */
        align-items: start; /* don't stretch items vertically */
        grid-auto-rows: auto;
        margin: 10px auto 0;
    }

   @media (max-width:1100px) {
       .top-rail {
           flex-direction: column;
           align-items: stretch;
       }
       .top-rail .icon-bar {
           order: 2;
           width: 100%;
           flex-wrap: wrap;
       }
       .content-card.skills-grid { grid-template-columns: 1fr; gap: 12px; }
       .video-container { width: 80%; max-width: 820px; align-self: center; }
   }
   @media (max-width:600px) {
   /* Make the card fit symmetrically on small screens (same left/right limit) */
   .content-card {
       box-sizing: border-box;
       /* limitar ao viewport menos safe-area + margem interna */
       max-width: calc(100vw - env(safe-area-inset-left) - env(safe-area-inset-right) - 16px);
       width: 100%;
       margin: 10px auto;
       padding: 12px;
       border-radius: 10px;
       overflow: hidden; /* evita scroll horizontal causado por filhos */
   }
   /* ensure the skills grid collapses to single column and doesn't overflow */
   .content-card.skills-grid {
       grid-template-columns: 1fr;
       gap: 12px;
   }
   /* top-rail full width, icon bar flush edge-to-edge */
   .top-rail {
       width: calc(100vw - env(safe-area-inset-left) - env(safe-area-inset-right));
       max-width: 100%;
       margin: 0 auto 8px;
       padding: 6px 8px;
       border-radius: 0;
       box-sizing: border-box;
       overflow: hidden;
   }
   .top-rail .icon-bar {
       width: 100%;
       padding: 0 6px;
       gap: 12px;
       justify-content: flex-start;
       overflow-x: auto;
       -webkit-overflow-scrolling: touch;
   }
   /* larger, more visible icons on mobile */
   :root { --icon-size: 92px; } /* overrides default on mobile */
   .icon-bar .skill-icon {
       width: var(--icon-size);
       height: var(--icon-size);
       flex: 0 0 auto;
   }
   /* limitar explicitamente elementos do vídeo/skins para não extrapolarem */
   .video-container,
   .skins-carousel-wrapper,
   .skins-carousel {
       max-width: calc(100vw - env(safe-area-inset-left) - env(safe-area-inset-right) - 16px);
       box-sizing: border-box;
   }
   .video-container > video,
   .video-container img,
   .skins-carousel img {
       max-width: 100%;
       width: 100%;
       height: auto;
   }
   /* extra safety: prevent horizontal scroll coming from other elements using 100vw */
   html, body, .mw-body, .mw-body-content {
       overflow-x: hidden;
   }

}

/* ===========================

  Base — mobile/overflow guards
  =========================== */

/* evitar qualquer elemento extrapolar o viewport e respeitar safe-area (iPhone notch) */ html, body, .mw-body, .mw-body-content {

   box-sizing: border-box;
   max-width: 100vw;
   overflow-x: hidden;
   -webkit-overflow-scrolling: touch;

}

/* garantir imagens / vídeos internos não forçarem overflow */ .content-card, .top-rail, .video-container, .skins-carousel-wrapper, .skins-carousel {

   box-sizing: border-box;
   max-width: 100%;

} /* impedir filhos de extrapolarem a coluna do card */ .content-card *, .top-rail *, .video-container * {

   max-width: 100%;
   box-sizing: border-box;

}

/* ---------------------------

  Visual tweaks: icons, attrs, desc
  --------------------------- */

/* override base icon size (desktop) */

root {
   --icon-size: 56px;   /* desktop icons larger */
   --icon-radius: 10px;

}

/* make skill icons use the variable (safety if other rules exist) */ .icon-bar .skill-icon {

   width: var(--icon-size) !important;
   height: var(--icon-size) !important;

}

/* Icon active color variables (only for icon visuals: border + glow) */

root {
   --icon-active: #F7D34B;
   --icon-active-ring: rgba(247,211,75,.35);
   --icon-active-glow: rgba(247,211,75,.32);

}

/* Ensure perfect clipping and aligned border for skill icons */ .icon-bar .skill-icon {

   overflow: hidden;
   border-radius: var(--icon-radius);
   contain: paint;
   width: var(--icon-size) !important;
   height: var(--icon-size) !important;
   position: relative; /* keep stacking for pseudo element */

}

.icon-bar .skill-icon img {

   display: block;
   width: 100%;
   height: 100%;
   object-fit: cover;
   border-radius: inherit;
   clip-path: inset(0 round var(--icon-radius));

}

.icon-bar .skill-icon::after {

   border-radius: inherit;
   /* keep neutral/inactive ring defined elsewhere; this ensures radius matches image */

}

.icon-bar .skill-icon.active::after {

   border-color: var(--icon-active);
   box-shadow: 0 0 0 2px var(--icon-active), 0 0 0 4px var(--icon-active-ring), 0 0 10px 2px var(--icon-active-glow);

}

/* Tooltip style (single global tooltip attached to body to avoid clipping) */ .skill-tooltip {

   position: fixed;
   z-index: 9999;
   pointer-events: none;
   padding: 6px 8px;
   border-radius: 6px;
   background: rgba(17,17,17,.9);
   color: #fff;
   font-size: 14px;
   box-shadow: 0 4px 12px rgba(0,0,0,.5);
   opacity: 0;
   transition: opacity .12s ease, transform .08s ease;
   white-space: nowrap;

}

/* Icon clipping, ring and active halo (overrides; scoped to icons only) */

root {
   --icon-ring-w: 2px;
   --icon-idle: #bbb;
   --icon-active: #FFD95A;                 /* amarelo mais visível */
   --icon-active-ring: rgba(255,217,90,.50);
   --icon-active-glow: rgba(255,217,90,.38);

}

.icon-bar .skill-icon {

   position: relative;
   border: none !important;
   outline: none !important;
   overflow: hidden;
   border-radius: var(--icon-radius);
   contain: paint;
   isolation: isolate;
   will-change: transform;
   transform: translateZ(0);
   /* keep original sizing/flow from existing rules (do not change --icon-size) */

}

.icon-bar .skill-icon img {

   display: block;
   width: 100%;
   height: 100%;
   object-fit: cover;
   border-radius: inherit;
   -webkit-clip-path: inset(0 round var(--icon-radius));
   clip-path: inset(0 round var(--icon-radius));
   transition: transform .12s ease;
   backface-visibility: hidden;
   transform: translateZ(0);

}

.icon-bar .skill-icon::after {

   content: "";
   position: absolute;
   inset: 0;
   border-radius: inherit;
   pointer-events: none;
   z-index: 2;
   /* internal ring that NEVER changes thickness between states */
   box-shadow: inset 0 0 0 var(--icon-ring-w) var(--icon-idle);
   transition: box-shadow .12s ease;

}

.icon-bar .skill-icon::before {

   content: "";
   position: absolute;
   inset: -2px;
   border-radius: calc(var(--icon-radius) + 2px);
   pointer-events: none;
   z-index: 1;
   box-shadow: none;
   opacity: 0;
   transition: opacity .12s ease, box-shadow .12s ease;

}

/* Active state: gentle scale + yellow ring + halo. No change in ring thickness. */ .icon-bar .skill-icon.active {

   transform: scale(1.08);

} .icon-bar .skill-icon.active::after {

   box-shadow: inset 0 0 0 var(--icon-ring-w) var(--icon-active);

} .icon-bar .skill-icon.active::before {

   opacity: 1;
   box-shadow:
       0 0 12px 3px var(--icon-active-glow),
       0 0 0 4px var(--icon-active-ring);

} .icon-bar .skill-icon.active img {

   transform: scale(1.08);

}

/* Hover should never change ring thickness — only color hint */ .icon-bar .skill-icon:hover:not(.active)::after {

   box-shadow: inset 0 0 0 var(--icon-ring-w) #e0e0e0;

}

    /* ===========================
  Fixed background (scoped to article content only)
  =========================== */

/* aplicar o background apenas quando .character-box estiver dentro do conteúdo do artigo */ .mw-body-content .character-box {

   background-image: url("https://i.imgur.com/RktmgO8.png");
   background-position: center top;
   background-repeat: no-repeat;
   /* keep the visual background stable when content height changes.
      using fixed attachment prevents the artwork from scaling as the box grows. */
   background-size: cover;
   background-attachment: fixed;
   position: relative; /* stacking context para pseudo-elementos */
   z-index: 1;

}

/* overlay interno (escuro) acima do background mas abaixo do conteúdo da caixa */ .mw-body-content .character-box::before {

   content: "";
   position: absolute;
   inset: 0;
   pointer-events: none;
   background: linear-gradient(to bottom, rgba(0,0,0,.45), rgba(0,0,0,.60));
   z-index: 0; /* abaixo do conteúdo (.character-box deve ter z-index > 0) */

}

/* remove any previous global fixed rules that targeted body.character-bg .character-box

  NOTE: original reset removed the background we want; removed to keep .character-box background. */

/* (reset removed) */ </style>