打开/关闭菜单
8
231
3
1270
夜幕之下 Wiki - Reign of Nightfall 中文资料站
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

MediaWiki:Common.js

MediaWiki界面页面

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */

/* 引入卡片数据渲染模块 */
mw.loader.load( '/index.php?title=MediaWiki:CardData.js&action=raw&ctype=text/javascript' );

/* 卡面滚动 */
window.addEventListener("scroll", function() {
  var bg = document.querySelector(".card_fullscreen-img");
  if (!bg) return;
  if (window.scrollY > 10) {
    bg.classList.add("scrolled");
  } else {
    bg.classList.remove("scrolled");
  }
});

/* 卡面切换 */
mw.hook("wikipage.content").add(function(){
    document.querySelectorAll(".ron-card").forEach(function(card){
        var bg = card.querySelector(".card_fullscreen-img--switchable");
        var buttons = card.querySelectorAll(".card_face-switch-btn");
        if (!bg || !buttons.length) return;

        function activate(face) {
            bg.setAttribute("data-active-face", face);
            bg.querySelectorAll(".card_face").forEach(function(item){
                item.classList.toggle("is-active", item.getAttribute("data-card-face") === face);
            });
            buttons.forEach(function(button){
                button.classList.toggle("is-active", button.getAttribute("data-card-face") === face);
            });
        }

        buttons.forEach(function(button){
            button.addEventListener("click", function(){
                activate(button.getAttribute("data-card-face"));
            });
            button.addEventListener("keydown", function(event){
                if (event.key === "Enter" || event.key === " ") {
                    event.preventDefault();
                    activate(button.getAttribute("data-card-face"));
                }
            });
        });
    });
});

