Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */
/* JavaScript used for https://zh.wikipedia.org/wiki/MediaWiki:Common.js : */

    /** metaBox
     *
     * Funcionament de la Plantilla:Metacaixa
     * Implementat per: Usuari:Peleguer.
     * Actualitzat per Joanjoc seguint les indicacions d'en Martorell
     */
     document.querySelector('.mw-logo-wordmark').textContent=''
    function MetaCaixaInit() {
      // S'executa al carregar-se la pàgina, si hi ha metacaixes,
      // s'assignen els esdeveniments als botons
      //alert("MetaCaixaInit");
      var i = 0; // Inicialitzem comptador de caixes
      for (i = 0; i <= 9; i++) {
        var vMc = document.getElementById("mc" + i);
        if (!vMc) break;
        //alert("MetaCaixaInit, trobada Metacaixa mc"+i);
        var j = 1; // Inicialitzem comptador de botons dins de la caixa
        var vPsIni = 0; // Pestanya visible inicial
        for (j = 1; j <= 9; j++) {
          var vBt = document.getElementById("mc" + i + "bt" + j);
          if (!vBt) break;
          //alert("MetaCaixaInit, trobat botó mc"+i+"bt"+j);
          vBt.onclick = MetaCaixaMostraPestanya; // A cada botó assignem l'esdeveniment onclick
          //alert (vBt.className);
          if (vBt.className == "mcBotoSel") vPsIni = j; // Si tenim un botó seleccionat, en guardem l'index
        }
        //alert ("mc="+i+", ps="+j+", psini="+vPsIni );
        if (vPsIni === 0) { // Si no tenim cap botó seleccionat, n'agafem un aleatòriament
          vPsIni = 1 + Math.floor((j - 1) * Math.random());
          //alert ("Activant Pestanya a l'atzar; _mc"+i+"bt"+vPsIni +"_");
          document.getElementById("mc" + i + "ps" + vPsIni).style.display = "block";
          document.getElementById("mc" + i + "ps" + vPsIni).style.visibility = "visible";
          document.getElementById("mc" + i + "bt" + vPsIni).className = "mcBotoSel";
        }
      }
    }

    function MetaCaixaMostraPestanya() {
      // S'executa al clicar una pestanya,
      // aquella es fa visible i les altres s'oculten
      var vMcNom = this.id.substr(0, 3); // A partir del nom del botó, deduïm el nom de la caixa
      var vIndex = this.id.substr(5, 1); // I l'index
      var i = 1;
      for (i = 1; i <= 9; i++) { // busquem totes les pestanyes d'aquella caixa
        //alert(vMcNom+"ps"+i);
        var vPsElem = document.getElementById(vMcNom + "ps" + i);
        if (!vPsElem) break;
        if (vIndex == i) { // Si és la pestanya bona la mostrem i canviem la classe de botó
          vPsElem.style.display = "block";
          vPsElem.style.visibility = "visible";
          document.getElementById(vMcNom + "bt" + i).className = "mcBotoSel";
        } else { // Sinó, l'ocultem i canviem la classe de botó
          vPsElem.style.display = "none";
          vPsElem.style.visibility = "hidden";
          document.getElementById(vMcNom + "bt" + i).className = "mcBoto";
        }
      }
      return false; // evitem la recàrrega de la pàgina
    }
    $(MetaCaixaInit);
    
 //BMV模板script
 (function(mw, $) {
    'use strict';

    function initBmvPlayer(wrapper) {
        var $wrapper = $(wrapper);
        
        var songName = $wrapper.data('song-name');
        var rawDifficulties = $wrapper.data('difficulties');

        var $difficultyDiv = $wrapper.find('.difficulty-div');
        var $ratioDiv = $wrapper.find('.ratio-div');
        var $outputSpan = $wrapper.find('.output-span');
        
        var difficultyColors = {
            "EZ": "#57E4C4", "HD": "#FDBA61", "IN": "#FE8661", "AT": "#4C364B"
        };

        function parseDifficulties(input) {
            if (!input) return null;
            try {
                return input.split(',').map(function(item) {
                    var match = item.trim().match(/^([A-Za-z]+)([\d]+[+-]?)$/); 
                    if (!match) {
                        throw new Error('Invalid difficulty format for item: ' + item);
                    }
                    return (match[1].toUpperCase() + ' ' + match[2]);
                });
            } catch (e) {
                console.error("Error parsing difficulties:", input, e);
                return null;
            }
        }


        var difficultyStates = parseDifficulties(rawDifficulties);

        if (!songName || !difficultyStates || difficultyStates.length === 0) {
            $wrapper.find('table').html('<div style="color:red; padding:10px; text-align:center;">错误: 缺少或无效的曲名/难度参数。</div>');
            return;
        }

        $difficultyDiv.attr('data-states', JSON.stringify(difficultyStates));

        function updateOutput() {
            var difficulty = $difficultyDiv.text().split(' ')[0];
            var ratio = $ratioDiv.text().replace(':', '-');
            var processedSongName = songName.replace(/[\\/:\*\?"<>\|]/g, '-');
            var encodedSongName = encodeURIComponent(processedSongName);
            var videoUrl = 'https://pan.rizwiki.cn/d/' + ratio + '_' + encodedSongName + '_' + difficulty + '.mp4';
            var isMobile = window.matchMedia("(max-width: 600px)").matches;
            var videoHeightStyle = isMobile ? '' : 'height:100%;';
            $outputSpan.html('<video class="html5media-video" src="' + videoUrl + '" controls preload="metadata" loading="lazy" style="' + videoHeightStyle + ' object-fit:contain;"></video>');
        }

        function cycleDifficulty() {
            var states = JSON.parse($difficultyDiv.attr('data-states'));
            var current = parseInt($difficultyDiv.attr('data-current'), 10);
            current = (current + 1) % states.length;
            
            var nextState = states[current];
            $difficultyDiv.text(nextState);
            $difficultyDiv.attr('data-current', current.toString());
            
            var difficultyType = nextState.split(' ')[0];
            $difficultyDiv.parent().css('background-color', difficultyColors[difficultyType] || '#FFFFFF');
            
            updateOutput();
        }

        function cycleRatio() {
            var states = JSON.parse($ratioDiv.attr('data-states'));
            var current = parseInt($ratioDiv.attr('data-current'), 10);
            current = (current + 1) % states.length;
            
            $ratioDiv.text(states[current]);
            $ratioDiv.attr('data-current', current.toString());
            
            updateOutput();
        }

        $difficultyDiv.on('click', cycleDifficulty);
        $ratioDiv.parent().on('click', cycleRatio);

        // 初始化
        (function initialize() {
            var firstDiff = difficultyStates[0];
            var diffType = firstDiff.split(' ')[0];
            
            $difficultyDiv.text(firstDiff);
            $difficultyDiv.parent().css('background-color', difficultyColors[diffType]);
            
            updateOutput();
        })();
    }

    mw.hook('wikipage.content').add(function($content) {
        // 初始化 BMV 播放器
        $content.find('.bmv-player-uninitialized').each(function() {
            initBmvPlayer(this);
            $(this).removeClass('bmv-player-uninitialized');
        });

/* 绿豆诡计播放器js */
        var $player = $('#riz-player');
        if ($player.length > 0) {
            if ($player.parent().is('body') === false) {
                $player.appendTo('body');
            }

            if ($player.data('initialized')) return;
            $player.data('initialized', true);

            var lastScrollTop = 0;
            var scrollThreshold = 10;

            $(window).on('scroll resize', function() {
                if (window.innerWidth > 768) {
                    $player.removeClass('rp-scroll-down');
                    return;
                }

                var st = $(this).scrollTop();
                
                if (Math.abs(lastScrollTop - st) <= scrollThreshold) return;
                if (st > lastScrollTop && st > 50) {
                    $player.addClass('rp-scroll-down');
                } else {
                    $player.removeClass('rp-scroll-down');
                }
                lastScrollTop = st;
            });

            var songUrl = $player.data('url');
            if (!songUrl) {
                console.error("RizPlayer Error: No audio URL found.");
                return;
            }

            // 封面图修复逻辑
            var $coverImg = $player.find('.rp-cover-img');
            if ($coverImg.length === 0) $coverImg = $player.find('.rp-cover img');
            
            var backupSrc = $player.data('image');
            
            function fixCoverImage() {
                var currentSrc = $coverImg.attr('src');
                if ((!currentSrc || currentSrc === "" || currentSrc === "undefined") && backupSrc) {
                    $coverImg.attr('src', backupSrc);
                }
            }
            fixCoverImage();

            var audio = new Audio(songUrl);
            audio.preload = "none";

            // Media Session API 集成
            function updateMediaSession() {
                if ('mediaSession' in navigator) {
                    try {
                        navigator.mediaSession.metadata = new MediaMetadata({
                            title: $player.find('.rp-title').text() || '未知曲目',
                            artist: $player.find('.rp-artist').text() || '未知艺术家',
                            album: 'RizWiki',
                            artwork: [{
                                src: backupSrc || '',
                                sizes: '512x512',
                                type: 'image/jpeg'
                            }]
                        });
                    } catch(e) {
                        console.log('Media Session metadata error:', e);
                    }
                }
            }

            var $toggleBtn = $player.find('.rp-btn-toggle');
            var $sideBtn = $player.find('.rp-side-ctrl');
            var $progressWrap = $player.find('.rp-progress-wrap');
            var $progressBar = $player.find('.rp-progress-bar');
            var $progressCurrent = $player.find('.rp-progress-current');
            var $progressHandle = $player.find('.rp-progress-handle');
            var $curTime = $player.find('.rp-cur');
            var $durTime = $player.find('.rp-dur');

            $player.css('display', 'flex');

            function formatTime(seconds) {
                if (isNaN(seconds)) return "00:00";
                var m = Math.floor(seconds / 60);
                var s = Math.floor(seconds % 60);
                return (m < 10 ? "0" + m : m) + ":" + (s < 10 ? "0" + s : s);
            }

            function togglePlay(e) {
                if(e) e.stopPropagation();
                if (audio.paused) {
                    var playPromise = audio.play();
                    if (playPromise !== undefined) {
                        playPromise.then(function() {
                            $player.addClass('playing');
                            if (!$player.hasClass('riz-player-expanded')) {
                                $player.addClass('riz-player-expanded');
                            }
                        }).catch(function(error) {
                            console.error("播放失败:", error);
                        });
                    }
                } else {
                    audio.pause();
                    $player.removeClass('playing');
                }
            }

            if ('mediaSession' in navigator) {
                try {
                    navigator.mediaSession.setActionHandler('play', function() {
                        audio.play();
                        $player.addClass('playing');
                    });

                    navigator.mediaSession.setActionHandler('pause', function() {
                        audio.pause();
                        $player.removeClass('playing');
                    });

                    navigator.mediaSession.setActionHandler('seekbackward', function() {
                        audio.currentTime = Math.max(audio.currentTime - 10, 0);
                    });

                    navigator.mediaSession.setActionHandler('seekforward', function() {
                        audio.currentTime = Math.min(audio.currentTime + 10, audio.duration || 0);
                    });

                    navigator.mediaSession.setActionHandler('seekto', function(details) {
                        if (details.seekTime !== null) {
                            audio.currentTime = details.seekTime;
                        }
                    });
                } catch(e) {
                    console.log('Media Session handlers error:', e);
                }
            }

            updateMediaSession();

            // 绑定点击事件
            $toggleBtn.on('click', togglePlay);
            $sideBtn.on('click', togglePlay);

            // 展开/折叠
            $player.on('click', function(e) {
                if ($(e.target).closest('.rp-progress-wrap, .rp-side-ctrl, .rp-btn-toggle').length > 0) return;
                $player.toggleClass('riz-player-expanded');
            });

            var isDragging = false;

            $progressWrap.on('mousedown touchstart', function(e) {
                isDragging = true;
                $progressWrap.addClass('dragging');
                updateProgressFromEvent(e);
            });

            $(document).on('mousemove touchmove', function(e) {
                if (isDragging) {
                    e.preventDefault();
                    updateProgressFromEvent(e);
                }
            });

            $(document).on('mouseup touchend', function(e) {
                if (isDragging) {
                    isDragging = false;
                    $progressWrap.removeClass('dragging');
                    if (audio.duration) {
                        var percent = parseFloat($progressCurrent[0].style.width) / 100;
                        audio.currentTime = percent * audio.duration;
                        if(audio.paused) {
                             audio.play();
                             $player.addClass('playing');
                        }
                    }
                }
            });

            function updateProgressFromEvent(e) {
                var clientX = e.clientX;
                if (e.type.includes('touch')) {
                    clientX = e.originalEvent.touches[0].clientX;
                }
                var offset = $progressWrap.offset();
                var width = $progressWrap.width();
                var relX = clientX - offset.left;
                var percent = (relX / width) * 100;
                if (percent < 0) percent = 0;
                if (percent > 100) percent = 100;

                $progressCurrent.css('width', percent + '%');
                $progressHandle.css('left', percent + '%');
                if (audio.duration) {
                    var dragTime = (percent / 100) * audio.duration;
                    $curTime.text(formatTime(dragTime));
                }
            }

            audio.addEventListener('timeupdate', function() {
                if (!isDragging && audio.duration) {
                    var percent = (audio.currentTime / audio.duration) * 100;
                    $progressCurrent.css('width', percent + '%');
                    $progressHandle.css('left', percent + '%');
                    $curTime.text(formatTime(audio.currentTime));

                    if ('mediaSession' in navigator && 'setPositionState' in navigator.mediaSession) {
                        try {
                            navigator.mediaSession.setPositionState({
                                duration: audio.duration,
                                playbackRate: audio.playbackRate,
                                position: audio.currentTime
                            });
                        } catch(e) {
                        }
                    }
                }
            });

            audio.addEventListener('loadedmetadata', function() {
                $durTime.text(formatTime(audio.duration));

                updateMediaSession();
                if ('mediaSession' in navigator && 'setPositionState' in navigator.mediaSession) {
                    try {
                        navigator.mediaSession.setPositionState({
                            duration: audio.duration,
                            playbackRate: audio.playbackRate,
                            position: 0
                        });
                    } catch(e) {
                    }
                }
            });

            audio.addEventListener('ended', function() {
                $player.removeClass('playing');
                $progressCurrent.css('width', '0%');
                $progressHandle.css('left', '0%');

                if ('mediaSession' in navigator && 'setPositionState' in navigator.mediaSession) {
                    try {
                        navigator.mediaSession.setPositionState({
                            duration: audio.duration,
                            playbackRate: audio.playbackRate,
                            position: 0
                        });
                    } catch(e) {
                    }
                }
            });

            audio.addEventListener('error', function(e) {
                console.error("音频加载错误:", audio.src);
            });
        }
    });

})(mediaWiki, jQuery);

/* PWA */
if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker.register('/sw.js').then(function(registration) {
        }, function(err) {
        });
    });
}

	// 点击短链接复制
    mw.hook('wikipage.content').add(function() {
        var shortlink = document.querySelector('.title-shortlink')
        
        if (shortlink) {
            shortlink.removeAttribute('href')
            shortlink.style.cursor = 'pointer'
            shortlink.title = '点击复制'
            
            shortlink.addEventListener('click', function(e) {
                e.preventDefault()
                
                navigator.clipboard.writeText(this.textContent).then(function() {
                    var parent = shortlink.parentNode
                    parent.removeChild(shortlink)
                    parent.insertBefore(shortlink, shortlink.nextSibling)
                    shortlink.title = '已复制'
                    
                    setTimeout(function() {
                        shortlink.title = '点击复制'
                    }, 1000)
                }).catch(function(err) {
                    console.error('复制失败: ', err)
                    shortlink.title = '复制失败'
                    var parent = shortlink.parentNode
                    parent.removeChild(shortlink)
                    parent.insertBefore(shortlink, shortlink.nextSibling)
                })
            })
        }
    })
