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

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
Linha 1: Linha 1:
<!-- SKINS SYSTEM - Layout Isométrico -->
<!-- SKINS SYSTEM - Layout Isométrico -->
<script>
<script>
     (function initSkinPodiumUI() {
     (function initSkinsPodiumUI() {
         const podium = document.querySelector('.skins-podium');
         const podium = document.querySelector('.skins-podium');
         if (!podium || podium.dataset.wired) return;
         if (!podium || podium.dataset.wired === '1') return;
         podium.dataset.wired = '1';
         podium.dataset.wired = '1';


Linha 17: Linha 17:
                 tip.style.transform = 'translate(-9999px,-9999px)';
                 tip.style.transform = 'translate(-9999px,-9999px)';
                 tip.style.opacity = '0';
                 tip.style.opacity = '0';
                 tip.style.transition = 'opacity .12s ease';
                 tip.style.transition = 'opacity .15s ease';
                 document.body.appendChild(tip);
                 document.body.appendChild(tip);
             }
             }
Linha 29: Linha 29:
             if (!card || tip.getAttribute('aria-hidden') === 'true') return;
             if (!card || tip.getAttribute('aria-hidden') === 'true') return;


            // Calcula nova posição - tooltip centralizada em relação à imagem do piso
             tip.style.transform = 'translate(-9999px,-9999px)';
             tip.style.transform = 'translate(-9999px,-9999px)';
             const tr = tip.getBoundingClientRect();
             const tr = tip.getBoundingClientRect();
Linha 34: Linha 35:
             // Pega a posição da imagem do tile (piso) para centralizar
             // Pega a posição da imagem do tile (piso) para centralizar
             const platformImg = card.querySelector('.podium-platform-top img');
             const platformImg = card.querySelector('.podium-platform-top img');
             let platformRect = card.getBoundingClientRect();
             let platformRect = card.getBoundingClientRect(); // Fallback para o card se não encontrar
             if (platformImg) {
             if (platformImg) {
                 platformRect = platformImg.getBoundingClientRect();
                 platformRect = platformImg.getBoundingClientRect();
             } else {
             } else {
                // Se não encontrar a img, tenta o container do tile
                 const platform = card.querySelector('.podium-platform');
                 const platform = card.querySelector('.podium-platform');
                 if (platform) {
                 if (platform) {
Linha 48: Linha 50:
             leftPos = Math.max(8, Math.min(leftPos, window.innerWidth - tr.width - 8));
             leftPos = Math.max(8, Math.min(leftPos, window.innerWidth - tr.width - 8));


             // Posiciona logo abaixo da imagem do tile
             // Posiciona logo abaixo da imagem do tile, com pequeno espaçamento
             let top = Math.round(platformRect.bottom + 15);
             let top = Math.round(platformRect.bottom + 15);
            // Se não couber embaixo, coloca em cima
             if (top + tr.height > window.innerHeight - 8) {
             if (top + tr.height > window.innerHeight - 8) {
                 top = Math.round(platformRect.top - tr.height - 15);
                 top = Math.round(platformRect.top - tr.height - 15);
                 if (top < 8) top = 8;
                 if (top < 8) top = 8; // Fallback se não couber em cima também
             }
             }


Linha 76: Linha 80:
             if (!slot) return;
             if (!slot) return;


            // Determina a posição do slot (1, 2, 3, 4, etc)
             const allSlots = Array.from(podium.querySelectorAll('.podium-slot'));
             const allSlots = Array.from(podium.querySelectorAll('.podium-slot'));
             const slotIndex = allSlots.indexOf(slot);
             const slotIndex = allSlots.indexOf(slot);


             // Z-index invertido: esquerda (0) = mais alto, direita = mais baixo
             // Z-index invertido: esquerda (0) = maior, direita (último) = menor
             const originalZIndex = allSlots.length - slotIndex;
             const originalZIndex = allSlots.length - slotIndex;
             const originalSpriteZIndex = (allSlots.length - slotIndex) * 1000;
             const originalSpriteZIndex = (allSlots.length - slotIndex) * 1000;
Linha 89: Linha 94:


         function setHovered(card) {
         function setHovered(card) {
             if (hoveredSlot === card) return;
             if (hoveredSlot === card) {
                // Se já está hovered, não reposiciona - mantém posição fixa
                return;
            }


            // Remove hover anterior e restaura z-index original
             if (hoveredSlot) {
             if (hoveredSlot) {
                 hoveredSlot.classList.remove('hovered');
                 hoveredSlot.classList.remove('hovered');
Linha 108: Linha 117:
             hoveredSlot.classList.add('hovered');
             hoveredSlot.classList.add('hovered');
             podium.classList.add('hovering');
             podium.classList.add('hovering');
             podium.querySelectorAll('.podium-slot').forEach(n => {
             podium.querySelectorAll('.podium-slot').forEach(n => { if (n !== hoveredSlot) n.classList.add('dim'); });
                if (n !== hoveredSlot) n.classList.add('dim');
            });


             // Qualquer skin com hover fica acima de tudo
             // Qualquer skin com hover fica acima de tudo
Linha 131: Linha 138:
                 ctx.drawImage(img, 0, 0);
                 ctx.drawImage(img, 0, 0);


                // Calcula a posição relativa na imagem
                 const rect = img.getBoundingClientRect();
                 const rect = img.getBoundingClientRect();
                 const scaleX = img.naturalWidth / rect.width;
                 const scaleX = img.naturalWidth / rect.width;
Linha 137: Linha 145:
                 const imgY = Math.floor((y - rect.top) * scaleY);
                 const imgY = Math.floor((y - rect.top) * scaleY);


                // Verifica se está dentro dos limites
                 if (imgX < 0 || imgX >= img.naturalWidth || imgY < 0 || imgY >= img.naturalHeight) {
                 if (imgX < 0 || imgX >= img.naturalWidth || imgY < 0 || imgY >= img.naturalHeight) {
                     return true;
                     return true;
                 }
                 }


                // Obtém o pixel
                 const pixelData = ctx.getImageData(imgX, imgY, 1, 1).data;
                 const pixelData = ctx.getImageData(imgX, imgY, 1, 1).data;
                 return pixelData[3] < 10;
                 const alpha = pixelData[3]; // Canal alpha
 
                return alpha < 10; // Considera transparente se alpha < 10
             } catch (e) {
             } catch (e) {
                 return false;
                 return false; // Em caso de erro, permite o hover
             }
             }
         }
         }


         // ---------- Abrir YouTube (sem duplicar) ----------
         // ---------- Clique YouTube (sem duplicar) ----------
         podium.addEventListener('click', (ev) => {
         podium.addEventListener('click', (ev) => {
             const card = ev.target?.closest('.podium-slot[data-youtube]');
             const slot = ev.target?.closest('.podium-slot[data-youtube]');
             if (!card) return;
             if (!slot) return;
             const url = (card.dataset.youtube || '').trim();
             const url = (slot.dataset.youtube || '').trim();
             if (!url) return;
             if (!url) return;
             if (card.dataset._opening === '1') return;
             if (slot.dataset._opening === '1') return;
             card.dataset._opening = '1';
             slot.dataset._opening = '1';
             ev.preventDefault();
             ev.preventDefault();
             ev.stopPropagation();
             ev.stopPropagation();
Linha 161: Linha 173:
             try { window.open(url, '_blank', 'noopener,noreferrer'); }
             try { window.open(url, '_blank', 'noopener,noreferrer'); }
             catch (e) { location.href = url; }
             catch (e) { location.href = url; }
             setTimeout(() => { delete card.dataset._opening; }, 500);
             setTimeout(() => { delete slot.dataset._opening; }, 500);
         }, { capture: true });
         }, { capture: true });


         podium.addEventListener('keydown', (ev) => {
         podium.addEventListener('keydown', (ev) => {
             if (ev.key !== 'Enter' && ev.key !== ' ') return;
             if (ev.key !== 'Enter' && ev.key !== ' ') return;
             const card = ev.target?.closest('.podium-slot[data-youtube]');
             const slot = ev.target?.closest('.podium-slot[data-youtube]');
             if (!card) return;
             if (!slot) return;
             const url = (card.dataset.youtube || '').trim();
             const url = (slot.dataset.youtube || '').trim();
             if (!url) return;
             if (!url) return;
             if (card.dataset._opening === '1') return;
             if (slot.dataset._opening === '1') return;
             card.dataset._opening = '1';
             slot.dataset._opening = '1';
             ev.preventDefault();
             ev.preventDefault();
             ev.stopPropagation();
             ev.stopPropagation();
Linha 177: Linha 189:
             try { window.open(url, '_blank', 'noopener,noreferrer'); }
             try { window.open(url, '_blank', 'noopener,noreferrer'); }
             catch (e) { location.href = url; }
             catch (e) { location.href = url; }
             setTimeout(() => { delete card.dataset._opening; }, 500);
             setTimeout(() => { delete slot.dataset._opening; }, 500);
         }, { capture: true });
         }, { capture: true });


         // ---------- Hitbox pixel-perfect de hover ----------
         // ---------- Hitbox pixel-perfect por sprite ----------
         const slots = podium.querySelectorAll('.podium-slot');
         const slots = Array.from(podium.querySelectorAll('.podium-slot'));
         slots.forEach(slot => {
         slots.forEach(slot => {
             const spriteImg = slot.querySelector('.podium-sprite img');
             const spriteImg = slot.querySelector('.podium-sprite img');


            // Hitbox apenas na imagem do sprite, verificando transparência
             if (spriteImg) {
             if (spriteImg) {
                 spriteImg.addEventListener('pointerenter', (ev) => {
                 spriteImg.addEventListener('pointermove', (ev) => {
                     if (!slot.hasAttribute('data-skin-tooltip')) return;
                     if (!slot.hasAttribute('data-skin-tooltip')) return;
                     if (!isPixelTransparent(spriteImg, ev.clientX, ev.clientY)) {
 
                    // Verifica se o pixel é transparente
                     if (isPixelTransparent(spriteImg, ev.clientX, ev.clientY)) {
                        // Se for transparente e estiver hovered, remove o hover
                        if (hoveredSlot === slot) {
                            setHovered(null);
                        }
                        return;
                    }
 
                    // Se não for transparente, ativa o hover
                    if (hoveredSlot !== slot) {
                         setHovered(slot);
                         setHovered(slot);
                     }
                     }
                 }, { passive: true });
                 }, { passive: true });


                 spriteImg.addEventListener('pointermove', (ev) => {
                 spriteImg.addEventListener('pointerenter', (ev) => {
                     if (!slot.hasAttribute('data-skin-tooltip')) return;
                     if (!slot.hasAttribute('data-skin-tooltip')) return;


                     if (isPixelTransparent(spriteImg, ev.clientX, ev.clientY)) {
                    // Verifica transparência no enter também
                         if (hoveredSlot === slot) setHovered(null);
                     if (!isPixelTransparent(spriteImg, ev.clientX, ev.clientY)) {
                    } else {
                         setHovered(slot);
                        if (hoveredSlot !== slot) setHovered(slot);
                     }
                     }
                 }, { passive: true });
                 }, { passive: true });


                 spriteImg.addEventListener('pointerleave', (ev) => {
                 spriteImg.addEventListener('pointerleave', (ev) => {
                     const toCard = ev.relatedTarget?.closest?.('.podium-slot');
                     const toCard = ev.relatedTarget && ev.relatedTarget.closest && ev.relatedTarget.closest('.podium-slot');
                     if (toCard && podium.contains(toCard)) {
                     if (toCard && podium.contains(toCard)) {
                        // Se está indo para outro slot, verifica transparência
                         const otherImg = toCard.querySelector('.podium-sprite img');
                         const otherImg = toCard.querySelector('.podium-sprite img');
                         if (otherImg && ev.relatedTarget && !isPixelTransparent(otherImg, ev.clientX, ev.clientY)) {
                         if (otherImg && ev.relatedTarget && !isPixelTransparent(otherImg, ev.clientX, ev.clientY)) {
                             return;
                             return; // Não remove hover se está indo para pixel não-transparente
                         }
                         }
                     }
                     }
Linha 217: Linha 241:


         podium.addEventListener('pointerleave', () => { setHovered(null); }, { passive: true });
         podium.addEventListener('pointerleave', () => { setHovered(null); }, { passive: true });
        // Só atualiza em scroll/resize, não em mousemove
         window.addEventListener('scroll', () => { if (hoveredSlot) place(hoveredSlot); }, true);
         window.addEventListener('scroll', () => { if (hoveredSlot) place(hoveredSlot); }, true);
         window.addEventListener('resize', () => { if (hoveredSlot) place(hoveredSlot); });
         window.addEventListener('resize', () => { if (hoveredSlot) place(hoveredSlot); });
        // Função para ajustar sombra ao tamanho exato da imagem do tile
        function updateShadows() {
            const platformTops = document.querySelectorAll('.podium-platform-top');
            platformTops.forEach(top => {
                const img = top.querySelector('img');
                if (img) {
                    const updateShadow = () => {
                        const imgWidth = img.offsetWidth || img.naturalWidth;
                        const imgHeight = img.offsetHeight || img.naturalHeight;
                        if (imgWidth > 0 && imgHeight > 0) {
                            top.style.setProperty('--img-width', imgWidth + 'px');
                            top.style.setProperty('--img-height', imgHeight + 'px');
                        }
                    };
                    if (img.complete) {
                        updateShadow();
                    } else {
                        img.addEventListener('load', updateShadow);
                    }
                }
            });
        }
        // Ajusta sombras quando imagens carregam
        if (document.readyState === 'loading') {
            window.addEventListener('load', () => {
                updateShadows();
                setTimeout(updateShadows, 100);
            });
        } else {
            updateShadows();
            setTimeout(updateShadows, 100);
        }
     })();
     })();
</script>
</script>
<style>
<style>
    /* Container escuro para área de skins */
    .card-skins {
        background: #0f0f12;
        padding: 20px;
        border-radius: 8px;
        box-sizing: border-box;
        width: 100%;
    }
    /* Podium de skins - layout isométrico */
     .skins-podium {
     .skins-podium {
         display: flex;
         display: flex;
         align-items: flex-end;
         align-items: flex-end;
        /* Alinha todos os slots pelo bottom */
         justify-content: center;
         justify-content: center;
         gap: 30px;
         gap: 30px;
         padding: 40px 20px;
         padding: 20px 0;
    }
         position: relative;
 
    .content-card .card-skins {
        overflow: visible;
        min-height: 0;
        height: auto;
    }
 
    #skins .content-card {
        overflow: visible;
        min-height: 0;
         height: auto;
     }
     }


Linha 248: Linha 309:
         align-items: center;
         align-items: center;
         justify-content: flex-end;
         justify-content: flex-end;
        /* Alinha pelo bottom */
         cursor: pointer;
         cursor: pointer;
         transition: filter 0.14s ease, box-shadow 0.14s ease;
         transition: filter 0.14s ease, box-shadow 0.14s ease;
Linha 253: Linha 315:


     /* Z-index INVERTIDO: sprites da direita passam por trás das da esquerda */
     /* Z-index INVERTIDO: sprites da direita passam por trás das da esquerda */
    /* Aplicado dinamicamente via JS, mas CSS base para primeiros slots */
     .podium-slot:nth-child(1) {
     .podium-slot:nth-child(1) {
         z-index: 4;
         z-index: 4;
        /* Esquerda: mais acima */
     }
     }


Linha 267: Linha 331:
     .podium-slot:nth-child(4) {
     .podium-slot:nth-child(4) {
         z-index: 1;
         z-index: 1;
        /* Direita: mais atrás */
     }
     }


    .podium-slot:nth-child(5) {
     /* Z-index INVERTIDO para sprite-containers */
        z-index: 1;
    }
 
     /* Z-index INVERTIDO: sprites da direita passam por trás das da esquerda */
     .podium-slot:nth-child(1) .podium-sprite-container {
     .podium-slot:nth-child(1) .podium-sprite-container {
         z-index: 4000;
         z-index: 4000;
        /* Esquerda: mais acima */
     }
     }


Linha 288: Linha 350:
     .podium-slot:nth-child(4) .podium-sprite-container {
     .podium-slot:nth-child(4) .podium-sprite-container {
         z-index: 1000;
         z-index: 1000;
    }
         /* Direita: mais atrás */
 
    .podium-slot:nth-child(5) .podium-sprite-container {
         z-index: 1000;
     }
     }


Linha 299: Linha 358:
     }
     }


    /* Apenas a imagem do sprite tem hitbox - container não interfere */
     .podium-sprite img {
     .podium-sprite img {
         pointer-events: auto;
         pointer-events: auto;
     }
     }


    /* Container e sprite não têm hitbox para não interferir */
     .podium-sprite-container,
     .podium-sprite-container,
     .podium-sprite {
     .podium-sprite {
Linha 308: Linha 369:
     }
     }


    /* Piso nunca tem hitbox */
     .podium-platform,
     .podium-platform,
     .podium-platform-top,
     .podium-platform-top,
Linha 314: Linha 376:
     }
     }


    /* Container do sprite - tamanho natural, não afetado pelo tile */
     .podium-sprite-container {
     .podium-sprite-container {
         position: relative;
         position: relative;
         z-index: 10;
         z-index: 10;
         display: flex;
         display: flex;
        flex-direction: column;
         align-items: flex-end;
         align-items: flex-end;
        /* Alinha pelo bottom (pés) - horizontalmente */
         justify-content: center;
         justify-content: center;
        /* Centraliza horizontalmente */
         filter: drop-shadow(0 6px 20px rgba(0, 0, 0, 0.5));
         filter: drop-shadow(0 6px 20px rgba(0, 0, 0, 0.5));
         transform: none !important;
         transform: none !important;
         flex-shrink: 0;
         flex-shrink: 0;
         width: 100%;
         width: 100%;
        /* Ocupa toda largura do slot */
     }
     }


Linha 345: Linha 412:
         position: relative;
         position: relative;
         z-index: 100 !important;
         z-index: 100 !important;
        /* Imagem sempre acima do piso */
         transform: none !important;
         transform: none !important;
        will-change: auto;
         flex-shrink: 0;
         flex-shrink: 0;
     }
     }


    /* Tile isométrico nos pés da sprite - atrás da sprite */
     .podium-platform {
     .podium-platform {
         position: absolute;
         position: absolute;
Linha 356: Linha 426:
         right: -25px;
         right: -25px;
         z-index: 0 !important;
         z-index: 0 !important;
        /* Piso sempre atrás da sprite do mesmo slot */
     }
     }


    /* Todos os pisos com z-index baixo - sempre atrás das sprites */
    .podium-slot .podium-platform {
        z-index: 0 !important;
    }
    /* Tile - usando imagem no tamanho natural */
     .podium-platform-top {
     .podium-platform-top {
         position: absolute;
         position: absolute;
Linha 364: Linha 441:
         transform-origin: center bottom;
         transform-origin: center bottom;
         transform: rotateX(15deg);
         transform: rotateX(15deg);
        /* Inclinação fixa em 15 graus */
         z-index: -1 !important;
         z-index: -1 !important;
        /* Garante que o topo do piso também fique atrás */
     }
     }


Linha 382: Linha 461:
         outline: none;
         outline: none;
         box-shadow: none;
         box-shadow: none;
        filter: drop-shadow(3px 3px 0 rgba(0, 0, 0, 0.5));
     }
     }


    /* Sombra quadrada sólida - perfeitamente alinhada com a imagem */
    .podium-platform-top::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        width: var(--img-width, 100%);
        height: var(--img-height, 100%);
        background: rgba(0, 0, 0, 0.5);
        z-index: 0;
        transform: translate(3px, 3px);
        pointer-events: none;
    }
    /* Sistema de hover - dim outras skins, destaque na hovered */
     .skins-podium.hovering .podium-slot.dim {
     .skins-podium.hovering .podium-slot.dim {
         filter: brightness(.55) saturate(.85);
         filter: brightness(.55) saturate(.85);
Linha 394: Linha 487:
     }
     }


    /* CRÍTICO: Remove efeito de hover da imagem do piso (sem "borda fantasma") */
    .skins-podium.hovering .podium-slot.dim .podium-platform-top,
    .skins-podium.hovering .podium-slot.hovered .podium-platform-top,
    .skins-podium.hovering .podium-slot.dim .podium-platform-top img,
    .skins-podium.hovering .podium-slot.hovered .podium-platform-top img {
        filter: none !important;
        box-shadow: none !important;
    }
    /* Borda no tile quando hovered - apenas no container, não na imagem */
     .skins-podium.hovering .podium-slot.hovered .podium-platform-top {
     .skins-podium.hovering .podium-slot.hovered .podium-platform-top {
         box-shadow: none;
         box-shadow:
            0 0 0 2px rgba(255, 255, 255, .12),
            0 10px 28px rgba(0, 0, 0, .45);
     }
     }


    /* Remove sombra da imagem do tile no hover */
     .skins-podium.hovering .podium-slot.hovered .podium-platform-top img {
     .skins-podium.hovering .podium-slot.hovered .podium-platform-top img {
         box-shadow: none !important;
         box-shadow: none !important;
         filter: drop-shadow(3px 3px 0 rgba(0, 0, 0, 0.5)) !important;
         filter: none !important;
     }
     }


    /* Tooltip */
     .skin-tooltip {
     .skin-tooltip {
         position: fixed;
         position: fixed;
Linha 438: Linha 545:
     }
     }


    /* Responsivo */
     @media (max-width: 1024px) {
     @media (max-width: 1024px) {
         .skins-podium {
         .skins-podium {
Linha 448: Linha 556:
             flex-wrap: wrap;
             flex-wrap: wrap;
             gap: 20px;
             gap: 20px;
        }
        #skins .content-card {
            overflow: visible !important;
            min-height: 0 !important;
            height: auto !important;
        }
        #skins .content-card .card-skins {
            overflow: visible !important;
            min-height: 0 !important;
            height: auto !important;
         }
         }
     }
     }