/* 卡面灯箱 */
(function(){
    var lightbox;
    var stage;
    var lightboxImg;
    var lastTrigger;
    var state = { scale: 1, x: 0, y: 0 };
    var drag = null;
    var suppressStageClickUntil = 0;

    function clamp(value, min, max) {
        return Math.min(max, Math.max(min, value));
    }

    function pointInRect(rect, event) {
        if (!rect || !event || event.clientX == null || event.clientY == null) return false;
        return event.clientX >= rect.left
            && event.clientX <= rect.right
            && event.clientY >= rect.top
            && event.clientY <= rect.bottom;
    }

    function cardImageTriggerRect(card, img) {
        if (!card || !img) return null;
        if (card.classList.contains("ron-card--rarity-4")) {
            var stageNode = card.querySelector(".card_fullscreen-img > a, .card_fullscreen-img > .mw-file-description, .card_fullscreen-img figure, .card_fullscreen-img .mw-halign-none");
            if (stageNode) {
                var stageRect = stageNode.getBoundingClientRect();
                if (stageRect.width > 20 && stageRect.height > 20) return stageRect;
            }
        }
        var imageRect = img.getBoundingClientRect();
        if (imageRect.width > 20 && imageRect.height > 20) return imageRect;
        return null;
    }

    function visibleCardImageRect(card, img) {
        var rect = cardImageTriggerRect(card, img);
        if (!rect) return null;

        var content = card.querySelector(".card_content-container");
        if (content) {
            var contentRect = content.getBoundingClientRect();
            if (contentRect.top > rect.top && contentRect.top < rect.bottom) {
                rect = {
                    left: rect.left,
                    right: rect.right,
                    top: rect.top,
                    bottom: contentRect.top,
                    width: rect.width,
                    height: Math.max(0, contentRect.top - rect.top),
                };
            }
        }
        return rect;
    }

    function pointInCardImage(card, img, event) {
        return pointInRect(visibleCardImageRect(card, img), event);
    }

    function cardLightboxPointInUi(selector, event) {
        if (!event || event.clientX == null || event.clientY == null) return false;
        var nodes = document.querySelectorAll(selector);
        for (var i = 0; i < nodes.length; i++) {
            var rect = nodes[i].getBoundingClientRect();
            if (rect.width > 0 && rect.height > 0 && pointInRect(rect, event)) return true;
        }
        return false;
    }

    function cardLightboxSearchUiOpen(event) {
        var details = document.getElementById("citizen-search-details");
        if (details && details.open) return true;
        if (document.querySelector(".citizen-dropdown-details[open]")) return true;
        var active = document.activeElement;
        if (active && active.closest && active.closest(".citizen-search, #citizen-search__card, #searchform, #searchInput, .cdx-typeahead-search, .cdx-search-input")) return true;
        return cardLightboxPointInUi(".citizen-search, #citizen-search__card, #searchform, #searchInput, [role=\"search\"], .cdx-menu, .cdx-menu-item, .cdx-typeahead-search, .cdx-search-input, .suggestions, .suggestions-results, .suggestions-special, .mw-searchSuggest-link", event);
    }

    function cardLightboxBlockedTarget(event) {
        if (cardLightboxSearchUiOpen(event)) return true;
        var target = event && event.target;
        if (!target || !target.closest) return false;
        return !!target.closest(".card_lightbox, .card_face-switch, .card_lightbox-tool, .card_lightbox-close, .card_content-container, .citizen-header, .citizen-page-header, .citizen-search, #citizen-search-details, #citizen-search__card, #searchform, #searchInput, [role=\"search\"], .cdx-menu, .cdx-menu-item, .cdx-typeahead-search, .cdx-search-input, .suggestions, .suggestions-results, .suggestions-special, .mw-searchSuggest-link, #mw-navigation, #p-personal");
    }

    function cardLightboxHit(event) {
        if (!event || event.clientX == null || event.clientY == null) return null;
        var cards = document.querySelectorAll(".ron-card");
        for (var i = cards.length - 1; i >= 0; i--) {
            var card = cards[i];
            var img = currentCardImage(card);
            if (!pointInCardImage(card, img, event)) continue;
            return {
                card: card,
                trigger: null,
            };
        }
        return null;
    }

    function cardLightboxAllowedHeroTrigger(card, event) {
        var target = event && event.target;
        if (!card || !target || !target.closest) return false;
        var hero = target.closest(".card_hero");
        if (!hero || !card.contains(hero)) return false;
        if (target.closest(".card_face-switch")) return false;
        return true;
    }

    function bindDocumentCardLightbox() {
        if (document.documentElement.getAttribute("data-card-lightbox-document-bound") === "1") return;
        document.documentElement.setAttribute("data-card-lightbox-document-bound", "1");

        document.addEventListener("mouseleave", function(){
            document.documentElement.classList.remove("card_lightbox-hover");
        });
        window.addEventListener("blur", function(){
            document.documentElement.classList.remove("card_lightbox-hover");
        });
        window.addEventListener("scroll", function(){
            document.documentElement.classList.remove("card_lightbox-hover");
        }, { passive: true });
    }

    function applyTransform(animate) {
        if (!lightboxImg) return;
        if (!animate) lightbox.classList.add("is-dragging");
        lightboxImg.style.setProperty("--card-lightbox-scale", state.scale);
        lightboxImg.style.setProperty("--card-lightbox-x", state.x + "px");
        lightboxImg.style.setProperty("--card-lightbox-y", state.y + "px");
        if (!animate) {
            window.requestAnimationFrame(function(){
                if (!drag) lightbox.classList.remove("is-dragging");
            });
        }
    }

    function resetTransform() {
        state = { scale: 1, x: 0, y: 0 };
        applyTransform(true);
    }

    function zoomBy(delta, clientX, clientY) {
        var previous = state.scale;
        var next = clamp(previous * delta, 1, 4);
        if (next === previous) return;

        if (clientX != null && clientY != null && stage) {
            var rect = stage.getBoundingClientRect();
            var cx = clientX - rect.left - rect.width / 2;
            var cy = clientY - rect.top - rect.height / 2;
            var ratio = next / previous;
            state.x = (state.x - cx) * ratio + cx;
            state.y = (state.y - cy) * ratio + cy;
        }

        state.scale = next;
        if (state.scale === 1) {
            state.x = 0;
            state.y = 0;
        }
        applyTransform(true);
    }

    function ensureLightbox() {
        if (lightbox) return lightbox;
        lightbox = document.createElement("div");
        lightbox.className = "card_lightbox";
        lightbox.setAttribute("role", "dialog");
        lightbox.setAttribute("aria-modal", "true");

        stage = document.createElement("div");
        stage.className = "card_lightbox-stage";

        lightboxImg = document.createElement("img");
        lightboxImg.className = "card_lightbox-img";
        lightboxImg.alt = "";
        lightboxImg.draggable = false;

        var close = document.createElement("div");
        close.className = "card_lightbox-close";
        close.setAttribute("role", "button");
        close.setAttribute("tabindex", "0");
        close.setAttribute("aria-label", "关闭");
        close.textContent = "×";

        var tools = document.createElement("div");
        tools.className = "card_lightbox-tools";
        var zoomOut = document.createElement("div");
        zoomOut.className = "card_lightbox-tool";
        zoomOut.setAttribute("role", "button");
        zoomOut.setAttribute("tabindex", "0");
        zoomOut.setAttribute("aria-label", "缩小");
        zoomOut.setAttribute("data-card-lightbox-action", "zoom-out");
        zoomOut.textContent = "−";
        var reset = document.createElement("div");
        reset.className = "card_lightbox-tool";
        reset.setAttribute("role", "button");
        reset.setAttribute("tabindex", "0");
        reset.setAttribute("aria-label", "重置");
        reset.setAttribute("data-card-lightbox-action", "reset");
        reset.textContent = "1:1";
        var zoomIn = document.createElement("div");
        zoomIn.className = "card_lightbox-tool";
        zoomIn.setAttribute("role", "button");
        zoomIn.setAttribute("tabindex", "0");
        zoomIn.setAttribute("aria-label", "放大");
        zoomIn.setAttribute("data-card-lightbox-action", "zoom-in");
        zoomIn.textContent = "+";

        stage.appendChild(lightboxImg);
        tools.appendChild(zoomOut);
        tools.appendChild(reset);
        tools.appendChild(zoomIn);
        lightbox.appendChild(stage);
        lightbox.appendChild(close);
        lightbox.appendChild(tools);
        document.body.appendChild(lightbox);

        function keyboardButton(event, fn) {
            if (event.type === "keydown" && event.key !== "Enter" && event.key !== " ") return;
            event.preventDefault();
            fn(event);
        }

        close.addEventListener("click", function(event){ keyboardButton(event, closeLightbox); });
        close.addEventListener("keydown", function(event){ keyboardButton(event, closeLightbox); });
        zoomOut.addEventListener("click", function(event){ keyboardButton(event, function(){ zoomBy(1 / 1.22); }); });
        zoomOut.addEventListener("keydown", function(event){ keyboardButton(event, function(){ zoomBy(1 / 1.22); }); });
        zoomIn.addEventListener("click", function(event){ keyboardButton(event, function(){ zoomBy(1.22); }); });
        zoomIn.addEventListener("keydown", function(event){ keyboardButton(event, function(){ zoomBy(1.22); }); });
        reset.addEventListener("click", function(event){ keyboardButton(event, resetTransform); });
        reset.addEventListener("keydown", function(event){ keyboardButton(event, resetTransform); });

        lightbox.addEventListener("click", function(event){
            if (Date.now() < suppressStageClickUntil) {
                event.preventDefault();
                event.stopPropagation();
                return;
            }
            if (event.target === lightbox || event.target === stage) closeLightbox();
        });
        lightbox.addEventListener("wheel", function(event){
            if (!lightbox.classList.contains("is-open")) return;
            event.preventDefault();
            zoomBy(event.deltaY < 0 ? 1.12 : 1 / 1.12, event.clientX, event.clientY);
        }, { passive: false });
        stage.addEventListener("dblclick", function(event){
            event.preventDefault();
            if (state.scale === 1) zoomBy(2, event.clientX, event.clientY);
            else resetTransform();
        });
        stage.addEventListener("pointerdown", function(event){
            if (event.button != null && event.button !== 0) return;
            if (state.scale <= 1) return;
            event.preventDefault();
            drag = {
                startX: event.clientX,
                startY: event.clientY,
                baseX: state.x,
                baseY: state.y,
                moved: false,
            };
            lightbox.classList.add("is-dragging");
            stage.setPointerCapture(event.pointerId);
        });
        stage.addEventListener("pointermove", function(event){
            if (!drag) return;
            var dx = event.clientX - drag.startX;
            var dy = event.clientY - drag.startY;
            if (Math.abs(dx) + Math.abs(dy) > 4) drag.moved = true;
            state.x = drag.baseX + dx;
            state.y = drag.baseY + dy;
            applyTransform(false);
        });
        stage.addEventListener("pointerup", function(event){
            if (!drag) return;
            if (drag.moved) suppressStageClickUntil = Date.now() + 280;
            drag = null;
            stage.releasePointerCapture(event.pointerId);
            lightbox.classList.remove("is-dragging");
        });
        stage.addEventListener("pointercancel", function(){
            drag = null;
            lightbox.classList.remove("is-dragging");
        });
        document.addEventListener("keydown", function(event){
            if (!lightbox.classList.contains("is-open")) return;
            if (event.key === "Escape") closeLightbox();
            if (event.key === "+" || event.key === "=") zoomBy(1.18);
            if (event.key === "-") zoomBy(1 / 1.18);
            if (event.key === "0") resetTransform();
        });
        return lightbox;
    }

    function currentCardImage(card) {
        var activeFace = card.querySelector(".card_face.is-active img");
        if (activeFace) return activeFace;
        return card.querySelector(".card_fullscreen-img img");
    }

    function originalImageSrc(img) {
        var src = img && (img.currentSrc || img.src || img.getAttribute("src"));
        if (!src) return "";

        try {
            var url = new URL(src, window.location.href);
            var thumbMarker = "/images/thumb/";
            var index = url.pathname.indexOf(thumbMarker);
            if (index === -1) return url.href;

            var prefix = url.pathname.slice(0, index) + "/images/";
            var rest = url.pathname.slice(index + thumbMarker.length);
            var parts = rest.split("/");
            if (parts.length < 4) return url.href;

            parts.pop();
            url.pathname = prefix + parts.join("/");
            url.search = "";
            url.hash = "";
            return url.href;
        } catch (error) {
            return src;
        }
    }

    function openImageLightbox(img, trigger) {
        if (!img || !img.src) return;
        ensureLightbox();
        lastTrigger = trigger || null;
        resetTransform();
        lightboxImg.src = originalImageSrc(img) || img.currentSrc || img.src;
        lightboxImg.alt = img.alt || "";
        document.body.classList.add("card_lightbox-open");
        window.requestAnimationFrame(function(){
            lightbox.classList.add("is-open");
        });
        var close = lightbox.querySelector(".card_lightbox-close");
        if (close) close.focus({ preventScroll: true });
    }

    function openLightbox(card, trigger) {
        openImageLightbox(currentCardImage(card), trigger);
    }

    function closeLightbox() {
        if (!lightbox) return;
        lightbox.classList.remove("is-open");
        document.body.classList.remove("card_lightbox-open");
        drag = null;
        window.setTimeout(resetTransform, 180);
        if (lastTrigger && typeof lastTrigger.focus === "function") {
            lastTrigger.focus({ preventScroll: true });
        }
    }

    function bindInlineImageLightbox(container) {
        container.querySelectorAll("[data-card-lightbox-image], .character_profile-portrait-layer--avatar, .character_profile-bg-art").forEach(function(trigger){
            if (trigger.getAttribute("data-card-lightbox-bound") === "1") return;
            var img = trigger.querySelector("img");
            if (!img) return;
            trigger.setAttribute("data-card-lightbox-bound", "1");
            var lightboxLabel = trigger.classList && trigger.classList.contains("character_profile-bg-art") ? "查看角色立绘" : "查看角色头像";
            if (!trigger.hasAttribute("role")) trigger.setAttribute("role", "button");
            if (!trigger.hasAttribute("tabindex")) trigger.setAttribute("tabindex", "0");
            if (!trigger.hasAttribute("title")) trigger.setAttribute("title", lightboxLabel);
            if (!trigger.hasAttribute("aria-label")) trigger.setAttribute("aria-label", lightboxLabel);

            function activate(event) {
                if (event) {
                    event.preventDefault();
                    event.stopPropagation();
                    if (typeof event.stopImmediatePropagation === "function") event.stopImmediatePropagation();
                }
                openImageLightbox(img, trigger);
            }

            trigger.addEventListener("click", activate, true);
            trigger.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                activate(event);
            });
        });
    }

    mw.hook("wikipage.content").add(function($content){
        bindDocumentCardLightbox();
        var container = $content && $content[0] ? $content[0] : document;
        bindInlineImageLightbox(container);
        container.querySelectorAll(".ron-card").forEach(function(card){
            var hero = card.querySelector(".card_hero");
            if (!hero || hero.getAttribute("data-card-lightbox-bound") === "1") return;
            hero.setAttribute("data-card-lightbox-bound", "1");
            hero.setAttribute("role", "button");
            hero.setAttribute("tabindex", "0");
            hero.setAttribute("aria-label", "放大卡面");

            hero.addEventListener("mousemove", function(event){
                var img = currentCardImage(card);
                var active = cardLightboxAllowedHeroTrigger(card, event) && pointInCardImage(card, img, event);
                hero.style.cursor = active ? "zoom-in" : "default";
                document.documentElement.classList.toggle("card_lightbox-hover", active);
            }, true);
            hero.addEventListener("mouseleave", function(){
                hero.style.cursor = "default";
                document.documentElement.classList.remove("card_lightbox-hover");
            });
            hero.addEventListener("click", function(event){
                if (!cardLightboxAllowedHeroTrigger(card, event)) return;
                var img = currentCardImage(card);
                if (!pointInCardImage(card, img, event)) return;
                event.preventDefault();
                event.stopPropagation();
                openLightbox(card, hero);
            }, true);
            hero.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                if (cardLightboxBlockedTarget(event)) return;
                event.preventDefault();
                openLightbox(card, hero);
            });
        });
    });
}());

