打开/关闭菜单
443
1190
40
4718
植物大战僵尸杂交版Wiki
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

MediaWiki:Common.js:修订间差异

MediaWiki界面页面
无编辑摘要
无编辑摘要
 
第1,158行: 第1,158行:
                 idle: 'https://new.pvzhe.wiki/images/1/10/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E5%BE%85%E6%9C%BA.gif',
                 idle: 'https://new.pvzhe.wiki/images/1/10/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E5%BE%85%E6%9C%BA.gif',
                 petting: 'https://new.pvzhe.wiki/images/4/47/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E6%8A%9A%E6%91%B8.gif',
                 petting: 'https://new.pvzhe.wiki/images/4/47/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E6%8A%9A%E6%91%B8.gif',
                 size: 150
                 size: 100
             },
             },
             '小猫向日葵': {
             '小猫向日葵': {
                 idle: 'https://new.pvzhe.wiki/images/9/9b/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E5%BE%85%E6%9C%BA.gif',
                 idle: 'https://new.pvzhe.wiki/images/9/9b/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E5%BE%85%E6%9C%BA.gif',
                 petting: 'https://new.pvzhe.wiki/images/8/85/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E6%8A%9A%E6%91%B8.gif',
                 petting: 'https://new.pvzhe.wiki/images/8/85/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E6%8A%9A%E6%91%B8.gif',
                 size: 200
                 size: 100
             }
             }
         };
         };

2026年6月13日 (六) 06:03的最新版本

/* 这里的任何JavaScript将为所有用户在每次页面加载时加载。 */

// 自动加载 MediaWiki:Footer 页面内容,并插入到每个页面底部
$(document).ready(function() {
    const namespace = mw.config.get('wgNamespaceNumber');
    const action = mw.config.get('wgAction');
    
    if (namespace !== 0 || action !== 'view') {
        return;
    }

    fetch("/api.php?action=parse&page=MediaWiki:Footer&format=json")
        .then(res => res.json())
        .then(data => {
            if (data.parse && data.parse.text) {
                const html = data.parse.text['*'];
                $('#mw-content-text').append('<div class="global-footer">' + html + '</div>');
            }
        });
});