//随机推荐
$(function() {
    var $source = $('#hidden-recommendation-source');
    var $target = $('.mw-parser-output');
    if ($source.length > 0 && $target.length > 0) {
        var $cards = $source.find('.citizen-related-card');
        if ($cards.length > 3) {
            var cardsArray = $cards.toArray();
            cardsArray.sort(function() { return 0.5 - Math.random() });
            var $selectedCards = $(cardsArray.slice(0, 3));
            $source.find('.citizen-related-grid').empty().append($selectedCards);
        }
        var $container = $('<div id="custom-read-more-section"></div>');
        $container.append($source.contents());
        $source.remove();
        $target.append($container);
    }
});

/* Tag处理 */
$(function() {
    var $dataSpans = $('.riz-page-tag-data');
    if ($dataSpans.length === 0) return;

    var allTags = new Set();
    $dataSpans.each(function() {
        var tags = ($(this).attr('data-tags') || '').split(',');
        tags.forEach(function(t) {
            t = t.trim();
            if (t) allTags.add(t);
        });
    });

    if (allTags.size === 0) return;

    var $ul = $('<ul>');
    
    Array.from(allTags).forEach(function(tag) {
        var url = mw.util.getUrl('Special:Search', { search: '', fulltext: '1' }) + '&tags%5B%5D=' + encodeURIComponent(tag);
        
        var $a = $('<a>')
            .attr('href', url)
            .attr('title', '搜索标签:' + tag)
            .text(tag);
            
        $('<li>').append($a).appendTo($ul);
    });

    var $container = $('<div>')
        .attr('id', 'page-tags')
        .addClass('catlinks citizen-page-tags mw-collapsible mw-collapsed');

    var $header = $('<div>')
        .addClass('mw-normal-catlinks')
        .css({ 'border': 'none', 'background': 'transparent', 'padding': '0' });
        
    $header.append('<b>页面标签</b>');
    
    var $content = $('<div>')
        .addClass('mw-collapsible-content')
        .css({ 'margin-top': '5px', 'padding-top': '5px', 'border-top': '1px solid var(--border-color-subtle, #eaecf0)' })
        .append($ul);

    $container.append($header).append($content);

    var $catlinks = $('#catlinks');
    if ($catlinks.length) {
        $catlinks.before($container);
    } else {
        $('#mw-content-text').append($container);
    }

    mw.loader.using(['jquery.makeCollapsible'], function() {
        $container.makeCollapsible();
    });
});