/* 小传折叠 + 标题 sticky */
mw.hook("wikipage.content").add(function(){
    document.querySelectorAll(".card_content_story").forEach(function(story){
        var toggle     = story.querySelector(".card_content_story-toggle");
        var timeline   = story.querySelector(".card_content_story-timeline");
        var headerLeft = story.querySelector(".card_content_story-header-left");
        if (!toggle || !timeline || !headerLeft) return;
        var icon = toggle.querySelector(".story-toggle-icon");
        var text = toggle.querySelector(".story-toggle-text");
        toggle.addEventListener("click", function(){
            var collapsed = toggle.getAttribute("data-collapsed") === "true";
            if (collapsed) {
                timeline.style.opacity   = "1";
                timeline.style.transform = "translateY(0)";
                timeline.style.maxHeight = "none";
            } else {
                timeline.style.opacity   = "0";
                timeline.style.transform = "translateY(-6px)";
                timeline.style.maxHeight = "0";
            }
            toggle.setAttribute("data-collapsed", collapsed ? "false" : "true");
            icon.innerText = collapsed ? "-" : "+";
            text.innerText = collapsed ? "收起" : "展开";
            if (!collapsed) headerLeft.classList.remove("is-sticky");
        });
        function updateSticky() {
            if (toggle.getAttribute("data-collapsed") === "true") {
                headerLeft.classList.remove("is-sticky");
                return;
            }
            var storyRect    = story.getBoundingClientRect();
            var timelineRect = timeline.getBoundingClientRect();
            if (storyRect.top < 0 && timelineRect.bottom > window.innerHeight) {
                headerLeft.classList.add("is-sticky");
            } else {
                headerLeft.classList.remove("is-sticky");
            }
        }
        window.addEventListener("scroll", updateSticky, { passive: true });
        updateSticky();
    });
});

/* 邀约故事区 */
mw.hook("wikipage.content").add(function(){
    var cards    = document.querySelectorAll(".card_content_date-item");
    var panel    = document.getElementById("date-story-panel");
    var contents = document.querySelectorAll(".card_date_story-content");
    if (!cards.length || !panel) return;
    var currentIndex = -1;
    cards.forEach(function(card){
        card.addEventListener("click", function(){
            var index = parseInt(card.getAttribute("data-date-index"), 10);
            if (currentIndex === index) {
                panel.classList.remove("is-visible");
                card.classList.remove("is-active");
                currentIndex = -1;
                return;
            }
            cards.forEach(function(c){ c.classList.remove("is-active"); });
            card.classList.add("is-active");
            currentIndex = index;
            panel.classList.remove("is-visible");
            void panel.offsetWidth;
            panel.classList.add("is-visible");
            contents.forEach(function(content){
                var storyIndex = parseInt(content.getAttribute("data-story-index"), 10);
                content.classList.toggle("is-active", storyIndex === index);
            });
        });
    });
});

/* 道具页:列表切换 + 搜索过滤 */
mw.hook("wikipage.content").add(function(){
    var tabs    = document.querySelectorAll(".item-tab");
    var details = document.querySelectorAll(".item-detail");
    var search  = document.getElementById("item-search");
    if (!tabs.length) return;

    function activate(id) {
        details.forEach(function(el){ el.style.display = "none"; });
        tabs.forEach(function(t){ t.setAttribute("data-active", "false"); });
        var detail = document.getElementById("item-" + id);
        if (detail) detail.style.display = "block";
        var tab = document.querySelector(".item-tab[data-item='" + id + "']");
        if (tab) tab.setAttribute("data-active", "true");
    }

    tabs.forEach(function(tab){
        tab.addEventListener("click", function(){
            activate(tab.getAttribute("data-item"));
        });
    });

    if (search) {
        search.addEventListener("input", function(){
            var q = search.value.trim().toLowerCase();
            tabs.forEach(function(tab){
                var text = (tab.getAttribute("data-name") || tab.textContent || "").toLowerCase();
                tab.style.display = text.indexOf(q) !== -1 ? "" : "none";
            });
        });
    }
});