// 用户颜色、图标、状态、师徒、昵称、里程碑、公告、植物/僵尸筛选、好友、私信 主逻辑
$(function () {
    // ==================== 动态注入彩虹样式 ====================
    var rainbowStyle = document.createElement('style');
    rainbowStyle.textContent =
        '.rainbow-user {' +
            'font-weight: bold;' +
            'background: repeating-linear-gradient(90deg, red 0px, orange 10px, yellow 20px, green 30px, blue 40px, indigo 50px, violet 60px);' +
            '-webkit-background-clip: text;' +
            '-webkit-text-fill-color: transparent;' +
            'background-clip: text;' +
        '}';
    document.head.appendChild(rainbowStyle);

    // ==================== 配置区 ====================
    var mentorList = ['愤怒的郎朗', 'Yuyaabc', '虚位以待', '知更鸟头号粉丝', '凌玥'];
    var newbieEditThreshold = 25;
    var milestoneThresholds = [10, 50, 100, 500, 1000, 2500, 5000, 10000, 20000, 50000];

    // ==================== 辅助函数 ====================
    function getUserName(link) {
        var $span = $(link).find('span').first();
        if ($span.length) return $span.text().trim();
        return $(link).text().trim();
    }

    function isUserLink(link) {
        return $(link).is('a.mw-userlink') || $(link).find('span').length > 0;
    }

    function getLevelIcons(editcount) {
        var totalStars = Math.floor(editcount / 100);
        if (totalStars === 0) return '';

        var crowns = Math.floor(totalStars / 125);
        var remaining = totalStars % 125;
        var suns = Math.floor(remaining / 25);
        remaining %= 25;
        var moons = Math.floor(remaining / 5);
        var stars = remaining % 5;

        var icons = '';
        if (crowns > 0) icons += '👑'.repeat(crowns);
        if (suns > 0)   icons += '☀️'.repeat(suns);
        if (moons > 0)  icons += '🌙'.repeat(moons);
        if (stars > 0)  icons += '⭐'.repeat(stars);
        return icons;
    }

    function updateLevelIcons($link, editcount) {
        var iconStr = getLevelIcons(editcount);
        var $iconSpan = $link.next('.user-level-icons');
        if (iconStr === '') {
            $iconSpan.remove();
            return;
        }
        if ($iconSpan.length === 0) {
            $iconSpan = $('<span class="user-level-icons"></span>');
            $link.after($iconSpan);
        }
        $iconSpan.text(iconStr);
    }

    function getTitleByEditcount(ec) {
        if (ec >= 10000) return '🐉 神话';
        if (ec >= 5000)  return '👑 传奇';
        if (ec >= 2500)  return '🏆 大师';
        if (ec >= 1000)  return '💎 专家';
        if (ec >= 500)   return '🔥 资深';
        if (ec >= 100)   return '⭐ 活跃';
        if (ec >= 50)    return '🌳 入门';
        if (ec >= 10)    return '🌿 新手';
        return '🌱 萌新';
    }

    function addTitleTag($link, editcount) {
        if ($link.data('title-tag-added')) return;
        $link.data('title-tag-added', true);
        var title = getTitleByEditcount(editcount);
        var $tag = $('<span class="user-title-tag">' + title + '</span>');
        $link.after($tag);
    }

    // ==================== 核心:颜色 + 图标 + 状态 + 师徒 + 昵称 ====================
    function colorizeAndStatus($links) {
        if ($links.length === 0) return;

        var $fresh = $links.filter(function () {
            return !$(this).data('user-status-processed');
        });
        if ($fresh.length === 0) return;

        var users = [];
        $fresh.each(function () {
            var name = getUserName(this);
            if (name && users.indexOf(name) === -1) users.push(name);
        });
        if (users.length === 0) return;

        $fresh.each(function () {
            $(this).data('user-status-processed', true);
        });

        var batchSize = 50;
        var batches = [];
        for (var i = 0; i < users.length; i += batchSize) {
            batches.push(users.slice(i, i + batchSize));
        }

        var processBatch = function (batch) {
            var api = new mw.Api();
            return api.get({
                action: 'query',
                list: 'users',
                ususers: batch.join('|'),
                usprop: 'editcount'
            }).then(function (data) {
                var classMap = {};
                var editCountMap = {};
                if (data.query && data.query.users) {
                    data.query.users.forEach(function (u) {
                        var ec = u.editcount || 0;
                        editCountMap[u.name] = ec;
                        if (ec >= 5000) classMap[u.name] = 'rainbow-user';
                        else if (ec >= 2500) classMap[u.name] = 'gold-user';
                        else if (ec >= 1000) classMap[u.name] = 'platinum-user';
                        else if (ec >= 500) classMap[u.name] = 'silver-user';
                        else if (ec >= 1) classMap[u.name] = 'bronze-user';
                    });
                }
                $fresh.each(function () {
                    var $this = $(this);
                    var name = getUserName(this);
                    var cls = classMap[name];
                    if (cls) {
                        $this.removeClass('bronze-user silver-user platinum-user gold-user rainbow-user');
                        $this.addClass(cls);
                    }
                    var ec = editCountMap[name];
                    if (typeof ec !== 'undefined') {
                        updateLevelIcons($this, ec);
                        var isInsideCard = $this.closest('.citizen-menu_card-content, .citizen-userMenu').length > 0;
                        if (!isInsideCard) {
                            addTitleTag($this, ec);
                        }
                    }
                });
            });
        };

        var colorPromise = $.Deferred().resolve();
        batches.forEach(function (batch) {
            colorPromise = colorPromise.then(function () {
                return processBatch(batch);
            });
        });

        $fresh.each(function () {
            if (isUserLink(this)) {
                var isInsideCard = $(this).closest('.citizen-menu_card-content, .citizen-userMenu').length > 0;
                if (!isInsideCard) {
                    var username = getUserName(this);
                    addStatusDot($(this), username);
                    addMentorTag($(this), username);
                }
            }
        });
    }

    // ==================== 状态圆点 ====================
    function addStatusDot($link, username) {
        if ($link.data('status-dot-added')) return;
        $link.data('status-dot-added', true);

        var $dot = $('<span class="user-status-dot status-offline" title="离线"></span>');
        $link.after($dot);

        var cacheKey = 'mw_user_status_' + mw.config.get('wgDBname') + '_' + username;
        var cached = localStorage.getItem(cacheKey);
        var now = Date.now();
        if (cached) {
            try {
                var data = JSON.parse(cached);
                if (now - data.timestamp < 5 * 60 * 1000) {
                    updateDotStyle($dot, data.lastEditTime);
                    return;
                }
            } catch (e) {}
        }

        var api = new mw.Api();
        api.get({
            action: 'query',
            list: 'usercontribs',
            ucuser: username,
            uclimit: 1,
            ucprop: 'timestamp'
        }).then(function (data) {
            var lastEditTime = null;
            if (data.query && data.query.usercontribs && data.query.usercontribs.length > 0) {
                lastEditTime = data.query.usercontribs[0].timestamp;
            }
            localStorage.setItem(cacheKey, JSON.stringify({
                lastEditTime: lastEditTime,
                timestamp: now
            }));
            updateDotStyle($dot, lastEditTime);
        }).fail(function () {});
    }

    function updateDotStyle($dot, lastEditTime) {
        if (!lastEditTime) {
            $dot.attr('title', '离线');
            $dot.removeClass('status-online status-away').addClass('status-offline');
            return;
        }
        var last = new Date(lastEditTime).getTime();
        var diffMinutes = (Date.now() - last) / 60000;
        if (diffMinutes < 15) {
            $dot.attr('title', '在线(15分钟内活跃)');
            $dot.removeClass('status-offline status-away').addClass('status-online');
        } else if (diffMinutes < 60) {
            $dot.attr('title', '近期活跃(1小时内)');
            $dot.removeClass('status-offline status-online').addClass('status-away');
        } else {
            $dot.attr('title', '离线');
            $dot.removeClass('status-online status-away').addClass('status-offline');
        }
    }

    // ==================== 师徒标签 ====================
    function addMentorTag($link, username) {
        if ($link.data('mentor-tag-added')) return;
        if ($link.next('.user-tag').length) return;

        var $tag;

        if (mentorList.indexOf(username) !== -1) {
            $link.data('mentor-tag-added', true);
            $tag = $('<span class="user-tag user-tag-mentor">导师</span>');
            $link.after($tag);
            return;
        }

        if ($link.data('newbie-tag-checked')) return;
        $link.data('newbie-tag-checked', true);

        var cacheKey = 'mw_newbie_check_' + mw.config.get('wgDBname') + '_' + username;
        var cached = localStorage.getItem(cacheKey);
        var now = Date.now();
        if (cached) {
            try {
                var data = JSON.parse(cached);
                if (now - data.timestamp < 60 * 60 * 1000) {
                    if (data.editcount <= newbieEditThreshold) {
                        $tag = $('<span class="user-tag user-tag-newbie">新手</span>');
                        $link.after($tag);
                    }
                    return;
                }
            } catch (e) {}
        }

        var api = new mw.Api();
        api.get({
            action: 'query',
            list: 'users',
            ususers: username,
            usprop: 'editcount'
        }).then(function (data) {
            var editcount = 0;
            if (data.query && data.query.users && data.query.users.length > 0) {
                editcount = data.query.users[0].editcount || 0;
            }
            localStorage.setItem(cacheKey, JSON.stringify({
                editcount: editcount,
                timestamp: now
            }));
            if (editcount <= newbieEditThreshold) {
                if ($link.next('.user-tag-newbie').length === 0) {
                    $tag = $('<span class="user-tag user-tag-newbie">新手</span>');
                    $link.after($tag);
                }
            }
        }).fail(function () {});
    }

    // ==================== 编辑里程碑弹窗 ====================
    var currentUser = mw.config.get('wgUserName');
    if (currentUser) {
        var api = new mw.Api();
        api.get({
            action: 'query',
            list: 'users',
            ususers: currentUser,
            usprop: 'editcount'
        }).then(function(data) {
            var editcount = 0;
            if (data.query && data.query.users && data.query.users.length > 0) {
                editcount = data.query.users[0].editcount || 0;
            }

            var achievedMilestone = null;
            milestoneThresholds.forEach(function(m) {
                if (editcount >= m) achievedMilestone = m;
            });

            if (achievedMilestone) {
                var storageKey = 'mw_milestone_shown_' + currentUser + '_' + achievedMilestone;
                if (!localStorage.getItem(storageKey)) {
                    localStorage.setItem(storageKey, '1');

                    var $overlay = $('<div>', {
                        css: {
                            position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
                            background: 'rgba(0,0,0,0.5)', 'z-index': 99998,
                            display: 'flex', 'align-items': 'center', 'justify-content': 'center'
                        }
                    });
                    var $box = $('<div>', {
                        css: {
                            background: '#fff', 'border-radius': '12px', padding: '30px 40px',
                            'text-align': 'center', 'box-shadow': '0 4px 20px rgba(0,0,0,0.3)',
                            'max-width': '400px', width: '80%', position: 'relative'
                        }
                    });
                    var $title = $('<h2>', {
                        text: '🎉 恭喜!',
                        css: { 'margin-bottom': '15px', 'font-size': '24px', color: '#333' }
                    });
                    var $msg = $('<p>', {
                        text: '你已达到 ' + achievedMilestone + ' 次编辑!',
                        css: { 'font-size': '18px', 'margin-bottom': '25px', color: '#555' }
                    });
                    var $btn = $('<button>', {
                        text: '太棒了!',
                        css: {
                            background: '#4CAF50', color: '#fff', border: 'none',
                            padding: '10px 30px', 'border-radius': '8px', 'font-size': '16px', cursor: 'pointer'
                        },
                        click: function() {
                            $overlay.fadeOut(300, function() { $(this).remove(); });
                        }
                    });

                    $box.append($title, $msg, $btn);
                    $overlay.append($box);
                    $('body').append($overlay);
                }
            }
        }).fail(function() {});
    }

    // ==================== 用户链接处理器启动 ====================
    var linkSelector = 'a.mw-userlink, .citizen-menu_card-content a, .citizen-userMenu a';
    colorizeAndStatus($(linkSelector));

    var observer = new MutationObserver(function () {
        colorizeAndStatus($(linkSelector));
    });
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    setInterval(function () {
        colorizeAndStatus($(linkSelector));
    }, 3000);

    // ==================== 植物卡片筛选 ====================
    if ($('#pf-name').length) {
        var $cards = $('.pvzhe-card');
        var $name = $('#pf-name');
        var $sunMax = $('#pf-sun-max');
        var $cdMax = $('#pf-cd-max');
        var $type = $('#pf-type');
        var $version = $('#pf-version');
        var $reset = $('#pf-reset');

        function filterPlants() {
            var name = $name.val().toLowerCase();
            var sunRaw = $sunMax.val().trim();
            var cdRaw = $cdMax.val().trim();
            var sunTarget = sunRaw !== '' ? parseFloat(sunRaw) : null;
            var cdTarget = cdRaw !== '' ? parseFloat(cdRaw) : null;
            var type = $type.val();
            var version = $version.val();

            $cards.each(function () {
                var $card = $(this);
                var n = $card.find('.pvzhe-card-name').text().toLowerCase();
                var sun = parseFloat($card.data('sun')) || 0;
                var cd = parseFloat($card.data('cooldown')) || 0;
                var t = ($card.data('type') || '').toString();
                var v = ($card.data('version') || '').toString();

                var show = true;
                if (name && n.indexOf(name) === -1) show = false;
                if (sunTarget !== null && sun !== sunTarget) show = false;
                if (cdTarget !== null && cd !== cdTarget) show = false;
                if (type) {
                    var cardTypes = t.split(',').map(function (s) { return s.trim(); });
                    if (cardTypes.indexOf(type) === -1) show = false;
                }
                if (version && v !== version) show = false;
                $card.toggleClass('hidden-card', !show);
            });
        }

        $name.on('input', filterPlants);
        $sunMax.on('input keyup', filterPlants);
        $cdMax.on('input keyup', filterPlants);
        $type.on('change', filterPlants);
        $version.on('change', filterPlants);
        $reset.on('click', function () {
            $name.val('');
            $sunMax.val('');
            $cdMax.val('');
            $type.val('');
            $version.val('');
            filterPlants();
        });
    }

    // ==================== 僵尸卡片筛选 ====================
    function getArmorArray(healthStr) {
        if (!healthStr || healthStr.indexOf('+') === -1) return [];
        var armorPart = healthStr.split('+')[0].trim();
        return armorPart.split(',').map(function(s) { return s.trim(); });
    }

    function extractHealth(healthStr) {
        var matches = healthStr.match(/\d+/g);
        if (matches && matches.length > 0) {
            return parseFloat(matches[matches.length - 1]) || 0;
        }
        return 0;
    }

    if ($('#zf-name').length) {
        var $zcards = $('.pvzhe-card');
        var $zname = $('#zf-name');
        var $healthMin = $('#zf-health-min');
        var $armor = $('#zf-armor');
        var $speed = $('#zf-speed');
        var $ztype = $('#zf-type');
        var $zversion = $('#zf-version');
        var $zreset = $('#zf-reset');

        function filterZombies() {
            var name = $zname.val().toLowerCase();
            var healthRaw = $healthMin.val().trim();
            var healthTarget = healthRaw !== '' ? parseFloat(healthRaw) : null;
            var armor = $armor.val();
            var speed = $speed.val();
            var type = $ztype.val();
            var version = $zversion.val();

            $zcards.each(function () {
                var $card = $(this);
                var n = $card.find('.pvzhe-card-name').text().toLowerCase();
                var t = ($card.data('type') || '').toString();
                var s = ($card.data('speed') || '').toString();
                var healthStr = ($card.data('health') || '').toString();
                var health = extractHealth(healthStr);
                var armors = getArmorArray(healthStr);
                var v = ($card.data('version') || '').toString();

                var show = true;
                if (name && n.indexOf(name) === -1) show = false;
                if (healthTarget !== null && health !== healthTarget) show = false;

                if (armor === 'none') {
                    if (armors.length > 0) show = false;
                } else if (armor) {
                    if (armors.indexOf(armor) === -1) show = false;
                }

                if (speed) {
                    var cardSpeeds = s.split(',').map(function (v) { return v.trim(); });
                    if (cardSpeeds.indexOf(speed) === -1) show = false;
                }
                if (type) {
                    var cardTypes = t.split(',').map(function (v) { return v.trim(); });
                    if (cardTypes.indexOf(type) === -1) show = false;
                }
                if (version && v !== version) show = false;
                $card.toggleClass('hidden-card', !show);
            });
        }

        $zname.on('input', filterZombies);
        $healthMin.on('input keyup', filterZombies);
        $armor.on('change', filterZombies);
        $speed.on('change', filterZombies);
        $ztype.on('change', filterZombies);
        $zversion.on('change', filterZombies);
        $zreset.on('click', function () {
            $zname.val('');
            $healthMin.val('');
            $armor.val('');
            $speed.val('');
            $ztype.val('');
            $zversion.val('');
            filterZombies();
        });
    }

    // ==================== 返回顶部按钮 ====================
    $('body').append('<button id="back-to-top" title="返回顶部">⬆</button>');
    var $backBtn = $('#back-to-top');
    $(window).scroll(function() {
        $backBtn.toggle($(this).scrollTop() > 300);
    });
    $backBtn.click(function() {
        $('html, body').animate({ scrollTop: 0 }, 400);
    });

    // ==================== 好友系统 ====================
    var friendApi = new mw.Api();

    function getFriendPageName(username) {
        return 'User:' + username + '/friends';
    }

    function getFriendRequestsPageName(username) {
        return 'User:' + username + '/friendrequests';
    }

    function loadFriendList(username, callback) {
        friendApi.get({
            action: 'query',
            titles: getFriendPageName(username),
            prop: 'revisions',
            rvprop: 'content',
            rvlimit: 1
        }).then(function(data) {
            var pages = data.query.pages;
            for (var id in pages) {
                if (pages[id].revisions && pages[id].revisions[0]) {
                    try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
                }
            }
            callback([]);
        }).fail(function() { callback([]); });
    }

    function loadFriendRequests(username, callback) {
        friendApi.get({
            action: 'query',
            titles: getFriendRequestsPageName(username),
            prop: 'revisions',
            rvprop: 'content',
            rvlimit: 1
        }).then(function(data) {
            var pages = data.query.pages;
            for (var id in pages) {
                if (pages[id].revisions && pages[id].revisions[0]) {
                    try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
                }
            }
            callback([]);
        }).fail(function() { callback([]); });
    }

    function saveToPage(pageName, data, summary, callback) {
        friendApi.postWithEditToken({
            action: 'edit',
            title: pageName,
            text: JSON.stringify(data, null, 2),
            summary: summary,
            minor: true,
            bot: true
        }).then(function() {
            if (callback) callback(true);
        }).fail(function() {
            if (callback) callback(false);
        });
    }

    function sendFriendRequest(toUser) {
        if (!toUser || toUser === currentUser) return;
        loadFriendList(currentUser, function(myFriends) {
            if (myFriends.indexOf(toUser) !== -1) {
                alert('你们已经是好友了!');
                return;
            }
            loadFriendRequests(toUser, function(requests) {
                var alreadySent = requests.some(function(r) { return r.from === currentUser; });
                if (alreadySent) {
                    alert('你已经发送过好友请求了,请等待对方回应。');
                    return;
                }
                requests.push({ from: currentUser, time: Date.now() });
                saveToPage(getFriendRequestsPageName(toUser), requests, currentUser + ' 发送了好友请求', function(success) {
                    if (success) {
                        alert('好友请求已发送给 ' + toUser + '!');
                        updateFriendButton(toUser, 'pending');
                    } else {
                        alert('发送失败,请稍后重试。');
                    }
                });
            });
        });
    }

    function acceptFriendRequest(fromUser) {
        loadFriendList(currentUser, function(myFriends) {
            myFriends.push(fromUser);
            saveToPage(getFriendPageName(currentUser), myFriends, '添加好友: ' + fromUser, function() {
                loadFriendList(fromUser, function(theirFriends) {
                    theirFriends.push(currentUser);
                    saveToPage(getFriendPageName(fromUser), theirFriends, '添加好友: ' + currentUser, function() {
                        loadFriendRequests(currentUser, function(requests) {
                            requests = requests.filter(function(r) { return r.from !== fromUser; });
                            saveToPage(getFriendRequestsPageName(currentUser), requests, '接受好友请求', function() {
                                if ($('#friend-requests-list').length) showFriendRequests();
                                updateFriendButton(fromUser, 'added');
                            });
                        });
                    });
                });
            });
        });
    }

    function rejectFriendRequest(fromUser) {
        loadFriendRequests(currentUser, function(requests) {
            requests = requests.filter(function(r) { return r.from !== fromUser; });
            saveToPage(getFriendRequestsPageName(currentUser), requests, '拒绝好友请求', function() {
                if ($('#friend-requests-list').length) showFriendRequests();
            });
        });
    }

    function removeFriend(friendName) {
        if (!confirm('确定要删除好友 ' + friendName + ' 吗?')) return;
        loadFriendList(currentUser, function(myFriends) {
            myFriends = myFriends.filter(function(f) { return f !== friendName; });
            saveToPage(getFriendPageName(currentUser), myFriends, '删除好友: ' + friendName, function() {
                loadFriendList(friendName, function(theirFriends) {
                    theirFriends = theirFriends.filter(function(f) { return f !== currentUser; });
                    saveToPage(getFriendPageName(friendName), theirFriends, '删除好友: ' + currentUser, function() {
                        if ($('#friend-list-container').length) showFriendList();
                        updateFriendButton(friendName, 'add');
                    });
                });
            });
        });
    }

    function updateFriendButton(username, status) {
        var $btn = $('#friend-action-btn');
        if (!$btn.length) return;
        $btn.removeClass('friend-add-btn friend-added-btn friend-pending-btn');
        if (status === 'added') {
            $btn.addClass('friend-added-btn').text('✓ 已添加');
            $btn.off('click').click(function() { removeFriend(username); });
        } else if (status === 'pending') {
            $btn.addClass('friend-pending-btn').text('⏳ 等待确认').off('click');
        } else {
            $btn.addClass('friend-add-btn').text('+ 加好友');
            $btn.off('click').click(function() { sendFriendRequest(username); });
        }
    }

    function showFriendRequests() {
        loadFriendRequests(currentUser, function(requests) {
            var $list = $('#friend-requests-list');
            if (!$list.length) return;
            $list.empty();
            if (requests.length === 0) {
                $list.append('<p style="color:#999;">暂无好友请求</p>');
                return;
            }
            requests.forEach(function(r) {
                var $item = $('<div>', { class: 'friend-request-item' });
                $item.append('<span><a href="/w/User:' + encodeURIComponent(r.from) + '">' + r.from + '</a></span>');
                var $actions = $('<div>');
                $actions.append('<button class="friend-accept-btn" data-from="' + r.from + '">接受</button>');
                $actions.append('<button class="friend-reject-btn" data-from="' + r.from + '">拒绝</button>');
                $item.append($actions);
                $list.append($item);
            });
            $list.off('click').on('click', '.friend-accept-btn', function() {
                acceptFriendRequest($(this).data('from'));
            }).on('click', '.friend-reject-btn', function() {
                rejectFriendRequest($(this).data('from'));
            });
        });
    }

    function showFriendList() {
        loadFriendList(currentUser, function(friends) {
            loadFriendRequests(currentUser, function(requests) {
                var $overlay = $('<div>', { class: 'friend-overlay' });
                var $dialog = $('<div>', { class: 'friend-dialog' });

                var realCount = friends.length;
                $dialog.append('<h3>👥 好友列表 <span class="friend-count">' + realCount + '人</span></h3>');

                if (requests.length > 0) {
                    $dialog.append('<p style="color:#f44336;cursor:pointer;" id="friend-req-notice">📩 有 ' + requests.length + ' 条好友请求,点击查看</p>');
                }

                $dialog.append('<div id="friend-requests-list" style="display:none;margin:10px 0;"></div>');

                if (realCount === 0) {
                    $dialog.append('<p style="color:#999;">还没有好友,去其他用户页面加好友吧!</p>');
                } else {
                    var $container = $('<div>', { id: 'friend-list-container', style: 'margin:10px 0;' });
                    friends.forEach(function(f) {
                        var $item = $('<span>', { class: 'friend-list-item' });
                        $item.append('<a href="/w/User:' + encodeURIComponent(f) + '">' + f + '</a>');
                        $item.append(' <a href="javascript:void(0)" class="friend-msg-link" data-friend="' + f + '" title="发私信">✉️</a>');
                        $item.append('<span class="friend-remove" data-friend="' + f + '"> ×</span>');
                        $container.append($item);
                    });
                    $dialog.append($container);
                }

                $dialog.append('<button style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>');
                $overlay.append($dialog);
                $('body').append($overlay);

                $overlay.on('click', function(e) {
                    if ($(e.target).is($overlay) || $(e.target).text() === '关闭') {
                        $overlay.remove();
                    }
                });

                $dialog.on('click', '#friend-req-notice', function() {
                    var $reqList = $('#friend-requests-list');
                    $reqList.toggle();
                    if ($reqList.is(':visible')) showFriendRequests();
                });

                $dialog.on('click', '.friend-remove', function() {
                    removeFriend($(this).data('friend'));
                });

                $dialog.on('click', '.friend-msg-link', function() {
                    var friendName = $(this).data('friend');
                    $overlay.remove();
                    sendMessage(friendName);
                });
            });
        });
    }

    if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) {
        var pageUser = mw.config.get('wgTitle');
        if (pageUser !== currentUser) {
            var $btn = $('<button>', {
                id: 'friend-action-btn',
                class: 'friend-btn friend-add-btn',
                text: '+ 加好友'
            });
            $('#firstHeading').append($btn);

            loadFriendList(currentUser, function(myFriends) {
                if (myFriends.indexOf(pageUser) !== -1) {
                    updateFriendButton(pageUser, 'added');
                } else {
                    loadFriendRequests(pageUser, function(requests) {
                        var alreadySent = requests.some(function(r) { return r.from === currentUser; });
                        if (alreadySent) {
                            updateFriendButton(pageUser, 'pending');
                        } else {
                            $btn.click(function() { sendFriendRequest(pageUser); });
                        }
                    });
                }
            });
        }
    }

    if (currentUser) {
        var $userMenu = $('#pt-userpage, .citizen-userMenu').first();
        if ($userMenu.length) {
            $userMenu.after(' <a href="javascript:void(0)" id="friend-list-trigger" style="font-size:13px;" title="好友列表">👥</a>');
        }
        $(document).on('click', '#friend-list-trigger', function() {
            showFriendList();
        });

        setInterval(function() {
            loadFriendRequests(currentUser, function(requests) {
                var $notice = $('#friend-request-count');
                if (requests.length > 0) {
                    if (!$notice.length && $('#friend-list-trigger').length) {
                        $('#friend-list-trigger').after('<span id="friend-request-count" style="color:#f44336;font-size:11px;vertical-align:super;">' + requests.length + '</span>');
                    } else if ($notice.length) {
                        $notice.text(requests.length);
                    }
                } else {
                    $notice.remove();
                }
            });
        }, 60000);
    }

    // ==================== 私信系统 ====================
    var msgApi = new mw.Api();

    function getMsgPageName(username) {
        return 'User:' + username + '/messages';
    }

    function loadMessages(callback) {
        if (!currentUser) { callback([]); return; }
        msgApi.get({
            action: 'query',
            titles: getMsgPageName(currentUser),
            prop: 'revisions',
            rvprop: 'content',
            rvlimit: 1
        }).then(function(data) {
            var pages = data.query.pages;
            for (var id in pages) {
                if (pages[id].revisions && pages[id].revisions[0]) {
                    try { callback(JSON.parse(pages[id].revisions[0]['*'])); return; } catch(e) {}
                }
            }
            callback([]);
        }).fail(function() { callback([]); });
    }

    function saveMessages(msgs, callback) {
        if (!currentUser) return;
        msgApi.postWithEditToken({
            action: 'edit',
            title: getMsgPageName(currentUser),
            text: JSON.stringify(msgs, null, 2),
            summary: '更新私信',
            minor: true,
            bot: true
        }).then(function() {
            if (callback) callback(true);
        }).fail(function() {
            if (callback) callback(false);
        });
    }

    function getUnreadCount(msgs) {
        var count = 0;
        msgs.forEach(function(m) { if (!m.read) count++; });
        return count;
    }

    function updateMsgBadge() {
        loadMessages(function(msgs) {
            var count = getUnreadCount(msgs);
            var $badge = $('#msg-badge');
            if ($badge.length === 0) {
                var $drawer = $('.citizen-drawer').first();
                if ($drawer.length) {
                    $drawer.css('position', 'relative');
                    $drawer.append('<span id="msg-badge" class="msg-badge" title="新私信">0</span>');
                    $badge = $('#msg-badge');
                }
            }
            if ($badge.length) {
                $badge.text(count).toggle(count > 0);
            }
        });
    }

    function showInbox() {
        loadMessages(function(msgs) {
            var $overlay = $('<div>', { class: 'msg-overlay' });
            var $box = $('<div>', { class: 'msg-box' });
            $box.append('<h3 style="margin-bottom:15px;">📬 私信箱</h3>');

            msgs.sort(function(a, b) { return b.time - a.time; });

            if (msgs.length === 0) {
                $box.append('<p style="color:#999;">暂无消息</p>');
            } else {
                msgs.forEach(function(m, index) {
                    var $item = $('<div>', { class: 'msg-item' + (m.read ? '' : ' unread') });
                    var timeStr = new Date(m.time).toLocaleString('zh-CN');
                    $item.append(
                        '<div><span class="msg-sender">' + m.from + '</span><span class="msg-time">' + timeStr + '</span></div>',
                        '<div class="msg-text">' + m.text.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>') + '</div>',
                        '<div class="msg-actions">' +
                            (m.read ? '' : '<button class="mark-read" data-index="' + index + '">已读</button>') +
                            '<button class="del-msg" data-index="' + index + '">删除</button>' +
                            '<button class="reply-msg" data-index="' + index + '" data-from="' + m.from + '">回复</button>' +
                        '</div>'
                    );
                    $box.append($item);
                });
            }

            $box.append('<button style="margin-top:15px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">关闭</button>');
            $overlay.append($box);
            $('body').append($overlay);

            $overlay.on('click', function(e) {
                if ($(e.target).is($overlay) || $(e.target).text() === '关闭') {
                    $overlay.remove();
                    updateMsgBadge();
                }
            });

            $box.on('click', '.mark-read', function() {
                var idx = parseInt($(this).data('index'));
                msgs[idx].read = true;
                saveMessages(msgs, function() {
                    updateMsgBadge();
                    $overlay.remove();
                    showInbox();
                });
            });

            $box.on('click', '.del-msg', function() {
                var idx = parseInt($(this).data('index'));
                msgs.splice(idx, 1);
                saveMessages(msgs, function() {
                    $overlay.remove();
                    showInbox();
                });
            });

            $box.on('click', '.reply-msg', function() {
                var toUser = $(this).data('from');
                $overlay.remove();
                sendMessage(toUser);
            });
        });
    }

    function sendMessage(toUser) {
        if (!toUser) return;
        var $overlay = $('<div>', { class: 'msg-overlay' });
        var $box = $('<div>', { class: 'msg-box' });
        $box.append('<h3 style="margin-bottom:10px;">✉️ 发送私信给 ' + toUser + '</h3>');
        $box.append('<textarea class="msg-textarea" placeholder="输入消息内容..."></textarea>');
        $box.append(
            '<button class="msg-send-submit" style="margin-top:10px;padding:8px 20px;cursor:pointer;background:#4CAF50;color:#fff;border:none;border-radius:6px;">发送</button>',
            '<button style="margin-left:10px;padding:8px 20px;cursor:pointer;background:#888;color:#fff;border:none;border-radius:6px;">取消</button>'
        );
        $overlay.append($box);
        $('body').append($overlay);

        $overlay.on('click', function(e) {
            if ($(e.target).is($overlay) || $(e.target).text() === '取消') {
                $overlay.remove();
            }
        });

        $box.on('click', '.msg-send-submit', function() {
            var text = $box.find('.msg-textarea').val().trim();
            if (!text) return;
            $box.find('.msg-send-submit').prop('disabled', true).text('发送中...');

            var tempApi = new mw.Api();
            tempApi.get({
                action: 'query',
                titles: getMsgPageName(toUser),
                prop: 'revisions',
                rvprop: 'content',
                rvlimit: 1
            }).then(function(data) {
                var pages = data.query.pages;
                var msgs = [];
                for (var id in pages) {
                    if (pages[id].revisions && pages[id].revisions[0]) {
                        try { msgs = JSON.parse(pages[id].revisions[0]['*']); } catch(e) {}
                    }
                }
                msgs.push({
                    from: currentUser,
                    to: toUser,
                    text: text,
                    time: Date.now(),
                    read: false
                });

                tempApi.postWithEditToken({
                    action: 'edit',
                    title: getMsgPageName(toUser),
                    text: JSON.stringify(msgs, null, 2),
                    summary: currentUser + ' 发来一条私信',
                    minor: false,
                    bot: false
                }).then(function() {
                    $overlay.remove();
                    alert('私信已发送给 ' + toUser + '!');
                }).fail(function(err) {
                    $overlay.remove();
                    alert('发送失败:' + (err.error && err.error.info ? err.error.info : '未知错误'));
                });
            }).fail(function() {
                $overlay.remove();
                alert('发送失败,请稍后重试。');
            });
        });
    }

    if (mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgTitle').indexOf('/') === -1) {
        var msgPageUser = mw.config.get('wgTitle');
        if (msgPageUser !== currentUser) {
            var $msgBtn = $('<button>', {
                text: '✉️ 发送私信',
                class: 'msg-send-btn',
                click: function() { sendMessage(msgPageUser); }
            });
            $('#firstHeading').append($msgBtn);
        }
    }

    if (currentUser) {
        updateMsgBadge();
        $(document).on('click', '#msg-badge', function() {
            showInbox();
        });

        var $msgEntry = $('<a>', {
            href: 'javascript:void(0)',
            id: 'msg-inbox-trigger',
            text: '✉️',
            title: '私信箱',
            css: { 'font-size': '13px', 'margin-left': '8px', 'cursor': 'pointer', 'text-decoration': 'none' }
        });
        var $friendTrigger = $('#friend-list-trigger');
        if ($friendTrigger.length) {
            $friendTrigger.after(' ');
            $friendTrigger.after($msgEntry);
        }

        $(document).on('click', '#msg-inbox-trigger', function() {
            showInbox();
        });

        setInterval(function() {
            updateMsgBadge();
        }, 30000);
    }

    // ==================== 版本更新公告弹窗 ====================
    function checkUpdateNotice() {
        var $versionEl = $('.update-version');
        if ($versionEl.length === 0) return;

        var currentVersion = $versionEl.text().trim();
        if (!currentVersion) return;

        var dismissedVersion = localStorage.getItem('mw_update_dismissed');

        if (dismissedVersion !== currentVersion) {
            var $overlay = $('<div>', {
                css: {
                    position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
                    background: 'rgba(0,0,0,0.6)', 'z-index': 99999,
                    display: 'flex', 'align-items': 'center', 'justify-content': 'center'
                }
            });

            var $box = $('<div>', {
                css: {
                    background: '#fff', 'border-radius': '12px', padding: '30px',
                    'max-width': '550px', width: '90%', 'max-height': '80vh',
                    'overflow-y': 'auto', 'box-shadow': '0 8px 30px rgba(0,0,0,0.4)',
                    position: 'relative'
                }
            });

            var $content = $('<div>').css({'text-align':'left','font-size':'14px','line-height':'1.8'}).html(
                '<div style="text-align:center;font-size:18px;font-weight:bold;margin-bottom:5px;">【植物大战僵尸杂交版0.22版本更新公告】</div>' +
                '<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">更新时间:2026年6月7号</div>'+
                '<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">电脑(Windows)版链接:https://pan.quark.cn/s/b145573873c3</div>'+
                '<div style="text-align:center;font-size:12px;color:#888;margin-bottom:15px;">其他版本:https://pan.quark.cn/s/3ffc82554918</div>'+
                '<b>🌱 植物更新</b><br>全新的植物加入了我们,我们的实力将更加强悍!<br>'+
                '<b>白卡:</b>幽灵吞噬者、僵尸地刺、魅惑咖啡豆、魅惑三叶草、灵感菇、蒜鸟、咖啡三叶草、叶子高坚果、巨型南瓜壳、绷带高坚果、磁力樱桃炸弹、樱桃土豆雷<br>' +
                '<b>至尊金卡:</b>黄金西瓜投手、大王钢齿花<br>' +
                '<b>臻享钻卡:</b>生命重塑者<br>' +
                '<b>星光闪卡:</b>黄金锤子<br><br>' +
                '<b>🧟 僵尸/障碍物更新</b><br>飞行器僵尸、胆小鬼僵尸、传送门僵尸<br><br>' +
                '<b>🗺️ 关卡更新</b><br>' +
                '冒险:第八章 1~4关<br>' +
                '金卡挑战:大王钢齿花 1~3,黄金西瓜投手 1~3<br>' +
                '钻卡挑战:生命重塑者 1~6<br><br>' +
                '<b>🎨 杂项更新</b><br>' +
                '植物皮肤更新:黄金西瓜投手-幸运之王、大王钢齿花-银锋锐影、生命重塑者-浮生净莲(商店15000购买)、魔鬼辣椒-拘灵炼狱(在线关卡5水晶兑换)<br>' +
                '道具更新:骷髅铲<br><br>' +
                '<b>⚙️ 全新功能</b><br>' +
                '新增游戏指令:/phonk [on/off] [弹性强度数]、/dismember [on/off]<br>' +
                '局内设置新增果冻弹性效果开关与强度调整<br><br>' +
                '<b>⚖️ 平衡性调整</b><br>' +
                '植物价格调整:巨型南瓜雪橇 200→300、墓碑爆破者 150→100<br>' +
                '植物强度调整:宝藏树桩亡语 1~3张黄金碎片→1张<br><br>' +
                '<b>🛠️ 游戏优化</b><br>' +
                '优化了关卡进度存档、返回选关体验、在线关卡分类筛选与通关率显示、更多模式板块内容返回体验<br>' +
                '修复商店与植物试玩切换假死BUG、追加宝藏树桩亡语掉落黄金碎片、荧光木槌可对僵尸使用、本次更新修复了一堆BUG<br><br>' +
                '<b>💎 水晶兑换商店</b><br>' +
                '巨型南瓜壳 5 | 磁力樱桃炸弹 10 | 绷带高坚果 10<br>' +
                '叶子高坚果 15 | 樱桃土豆雷 15 | 魔鬼辣椒皮肤-拘灵炼狱 5<br><br>' +
                '<span style="color:#888;">结语:我们会持续建立与玩家社群的紧密联系,您的支持就是对我们工作最大的认可</span>'
            );

            var $closeBtn = $('<button>', {
                text: '我知道了',
                css: {
                    display: 'block', margin: '20px auto 0',
                    background: '#4CAF50', color: '#fff', border: 'none',
                    padding: '10px 30px', 'border-radius': '8px',
                    'font-size': '16px', cursor: 'pointer'
                },
                click: function() {
                    localStorage.setItem('mw_update_dismissed', currentVersion);
                    $overlay.fadeOut(300, function() { $(this).remove(); });
                }
            });

            $box.append($content, $closeBtn);
            $overlay.append($box);
            $('body').append($overlay);

            $overlay.on('click', function(e) {
                if ($(e.target).is($overlay)) {
                    $overlay.fadeOut(300, function() { $(this).remove(); });
                }
            });
        }
    }

    setTimeout(function() {
        checkUpdateNotice();
    }, 500);

    // ==================== Wiki 宠物 ====================
    (function() {
        var pets = {
            '冰瓜香蒲': {
                idle: 'https://new.pvzhe.wiki/images/1/10/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E5%BE%85%E6%9C%BA.gif',
                petting: 'https://new.pvzhe.wiki/images/4/47/%E5%86%B0%E7%93%9C%E9%A6%99%E8%92%B2%E6%8A%9A%E6%91%B8.gif',
                size: 100
            },
            '小猫向日葵': {
                idle: 'https://new.pvzhe.wiki/images/9/9b/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E5%BE%85%E6%9C%BA.gif',
                petting: 'https://new.pvzhe.wiki/images/8/85/%E5%B0%8F%E7%8C%AB%E5%90%91%E6%97%A5%E8%91%B5%E6%8A%9A%E6%91%B8.gif',
                size: 100
            }
        };
        var petNames = Object.keys(pets);
        var currentPet = localStorage.getItem('wiki_pet_current') || '冰瓜香蒲';
        if (!pets[currentPet]) currentPet = '冰瓜香蒲';

        var petState = 'idle';
        var petTimer = null;
        var affectionKey = 'wiki_pet_affection_';
        var positionKey = 'wiki_pet_position';
        var affection = parseInt(localStorage.getItem(affectionKey + currentPet)) || 0;
        var affectionPerPet = 5;
        var affectionCooldown = 100;
        var lastPetTime = 0;
        var isDragging = false;
        var dragStartX, dragStartY, petStartX, petStartY;
        var hasMoved = false;
        var levels = [
            { min: 0, title: '陌生', emoji: '🤔' },
            { min: 50, title: '认识', emoji: '👋' },
            { min: 150, title: '友好', emoji: '😊' },
            { min: 400, title: '亲密', emoji: '💚' },
            { min: 1000, title: '挚友', emoji: '💖' },
            { min: 2500, title: '灵魂伴侣', emoji: '✨' }
        ];

        function getLevel(aff) {
            var lvl = levels[0];
            for (var i = levels.length - 1; i >= 0; i--) {
                if (aff >= levels[i].min) { lvl = levels[i]; break; }
            }
            return lvl;
        }

        function saveAffection() { localStorage.setItem(affectionKey + currentPet, affection); }

        function savePosition() {
            var pos = $pet.position();
            var bottom = $(window).height() - pos.top - $pet.height();
            localStorage.setItem(positionKey, JSON.stringify({ left: pos.left, bottom: Math.max(0, bottom) }));
        }

        function showAffectionPanel() {
            var lvl = getLevel(affection);
            var nextLvl = null;
            for (var i = 0; i < levels.length; i++) {
                if (affection < levels[i].min) { nextLvl = levels[i]; break; }
            }
            var text = lvl.emoji + ' ' + lvl.title + ' (' + affection + ')';
            if (nextLvl) {
                var progress = Math.round((affection - lvl.min) / (nextLvl.min - lvl.min) * 100);
                text += ' → ' + nextLvl.emoji + ' ' + progress + '%';
            } else { text += ' MAX'; }
            $affectionPanel.text(text).css('color', '#fff');
        }

        function spawnHeart(x, y) {
            var hearts = ['💚', '💙', '💛', '💜', '❤️', '💖', '✨'];
            var heart = hearts[Math.floor(Math.random() * hearts.length)];
            var $heart = $('<div>', { class: 'pet-heart', text: heart, css: { left: x, top: y } });
            $('body').append($heart);
            setTimeout(function() { $heart.remove(); }, 1500);
        }

        function triggerLevelUp() {
            $pet.addClass('level-up');
            setTimeout(function() { $pet.removeClass('level-up'); }, 800);
        }

        function switchPet(newPet) {
            if (newPet === currentPet) return;
            currentPet = newPet;
            localStorage.setItem('wiki_pet_current', currentPet);
            affection = parseInt(localStorage.getItem(affectionKey + currentPet)) || 0;
            $img.attr('src', pets[currentPet].idle);
            showAffectionPanel();
            setPetState('idle');
        }

        var savedPos = JSON.parse(localStorage.getItem(positionKey)) || { left: 20, bottom: 20 };
        var $pet = $('<div>', {
            class: 'wiki-pet',
            css: { opacity: 0, left: savedPos.left + 'px', bottom: savedPos.bottom + 'px', top: 'auto', right: 'auto' }
        });
        var $img = $('<img>', {
            src: pets[currentPet].idle,
            alt: currentPet,
            draggable: 'false',
            css: { width: pets[currentPet].size + 'px', height: 'auto' }
        });
        var $affectionPanel = $('<div>', { class: 'pet-affection' });
        var $tooltip = $('<div>', { class: 'pet-tooltip', text: '点击抚摸我~ 🐱' });
        var $switchBtn = $('<button>', { class: 'pet-switch-btn', text: '🔄', title: '切换宠物' });
        $pet.append($img, $affectionPanel, $tooltip, $switchBtn);
        $('body').append($pet);

        showAffectionPanel();
        setTimeout(function() { $pet.animate({ opacity: 1 }, 500); }, 1000);

        $switchBtn.on('click', function(e) {
            e.stopPropagation();
            e.preventDefault();
            var idx = petNames.indexOf(currentPet);
            var nextIdx = (idx + 1) % petNames.length;
            switchPet(petNames[nextIdx]);
        });

        function setPetState(state) {
            if (petState === state) return;
            petState = state;
            clearTimeout(petTimer);
            $pet.removeClass('petting');
            if (state === 'idle') {
                $img.attr('src', pets[currentPet].idle);
                $img.css({ width: pets[currentPet].size + 'px', height: 'auto' });
                $tooltip.text('点击抚摸我~ ' + getLevel(affection).emoji);
            } else if (state === 'petting') {
                $img.attr('src', pets[currentPet].petting);
                var petSize = currentPet === '小猫向日葵' ? pets[currentPet].size * 1.2 : Math.round(pets[currentPet].size * 1.2);
                $img.css({ width: petSize + 'px', height: 'auto' });
                $pet.addClass('petting');
                $tooltip.text('好舒服~ 💚');
                petTimer = setTimeout(function() { setPetState('idle'); }, 1200);
            }
        }

        $pet.on('mousedown', function(e) {
            if (e.button !== 0) return;
            if ($(e.target).is($switchBtn)) return;
            isDragging = true; hasMoved = false;
            dragStartX = e.clientX; dragStartY = e.clientY;
            petStartX = $pet.offset().left; petStartY = $pet.offset().top;
            $pet.css('transition', 'none');
            e.preventDefault();
        });

        $(document).on('mousemove', function(e) {
            if (!isDragging) return;
            var dx = e.clientX - dragStartX, dy = e.clientY - dragStartY;
            if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true;
            var newX = Math.max(0, Math.min(petStartX + dx, window.innerWidth - pets[currentPet].size));
            var newY = Math.max(0, Math.min(petStartY + dy, window.innerHeight - pets[currentPet].size));
            $pet.css({ left: newX + 'px', top: newY + 'px', bottom: 'auto', right: 'auto' });
        });

        $(document).on('mouseup', function() {
            if (!isDragging) return;
            isDragging = false;
            $pet.css('transition', 'transform 0.2s');
            savePosition();
        });

        $pet.on('click', function(e) {
            if ($(e.target).is($switchBtn)) return;
            e.stopPropagation();
            if (hasMoved) return;
            var now = Date.now();
            if (now - lastPetTime < affectionCooldown) return;
            lastPetTime = now;
            setPetState('petting');
            var oldLevel = getLevel(affection).title;
            affection += affectionPerPet;
            saveAffection();
            showAffectionPanel();
            var newLevel = getLevel(affection).title;
            if (newLevel !== oldLevel) {
                triggerLevelUp();
                $affectionPanel.text('🎉 升级!' + newLevel + '!').css('color', '#FFD700');
                setTimeout(function() { showAffectionPanel(); }, 2000);
            }
            var offset = $pet.offset();
            spawnHeart(offset.left + pets[currentPet].size / 3, offset.top - 10);
        });

        setInterval(function() {
            if (petState === 'idle' && !isDragging && Math.random() < 0.3) {
                $pet.css('transform', 'scale(1.05) translateY(-5px)');
                setTimeout(function() { $pet.css('transform', 'scale(1) translateY(0)'); }, 500);
            }
        }, 10000);

        $pet.on('touchstart', function(e) {
            $pet.addClass('touching');
            var touch = e.originalEvent.touches[0];
            isDragging = true; hasMoved = false;
            dragStartX = touch.clientX; dragStartY = touch.clientY;
            petStartX = $pet.offset().left; petStartY = $pet.offset().top;
            $pet.css('transition', 'none');
            e.stopPropagation();
        });

        $pet.on('touchend', function() {
            $pet.removeClass('touching');
        });

        $(document).on('touchmove', function(e) {
            if (!isDragging) return;
            var touch = e.originalEvent.touches[0];
            var dx = touch.clientX - dragStartX, dy = touch.clientY - dragStartY;
            if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasMoved = true;
            var newX = Math.max(0, Math.min(petStartX + dx, window.innerWidth - pets[currentPet].size));
            var newY = Math.max(0, Math.min(petStartY + dy, window.innerHeight - pets[currentPet].size));
            $pet.css({ left: newX + 'px', top: newY + 'px', bottom: 'auto', right: 'auto' });
        });

        $(document).on('touchend', function() {
            if (!isDragging) return;
            isDragging = false;
            $pet.css('transition', 'transform 0.2s');
            $pet.removeClass('touching');
            savePosition();
            if (!hasMoved) {
                var now = Date.now();
                if (now - lastPetTime < affectionCooldown) return;
                lastPetTime = now;
                setPetState('petting');
                var oldLevel = getLevel(affection).title;
                affection += affectionPerPet;
                saveAffection();
                showAffectionPanel();
                var newLevel = getLevel(affection).title;
                if (newLevel !== oldLevel) {
                    triggerLevelUp();
                    $affectionPanel.text('🎉 升级!' + newLevel + '!').css('color', '#FFD700');
                    setTimeout(function() { showAffectionPanel(); }, 2000);
                }
                var offset = $pet.offset();
                spawnHeart(offset.left + pets[currentPet].size / 3, offset.top - 10);
            }
        });
    })();
    
}); // 结束主 $(function () { ... })