</style>
</style>

Edição das 20h38min de 24 de dezembro de 2025

<script>

   (function initSkinsPodiumUI() {
       const podium = document.querySelector('.skins-podium');
       if (!podium || podium.dataset.wired === '1') return;
       podium.dataset.wired = '1';
       // ---------- Tooltip único ----------
       function ensureTip() {
           let tip = document.querySelector('.skin-tooltip');
           if (!tip) {
               tip = document.createElement('div');
               tip.className = 'skin-tooltip';
               tip.setAttribute('role', 'tooltip');
               tip.setAttribute('aria-hidden', 'true');
               tip.style.position = 'fixed';
               tip.style.transform = 'translate(-9999px,-9999px)';
               tip.style.opacity = '0';
               tip.style.transition = 'opacity .15s ease';
               document.body.appendChild(tip);
           }
           return tip;
       }
       const tip = ensureTip();
       let hoveredSlot = null;
       function place(card) {
           if (!card || tip.getAttribute('aria-hidden') === 'true') return;
           // Calcula nova posição - tooltip centralizada em relação à imagem do piso
           tip.style.transform = 'translate(-9999px,-9999px)';
           const tr = tip.getBoundingClientRect();
           // Pega a posição da imagem do tile (piso) para centralizar
           const platformImg = card.querySelector('.podium-platform-top img');
           let platformRect = card.getBoundingClientRect(); // Fallback para o card se não encontrar
           if (platformImg) {
               platformRect = platformImg.getBoundingClientRect();
           } else {
               // Se não encontrar a img, tenta o container do tile
               const platform = card.querySelector('.podium-platform');
               if (platform) {
                   platformRect = platform.getBoundingClientRect();
               }
           }
           // Centraliza horizontalmente baseado na imagem do tile
           let leftPos = Math.round(platformRect.left + (platformRect.width - tr.width) / 2);
           leftPos = Math.max(8, Math.min(leftPos, window.innerWidth - tr.width - 8));
           // Posiciona logo abaixo da imagem do tile, com pequeno espaçamento
           let top = Math.round(platformRect.bottom + 15);
           // Se não couber embaixo, coloca em cima
           if (top + tr.height > window.innerHeight - 8) {
               top = Math.round(platformRect.top - tr.height - 15);
               if (top < 8) top = 8; // Fallback se não couber em cima também
           }
           tip.style.transform = `translate(${leftPos}px, ${top}px)`;
       }
       function show(card) {
           const tooltipText = card.getAttribute('data-skin-tooltip') || ;
           tip.innerHTML = tooltipText;
           tip.setAttribute('aria-hidden', 'false');
           place(card);
           tip.style.opacity = '1';
       }
       function hide() {
           tip.setAttribute('aria-hidden', 'true');
           tip.style.opacity = '0';
           tip.style.transform = 'translate(-9999px,-9999px)';
       }
       // Função para restaurar z-index original baseado na posição do slot
       function restoreOriginalZIndex(slot) {
           if (!slot) return;
           // Determina a posição do slot (1, 2, 3, 4, etc)
           const allSlots = Array.from(podium.querySelectorAll('.podium-slot'));
           const slotIndex = allSlots.indexOf(slot);
           // Z-index invertido: esquerda (0) = maior, direita (último) = menor
           const originalZIndex = allSlots.length - slotIndex;
           const originalSpriteZIndex = (allSlots.length - slotIndex) * 1000;
           slot.style.zIndex = originalZIndex.toString();
           const spriteContainer = slot.querySelector('.podium-sprite-container');
           if (spriteContainer) spriteContainer.style.zIndex = originalSpriteZIndex.toString();
       }
       function setHovered(card) {
           if (hoveredSlot === card) {
               // Se já está hovered, não reposiciona - mantém posição fixa
               return;
           }
           // Remove hover anterior e restaura z-index original
           if (hoveredSlot) {
               hoveredSlot.classList.remove('hovered');
               restoreOriginalZIndex(hoveredSlot);
           }
           podium.classList.remove('hovering');
           podium.querySelectorAll('.podium-slot.dim').forEach(n => n.classList.remove('dim'));
           if (!card) {
               hoveredSlot = null;
               hide();
               return;
           }
           hoveredSlot = card;
           hoveredSlot.classList.add('hovered');
           podium.classList.add('hovering');
           podium.querySelectorAll('.podium-slot').forEach(n => { if (n !== hoveredSlot) n.classList.add('dim'); });
           // Qualquer skin com hover fica acima de tudo
           card.style.zIndex = '9999';
           const spriteContainer = card.querySelector('.podium-sprite-container');
           if (spriteContainer) spriteContainer.style.zIndex = '9999';
           show(card);
       }
       // Função para verificar se o pixel na posição do mouse é transparente
       function isPixelTransparent(img, x, y) {
           if (!img.complete || img.naturalWidth === 0) return true;
           try {
               const canvas = document.createElement('canvas');
               canvas.width = img.naturalWidth;
               canvas.height = img.naturalHeight;
               const ctx = canvas.getContext('2d');
               ctx.drawImage(img, 0, 0);
               // Calcula a posição relativa na imagem
               const rect = img.getBoundingClientRect();
               const scaleX = img.naturalWidth / rect.width;
               const scaleY = img.naturalHeight / rect.height;
               const imgX = Math.floor((x - rect.left) * scaleX);
               const imgY = Math.floor((y - rect.top) * scaleY);
               // Verifica se está dentro dos limites
               if (imgX < 0 || imgX >= img.naturalWidth || imgY < 0 || imgY >= img.naturalHeight) {
                   return true;
               }
               // Obtém o pixel
               const pixelData = ctx.getImageData(imgX, imgY, 1, 1).data;
               const alpha = pixelData[3]; // Canal alpha
               return alpha < 10; // Considera transparente se alpha < 10
           } catch (e) {
               return false; // Em caso de erro, permite o hover
           }
       }
       // ---------- Clique YouTube (sem duplicar) ----------
       podium.addEventListener('click', (ev) => {
           const slot = ev.target?.closest('.podium-slot[data-youtube]');
           if (!slot) return;
           const url = (slot.dataset.youtube || ).trim();
           if (!url) return;
           if (slot.dataset._opening === '1') return;
           slot.dataset._opening = '1';
           ev.preventDefault();
           ev.stopPropagation();
           ev.stopImmediatePropagation();
           try { window.open(url, '_blank', 'noopener,noreferrer'); }
           catch (e) { location.href = url; }
           setTimeout(() => { delete slot.dataset._opening; }, 500);
       }, { capture: true });
       podium.addEventListener('keydown', (ev) => {
           if (ev.key !== 'Enter' && ev.key !== ' ') return;
           const slot = ev.target?.closest('.podium-slot[data-youtube]');
           if (!slot) return;
           const url = (slot.dataset.youtube || ).trim();
           if (!url) return;
           if (slot.dataset._opening === '1') return;
           slot.dataset._opening = '1';
           ev.preventDefault();
           ev.stopPropagation();
           ev.stopImmediatePropagation();
           try { window.open(url, '_blank', 'noopener,noreferrer'); }
           catch (e) { location.href = url; }
           setTimeout(() => { delete slot.dataset._opening; }, 500);
       }, { capture: true });
       // ---------- Hitbox pixel-perfect por sprite ----------
       const slots = Array.from(podium.querySelectorAll('.podium-slot'));
       slots.forEach(slot => {
           const spriteImg = slot.querySelector('.podium-sprite img');
           // Hitbox apenas na imagem do sprite, verificando transparência
           if (spriteImg) {
               spriteImg.addEventListener('pointermove', (ev) => {
                   if (!slot.hasAttribute('data-skin-tooltip')) return;
                   // Verifica se o pixel é transparente
                   if (isPixelTransparent(spriteImg, ev.clientX, ev.clientY)) {
                       // Se for transparente e estiver hovered, remove o hover
                       if (hoveredSlot === slot) {
                           setHovered(null);
                       }
                       return;
                   }
                   // Se não for transparente, ativa o hover
                   if (hoveredSlot !== slot) {
                       setHovered(slot);
                   }
               }, { passive: true });
               spriteImg.addEventListener('pointerenter', (ev) => {
                   if (!slot.hasAttribute('data-skin-tooltip')) return;
                   // Verifica transparência no enter também
                   if (!isPixelTransparent(spriteImg, ev.clientX, ev.clientY)) {
                       setHovered(slot);
                   }
               }, { passive: true });
               spriteImg.addEventListener('pointerleave', (ev) => {
                   const toCard = ev.relatedTarget && ev.relatedTarget.closest && ev.relatedTarget.closest('.podium-slot');
                   if (toCard && podium.contains(toCard)) {
                       // Se está indo para outro slot, verifica transparência
                       const otherImg = toCard.querySelector('.podium-sprite img');
                       if (otherImg && ev.relatedTarget && !isPixelTransparent(otherImg, ev.clientX, ev.clientY)) {
                           return; // Não remove hover se está indo para pixel não-transparente
                       }
                   }
                   setHovered(null);
               }, { passive: true });
           }
       });
       podium.addEventListener('pointerleave', () => { setHovered(null); }, { passive: true });
       // Só atualiza em scroll/resize, não em mousemove
       window.addEventListener('scroll', () => { if (hoveredSlot) place(hoveredSlot); }, true);
       window.addEventListener('resize', () => { if (hoveredSlot) place(hoveredSlot); });
       // Função para ajustar sombra ao tamanho exato da imagem do tile
       function updateShadows() {
           const platformTops = document.querySelectorAll('.podium-platform-top');
           platformTops.forEach(top => {
               const img = top.querySelector('img');
               if (img) {
                   const updateShadow = () => {
                       const imgWidth = img.offsetWidth || img.naturalWidth;
                       const imgHeight = img.offsetHeight || img.naturalHeight;
                       if (imgWidth > 0 && imgHeight > 0) {
                           top.style.setProperty('--img-width', imgWidth + 'px');
                           top.style.setProperty('--img-height', imgHeight + 'px');
                       }
                   };
                   if (img.complete) {
                       updateShadow();
                   } else {
                       img.addEventListener('load', updateShadow);
                   }
               }
           });
       }
       // Ajusta sombras quando imagens carregam
       if (document.readyState === 'loading') {
           window.addEventListener('load', () => {
               updateShadows();
               setTimeout(updateShadows, 100);
           });
       } else {
           updateShadows();
           setTimeout(updateShadows, 100);
       }
   })();