/* Character archive sandbox anchor controls. */
(function(){
    function archiveEscapeId(id) {
        if (window.CSS && CSS.escape) return CSS.escape(id);
        return String(id).replace(/["\\#.;?+*~':!^$[\]()=>|/@]/g, "\\$&");
    }

    function setArchiveNavActive(control, active) {
        control.setAttribute("data-archive-active", active ? "1" : "0");
    }

    function bindArchiveAnchorControls(root) {
        var controls = Array.prototype.slice.call(root.querySelectorAll("[data-archive-snap-target]"));
        controls.forEach(function(control){
            if (control.getAttribute("data-archive-bound") === "1") return;
            control.setAttribute("data-archive-bound", "1");
            var activate = function(){
                var id = control.getAttribute("data-archive-snap-target");
                if (!id) return;
                var target = root.querySelector("#" + archiveEscapeId(id));
                if (!target) return;
                if (typeof root.__archiveForceActive === "function") root.__archiveForceActive(id, 1200);
                target.scrollIntoView({ behavior: "smooth", block: "start" });
            };
            control.addEventListener("click", activate);
            control.addEventListener("keydown", function(event){
                if (event.key === "Enter" || event.key === " ") {
                    event.preventDefault();
                    activate();
                }
            });
        });
    }

    function bindArchiveScrollSpy(root) {
        var nav = root.querySelector("[data-archive-sticky-nav]");
        var controls = Array.prototype.slice.call(root.querySelectorAll("[data-archive-nav-item][data-archive-snap-target]"));
        if (!controls.length) return;

        var targets = [];
        var seen = {};
        controls.forEach(function(control){
            var id = control.getAttribute("data-archive-snap-target");
            if (!id || seen[id]) return;
            var target = root.querySelector("#" + archiveEscapeId(id));
            if (!target) return;
            seen[id] = true;
            targets.push({ id: id, element: target });
        });
        if (!targets.length) return;

        function measureNav() {
            if (!nav) return;
            var half = Math.max(120, Math.round(nav.getBoundingClientRect().height / 2));
            nav.style.setProperty("--archive-nav-half-height", half + "px");
        }

        var currentId = "";
        var forcedId = "";
        var forcedUntil = 0;

        function applyActive(activeId) {
            if (activeId === currentId) return;
            currentId = activeId;
            controls.forEach(function(control){
                var targetId = control.getAttribute("data-archive-snap-target");
                var parentScope = control.getAttribute("data-archive-nav-parent-scope");
                var parentActive = parentScope && (activeId === "archive-dossier-top" || activeId.indexOf(parentScope + "-") === 0);
                setArchiveNavActive(control, targetId === activeId || parentActive);
            });
        }

        root.__archiveForceActive = function(id, duration) {
            forcedId = id || "";
            forcedUntil = Date.now() + (duration || 1000);
            if (forcedId) applyActive(forcedId);
            requestUpdate();
        };

        function updateActive() {
            if (forcedId && Date.now() < forcedUntil) {
                applyActive(forcedId);
                return;
            }
            forcedId = "";
            var anchor = window.innerHeight * 0.46;
            var activeId = targets[0].id;
            targets.forEach(function(item){
                if (item.element.getBoundingClientRect().top <= anchor) activeId = item.id;
            });
            if (window.innerHeight + window.pageYOffset >= document.documentElement.scrollHeight - 3) {
                activeId = targets[targets.length - 1].id;
            }
            applyActive(activeId);
        }

        var ticking = false;
        function requestUpdate() {
            if (ticking) return;
            ticking = true;
            window.requestAnimationFrame(function(){
                ticking = false;
                measureNav();
                updateActive();
            });
        }

        if (nav && nav.getAttribute("data-archive-spy-bound") !== "1") {
            nav.setAttribute("data-archive-spy-bound", "1");
            window.addEventListener("resize", requestUpdate, { passive: true });
        }
        window.addEventListener("scroll", requestUpdate, { passive: true });
        requestUpdate();
        window.setTimeout(requestUpdate, 240);
    }

    mw.hook("wikipage.content").add(function($content){
        var container = $content && $content[0] ? $content[0] : document;
        container.querySelectorAll(".character_archive-snap").forEach(function(root){
            bindArchiveAnchorControls(root);
            bindArchiveScrollSpy(root);
        });
    });
}());


/* Style attribute growth slider */
(function(){
    var BREAK_LEVELS = [20, 40, 60, 80, 100];

    function numberAttr(root, name, fallback) {
        var value = Number(root.getAttribute(name));
        return Number.isFinite(value) ? value : fallback;
    }

    function breakForLevel(level) {
        if (level <= 20) return 0;
        if (level <= 40) return 1;
        if (level <= 60) return 2;
        if (level <= 80) return 3;
        return 4;
    }

    function clampLevel(value) {
        value = Math.round(Number(value));
        if (!Number.isFinite(value)) return 1;
        return Math.min(100, Math.max(1, value));
    }

    function formatNumber(value) {
        return Math.floor(value).toLocaleString("zh-CN");
    }

    function calc(data, level) {
        var breakCount = breakForLevel(level);
        return {
            breakCount: breakCount,
            hp: Math.floor(data.baseHp + (level - 1) * data.hpRate + breakCount * data.breakHp),
            atk: Math.floor(data.baseAtk + (level - 1) * data.atkRate + breakCount * data.breakAtk),
            def: Math.floor(data.baseDef + (level - 1) * data.defRate + breakCount * data.breakDef)
        };
    }

    function styleAttrThumbColorForPct(pct) {
    var t = Math.max(0, Math.min(1, pct / 100));
    var start = { r: 255, g: 198, b: 198 };
    var end = { r: 255, g: 124, b: 124 };
    var r = Math.round(start.r + (end.r - start.r) * t);
    var g = Math.round(start.g + (end.g - start.g) * t);
    var b = Math.round(start.b + (end.b - start.b) * t);
    return "rgba(" + r + ", " + g + ", " + b + ", 0.78)";
}

function setLevel(root, data, level) {
        level = clampLevel(level);
        var result = calc(data, level);
        var pct = ((level - 1) / 99) * 100;
        var slider = root.querySelector("[data-style-attr-slider]");
        var fill = root.querySelector(".style_attr-slider-fill");
        var thumb = root.querySelector(".style_attr-slider-thumb");
        var levelNumber = root.querySelector("[data-style-attr-level-number]");
        var levelLabel = root.querySelector("[data-style-attr-level-label]");
        var breakLabel = root.querySelector("[data-style-attr-break-label]");
        var hp = root.querySelector('[data-style-attr-stat="hp"]');
        var atk = root.querySelector('[data-style-attr-stat="atk"]');
        var def = root.querySelector('[data-style-attr-stat="def"]');

        if (slider) {
            slider.setAttribute("aria-valuenow", String(level));
            slider.setAttribute("aria-valuetext", "Lv." + level);
        }
        if (fill) fill.style.width = pct + "%";
        if (thumb) {
      thumb.style.left = pct + "%";
      thumb.style.setProperty("--style-attr-thumb-color", styleAttrThumbColorForPct(pct));
    }
        if (levelNumber) {
            levelNumber.textContent = String(level);
        } else if (levelLabel) {
            levelLabel.textContent = "Lv." + level;
        }
        if (breakLabel) breakLabel.textContent = "晋升 " + result.breakCount;
        if (hp) hp.textContent = formatNumber(result.hp);
        if (atk) atk.textContent = formatNumber(result.atk);
        if (def) def.textContent = formatNumber(result.def);

        root.querySelectorAll("[data-style-attr-preset]").forEach(function(item){
            item.classList.toggle("is-active", Number(item.getAttribute("data-style-attr-preset")) === level);
        });
    }

    function levelFromPointer(slider, event) {
        var rect = slider.getBoundingClientRect();
        var ratio = rect.width ? (event.clientX - rect.left) / rect.width : 0;
        ratio = Math.min(1, Math.max(0, ratio));
        return 1 + Math.round(ratio * 99);
    }

    function bindGrowth(root) {
        if (root.getAttribute("data-style-attr-bound") === "1") return;
        root.setAttribute("data-style-attr-bound", "1");

        var data = {
            baseHp: numberAttr(root, "data-base-hp", 0),
            baseAtk: numberAttr(root, "data-base-atk", 0),
            baseDef: numberAttr(root, "data-base-def", 0),
            hpRate: numberAttr(root, "data-rate-hp", 0),
            atkRate: numberAttr(root, "data-rate-atk", 0),
            defRate: numberAttr(root, "data-rate-def", 0),
            breakHp: numberAttr(root, "data-break-hp", 0),
            breakAtk: numberAttr(root, "data-break-atk", 0),
            breakDef: numberAttr(root, "data-break-def", 0)
        };

        var slider = root.querySelector("[data-style-attr-slider]");
        var defaultLevel = clampLevel(root.getAttribute("data-default-level") || 100);

        if (slider) {
            var dragging = false;
            var applyPointer = function(event) {
                setLevel(root, data, levelFromPointer(slider, event));
            };
            slider.addEventListener("pointerdown", function(event){
                event.preventDefault();
                dragging = true;
                slider.setPointerCapture(event.pointerId);
                applyPointer(event);
            });
            slider.addEventListener("pointermove", function(event){
                if (!dragging) return;
                applyPointer(event);
            });
            slider.addEventListener("pointerup", function(event){
                dragging = false;
                if (slider.hasPointerCapture(event.pointerId)) slider.releasePointerCapture(event.pointerId);
            });
            slider.addEventListener("pointercancel", function(event){
                dragging = false;
                if (slider.hasPointerCapture(event.pointerId)) slider.releasePointerCapture(event.pointerId);
            });
            slider.addEventListener("keydown", function(event){
                var current = clampLevel(slider.getAttribute("aria-valuenow") || defaultLevel);
                var next = current;
                if (event.key === "ArrowLeft" || event.key === "ArrowDown") next = current - 1;
                else if (event.key === "ArrowRight" || event.key === "ArrowUp") next = current + 1;
                else if (event.key === "PageDown") next = current - 10;
                else if (event.key === "PageUp") next = current + 10;
                else if (event.key === "Home") next = 1;
                else if (event.key === "End") next = 100;
                else return;
                event.preventDefault();
                setLevel(root, data, next);
            });
        }

        root.querySelectorAll("[data-style-attr-preset]").forEach(function(item){
            var activate = function() {
                setLevel(root, data, item.getAttribute("data-style-attr-preset"));
            };
            item.addEventListener("click", activate);
            item.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                event.preventDefault();
                activate();
            });
        });

        setLevel(root, data, defaultLevel);
    }

    mw.hook("wikipage.content").add(function($content){
        var container = $content && $content[0] ? $content[0] : document;
        container.querySelectorAll("[data-style-attr-growth]").forEach(bindGrowth);
    });
}());