/* songinfo PE/PC 切换 */
$(function () {
  $(document).on('click', '.platform-tab', function () {
    var $box = $(this).closest('.song-infobox');
    $box.toggleClass('show-pc', $(this).data('platform') === 'pc');
    $(this).siblings('.platform-tab').removeClass('active');
    $(this).addClass('active');
  });
  $('table').has('math').addClass('has-math');
  $('.navbox').not(':has(.mw-collapsed)').addClass('navbox-not-collapsed');
});

// Logo SVG 动画控制
(function() {
    'use strict';
    $(document).ready(function() {
        var $logo = $('.mw-logo-wordmark');

        if ($logo.length === 0) return;

        // 点击logo时重新播放动画
        $logo.on('click', function(e) {
            e.preventDefault();
            var timestamp = new Date().getTime();
            var currentBg = $logo.css('background-image');
            var svgUrl = '/extensions/Rizwiki_logo_animated.svg?t=' + timestamp;

            $logo.css('background-image', 'url("' + svgUrl + '")');
        });
    });
})();

// wikitable 表头固定
function initStickyHeader($content) {
  const container = $content instanceof jQuery ? $content[0] : $content;

  function processTable(table, thead) {
    if (!thead) {
      thead = table.querySelector('thead');
    }
    if (!thead) {
      return;
    }

    const headerRow = thead.querySelector('tr');
    if (!headerRow || headerRow.querySelectorAll('th').length === 0) {
      return;
    }

    let scrollParent = table.parentElement;
    while (scrollParent && scrollParent !== document.body) {
      const style = getComputedStyle(scrollParent);
      if (style.overflowX === 'auto' || style.overflowX === 'scroll') {
        break;
      }
      scrollParent = scrollParent.parentElement;
    }

    const stickyHeader = document.createElement('div');
    stickyHeader.style.position = 'fixed';
    stickyHeader.style.display = 'none';
    stickyHeader.style.zIndex = '1000';
    stickyHeader.style.overflow = 'hidden';

    const stickyTable = document.createElement('table');
    stickyTable.className = table.className;
    stickyTable.style.margin = '0';

    const stickyThead = document.createElement('thead');
    stickyThead.innerHTML = headerRow.outerHTML;
    stickyTable.appendChild(stickyThead);
    stickyHeader.appendChild(stickyTable);
    document.body.appendChild(stickyHeader);

    stickyThead.addEventListener('click', function(e) {
      const target = e.target;
      if (target.tagName === 'TH') {
        const index = Array.from(target.parentElement.children).indexOf(target);
        const originalTh = headerRow.children[index];
        if (originalTh) {
          originalTh.click();
        }
      }
    });

    function updateStickyHeader() {
      const stickyNav = document.querySelector('.citizen-sticky-header');
      const headerHeight = stickyNav ? stickyNav.offsetHeight : 0;

      const tableRect = table.getBoundingClientRect();
      const originalCells = headerRow.querySelectorAll('th');
      const stickyCells = stickyThead.querySelectorAll('th');

      stickyHeader.style.top = headerHeight + 'px';
      stickyTable.style.width = table.offsetWidth + 'px';

      originalCells.forEach((cell, i) => {
        if (stickyCells[i]) {
          stickyCells[i].style.width = cell.offsetWidth + 'px';
          stickyCells[i].className = cell.className;
          if (cell.hasAttribute('aria-sort')) {
            stickyCells[i].setAttribute('aria-sort', cell.getAttribute('aria-sort'));
          } else {
            stickyCells[i].removeAttribute('aria-sort');
          }
        }
      });

      if (scrollParent && scrollParent !== document.body) {
        const scrollLeft = scrollParent.scrollLeft;
        const parentRect = scrollParent.getBoundingClientRect();
        stickyHeader.style.left = parentRect.left + 'px';
        stickyHeader.style.width = parentRect.width + 'px';
        stickyTable.style.transform = `translateX(${-scrollLeft}px)`;
      } else {
        stickyTable.style.transform = 'translateX(0)';
      }

      if (tableRect.top < headerHeight && tableRect.bottom > headerHeight) {
        stickyHeader.style.display = 'block';
      } else {
        stickyHeader.style.display = 'none';
      }
    }

    window.addEventListener('scroll', updateStickyHeader, { passive: true });
    if (scrollParent && scrollParent !== document.body) {
      scrollParent.addEventListener('scroll', updateStickyHeader, { passive: true });
    }
    window.addEventListener('resize', updateStickyHeader, { passive: true });

    updateStickyHeader();
  }

  const tables = container.querySelectorAll('table.wikitable');
  tables.forEach(table => {
    let attempts = 0;
    const checkAndProcess = () => {
      const thead = table.querySelector('thead');
      if (thead) {
        processTable(table, thead);
      } else if (attempts < 40) {
        attempts++;
        setTimeout(checkAndProcess, 50);
      }
    };
    checkAndProcess();
  });
}

