미디어위키:Gadget-core.js: 두 판 사이의 차이

식물 vs 좀비 위키
새 문서: ========================= MediaWiki:Gadget-core.js =========================: (function () { 'use strict'; document.documentElement.classList.add('pvz-desktop-booting'); var PVZ_SEARCH_TIMER = null; var PVZ_SEARCH_REQUEST_ID = 0; var PVZ_SELECTED_SUGGESTION = -1; var PVZ_SUGGESTIONS = []; var PVZ_LAST_SEARCH_QUERY = ''; function pvzReady(callback) { function run() { if (window.mw && mw.loader && mw.loader.using) { mw.loader.using('media...
 
편집 요약 없음
태그: 모바일 편집 모바일 웹 편집
1번째 줄: 1번째 줄:
/* ===== core-js.txt ===== */
/* =========================
/* =========================
MediaWiki:Gadget-core.js
MediaWiki:Gadget-core.js
2,159번째 줄: 2,160번째 줄:


   pvzReady(pvzInit);
   pvzReady(pvzInit);
}());
/* ===== mobile-js.txt ===== */
/* =========================
MediaWiki:Gadget-mobile.js
Timeless 모바일 알림·리본·본문 프레임 재정리 v125
- v124 기준
- 검색바 1280px 이상 판정을 visualViewport가 아닌 레이아웃 viewport 기준으로 변경
- 아이패드 가로 화면 핀치 확대 시 검색바가 100% 폭으로 풀리는 문제 수정
- 검색바 왼쪽 돋보기 아이콘 제거
- 검색 문구를 '위키에서 검색하세요'로 변경
- 검색 제안에서 입력 문자열과 일치하는 부분만 굵게 강조
- MediaWiki API 기반 검색 제안과 문서 대표 이미지 표시 유지
- 한글 IME 입력·키보드 탐색·바깥 클릭 닫기 대응 유지
- 화면 회전·리사이즈·본문 폭 변화 시 검색바 폭 자동 재계산
- 사용자 환경 기반 초기 테마 적용 유지
- Timeless 상단 패널 absolute 문서 좌표 방식 유지
- 모바일 목차 .mf-section-0 대응 유지
- footer 아래 실제 여백 감지 기능 유지
========================= */
(function () {
  'use strict';
  document.documentElement.classList.add('pvz-timeless-mobile-booting');
  function pvzArray(list) {
    return Array.prototype.slice.call(list || []);
  }
  function pvzIsTimeless() {
    var skinName = '';
    if (window.mw && mw.config && typeof mw.config.get === 'function') {
      skinName = mw.config.get('skin') || mw.config.get('wgDefaultSkin') || '';
    }
    return skinName === 'timeless' ||
      (document.body && document.body.classList && document.body.classList.contains('skin-timeless'));
  }
  function pvzMarkReady() {
    document.documentElement.classList.add('pvz-timeless-mobile-ready');
      document.documentElement.classList.add('pvz-timeless-mobile-booted');
    if (document.body) {
      document.body.classList.add('pvz-timeless-mobile-ready');
    }
  }
  function pvzSkipHeading(heading) {
    if (!heading) return true;
    if (heading.closest && heading.closest('.pvz-custom-toc, .toc, #toc, .navbox, .metadata, .catlinks, .mw-references-wrap')) return true;
    if (heading.classList && heading.classList.contains('pvz-custom-toc-title')) return true;
    return !(heading.textContent || '').replace(/\s+/g, ' ').trim();
  }
  function pvzHeadingText(heading) {
    var clone = heading.cloneNode(true);
    var text;
    clone.querySelectorAll('.mw-editsection, .mw-headline-anchor, .reference, sup').forEach(function (node) {
      node.remove();
    });
    text = (clone.textContent || '').replace(/\s+/g, ' ').trim();
    return text || '문단';
  }
  function pvzHeadingId(heading, text, index) {
    var headline = heading.querySelector && heading.querySelector('.mw-headline');
    var anchor = headline || heading;
    var id = anchor.id;
    if (!id) {
      id = 'pvz-heading-' + index + '-' + encodeURIComponent(text).replace(/%/g, '');
      anchor.id = id;
    }
    return id;
  }
  function pvzNormalizeText(text) {
    return (text || '').replace(/\s+/g, ' ').trim();
  }
  function pvzLinkKey(link) {
    var href = link && link.getAttribute ? link.getAttribute('href') || '' : '';
    var text = pvzNormalizeText(link ? link.textContent : '');
    return href + '|' + text;
  }
  function pvzFindExistingLink(config) {
    var links = pvzArray(document.querySelectorAll('a[href]'));
    var idPatterns = config.ids || [];
    var textPatterns = config.text || [];
    var hrefPatterns = config.href || [];
    var i;
    var link;
    var href;
    var text;
    var id;
    for (i = 0; i < links.length; i += 1) {
      link = links[i];
      id = link.id || (link.parentNode && link.parentNode.id) || '';
      if (id && idPatterns.some(function (pattern) {
        return pattern.test(id);
      })) {
        return link;
      }
    }
    for (i = 0; i < links.length; i += 1) {
      link = links[i];
      href = link.getAttribute('href') || '';
      if (href && hrefPatterns.some(function (pattern) {
        return pattern.test(href);
      })) {
        return link;
      }
    }
    for (i = 0; i < links.length; i += 1) {
      link = links[i];
      text = pvzNormalizeText(link.textContent);
      if (text && textPatterns.some(function (pattern) {
        return pattern.test(text);
      })) {
        return link;
      }
    }
    return null;
  }
  function pvzMakeFallbackUrl(type) {
    var pageName = '';
    var title = '';
    if (window.mw && mw.config && typeof mw.config.get === 'function') {
      pageName = mw.config.get('wgPageName') || '';
      title = mw.config.get('wgTitle') || pageName || '';
    }
    if (!window.mw || !mw.util || typeof mw.util.getUrl !== 'function') {
      return '';
    }
    if (type === 'upload') return mw.util.getUrl('Special:Upload');
    if (type === 'shorturl') return mw.util.getUrl('Special:UrlShortener');
    if (type === 'whatlinkshere') return mw.util.getUrl('Special:WhatLinksHere/' + pageName);
    if (type === 'recentchangeslinked') return mw.util.getUrl('Special:RecentChangesLinked/' + pageName);
    if (type === 'printable') return mw.util.getUrl(pageName, { printable: 'yes' });
    if (type === 'permalink') {
      if (mw.config.get('wgRevisionId')) {
        return mw.util.getUrl(pageName, { oldid: mw.config.get('wgRevisionId') });
      }
      return '';
    }
    if (type === 'info') return mw.util.getUrl(pageName, { action: 'info' });
    if (type === 'history') return mw.util.getUrl(pageName, { action: 'history' });
    /* 권한성 문서 도구는 기본 링크가 실제로 존재할 때만 노출한다. */
    if (type === 'move') return '';
    if (type === 'delete') return '';
    if (type === 'protect') return '';
    return title ? mw.util.getUrl(title) : '';
  }
  function pvzToolEntry(label, type, matchConfig) {
    var found = pvzFindExistingLink(matchConfig || {});
    var href = found ? found.getAttribute('href') : pvzMakeFallbackUrl(type);
    if (!href) return null;
    return {
      label: label,
      href: href,
      source: found || null
    };
  }
  function pvzMakeSpecialUrl(specialName) {
    if (window.mw && mw.util && typeof mw.util.getUrl === 'function') {
      return mw.util.getUrl('Special:' + specialName);
    }
    return '/wiki/Special:' + specialName;
  }
  function pvzMakePageUrl(pageName) {
    if (window.mw && mw.util && typeof mw.util.getUrl === 'function') {
      return mw.util.getUrl(pageName);
    }
    return '/wiki/' + String(pageName || '').replace(/\s/g, '_');
  }
  function pvzPageName() {
    if (window.mw && mw.config && typeof mw.config.get === 'function') {
      return mw.config.get('wgPageName') || '';
    }
    return '';
  }
  function pvzMainPageName() {
    if (window.mw && mw.config && typeof mw.config.get === 'function') {
      return mw.config.get('wgMainPageTitle') || '식물 vs 좀비 위키';
    }
    return '식물 vs 좀비 위키';
  }
  function pvzToolEntryWithFallback(label, type, matchConfig, fallbackHref) {
    var found = pvzFindExistingLink(matchConfig || {});
    var href = found ? found.getAttribute('href') : fallbackHref;
    if (!href) return null;
    return {
      label: label,
      href: href,
      source: found || null
    };
  }
  function pvzToolEntryFromNativeOnly(label, matchConfig) {
    var found = pvzFindExistingLink(matchConfig || {});
    if (!found) return null;
    return {
      label: label,
      href: found.getAttribute('href'),
      source: found
    };
  }
  function pvzCreateToolsSvgIcon() {
    var span = document.createElement('span');
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    var path = document.createElementNS(svgNS, 'path');
    span.className = 'pvz-custom-mobile-tools-icon';
    span.setAttribute('aria-hidden', 'true');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('focusable', 'false');
    path.setAttribute('d', 'M21.2 18.6 13.9 11.3c.7-1.9.3-4.1-1.2-5.6-1.6-1.6-4-2-6-1.1l3.5 3.5-2.1 2.1-3.6-3.5c-.8 2-.4 4.4 1.2 6 1.5 1.5 3.7 1.9 5.6 1.2l7.3 7.3c.3.3.8.3 1.1 0l1.5-1.5c.3-.3.3-.8 0-1.1Z');
    svg.appendChild(path);
    span.appendChild(svg);
    return span;
  }
  function pvzCreateToolsToggle(shell) {
    var button = document.createElement('button');
    var text = document.createElement('span');
    button.type = 'button';
    button.className = 'pvz-custom-mobile-tools-toggle';
    button.setAttribute('aria-controls', 'pvz-custom-mobile-tools-list');
    button.setAttribute('aria-expanded', 'false');
    button.title = '도구';
    text.className = 'pvz-custom-mobile-tools-toggle-text';
    text.textContent = '도구';
    button.appendChild(pvzCreateToolsSvgIcon());
    button.appendChild(text);
    button.addEventListener('click', function (event) {
      var collapsed;
      event.preventDefault();
      collapsed = !shell.classList.contains('is-collapsed');
      shell.classList.toggle('is-collapsed', collapsed);
      shell.dataset.pvzManualToggle = '1';
      button.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
    });
    return button;
  }
  function pvzApplyToolsCompactState(shell) {
    var button;
    var isCompact;
    if (!shell) return;
    button = shell.querySelector('.pvz-custom-mobile-tools-toggle');
    isCompact = !!(window.matchMedia && window.matchMedia('(max-width: 760px)').matches);
    /* 모바일에서도 보조창은 기본적으로 보이게 둔다.
      사용자가 직접 도구 버튼을 눌렀을 때만 접힌 상태를 유지한다. */
    if (!isCompact || shell.dataset.pvzManualToggle !== '1') {
      shell.classList.remove('is-collapsed');
    }
    if (button) {
      button.setAttribute('aria-expanded', shell.classList.contains('is-collapsed') ? 'false' : 'true');
    }
  }
  function pvzFindLogoBlockInside(anchor) {
    var candidates;
    var i;
    var node;
    if (!anchor || !anchor.querySelectorAll) return null;
    candidates = pvzArray(anchor.querySelectorAll(
      '#p-logo, .mw-logo, .mw-wiki-logo, #mw-wiki-logo, ' +
      'a.mw-wiki-logo, a[title*="식물 vs 좀비 위키"], a[aria-label*="식물 vs 좀비 위키"], ' +
      'a[href*="식물_vs_좀비_위키"], a[href*="Main_Page"]'
    ));
    for (i = 0; i < candidates.length; i += 1) {
      node = candidates[i];
      while (node && node.parentNode && node.parentNode !== anchor) {
        if (
          node.matches &&
          node.matches('#p-logo, .mw-logo, .mw-wiki-logo, #mw-wiki-logo, .mw-portlet, nav, header, div, a')
        ) {
          if (node.parentNode === anchor) return node;
        }
        node = node.parentNode;
      }
      if (node && node.parentNode === anchor) return node;
    }
    return null;
  }
  function pvzInsertToolsBelowLogo(anchor, shell) {
    var logoBlock;
    if (!anchor || !shell) return;
    logoBlock = pvzFindLogoBlockInside(anchor);
    if (logoBlock && logoBlock.parentNode === anchor) {
      if (logoBlock.nextSibling) {
        anchor.insertBefore(shell, logoBlock.nextSibling);
      } else {
        anchor.appendChild(shell);
      }
      return;
    }
    anchor.appendChild(shell);
  }
  function pvzCreateToolsSection(title, entries, sectionKey) {
    var section;
    var heading;
    var list;
    entries = (entries || []).filter(Boolean);
    if (!entries.length) return null;
    section = document.createElement('section');
    section.className = 'pvz-custom-mobile-tools-section';
    if (sectionKey) section.setAttribute('data-pvz-tools-section', sectionKey);
    heading = document.createElement('h2');
    heading.className = 'pvz-custom-mobile-tools-heading';
    heading.textContent = title;
    list = document.createElement('ul');
    list.className = 'pvz-custom-mobile-tools-list';
    entries.forEach(function (entry) {
      var item = document.createElement('li');
      var link = document.createElement('a');
      var text;
      item.className = 'pvz-custom-mobile-tools-item';
      link.href = entry.href || '#';
      if (entry.className) link.className = entry.className;
      if (entry.themeMode) {
        link.setAttribute('data-pvz-timeless-theme-mode', entry.themeMode);
        link.setAttribute('role', 'button');
      }
      if (entry.icon) {
        link.appendChild(pvzCreateInlineSvgIcon(entry.icon));
        text = document.createElement('span');
        text.textContent = entry.label;
        link.appendChild(text);
      } else {
        link.textContent = entry.label;
      }
      if (entry.source) {
        if (entry.source.title) link.title = entry.source.title;
        if (entry.source.target) link.target = entry.source.target;
      }
      item.appendChild(link);
      list.appendChild(item);
    });
    section.appendChild(heading);
    section.appendChild(list);
    return section;
  }
  function pvzFindTimelessToolsAnchor() {
    var selectors = [
      '#mw-site-navigation',
      '#mw-related-navigation',
      '#mw-content',
      '#content',
      '#bodyContent',
      '.mw-body'
    ];
    var i;
    var node;
    for (i = 0; i < selectors.length; i += 1) {
      node = document.querySelector('body.skin-timeless ' + selectors[i]) ||
        document.querySelector(selectors[i]);
      if (node) return node;
    }
    return document.body;
  }
  function pvzHideNativeTimelessToolPortlets() {
    var candidates = pvzArray(document.querySelectorAll(
      'body.skin-timeless #mw-site-navigation .mw-portlet, ' +
      'body.skin-timeless #mw-related-navigation .mw-portlet, ' +
      'body.skin-timeless #mw-site-navigation [id^="p-"], ' +
      'body.skin-timeless #mw-related-navigation [id^="p-"], ' +
      'body.skin-timeless nav[id^="p-"], ' +
      'body.skin-timeless div[id^="p-"], ' +
      'body.skin-timeless #catlinks, ' +
      'body.skin-timeless .catlinks'
    ));
    candidates.forEach(function (node) {
      var text;
      var id;
      if (!node || !node.classList) return;
      if (node.classList.contains('pvz-custom-mobile-tools') || node.classList.contains('pvz-custom-mobile-tools-shell') || node.classList.contains('pvz-timeless-wide-rail')) return;
      if (node.id === 'p-logo') return;
      if (node.closest && node.closest('.pvz-custom-mobile-tools-shell')) return;
      text = pvzNormalizeText(node.textContent);
      id = node.id || '';
      if (
        /p-(navigation|namespaces|views|variants|personal|tb|wiki-tools|wikitools|page-tools|actions|cactions|more|tb-page|tb-more|managewiki|management|admin)/i.test(id) ||
        /둘러보기|Navigation|관리|Manage|위키 도구|Wiki tools|Site tools|문서 도구|Page tools|Document tools|더 보기|More/.test(text) ||
        /대문|최근 바뀜|임의 문서|미디어위키 도움말|특수 문서 목록|Main page|Recent changes|Random page|Help|Special pages/.test(text) ||
        /이 위키의 핵심 설정|이 위키의 확장 기능|이 위키의 이름공간|이 위키의 권한|이 위키의 추가 설정|이 위키의 백업/.test(text) ||
        (/파일 올리기|Upload file/.test(text) && /축약된 URL|Short URL|Get shortened URL/.test(text)) ||
        (/삭제|Delete/.test(text) && /이동|Move/.test(text) && /보호|Protect/.test(text)) ||
        (/여기를 가리키는 문서|What links here/.test(text) && /문서 정보|Page information/.test(text))
      ) {
        node.classList.add('pvz-native-tools-hidden');
      }
    });
    pvzArray(document.querySelectorAll(
      'body.skin-timeless #mw-site-navigation, ' +
      'body.skin-timeless #mw-related-navigation'
    )).forEach(function (container) {
      var hasVisibleNonCustom = false;
      pvzArray(container.children).forEach(function (child) {
        if (!child || !child.classList) return;
        if (child.classList.contains('pvz-custom-mobile-tools-shell') || child.classList.contains('pvz-timeless-wide-rail')) return;
        if (child.id === 'p-logo' || child.matches('.mw-logo, .mw-wiki-logo, #mw-wiki-logo, header')) return;
        if (!child.classList.contains('pvz-native-tools-hidden')) {
          if ((child.textContent || '').replace(/\s+/g, '').length > 0) {
            hasVisibleNonCustom = true;
          }
        }
      });
      if (!hasVisibleNonCustom) {
        container.classList.add('pvz-native-tools-container-cleaned');
      }
    });
  }
  function pvzRemoveTimelessColorStripes() {
    pvzArray(document.querySelectorAll(
      'body.skin-timeless #mw-header-hack, ' +
      'body.skin-timeless .color-bar, ' +
      'body.skin-timeless .header-color-bar, ' +
      'body.skin-timeless .mw-header-color-bar, ' +
      'body.skin-timeless #mw-header-hack .color-left, ' +
      'body.skin-timeless #mw-header-hack .color-middle, ' +
      'body.skin-timeless #mw-header-hack .color-right, ' +
      'body.skin-timeless #mw-header-container .color-left, ' +
      'body.skin-timeless #mw-header-container .color-middle, ' +
      'body.skin-timeless #mw-header-container .color-right, ' +
      'body.skin-timeless #mw-content-container .color-left, ' +
      'body.skin-timeless #mw-content-container .color-middle, ' +
      'body.skin-timeless #mw-content-container .color-right, ' +
      'body.skin-timeless [class~="color-left"], ' +
      'body.skin-timeless [class~="color-middle"], ' +
      'body.skin-timeless [class~="color-right"], ' +
      'body.skin-timeless [class*="color-left"], ' +
      'body.skin-timeless [class*="color-middle"], ' +
      'body.skin-timeless [class*="color-right"]'
    )).forEach(function (node) {
      if (node && node.parentNode) node.parentNode.removeChild(node);
    });
  }
  function pvzCategoryEntriesForCustomBox() {
    var entries = [];
    var seen = {};
    pvzArray(document.querySelectorAll('#catlinks a[href], .catlinks a[href]')).forEach(function (link) {
      var label = pvzNormalizeText(link.textContent);
      var href = link.getAttribute('href');
      var key;
      if (!label || !href) return;
      if (/^(분류|Categories?|Category)$/i.test(label)) return;
      key = href + '|' + label;
      if (seen[key]) return;
      seen[key] = true;
      entries.push({
        label: label,
        href: href,
        source: link
      });
    });
    return entries;
  }
  /* =========================
  커스텀 검색바 시작
  - 기존 Timeless/MediaWiki 헤더 검색창 대신 독립 검색 UI를 사용한다.
  - 실제 검색은 Special:Search로 제출한다.
  - 검색 제안은 MediaWiki API opensearch를 사용한다.
  - 1280px 이상에서는 실제 #mw-content의 위치와 폭을 그대로 따라간다.
  ========================= */
  var pvzCustomSearchInputTimer = null;
  var pvzCustomSearchRequestId = 0;
  var pvzCustomSearchActiveIndex = -1;
  var pvzCustomSearchComposing = false;
  var pvzCustomSearchEventsBound = false;
  var pvzCustomSearchViewportBound = false;
  var pvzCustomSearchResizeObserver = null;
  var pvzCustomSearchWidthFrame = 0;
  var pvzSearchThumbnailCache = Object.create(null);
  function pvzCreateSearchSvgIcon() {
    var span = document.createElement('span');
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    var circle = document.createElementNS(svgNS, 'circle');
    var line = document.createElementNS(svgNS, 'path');
    span.className = 'pvz-custom-search-icon';
    span.setAttribute('aria-hidden', 'true');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('focusable', 'false');
    circle.setAttribute('cx', '10.8');
    circle.setAttribute('cy', '10.8');
    circle.setAttribute('r', '6.6');
    circle.setAttribute('fill', 'none');
    circle.setAttribute('stroke', 'currentColor');
    circle.setAttribute('stroke-width', '2.2');
    line.setAttribute('d', 'M15.7 15.7 21 21');
    line.setAttribute('fill', 'none');
    line.setAttribute('stroke', 'currentColor');
    line.setAttribute('stroke-width', '2.2');
    line.setAttribute('stroke-linecap', 'round');
    svg.appendChild(circle);
    svg.appendChild(line);
    span.appendChild(svg);
    return span;
  }
  function pvzCustomSearchElements() {
    return {
      root: document.getElementById('pvz-custom-searchbox'),
      form: document.getElementById('pvz-custom-searchbox-form'),
      input: document.getElementById('pvz-custom-searchbox-input'),
      suggestions: document.getElementById('pvz-custom-searchbox-suggestions')
    };
  }
  function pvzClearCustomSearchSuggestions() {
    var elements = pvzCustomSearchElements();
    pvzCustomSearchActiveIndex = -1;
    if (elements.suggestions) {
      elements.suggestions.textContent = '';
      elements.suggestions.hidden = true;
      elements.suggestions.classList.remove('is-open');
    }
    if (elements.input) {
      elements.input.setAttribute('aria-expanded', 'false');
      elements.input.removeAttribute('aria-activedescendant');
    }
    if (document.body) {
      document.body.classList.remove('pvz-custom-search-suggestions-open');
    }
  }
  function pvzCustomSearchSuggestionLinks() {
    var elements = pvzCustomSearchElements();
    if (!elements.suggestions) return [];
    return pvzArray(
      elements.suggestions.querySelectorAll('.pvz-custom-search-suggestion')
    );
  }
  function pvzSetCustomSearchActiveIndex(index) {
    var elements = pvzCustomSearchElements();
    var links = pvzCustomSearchSuggestionLinks();
    if (!links.length || !elements.input) {
      pvzCustomSearchActiveIndex = -1;
      return;
    }
    if (index < 0) index = links.length - 1;
    if (index >= links.length) index = 0;
    pvzCustomSearchActiveIndex = index;
    links.forEach(function (link, currentIndex) {
      var active = currentIndex === index;
      link.classList.toggle('is-active', active);
      link.setAttribute('aria-selected', active ? 'true' : 'false');
    });
    elements.input.setAttribute(
      'aria-activedescendant',
      links[index].id
    );
    if (typeof links[index].scrollIntoView === 'function') {
      links[index].scrollIntoView({
        block: 'nearest'
      });
    }
  }
  function pvzLoadSearchThumbnail(title) {
    if (!title) return Promise.resolve('');
    if (Object.prototype.hasOwnProperty.call(pvzSearchThumbnailCache, title)) {
      return pvzSearchThumbnailCache[title];
    }
    if (!window.mw || !mw.loader) {
      return Promise.resolve('');
    }
    pvzSearchThumbnailCache[title] = mw.loader.using('mediawiki.api').then(function () {
      var api = new mw.Api();
      return api.get({
        action: 'query',
        prop: 'pageimages',
        piprop: 'thumbnail',
        pithumbsize: 80,
        titles: title,
        formatversion: 2
      });
    }).then(function (data) {
      var pages = data && data.query && data.query.pages;
      var page = pages && pages[0];
      return page && page.thumbnail && page.thumbnail.source ?
        page.thumbnail.source :
        '';
    }).catch(function () {
      return '';
    });
    return pvzSearchThumbnailCache[title];
  }
  function pvzAppendHighlightedSearchText(container, text, query) {
    var source = String(text || '');
    var needle = String(query || '');
    var sourceLower;
    var needleLower;
    var cursor = 0;
    var index;
    var strong;
    container.textContent = '';
    if (!needle) {
      container.textContent = source;
      return;
    }
    sourceLower = source.toLocaleLowerCase();
    needleLower = needle.toLocaleLowerCase();
    while (cursor < source.length) {
      index = sourceLower.indexOf(needleLower, cursor);
      if (index === -1) {
        container.appendChild(
          document.createTextNode(source.slice(cursor))
        );
        break;
      }
      if (index > cursor) {
        container.appendChild(
          document.createTextNode(source.slice(cursor, index))
        );
      }
      strong = document.createElement('strong');
      strong.className = 'pvz-custom-search-suggestion-match';
      strong.textContent = source.slice(index, index + needle.length);
      container.appendChild(strong);
      cursor = index + needle.length;
    }
    if (!source.length) {
      container.textContent = '';
    }
  }
  function pvzRenderCustomSearchSuggestions(query, data, requestId) {
    var elements = pvzCustomSearchElements();
    var titles;
    var urls;
    if (
      requestId !== pvzCustomSearchRequestId ||
      !elements.suggestions ||
      !elements.input
    ) {
      return;
    }
    if (pvzNormalizeText(elements.input.value) !== query) return;
    titles = Array.isArray(data && data[1]) ? data[1] : [];
    urls = Array.isArray(data && data[3]) ? data[3] : [];
    elements.suggestions.textContent = '';
    pvzCustomSearchActiveIndex = -1;
    if (!titles.length) {
      pvzClearCustomSearchSuggestions();
      return;
    }
    titles.slice(0, 7).forEach(function (title, index) {
      var link = document.createElement('a');
      var thumb = document.createElement('span');
      var label = document.createElement('span');
      var href = urls[index] || pvzMakePageUrl(title);
      link.id = 'pvz-custom-search-option-' + index;
      link.className = 'pvz-custom-search-suggestion';
      link.href = href;
      link.setAttribute('role', 'option');
      link.setAttribute('aria-selected', 'false');
      thumb.className = 'pvz-custom-search-suggestion-thumb';
      thumb.setAttribute('aria-hidden', 'true');
      label.className = 'pvz-custom-search-suggestion-label';
      pvzAppendHighlightedSearchText(label, title, query);
      link.appendChild(thumb);
      link.appendChild(label);
      elements.suggestions.appendChild(link);
      pvzLoadSearchThumbnail(title).then(function (source) {
        var image;
        if (!source || !thumb.isConnected) return;
        image = document.createElement('img');
        image.src = source;
        image.alt = '';
        image.loading = 'lazy';
        image.decoding = 'async';
        thumb.appendChild(image);
        thumb.classList.add('has-image');
      });
    });
    elements.suggestions.hidden = false;
    elements.suggestions.classList.add('is-open');
    elements.input.setAttribute('aria-expanded', 'true');
    if (document.body) {
      document.body.classList.add('pvz-custom-search-suggestions-open');
    }
  }
  function pvzRequestCustomSearchSuggestions(query) {
    var requestId;
    query = pvzNormalizeText(query);
    if (query.length < 1) {
      pvzClearCustomSearchSuggestions();
      return;
    }
    if (!window.mw || !mw.loader) {
      pvzClearCustomSearchSuggestions();
      return;
    }
    requestId = ++pvzCustomSearchRequestId;
    mw.loader.using('mediawiki.api').then(function () {
      var api = new mw.Api();
      return api.get({
        action: 'opensearch',
        search: query,
        limit: 7,
        namespace: 0,
        redirects: 'resolve',
        format: 'json'
      });
    }).then(function (data) {
      pvzRenderCustomSearchSuggestions(query, data, requestId);
    }).catch(function () {
      if (requestId === pvzCustomSearchRequestId) {
        pvzClearCustomSearchSuggestions();
      }
    });
  }
  function pvzQueueCustomSearchSuggestions() {
    var elements = pvzCustomSearchElements();
    window.clearTimeout(pvzCustomSearchInputTimer);
    if (!elements.input || pvzCustomSearchComposing) return;
    pvzCustomSearchInputTimer = window.setTimeout(function () {
      pvzRequestCustomSearchSuggestions(elements.input.value);
    }, 170);
  }
  function pvzFindCustomSearchMainContent() {
    return (
      document.querySelector('body.skin-timeless #mw-content') ||
      document.querySelector('body.skin-timeless #content') ||
      document.querySelector('body.skin-timeless .mw-body')
    );
  }
  function pvzSyncCustomSearchWidth() {
    var elements = pvzCustomSearchElements();
    var header = document.getElementById('mw-header-container');
    var content;
    var headerRect;
    var contentRect;
    var layoutViewportWidth = Math.round(
      document.documentElement.clientWidth ||
      window.innerWidth ||
      0
    );
    if (!elements.root || !header) return;
    elements.root.classList.remove('is-main-width');
    elements.root.style.removeProperty('--pvz-custom-search-left');
    elements.root.style.removeProperty('--pvz-custom-search-width');
    if (layoutViewportWidth < 1280) return;
    content = pvzFindCustomSearchMainContent();
    if (!content || typeof content.getBoundingClientRect !== 'function') return;
    headerRect = header.getBoundingClientRect();
    contentRect = content.getBoundingClientRect();
    if (contentRect.width <= 0) return;
    elements.root.style.setProperty(
      '--pvz-custom-search-left',
      Math.round(contentRect.left - headerRect.left) + 'px'
    );
    elements.root.style.setProperty(
      '--pvz-custom-search-width',
      Math.round(contentRect.width) + 'px'
    );
    elements.root.classList.add('is-main-width');
  }
  function pvzQueueCustomSearchWidthSync() {
    window.cancelAnimationFrame(pvzCustomSearchWidthFrame);
    pvzCustomSearchWidthFrame = window.requestAnimationFrame(function () {
      pvzSyncCustomSearchWidth();
    });
  }
  function pvzBindCustomSearchViewportSync() {
    var content;
    if (pvzCustomSearchViewportBound) {
      pvzQueueCustomSearchWidthSync();
      return;
    }
    pvzCustomSearchViewportBound = true;
    window.addEventListener('resize', pvzQueueCustomSearchWidthSync, {
      passive: true
    });
    if (
      window.screen &&
      window.screen.orientation &&
      typeof window.screen.orientation.addEventListener === 'function'
    ) {
      window.screen.orientation.addEventListener(
        'change',
        pvzQueueCustomSearchWidthSync
      );
    }
    if (
      window.visualViewport &&
      typeof window.visualViewport.addEventListener === 'function'
    ) {
      window.visualViewport.addEventListener(
        'resize',
        pvzQueueCustomSearchWidthSync,
        { passive: true }
      );
    }
    content = pvzFindCustomSearchMainContent();
    if (window.ResizeObserver && content) {
      pvzCustomSearchResizeObserver = new ResizeObserver(function () {
        pvzQueueCustomSearchWidthSync();
      });
      pvzCustomSearchResizeObserver.observe(content);
    }
  }
  function pvzBindCustomSearchEvents() {
    var elements = pvzCustomSearchElements();
    if (
      pvzCustomSearchEventsBound ||
      !elements.form ||
      !elements.input ||
      !elements.suggestions
    ) {
      return;
    }
    pvzCustomSearchEventsBound = true;
    elements.input.addEventListener('compositionstart', function () {
      pvzCustomSearchComposing = true;
    });
    elements.input.addEventListener('compositionend', function () {
      pvzCustomSearchComposing = false;
      pvzQueueCustomSearchSuggestions();
    });
    elements.input.addEventListener('input', function () {
      if (!pvzCustomSearchComposing) {
        pvzQueueCustomSearchSuggestions();
      }
    });
    elements.input.addEventListener('focus', function () {
      if (pvzNormalizeText(elements.input.value)) {
        pvzQueueCustomSearchSuggestions();
      }
    });
    elements.input.addEventListener('keydown', function (event) {
      var links = pvzCustomSearchSuggestionLinks();
      if (event.key === 'ArrowDown' && links.length) {
        event.preventDefault();
        pvzSetCustomSearchActiveIndex(pvzCustomSearchActiveIndex + 1);
        return;
      }
      if (event.key === 'ArrowUp' && links.length) {
        event.preventDefault();
        pvzSetCustomSearchActiveIndex(pvzCustomSearchActiveIndex - 1);
        return;
      }
      if (
        event.key === 'Enter' &&
        pvzCustomSearchActiveIndex >= 0 &&
        links[pvzCustomSearchActiveIndex]
      ) {
        event.preventDefault();
        window.location.href = links[pvzCustomSearchActiveIndex].href;
        return;
      }
      if (event.key === 'Escape') {
        pvzClearCustomSearchSuggestions();
        elements.input.blur();
      }
    });
    elements.form.addEventListener('submit', function (event) {
      if (!pvzNormalizeText(elements.input.value)) {
        event.preventDefault();
        elements.input.focus();
      }
    });
    elements.suggestions.addEventListener('pointerdown', function (event) {
      var link = event.target && event.target.closest ?
        event.target.closest('.pvz-custom-search-suggestion') :
        null;
      if (!link) return;
      link.classList.add('is-pressed');
      window.setTimeout(function () {
        if (link.classList) link.classList.remove('is-pressed');
      }, 140);
    });
    document.addEventListener('pointerdown', function (event) {
      var root = document.getElementById('pvz-custom-searchbox');
      if (!root || root.contains(event.target)) return;
      pvzClearCustomSearchSuggestions();
    }, { passive: true });
  }
  function pvzBuildCustomSearch() {
    var header = document.getElementById('mw-header-container');
    var existing = document.getElementById('pvz-custom-searchbox');
    var root;
    var form;
    var field;
    var input;
    var button;
    var suggestions;
    if (!header) return null;
    if (existing) {
      pvzSyncCustomSearchWidth();
      return existing;
    }
    root = document.createElement('div');
    root.id = 'pvz-custom-searchbox';
    root.className = 'pvz-custom-searchbox';
    root.setAttribute('role', 'search');
    form = document.createElement('form');
    form.id = 'pvz-custom-searchbox-form';
    form.className = 'pvz-custom-searchbox-form';
    form.method = 'get';
    form.action = pvzMakeSpecialUrl('Search');
    field = document.createElement('div');
    field.className = 'pvz-custom-searchbox-field';
    input = document.createElement('input');
    input.id = 'pvz-custom-searchbox-input';
    input.className = 'pvz-custom-searchbox-input';
    input.type = 'search';
    input.name = 'search';
    input.placeholder = '위키에서 검색하세요';
    input.autocomplete = 'off';
    input.spellcheck = false;
    input.setAttribute('autocapitalize', 'off');
    input.setAttribute('enterkeyhint', 'search');
    input.setAttribute('aria-label', '위키에서 검색하세요');
    input.setAttribute('aria-autocomplete', 'list');
    input.setAttribute('aria-controls', 'pvz-custom-searchbox-suggestions');
    input.setAttribute('aria-expanded', 'false');
    button = document.createElement('button');
    button.className = 'pvz-custom-searchbox-submit';
    button.type = 'submit';
    button.setAttribute('aria-label', '검색');
    button.appendChild(pvzCreateSearchSvgIcon());
    suggestions = document.createElement('div');
    suggestions.id = 'pvz-custom-searchbox-suggestions';
    suggestions.className = 'pvz-custom-searchbox-suggestions';
    suggestions.setAttribute('role', 'listbox');
    suggestions.setAttribute('aria-label', '검색 제안');
    suggestions.hidden = true;
    field.appendChild(input);
    field.appendChild(button);
    form.appendChild(field);
    root.appendChild(form);
    root.appendChild(suggestions);
    header.appendChild(root);
    if (document.body) {
      document.body.classList.add('pvz-custom-search-ready');
    }
    pvzBindCustomSearchEvents();
    pvzBindCustomSearchViewportSync();
    pvzQueueCustomSearchWidthSync();
    window.setTimeout(pvzQueueCustomSearchWidthSync, 120);
    window.setTimeout(pvzQueueCustomSearchWidthSync, 420);
    return root;
  }
  /* 커스텀 검색바 끝 */
  function pvzCreateInlineSvgIcon(kind) {
    var span = document.createElement('span');
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    var path = document.createElementNS(svgNS, 'path');
    var paths = {
      menu: 'M3 6h18v2.4H3V6Zm0 5h18v2.4H3V11Zm0 5h18v2.4H3V16Z',
      tools: 'M21.2 18.6 13.9 11.3c.7-1.9.3-4.1-1.2-5.6-1.6-1.6-4-2-6-1.1l3.5 3.5-2.1 2.1-3.6-3.5c-.8 2-.4 4.4 1.2 6 1.5 1.5 3.7 1.9 5.6 1.2l7.3 7.3c.3.3.8.3 1.1 0l1.5-1.5c.3-.3.3-.8 0-1.1Z',
      document: 'M6 2h9l5 5v15H6V2Zm8 1.8V8h4.2L14 3.8ZM8.5 12h7v1.8h-7V12Zm0 4h7v1.8h-7V16Z',
      view: 'M6 2h9l5 5v15H6V2Zm8 1.8V8h4.2L14 3.8ZM8.5 12h7v1.8h-7V12Zm0 4h7v1.8h-7V16Z',
      talk: 'M4 4h16v12H9.2L4 20V4Zm3 3v2h10V7H7Zm0 4v2h7v-2H7Z',
      edit: 'M4 17.2V21h3.8L18.9 9.9l-3.8-3.8L4 17.2Zm16.2-9.6-1.8 1.8-3.8-3.8 1.8-1.8a1.2 1.2 0 0 1 1.7 0l2.1 2.1a1.2 1.2 0 0 1 0 1.7Z',
      history: 'M12 4a8 8 0 1 1-7.4 5H2l4-4 4 4H6.8A5.9 5.9 0 1 0 12 6V4Zm1 4v4l3 2-1.1 1.7-3.4-2.2A1 1 0 0 1 11 12V8h2Z',
      watch: 'm12 2 2.9 6.2 6.6 1-4.8 4.7 1.2 6.7-5.9-3.2-5.9 3.2 1.2-6.7-4.8-4.8 6.6-.9L12 2Z',
      more: 'M6 10a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm6 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm6 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4Z',
      alerts: 'M12 2a5 5 0 0 0-5 5v2.1c0 1.1-.4 2.2-1.1 3L4.3 14a1 1 0 0 0 .7 1.7h14a1 1 0 0 0 .7-1.7l-1.6-1.9A4.8 4.8 0 0 1 17 9.1V7a5 5 0 0 0-5-5Zm0 20a3 3 0 0 0 2.8-2H9.2A3 3 0 0 0 12 22Z',
      account: 'M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0 2c-5.4 0-9 2.7-9 6v1h18v-1c0-3.3-3.6-6-9-6Z',
      sun: 'M12 4V1h2v3h-2Zm0 19v-3h2v3h-2ZM4.2 5.6 2.1 3.5 3.5 2.1l2.1 2.1-1.4 1.4Zm16.3 16.3-2.1-2.1 1.4-1.4 2.1 2.1-1.4 1.4ZM1 13v-2h3v2H1Zm19 0v-2h3v2h-3ZM3.5 21.9l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1ZM19.8 5.6l-1.4-1.4 2.1-2.1 1.4 1.4-2.1 2.1ZM13 7a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z',
      moon: 'M21 14.8A8.5 8.5 0 0 1 9.2 3a7 7 0 1 0 8.8 8.8A8.4 8.4 0 0 1 21 14.8Z'
    };
    span.className = 'pvz-timeless-action-icon pvz-timeless-action-icon-' + kind;
    span.setAttribute('aria-hidden', 'true');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('focusable', 'false');
    path.setAttribute('d', paths[kind] || paths.menu);
    svg.appendChild(path);
    span.appendChild(svg);
    return span;
  }
  function pvzClampNumber(value, min, max) {
    if (max < min) return min;
    return Math.max(min, Math.min(value, max));
  }
  function pvzFindTimelessPanelButton(name) {
    var selector = '.pvz-timeless-action-button[data-pvz-timeless-panel="' + name + '"], ' +
      '.pvz-timeless-page-action[data-pvz-timeless-panel="' + name + '"]';
    var buttons = pvzArray(document.querySelectorAll(selector));
    var i;
    var rect;
    for (i = 0; i < buttons.length; i += 1) {
      if (!buttons[i] || typeof buttons[i].getBoundingClientRect !== 'function') continue;
      rect = buttons[i].getBoundingClientRect();
      if (rect.width > 0 && rect.height > 0) return buttons[i];
    }
    return buttons[0] || null;
  }
  function pvzGetTimelessViewport() {
    var vv = window.visualViewport;
    var width = window.innerWidth || document.documentElement.clientWidth || 0;
    var height = window.innerHeight || document.documentElement.clientHeight || 0;
    if (!vv || typeof vv.width !== 'number' || typeof vv.height !== 'number') {
      return {
        left: 0,
        top: 0,
        width: width,
        height: height,
        centerX: width / 2
      };
    }
    return {
      left: typeof vv.offsetLeft === 'number' ? vv.offsetLeft : 0,
      top: typeof vv.offsetTop === 'number' ? vv.offsetTop : 0,
      width: vv.width || width,
      height: vv.height || height,
      centerX: (typeof vv.offsetLeft === 'number' ? vv.offsetLeft : 0) + ((vv.width || width) / 2)
    };
  }
  var pvzTimelessViewportWidth = 0;
  var pvzTimelessViewportHeight = 0;
  var pvzTimelessPanelRefreshTimers = [];
  var pvzTimelessPanelCloseTimers = new WeakMap();
  function pvzResetTimelessPanelGeometry(panel) {
    if (!panel || panel.nodeType !== 1) return;
    panel.removeAttribute('data-pvz-panel-fixed-at-open');
    panel.removeAttribute('data-pvz-panel-document-locked');
    panel.removeAttribute('data-pvz-panel-document-top');
    panel.removeAttribute('data-pvz-panel-document-left');
    panel.removeAttribute('data-pvz-panel-document-width');
    [
      'position',
      'top',
      'left',
      'right',
      'width',
      'max-width',
      'max-height',
      'overflow',
      'transform',
      'transform-origin',
      'z-index'
    ].forEach(function (property) {
      panel.style.removeProperty(property);
    });
  }
  function pvzClearTimelessPanelCloseTimer(panel) {
    var timer;
    if (!panel) return;
    timer = pvzTimelessPanelCloseTimers.get(panel);
    if (!timer) return;
    window.clearTimeout(timer);
    pvzTimelessPanelCloseTimers.delete(panel);
  }
  function pvzScheduleTimelessPanelGeometryCleanup(panel) {
    var timer;
    if (!panel) return;
    pvzClearTimelessPanelCloseTimer(panel);
    timer = window.setTimeout(function () {
      pvzTimelessPanelCloseTimers.delete(panel);
      if (!panel.classList.contains('is-open')) {
        pvzResetTimelessPanelGeometry(panel);
      }
    }, 240);
    pvzTimelessPanelCloseTimers.set(panel, timer);
  }
  function pvzPositionTimelessPanel(name) {
    var panel = document.getElementById('pvz-timeless-panel-' + name);
    var button = pvzFindTimelessPanelButton(name);
    var viewport;
    var buttonRect;
    var panelRect;
    var panelWidth;
    var center;
    var minLeft;
    var maxLeft;
    var left;
    var top;
    var scrollX;
    var scrollY;
    if (!panel || !button || typeof button.getBoundingClientRect !== 'function') return;
    viewport = pvzGetTimelessViewport();
    buttonRect = button.getBoundingClientRect();
    panelRect = panel.getBoundingClientRect();
    panelWidth = panelRect.width || 236;
    panelWidth = Math.min(panelWidth, Math.max(0, viewport.width - 16));
    center = buttonRect.left + (buttonRect.width / 2);
    minLeft = viewport.left + 8;
    maxLeft = viewport.left + viewport.width - panelWidth - 8;
    left = pvzClampNumber(center - (panelWidth / 2), minLeft, maxLeft);
    top = viewport.top + buttonRect.bottom + 6;
    scrollX = window.pageXOffset || document.documentElement.scrollLeft || 0;
    scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
    panel.style.setProperty('position', 'absolute', 'important');
    panel.style.setProperty('top', Math.round(top + scrollY) + 'px', 'important');
    panel.style.setProperty('left', Math.round(left + scrollX) + 'px', 'important');
    panel.style.setProperty('right', 'auto', 'important');
    panel.style.setProperty('max-width', 'calc(100vw - 12px)', 'important');
    panel.style.setProperty('max-height', 'none', 'important');
    panel.style.setProperty('overflow', 'visible', 'important');
    panel.style.setProperty('transform', 'none', 'important');
  }
  function pvzRepositionOpenTimelessPanel() {
    var open = document.querySelector('.pvz-timeless-sheet.is-open');
    var name;
    if (!open || !open.id) return;
    name = open.id.replace(/^pvz-timeless-panel-/, '');
    if (name) pvzPositionTimelessPanel(name);
  }
  function pvzQueueTimelessPanelViewportRefresh() {
    pvzTimelessPanelRefreshTimers.forEach(function (timer) {
      window.clearTimeout(timer);
    });
    pvzTimelessPanelRefreshTimers = [];
    window.requestAnimationFrame(function () {
      pvzRepositionOpenTimelessPanel();
    });
    [140, 420].forEach(function (delay) {
      pvzTimelessPanelRefreshTimers.push(
        window.setTimeout(function () {
          pvzRepositionOpenTimelessPanel();
        }, delay)
      );
    });
  }
  function pvzHandleTimelessViewportResize() {
    var viewport = pvzGetTimelessViewport();
    var width = Math.round(viewport.width || window.innerWidth || 0);
    var height = Math.round(viewport.height || window.innerHeight || 0);
    var widthChanged;
    var heightChanged;
    var orientationChanged;
    if (!pvzTimelessViewportWidth || !pvzTimelessViewportHeight) {
      pvzTimelessViewportWidth = width;
      pvzTimelessViewportHeight = height;
      return;
    }
    widthChanged = Math.abs(width - pvzTimelessViewportWidth);
    heightChanged = Math.abs(height - pvzTimelessViewportHeight);
    orientationChanged =
      (width > height) !== (pvzTimelessViewportWidth > pvzTimelessViewportHeight);
    pvzTimelessViewportWidth = width;
    pvzTimelessViewportHeight = height;
    if (orientationChanged || widthChanged >= 80 || heightChanged >= 120) {
      pvzQueueTimelessPanelViewportRefresh();
    }
  }
  function pvzCloseTimelessPanels() {
    document.documentElement.classList.remove('pvz-timeless-panel-open');
    if (document.body) document.body.classList.remove('pvz-timeless-panel-open');
    pvzArray(document.querySelectorAll('.pvz-timeless-sheet')).forEach(function (panel) {
      panel.classList.remove('is-open');
      pvzScheduleTimelessPanelGeometryCleanup(panel);
    });
    pvzArray(document.querySelectorAll('.pvz-timeless-action-button, .pvz-timeless-page-action[data-pvz-timeless-panel]')).forEach(function (button) {
      button.classList.remove('is-active');
      button.setAttribute('aria-expanded', 'false');
    });
  }
  function pvzOpenTimelessPanel(name) {
    var panel = document.getElementById('pvz-timeless-panel-' + name);
    if (!panel) return;
    if (name === 'alerts') pvzRefreshTimelessAlertsPanel();
    pvzCloseTimelessPanels();
    pvzClearTimelessPanelCloseTimer(panel);
    pvzResetTimelessPanelGeometry(panel);
    pvzPositionTimelessPanel(name);
    document.documentElement.classList.add('pvz-timeless-panel-open');
    if (document.body) document.body.classList.add('pvz-timeless-panel-open');
    panel.classList.add('is-open');
    pvzArray(document.querySelectorAll('.pvz-timeless-action-button, .pvz-timeless-page-action[data-pvz-timeless-panel]')).forEach(function (button) {
      var active = button.getAttribute('data-pvz-timeless-panel') === name;
      button.classList.toggle('is-active', active);
      button.setAttribute('aria-expanded', active ? 'true' : 'false');
    });
  }
  function pvzToggleTimelessPanel(name) {
    var panel = document.getElementById('pvz-timeless-panel-' + name);
    if (panel && panel.classList.contains('is-open')) {
      pvzCloseTimelessPanels();
      return;
    }
    pvzOpenTimelessPanel(name);
  }
  function pvzCreateTimelessActionButton(name, label, iconKind) {
    var button = document.createElement('button');
    button.type = 'button';
    button.className = 'pvz-timeless-action-button pvz-timeless-action-' + name;
    button.setAttribute('data-pvz-timeless-panel', name);
    button.setAttribute('aria-label', label);
    button.setAttribute('aria-expanded', 'false');
    button.title = label;
    button.appendChild(pvzCreateInlineSvgIcon(iconKind));
    return button;
  }
  function pvzGetTalkPageUrl() {
    var pageName = pvzPageName();
    var title = '';
    if (window.mw && mw.config && typeof mw.config.get === 'function') {
      title = mw.config.get('wgTitle') || pageName;
    }
    if (!pageName) return pvzMakePageUrl('Talk:' + title);
    if (/^토론:|^Talk:/i.test(pageName)) return pvzMakePageUrl(pageName);
    if (pageName.indexOf(':') === -1) return pvzMakePageUrl('토론:' + title);
    return pvzMakePageUrl('토론:' + title);
  }
  function pvzIsCurrentPageAction(kind) {
    var action = 'view';
    var ns = null;
    if (window.mw && mw.config && typeof mw.config.get === 'function') {
      action = mw.config.get('wgAction') || 'view';
      ns = mw.config.get('wgNamespaceNumber');
    }
    if (kind === 'view') {
      return action === 'view' && !(typeof ns === 'number' && ns % 2 === 1);
    }
    if (kind === 'talk') {
      return typeof ns === 'number' && ns % 2 === 1;
    }
    if (kind === 'edit') {
      return action === 'edit' || action === 'submit' || action === 'visualeditor';
    }
    if (kind === 'history') {
      return action === 'history';
    }
    return false;
  }
  function pvzCreatePageActionElement(kind, label, href, extraClass) {
    var el;
    if (href) {
      el = document.createElement('a');
      el.href = href;
    } else {
      el = document.createElement('button');
      el.type = 'button';
    }
    el.className = 'pvz-timeless-page-action pvz-timeless-page-action-' + (extraClass || kind);
    if (pvzIsCurrentPageAction(kind)) {
      el.classList.add('is-current');
      el.setAttribute('aria-current', 'page');
    }
    el.setAttribute('aria-label', label);
    el.title = label;
    el.appendChild(pvzCreateInlineSvgIcon(kind));
    el.querySelector('.pvz-timeless-action-icon').classList.add('pvz-timeless-page-action-icon');
    return el;
  }
  function pvzCreatePagePanelButton(kind, label, panelName, extraClass) {
    var button = pvzCreatePageActionElement(kind, label, '', extraClass);
    button.setAttribute('data-pvz-timeless-panel', panelName);
    button.setAttribute('aria-expanded', 'false');
    return button;
  }
  function pvzPageActionUrl(type) {
    var found;
    var pageName = pvzPageName();
    if (type === 'view') {
      found = pvzFindExistingLink({
        ids: [/ca-view/i, /ca-nstab-main/i, /ca-nstab-project/i],
        text: [/^문서$/, /^Read$/i, /^Page$/i]
      });
      return found ? found.getAttribute('href') : pvzMakePageUrl(pageName);
    }
    if (type === 'talk') {
      found = pvzFindExistingLink({
        ids: [/ca-talk/i, /ca-talkpage/i],
        text: [/^토론$/, /^Discussion$/i, /^Talk$/i]
      });
      return found ? found.getAttribute('href') : pvzGetTalkPageUrl();
    }
    if (type === 'edit') {
      found = pvzFindExistingLink({
        ids: [/ca-edit/i, /ca-ve-edit/i, /ca-viewsource/i],
        href: [/action=(edit|submit)/i, /veaction=edit/i],
        text: [/^편집$/, /^원본 편집$/, /^Edit$/i, /^View source$/i]
      });
      return found ? found.getAttribute('href') : (pageName ? pvzMakePageUrl(pageName) + '?action=edit' : '');
    }
    if (type === 'history') {
      found = pvzFindExistingLink({
        ids: [/ca-history/i],
        href: [/action=history/i],
        text: [/^역사$/, /^문서 역사$/, /^View history$/i, /^History$/i]
      });
      return found ? found.getAttribute('href') : (pageName ? pvzMakePageUrl(pageName) + '?action=history' : '');
    }
    if (type === 'watch') {
      found = pvzFindExistingLink({
        ids: [/ca-watch/i, /ca-unwatch/i, /ca-watchlist/i],
        href: [/action=(watch|unwatch)/i, /Special:(Watchlist|주시문서)/i, /특수:(주시문서|Watchlist)/i],
        text: [/주시문서/, /즐겨찾기/, /^Watch$/i, /^Unwatch$/i]
      });
      return found ? found.getAttribute('href') : (pageName ? pvzMakePageUrl(pageName) + '?action=watch' : pvzMakeSpecialUrl('Watchlist'));
    }
    return '';
  }
  function pvzEnsureTimelessPageActions() {
    var old = document.getElementById('pvz-timeless-page-actions');
    var heading = document.getElementById('firstHeading') ||
      document.querySelector('.mw-first-heading, .firstHeading');
    var wrap = document.createElement('nav');
    var left = document.createElement('div');
    var right = document.createElement('div');
    var watchUrl = pvzPageActionUrl('watch');
    if (!heading || !heading.parentNode) return;
    if (old && old.parentNode) old.parentNode.removeChild(old);
    wrap.id = 'pvz-timeless-page-actions';
    wrap.className = 'pvz-timeless-page-actions';
    wrap.setAttribute('aria-label', '문서 작업');
    left.className = 'pvz-timeless-page-actions-left';
    right.className = 'pvz-timeless-page-actions-right';
    left.appendChild(pvzCreatePageActionElement('view', '문서', pvzPageActionUrl('view'), 'view'));
    left.appendChild(pvzCreatePageActionElement('talk', '토론', pvzPageActionUrl('talk'), 'talk'));
    right.appendChild(pvzCreatePageActionElement('edit', '편집', pvzPageActionUrl('edit'), 'edit'));
    right.appendChild(pvzCreatePageActionElement('history', '역사', pvzPageActionUrl('history'), 'history'));
    if (watchUrl) {
      right.appendChild(pvzCreatePageActionElement('watch', '즐겨찾기', watchUrl, 'watch'));
    }
    right.appendChild(pvzCreatePagePanelButton('more', '더보기', 'more', 'more'));
    wrap.appendChild(left);
    wrap.appendChild(right);
    if (heading.nextSibling) {
      heading.parentNode.insertBefore(wrap, heading.nextSibling);
    } else {
      heading.parentNode.appendChild(wrap);
    }
  }
  function pvzEnsureTimelessActionbar() {
    var bar = document.getElementById('pvz-timeless-mobile-actions');
    var host = document.querySelector('body.skin-timeless #mw-header-container') ||
      document.getElementById('mw-header-container') ||
      document.querySelector('body.skin-timeless #mw-header') ||
      document.body;
    if (!host) return bar;
    if (!bar) {
      bar = document.createElement('div');
      bar.id = 'pvz-timeless-mobile-actions';
      bar.className = 'pvz-timeless-mobile-actions';
      bar.setAttribute('aria-label', '식좀위키 모바일 빠른 메뉴');
    }
    bar.innerHTML = '';
    bar.appendChild(pvzCreateTimelessActionButton('browse', '둘러보기', 'menu'));
    bar.appendChild(pvzCreateTimelessActionButton('settings', '위키 도구', 'tools'));
    bar.appendChild(pvzCreateTimelessActionButton('document', '문서 도구', 'document'));
    bar.appendChild(pvzCreateTimelessActionButton('alerts', '알림', 'alerts'));
    bar.appendChild(pvzCreateTimelessActionButton('account', '계정', 'account'));
    if (bar.parentNode !== host) {
      host.appendChild(bar);
    }
    var title = document.getElementById('pvz-timeless-mobile-title');
    if (!title) {
      title = document.createElement('a');
      title.id = 'pvz-timeless-mobile-title';
      title.href = pvzMakePageUrl(pvzMainPageName());
      title.textContent = '식물 vs 좀비 위키';
      title.setAttribute('aria-label', '식물 vs 좀비 위키 대문으로 이동');
    }
    if (title.parentNode !== host) {
      host.appendChild(title);
    }
    return bar;
  }
  function pvzCreateTimelessPanel(name, label, sections) {
    var old = document.getElementById('pvz-timeless-panel-' + name);
    var panel = document.createElement('section');
    var body = document.createElement('div');
    if (old && old.parentNode) old.parentNode.removeChild(old);
    panel.id = 'pvz-timeless-panel-' + name;
    panel.className = 'pvz-timeless-sheet pvz-timeless-sheet-' + name;
    panel.setAttribute('aria-label', label);
    body.className = 'pvz-timeless-sheet-body';
    (sections || []).filter(Boolean).forEach(function (section) {
      body.appendChild(section);
    });
    panel.appendChild(body);
    document.body.appendChild(panel);
  }
  function pvzEnsureTimelessPanelBackdrop() {
    var old = document.getElementById('pvz-timeless-panel-backdrop');
    if (old) return old;
    old = document.createElement('div');
    old.id = 'pvz-timeless-panel-backdrop';
    old.className = 'pvz-timeless-panel-backdrop';
    document.body.appendChild(old);
    return old;
  }
  function pvzStoredColorMode() {
    try {
      return localStorage.getItem('pvz-timeless-color-mode') || '';
    } catch (e) {
      return '';
    }
  }
  function pvzCurrentColorMode() {
    var stored = pvzStoredColorMode();
    var root = document.documentElement;
    if (stored === 'light' || stored === 'dark') return stored;
    if (root && root.classList && (
      root.classList.contains('theme-dark') ||
      root.classList.contains('dark') ||
      root.classList.contains('client-dark-mode') ||
      root.classList.contains('mw-theme-night') ||
      root.classList.contains('skin-theme-clientpref-night')
    )) {
      return 'dark';
    }
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
      return 'dark';
    }
    return 'light';
  }
  function pvzApplyTimelessColorMode(mode, persist) {
    var root = document.documentElement;
    var body = document.body;
    var add;
    var remove;
    if (mode !== 'light' && mode !== 'dark') mode = 'light';
    if (typeof persist === 'undefined') persist = true;
    if (persist) {
      try {
        localStorage.setItem('pvz-timeless-color-mode', mode);
      } catch (e) {}
    }
    add = mode === 'dark'
      ? ['theme-dark', 'dark', 'client-dark-mode', 'mw-theme-night', 'skin-theme-clientpref-night']
      : ['theme-light', 'light', 'client-light-mode', 'skin-theme-clientpref-day'];
    remove = mode === 'dark'
      ? ['theme-light', 'light', 'client-light-mode', 'skin-theme-clientpref-day']
      : ['theme-dark', 'dark', 'client-dark-mode', 'mw-theme-night', 'skin-theme-clientpref-night'];
    [root, body].forEach(function (node) {
      if (!node || !node.classList) return;
      remove.forEach(function (name) { node.classList.remove(name); });
      add.forEach(function (name) { node.classList.add(name); });
    });
  }
  var pvzSystemThemeMedia = null;
  var pvzSystemThemeListenerBound = false;
  function pvzHasStoredColorMode() {
    var stored = pvzStoredColorMode();
    return stored === 'light' || stored === 'dark';
  }
  function pvzApplyInitialTimelessColorMode() {
    var stored = pvzStoredColorMode();
    var mode = pvzCurrentColorMode();
    pvzApplyTimelessColorMode(mode, stored === 'light' || stored === 'dark');
  }
  function pvzBindSystemThemeListener() {
    if (
      pvzSystemThemeListenerBound ||
      !window.matchMedia
    ) {
      return;
    }
    pvzSystemThemeMedia = window.matchMedia('(prefers-color-scheme: dark)');
    pvzSystemThemeListenerBound = true;
    function handleSystemThemeChange(event) {
      if (pvzHasStoredColorMode()) return;
      pvzApplyTimelessColorMode(event.matches ? 'dark' : 'light', false);
      window.setTimeout(function () {
        if (typeof pvzBuildCustomMobileTools === 'function') {
          pvzBuildCustomMobileTools();
        }
      }, 0);
    }
    if (typeof pvzSystemThemeMedia.addEventListener === 'function') {
      pvzSystemThemeMedia.addEventListener('change', handleSystemThemeChange);
    } else if (typeof pvzSystemThemeMedia.addListener === 'function') {
      pvzSystemThemeMedia.addListener(handleSystemThemeChange);
    }
  }
  function pvzThemeToggleEntry() {
    var current = pvzCurrentColorMode();
    if (current === 'dark') {
      return {
        label: '라이트 모드',
        href: '#',
        icon: 'sun',
        className: 'pvz-timeless-theme-toggle',
        themeMode: 'light'
      };
    }
    return {
      label: '다크 모드',
      href: '#',
      icon: 'moon',
      className: 'pvz-timeless-theme-toggle',
      themeMode: 'dark'
    };
  }
  /* =========================
  계정 패널 사용자 정보 시작
  - 현재 로그인 사용자의 명시적 직책, 편집 횟수, 가입일을 Action API에서 불러온다.
  ========================= */
  var pvzAccountInfoPromise = null;
  function pvzAccountRoleLabel(group) {
    var labels = {
      'bureaucrat': '사무관',
      'interface-admin': '인터페이스 관리자',
      'sysop': '관리자',
      'patroller': '순찰자',
      'rollbacker': '되돌리기 사용자',
      'autopatrolled': '자동 순찰 면제 사용자',
      'bot': '봇',
      'confirmed': '인증된 사용자',
      'checkuser': '검사관',
      'suppress': '기록보호자',
      'oversight': '기록보호자',
      'steward': '스튜어드',
      'translator': '번역자',
      'translationadmin': '번역 관리자'
    };
    return labels[group] || group;
  }
  function pvzAccountExplicitRoles(userinfo) {
    var memberships = userinfo && userinfo.groupmemberships;
    var groups = userinfo && userinfo.groups;
    var names = [];
    if (Array.isArray(memberships) && memberships.length) {
      memberships.forEach(function (membership) {
        if (membership && membership.group) names.push(membership.group);
      });
    } else if (Array.isArray(groups)) {
      groups.forEach(function (group) {
        if (group && group !== '*' && group !== 'user' && group !== 'autoconfirmed') {
          names.push(group);
        }
      });
    }
    names = names.filter(function (group, index, list) {
      return group && list.indexOf(group) === index;
    });
    if (!names.length) return '일반 사용자';
    return names.map(pvzAccountRoleLabel).join(' · ');
  }
  function pvzFormatAccountRegistrationDate(value) {
    var date;
    var formatter;
    var parts;
    var data = {};
    if (!value) return '확인할 수 없음';
    date = new Date(value);
    if (isNaN(date.getTime())) return '확인할 수 없음';
    try {
      formatter = new Intl.DateTimeFormat('ko-KR', {
        timeZone: 'Asia/Seoul',
        year: 'numeric',
        month: 'numeric',
        day: 'numeric',
        weekday: 'short'
      });
      parts = formatter.formatToParts(date);
      parts.forEach(function (part) {
        if (part.type !== 'literal') data[part.type] = part.value;
      });
      return (
        data.year + '년 ' +
        data.month + '월 ' +
        data.day + '일 (' +
        data.weekday + ')'
      );
    } catch (e) {
      return date.getFullYear() + '년 ' +
        (date.getMonth() + 1) + '월 ' +
        date.getDate() + '일';
    }
  }
  function pvzLoadAccountInfo() {
    if (pvzAccountInfoPromise) return pvzAccountInfoPromise;
    if (!window.mw || !mw.loader || !mw.config || !mw.config.get('wgUserName')) {
      return Promise.resolve(null);
    }
    pvzAccountInfoPromise = mw.loader.using('mediawiki.api').then(function () {
      var api = new mw.Api();
      return api.get({
        action: 'query',
        meta: 'userinfo',
        uiprop: 'groups|groupmemberships|editcount|registrationdate',
        formatversion: 2
      });
    }).then(function (data) {
      return data && data.query ? data.query.userinfo : null;
    }).catch(function () {
      return null;
    });
    return pvzAccountInfoPromise;
  }
  function pvzCreateAccountInfoRow(label, value) {
    var row = document.createElement('div');
    var key = document.createElement('span');
    var text = document.createElement('span');
    row.className = 'pvz-timeless-account-info-row';
    key.className = 'pvz-timeless-account-info-label';
    key.textContent = label;
    text.className = 'pvz-timeless-account-info-value';
    text.textContent = value;
    row.appendChild(key);
    row.appendChild(text);
    return row;
  }
  function pvzCreateAccountInfoBlock(username) {
    var box = document.createElement('div');
    box.className = 'pvz-timeless-account-info';
    box.setAttribute('aria-label', username + ' 계정 정보');
    box.appendChild(pvzCreateAccountInfoRow('직책', '불러오는 중…'));
    box.appendChild(pvzCreateAccountInfoRow('편집', '불러오는 중…'));
    box.appendChild(pvzCreateAccountInfoRow('가입일', '불러오는 중…'));
    pvzLoadAccountInfo().then(function (userinfo) {
      var rows;
      var editcount;
      if (!box.isConnected || !userinfo) return;
      rows = box.querySelectorAll('.pvz-timeless-account-info-value');
      editcount = Number(userinfo.editcount);
      if (rows[0]) rows[0].textContent = pvzAccountExplicitRoles(userinfo);
      if (rows[1]) {
        rows[1].textContent = isNaN(editcount) ?
          '확인할 수 없음' :
          editcount.toLocaleString('ko-KR') + '회';
      }
      if (rows[2]) rows[2].textContent = pvzFormatAccountRegistrationDate(userinfo.registrationdate);
    });
    return box;
  }
  /* 계정 패널 사용자 정보 끝 */
  function pvzAccountSection() {
    var username = window.mw && mw.config && typeof mw.config.get === 'function' ? mw.config.get('wgUserName') : null;
    var entries;
    var section;
    var list;
    if (username) {
      entries = [
        { label: username, href: pvzMakePageUrl('User:' + username) },
        { label: '사용자 토론', href: pvzMakePageUrl('User talk:' + username) },
        { label: '주시문서 목록', href: pvzMakeSpecialUrl('Watchlist') },
        { label: '기여', href: pvzMakeSpecialUrl('Contributions/' + username) },
        { label: '환경설정', href: pvzMakeSpecialUrl('Preferences') },
        { label: '로그아웃', href: pvzMakeSpecialUrl('UserLogout') },
        pvzThemeToggleEntry()
      ];
    } else {
      entries = [
        { label: '로그인', href: pvzMakeSpecialUrl('UserLogin') },
        { label: '회원 가입', href: pvzMakeSpecialUrl('CreateAccount') },
        pvzThemeToggleEntry()
      ];
    }
    section = pvzCreateToolsSection('계정', entries, 'account');
    if (section && username) {
      list = section.querySelector('.pvz-custom-mobile-tools-list');
      section.insertBefore(pvzCreateAccountInfoBlock(username), list || null);
    }
    return section;
  }
  function pvzNativeNotificationCount() {
    var nodes = pvzArray(document.querySelectorAll(
      '.mw-echo-notifications-badge, .mw-echo-notification-badge, ' +
      '#pt-notifications-alert .mw-echo-notifications-badge, ' +
      '#pt-notifications-notice .mw-echo-notifications-badge, ' +
      '#pt-notifications-alert [data-counter-num], ' +
      '#pt-notifications-notice [data-counter-num]'
    ));
    var count = 0;
    nodes.forEach(function (node) {
      var raw = '';
      var value;
      if (!node) return;
      raw = node.getAttribute('data-counter-num') || node.getAttribute('data-counter-text') || node.getAttribute('aria-label') || node.textContent || '';
      value = parseInt(String(raw).replace(/[^0-9]/g, ''), 10);
      if (!isNaN(value)) count += value;
    });
    return count;
  }
  function pvzCollectNotificationEntries() {
    var entries = [];
    var seen = {};
    var selectors = [
      '.mw-echo-ui-notificationItemWidget a[href]',
      '.mw-echo-notification a[href]',
      '.mw-echo-notifications a[href]',
      '.mw-notification a[href]',
      '.mw-notifications a[href]',
      '#pt-notifications-alert a[href]',
      '#pt-notifications-notice a[href]'
    ];
    pvzArray(document.querySelectorAll(selectors.join(', '))).forEach(function (link) {
      var label = pvzNormalizeText(link.textContent || link.getAttribute('title') || link.getAttribute('aria-label') || '');
      var href = link.getAttribute('href');
      var key;
      if (!href) return;
      if (!label || /^(알림|공지|Notifications?|Alerts?|Notices?)$/i.test(label)) return;
      key = href + '|' + label;
      if (seen[key]) return;
      seen[key] = true;
      entries.push({
        label: label,
        href: href,
        source: link
      });
    });
    return entries.slice(0, 8);
  }
  function pvzCreateAlertEmptySection() {
    var section = document.createElement('section');
    var heading = document.createElement('h2');
    var list = document.createElement('ul');
    var item = document.createElement('li');
    var empty = document.createElement('span');
    section.className = 'pvz-custom-mobile-tools-section pvz-timeless-alert-section';
    section.setAttribute('data-pvz-tools-section', 'alerts');
    heading.className = 'pvz-custom-mobile-tools-heading';
    heading.textContent = '알림';
    list.className = 'pvz-custom-mobile-tools-list';
    item.className = 'pvz-custom-mobile-tools-item';
    empty.className = 'pvz-timeless-alert-empty';
    empty.textContent = '새 알림이 없습니다.';
    item.appendChild(empty);
    list.appendChild(item);
    section.appendChild(heading);
    section.appendChild(list);
    return section;
  }
  function pvzAlertsSection() {
    var entries = pvzCollectNotificationEntries();
    var count = pvzNativeNotificationCount();
    if (!entries.length && count < 1) {
      return pvzCreateAlertEmptySection();
    }
    if (!entries.length && count > 0) {
      entries.push({ label: '알림 확인하기', href: pvzMakeSpecialUrl('Notifications') });
    }
    entries.push({ label: '전체 알림', href: pvzMakeSpecialUrl('Notifications') });
    entries.push({ label: '환경설정', href: pvzMakeSpecialUrl('Preferences') });
    return pvzCreateToolsSection('알림', entries, 'alerts');
  }
  function pvzRefreshTimelessAlertsPanel() {
    var panel = document.getElementById('pvz-timeless-panel-alerts');
    var body = panel && panel.querySelector('.pvz-timeless-sheet-body');
    if (!body) return;
    body.innerHTML = '';
    body.appendChild(pvzAlertsSection());
  }
  function pvzBuildTimelessWideRail(side, sections, includeLogo) {
    var rail = document.createElement('aside');
    var logo;
    var img;
    rail.className = 'pvz-timeless-wide-rail pvz-timeless-wide-rail-' + side;
    rail.setAttribute('aria-label', side === 'left' ? '왼쪽 보조창' : '오른쪽 보조창');
    if (includeLogo) {
      logo = document.createElement('a');
      logo.className = 'pvz-timeless-wide-logo';
      logo.href = pvzMakePageUrl(pvzMainPageName());
      logo.title = '식물 vs 좀비 위키';
      logo.setAttribute('aria-label', '식물 vs 좀비 위키로 이동');
      img = document.createElement('img');
      img.src = '/wiki/Special:Redirect/file/PvZKR_Wiki_Logo2.png';
      img.alt = 'Plants vs. Zombies 위키';
      img.loading = 'eager';
      logo.appendChild(img);
      rail.appendChild(logo);
    }
    (sections || []).filter(Boolean).forEach(function (section) {
      rail.appendChild(section);
    });
    return rail;
  }
  function pvzBuildTimelessWideTools(navEntries, wikiEntries, moreEntries, manageEntries, categoryEntries) {
    var leftHost = document.querySelector('body.skin-timeless #mw-site-navigation') || document.getElementById('mw-site-navigation');
    var rightHost = document.querySelector('body.skin-timeless #mw-related-navigation') || document.getElementById('mw-related-navigation');
    var leftRail;
    var rightRail;
    pvzArray(document.querySelectorAll('.pvz-timeless-wide-rail')).forEach(function (node) {
      if (node && node.parentNode) node.parentNode.removeChild(node);
    });
    if (!leftHost && !rightHost) return;
    leftRail = pvzBuildTimelessWideRail('left', [
      pvzCreateToolsSection('둘러보기', navEntries, 'navigation'),
      pvzCreateToolsSection('위키 도구', wikiEntries, 'wiki')
    ], true);
    rightRail = pvzBuildTimelessWideRail('right', [
      pvzCreateToolsSection('문서 도구', moreEntries, 'document'),
      pvzCreateToolsSection('분류', categoryEntries, 'category'),
      pvzCreateToolsSection('위키 관리', manageEntries, 'management')
    ], false);
    if (leftHost) leftHost.appendChild(leftRail);
    else document.body.insertBefore(leftRail, document.body.firstChild);
    if (rightHost) rightHost.appendChild(rightRail);
    else document.body.appendChild(rightRail);
  }
  function pvzBuildMobileCategoryBox(categorySection) {
    var old = document.querySelector('.pvz-mobile-category-box');
    var box;
    var content;
    if (old && old.parentNode) old.parentNode.removeChild(old);
    if (!categorySection) return;
    box = document.createElement('div');
    box.className = 'pvz-mobile-category-box';
    box.appendChild(categorySection);
    content = document.getElementById('mw-content-text') || document.querySelector('#bodyContent, .mw-body-content, #mw-content, #content');
    if (content && content.parentNode) {
      if (content.nextSibling) content.parentNode.insertBefore(box, content.nextSibling);
      else content.parentNode.appendChild(box);
    } else {
      document.body.appendChild(box);
    }
  }
  function pvzBindTimelessPanelEvents() {
    if (window.PVZ_TIMELESS_PANEL_EVENTS_BOUND) return;
    window.PVZ_TIMELESS_PANEL_EVENTS_BOUND = true;
    document.addEventListener('click', function (event) {
      var themeToggle = event.target && event.target.closest ? event.target.closest('[data-pvz-timeless-theme-mode]') : null;
      var button = event.target && event.target.closest ? event.target.closest('.pvz-timeless-action-button[data-pvz-timeless-panel], .pvz-timeless-page-action[data-pvz-timeless-panel]') : null;
      var panel = event.target && event.target.closest ? event.target.closest('.pvz-timeless-sheet') : null;
      var backdrop = event.target && event.target.closest ? event.target.closest('#pvz-timeless-panel-backdrop') : null;
      if (themeToggle) {
        event.preventDefault();
        event.stopPropagation();
        pvzApplyTimelessColorMode(themeToggle.getAttribute('data-pvz-timeless-theme-mode'));
        pvzCloseTimelessPanels();
        window.setTimeout(pvzBuildCustomMobileTools, 0);
        return;
      }
      if (button) {
        event.preventDefault();
        event.stopPropagation();
        pvzToggleTimelessPanel(button.getAttribute('data-pvz-timeless-panel'));
        return;
      }
      if (backdrop) {
        event.preventDefault();
        pvzCloseTimelessPanels();
        return;
      }
      if (!panel) pvzCloseTimelessPanels();
    }, true);
    document.addEventListener('keydown', function (event) {
      if (event.key === 'Escape') pvzCloseTimelessPanels();
    });
    pvzHandleTimelessViewportResize();
    window.addEventListener('resize', function () {
      pvzHandleTimelessViewportResize();
    }, { passive: true });
    if (
      window.screen &&
      window.screen.orientation &&
      typeof window.screen.orientation.addEventListener === 'function'
    ) {
      window.screen.orientation.addEventListener('change', function () {
        pvzQueueTimelessPanelViewportRefresh();
      });
    } else {
      window.addEventListener('orientationchange', function () {
        pvzQueueTimelessPanelViewportRefresh();
      }, { passive: true });
    }
    if (window.visualViewport && typeof window.visualViewport.addEventListener === 'function') {
      window.visualViewport.addEventListener('resize', function () {
        pvzHandleTimelessViewportResize();
      }, { passive: true });
      /* visualViewport.scroll은 등록하지 않는다.
        주소창 접힘/스크롤만으로 패널 좌표를 계속 재계산하지 않는다. */
    }
  }
  function pvzBuildCustomMobileTools() {
    var oldShell = document.querySelector('.pvz-custom-mobile-tools-shell, .pvz-custom-mobile-tools');
    var oldCategory = document.querySelector('.pvz-mobile-category-box');
    var navSection;
    var manageSection;
    var wikiSection;
    var moreSection;
    var documentSection;
    var alertsSection;
    var categorySection;
    var createdKeys = {};
    var navEntries;
    var manageEntries;
    var wikiEntries;
    var moreEntries;
    var categoryEntries;
    function unique(entry) {
      var key;
      if (!entry) return null;
      key = entry.href + '|' + entry.label;
      if (createdKeys[key]) return null;
      createdKeys[key] = true;
      return entry;
    }
    if (!pvzIsTimeless() || !document.body) return;
    document.body.classList.remove('pvz-custom-mobile-tools-ready');
    if (oldShell && oldShell.parentNode) oldShell.parentNode.removeChild(oldShell);
    if (oldCategory && oldCategory.parentNode) oldCategory.parentNode.removeChild(oldCategory);
    pvzArray(document.querySelectorAll('.pvz-timeless-wide-rail')).forEach(function (node) {
      if (node && node.parentNode) node.parentNode.removeChild(node);
    });
    navEntries = [
      unique(pvzToolEntryWithFallback('대문', 'mainpage', {
        ids: [/n-mainpage/i, /n-main-page/i],
        href: [/\/wiki\/(%EC%8B%9D%EB%AC%BC_vs_%EC%A2%80%EB%B9%84_%EC%9C%84%ED%82%A4|식물_vs_좀비_위키|Main_Page)/i],
        text: [/^대문$/, /^Main page$/i]
      }, pvzMakePageUrl(pvzMainPageName()))),
      unique(pvzToolEntryWithFallback('최근 바뀜', 'recentchanges', {
        ids: [/n-recentchanges/i],
        href: [/Special:(RecentChanges|최근바뀜)/i, /특수:(최근바뀜|RecentChanges)/i],
        text: [/^최근 바뀜$/, /^Recent changes$/i]
      }, pvzMakeSpecialUrl('RecentChanges'))),
      unique(pvzToolEntryWithFallback('미디어위키 도움말', 'help', {
        ids: [/n-help/i],
        href: [/mediawiki\.org.*Help/i, /Help:Contents/i],
        text: [/미디어위키 도움말/, /^도움말$/, /MediaWiki help/i, /^Help$/i]
      }, 'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents')),
      unique(pvzToolEntryWithFallback('특수 문서 목록', 'specialpages', {
        ids: [/n-specialpages/i, /t-specialpages/i],
        href: [/Special:(SpecialPages|특수문서)/i, /특수:(특수문서|SpecialPages)/i],
        text: [/특수 문서 목록/, /Special pages/i]
      }, pvzMakeSpecialUrl('SpecialPages'))),
      unique(pvzToolEntryWithFallback('무작위 문서', 'random', {
        ids: [/n-randompage/i, /n-random/i],
        href: [/Special:(Random|RandomPage|임의문서)/i, /특수:(임의문서|Random|RandomPage)/i],
        text: [/임의 문서/, /무작위 문서/, /Random page/i]
      }, pvzMakeSpecialUrl('Random')))
    ].filter(Boolean);
    moreEntries = [
      unique(pvzToolEntry('여기를 가리키는 문서', 'whatlinkshere', {
        ids: [/t-whatlinkshere/i],
        href: [/Special:(WhatLinksHere|가리키는문서)/i, /특수:(가리키는문서|WhatLinksHere)/i],
        text: [/여기를 가리키는 문서/, /What links here/i]
      })),
      unique(pvzToolEntry('가리키는 글의 최근 바뀜', 'recentchangeslinked', {
        ids: [/t-recentchangeslinked/i],
        href: [/Special:(RecentChangesLinked|가리키는글의최근바뀜)/i, /특수:(가리키는글의최근바뀜|RecentChangesLinked)/i],
        text: [/가리키는 글의 최근 바뀜/, /Related changes/i]
      })),
      unique(pvzToolEntry('고유 링크', 'permalink', {
        ids: [/t-permalink/i],
        href: [/oldid=/i],
        text: [/고유 링크/, /Permanent link/i]
      })),
      unique(pvzToolEntry('문서 정보', 'info', {
        ids: [/t-info/i, /ca-info/i],
        href: [/action=info/i],
        text: [/문서 정보/, /Page information/i]
      })),
      unique(pvzToolEntry('문서 기록', 'history', {
        ids: [/ca-history/i],
        href: [/action=history/i],
        text: [/문서 역사/, /문서 기록/, /^역사$/, /^History$/i, /^View history$/i]
      }))
    ].filter(Boolean);
    wikiEntries = [
      unique(pvzToolEntry('파일 올리기', 'upload', {
        ids: [/t-upload/i, /pt-upload/i],
        href: [/Special:(Upload|올리기)/i, /특수:(올리기|Upload)/i],
        text: [/^파일 올리기$/, /^Upload file$/i]
      })),
      unique(pvzToolEntry('축약된 URL 얻기', 'shorturl', {
        ids: [/t-urlshortener/i, /t-shorturl/i],
        href: [/Special:(UrlShortener|ShortUrl|URLShortener)/i, /특수:(UrlShortener|ShortUrl|축약)/i],
        text: [/축약된 URL/, /Short URL/i, /Get shortened URL/i]
      }))
    ].filter(Boolean);
    manageEntries = [
      unique(pvzToolEntryWithFallback('특수 문서', 'specialpages', {
        href: [/Special:(SpecialPages|특수문서)/i, /특수:(SpecialPages|특수문서)/i],
        text: [/^특수 문서$/, /^Special pages$/i]
      }, pvzMakeSpecialUrl('SpecialPages'))),
      unique(pvzToolEntryWithFallback('핵심 설정 관리', 'managewiki-core', {
        href: [/Special:ManageWiki(?:\/core)?(?:[/?#]|$)/i, /특수:ManageWiki(?:\/core)?(?:[/?#]|$)/i],
        text: [/이 위키의 핵심 설정을 관리하기/, /Manage this wiki's core settings/i]
      }, pvzMakeSpecialUrl('ManageWiki/core'))),
      unique(pvzToolEntryWithFallback('확장 기능 관리', 'managewiki-extensions', {
        href: [/Special:ManageWiki\/extensions/i, /특수:ManageWiki\/extensions/i],
        text: [/이 위키의 확장 기능을 관리하기/, /Manage this wiki's extensions/i]
      }, pvzMakeSpecialUrl('ManageWiki/extensions'))),
      unique(pvzToolEntryWithFallback('권한 관리', 'managewiki-permissions', {
        href: [/Special:ManageWiki\/permissions/i, /특수:ManageWiki\/permissions/i],
        text: [/이 위키의 권한 관리/, /Manage this wiki's permissions/i]
      }, pvzMakeSpecialUrl('ManageWiki/permissions'))),
      unique(pvzToolEntryWithFallback('이름공간 관리', 'managewiki-namespaces', {
        href: [/Special:ManageWiki\/namespaces/i, /특수:ManageWiki\/namespaces/i],
        text: [/이 위키의 이름공간 관리/, /Manage this wiki's namespaces/i]
      }, pvzMakeSpecialUrl('ManageWiki/namespaces'))),
      unique(pvzToolEntryWithFallback('백업 관리', 'datadump', {
        href: [/Special:(DataDump|ManageWiki\/backup|ManageWiki\/backups)/i, /특수:(DataDump|ManageWiki\/backup|ManageWiki\/backups)/i],
        text: [/이 위키의 백업을 관리/, /Manage this wiki's backups/i, /^DataDump$/i]
      }, pvzMakeSpecialUrl('DataDump')))
    ].filter(Boolean);
    categoryEntries = pvzCategoryEntriesForCustomBox().map(unique).filter(Boolean);
    navSection = pvzCreateToolsSection('둘러보기', navEntries, 'navigation');
    moreSection = pvzCreateToolsSection('더 보기', moreEntries, 'more');
    documentSection = pvzCreateToolsSection('문서 도구', moreEntries, 'document');
    wikiSection = pvzCreateToolsSection('위키 도구', wikiEntries, 'wiki');
    manageSection = pvzCreateToolsSection('위키 관리', manageEntries, 'management');
    alertsSection = pvzAlertsSection();
    categorySection = pvzCreateToolsSection('분류', categoryEntries, 'category');
    pvzEnsureTimelessPageActions();
    pvzEnsureTimelessActionbar();
    pvzEnsureTimelessPanelBackdrop();
    pvzCreateTimelessPanel('browse', '둘러보기', [navSection]);
    pvzCreateTimelessPanel('settings', '위키 도구', [wikiSection, manageSection]);
    pvzCreateTimelessPanel('document', '문서 도구', [documentSection]);
    pvzCreateTimelessPanel('more', '더 보기', [moreSection]);
    pvzCreateTimelessPanel('alerts', '알림', [alertsSection]);
    pvzCreateTimelessPanel('account', '계정', [pvzAccountSection()]);
    pvzBuildMobileCategoryBox(categorySection);
    pvzBuildTimelessWideTools(navEntries, wikiEntries, moreEntries, manageEntries, categoryEntries);
    pvzBindTimelessPanelEvents();
    document.body.classList.add('pvz-custom-mobile-tools-ready');
    document.body.classList.add('pvz-timeless-actions-ready');
    pvzHideNativeTimelessToolPortlets();
    pvzRemoveTimelessColorStripes();
  }
  function pvzStabilizeCustomToc() {
    var toc = document.querySelector('.pvz-custom-toc');
    var list = toc && toc.querySelector('.pvz-custom-toc-list');
    if (!toc || !list || toc.classList.contains('is-collapsed')) return;
    toc.classList.add('pvz-toc-reflowing');
    window.requestAnimationFrame(function () {
      var width;
      toc.classList.remove('pvz-toc-reflowing');
      /*
        첫 렌더링 때 Timeless/폰트 로딩/float 계산이 겹치면
        목차 항목의 max-content 폭이 너무 좁게 잡히는 경우가 있다.
        offsetWidth를 한 번 읽고 클래스를 붙여 강제로 레이아웃을 재계산한다.
      */
      width = toc.offsetWidth;
      toc.style.setProperty('--pvz-toc-measured-width', width + 'px');
      toc.classList.add('pvz-toc-layout-ready');
    });
  }
  function pvzQueueTocStabilize() {
    pvzStabilizeCustomToc();
    window.setTimeout(pvzStabilizeCustomToc, 80);
    window.setTimeout(pvzStabilizeCustomToc, 250);
    window.setTimeout(pvzStabilizeCustomToc, 700);
    if (document.fonts && document.fonts.ready && typeof document.fonts.ready.then === 'function') {
      document.fonts.ready.then(function () {
        pvzStabilizeCustomToc();
      });
    }
  }
  function pvzCreateCustomToc() {
    var contentText = document.getElementById('mw-content-text');
    var parserOutput = contentText && (
      contentText.querySelector('.mw-parser-output') || contentText
    );
    var leadContainer = parserOutput && (
      parserOutput.querySelector(':scope > .mf-section-0') ||
      parserOutput.querySelector(':scope > section.mf-section-0') ||
      parserOutput
    );
    var existingWrap;
    var nativeTocs;
    var headings;
    var leadParagraph;
    var wrap;
    var toc;
    var head;
    var title;
    var toggle;
    var list;
    var counters = [0, 0, 0, 0, 0];
    function getDepth(heading) {
      var level = parseInt(heading.tagName.replace('H', ''), 10);
      var depth = level - 1;
      if (depth < 1) depth = 1;
      if (depth > 5) depth = 5;
      return depth;
    }
    function getNumber(depth) {
      var i;
      var parts = [];
      counters[depth - 1] += 1;
      for (i = depth; i < counters.length; i += 1) {
        counters[i] = 0;
      }
      for (i = 0; i < depth; i += 1) {
        if (!counters[i]) counters[i] = 1;
        parts.push(counters[i]);
      }
      return parts.join('.');
    }
    function isExcludedLeadContainer(element) {
      if (!element || !element.matches) return false;
      return element.matches(
        'table, figure, .thumb, .tright, .tleft, .floatright, .floatleft, .floatnone, ' +
        '.infobox, .pvz-cp-infobox, .pvz-character-infobox, .character-infobox, ' +
        '.navbox, .metadata, .catlinks, .mw-references-wrap, ' +
        '.pvz-message, .pvz-custom-toc-wrap, #toc, .toc'
      );
    }
    function isLeadParagraph(element) {
      var clone;
      var text;
      if (
        !element ||
        element.parentNode !== leadContainer ||
        element.tagName !== 'P'
      ) {
        return false;
      }
      if (isExcludedLeadContainer(element)) return false;
      if (
        element.querySelector(
          'style, script, template, noscript, ' +
          'link[rel="mw-deduplicated-inline-style"], ' +
          '[data-mw-deduplicate]'
        )
      ) {
        return false;
      }
      clone = element.cloneNode(true);
      clone.querySelectorAll(
        'figure, .thumb, .mw-file-description, .mw-default-size, ' +
        '.mw-image-border, .reference, sup, .mw-editsection, ' +
        'style, script, template, noscript, link, [data-mw-deduplicate]'
      ).forEach(function (node) {
        node.remove();
      });
      text = (clone.textContent || '').replace(/\s+/g, ' ').trim();
      if (!text) return false;
      if (
        /^(?:\.mw-parser-output\b|@media\b|@supports\b|@layer\b|:root\b)/.test(text) &&
        /[{};]/.test(text)
      ) {
        return false;
      }
      return true;
    }
    function findLeadParagraph() {
      var children;
      var i;
      var child;
      if (!leadContainer) return null;
      children = pvzArray(leadContainer.children);
      for (i = 0; i < children.length; i += 1) {
        child = children[i];
        if (isExcludedLeadContainer(child)) continue;
        if (isLeadParagraph(child)) {
          return child;
        }
      }
      return null;
    }
    function placeTocAfterLead(tocWrap, paragraph) {
      if (
        !tocWrap ||
        !paragraph ||
        paragraph.parentNode !== leadContainer
      ) {
        return false;
      }
      if (
        tocWrap.parentNode === leadContainer &&
        paragraph.nextSibling === tocWrap
      ) {
        return true;
      }
      leadContainer.insertBefore(tocWrap, paragraph.nextSibling);
      return true;
    }
    if (
      !parserOutput ||
      !leadContainer ||
      parserOutput.querySelector('.pvz-no-custom-toc')
    ) {
      return;
    }
    leadParagraph = findLeadParagraph();
    if (!leadParagraph) return;
    existingWrap = parserOutput.querySelector('.pvz-custom-toc-wrap');
    if (existingWrap) {
      placeTocAfterLead(existingWrap, leadParagraph);
      pvzQueueTocStabilize();
      return;
    }
    headings = pvzArray(
      parserOutput.querySelectorAll('h2, h3, h4, h5, h6')
    ).filter(function (heading) {
      return !pvzSkipHeading(heading);
    });
    if (headings.length < 2) return;
    nativeTocs = pvzArray(
      parserOutput.querySelectorAll('#toc, .toc')
    ).filter(function (node) {
      return node && !(
        node.classList &&
        node.classList.contains('pvz-custom-toc')
      );
    });
    nativeTocs.forEach(function (old) {
      if (old && old.parentNode) {
        old.parentNode.removeChild(old);
      }
    });
    wrap = document.createElement('div');
    wrap.className = 'pvz-custom-toc-wrap';
    toc = document.createElement('nav');
    toc.className = 'pvz-custom-toc';
    toc.setAttribute('aria-label', '목차');
    head = document.createElement('div');
    head.className = 'pvz-custom-toc-head';
    title = document.createElement('div');
    title.className = 'pvz-custom-toc-title';
    title.textContent = '목차';
    toggle = document.createElement('button');
    toggle.type = 'button';
    toggle.className = 'pvz-custom-toc-toggle';
    toggle.setAttribute('aria-expanded', 'true');
    toggle.textContent = '접기';
    toggle.addEventListener('click', function (event) {
      var collapsed;
      event.preventDefault();
      collapsed = !toc.classList.contains('is-collapsed');
      toc.classList.toggle('is-collapsed', collapsed);
      toggle.setAttribute(
        'aria-expanded',
        collapsed ? 'false' : 'true'
      );
      toggle.textContent = collapsed ? '펼치기' : '접기';
    });
    head.appendChild(title);
    head.appendChild(toggle);
    list = document.createElement('ol');
    list.className = 'pvz-custom-toc-list';
    headings.forEach(function (heading, index) {
      var depth = getDepth(heading);
      var level = depth + 1;
      var text = pvzHeadingText(heading);
      var anchor = pvzHeadingId(heading, text, index + 1);
      var number = getNumber(depth);
      var item = document.createElement('li');
      var link = document.createElement('a');
      var numberSpan = document.createElement('span');
      var textSpan = document.createElement('span');
      item.className =
        'pvz-custom-toc-item pvz-custom-toc-level-' +
        level +
        ' pvz-custom-toc-depth-' +
        depth;
      link.href = '#' + anchor;
      numberSpan.className = 'pvz-custom-toc-number';
      numberSpan.textContent = number;
      textSpan.className = 'pvz-custom-toc-text';
      textSpan.textContent = text;
      link.appendChild(numberSpan);
      link.appendChild(textSpan);
      item.appendChild(link);
      list.appendChild(item);
    });
    toc.appendChild(head);
    toc.appendChild(list);
    wrap.appendChild(toc);
    placeTocAfterLead(wrap, leadParagraph);
    pvzQueueTocStabilize();
  }
  var pvzFooterGapTimer = null;
  function pvzGetFooterElement() {
    return document.getElementById('mw-footer-container') ||
      document.getElementById('footer') ||
      document.querySelector('.mw-footer') ||
      document.querySelector('footer');
  }
  function pvzUpdateFooterBackdropGapClass() {
    var footer = pvzGetFooterElement();
    var html = document.documentElement;
    var body = document.body;
    var rect;
    var viewportHeight;
    var docHeight;
    var hasGap;
    if (!html || !body || !footer || !footer.getBoundingClientRect) return;
    html.classList.remove('pvz-footer-backdrop-gap-needed');
    body.classList.remove('pvz-footer-backdrop-gap-needed');
    rect = footer.getBoundingClientRect();
    viewportHeight = window.visualViewport && window.visualViewport.height ?
      window.visualViewport.height :
      window.innerHeight;
    docHeight = Math.max(
      body.scrollHeight || 0,
      body.offsetHeight || 0,
      html.clientHeight || 0,
      html.scrollHeight || 0,
      html.offsetHeight || 0
    );
    /* footer container의 실제 바닥이 viewport 아래까지 닿지 못할 때만 켠다.
      3px 여유값은 확대/축소 반올림 오차 방지용이다. */
    hasGap = rect.bottom < viewportHeight - 3 && docHeight <= viewportHeight + 6;
    if (hasGap) {
      html.classList.add('pvz-footer-backdrop-gap-needed');
      body.classList.add('pvz-footer-backdrop-gap-needed');
    }
  }
  function pvzQueueFooterBackdropGapCheck() {
    window.clearTimeout(pvzFooterGapTimer);
    pvzFooterGapTimer = window.setTimeout(pvzUpdateFooterBackdropGapClass, 80);
  }
  function pvzStartFooterBackdropGapWatcher() {
    pvzUpdateFooterBackdropGapClass();
    window.setTimeout(pvzUpdateFooterBackdropGapClass, 120);
    window.setTimeout(pvzUpdateFooterBackdropGapClass, 400);
    window.setTimeout(pvzUpdateFooterBackdropGapClass, 1000);
    window.addEventListener('resize', pvzQueueFooterBackdropGapCheck, { passive: true });
    window.addEventListener('orientationchange', pvzQueueFooterBackdropGapCheck, { passive: true });
    window.addEventListener('load', pvzQueueFooterBackdropGapCheck, { passive: true });
    if (window.visualViewport) {
      window.visualViewport.addEventListener('resize', pvzQueueFooterBackdropGapCheck, { passive: true });
      window.visualViewport.addEventListener('scroll', pvzQueueFooterBackdropGapCheck, { passive: true });
    }
    if (window.MutationObserver && document.body) {
      var observer = new MutationObserver(pvzQueueFooterBackdropGapCheck);
      observer.observe(document.body, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['class', 'style']
      });
    }
  }
  function pvzInit() {
    if (!pvzIsTimeless()) return;
    pvzApplyInitialTimelessColorMode();
    pvzBindSystemThemeListener();
    pvzBuildCustomSearch();
    pvzMarkReady();
    pvzRemoveTimelessColorStripes();
    pvzStartFooterBackdropGapWatcher();
    pvzCreateCustomToc();
    pvzBuildCustomMobileTools();
    window.setTimeout(pvzBuildCustomSearch, 120);
    window.setTimeout(pvzBuildCustomMobileTools, 300);
    window.setTimeout(pvzBuildCustomSearch, 600);
    window.setTimeout(pvzBuildCustomMobileTools, 1000);
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', pvzInit);
  } else {
    pvzInit();
  }
  if (window.mw && mw.hook) {
    mw.hook('wikipage.content').add(function () {
      pvzInit();
    });
  }
  if (window.addEventListener) {
    window.addEventListener('resize', function () {
      var shell = document.querySelector('.pvz-custom-mobile-tools-shell');
      pvzApplyToolsCompactState(shell);
    });
  }
  /* =========================
  v94 쿠키 안내문 portal
  - CSS z-index만으로 footer 뒤에서 빠져나오지 못하는 경우를 해결한다.
  - 쿠키 안내문 DOM을 footer 밖, body 직속으로 옮겨 최상위 fixed 레이어로 표시한다.
  ========================= */
  function pvzLooksLikeCookieWarning(el) {
    var text;
    var idClass;
    if (!el || el.nodeType !== 1) return false;
    text = (el.textContent || '').replace(/\s+/g, ' ').trim();
    idClass = ((el.id || '') + ' ' + (el.className || '')).toLowerCase();
    if (text.indexOf('쿠키는 저희의 서비스 전달에 도움을 줍니다') !== -1) return true;
    if (text.indexOf('저희의 쿠키 사용에 동의') !== -1) return true;
    if (text.indexOf('쿠키') !== -1 && text.indexOf('서비스') !== -1 && text.length > 20) return true;
    if (idClass.indexOf('cookiewarning') !== -1) return true;
    if (idClass.indexOf('cookie-warning') !== -1) return true;
    if (idClass.indexOf('cookiewarning') !== -1) return true;
    if (idClass.indexOf('mw-cookie') !== -1) return true;
    if (idClass.indexOf('ext-cookie') !== -1) return true;
    return false;
  }
  function pvzFindCookieWarningElement() {
    var selectors = [
      '#mw-cookiewarning-container',
      '.mw-cookiewarning-container',
      '#mw-cookiewarning',
      '.mw-cookiewarning',
      '.mw-cookie-warning',
      '#cookieWarning',
      '.cookieWarning',
      '.cookie-warning',
      '.ext-cookie-warning',
      '.ext-cookie-warning-container',
      '[id*="cookiewarning" i]',
      '[class*="cookiewarning" i]',
      '[id*="cookie-warning" i]',
      '[class*="cookie-warning" i]',
      '[id*="cookieWarning" i]',
      '[class*="cookieWarning" i]',
      '[id*="mw-cookie" i]',
      '[class*="mw-cookie" i]',
      '[id*="ext-cookie" i]',
      '[class*="ext-cookie" i]'
    ];
    var candidates = [];
    var found;
    var all;
    var i;
    var el;
    var parent;
    selectors.forEach(function (selector) {
      try {
        pvzArray(document.querySelectorAll(selector)).forEach(function (node) {
          if (candidates.indexOf(node) === -1) candidates.push(node);
        });
      } catch (e) {}
    });
    for (i = 0; i < candidates.length; i += 1) {
      if (pvzLooksLikeCookieWarning(candidates[i])) return candidates[i];
    }
    all = document.body ? document.body.querySelectorAll('div, aside, section, p, span') : [];
    for (i = 0; i < all.length; i += 1) {
      el = all[i];
      if (pvzLooksLikeCookieWarning(el)) {
        parent = el.closest('#mw-cookiewarning-container, .mw-cookiewarning-container, #mw-cookiewarning, .mw-cookiewarning, .mw-cookie-warning, #cookieWarning, .cookieWarning, .cookie-warning, .ext-cookie-warning, .ext-cookie-warning-container, div, aside, section') || el;
        return parent;
      }
    }
    return null;
  }
  function pvzPortalCookieWarning() {
    var el = pvzFindCookieWarningElement();
    if (!el || !document.body) return;
    el.classList.add('pvz-cookie-warning-portal');
    if (el.parentNode !== document.body) {
      document.body.appendChild(el);
    }
  }
  function pvzInitCookieWarningPortal() {
    var observer;
    pvzPortalCookieWarning();
    window.setTimeout(pvzPortalCookieWarning, 250);
    window.setTimeout(pvzPortalCookieWarning, 1000);
    window.setTimeout(pvzPortalCookieWarning, 2500);
    if (window.MutationObserver && document.body) {
      observer = new MutationObserver(function () {
        pvzPortalCookieWarning();
      });
      observer.observe(document.body, {
        childList: true,
        subtree: true
      });
    }
  }
  pvzInitCookieWarningPortal();
  /* =========================
  액션바 계열 패널 문서 좌표 고정
  - Timeless 상단 sheet는 v122의 전용 위치 처리에서 직접 absolute 좌표를 계산한다.
  - 아래 document-lock은 그 외 액션바/드롭다운 패널에만 사용한다.
  ========================= */
  function pvzGetActionAndTopbarPanels() {
    var selectors = [
      '.pvz-timeless-panel.is-open',
      '.pvz-mobile-panel.is-open',
      '.pvz-timeless-menu.is-open',
      '.pvz-timeless-dropdown.is-open',
      '.pvz-actionbar-panel.is-open',
      '.pvz-action-panel.is-open',
      '.pvz-topbar-panel.is-open',
      '.pvz-topbar-menu.is-open',
      '.pvz-timeless-panel[aria-hidden="false"]',
      '.pvz-mobile-panel[aria-hidden="false"]',
      '.pvz-timeless-menu[aria-hidden="false"]',
      '.pvz-timeless-dropdown[aria-hidden="false"]',
      '[data-pvz-panel-open="true"]',
      '[data-pvz-sheet-open="true"]',
      '[data-pvz-menu-open="true"]'
    ];
    var panels = [];
    selectors.forEach(function (selector) {
      try {
        pvzArray(document.querySelectorAll(selector)).forEach(function (panel) {
          if (panels.indexOf(panel) === -1) panels.push(panel);
        });
      } catch (e) {}
    });
    return panels;
  }
  function pvzDocumentLockPanel(panel) {
    var rect;
    var scrollX;
    var scrollY;
    var docTop;
    var docLeft;
    var width;
    if (!panel || panel.nodeType !== 1) return;
    rect = panel.getBoundingClientRect();
    scrollX = window.pageXOffset || document.documentElement.scrollLeft || 0;
    scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
    docTop = rect.top + scrollY;
    docLeft = rect.left + scrollX;
    width = rect.width || panel.offsetWidth || 0;
    panel.dataset.pvzPanelDocumentLocked = 'true';
    panel.dataset.pvzPanelDocumentTop = String(docTop);
    panel.dataset.pvzPanelDocumentLeft = String(docLeft);
    panel.dataset.pvzPanelDocumentWidth = String(width);
    panel.style.setProperty('position', 'absolute', 'important');
    panel.style.setProperty('top', docTop + 'px', 'important');
    panel.style.setProperty('left', docLeft + 'px', 'important');
    panel.style.setProperty('right', 'auto', 'important');
    panel.style.setProperty('width', width + 'px', 'important');
    panel.style.setProperty('max-width', 'calc(100vw - 12px)', 'important');
    panel.style.setProperty('transform', 'none', 'important');
    panel.style.setProperty('z-index', '2147483000', 'important');
  }
  function pvzRestoreDocumentLockedPanel(panel) {
    var top;
    var left;
    var width;
    if (!panel || panel.nodeType !== 1) return;
    if (panel.dataset.pvzPanelDocumentLocked !== 'true') return;
    top = panel.dataset.pvzPanelDocumentTop;
    left = panel.dataset.pvzPanelDocumentLeft;
    width = panel.dataset.pvzPanelDocumentWidth;
    panel.style.setProperty('position', 'absolute', 'important');
    if (top) panel.style.setProperty('top', top + 'px', 'important');
    if (left) panel.style.setProperty('left', left + 'px', 'important');
    if (width) panel.style.setProperty('width', width + 'px', 'important');
    panel.style.setProperty('right', 'auto', 'important');
    panel.style.setProperty('transform', 'none', 'important');
  }
  function pvzDocumentLockOpenPanels() {
    pvzGetActionAndTopbarPanels().forEach(function (panel) {
      if (panel.dataset.pvzPanelDocumentLocked === 'true') {
        pvzRestoreDocumentLockedPanel(panel);
        return;
      }
      pvzDocumentLockPanel(panel);
    });
  }
  function pvzRecalculateDocumentLockedPanels() {
    pvzGetActionAndTopbarPanels().forEach(function (panel) {
      panel.removeAttribute('data-pvz-panel-document-locked');
      panel.removeAttribute('data-pvz-panel-document-top');
      panel.removeAttribute('data-pvz-panel-document-left');
      panel.removeAttribute('data-pvz-panel-document-width');
      panel.style.removeProperty('position');
      panel.style.removeProperty('top');
      panel.style.removeProperty('left');
      panel.style.removeProperty('right');
      panel.style.removeProperty('width');
      panel.style.removeProperty('max-width');
      panel.style.removeProperty('transform');
      window.setTimeout(function () {
        pvzDocumentLockPanel(panel);
      }, 0);
    });
  }
  function pvzInitPanelDocumentLock() {
    var observer;
    pvzDocumentLockOpenPanels();
    if (window.MutationObserver && document.body) {
      observer = new MutationObserver(function () {
        pvzDocumentLockOpenPanels();
      });
      observer.observe(document.body, {
        attributes: true,
        childList: true,
        subtree: true,
        attributeFilter: ['class', 'style', 'aria-hidden', 'data-pvz-panel-open', 'data-pvz-sheet-open', 'data-pvz-menu-open']
      });
    }
    /* 스크롤 중에는 좌표를 새로 계산하지 않는다.
      기존 코드가 fixed/top 값을 다시 넣는 경우만 문서 좌표 absolute 값으로 되돌린다. */
    window.addEventListener('scroll', function () {
      window.requestAnimationFrame(function () {
        pvzGetActionAndTopbarPanels().forEach(pvzRestoreDocumentLockedPanel);
      });
    }, { passive: true });
    window.addEventListener('resize', function () {
      window.clearTimeout(pvzInitPanelDocumentLock._resizeTimer);
      pvzInitPanelDocumentLock._resizeTimer = window.setTimeout(
        pvzRecalculateDocumentLockedPanels,
        260
      );
    }, { passive: true });
    function pvzQueueDocumentLockedPanelRecalculation() {
      window.setTimeout(pvzRecalculateDocumentLockedPanels, 520);
    }
    if (
      window.screen &&
      window.screen.orientation &&
      typeof window.screen.orientation.addEventListener === 'function'
    ) {
      window.screen.orientation.addEventListener(
        'change',
        pvzQueueDocumentLockedPanelRecalculation
      );
    } else {
      window.addEventListener(
        'orientationchange',
        pvzQueueDocumentLockedPanelRecalculation,
        { passive: true }
      );
    }
  }
  pvzInitPanelDocumentLock();
}());
}());