/* atlas filter sandbox sticky settle */
(function(){
    function isAllOption(option) {
        var key = option && option.getAttribute("data-atlas-filter");
        return !!key && /-all$/.test(key);
    }

    function filterKey(option) {
        var key = option && option.getAttribute("data-atlas-filter");
        if (!key) return null;
        var match = key.match(/^([^-]+)-(.+)$/);
        if (!match) return null;
        return { group: match[1], value: match[2], all: match[2] === "all" };
    }

    function normalize(text) {
        return String(text || "").trim().toLowerCase();
    }

    function searchValue(node) {
        if (!node) return "";
        if (typeof node.value === "string") return node.value;
        return node.textContent || "";
    }

    function pageUrlForTitle(title) {
        if (window.mw && mw.util && mw.util.getUrl) return mw.util.getUrl(title);
        return "/wiki/" + encodeURIComponent(title).replace(/%20/g, "_");
    }

    function openTitleInNewWindow(title) {
        var url = pageUrlForTitle(title);
        var opened = window.open(url, "_blank", "noopener");
        if (opened) {
            try { opened.opener = null; } catch (error) {}
        }
    }

    function previewModeEnabled(root) {
        return root && root.getAttribute("data-atlas-preview-mode") === "1";
    }

    function previewImageUrl(card) {
        var img = card && card.querySelector(".atlas_card-proto-img img");
        return img ? (img.currentSrc || img.src || img.getAttribute("src") || "") : "";
    }

    function fileRedirectUrl(fileName) {
        if (!fileName) return "";
        if (window.mw && mw.config && mw.config.get && mw.config.get("wgScript")) {
            return mw.config.get("wgScript") + "?title=Special:Redirect/file/" + encodeURIComponent(fileName);
        }
        return "/wiki/Special:Redirect/file/" + encodeURIComponent(fileName);
    }

    function setPreviewIcon(root, selector, fileName) {
        var node = root.querySelector(selector);
        if (!node) return;
        node.innerHTML = "";
        if (!fileName || fileName === "—") return;
        var img = document.createElement("img");
        img.alt = "";
        img.loading = "lazy";
        img.decoding = "async";
        img.src = fileRedirectUrl(fileName);
        node.appendChild(img);
    }

    function setText(root, selector, value) {
        root.querySelectorAll(selector).forEach(function(node){
            node.textContent = value || "—";
        });
    }

    function atlasDangerStars(value) {
        var count = parseInt(value, 10);
        if (!isFinite(count) || count < 1) return "—";
        count = Math.max(1, Math.min(6, count));
        return Array(count + 1).join("★");
    }

    function renderAtlasPreviewTags(root, card) {
        var node = root.querySelector("[data-atlas-preview-tags]");
        if (!node) return;
        node.innerHTML = "";
        var raw = card.getAttribute("data-atlas-tags") || "";
        var tags = raw.split("|").map(function(tag){ return tag.trim(); }).filter(Boolean);
        if (!tags.length) {
            node.textContent = "—";
            return;
        }
        tags.forEach(function(tag){
            var item = document.createElement("div");
            item.className = "card_content_style-tag card_content_skill-tag atlas_frame-preview-tag";
            item.textContent = tag;
            node.appendChild(item);
        });
    }

    function cloneAtlasSkillDesc(root, card, slot) {
        var target = root.querySelector("[data-atlas-preview-skill-" + slot + "-desc]");
        if (!target) return;
        target.innerHTML = "";
        var source = card.querySelector('[data-atlas-skill-desc-source="' + slot + '"]');
        if (!source) {
            target.textContent = card.getAttribute("data-atlas-skill-" + slot + "-desc") || "—";
            return;
        }
        Array.prototype.forEach.call(source.childNodes, function(child){
            target.appendChild(child.cloneNode(true));
        });
    }


    function renderAtlasPreviewCollectionTerms(root, card) {
        var target = root.querySelector("[data-atlas-preview-collection-list]");
        if (!target) return;
        target.innerHTML = "";
        if (!card) {
            target.textContent = "—";
            return;
        }
        var sources = card.querySelectorAll("[data-atlas-collection-term-source]");
        if (!sources.length) {
            target.textContent = "—";
            return;
        }
        Array.prototype.forEach.call(sources, function(source){
            target.appendChild(source.cloneNode(true));
        });
    }

    function clearAtlasPreview(root) {
        if (!root) return;
        root.__atlasPreviewCard = null;
        root.setAttribute("data-atlas-has-selection", "0");
        root.querySelectorAll("[data-atlas-preview-face]").forEach(function(faceButton){
            if (faceButton.getAttribute("data-atlas-preview-face-bound") === "1") return;
            faceButton.setAttribute("data-atlas-preview-face-bound", "1");
            function activateFace(event) {
                if (event) event.preventDefault();
                var card = root.__atlasPreviewCard;
                if (!card) return;
                setAtlasPreviewFace(root, card, faceButton.getAttribute("data-atlas-preview-face"));
            }
            faceButton.addEventListener("click", activateFace);
            faceButton.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                event.preventDefault();
                activateFace(event);
            });
        });

        var previewOpen = root.querySelector("[data-atlas-preview-open]");
        if (previewOpen && previewOpen.getAttribute("data-atlas-preview-open-bound") !== "1") {
            previewOpen.setAttribute("data-atlas-preview-open-bound", "1");
            function openPreviewStyle(event) {
                if (event) {
                    event.preventDefault();
                    event.stopPropagation();
                }
                if (previewOpen.getAttribute("aria-disabled") === "true") return;
                var title = previewOpen.getAttribute("data-atlas-preview-open-title");
                if (!title && root.__atlasPreviewCard) {
                    title = root.__atlasPreviewCard.getAttribute("data-atlas-page-title")
                        || root.__atlasPreviewCard.getAttribute("data-atlas-style-name");
                }
                if (!title) return;
                var opened = window.open(pageUrlForTitle(title), "_blank", "noopener");
                if (opened) {
                    try { opened.opener = null; } catch (error) {}
                }
            }
            previewOpen.addEventListener("click", openPreviewStyle);
            previewOpen.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                openPreviewStyle(event);
            });
        }

        root.querySelectorAll("[data-atlas-card]").forEach(function(card){
            card.classList.remove("is-active");
        });
        var panel = root.querySelector("[data-atlas-preview-panel]");
        if (panel) panel.classList.remove("is-filled");
        var openAction = root.querySelector("[data-atlas-preview-open]");
        if (openAction) {
            openAction.setAttribute("aria-disabled", "true");
            openAction.removeAttribute("data-atlas-preview-open-title");
        }
        var art = root.querySelector("[data-atlas-preview-art]");
        if (art) art.innerHTML = "";
        var switcher = root.querySelector("[data-atlas-preview-face-switch]");
        if (switcher) switcher.style.display = "none";
        setText(root, "[data-atlas-preview-title]", "详情预览");
        setText(root, "[data-atlas-preview-style]", "未选择");
        setText(root, "[data-atlas-preview-sub]", "—");
        setText(root, "[data-atlas-preview-copy]", "");
        renderAtlasPreviewCollectionTerms(root, null);
        var clearPreviewPanel = root.querySelector("[data-atlas-preview-panel]");
        if (clearPreviewPanel) clearPreviewPanel.scrollTop = 0;
    }

    function updateAtlasPreview(root, card) {
        if (!root || !card) return;
        root.setAttribute("data-atlas-has-selection", "1");
        root.querySelectorAll("[data-atlas-card]").forEach(function(other){
            other.classList.toggle("is-active", other === card);
        });
        var panel = root.querySelector("[data-atlas-preview-panel]");
        if (panel) panel.classList.add("is-filled");
        var styleName = card.getAttribute("data-atlas-style-name") || card.getAttribute("data-atlas-page-title") || "—";
        var openAction = root.querySelector("[data-atlas-preview-open]");
        if (openAction) {
            openAction.setAttribute("aria-disabled", "false");
            openAction.setAttribute("data-atlas-preview-open-title", card.getAttribute("data-atlas-page-title") || styleName);
        }
        setText(root, "[data-atlas-preview-title]", "详情预览");
        setText(root, "[data-atlas-preview-style]", styleName);
        setText(root, "[data-atlas-preview-sub]", card.getAttribute("data-atlas-character-name") || "—");
        setText(root, "[data-atlas-preview-character]", card.getAttribute("data-atlas-character-name"));
        setText(root, "[data-atlas-preview-profession-text]", card.getAttribute("data-atlas-profession-name"));
        setText(root, "[data-atlas-preview-desire-text]", card.getAttribute("data-atlas-desire-name"));
        setText(root, "[data-atlas-preview-danger]", atlasDangerStars(card.getAttribute("data-atlas-danger")));
        setText(root, "[data-atlas-preview-acquisition]", card.getAttribute("data-atlas-acquisition-label"));
        renderAtlasPreviewTags(root, card);
        setPreviewIcon(root, "[data-atlas-preview-profession-icon]", card.getAttribute("data-atlas-profession-icon"));
        setPreviewIcon(root, "[data-atlas-preview-desire-icon]", card.getAttribute("data-atlas-desire-icon"));
        setText(root, "[data-atlas-preview-attr-intel]", card.getAttribute("data-atlas-info-intel"));
        setText(root, "[data-atlas-preview-attr-supply]", card.getAttribute("data-atlas-info-supply"));
        setText(root, "[data-atlas-preview-attr-execute]", card.getAttribute("data-atlas-info-execute"));
        setText(root, "[data-atlas-preview-attr-strategy]", card.getAttribute("data-atlas-info-strategy"));
        ["normal", "passive", "ultimate"].forEach(function(slot){
            var name = card.getAttribute("data-atlas-skill-" + slot + "-name") || "—";
            var meta = card.getAttribute("data-atlas-skill-" + slot + "-meta") || "";
            var tags = card.getAttribute("data-atlas-skill-" + slot + "-tags") || "";
            var type = card.getAttribute("data-atlas-skill-" + slot + "-type") || "—";
            var metaParts = [];
            if (tags) metaParts.push(tags);
            if (meta && meta !== type) metaParts.push(meta);
            setText(root, "[data-atlas-preview-skill-" + slot + "-name]", name);
            setText(root, "[data-atlas-preview-skill-" + slot + "-type]", "「" + type + "」");
            setText(root, "[data-atlas-preview-skill-" + slot + "-meta]", metaParts.length ? metaParts.join(" / ") : "—");
            cloneAtlasSkillDesc(root, card, slot);
        });
        renderAtlasPreviewCollectionTerms(root, card);
        setText(root, "[data-atlas-preview-copy]", "");
        var updatePreviewPanel = root.querySelector("[data-atlas-preview-panel]");
        if (updatePreviewPanel) updatePreviewPanel.scrollTop = 0;
        root.__atlasPreviewCard = card;
        var face = card.getAttribute("data-atlas-preview-face") || (card.getAttribute("data-atlas-danger") === "6" ? "2" : "1");
        setAtlasPreviewFace(root, card, face);
    }

    function setAtlasPreviewFace(root, card, face) {
        if (!root || !card) return;
        var rarity = card.getAttribute("data-atlas-danger") || "";
        var normalizedFace = rarity === "6" ? String(face || "2") : "1";
        card.setAttribute("data-atlas-preview-face", normalizedFace);
        var art = root.querySelector("[data-atlas-preview-art]");
        if (art) {
            art.innerHTML = "";
            var source = card.querySelector('[data-atlas-preview-face-source="' + normalizedFace + '"]')
                || card.querySelector('[data-atlas-preview-face-source="1"]');
            if (source) {
                Array.prototype.forEach.call(source.childNodes, function(child){
                    art.appendChild(child.cloneNode(true));
                });
            }
            art.querySelectorAll("img").forEach(function(img){
                img.setAttribute("role", "button");
                img.setAttribute("tabindex", "0");
                img.setAttribute("aria-label", "放大卡面");
                img.setAttribute("data-atlas-preview-lightbox-image", "1");
            });
        }
        var switcher = root.querySelector("[data-atlas-preview-face-switch]");
        if (switcher) {
            switcher.style.display = rarity === "6" ? "flex" : "none";
            switcher.querySelectorAll("[data-atlas-preview-face]").forEach(function(btn){
                btn.classList.toggle("is-active", btn.getAttribute("data-atlas-preview-face") === normalizedFace);
            });
        }
    }

    function atlasPreviewOriginalImageSrc(img) {
        var src = img && (img.currentSrc || img.src || img.getAttribute("src"));
        if (!src) return "";
        try {
            var url = new URL(src, window.location.href);
            var marker = "/images/thumb/";
            var index = url.pathname.indexOf(marker);
            if (index !== -1) {
                var path = url.pathname.slice(0, index) + "/images/" + url.pathname.slice(index + marker.length);
                var parts = path.split("/");
                if (parts.length > 1 && /^\d+px-/.test(parts[parts.length - 1])) parts.pop();
                url.pathname = parts.join("/");
                url.search = "";
                url.hash = "";
                return url.toString();
            }
        } catch (error) {}
        return src;
    }

    function ensureAtlasPreviewLightbox() {
        var box = document.querySelector(".card_lightbox");
        if (box) return box;
        box = document.createElement("div");
        box.className = "card_lightbox";
        box.setAttribute("role", "dialog");
        box.setAttribute("aria-modal", "true");

        var image = document.createElement("img");
        image.className = "card_lightbox-img";
        image.alt = "";
        box.appendChild(image);

        var close = document.createElement("div");
        close.className = "card_lightbox-close";
        close.setAttribute("role", "button");
        close.setAttribute("tabindex", "0");
        close.setAttribute("aria-label", "关闭");
        close.textContent = "×";
        box.appendChild(close);

        function closeLightbox(event) {
            if (event && event.type === "keydown" && event.key !== "Enter" && event.key !== " ") return;
            if (event) event.preventDefault();
            box.classList.remove("is-open");
            document.body.classList.remove("card_lightbox-open");
        }
        close.addEventListener("click", closeLightbox);
        close.addEventListener("keydown", closeLightbox);
        box.addEventListener("click", function(event){
            if (event.target === box) closeLightbox(event);
        });
        document.addEventListener("keydown", function(event){
            if (event.key === "Escape" && box.classList.contains("is-open")) closeLightbox(event);
        });
        document.body.appendChild(box);
        return box;
    }

    function openAtlasPreviewLightbox(img) {
        if (!img) return;
        var box = ensureAtlasPreviewLightbox();
        var image = box.querySelector(".card_lightbox-img");
        if (!image) return;
        image.src = atlasPreviewOriginalImageSrc(img);
        image.alt = img.alt || "";
        document.body.classList.add("card_lightbox-open");
        window.requestAnimationFrame(function(){
            box.classList.add("is-open");
        });
        var close = box.querySelector(".card_lightbox-close");
        if (close && typeof close.focus === "function") close.focus({ preventScroll: true });
    }

    function setPreviewMode(root, enabled) {
        if (root.__atlasPreviewModeTimer) {
            window.clearTimeout(root.__atlasPreviewModeTimer);
            root.__atlasPreviewModeTimer = null;
        }
        root.classList.remove("atlas_frame-preview-closing", "atlas_frame-mode-crossfade");

        var current = root.getAttribute("data-atlas-preview-mode") === "1";
        var initialized = root.hasAttribute("data-atlas-preview-mode");
        var toggle = root.querySelector("[data-atlas-preview-toggle]");
        if (toggle) {
            toggle.classList.toggle("is-active", enabled);
            toggle.setAttribute("aria-checked", enabled ? "true" : "false");
        }
        setText(root, "[data-atlas-preview-mode-label]", enabled ? "预览模式开启" : "点击卡片会在新窗口打开风格页");

        if (!initialized || current === enabled) {
            root.setAttribute("data-atlas-preview-mode", enabled ? "1" : "0");
            clearAtlasPreview(root);
            return;
        }

        root.classList.add("atlas_frame-mode-crossfade");
        root.__atlasPreviewModeTimer = window.setTimeout(function(){
            clearAtlasPreview(root);
            root.setAttribute("data-atlas-preview-mode", enabled ? "1" : "0");
            var layout = root.querySelector(".atlas_frame-layout");
            if (layout) void layout.offsetWidth;
            window.requestAnimationFrame(function(){
                root.classList.remove("atlas_frame-mode-crossfade");
                root.__atlasPreviewModeTimer = null;
            });
        }, 135);
    }
    function activeFilterValues(root, groupName) {
        var group = root.querySelector('[data-atlas-filter-group="' + groupName + '"]');
        if (!group) return [];
        var options = Array.prototype.slice.call(group.querySelectorAll(".atlas_frame-filter-option.is-active"));
        if (options.some(isAllOption)) return [];
        return options.map(function(option){
            var parsed = filterKey(option);
            return parsed && !parsed.all ? parsed.value : null;
        }).filter(Boolean);
    }

    function tagsFor(card) {
        return String(card.getAttribute("data-atlas-tags") || "")
            .split("|")
            .map(function(item){ return item.trim(); })
            .filter(Boolean);
    }

    function applyAtlasFilters(root) {
        var cards = Array.prototype.slice.call(root.querySelectorAll("[data-atlas-card]"));
        var activeStatus = root.querySelector(".atlas_frame-status-item.is-active");
        var status = activeStatus ? activeStatus.getAttribute("data-atlas-status") : "all";
        var danger = activeFilterValues(root, "danger");
        var profession = activeFilterValues(root, "profession");
        var desire = activeFilterValues(root, "desire");
        var character = activeFilterValues(root, "character");
        var acquisition = activeFilterValues(root, "acquisition");
        var selectedTags = Array.prototype.slice.call(root.querySelectorAll(".atlas_frame-tag-option.is-active"))
            .map(function(item){ return item.getAttribute("data-atlas-tag"); })
            .filter(Boolean);
        var searchNode = root.querySelector("[data-atlas-search]");
        var query = normalize(searchValue(searchNode));
        var visibleCount = 0;

        cards.forEach(function(card){
            var visible = true;
            if (status && status !== "all") {
                visible = visible && card.getAttribute("data-atlas-status") === status;
            }
            if (danger.length) {
                visible = visible && danger.indexOf(card.getAttribute("data-atlas-danger")) !== -1;
            }
            if (profession.length) {
                visible = visible && profession.indexOf(card.getAttribute("data-atlas-profession")) !== -1;
            }
            if (desire.length) {
                visible = visible && desire.indexOf(card.getAttribute("data-atlas-desire")) !== -1;
            }
            if (character.length) {
                visible = visible && character.indexOf(card.getAttribute("data-atlas-character")) !== -1;
            }
            if (acquisition.length) {
                visible = visible && acquisition.indexOf(card.getAttribute("data-atlas-acquisition")) !== -1;
            }
            if (selectedTags.length) {
                var cardTags = tagsFor(card);
                visible = visible && selectedTags.every(function(tag){ return cardTags.indexOf(tag) !== -1; });
            }
            if (query) {
                visible = visible && normalize(card.getAttribute("data-atlas-search-text")).indexOf(query) !== -1;
            }
            card.hidden = !visible;
            card.classList.toggle("is-filtered-out", !visible);
            if (visible) visibleCount += 1;
        });

        var empty = root.querySelector("[data-atlas-empty]");
        if (empty) empty.hidden = visibleCount !== 0;
        root.setAttribute("data-atlas-visible-count", String(visibleCount));
    }

    function activateOption(option) {
        if (!option || option.classList.contains("is-disabled")) return;
        var group = option.closest("[data-atlas-filter-group]");
        if (group) {
            var options = Array.prototype.slice.call(group.querySelectorAll(".atlas_frame-filter-option"));
            var allOption = options.find(isAllOption);
            if (isAllOption(option)) {
                options.forEach(function(item){
                    item.classList.toggle("is-active", item === option);
                });
                return;
            }

            option.classList.toggle("is-active");
            if (allOption) allOption.classList.remove("is-active");
            var activeSpecific = options.some(function(item){
                return item !== allOption && item.classList.contains("is-active");
            });
            if (!activeSpecific && allOption) allOption.classList.add("is-active");
            return;
        }
        if (option.classList.contains("atlas_frame-tag-option")) {
            option.classList.toggle("is-active");
        }
    }

    function bindAtlasFrame(root) {
        if (!root || root.getAttribute("data-atlas-frame-bound") === "1") return;
        root.setAttribute("data-atlas-frame-bound", "1");

        var ticking = false;
        var pinned = false;

        function settle() {
            root.classList.remove("atlas_frame-pin-kick");
            void root.offsetWidth;
            root.classList.add("atlas_frame-pin-kick");
        }

        function updatePinned() {
            ticking = false;
            var rect = root.getBoundingClientRect();
            var next = rect.top < -96;
            if (next === pinned) return;
            pinned = next;
            root.classList.toggle("atlas_frame-is-pinned", pinned);
            if (pinned) settle();
        }

        function requestUpdate() {
            if (ticking) return;
            ticking = true;
            window.requestAnimationFrame(updatePinned);
        }

        window.addEventListener("scroll", requestUpdate, { passive: true });
        window.addEventListener("resize", requestUpdate, { passive: true });
        requestUpdate();

        root.querySelectorAll("[data-atlas-tag-toggle]").forEach(function(toggle){
            if (toggle.getAttribute("data-atlas-toggle-bound") === "1") return;
            toggle.setAttribute("data-atlas-toggle-bound", "1");
            function activate() {
                var expanded = root.classList.toggle("atlas_frame-tags-expanded");
                toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
            }
            toggle.addEventListener("click", activate);
            toggle.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                event.preventDefault();
                activate();
            });
        });

        root.querySelectorAll(".atlas_frame-filter-option, .atlas_frame-tag-option").forEach(function(option){
            if (option.getAttribute("data-atlas-option-bound") === "1") return;
            option.setAttribute("data-atlas-option-bound", "1");
            option.addEventListener("click", function(){
                activateOption(option);
                applyAtlasFilters(root);
            });
            option.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                event.preventDefault();
                activateOption(option);
                applyAtlasFilters(root);
            });
        });

        root.querySelectorAll(".atlas_frame-status-item").forEach(function(item){
            if (item.getAttribute("data-atlas-status-bound") === "1") return;
            item.setAttribute("data-atlas-status-bound", "1");
            function activateStatus() {
                root.querySelectorAll(".atlas_frame-status-item").forEach(function(other){
                    other.classList.toggle("is-active", other === item);
                });
                applyAtlasFilters(root);
            }
            item.addEventListener("click", activateStatus);
            item.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                event.preventDefault();
                activateStatus();
            });
        });

        var search = root.querySelector("[data-atlas-search]");
        if (search && search.getAttribute("data-atlas-search-bound") !== "1") {
            search.setAttribute("data-atlas-search-bound", "1");
            if (!search.getAttribute("contenteditable")) {
                search.setAttribute("contenteditable", "true");
            }
            search.setAttribute("spellcheck", "false");
            search.addEventListener("input", function(){ applyAtlasFilters(root); });
            search.addEventListener("search", function(){ applyAtlasFilters(root); });
            search.addEventListener("keydown", function(event){
                if (event.key === "Enter") event.preventDefault();
            });
        }

        var previewArt = root.querySelector("[data-atlas-preview-art]");
        if (previewArt && previewArt.getAttribute("data-atlas-preview-lightbox-bound") !== "1") {
            previewArt.setAttribute("data-atlas-preview-lightbox-bound", "1");
            previewArt.addEventListener("click", function(event){
                var img = event.target && event.target.closest ? event.target.closest("[data-atlas-preview-lightbox-image]") : null;
                if (!img || !previewArt.contains(img)) return;
                event.preventDefault();
                event.stopPropagation();
                openAtlasPreviewLightbox(img);
            });
            previewArt.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                var img = event.target && event.target.closest ? event.target.closest("[data-atlas-preview-lightbox-image]") : null;
                if (!img || !previewArt.contains(img)) return;
                event.preventDefault();
                openAtlasPreviewLightbox(img);
            });
        }


        var previewToggle = root.querySelector("[data-atlas-preview-toggle]");
        if (previewToggle && previewToggle.getAttribute("data-atlas-preview-toggle-bound") !== "1") {
            previewToggle.setAttribute("data-atlas-preview-toggle-bound", "1");
            function togglePreviewMode() {
                setPreviewMode(root, !previewModeEnabled(root));
            }
            previewToggle.addEventListener("click", togglePreviewMode);
            previewToggle.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                event.preventDefault();
                togglePreviewMode();
            });
            setPreviewMode(root, false);
        }

        root.querySelectorAll("[data-atlas-card]").forEach(function(card){
            if (card.getAttribute("data-atlas-card-bound") === "1") return;
            card.setAttribute("data-atlas-card-bound", "1");
            function activateCard(event) {
                if (event) {
                    event.preventDefault();
                    event.stopPropagation();
                }
                if (previewModeEnabled(root)) {
                    if (root.__atlasPreviewCard === card) {
                        clearAtlasPreview(root);
                    } else {
                        updateAtlasPreview(root, card);
                    }
                    return;
                }
                var title = card.getAttribute("data-atlas-page-title") || card.getAttribute("data-atlas-style-name");
                if (title) openTitleInNewWindow(title);
            }
            card.addEventListener("click", activateCard);
            card.addEventListener("keydown", function(event){
                if (event.key !== "Enter" && event.key !== " ") return;
                activateCard(event);
            });
        });

        applyAtlasFilters(root);
    }

    mw.hook("wikipage.content").add(function($content){
        var container = $content && $content[0] ? $content[0] : document;
        container.querySelectorAll("[data-atlas-frame-sandbox]").forEach(bindAtlasFrame);
    });
}());
/* /atlas filter sandbox sticky settle */

