您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在摸鱼派聊天室谁是卧底游戏
// ==UserScript== // @name 摸鱼派聊天室《谁是卧底》 // @namespace http://tampermonkey.net/ // @version 1.98 // @description 在摸鱼派聊天室谁是卧底游戏 // @author drda // @match https://fishpi.cn/cr* // @icon https://fishpi.cn/images/favicon.png // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (function () { 'use strict'; //开头中 @match https://fishpi.cn/cr* 中https://fishpi.cn/cr*是运行脚本的地址,需要可自定义(注意地址后面需要加*) GM_registerMenuCommand("开始游戏", () => { showGameModal() }); GM_registerMenuCommand("游戏榜单", () => { showRankModal(); }); let roomsInfo = []//房间信息 let sessionlocaid = null //会话标识 let isOne = true // 添加拖拽功能变量 let isDragging = false; let length = 0; let offsetX, offsetY; let tipWord = null // let httpHost="https://undercover.gakkiyomi.blog" let httpHost = "https://undercover.aweoo.com"//新域名 let wssHost = "wss://undercover.aweoo.com" //默认头像 let avatar = "https://tupian.li/images/2025/03/27/67e513485d95d.png"; //获取鱼排网页头像 let avatarHtml = document.getElementsByClassName('avatar-small') const urlMatch = avatarHtml.length > 0 ? avatarHtml[0].outerHTML.match(/url\('([^']+)'\)/) : null; avatar = urlMatch ? urlMatch[1] : avatar; // 创建游戏按钮和UI元素 // const gameBtn = document.createElement('button'); // gameBtn.className = 'undercover-btn cover-btn'; // gameBtn.textContent = '谁是卧底'; // gameBtn.onclick = showGameModal; // 创建游戏模态框 const modal = document.createElement('div'); modal.className = 'undercover-modal'; modal.innerHTML = ` <div class="modal-header" style="cursor: move;background: #BBC2C9; border-radius: 8px 8px 0 0;width:40px;color:#fff;font-size:12px;text-align: center;"> <div style="position: absolute; left: 10px; top: 8px; cursor: pointer;border-radius: 8px 8px 0 0;"><div style="background:url(${avatar});background-size:cover; background-position:center; background-repeat:no-repeat;width:20px;height:20px;border-radius: 5px ;"></div></div> <div style="position: absolute; right: 35px; top: 8px; cursor: pointer;border-radius: 8px 8px 0 0;" id='chang-bg-btn'">🌗</div> <div style="position: absolute; right:12px;top: 5px; cursor: pointer;" id='close-bg-btn' class='closeBtn'>x</div> </div> <h2 style="margin: 0 0 0px; text-align: center; color: #7a8da1;">谁是卧底 <span id="cd"></span></h2> <span style="margin: 0 0 0px; text-align: center; color: #7a8da1;" id="tipword"></span> <div id="game-content"></div> `; //创建模态遮罩层(默认不加) const overlay = document.createElement('div'); overlay.className = 'undercover-overlay'; overlay.onclick = closeGameModal;//点击遮罩层关闭 // document.body.appendChild(gameBtn); document.body.appendChild(modal); // document.body.appendChild(overlay);//添加遮罩层 // 获取URL参数 function getUrlParam(param) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(param); } // 监控URL参数变化 function watchUrlParams() { const sessionId = getUrlParam('session_id'); if (sessionId) { localStorage.setItem('fishpi_session_id', sessionId); sessionlocaid = sessionId // 移除URL中的session_id参数 const newUrl = window.location.href.replace(/[?&]session_id=[^&]+/, ''); window.history.replaceState({}, document.title, newUrl); // 自动验证会话 // autoValidateSession(sessionId); showGameModal() } } // 获取房间状态 async function getRoomsStatus(sessionId) { try { let response = await fetch(`${httpHost}/rooms/status`); const data = await response.json(); roomsInfo = data if (roomsInfo.success) { showRooms(); } } catch (error) { console.error('获取房间失败:', error); showNotification('获取房间失败', 'error'); } } // 显示游戏模态框 function showGameModal() { modal.classList.add('show'); overlay.classList.add('show'); // 添加弹出动画 setTimeout(() => { modal.classList.add('active'); }, 10); // 小延迟确保CSS过渡生效 initGame(); } // 关闭游戏模态框 function closeGameModal() { // 添加关闭动画 modal.classList.remove('active'); // 动画结束后完全隐藏 setTimeout(() => { modal.classList.remove('show'); overlay.classList.remove('show'); let cd = document.getElementById('cd'); if (tipWord) tipWord.innerText = ''; if (cd) cd.innerText = ''; if (window.gameWs) { window.gameWs.close(); window.gameWs = null; } }, 300); // 动画持续时间300ms } // 显示通知 function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = 'game-notification'; notification.textContent = message; notification.style.backgroundColor = type === 'error' ? '#e74c3c' : 'rgba(0,0,0,0.8)'; document.body.appendChild(notification); setTimeout(() => notification.remove(), 3000); } // 初始化游戏 async function initGame() { //切换主题 const changBgBtn = document.getElementById('chang-bg-btn'); const closeBgBtn = document.getElementById('close-bg-btn'); if (changBgBtn) { changBgBtn.onclick = toggleDarkMode; closeBgBtn.onclick = closeGameModal; } const gameContent = document.getElementById('game-content'); gameContent.innerHTML = '<p style="text-align: center;">正在连接游戏服务器...</p>'; try { const sessionId = localStorage.getItem('fishpi_session_id'); if (sessionId) { const user = await validateSession(sessionId); if (user) { localStorage.setItem('undercover_user_id', user.id); localStorage.setItem('undercover_user_name', user.username); getRoomsStatus(sessionId) return; } } // 获取登录(不可用)URL,携带当前页面URL作为回调地址 const loginUrl = await getLoginUrl(); if (loginUrl) { gameContent.innerHTML = ` <div style="text-align: center;"> <p style="margin-bottom: 20px;">请先登录(不可用)摸鱼派账号</p> <button class="undercover-btn" onclick="window.location.href='${loginUrl}'"> 使用摸鱼派账号登录(不可用) </button> </div> `; } else { throw new Error('获取登录(不可用)URL失败'); } } catch (error) { gameContent.innerHTML = ` <div style="text-align: center;"> <p style="color: #e74c3c; margin-bottom: 20px;"> 连接游戏服务器失败: ${error.message} </p> <button class="undercover-btn" id='closegame'> 关闭 </button> </div> `; var closeGame = document.getElementById('closegame'); if (closeGame) { closeGame.onclick = closeGameModal; } } } // 获取登录(不可用)URL async function getLoginUrl() { try { const callbackUrl = encodeURIComponent(window.location.href); const response = await fetch(`${httpHost}/auth/login?callback_url=${callbackUrl}`); const data = await response.json(); if (data.success) { return data.login_url; } throw new Error(data.error || '获取登录(不可用)URL失败'); } catch (error) { console.error('获取登录(不可用)URL失败:', error); throw error; } } // 验证会话 async function validateSession(sessionId) { try { const response = await fetch( `${httpHost}/auth/validate?session_id=${sessionId}` ); const data = await response.json(); return data.success ? data.user : null; } catch (error) { console.error('验证会话失败:', error); return null; } } // 连接WebSocket并获取房间状态 function connectWebSocket(sessionId, roomId = null) { const wsUrl = `${wssHost}/ws?session_id=${sessionId}${roomId ? `&room_id=${roomId}` : ''}`; const ws = new WebSocket(wsUrl); ws.onopen = () => { console.log('WebSocket游戏连接已建立'); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); console.log(message) handleWebSocketMessage(message); }; ws.onerror = (error) => { console.error('WebSocket错误:', error); showNotification('连接游戏服务器失败,请稍后重试', 'error'); }; ws.onclose = () => { console.log('WebSocket连接已关闭'); isOne = true }; window.gameWs = ws; return ws; } // 显示所有房间 function showRooms() { const gameContent = document.getElementById('game-content'); const roomsDiv = document.createElement('div'); let roomInfoHtml = ''; if (roomsInfo.rooms.length > 0) { roomsInfo.rooms.forEach(item => { roomInfoHtml += ` <div class="room-info" style="display:flex;justify-content: space-between;align-items:center;margin-bottom: 16px; padding: 10px; background: #bbc2c9; border-radius: 8px; text-align: center;"> <div style="display:flex;align-items: flex-start;flex-direction: column;"> <span style="margin-left: 16px;font-weight: bold; color: #2980b9;">房间号:${item.room_id || item.id}</span> <span style="margin-left: 16px; font-weight: bold; color: #d4e5ef;">现有人数:${item.player_count}/12</span> </div> <div> <button class="undercover-btn join-add-room-btn" data-add-room-id="${item.room_id || item.id}" style="background-color: #e8a8a2;height:35px"> 参与 </button> <button class="undercover-btn join-view-room-btn" data-view-room-id="${item.room_id || item.id}" style="background-color: #b3c4e6;height:35px"> 观战 </button> </div> </div> `; }) } else { roomInfoHtml = `<span style="color:#666">暂无房间</span>` // roomsDiv.insertAdjacentHTML('beforeend',`<span>暂无房间</span>`) } gameContent.innerHTML = ` <div class="game-room" style="width:450px;word-wrap: break-word;"> <h3 style="margin: 0 0 10px; color: #7a8da1;">当前房间数量${roomsInfo.rooms.length}</h3> <div class="player-list"> ${roomInfoHtml} </div> <div style="text-align: center; margin: 20px 0;"> <button id="creat-room-btn" class="undercover-btn"> 创建自己房间 </button> </div> </div> `; // 绑定事件 document.getElementById('creat-room-btn').onclick = creatGameRoom; // 为所有加入房间按钮绑定事件 document.querySelectorAll('.join-add-room-btn').forEach(button => { button.onclick = function () { const roomId = this.getAttribute('data-add-room-id'); joinGameRoom(roomId, "false"); // 调用加入房间函数 }; }); // 为所有加入房间按钮绑定事件 document.querySelectorAll('.join-view-room-btn').forEach(button => { button.onclick = function () { const roomId = this.getAttribute('data-view-room-id'); joinGameRoom(roomId, "true"); // 调用加入房间函数 }; }); tipWord = document.getElementById('tipword'); } // 创建自己的房间 function creatGameRoom() { let useName = localStorage.getItem('undercover_user_name') let fishpi_session_id = localStorage.getItem('fishpi_session_id') if (roomsInfo.rooms.length == 0) { fetchCreatRoom(fishpi_session_id, useName) } else { let index = roomsInfo.rooms.filter(item => item.room_id == useName || item.id == useName) if (index.length > 0) { showNotification("已经有自己id命名的房间,已随机命名新建一个房间") fetchCreatRoom(fishpi_session_id) } else { fetchCreatRoom(fishpi_session_id, useName) } } } // 请求创建房间函数 async function fetchCreatRoom(sessionid, roomid) { try { const response = await fetch(`${httpHost}/rooms/create?session_id=${sessionid}${roomid ? `&room_id=${roomid}` : ''}`); const data = await response.json(); if (data.success) { getRoomsStatus(sessionid) } } catch (error) { console.log("创建房间失败", error) showNotification("创建房间失败", "error") } } // 加入游戏房间 function joinGameRoom(roomId, is_spectator) { console.log('is_spectator', is_spectator) let fishpi_session_id = localStorage.getItem('fishpi_session_id') localStorage.setItem('fishpi_is_spectator', is_spectator) connectWebSocket(fishpi_session_id, roomId)//加入房间 if (window.gameWs) { showGameRoom() window.gameWs.onopen = () => { window.gameWs.send( //加入游戏 JSON.stringify({ type: 'join', data: { player_id: localStorage.getItem('undercover_user_id'), player_name: localStorage.getItem('undercover_user_name') } }) ); }; } } // 显示加入的游戏房间 function showGameRoom() { const gameContent = document.getElementById('game-content'); gameContent.innerHTML = ` <div class="game-room" style="width:450px;word-wrap: break-word;"> <div id="desc"></div> <div class="player-list"> <h3 style="margin: 0 0 10px; color: #7a8da1;">玩家列表</h3> <div id="players"></div> </div> <div style="text-align: center; margin: 20px 0;" id="option_btn"> <button id="ready-btn" class="undercover-btn"> 准备 </button> <button id="leave-btn" class="undercover-btn" style="background-color: #e8a8a2;"> 离开房间 </button> </div> <div class="chat-container"> <div id="chat-messages" class="chat-messages"></div> <!-- 聊天输入区域 --> <div class="chat-input-container" id="chat-input-area"> <input type="text" id="chat-input" class="chat-input" placeholder="输入聊天内容..."> <button id="send-btn" class="undercover-btn">发送</button> </div> <!-- 描述输入区域 (默认隐藏) --> <div class="chat-input-container" id="description-input-area" style="display: none;"> <input type="text" id="description-input" class="chat-input" placeholder="请描述你的词条..."> <button id="submit-description-btn" class="undercover-btn">发送</button> </div> </div> </div> `; // 绑定事件 document.getElementById('ready-btn').onclick = toggleReady; document.getElementById('leave-btn').onclick = leaveGame; document.getElementById('send-btn').onclick = sendChat; document.getElementById('chat-input').onkeypress = (e) => { if (e.key === 'Enter') sendChat(); }; document.getElementById('submit-description-btn').onclick = submitDescription; document.getElementById('description-input').onkeypress = (e) => { if (e.key === 'Enter') submitDescription(); }; if (localStorage.getItem('fishpi_is_spectator') == "true") { document.getElementById('ready-btn').style.display = 'none' } } // 准备/取消准备 window.toggleReady = function () { if (window.gameWs) { window.gameWs.send( JSON.stringify({ type: 'ready', data: { player_id: localStorage.getItem('undercover_user_id') } }) ); } }; // 处理WebSocket消息 function handleWebSocketMessage(message) { switch (message.type) { case 'state_update': if (message.data.descriptions && message.data.descriptions.length > 0) { addDescription(message.data); } updateGameState(message.data); break; case 'notification': if (message.data.ready_count && message.data.ready_count > 0) { showNotification('当前已有:' + message.data.ready_count + ' 名玩家准备'); } else { if (message.data.message) { showNotification(message.data.message); addDescription({ sys: "系统消息", msg: message.data.message }); } } if (message.data.descriptions && message.data.descriptions.length > 0) { addDescription(message.data); } break; case 'descriptions_update': if (message.data.descriptions && message.data.descriptions.length > 0) { addDescription(message.data); } break; case 'chat': addDescription(message.data); break; case 'eliminated_chat': if (localStorage.getItem('undercover_player_alive') == "false") { addDescription(message.data); } break; case 'vote': updateVotes(message.data); break; case 'error': showNotification(message.data.message, 'error'); break; case 'kicked': if (message.data.message) { showNotification(message.data.message); addDescription({ sys: "提示", msg: message.data.message }); addDescription({ sys: "提示", msg: `点击"离开房间"重新加入房间` }); } break; case 'room_list': roomsInfo.rooms = message.data.rooms showRooms() break; case 'switch_mode_response': isOne = false break; case 'countdown': //定时器 var countdown = document.getElementById('cd'); if (countdown) { countdown.innerText = message.data.seconds } break; } } // 更新游戏状态 function updateGameState(state) { const playerList = document.getElementById('players'); const currentUserId = localStorage.getItem('undercover_user_id'); const isHost = state.host === currentUserId; if (playerList) { playerList.innerHTML = state.players.map((player) => { // 检查是否为当前用户 const isCurrentUser = player.id === currentUserId; // 检查是否显示踢出按钮(房主且不是自己) const showKickButton = isHost && !isCurrentUser && player.id !== state.host; return ` <div class="player ${player.is_ready == true ? 'ready' : player.is_ready == false ? 'unready' : ''} ${player.is_alive ? 'alive' : 'dead'}" > <div style="display: flex; justify-content: space-between; align-items: center; width: 100%;"> <div> ${player.nickname || player.name} ${player.is_ready ? '(已准备)' : ''} ${!player.is_alive && !player.is_spectator ? '(已淘汰)' : (player.is_alive || !player.is_alive) && player.is_spectator ? '<span style="background-color:#91ade6;width:30px;height:20px;padding:5px;color:#fff;font-size:10px;margin-left:10px;">观战</span>' : ''} ${state.host === player.id ? '<span style="background-color:green;width:30px;height:20px;padding:5px;color:#fff;font-size:10px;margin-left:10px;">房主</span>' : ''} </div> ${showKickButton ? `<button class="kick-btn" data-player-id="${player.id}" style="background: #e74c3c; color: white; border: none; border-radius: 3px; padding: 3px 8px; font-size: 12px; cursor: pointer;"> 踢出 </button>` : '' } </div> </div> `; }).join(''); // 为踢出按钮绑定事件 document.querySelectorAll('.kick-btn').forEach(button => { button.onclick = function () { const playerId = this.getAttribute('data-player-id'); kickPlayer(playerId); }; }); } const readyBtn = document.getElementById('ready-btn'); if (readyBtn) { const currentPlayer = state.players.find( (p) => p.id === localStorage.getItem('undercover_user_id') ); if (currentPlayer && currentPlayer.is_ready) { readyBtn.classList.add('ready'); readyBtn.textContent = '取消准备'; } else { readyBtn.classList.remove('ready'); readyBtn.textContent = '准备'; } } // 根据游戏状态更新界面 updateGamePhase(state); } // 更新游戏阶段 function updateGamePhase(state) { const gameContent = document.getElementById('game-content'); const currentPlayer = state.players.find( (p) => p.id === localStorage.getItem('undercover_user_id') ); localStorage.setItem('undercover_player_alive', currentPlayer.is_alive) if (localStorage.getItem('fishpi_is_spectator') == "true" && isOne) { switchMode() } if (currentPlayer.is_spectator) { isOne = false } const describeContainer = document.getElementById('desc'); describeContainer.style.textAlign = 'center'; describeContainer.style.margin = '20px 0'; const chatInputArea = document.getElementById('chat-input-area'); const descInputArea = document.getElementById('description-input-area'); // 默认显示聊天输入框,隐藏描述输入框 if (chatInputArea) chatInputArea.style.display = 'flex'; if (descInputArea) descInputArea.style.display = 'none'; switch (state.state) { case 'DescribePhase': if (tipWord) { tipWord.innerText = currentPlayer.word ? '你要描述的词条:' + currentPlayer.word : '' } if (state.current_player === localStorage.getItem('undercover_user_id')) { // 隐藏聊天输入框,显示描述输入框 if (chatInputArea) chatInputArea.style.display = 'none'; if (descInputArea) descInputArea.style.display = 'flex'; // 清空描述输入框 const descInput = document.getElementById('description-input'); if (descInput) descInput.value = ''; // 显示描述提示 describeContainer.innerHTML = ` <div style=" background: linear-gradient(135deg, #ff9a9e, #fad0c4); padding: 15px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); margin-bottom: 15px; "> <p style=" font-size: 18px; font-weight: bold; color: #d63031; margin: 0 0 10px; "> 轮到你的回合! </p> <p style="margin: 0;"> 请描述你的词条:<strong>${currentPlayer.word}</strong> </p> </div> `; } else { describeContainer.innerHTML = ` <div style=" background: #f0f7ff; padding: 10px; border-radius: 8px; margin-bottom: 10px; "> <p style="margin: 0;"> 等待 <strong>${state.players.find((p) => p.id === state.current_player).nickname || state.players.find((p) => p.id === state.current_player).name}</strong> 描述词条... </p> </div> `; } break; case 'VotePhase': // 确保显示聊天输入框 if (chatInputArea) chatInputArea.style.display = 'flex'; if (descInputArea) descInputArea.style.display = 'none'; if (currentPlayer && currentPlayer.is_alive) { describeContainer.innerHTML = ` <p style="margin-bottom: 10px;">请投票选出你认为的卧底</p> <div class="vote-buttons"> ${state.players .filter((p) => p.is_alive && p.id !== currentPlayer.id) .map( (player) => ` <button class="undercover-btn vote-player-btn" data-vote-id="${player.id}" style="margin: 5px;"> ${player.nickname || player.name} </button> ` ) .join('')} </div> `; // 为所有玩家按钮绑定事件 document.querySelectorAll('.vote-player-btn').forEach(button => { if (localStorage.getItem('fishpi_is_spectator') == "false") { button.onclick = function () { const playerId = this.getAttribute('data-vote-id'); vote(playerId); // 调用投票函数 }; } }); } break; case 'GameOver': // 确保显示聊天输入框 if (localStorage.getItem('fishpi_is_spectator') == "true") { if (chatInputArea) chatInputArea.style.display = 'none'; if (descInputArea) descInputArea.style.display = 'none'; } else { if (chatInputArea) chatInputArea.style.display = 'flex'; if (descInputArea) descInputArea.style.display = 'none'; } if (state.winner) { var resultContainer = document.createElement('div'); var countdown = document.getElementById('cd'); resultContainer.style.textAlign = 'center'; resultContainer.style.margin = '20px 0'; describeContainer.innerHTML = ` <h3 style="color: #7a8da1; margin-bottom: 15px;"> 游戏结束!${state.winner}胜利! </h3> <h3 style="color: #7a8da1; margin-bottom: 15px;"> 卧底词条${state.undercover_word},平民词条${state.civilian_word}。 </h3> `; length = 0 if (tipWord) { tipWord.innerText = '' } if (countdown) { countdown.innerText = '' } } else { describeContainer.innerHTML = `<p style="margin-bottom: 10px;">点击准备可复活,开始下一局...</p>`; } break; } } // 踢出玩家函数 function kickPlayer(playerId) { if (window.gameWs) { window.gameWs.send( JSON.stringify({ type: 'kick', data: { player_id: localStorage.getItem('undercover_user_id'), target_id: playerId } }) ); } } // 提交描述 function submitDescription() { const input = document.getElementById('description-input'); if (input && input.value.trim() && window.gameWs) { window.gameWs.send( JSON.stringify({ type: 'describe', data: { player_id: localStorage.getItem('undercover_user_id'), content: input.value.trim() } }) ); input.value = ''; // 提交后切换回聊天输入框 const chatInputArea = document.getElementById('chat-input-area'); const descInputArea = document.getElementById('description-input-area'); if (chatInputArea) chatInputArea.style.display = 'flex'; if (descInputArea) descInputArea.style.display = 'none'; } }; // 投票 function vote(targetId) { if (window.gameWs) { window.gameWs.send( JSON.stringify({ type: 'vote', data: { player_id: localStorage.getItem('undercover_user_id'), target_id: targetId } }) ); } }; // 观战模式 function switchMode() { if (window.gameWs) { window.gameWs.send( JSON.stringify({ type: 'switch_mode', data: { player_id: localStorage.getItem('undercover_user_id'), is_spectator: true } }) ); } }; // 发送聊天消息 function sendChat() { const input = document.getElementById('chat-input'); const content = input.value.trim(); if (content && window.gameWs) { window.gameWs.send( JSON.stringify({ type: 'chat', data: { player_id: localStorage.getItem('undercover_user_id'), content: content } }) ); input.value = ''; } } // 离开游戏 function leaveGame() { let cd = document.getElementById('cd'); if (tipWord) tipWord.innerText = '' if (cd) cd.innerText = '' if (window.gameWs) { window.gameWs.send( JSON.stringify({ type: 'leave', data: { player_id: localStorage.getItem('undercover_user_id') } }) ); window.gameWs.close(); window.gameWs = null; } isOne = true initGame(); } // 添加描述到聊天区域 function addDescription(data) { const chatMessages = document.getElementById('chat-messages'); if (chatMessages) { const message = document.createElement('div'); if (data.sys) { message.style.color = data.sys == '提示' ? '#e74c3c' : '#666' message.style.marginBottom = '5px'; message.style.paddingBottom = '2px'; message.style.fontSize = data.sys == '提示' ? '14px' : '10px'; message.style.textAlign = 'center'; message.style.borderBottom = '1px solid #999e9e'; message.textContent = `${data.msg}`; } else if (data.descriptions) { if (length !== data.descriptions.length) { length = data.descriptions.length message.style.padding = '5px'; message.style.marginBottom = '5px'; message.style.background = 'linear-gradient(62deg,#ada996,#f2f2f2,#dbdbdb,#eaeaea)'; message.style.fontSize = '12px'; message.style.borderRadius = '4px'; message.textContent = `${data.descriptions[data.descriptions.length - 1].player_nickname || data.descriptions[data.descriptions.length - 1].player_name}描述: ${data.descriptions[data.descriptions.length - 1].description}`; } } else { message.style.padding = '5px'; message.style.marginBottom = '5px'; message.style.backgroundColor = data.player_id == localStorage.getItem('undercover_user_id') ? '#f1f8e9' : '#f0e9f8'; message.style.borderRadius = '4px'; message.textContent = `${data.player_nickname || data.player_name}: ${data.content}`; } chatMessages.appendChild(message); chatMessages.scrollTop = chatMessages.scrollHeight; } } // 更新投票信息 function updateVotes(data) { showNotification(`${data.voter_name} 投票给了 ${data.target_name}`); } // ========== 游戏榜单相关 ========== // 创建榜单模态框 const rankModal = document.createElement('div'); rankModal.className = 'undercover-modal'; rankModal.style.display = 'none'; rankModal.innerHTML = ` <div class="modal-header" style="cursor: move;background: #BBC2C9; border-radius: 8px 8px 0 0;width:40px;color:#fff;font-size:12px;text-align: center;"> <div style="position: absolute; left: 10px; top: 8px; cursor: pointer;border-radius: 8px 8px 0 0;"><div style="background:url(${avatar});background-size:cover; background-position:center; background-repeat:no-repeat;width:20px;height:20px;border-radius: 5px ;"></div></div> <div style="position: absolute; right: 35px; top: 8px; cursor: pointer;border-radius: 8px 8px 0 0;" id='chang-bg-btn2'">🌗</div> <div style="position: absolute; right:12px;top: 5px; cursor: pointer;" id='close-rank-btn' class='closeBtn'>x</div> </div> <h2 style="margin: 0 0 0px; text-align: center; color: #7a8da1;">谁是卧底 - 游戏榜单</h2> <div id="rank-content"></div> `; document.body.appendChild(rankModal); // 显示榜单模态框 function showRankModal() { rankModal.style.display = 'block'; rankModal.classList.add('show'); setTimeout(() => { rankModal.classList.add('active'); }, 10); initRank(); } // 关闭榜单模态框 function closeRankModal() { rankModal.classList.remove('active'); setTimeout(() => { rankModal.classList.remove('show'); rankModal.style.display = 'none'; }, 300); } rankModal.querySelector('#close-rank-btn').onclick = closeRankModal; rankModal.querySelector('#chang-bg-btn2').onclick = toggleDarkMode; // 初始化榜单流程 async function initRank() { const rankContent = document.getElementById('rank-content'); rankContent.innerHTML = '<p style="text-align: center;">正在连接服务器...</p>'; try { const sessionId = localStorage.getItem('fishpi_session_id'); if (sessionId) { const user = await validateSession(sessionId); if (user) { localStorage.setItem('undercover_user_id', user.id); localStorage.setItem('undercover_user_name', user.username); showRankPage(); return; } } // 获取登录(不可用)URL,携带当前页面URL作为回调地址 const loginUrl = await getLoginUrl(); if (loginUrl) { rankContent.innerHTML = ` <div style="text-align: center;"> <p style="margin-bottom: 20px;">请先登录(不可用)摸鱼派账号</p> <button class="undercover-btn" onclick="window.location.href='${loginUrl}'"> 使用摸鱼派账号登录(不可用) </button> </div> `; } else { throw new Error('获取登录(不可用)URL失败'); } } catch (error) { rankContent.innerHTML = ` <div style="text-align: center;"> <p style="color: #e74c3c; margin-bottom: 20px;"> 连接服务器失败: ${error.message} </p> <button class="undercover-btn" id='close-rank'> 关闭 </button> </div> `; var closeRank = document.getElementById('close-rank'); if (closeRank) closeRank.onclick = closeRankModal; } } // 榜单主页面 async function showRankPage() { const rankContent = document.getElementById('rank-content'); rankContent.innerHTML = ` <div style="text-align:center;margin-bottom:10px;"> <input type="text" id="rank-search-input" class="chat-input" placeholder="输入玩家名称查询..." style="width:200px;"> <button id="rank-search-btn" class="undercover-btn">查询</button> </div> <div id="rank-list"></div> <div id="rank-player-detail" style="display:none;"></div> `; document.getElementById('rank-search-btn').onclick = async function () { const name = document.getElementById('rank-search-input').value.trim(); if (name) { await showPlayerRank(name); } }; await fetchRankList(); } // 获取前十名榜单 async function fetchRankList() { const rankListDiv = document.getElementById('rank-list'); const detailDiv = document.getElementById('rank-player-detail'); if (detailDiv) detailDiv.style.display = 'none'; if (rankListDiv) rankListDiv.style.display = ''; rankListDiv.innerHTML = '<p style="text-align:center;">加载中...</p>'; try { const response = await fetch(`${httpHost}/rank?multi_sort=wins:desc,total_games:desc&limit=10`); const data = await response.json(); if (data.success && data.data.length > 0) { rankListDiv.innerHTML = ` <div class="rank-table-wrapper"> <table class="undercover-rank-table"> <thead> <tr> <th>排名</th> <th>玩家</th> <th>总局数</th> <th>胜利</th> <th>失败</th> <th>胜率</th> <th>操作</th> </tr> </thead> <tbody> ${data.data.map((item, idx) => { const winRate = item.win_rate.toString().split('.').length > 1 ? (item.win_rate * 1000 / 1000).toFixed(2) : item.win_rate; return ` <tr> <td><span class="rank-badge-table">${idx + 1}</span></td> <td>${item.player_nickname !== item.player_name ? item.player_nickname + ' (' + item.player_name + ')' : item.player_name}</td> <td>${item.total_games}</td> <td class="wins">${item.wins}</td> <td class="losses">${item.losses}</td> <td class="win-rate">${winRate}%</td> <td><button class="undercover-btn show-info-btn" data-info-id="${item.player_name}">详情</button></td> </tr> `; }).join('')} </tbody> </table> </div> `; document.querySelectorAll('.show-info-btn').forEach(button => { button.onclick = function () { const player_name = this.getAttribute('data-info-id'); showPlayerRank(player_name); }; }); } else { rankListDiv.innerHTML = '<p style="text-align:center;">暂无数据</p>'; } } catch (e) { rankListDiv.innerHTML = '<p style="color:red;text-align:center;">获取榜单失败</p>'; } } //详情 async function showPlayerRank(name) { document.getElementById('rank-search-input').value = name; // 隐藏榜单,显示详情 const rankListDiv = document.getElementById('rank-list'); const detailDiv = document.getElementById('rank-player-detail'); if (rankListDiv) rankListDiv.style.display = 'none'; if (detailDiv) { detailDiv.style.display = ''; detailDiv.innerHTML = `<div style="text-align:left;margin-bottom:10px;"><button id="back-to-rank-list" class="undercover-btn">← 返回榜单</button></div>`; } await fetchPlayerRank(name); // 绑定返回按钮 const backBtn = document.getElementById('back-to-rank-list'); if (backBtn) { backBtn.onclick = function () { // 隐藏详情,显示榜单 document.getElementById('rank-search-input').value = ""; if (detailDiv) detailDiv.style.display = 'none'; if (rankListDiv) rankListDiv.style.display = ''; }; } } // 查询单个玩家 async function fetchPlayerRank(name) { const detailDiv = document.getElementById('rank-player-detail'); // 若有返回按钮,保留,详情内容插入其后 let hasBackBtn = detailDiv && detailDiv.querySelector('#back-to-rank-list'); let backBtnHtml = hasBackBtn ? detailDiv.innerHTML : ''; detailDiv.innerHTML = (backBtnHtml ? backBtnHtml : '') + '<div class="loading-spinner"><div class="spinner"></div><p>查询中...</p></div>'; try { const response = await fetch(`${httpHost}/${encodeURIComponent(name)}/status`); const data = await response.json(); if (data.success && data.data) { const item = data.data; const winRate = item.win_rate.toString().split('.').length > 1 ? (item.win_rate * 1000 / 1000).toFixed(2) : item.win_rate; const currentStreakText = item.current_streak >= 0 ? `当前连胜:${item.current_streak}` : `当前连败:${Math.abs(item.current_streak)}`; const currentStreakClass = item.current_streak >= 0 ? 'streak-win' : 'streak-lose'; detailDiv.innerHTML = (backBtnHtml ? backBtnHtml : '') + ` <div class="player-detail-card"> <div class="player-header"> <div class="player-avatar"> <div class="avatar-placeholder">${(item.player_nickname || item.player_name).charAt(0).toUpperCase()}</div> </div> <div class="player-info"> <h3 class="player-name">${item.player_nickname ? item.player_nickname + ' (' + item.player_name + ')' : item.player_name}</h3> <div class="player-stats-summary"> <span class="stat-item"> <span class="stat-label">总局数</span> <span class="stat-value">${item.total_games}</span> </span> <span class="stat-item"> <span class="stat-label">胜率</span> <span class="stat-value win-rate">${winRate}%</span> </span> </div> </div> </div> <div class="stats-grid"> <div class="stat-card"> <div class="stat-icon">🏆</div> <div class="stat-content"> <div class="stat-title">胜利</div> <div class="stat-number wins">${item.wins}</div> </div> </div> <div class="stat-card"> <div class="stat-icon">💔</div> <div class="stat-content"> <div class="stat-title">失败</div> <div class="stat-number losses">${item.losses}</div> </div> </div> <div class="stat-card"> <div class="stat-icon">🔥</div> <div class="stat-content"> <div class="stat-title">最佳连胜</div> <div class="stat-number best-streak">${item.best_streak}</div> </div> </div> <div class="stat-card"> <div class="stat-icon">📉</div> <div class="stat-content"> <div class="stat-title">最差连败</div> <div class="stat-number worst-streak">${item.worst_streak}</div> </div> </div> </div> <div class="current-streak ${currentStreakClass}"> <span class="streak-icon">${item.current_streak >= 0 ? '🔥' : '❄️'}</span> <span class="streak-text">${currentStreakText}</span> </div> <div class="time-stats"> <div class="time-stat"> <span class="time-label">本周游戏</span> <span class="time-value">${item.games_this_week} 场</span> </div> <div class="time-stat"> <span class="time-label">本月游戏</span> <span class="time-value">${item.games_this_month} 场</span> </div> </div> <div class="last-update"> <div class="update-item"> <span class="update-label">最后游戏</span> <span class="update-time">${item.last_game_time}</span> </div> <div class="update-item"> <span class="update-label">数据更新</span> <span class="update-time">${item.last_updated}</span> </div> </div> </div> `; } else { detailDiv.innerHTML = (backBtnHtml ? backBtnHtml : '') + ` <div class="error-message"> <div class="error-icon">❌</div> <p>${data.message || '未查询到该玩家'}</p> </div> `; } } catch (e) { detailDiv.innerHTML = (backBtnHtml ? backBtnHtml : '') + ` <div class="error-message"> <div class="error-icon">⚠️</div> <p>查询失败,请稍后重试</p> </div> `; } } // ========== 游戏榜单相关 END ========== // 添加拖拽事件处理函数 function startDrag(e) { if (e.target.closest('.modal-header')) { isDragging = true; const rect = modal.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; modal.style.cursor = 'grabbing'; // 添加防文本选择类 document.body.classList.add('no-select-during-drag'); } } function drag(e) { if (!isDragging) return; // 计算新位置 let left = e.clientX - offsetX; let top = e.clientY - offsetY; // 限制在视口内 const maxLeft = window.innerWidth - modal.offsetWidth; const maxTop = window.innerHeight - modal.offsetHeight; left = Math.max(0, Math.min(left, maxLeft)); top = Math.max(0, Math.min(top, maxTop)); modal.style.left = `${left}px`; modal.style.top = `${top}px`; modal.style.transform = 'none'; // 移除原来的居中transform } function endDrag() { isDragging = false; modal.style.cursor = ''; // 移除防文本选择类 document.body.classList.remove('no-select-during-drag'); } // 添加事件监听 modal.addEventListener('mousedown', startDrag); rankModal.addEventListener('mousedown', startDrag); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', endDrag); // 切换暗黑模式函数 function toggleDarkMode() { document.body.classList.toggle('undercover-dark'); // 可选:保存用户偏好到localStorage localStorage.setItem('undercover_dark_mode', document.body.classList.contains('undercover-dark') ? '1' : '0'); } // 页面加载时自动应用用户偏好 if (localStorage.getItem('undercover_dark_mode') === '1') { document.body.classList.add('undercover-dark'); } // 在页面加载时检查URL参数 window.addEventListener('load', watchUrlParams); // 在页面关闭时清理WebSocket连接 window.addEventListener('beforeunload', () => { if (window.gameWs) { window.gameWs.close(); } }); GM_addStyle(` #tipword.pulse { animation: pulse 1s infinite; } /* 添加踢出按钮样式 */ .kick-btn { transition: all 0.3s ease; } .kick-btn:hover { background-color: #c0392b !important; transform: translateY(-1px); box-shadow: 0 2px 5px rgba(0,0,0,0.2); } .kick-btn:active { transform: translateY(0); } /* 添加弹出动画 */ .undercover-modal { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.95); /* 减小缩放幅度 */ opacity: 0; background-color: white; padding: 25px; border-radius: 12px; box-shadow: 0 5px 15px rgba(0,0,0,0.2); z-index: 1001; min-width: 320px; max-width: 90%; max-height: 90vh; overflow-y: auto; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; transform: translateZ(0); /* 触发GPU加速 */ backface-visibility: hidden; filter: blur(0); /* 确保没有模糊效果 */ font-smooth: always; /* 确保字体平滑 */ } /* 防止拖拽时选中文本 */ .no-select-during-drag { user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; } .undercover-modal.show { display: block; } .undercover-modal.active { transform: translate(-50%, -50%) scale(1); opacity: 1; } /* 遮罩层动画 */ .undercover-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 1000; backdrop-filter: blur(2px); opacity: 0; transition: opacity 0.3s ease; } .undercover-overlay.show { display: block; opacity: 1; } /* 关闭按钮动画 */ .closeBtn { background-color: rgba(255, 255, 255, 0); color: #386a70; border: none; font-size: 16px; transition: transform 0.2s ease, color 0.2s ease; } .closeBtn:hover { background-color: rgba(255, 255, 255, 0); color: red; transform: scale(1.2); } /* 添加按钮悬停动画 */ .undercover-btn { display: inline-block; padding: 8px 16px; margin: 5px; background-color: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; font-size: 14px; font-weight: 500; transform: translateY(0); box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .undercover-btn:hover { background-color: #2980b9; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); } .undercover-btn:active { transform: translateY(0); box-shadow: 0 1px 3px rgba(0,0,0,0.1); } /* 添加房间卡片动画 */ .room-info { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding: 10px; background: #bbc2c9; border-radius: 8px; text-align: center; transition: all 0.3s ease; transform: translateX(0); } .room-info:hover { transform: translateX(5px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); background: #a8b0b8; } /* 玩家卡片动画 */ .player { padding: 8px 12px; margin: 5px 0; border-radius: 4px; background-color: white; box-shadow: 0 1px 3px rgba(0,0,0,0.1); transition: all 0.3s ease; } // .player:hover { // transform: scale(1.02); // box-shadow: 0 3px 6px rgba(0,0,0,0.15); // } /* 添加聊天消息动画 */ .chat-messages div { animation: messageAppear 0.3s ease; } @keyframes messageAppear { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* 添加倒计时动画 */ #cd { display: inline-block; animation: pulse 1s infinite alternate; } @keyframes pulse { from { transform: scale(1); } to { transform: scale(1.2); color: #e74c3c; } } /* 其他样式保持不变... */ .closeBtn { background-color: rgba(255, 255, 255, 0); color:#386a70; border:none; font-size:16px; } .closeBtn:hover{ background-color: rgba(255, 255, 255, 0); color:red !important } body{ position: relative; } .cover-btn{ position: fixed; top:100px; } .undercover-btn.ready { background-color: #317B50; } .undercover-btn.ready:hover { background-color: #27ae60; } .game-notification { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background-color: rgba(0,0,0,0.8); color: white; padding: 10px 20px; border-radius: 20px; z-index: 1002; animation: notificationSlideUp 0.3s ease; } .player-list { height:350px; overflow-y: auto; margin: 15px 0; padding: 10px; background-color: #f8f9fa; border-radius: 8px; } .player.ready { border-left: 10px solid #abcfba !important; } .player.unready { border-left: 10px solid #ed776b !important; } .player.alive { border-top: 4px solid #abcfba !important; border-bottom: 4px solid #abcfba !important; border-right:4px solid #abcfba !important; } .player.dead { border-top: 4px solid #ed776b !important; border-bottom: 4px solid #ed776b !important; border-right: 4px solid #ed776b !important; } .chat-container { margin-top: 15px; padding: 10px; background-color: #f8f9fa; border-radius: 8px; } .chat-messages { height: 200px; height: 280px; overflow-y: auto; padding: 10px; background-color: white; border-radius: 4px; margin-bottom: 10px; } .chat-input-container { display: flex; gap: 8px; } .chat-input { flex: 1; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; outline: none; transition: border-color 0.2s ease; } .chat-input:focus { border-color: #3498db; } @keyframes notificationSlideUp { from { transform: translate(-50%, 100%); opacity: 0; } to { transform: translate(-50%, 0); opacity: 1; } } /* 暗黑模式CSS */ body.undercover-dark .undercover-modal { background: #23272f; color: #847d78; } body.undercover-dark .undercover-btn { background: #444a5a; color: #f1f1f1; } body.undercover-dark .undercover-btn:hover { background: #22242a; } body.undercover-dark .undercover-overlay { background: rgba(20,20,20,0.7); } .player{ background-color: #fff6f630; color: #22242a; } body.undercover-dark .player-list, body.undercover-dark .chat-container { background: #23272f; color: #22242a; } body.undercover-dark .chat-messages { background: #181a20; color:#22242a; } body.undercover-dark .chat-input { background: #23272f; color: #f1f1f1; border-color: #444a5a; } body.undercover-dark .room-info { background: #181a20; color: #22242a; } /* 玩家详情卡片样式 - 浅色简约风格 */ .player-detail-card { background: #fff; border-radius: 16px; padding: 24px; margin: 16px auto; max-width: 480px; box-shadow: 0 4px 24px rgba(0,0,0,0.06); color: #222; position: relative; overflow: hidden; backdrop-filter: blur(6px); border: 1px solid #e3e8ee; } .player-detail-card::before { display: none; } .player-header { display: flex; align-items: center; margin-bottom: 24px; gap: 16px; } .player-avatar { flex-shrink: 0; } .avatar-placeholder { width: 60px; height: 60px; background: #e3e8ee; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; color: #3b4a5a; border: 2px solid #d1d9e6; } .player-info { flex: 1; } .player-name { margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #3b4a5a; } .player-stats-summary { display: flex; gap: 16px; } .stat-item { display: flex; flex-direction: column; align-items: center; } .stat-label { font-size: 12px; opacity: 0.8; margin-bottom: 4px; } .stat-value { font-size: 16px; font-weight: 600; } .win-rate { color: #e6b800; } .stats-grid, .rank-stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 20px; } .stat-card { background: #f3f6fa; border-radius: 12px; padding: 16px; display: flex; align-items: center; gap: 12px; border: 1px solid #e3e8ee; transition: all 0.3s ease; } .stat-card:hover { transform: translateY(-2px); background: #eaf2fb; box-shadow: 0 4px 12px rgba(0,0,0,0.08); } .stat-icon { font-size: 24px; flex-shrink: 0; } .stat-content { flex: 1; } .stat-title { font-size: 12px; opacity: 0.8; margin-bottom: 4px; } .stat-number { font-size: 18px; font-weight: 600; } .wins { color: #3bb273; } .losses { color: #e57373; } .best-streak { color: #e6b800; } .worst-streak { color: #a0a7b8; } .current-streak { background: #f3f6fa; border-radius: 12px; padding: 16px; margin-bottom: 20px; display: flex; align-items: center; justify-content: center; gap: 12px; border: 1px solid #e3e8ee; font-weight: 600; font-size: 16px; color: #3b4a5a; } .streak-win { background: #eafbf3; border-color: #b6e9d0; color: #3bb273; } .streak-lose { background: #fbeaea; border-color: #f3b6b6; color: #e57373; } .streak-icon { font-size: 20px; } .time-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 20px; } .time-stat { background: #f3f6fa; border-radius: 8px; padding: 12px; display: flex; justify-content: space-between; align-items: center; border: 1px solid #e3e8ee; } .time-label { font-size: 12px; opacity: 0.8; } .time-value { font-size: 14px; font-weight: 600; } .last-update { background: #f3f6fa; border-radius: 12px; padding: 16px; border: 1px solid #e3e8ee; } .update-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .update-item:last-child { margin-bottom: 0; } .update-label { font-size: 12px; opacity: 0.8; } .update-time { font-size: 12px; font-weight: 500; } /* 暗黑模式适配 */ body.undercover-dark .player-detail-card { background: #23272f; color: #f1f1f1; border: 1px solid #353b47; } body.undercover-dark .avatar-placeholder { background: #353b47; color: #bfc9d8; border-color: #23272f; } body.undercover-dark .player-name { color: #bfc9d8; } body.undercover-dark .stat-card { background: #2d323c; border-color: #353b47; } body.undercover-dark .stat-card:hover { background: #23272f; } body.undercover-dark .wins { color: #4ade80; } body.undercover-dark .losses { color: #f87171; } body.undercover-dark .best-streak { color: #fbbf24; } body.undercover-dark .worst-streak { color: #a78bfa; } body.undercover-dark .current-streak { background: #2d323c; border-color: #353b47; color: #bfc9d8; } body.undercover-dark .streak-win { background: #23382d; border-color: #3bb273; color: #4ade80; } body.undercover-dark .streak-lose { background: #382323; border-color: #e57373; color: #f87171; } body.undercover-dark .time-stat { background: #2d323c; border-color: #353b47; } body.undercover-dark .last-update { background: #2d323c; border-color: #353b47; } /* 加载动画 */ .loading-spinner { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px; color: #7a8da1; } .spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 16px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* 错误消息样式 */ .error-message { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px; text-align: center; color: #e74c3c; } .error-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.8; } .error-message p { margin: 0; font-size: 16px; font-weight: 500; } /* 美化排行榜表格 - 浅色简约风格 */ .rank-table-wrapper { width: 100%; margin: 0 auto 20px auto; overflow-x: auto; padding: 0 0 10px 0; } .undercover-rank-table { width: 100%; border-collapse: separate; border-spacing: 0; background: #fff; border-radius: 16px; box-shadow: 0 4px 24px rgba(0,0,0,0.06); overflow: hidden; color: #222; margin: 0 auto; font-size: 15px; backdrop-filter: blur(6px); } .undercover-rank-table thead tr { background: #f3f6fa; } .undercover-rank-table th, .undercover-rank-table td { padding: 12px 10px; text-align: center; } .undercover-rank-table th { font-size: 16px; font-weight: 600; letter-spacing: 1px; border-bottom: 2px solid #e3e8ee; color: #3b4a5a; background: #f3f6fa; } .undercover-rank-table tbody tr { background: #f8fafc; transition: background 0.2s; } .undercover-rank-table tbody tr:hover { background: #eaf2fb; } .undercover-rank-table td { border-bottom: 1px solid #e3e8ee; } .undercover-rank-table tr:last-child td { border-bottom: none; } .rank-badge-table { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; background: #e3e8ee; color: #3b4a5a; font-weight: bold; font-size: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.04); border: 2px solid #d1d9e6; } .undercover-rank-table .wins { color: #3bb273; font-weight: 600; } .undercover-rank-table .losses { color: #e57373; font-weight: 600; } .undercover-rank-table .win-rate { color: #e6b800; font-weight: 600; } .undercover-rank-table .undercover-btn { margin: 0; padding: 4px 14px; font-size: 14px; border-radius: 8px; background: #eaf2fb; color: #3b4a5a; border: 1px solid #d1d9e6; transition: background 0.2s, color 0.2s; } .undercover-rank-table .undercover-btn:hover { background: #d1e3fa; color: #1a2a3a; } /* 暗黑模式适配 */ body.undercover-dark .undercover-rank-table { background: #23272f; color: #f1f1f1; box-shadow: 0 4px 24px rgba(0,0,0,0.18); } body.undercover-dark .undercover-rank-table thead tr { background: #2d323c; } body.undercover-dark .undercover-rank-table th { color: #bfc9d8; background: #2d323c; border-bottom: 2px solid #353b47; } body.undercover-dark .undercover-rank-table tbody tr { background: #23272f; } body.undercover-dark .undercover-rank-table tbody tr:hover { background: #2d323c; } body.undercover-dark .undercover-rank-table td { border-bottom: 1px solid #353b47; } body.undercover-dark .rank-badge-table { background: #353b47; color: #bfc9d8; border-color: #23272f; } body.undercover-dark .undercover-rank-table .undercover-btn { background: #353b47; color: #bfc9d8; border: 1px solid #23272f; } body.undercover-dark .undercover-rank-table .undercover-btn:hover { background: #23272f; color: #fff; } /* 新增卡片样式 */ .rank-cards-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 15px; padding: 10px; } .rank-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; padding: 20px; display: flex; flex-direction: column; align-items: center; gap: 10px; backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.2); transition: all 0.3s ease; color: white; position: relative; overflow: hidden; } .rank-card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); } .rank-badge { position: absolute; top: 10px; left: 10px; background-color: rgba(255,255,255,0.2); border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: bold; color: white; border: 2px solid rgba(255,255,255,0.5); z-index: 1; } .rank-card-header { display: flex; align-items: center; gap: 15px; width: 100%; margin-bottom: 15px; } .rank-avatar { flex-shrink: 0; } .rank-player-info { flex: 1; } .rank-player-name { font-size: 18px; font-weight: 600; margin-bottom: 5px; } .rank-player-stats { display: flex; gap: 10px; font-size: 14px; opacity: 0.9; } .rank-stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; width: 100%; margin-bottom: 15px; } .rank-card-action { width: 100%; text-align: center; } `); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址