Mudanças entre as edições de "Widget:Conquistas"

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
 
(Uma revisão intermediária pelo mesmo usuário não está sendo mostrada)
Linha 6: Linha 6:
           {{#widget:Item}}
           {{#widget:Item}}
           {{#widget:Conquistas}}
           {{#widget:Conquistas}}
           {{#invoke:Conquistas|renderAll}}
           {{#invoke:Conquistas|renderTab|tab=geral}}


       Widget:Item é quem traz a CSS dos ícones (.reward-wrapper,
       Widget:Item é quem traz a CSS dos ícones (.reward-wrapper,
Linha 73: Linha 73:


         <!--
         <!--
           Painéis vazios. O Módulo:Conquistas (#invoke renderAll) emite os
           Painéis vazios. O Módulo:Conquistas (#invoke renderTab) emite os
           cards FORA do widget (Smarty não reparsa wikitext, então não dá pra
           cards FORA do widget (Smarty não reparsa wikitext, então não dá pra
           passar como parâmetro). O JS abaixo move cada card pro painel certo
           passar como parâmetro). O JS abaixo move cada card pro painel certo
Linha 80: Linha 80:
           Uso na página da wiki:
           Uso na página da wiki:
               {{#widget:Conquistas}}
               {{#widget:Conquistas}}
               {{#invoke:Conquistas|renderAll}}
               {{#invoke:Conquistas|renderTab|tab=geral}}
         -->
         -->
         <div class="gla-conquistas-panel is-active" data-tab-content="geral">
         <div class="gla-conquistas-panel is-active" data-tab-content="geral">
Linha 351: Linha 351:
             flex-direction: column;
             flex-direction: column;
             gap: 10px;
             gap: 10px;
            position: relative;
            min-height: 0;
        }
        .gla-list.gla-tab-loading {
            min-height: 88px;
            opacity: 0.55;
            pointer-events: none;
        }
        .gla-list.gla-tab-loading::after {
            content: "Carregando...";
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-size: 13px;
            font-weight: 600;
            color: var(--gla-ink-2);
            letter-spacing: 0.04em;
        }
        .gla-list.gla-tab-error::after {
            content: "Não foi possível carregar esta aba. Recarregue a página.";
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: min(320px, 90%);
            text-align: center;
            font-size: 13px;
            color: #b45309;
            line-height: 1.4;
         }
         }


Linha 363: Linha 396:
             box-shadow: var(--gla-shadow);
             box-shadow: var(--gla-shadow);
             transition: border-color 0.15s, box-shadow 0.15s;
             transition: border-color 0.15s, box-shadow 0.15s;
            content-visibility: auto;
            contain-intrinsic-size: auto 88px;
         }
         }


Linha 1 211: Linha 1 246:
             });
             });


             // Move cards do invoke source para os painéis corretos
             function normalizeTabId(tab) {
             document.querySelectorAll(".gla-item[data-tab]").forEach(function (card) {
                return (tab || "").toLowerCase();
                 if (root.contains(card)) return;
            }
                 var tag = (card.getAttribute("data-tab") || "").toLowerCase();
 
                var list = validTabs[tag] || validTabs["geral"];
             function getCardTab(card) {
                if (list) list.appendChild(card);
                var tag = normalizeTabId(card.getAttribute("data-tab"));
             });
                return validTabs[tag] ? tag : "geral";
            }
 
            function mountCardToPanel(card) {
                 if (!card) return;
                 var tag = getCardTab(card);
                var list = validTabs[tag];
                if (list) list.appendChild(card);
            }
 
            function redistributeCardsToPanels() {
                root.querySelectorAll(".gla-item[data-tab]").forEach(function (card) {
                    var list = validTabs[getCardTab(card)];
                    if (list && card.parentNode !== list) list.appendChild(card);
                });
            }
 
            // Move cards do invoke source para os painéis corretos (sempre por data-tab)
            document.querySelectorAll(".gla-item[data-tab]").forEach(function (card) {
                if (root.contains(card)) return;
                mountCardToPanel(card);
             });


             document.querySelectorAll(".gla-conquistas-source").forEach(function (src) {
             document.querySelectorAll(".gla-conquistas-source").forEach(function (src) {
Linha 1 228: Linha 1 284:


             function buildTabCaches() {
             function buildTabCaches() {
                redistributeCardsToPanels();
                 cardsByTab = {};
                 cardsByTab = {};
                 hiddenCountByTab = {};
                 hiddenCountByTab = {};
Linha 1 242: Linha 1 299:
                         var el = list.children[i];
                         var el = list.children[i];
                         if (!(el.classList && el.classList.contains("gla-item"))) continue;
                         if (!(el.classList && el.classList.contains("gla-item"))) continue;
                        if (getCardTab(el) !== tabName) continue;
                         cards.push(el);
                         cards.push(el);
                         if (el.getAttribute("data-hidden") === "true") hiddenCount++;
                         if (el.getAttribute("data-hidden") === "true") hiddenCount++;
Linha 1 251: Linha 1 309:
             buildTabCaches();
             buildTabCaches();


            // Estado global
             var REVEAL_STORAGE_KEY = "glaConquistasRevealed";
             var currentSearch = "";
            var currentFilter = "all";
            var currentTab = "geral";
            var searchTimeout = null;


             var filterDefault = root.querySelector("#gla-filter-default");
             function loadRevealed() {
            var filterColiseu = root.querySelector("#gla-filter-coliseu");
                try {
            var searchInput = root.querySelector(".gla-conquistas-search");
                    var raw = window.localStorage.getItem(REVEAL_STORAGE_KEY);
 
                    if (!raw) return {};
            function countHiddenInTab(tabName) {
                    var arr = JSON.parse(raw);
                return hiddenCountByTab[tabName] || 0;
                    if (!Array.isArray(arr)) return {};
                    var set = {};
                    for (var i = 0; i < arr.length; i++) {
                        if (arr[i] != null) set[String(arr[i])] = true;
                    }
                    return set;
                } catch (e) {
                    return {};
                }
             }
             }


             // Mostra a barra de filtros só quando faz sentido: na aba Coliseu mostra
             var revealedSet = loadRevealed();
            // o subset (One Man Army / Corrida). Nas outras, só mostra o filtro
            // normal/hidden se a aba tiver pelo menos uma conquista oculta —
            // se não tiver, esconde pra não poluir.
            function syncFilterBarForTab(tabName) {
                var hiddenCount = countHiddenInTab(tabName);


                if (filterColiseu) {
            // Abas já com cards no HTML (ex.: renderAll legado) ficam marcadas.
                    filterColiseu.style.display = tabName === "coliseu" ? "" : "none";
            // Com renderTab|tab=geral só "geral" vem na primeira carga.
                 }
            var loadedTabs = {};
                 if (filterDefault) {
            var loadingTabs = {};
                     if (tabName === "coliseu") {
            Object.keys(validTabs).forEach(function (tabName) {
                         filterDefault.style.display = "none";
                var list = validTabs[tabName];
                    } else {
                 if (!list) return;
                         filterDefault.style.display = hiddenCount > 0 ? "" : "none";
                 for (var i = 0; i < list.children.length; i++) {
                    var el = list.children[i];
                     if (el.classList && el.classList.contains("gla-item") && getCardTab(el) === tabName) {
                         loadedTabs[tabName] = true;
                         break;
                     }
                     }
                 }
                 }
            });


                 if (tabName !== "coliseu" && hiddenCount === 0) {
            function getApiUrl() {
                     if (currentFilter === "hidden" || currentFilter === "normal") {
                 if (window.mw && mw.util && mw.util.wikiScript) {
                        currentFilter = "all";
                     return mw.util.wikiScript("api");
                    }
                }
                    if (filterDefault) {
                var path = window.location.pathname || "";
                        filterDefault.querySelectorAll(".gla-conquistas-filter").forEach(function (p) {
                if (path.indexOf("/index.php") !== -1) {
                            p.classList.toggle("is-active", p.getAttribute("data-filter") === "all");
                    return path.split("/index.php")[0] + "/api.php";
                        });
                }
                    }
                return "/api.php";
            }
 
            function fetchParse(wikitext) {
                if (window.mw && mw.Api) {
                    return new mw.Api().post({
                        action: "parse",
                        text: wikitext,
                        contentmodel: "wikitext",
                        disablelimitreport: true
                    }).then(function (data) {
                        return data.parse.text["*"];
                    });
                 }
                 }
                var body = new URLSearchParams();
                body.set("action", "parse");
                body.set("format", "json");
                body.set("text", wikitext);
                body.set("contentmodel", "wikitext");
                body.set("disablelimitreport", "1");
                return fetch(getApiUrl(), {
                    method: "POST",
                    credentials: "same-origin",
                    headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
                    body: body.toString()
                })
                    .then(function (res) { return res.json(); })
                    .then(function (data) {
                        if (data.error) throw new Error(data.error.info || "API parse failed");
                        return data.parse.text["*"];
                    });
             }
             }


             // Normalização pra busca: lowercase + remove acentos (NFD divide
             function fetchTabHtml(tabName) {
            // base+diacrítico, regex remove os diacríticos). Assim "voce"
                return fetchParse("{{#invoke:Conquistas|renderTab|tab=" + tabName + "}}");
             // acha "você", "missao" acha "missão", etc.
            }
             function normalize(s) {
 
                 return (s || "")
             var prefetchRestPromise = null;
                    .toLowerCase()
            var prefetchRestStarted = false;
                     .normalize("NFD")
 
                     .replace(/[̀-ͯ]/g, "");
             function runRewardsDeferred(rootEl) {
                 if (!window.glaConquistasProcessRewards) return;
                var run = function () { window.glaConquistasProcessRewards(rootEl); };
                if (window.requestIdleCallback) {
                     requestIdleCallback(run, { timeout: 1000 });
                } else {
                     setTimeout(run, 0);
                }
             }
             }


            // Cacheia o "haystack" (title + desc normalizados) no próprio card
             function applyRevealedToCards(cards) {
            // pra não recalcular a cada keystroke. Recomputa só se o conteúdo
                 if (!cards || !cards.length) return;
            // do card mudar (não acontece na vida do widget — é estático).
                 for (var i = 0; i < cards.length; i++) {
             function getHaystack(card) {
                    var card = cards[i];
                var cached = card.__glaHaystack;
                    if (card.getAttribute("data-hidden") !== "true") continue;
                 if (cached) return cached;
                    var id = card.getAttribute("data-id");
                 var titleEl = card.querySelector(".gla-item-title");
                    if (id && revealedSet[id]) card.classList.add("is-revealed");
                var descEl = card.querySelector(".gla-item-desc");
                 }
                var titleText = titleEl ? titleEl.textContent : "";
                var descText = descEl ? descEl.textContent : "";
                cached = normalize(titleText + " " + descText);
                 card.__glaHaystack = cached;
                return cached;
             }
             }


             function applyVisibility() {
             function mountCardsFromHtml(html, tabName) {
                 var cards = cardsByTab[currentTab] || [];
                 var list = validTabs[tabName];
                 if (!cards.length) return;
                 if (!list || !html) return;


                 // Token-based search: divide a query em palavras e exige que
                 var tmp = document.createElement("div");
                // TODAS apareçam no haystack (title + desc), em qualquer
                 tmp.innerHTML = html;
                // ordem. "voce grand" → tokens ["voce", "grand"] → casa com
                // "Você entrou na Grand Line pela primeira vez". Acentos e
                 // case são ignorados via normalize().
                var tokens = normalize(currentSearch).split(/\s+/).filter(Boolean);


                 cards.forEach(function (card) {
                 var moved = [];
                    var matchSearch = true;
                tmp.querySelectorAll(".gla-item").forEach(function (card) {
                     if (tokens.length > 0) {
                     if (getCardTab(card) !== tabName) {
                        var hay = getHaystack(card);
                        mountCardToPanel(card);
                        for (var i = 0; i < tokens.length; i++) {
                         return;
                            if (hay.indexOf(tokens[i]) === -1) { matchSearch = false; break; }
                         }
                     }
                     }
                    list.appendChild(card);
                    moved.push(card);
                });


                    var hidden = card.getAttribute("data-hidden") === "true";
                applyRevealedToCards(moved);
                    var subtype = (card.getAttribute("data-subtype") || "").toLowerCase();
 
                    var matchFilter = true;
                loadedTabs[tabName] = true;
                    if (currentFilter === "normal") matchFilter = !hidden;
                buildTabCaches();
                    else if (currentFilter === "hidden") matchFilter = hidden;
                runRewardsDeferred(list);
                    else if (currentFilter === "onemany") matchFilter = subtype === "onemany";
            }
                    else if (currentFilter === "corrida") matchFilter = subtype === "corrida";


                    card.style.display = (matchSearch && matchFilter) ? "" : "none";
            function mountBundleFromHtml(html) {
                if (!html) return;
                var tmp = document.createElement("div");
                tmp.innerHTML = html;
                tmp.querySelectorAll(".gla-conquistas-source[data-gla-tab]").forEach(function (src) {
                    var tabName = src.getAttribute("data-gla-tab");
                    if (!tabName || !validTabs[tabName]) return;
                    var list = validTabs[tabName];
                    var moved = [];
                    src.querySelectorAll(".gla-item").forEach(function (card) {
                        if (getCardTab(card) !== tabName) {
                            mountCardToPanel(card);
                            return;
                        }
                        list.appendChild(card);
                        moved.push(card);
                    });
                    applyRevealedToCards(moved);
                    loadedTabs[tabName] = true;
                 });
                 });
                buildTabCaches();
                runRewardsDeferred(root);
            }
            function startPrefetchRest() {
                if (prefetchRestStarted) return prefetchRestPromise;
                prefetchRestStarted = true;
                prefetchRestPromise = fetchParse("{{#invoke:Conquistas|renderRest|skip=geral}}")
                    .then(function (html) {
                        mountBundleFromHtml(html);
                    })
                    .catch(function () { /* falha silenciosa — aba carrega sob demanda */ });
                return prefetchRestPromise;
             }
             }


             // Busca com debounce
             function schedulePrefetchRest() {
            if (searchInput) {
                 var run = function () { startPrefetchRest(); };
                 searchInput.addEventListener("input", function () {
                if (window.requestIdleCallback) {
                    clearTimeout(searchTimeout);
                    requestIdleCallback(run, { timeout: 2000 });
                    searchTimeout = setTimeout(function () {
                } else {
                        currentSearch = searchInput.value.trim();
                     setTimeout(run, 350);
                        applyVisibility();
                 }
                     }, 240);
                 });
             }
             }


             // Filtros (normal / oculta / subtype)
             function loadTabNow(tabName) {
            root.querySelectorAll(".gla-conquistas-filter").forEach(function (pill) {
                var list = validTabs[tabName];
                pill.addEventListener("click", function () {
                if (list) {
                    pill.parentNode.querySelectorAll(".gla-conquistas-filter").forEach(function (p) {
                    list.classList.remove("gla-tab-error");
                         p.classList.remove("is-active");
                    list.classList.add("gla-tab-loading");
                }
                return fetchTabHtml(tabName)
                    .then(function (html) {
                        mountCardsFromHtml(html, tabName);
                        if (list) list.classList.remove("gla-tab-loading");
                    })
                    .catch(function () {
                         if (list) {
                            list.classList.remove("gla-tab-loading");
                            list.classList.add("gla-tab-error");
                        }
                     });
                     });
                    pill.classList.add("is-active");
             }
                    currentFilter = pill.getAttribute("data-filter");
                    applyVisibility();
                });
             });


             // Spoiler — abre/fecha só no botão "Spoiler".
             function ensureTabLoaded(tabName) {
            // O Lua emite <span role="button"> (MediaWiki não aceita <button>
                if (loadedTabs[tabName]) return Promise.resolve();
            // em wikitext), por isso aceitamos click + Enter/Space pra
                if (loadingTabs[tabName]) return loadingTabs[tabName];
            // preservar semântica de botão acessível.
            var openSpoilerCard = null;
            var openSpoilerToggle = null;


            function closeOpenSpoiler() {
                 if (prefetchRestPromise) {
                 if (openSpoilerCard) {
                     loadingTabs[tabName] = prefetchRestPromise.then(function () {
                     openSpoilerCard.classList.remove("is-open");
                        if (loadedTabs[tabName]) return;
                    openSpoilerCard = null;
                        return loadTabNow(tabName);
                }
                     });
                if (openSpoilerToggle) {
                     return loadingTabs[tabName];
                     openSpoilerToggle.setAttribute("aria-expanded", "false");
                     openSpoilerToggle = null;
                 }
                 }
                loadingTabs[tabName] = loadTabNow(tabName).then(function () {
                    delete loadingTabs[tabName];
                });
                return loadingTabs[tabName];
             }
             }


             function toggleSpoiler(toggle) {
             // Estado global
                var card = toggle.closest(".gla-item.has-spoiler");
            var currentSearch = "";
                if (!card) return;
            var currentFilter = "all";
                var isOpen = (openSpoilerCard === card);
            var currentTab = "geral";
                closeOpenSpoiler();
            var searchTimeout = null;
                if (!isOpen) {
 
                    card.classList.add("is-open");
            var filterDefault = root.querySelector("#gla-filter-default");
                    toggle.setAttribute("aria-expanded", "true");
            var filterColiseu = root.querySelector("#gla-filter-coliseu");
                    openSpoilerCard = card;
            var searchInput = root.querySelector(".gla-conquistas-search");
                    openSpoilerToggle = toggle;
 
                 }
            function countHiddenInTab(tabName) {
                 return hiddenCountByTab[tabName] || 0;
             }
             }


             root.addEventListener("click", function (e) {
             // Mostra a barra de filtros só quando faz sentido: na aba Coliseu mostra
                var toggle = e.target.closest(".gla-item-spoiler-toggle");
            // o subset (One Man Army / Corrida). Nas outras, só mostra o filtro
                if (!toggle) return;
            // normal/hidden se a aba tiver pelo menos uma conquista oculta —
                if (e.target.closest(".item-wrapper")) return;
            // se não tiver, esconde pra não poluir.
                e.preventDefault();
            function syncFilterBarForTab(tabName) {
                 toggleSpoiler(toggle);
                 var hiddenCount = countHiddenInTab(tabName);
            });


            root.addEventListener("keydown", function (e) {
                if (filterColiseu) {
                if (e.key !== "Enter" && e.key !== " ") return;
                    filterColiseu.style.display = tabName === "coliseu" ? "" : "none";
                var toggle = e.target.closest(".gla-item-spoiler-toggle");
                }
                if (!toggle) return;
                if (filterDefault) {
                 e.preventDefault();
                    if (tabName === "coliseu") {
                toggleSpoiler(toggle);
                        filterDefault.style.display = "none";
            });
                    } else {
                        filterDefault.style.display = hiddenCount > 0 ? "" : "none";
                    }
                 }


            // ─── Reveal + persistência ────────────────────────────────────────
                if (tabName !== "coliseu" && hiddenCount === 0) {
            // Click no card hidden remove a censura (anti-spoiler) e o estado
                    if (currentFilter === "hidden" || currentFilter === "normal") {
            // é salvo no localStorage. Próxima visita à página, conquistas que
                        currentFilter = "all";
            // o jogador já revelou voltam reveladas — não precisa clicar de novo.
                    }
            //
                    if (filterDefault) {
            // Chave: "glaConquistasRevealed" → JSON com array de data-id.
                        filterDefault.querySelectorAll(".gla-conquistas-filter").forEach(function (p) {
            // Robusto a localStorage indisponível (modo privado, cookies
                            p.classList.toggle("is-active", p.getAttribute("data-filter") === "all");
            // bloqueados) — só falha silenciosamente.
                         });
            var REVEAL_STORAGE_KEY = "glaConquistasRevealed";
 
            function loadRevealed() {
                try {
                    var raw = window.localStorage.getItem(REVEAL_STORAGE_KEY);
                    if (!raw) return {};
                    var arr = JSON.parse(raw);
                    if (!Array.isArray(arr)) return {};
                    var set = {};
                    for (var i = 0; i < arr.length; i++) {
                         if (arr[i] != null) set[String(arr[i])] = true;
                     }
                     }
                    return set;
                } catch (e) {
                    return {};
                 }
                 }
             }
             }


             function saveRevealed(set) {
            // Normalização pra busca: lowercase + remove acentos (NFD divide
                 try {
            // base+diacrítico, regex remove os diacríticos). Assim "voce"
                     var arr = Object.keys(set);
            // acha "você", "missao" acha "missão", etc.
                     window.localStorage.setItem(REVEAL_STORAGE_KEY, JSON.stringify(arr));
             function normalize(s) {
                 } catch (e) { /* localStorage indisponível — ok */ }
                 return (s || "")
                     .toLowerCase()
                     .normalize("NFD")
                    .replace(/[̀-ͯ]/g, "");
            }
 
            // Cacheia o "haystack" (title + desc normalizados) no próprio card
            // pra não recalcular a cada keystroke. Recomputa só se o conteúdo
            // do card mudar (não acontece na vida do widget — é estático).
            function getHaystack(card) {
                var cached = card.__glaHaystack;
                if (cached) return cached;
                var titleEl = card.querySelector(".gla-item-title");
                 var descEl = card.querySelector(".gla-item-desc");
                var titleText = titleEl ? titleEl.textContent : "";
                var descText = descEl ? descEl.textContent : "";
                cached = normalize(titleText + " " + descText);
                card.__glaHaystack = cached;
                return cached;
             }
             }


             var revealedSet = loadRevealed();
             function applyVisibility() {
                var cards = cardsByTab[currentTab] || [];
                if (!cards.length) return;


            // Re-aplica .is-revealed nos cards que já tavam revelados em
                // Token-based search: divide a query em palavras e exige que
            // sessões anteriores. Roda agora (depois do JS já ter movido os
                // TODAS apareçam no haystack (title + desc), em qualquer
            // cards do source pros painéis).
                // ordem. "voce grand" → tokens ["voce", "grand"] → casa com
            root.querySelectorAll('.gla-item[data-hidden="true"][data-id]').forEach(function (card) {
                // "Você entrou na Grand Line pela primeira vez". Acentos e
                 if (revealedSet[card.getAttribute("data-id")]) {
                // case são ignorados via normalize().
                    card.classList.add("is-revealed");
                 var tokens = normalize(currentSearch).split(/\s+/).filter(Boolean);
                }
            });


            // Click — revela e persiste. Só ativa se data-reveal-mode no root
                cards.forEach(function (card) {
            // estiver definido como blur/redacted/placeholder/veil.
                    if (getCardTab(card) !== currentTab) {
            //
                        card.style.display = "none";
            // O CSS aplica `pointer-events: none` no .gla-item-main enquanto
                        return;
            // a censura está ativa — então click em qualquer ponto interno
                    }
            // (icon/title/desc/items/spoiler/chip+N) cai direto no .gla-item.
            // Aqui não precisamos mais filtrar interativos: enquanto censurado,
            // tudo é "click pra revelar"; depois que revela, os filtros internos
            // voltam ao normal porque o seletor :not(.is-revealed) deixa de bater.
            root.addEventListener("click", function (e) {
                var mode = root.getAttribute("data-reveal-mode") || "none";
                if (mode === "none") return;
                var card = e.target.closest('.gla-item[data-hidden="true"]');
                if (!card) return;
                if (card.classList.contains("is-revealed")) return;
                card.classList.add("is-revealed");


                var id = card.getAttribute("data-id");
                    var matchSearch = true;
                 if (id) {
                    if (tokens.length > 0) {
                     revealedSet[id] = true;
                        var hay = getHaystack(card);
                     saveRevealed(revealedSet);
                        for (var i = 0; i < tokens.length; i++) {
                 }
                            if (hay.indexOf(tokens[i]) === -1) { matchSearch = false; break; }
             });
                        }
 
                    }
             function abrirAba(nome) {
 
                 currentTab = nome;
                    var hidden = card.getAttribute("data-hidden") === "true";
                 currentFilter = "all";
                    var subtype = (card.getAttribute("data-subtype") || "").toLowerCase();
                 closeOpenSpoiler();
                    var matchFilter = true;
 
                    if (currentFilter === "normal") matchFilter = !hidden;
                 tabs.forEach(function (t) { t.classList.remove("is-active"); });
                    else if (currentFilter === "hidden") matchFilter = hidden;
                 panels.forEach(function (p) { p.classList.remove("is-active"); });
                    else if (currentFilter === "onemany") matchFilter = subtype === "onemany";
 
                    else if (currentFilter === "corrida") matchFilter = subtype === "corrida";
                 var tab = tabByName[nome];
 
                 var panel = panelByTab[nome];
                    card.style.display = (matchSearch && matchFilter) ? "" : "none";
                 if (tab) tab.classList.add("is-active");
                 });
                 if (panel) panel.classList.add("is-active");
            }
 
 
                 syncFilterBarForTab(nome);
            // Busca com debounce
 
            if (searchInput) {
                 root.querySelectorAll(".gla-conquistas-filter").forEach(function (p) {
                searchInput.addEventListener("input", function () {
                     if (p.offsetParent !== null && p.style.display !== "none") {
                    clearTimeout(searchTimeout);
                         p.classList.toggle("is-active", p.getAttribute("data-filter") === "all");
                    searchTimeout = setTimeout(function () {
                     }
                        currentSearch = searchInput.value.trim();
                        applyVisibility();
                    }, 240);
                });
            }
 
            // Filtros (normal / oculta / subtype)
            root.querySelectorAll(".gla-conquistas-filter").forEach(function (pill) {
                pill.addEventListener("click", function () {
                     pill.parentNode.querySelectorAll(".gla-conquistas-filter").forEach(function (p) {
                        p.classList.remove("is-active");
                    });
                    pill.classList.add("is-active");
                    currentFilter = pill.getAttribute("data-filter");
                     applyVisibility();
                 });
             });
 
            // Spoiler — abre/fecha só no botão "Spoiler".
            // O Lua emite <span role="button"> (MediaWiki não aceita <button>
            // em wikitext), por isso aceitamos click + Enter/Space pra
            // preservar semântica de botão acessível.
            var openSpoilerCard = null;
            var openSpoilerToggle = null;
 
             function closeOpenSpoiler() {
                if (openSpoilerCard) {
                    openSpoilerCard.classList.remove("is-open");
                    openSpoilerCard = null;
                 }
                if (openSpoilerToggle) {
                    openSpoilerToggle.setAttribute("aria-expanded", "false");
                    openSpoilerToggle = null;
                 }
            }
 
            function toggleSpoiler(toggle) {
                var card = toggle.closest(".gla-item.has-spoiler");
                if (!card) return;
                var isOpen = (openSpoilerCard === card);
                 closeOpenSpoiler();
                if (!isOpen) {
                    card.classList.add("is-open");
                    toggle.setAttribute("aria-expanded", "true");
                    openSpoilerCard = card;
                    openSpoilerToggle = toggle;
                 }
            }
 
            root.addEventListener("click", function (e) {
                var toggle = e.target.closest(".gla-item-spoiler-toggle");
                if (!toggle) return;
                if (e.target.closest(".item-wrapper")) return;
                e.preventDefault();
                toggleSpoiler(toggle);
            });
 
            root.addEventListener("keydown", function (e) {
                if (e.key !== "Enter" && e.key !== " ") return;
                var toggle = e.target.closest(".gla-item-spoiler-toggle");
                if (!toggle) return;
                e.preventDefault();
                toggleSpoiler(toggle);
            });
 
            // ─── Reveal + persistência ────────────────────────────────────────
            function saveRevealed(set) {
                 try {
                    var arr = Object.keys(set);
                    window.localStorage.setItem(REVEAL_STORAGE_KEY, JSON.stringify(arr));
                } catch (e) { /* localStorage indisponível — ok */ }
            }
 
            // Re-aplica .is-revealed nos cards que já tavam revelados em
            // sessões anteriores. Roda agora (depois do JS já ter movido os
            // cards do source pros painéis).
            root.querySelectorAll('.gla-item[data-hidden="true"][data-id]').forEach(function (card) {
                if (revealedSet[card.getAttribute("data-id")]) {
                    card.classList.add("is-revealed");
                }
            });
 
            // Click — revela e persiste. Só ativa se data-reveal-mode no root
            // estiver definido como blur/redacted/placeholder/veil.
            //
            // O CSS aplica `pointer-events: none` no .gla-item-main enquanto
            // a censura está ativa — então click em qualquer ponto interno
            // (icon/title/desc/items/spoiler/chip+N) cai direto no .gla-item.
            // Aqui não precisamos mais filtrar interativos: enquanto censurado,
            // tudo é "click pra revelar"; depois que revela, os filtros internos
            // voltam ao normal porque o seletor :not(.is-revealed) deixa de bater.
            root.addEventListener("click", function (e) {
                 var mode = root.getAttribute("data-reveal-mode") || "none";
                if (mode === "none") return;
                 var card = e.target.closest('.gla-item[data-hidden="true"]');
                 if (!card) return;
                if (card.classList.contains("is-revealed")) return;
                card.classList.add("is-revealed");
 
                var id = card.getAttribute("data-id");
                 if (id) {
                    revealedSet[id] = true;
                    saveRevealed(revealedSet);
                }
            });
 
            function resetVisibilityAllTabs() {
                root.querySelectorAll(".gla-item[data-tab]").forEach(function (card) {
                    card.style.display = "";
                });
            }
 
            function finishAbrirAba(nome) {
                 syncFilterBarForTab(nome);
                buildTabCaches();
                resetVisibilityAllTabs();
 
                 root.querySelectorAll(".gla-conquistas-filter").forEach(function (p) {
                     if (p.offsetParent !== null && p.style.display !== "none") {
                         p.classList.toggle("is-active", p.getAttribute("data-filter") === "all");
                     }
                });
 
                applyVisibility();
            }
 
            function abrirAba(nome) {
                currentTab = nome;
                currentFilter = "all";
                closeOpenSpoiler();
 
                tabs.forEach(function (t) { t.classList.remove("is-active"); });
                panels.forEach(function (p) { p.classList.remove("is-active"); });
 
                var tab = tabByName[nome];
                var panel = panelByTab[nome];
                if (tab) tab.classList.add("is-active");
                if (panel) panel.classList.add("is-active");
 
                ensureTabLoaded(nome).then(function () {
                    finishAbrirAba(nome);
                });
            }
 
            tabs.forEach(function (tab) {
                tab.addEventListener("click", function () {
                    abrirAba(tab.getAttribute("data-tab"));
                 });
                 });
 
                 tab.addEventListener("mouseenter", function () {
                applyVisibility();
                     var nome = tab.getAttribute("data-tab");
            }
                    if (nome && !loadedTabs[nome]) ensureTabLoaded(nome);
 
            tabs.forEach(function (tab) {
                 tab.addEventListener("click", function () {
                     abrirAba(tab.getAttribute("data-tab"));
                 });
                 });
             });
             });


            schedulePrefetchRest();
             abrirAba("geral");
             abrirAba("geral");
         });
         });
Linha 1 671: Linha 1 935:
             });
             });


             function processAll() {
             function processRewardsIn(rootEl) {
                 document.querySelectorAll(".gla-item-reward").forEach(processOne);
                 if (!rootEl) return;
                rootEl.querySelectorAll(".gla-item-reward").forEach(processOne);
            }
 
            window.glaConquistasProcessRewards = processRewardsIn;
 
            function scheduleProcessRewards(rootEl) {
                var run = function () { processRewardsIn(rootEl || document); };
                if (window.requestIdleCallback) {
                    requestIdleCallback(run, { timeout: 1200 });
                } else {
                    setTimeout(run, 0);
                }
             }
             }


             if (document.readyState === "loading") {
             if (document.readyState === "loading") {
                 document.addEventListener("DOMContentLoaded", processAll);
                 document.addEventListener("DOMContentLoaded", function () {
                    scheduleProcessRewards(document);
                });
             } else {
             } else {
                 processAll();
                 scheduleProcessRewards(document);
             }
             }



Edição atual tal como às 04h21min de 21 de maio de 2026