/* Homepage today clock. */
(function(){
    function pad(value) {
        return String(value).padStart(2, "0");
    }

    function greeting(date) {
        var hour = date.getHours();
        if (hour < 5) return "夜深了";
        if (hour < 11) return "早上好";
        if (hour < 14) return "中午好";
        if (hour < 18) return "下午好";
        return "晚上好";
    }

    function updateToday(root) {
        var now = new Date();
        var greetingNode = root.querySelector("[data-wiki-home-greeting]");
        var month = root.querySelector("[data-wiki-home-month]");
        var day = root.querySelector("[data-wiki-home-day]");
        var dateNode = root.querySelector("[data-wiki-home-date]");
        var year = root.querySelector("[data-wiki-home-year]");
        var time = root.querySelector("[data-wiki-home-time]");
        var monthValue = now.getMonth() + 1;
        var dayValue = now.getDate();
        if (greetingNode) greetingNode.textContent = greeting(now);
        if (month) month.textContent = monthValue + "月";
        if (day) day.textContent = pad(dayValue);
        if (dateNode) dateNode.textContent = monthValue + "月" + pad(dayValue) + "日";
        if (year) year.textContent = String(now.getFullYear());
        if (time) time.textContent = pad(now.getHours()) + ":" + pad(now.getMinutes());
    }

    function bindToday(root) {
        if (!root || root.getAttribute("data-wiki-home-today-bound") === "1") return;
        root.setAttribute("data-wiki-home-today-bound", "1");
        updateToday(root);
        window.setInterval(function(){ updateToday(root); }, 1000);
    }

    mw.hook("wikipage.content").add(function($content){
        var container = $content && $content[0] ? $content[0] : document;
        container.querySelectorAll("[data-wiki-home-today]").forEach(bindToday);
    });
}());
/* /Homepage today clock. */