</script> <style>

   /* Container escuro para área de skins */
   .card-skins {
       background: #0f0f12;
       padding: 20px;
       border-radius: 8px;
       box-sizing: border-box;
       width: 100%;
   }
   /* Podium de skins - layout isométrico */
   .skins-podium {
       display: flex;
       align-items: flex-end;
       /* Alinha todos os slots pelo bottom */
       justify-content: center;
       gap: 30px;
       padding: 20px 0;
       position: relative;
   }
   .podium-slot {
       position: relative;
       display: flex;
       flex-direction: column;
       align-items: center;
       justify-content: flex-end;
       /* Alinha pelo bottom */
       cursor: pointer;
       transition: filter 0.14s ease, box-shadow 0.14s ease;
   }
   /* Z-index INVERTIDO: sprites da direita passam por trás das da esquerda */
   /* Aplicado dinamicamente via JS, mas CSS base para primeiros slots */
   .podium-slot:nth-child(1) {
       z-index: 4;
       /* Esquerda: mais acima */
   }
   .podium-slot:nth-child(2) {
       z-index: 3;
   }
   .podium-slot:nth-child(3) {
       z-index: 2;
   }
   .podium-slot:nth-child(4) {
       z-index: 1;
       /* Direita: mais atrás */
   }
   /* Z-index INVERTIDO para sprite-containers */
   .podium-slot:nth-child(1) .podium-sprite-container {
       z-index: 4000;
       /* Esquerda: mais acima */
   }
   .podium-slot:nth-child(2) .podium-sprite-container {
       z-index: 3000;
   }
   .podium-slot:nth-child(3) .podium-sprite-container {
       z-index: 2000;
   }
   .podium-slot:nth-child(4) .podium-sprite-container {
       z-index: 1000;
       /* Direita: mais atrás */
   }
   /* Hitbox baseada na imagem, não no container */
   .podium-slot>* {
       pointer-events: none;
   }
   /* Apenas a imagem do sprite tem hitbox - container não interfere */
   .podium-sprite img {
       pointer-events: auto;
   }
   /* Container e sprite não têm hitbox para não interferir */
   .podium-sprite-container,
   .podium-sprite {
       pointer-events: none;
   }
   /* Piso nunca tem hitbox */
   .podium-platform,
   .podium-platform-top,
   .podium-platform-top img {
       pointer-events: none !important;
   }
   /* Container do sprite - tamanho natural, não afetado pelo tile */
   .podium-sprite-container {
       position: relative;
       z-index: 10;
       display: flex;
       flex-direction: column;
       align-items: flex-end;
       /* Alinha pelo bottom (pés) - horizontalmente */
       justify-content: center;
       /* Centraliza horizontalmente */
       filter: drop-shadow(0 6px 20px rgba(0, 0, 0, 0.5));
       transform: none !important;
       flex-shrink: 0;
       width: 100%;
       /* Ocupa toda largura do slot */
   }
   .podium-sprite {
       display: block;
       position: relative;
       transform: none !important;
       flex-shrink: 0;
   }
   .podium-sprite img {
       width: auto;
       height: auto;
       max-width: none;
       max-height: none;
       image-rendering: pixelated;
       image-rendering: -moz-crisp-edges;
       image-rendering: -webkit-optimize-contrast;
       image-rendering: crisp-edges;
       display: block;
       position: relative;
       z-index: 100 !important;
       /* Imagem sempre acima do piso */
       transform: none !important;
       will-change: auto;
       flex-shrink: 0;
   }
   /* Tile isométrico nos pés da sprite - atrás da sprite */
   .podium-platform {
       position: absolute;
       width: auto;
       height: auto;
       bottom: -15px;
       right: -25px;
       z-index: 0 !important;
       /* Piso sempre atrás da sprite do mesmo slot */
   }
   /* Todos os pisos com z-index baixo - sempre atrás das sprites */
   .podium-slot .podium-platform {
       z-index: 0 !important;
   }
   /* Tile - usando imagem no tamanho natural */
   .podium-platform-top {
       position: absolute;
       width: auto;
       height: auto;
       transform-origin: center bottom;
       transform: rotateX(15deg);
       /* Inclinação fixa em 15 graus */
       z-index: -1 !important;
       /* Garante que o topo do piso também fique atrás */
   }
   .podium-platform-top img {
       display: block;
       width: auto;
       height: auto;
       max-width: none;
       max-height: none;
       image-rendering: pixelated;
       image-rendering: -moz-crisp-edges;
       image-rendering: -webkit-optimize-contrast;
       image-rendering: crisp-edges;
       position: relative;
       z-index: 1;
       border: none !important;
       outline: none;
       box-shadow: none;
   }
   /* Sombra quadrada sólida - perfeitamente alinhada com a imagem */
   .podium-platform-top::before {
       content: ;
       position: absolute;
       top: 0;
       left: 0;
       width: var(--img-width, 100%);
       height: var(--img-height, 100%);
       background: rgba(0, 0, 0, 0.5);
       z-index: 0;
       transform: translate(3px, 3px);
       pointer-events: none;
   }
   /* Sistema de hover - dim outras skins, destaque na hovered */
   .skins-podium.hovering .podium-slot.dim {
       filter: brightness(.55) saturate(.85);
       transition: filter .14s ease;
   }
   .skins-podium.hovering .podium-slot.hovered {
       filter: none;
   }
   /* CRÍTICO: Remove efeito de hover da imagem do piso (sem "borda fantasma") */
   .skins-podium.hovering .podium-slot.dim .podium-platform-top,
   .skins-podium.hovering .podium-slot.hovered .podium-platform-top,
   .skins-podium.hovering .podium-slot.dim .podium-platform-top img,
   .skins-podium.hovering .podium-slot.hovered .podium-platform-top img {
       filter: none !important;
       box-shadow: none !important;
   }
   /* Borda no tile quando hovered - apenas no container, não na imagem */
   .skins-podium.hovering .podium-slot.hovered .podium-platform-top {
       box-shadow:
           0 0 0 2px rgba(255, 255, 255, .12),
           0 10px 28px rgba(0, 0, 0, .45);
   }
   /* Remove sombra da imagem do tile no hover */
   .skins-podium.hovering .podium-slot.hovered .podium-platform-top img {
       box-shadow: none !important;
       filter: none !important;
   }
   /* Tooltip */
   .skin-tooltip {
       position: fixed;
       z-index: 9999;
       left: 0;
       top: 0;
       pointer-events: none;
       padding: 10px 12px;
       border-radius: 8px;
       background: rgba(28, 28, 34, .98);
       color: #eaeaea;
       font-size: 13px;
       line-height: 1.4;
       box-shadow: 0 8px 24px rgba(0, 0, 0, .5), inset 0 0 0 1px rgba(255, 255, 255, .06);
       transform: translate(-9999px, -9999px);
       opacity: 0;
       transition: opacity .15s ease;
       text-align: center;
       white-space: normal;
       max-width: 280px;
       min-width: 200px;
   }
   .skin-tooltip b {
       color: #fff;
   }
   .podium-slot.is-clickable {
       cursor: pointer;
   }
   .podium-slot.is-clickable:focus {
       outline: 2px solid #156bc7;
       outline-offset: 2px;
   }
   /* Responsivo */
   @media (max-width: 1024px) {
       .skins-podium {
           gap: 24px;
       }
   }
   @media (max-width: 768px) {
       .skins-podium {
           flex-wrap: wrap;
           gap: 20px;
       }
   }

</style>