mw.hook('wikipage.content').add(initStickyHeader);

//外链拦截
(function () {
  var CONFIRM_BASE = 'https://riz.wiki/';

  function isWhitelistedHost(hostname) {
    var host = (hostname || '').toLowerCase();
    if (!host) return false;
    if (/^([a-z0-9-]+\.)*rizwiki\.[a-z0-9-]+$/.test(host)) return true;
    if (host === 'riz.wiki' || host.endsWith('.riz.wiki')) return true;
    if (host === 'guguwo.top' || host.endsWith('.guguwo.top')) return true;
    if (host === 'miukar.com' || host.endsWith('.miukar.com')) return true;
    return false;
  }

  function isExternal(a) {
    try {
      var u = new URL(a.href, location.href);
      return u.origin !== location.origin;
    } catch (_) {
      return false;
    }
  }

  function encodeB64UrlSafe(str) {
    var bytes = new TextEncoder().encode(str);
    var bin = '';
    for (var i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
    return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
  }

  document.addEventListener('click', function (e) {
    var a = e.target.closest && e.target.closest('a[href]');
    if (!a) return;
    if (e.defaultPrevented) return;
    if (e.button !== 0) return;
    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
    if (a.target === '_blank' || a.hasAttribute('download')) return;
    if (!isExternal(a)) return;

    try {
      var u = new URL(a.href, location.href);
      if (!/^https?:$/.test(u.protocol)) return;
      if (isWhitelistedHost(u.hostname)) return;

      e.preventDefault();
      var toB64 = encodeB64UrlSafe(u.toString());
      var next = CONFIRM_BASE + '?confirm=1&to_b64=' + encodeURIComponent(toB64);
      location.href = next;
    } catch (_) {}
  }, true);
})();

/* 泡泡背景 */
(function() {
  'use strict';

  var bubbleEnabled = localStorage.getItem('citizen-bubble-background') === 'true';

  var themeClass = 'citizen-bubble-theme-enabled';

  var backgroundSelectors = [
    '.citizen-page-container',
    '.citizen-body-container',
    '.mw-body',
    '#content',
    '#mw-content-text',
    '.mw-page-container',
    '.mw-body-content',
    '.citizen-section',
    'main',
    '.mw-header',
    '.citizen-header',
    'html',
    '.skin-citizen'
  ];

  function ensureBubbleThemeStyle() {
    if (document.getElementById('citizen-bubble-theme-style')) return;

    var style = document.createElement('style');
    style.id = 'citizen-bubble-theme-style';
    style.textContent = [
      'html.' + themeClass + ', html.' + themeClass + ' body { background: transparent !important; background-color: transparent !important; background-image: none !important; }',
      'html.' + themeClass + ' .citizen-page-container, html.' + themeClass + ' .citizen-body-container, html.' + themeClass + ' .citizen-body, html.' + themeClass + ' .citizen-body-header, html.' + themeClass + ' .citizen-body-content, html.' + themeClass + ' .citizen-page-header, html.' + themeClass + ' .citizen-page-sidebar, html.' + themeClass + ' .citizen-footer, html.' + themeClass + ' .mw-body, html.' + themeClass + ' #content, html.' + themeClass + ' #mw-content-text, html.' + themeClass + ' .mw-page-container, html.' + themeClass + ' .mw-body-content, html.' + themeClass + ' .mw-parser-output, html.' + themeClass + ' .citizen-section, html.' + themeClass + ' main { background: transparent !important; background-color: transparent !important; background-image: none !important; }',
      'html.' + themeClass + ' .citizen-section::before, html.' + themeClass + ' .citizen-section::after { background: transparent !important; background-color: transparent !important; background-image: none !important; }'
    ].join('\n');
    document.head.appendChild(style);
  }

  function addToggleSwitch() {
    var appearanceSection = document.querySelector('.citizen-preferences-section__content');
    if (!appearanceSection) {
      setTimeout(addToggleSwitch, 100);
      return;
    }

    if (document.getElementById('skin-client-prefs-citizen-bubble-background')) {
      return;
    }

    var toggleHTML = '<span class="cdx-toggle-switch cdx-toggle-switch--align-switch citizen-preferences-group">' +
      '<input id="skin-client-prefs-citizen-bubble-background" class="cdx-toggle-switch__input" type="checkbox" role="switch" aria-describedby="bubble-bg-desc" ' + (bubbleEnabled ? 'checked' : '') + '>' +
      '<span class="cdx-toggle-switch__switch"><span class="cdx-toggle-switch__switch__grip"></span></span>' +
      '<div class="cdx-label cdx-toggle-switch__label">' +
      '<label class="cdx-label__label" for="skin-client-prefs-citizen-bubble-background">' +
      '<span class="cdx-label__label__text">浮动泡泡背景</span>' +
      '</label>' +
      '<span id="bubble-bg-desc" class="cdx-label__description">显示动态泡泡背景效果</span>' +
      '</div></span>';

    appearanceSection.insertAdjacentHTML('beforeend', toggleHTML);

    var toggle = document.getElementById('skin-client-prefs-citizen-bubble-background');
    if (toggle) {
      toggle.addEventListener('change', function() {
        bubbleEnabled = this.checked;
        localStorage.setItem('citizen-bubble-background', bubbleEnabled);
        toggleBubbleCanvas(bubbleEnabled);
      });
    }
  }

  function eachElement(selectors, callback) {
    selectors.forEach(function(selector) {
      var elements = document.querySelectorAll(selector);
      elements.forEach(callback);
    });
  }

  function toggleBubbleCanvas(enabled) {
    var canvas = document.getElementById('bubble-canvas');
    if (canvas) {
      canvas.style.display = enabled ? 'block' : 'none';
    }
    if (enabled) {
      ensureBubbleThemeStyle();
      document.documentElement.classList.add(themeClass);
      document.body.style.background = window.matchMedia('(prefers-color-scheme: light)').matches ? '#F8F9FC' : '#1A1B2E';
      document.body.style.overflowX = 'hidden';
      eachElement(backgroundSelectors, function(el) {
        el.style.background = 'transparent';
        el.style.backgroundColor = 'transparent';
      });
      if (window.bubbleAnimation) {
        window.bubbleAnimation.start();
      }
    } else {
      document.documentElement.classList.remove(themeClass);
      document.body.style.background = '';
      document.body.style.overflowX = '';
      eachElement(backgroundSelectors, function(el) {
        el.style.background = '';
        el.style.backgroundColor = '';
        el.style.backgroundImage = '';
      });
      if (window.bubbleAnimation) {
        window.bubbleAnimation.stop();
      }
    }
  }

  function init() {
    ensureBubbleThemeStyle();
    addToggleSwitch();

    var canvas = document.createElement('canvas');
    canvas.id = 'bubble-canvas';
    document.body.appendChild(canvas);

    function setCanvasStyle() {
      canvas.style.position = 'fixed';
      canvas.style.top = '0';
      canvas.style.left = '0';
      canvas.style.width = '100vw';
      canvas.style.height = '160vh';
      canvas.style.pointerEvents = 'none';
      canvas.style.zIndex = '-9999';
      canvas.style.filter = 'saturate(1.35) contrast(1.1)';
      canvas.style.margin = '0';
      canvas.style.padding = '0';
      canvas.style.display = bubbleEnabled ? 'block' : 'none';
    }
    setCanvasStyle();
    setTimeout(setCanvasStyle, 100);
    setTimeout(setCanvasStyle, 500);

    if (bubbleEnabled) {
      document.documentElement.classList.add(themeClass);
      document.body.style.background = '#1A1B2E';
      document.body.style.overflowX = 'hidden';

      if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
        document.body.style.background = '#F8F9FC';
      }

      eachElement(backgroundSelectors, function(el) {
        el.style.background = 'transparent';
        el.style.backgroundColor = 'transparent';
      });
    }

    startBubbles(canvas);
  }

  function startBubbles(canvas) {
    var ctx = canvas.getContext('2d');
    var DARK = !window.matchMedia('(prefers-color-scheme: light)').matches;
    var COLORS = ['#57E4C4', '#B8A9C9', '#F5C842'];
    var W, H, viewportH, bubbles = [], mx = -9999, my = -9999;
    var animationId = null;
    var isRunning = false;
    var resizeTimer = null;

    function resize() {
      var vv = window.visualViewport;
      var dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));
      var baseW = Math.ceil(Math.max(
        vv ? vv.width : 0,
        window.innerWidth || 0,
        document.documentElement.clientWidth || 0
      ));
      var baseH = Math.ceil(Math.max(
        vv ? vv.height : 0,
        window.innerHeight || 0,
        document.documentElement.clientHeight || 0
      ));
      var extraH = Math.ceil(Math.max(500, baseH * 0.65));
      var extraW = 80;

      viewportH = baseH;
      W = baseW + extraW;
      H = baseH + extraH;

      canvas.style.width = W + 'px';
      canvas.style.height = H + 'px';
      canvas.width = Math.ceil(W * dpr);
      canvas.height = Math.ceil(H * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }

    function srand(seed) {
      var s = seed >>> 0;
      return function() {
        s = Math.imul(s^(s>>>17),0xd4d9c3b7)>>>0;
        s = Math.imul(s^(s>>>13),0x9e3779b9)>>>0;
        return (s>>>0)/0x100000000;
      };
    }

    function rgb(hex) {
      var n = parseInt(hex.slice(1),16);
      return [(n>>16)&255,(n>>8)&255,n&255];
    }

    function initBubbles() {
      var r = srand(20240315);
      bubbles = [];

      [[.08,.04],[.92,.08],[.03,.28],[.97,.22],[.01,.52],[.99,.48],
       [.07,.73],[.93,.67],[.02,.88],[.98,.92],[.14,.97],[.86,.98]]
      .forEach(function(p,i){
        bubbles.push({
          x:p[0]*W,
          y:p[1]*H,
          r:90+r()*130,
          c:COLORS[i%3],
          a:DARK?.28:.18,
          vx:(r()-.5)*.28,
          vy:(r()-.5)*.28
        });
      });

      var scale = Math.min(2, Math.max(1, H / Math.max(viewportH || H, 1)));
      var mediumCount = Math.round(18 * scale);
      var smallCount = Math.round(22 * scale);

      for(var i=0;i<mediumCount;i++) {
        bubbles.push({
          x:r()*W,
          y:r()*H,
          r:28+r()*65,
          c:COLORS[Math.floor(r()*3)],
          a:DARK?.17:.12,
          vx:(r()-.5)*.38,
          vy:(r()-.5)*.38
        });
      }

      for(var i=0;i<smallCount;i++) {
        bubbles.push({
          x:r()*W,
          y:r()*H,
          r:8+r()*20,
          c:COLORS[Math.floor(r()*3)],
          a:DARK?.24:.16,
          vx:(r()-.5)*.55,
          vy:(r()-.5)*.55
        });
      }
    }

    function tick() {
      bubbles.forEach(function(b){
        b.x+=b.vx;
        b.y+=b.vy;

        if(b.x-b.r>W) b.x=-b.r;
        else if(b.x+b.r<0) b.x=W+b.r;
        if(b.y-b.r>H) b.y=-b.r;
        else if(b.y+b.r<0) b.y=H+b.r;

        var dx=b.x-mx, dy=b.y-my, d=Math.sqrt(dx*dx+dy*dy), rr=170+b.r;
        if(d<rr&&d>0){
          var f=(rr-d)/rr*1.9;
          b.vx+=dx/d*f*.06;
          b.vy+=dy/d*f*.06;
        }

        b.vx*=.984;
        b.vy*=.984;

        var sp=Math.sqrt(b.vx*b.vx+b.vy*b.vy);
        if(sp<.07){
          b.vx+=(Math.random()-.5)*.04;
          b.vy+=(Math.random()-.5)*.04;
        }

        if(sp>2.8){
          b.vx*=2.8/sp;
          b.vy*=2.8/sp;
        }
      });
    }

    function draw() {
      ctx.clearRect(0,0,W,H);
      bubbles.forEach(function(b){
        var c=rgb(b.c);
        var g2=ctx.createRadialGradient(b.x,b.y,0,b.x,b.y,b.r);
        g2.addColorStop(0,"rgba("+c[0]+","+c[1]+","+c[2]+","+b.a+")");
        g2.addColorStop(0.45,"rgba("+c[0]+","+c[1]+","+c[2]+","+(b.a*.65)+")");
        g2.addColorStop(1,"rgba("+c[0]+","+c[1]+","+c[2]+",0)");
        ctx.beginPath();
        ctx.arc(b.x,b.y,b.r,0,Math.PI*2);
        ctx.fillStyle=g2;
        ctx.fill();
      });
    }

    function loop() {
      if (!isRunning) return;
      tick();
      draw();
      animationId = requestAnimationFrame(loop);
    }

    function startAnimation() {
      if (isRunning) return;
      isRunning = true;
      loop();
    }

    function stopAnimation() {
      isRunning = false;
      if (animationId) {
        cancelAnimationFrame(animationId);
        animationId = null;
      }
    }

    window.bubbleAnimation = {
      start: startAnimation,
      stop: stopAnimation
    };

    function scheduleResize() {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(function() {
        resize();
        initBubbles();
        if (isRunning) {
          draw();
        }
      }, 150);
    }

    window.addEventListener('resize', scheduleResize);
    window.addEventListener('orientationchange', scheduleResize);

    if (window.visualViewport) {
      window.visualViewport.addEventListener('resize', scheduleResize);
      window.visualViewport.addEventListener('scroll', scheduleResize);
    }

    window.addEventListener('load', function(){
      resize();
      initBubbles();
    });

    window.addEventListener('mousemove', function(e){
      mx=e.clientX;
      my=e.clientY;
    });

    window.addEventListener('touchmove', function(e){
      if(e.touches.length){
        mx=e.touches[0].clientX;
        my=e.touches[0].clientY;
      }
    }, {passive:true});

    window.addEventListener('mouseleave', function(){
      mx=-9999;
      my=-9999;
    });

    resize();
    initBubbles();
    if (bubbleEnabled) {
      startAnimation();
    }
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();
Cookies help us deliver our services. By using our services, you agree to our use of cookies.