/* Card weapon attack popovers */
(function(){
    function closeAll(exceptPanel) {
        document.querySelectorAll(".card_weapon-popover.is-open").forEach(function(panel){
            if (exceptPanel && panel === exceptPanel) return;
            panel.classList.remove("is-open");
            panel.setAttribute("aria-hidden", "true");
            var id = panel.getAttribute("data-card-weapon-popover-panel");
            if (!id) return;
            document.querySelectorAll('[data-card-weapon-popover="' + CSS.escape(id) + '"]').forEach(function(trigger){
                trigger.setAttribute("aria-expanded", "false");
                var root = trigger.closest(".card_weapon-popover-root");
                if (root) root.classList.remove("is-open");
            });
        });
    }

    function bindCardWeaponPopovers(container) {
        container.querySelectorAll("[data-card-weapon-popover]").forEach(function(trigger){
            if (trigger.getAttribute("data-card-weapon-bound") === "1") return;
            trigger.setAttribute("data-card-weapon-bound", "1");
            var activate = function(event){
                if (event) {
                    event.preventDefault();
                    event.stopPropagation();
                }
                var id = trigger.getAttribute("data-card-weapon-popover");
                if (!id) return;
                var root = trigger.closest(".card_weapon-popover-root") || document;
                var panel = root.querySelector('[data-card-weapon-popover-panel="' + CSS.escape(id) + '"]');
                if (!panel) return;
                var willOpen = !panel.classList.contains("is-open");
                closeAll(panel);
                panel.classList.toggle("is-open", willOpen);
                panel.setAttribute("aria-hidden", willOpen ? "false" : "true");
                root.classList.toggle("is-open", willOpen);
                root.querySelectorAll('[data-card-weapon-popover="' + CSS.escape(id) + '"]').forEach(function(item){
                    item.setAttribute("aria-expanded", willOpen ? "true" : "false");
                });
            };
            trigger.addEventListener("click", activate);
            trigger.addEventListener("keydown", function(event){
                if (event.key === "Enter" || event.key === " ") activate(event);
                if (event.key === "Escape") closeAll();
            });
        });
    }

    document.addEventListener("click", function(event){
        if (event.target.closest && event.target.closest(".card_weapon-popover, [data-card-weapon-popover]")) return;
        closeAll();
    });

    document.addEventListener("keydown", function(event){
        if (event.key === "Escape") closeAll();
    });

    mw.hook("wikipage.content").add(function($content){
        bindCardWeaponPopovers($content && $content[0] ? $content[0] : document);
    });
}());

/* Homepage style entry target. */
(function(){
    function bindHomepageStyleTarget(container) {
        var root = container.querySelector ? container : document;
        root.querySelectorAll(".wiki_home-style-card a").forEach(function(link){
            if (link.getAttribute("data-wiki-home-style-target-bound") === "1") return;
            link.setAttribute("data-wiki-home-style-target-bound", "1");
            link.setAttribute("target", "_blank");
            link.setAttribute("rel", "noopener noreferrer");
        });
    }

    mw.hook("wikipage.content").add(function($content){
        bindHomepageStyleTarget($content && $content[0] ? $content[0] : document);
    });
}());
/* /Homepage style entry target. */

/* Character profile avatar/full-body art toggle. */
(function(){
    function setMode(root, mode) {
        root.setAttribute("data-character-profile-art-mode", mode);
        root.querySelectorAll("[data-character-profile-art-toggle]").forEach(function(toggle){
            toggle.setAttribute("aria-pressed", mode === "figure" ? "true" : "false");
            toggle.setAttribute("title", mode === "figure" ? "返回头像档案" : "切换为立绘欣赏");
            toggle.setAttribute("aria-label", mode === "figure" ? "返回头像档案" : "切换为立绘欣赏");
        });
    }

    function bindCharacterProfileArtToggle(container) {
        var rootNode = container && container.querySelector ? container : document;
        rootNode.querySelectorAll("[data-character-profile-art-toggle]").forEach(function(toggle){
            if (toggle.getAttribute("data-character-profile-art-toggle-bound") === "1") return;
            toggle.setAttribute("data-character-profile-art-toggle-bound", "1");
            var activate = function(event){
                if (event) {
                    event.preventDefault();
                    event.stopPropagation();
                }
                var root = toggle.closest(".character_profile-root");
                if (!root) return;
                var current = root.getAttribute("data-character-profile-art-mode") || "avatar";
                setMode(root, current === "figure" ? "avatar" : "figure");
            };
            toggle.addEventListener("click", activate);
            toggle.addEventListener("keydown", function(event){
                if (event.key === "Enter" || event.key === " ") activate(event);
            });
        });
    }

    mw.hook("wikipage.content").add(function($content){
        bindCharacterProfileArtToggle($content && $content[0] ? $content[0] : document);
    });
}());
/* /Character profile avatar/full-body art toggle. */

/* character atlas wheel handoff */
(function () {
    function onReady(fn) {
        if (document.readyState === "loading") {
            document.addEventListener("DOMContentLoaded", fn, { once: true });
        } else {
            fn();
        }
    }

    function normalizedDeltaY(event) {
        var delta = event.deltaY || 0;
        if (event.deltaMode === 1) return delta * 16;
        if (event.deltaMode === 2) return delta * window.innerHeight;
        return delta;
    }

    function canScrollGrid(grid, deltaY) {
        if (!grid || !deltaY) return false;
        if (deltaY > 0) return grid.scrollTop + grid.clientHeight < grid.scrollHeight - 1;
        return grid.scrollTop > 0;
    }

    function atlasSurfaceIsSticky(surface) {
        if (!surface) return false;
        var rect = surface.getBoundingClientRect();
        var top = parseFloat(window.getComputedStyle(surface).top) || 0;
        return rect.top <= top + 2 && rect.bottom > top + 120;
    }

    function visibleAtlasRoot() {
        var roots = document.querySelectorAll(".character_atlas-root");
        for (var i = 0; i < roots.length; i += 1) {
            var rect = roots[i].getBoundingClientRect();
            if (rect.top < window.innerHeight && rect.bottom > 0) return roots[i];
        }
        return null;
    }

    onReady(function () {
        if (document.documentElement.dataset.characterAtlasWheelHandoff === "1") return;
        if (!document.querySelector(".character_atlas-root")) return;
        document.documentElement.dataset.characterAtlasWheelHandoff = "1";

        document.addEventListener("wheel", function (event) {
            if (event.defaultPrevented || event.ctrlKey || event.metaKey) return;
            if (event.target && event.target.closest && event.target.closest(".rn-card-lightbox.is-open")) return;

            var root = visibleAtlasRoot();
            if (!root) return;

            var surface = root.querySelector(".atlas_frame-surface");
            var grid = root.querySelector(".character_atlas-grid");
            if (!atlasSurfaceIsSticky(surface)) return;

            var deltaY = normalizedDeltaY(event);
            if (!canScrollGrid(grid, deltaY)) return;

            event.preventDefault();
            grid.scrollTop += deltaY;
        }, { passive: false });
    });
})();
/* /character atlas wheel handoff */

(function(){
    function setExpanded(root, expanded){
        var toggle = root.querySelector("[data-style-collection-toggle]");
        if (!toggle) return;
        root.setAttribute("data-style-collection-expanded", expanded ? "true" : "false");
        toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
        var text = toggle.querySelector(".style_collection-toggle-text");
        var icon = toggle.querySelector(".style_collection-toggle-icon");
        if (text) text.textContent = expanded ? "收起映像详情" : "展开映像详情";
        if (icon) icon.textContent = expanded ? "-" : "+";
    }
    document.addEventListener("click", function(event){
        var toggle = event.target.closest("[data-style-collection-toggle]");
        if (!toggle) return;
        var root = toggle.closest("[data-style-collection]");
        if (!root) return;
        setExpanded(root, root.getAttribute("data-style-collection-expanded") !== "true");
    });
    document.addEventListener("keydown", function(event){
        if (event.key !== "Enter" && event.key !== " ") return;
        var toggle = event.target.closest("[data-style-collection-toggle]");
        if (!toggle) return;
        event.preventDefault();
        var root = toggle.closest("[data-style-collection]");
        if (!root) return;
        setExpanded(root, root.getAttribute("data-style-collection-expanded") !== "true");
    });
})();


/* Style page left column hover scroll. */
(function(){
    function normalizedDeltaY(event) {
        var delta = event.deltaY || 0;
        if (event.deltaMode === 1) return delta * 16;
        if (event.deltaMode === 2) return delta * window.innerHeight;
        return delta;
    }

    function desktopStyleLayout() {
        if (!window.matchMedia) return window.innerWidth >= 1400;
        return window.matchMedia("(min-width: 1400px)").matches;
    }

    function canScroll(node, deltaY) {
        if (!node || !deltaY) return false;
        if (node.scrollHeight <= node.clientHeight + 1) return false;
        if (deltaY < 0) return node.scrollTop > 0;
        if (deltaY > 0) return node.scrollTop + node.clientHeight < node.scrollHeight - 1;
        return false;
    }

    function bindStyleLeftScroll(container) {
        container.querySelectorAll(".ron-card .card_content_left").forEach(function(left){
            if (left.getAttribute("data-style-left-scroll-bound") === "1") return;
            left.setAttribute("data-style-left-scroll-bound", "1");
            left.addEventListener("wheel", function(event){
                if (!desktopStyleLayout()) return;
                var deltaY = normalizedDeltaY(event);
                if (!canScroll(left, deltaY)) return;
                event.preventDefault();
                left.scrollTop += deltaY;
            }, { passive: false });
        });
    }

    mw.hook("wikipage.content").add(function($content){
        bindStyleLeftScroll($content && $content[0] ? $content[0] : document);
    });
}());

/* BEGIN ATLAS_PREVIEW_INTERNAL_SCROLL */
(function(){
    function normalizedDeltaY(event) {
        var delta = event.deltaY || 0;
        if (event.deltaMode === 1) return delta * 16;
        if (event.deltaMode === 2) return delta * window.innerHeight;
        return delta;
    }

    function canScroll(node, deltaY) {
        if (!node || !deltaY) return false;
        if (node.scrollHeight <= node.clientHeight + 1) return false;
        if (deltaY < 0) return node.scrollTop > 0;
        if (deltaY > 0) return node.scrollTop + node.clientHeight < node.scrollHeight - 1;
        return false;
    }

    function bindAtlasPreviewScroll(container) {
        container.querySelectorAll(".atlas_frame-root .atlas_frame-preview").forEach(function(preview){
            if (preview.getAttribute("data-atlas-preview-scroll-bound") === "1") return;
            preview.setAttribute("data-atlas-preview-scroll-bound", "1");
            preview.addEventListener("wheel", function(event){
                if (event.ctrlKey || event.metaKey) return;
                var deltaY = normalizedDeltaY(event);
                if (!canScroll(preview, deltaY)) return;
                event.preventDefault();
                preview.scrollTop += deltaY;
            }, { passive: false });
        });
    }

    mw.hook("wikipage.content").add(function($content){
        bindAtlasPreviewScroll($content && $content[0] ? $content[0] : document);
    });
}());
/* END ATLAS_PREVIEW_INTERNAL_SCROLL */