Compare commits

...

6 Commits

Author SHA1 Message Date
ba6925afd9 Merge branch 'main' of https://git.bitnet.fun/ice/wuziqi
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-29 18:38:59 +08:00
35b805f53f Merge branch 'main' of https://git.bitnet.fun/ice/wuziqi 2024-12-29 18:36:39 +08:00
a158249f23 删除未使用代码 2024-12-29 18:11:32 +08:00
aab9b43fe0 feat: 部署 2024-12-29 18:03:53 +08:00
c389bb7d95 继续落子修复;新增战绩;优化UI 2024-12-29 18:03:09 +08:00
c2a44ba7c7 继续落子修复;新增战绩;优化UI 2024-12-29 17:44:22 +08:00
3 changed files with 370 additions and 274 deletions

View File

@ -76,25 +76,57 @@
<!-- 胜利弹窗 --> <!-- 胜利弹窗 -->
<div v-if="winner" class="victory-modal"> <div v-if="winner" class="victory-modal">
<div class="modal-content"> <div class="modal-content">
<h2>🎉 游戏结束 🎉</h2> <div class="victory-header">
<p v-if="winner === 'timeout'"> <div class="victory-emoji">
{{ getPlayerName(currentPlayer === 1 ? 2 : 1) }}胜利<br />对手超时三次被判负 <template v-if="gameMode === 'pve'">
</p> <template v-if="winner === (isPlayerBlack ? 1 : 2)">
<p v-else> <!-- 玩家胜利 -->
{{ getPlayerName(winner) }}胜利<br /> 🎉
{{ moveCount }} 手获胜 </template>
</p> <template v-else>
<GameStatistics v-if="gameMode === 'pve'" :stats="statistics" /> <!-- 玩家失败 -->
<p 😢
v-if="gameMode === 'pve' && winner !== (isPlayerBlack ? 1 : 2)" </template>
class="taunt-message" </template>
> <template v-else>
{{ getTauntMessage() }} <!-- PVP模式 -->
</p> 🎉
<div class="modal-buttons"> </template>
<button @click="closeModal" class="modal-button secondary">查看棋盘</button> </div>
<button @click="playAgain" class="modal-button primary">再来一局</button> <h2>游戏结束</h2>
<button @click="exitGame" class="modal-button">退出游戏</button> </div>
<div class="victory-info">
<div class="victory-result">
<p v-if="winner === 'timeout'">
{{ getPlayerName(currentPlayer === 1 ? 2 : 1) }}胜利<br />对手超时三次被判负
</p>
<p v-else>
{{ getPlayerName(winner) }}胜利<br />
{{ moveCount }} 手获胜
</p>
</div>
<div class="divider"></div>
<div class="victory-stats">
<GameStatistics v-if="gameMode === 'pve'" :stats="statistics" />
</div>
</div>
<div class="victory-footer">
<p
v-if="gameMode === 'pve' && winner !== (isPlayerBlack ? 1 : 2)"
class="taunt-message"
>
{{ getTauntMessage() }}
</p>
<div class="modal-buttons">
<button @click="closeModal" class="modal-button secondary">查看棋盘</button>
<button @click="playAgain" class="modal-button primary">再来一局</button>
<button @click="exitGame" class="modal-button exit">退出游戏</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -197,8 +229,7 @@ export default {
winningStreaks: { winningStreaks: {
current: 0, current: 0,
max: 0, max: 0,
player: 0, maxLose: 0,
computer: 0,
}, },
}, },
winningPositions: null, // winningPositions: null, //
@ -242,7 +273,15 @@ export default {
}, },
handleClick(row, col) { handleClick(row, col) {
if (this.board[row][col].player !== 0) return if (
!this.gameStarted ||
this.board[row][col].player !== 0 ||
this.winner ||
this.gameEnded || //
(this.gameMode === 'pve' && this.isComputerTurn)
) {
return
}
this.stopTimer() this.stopTimer()
@ -313,7 +352,6 @@ export default {
handleTimeout(player) { handleTimeout(player) {
this.stopTimer() this.stopTimer()
console.log('超时玩家:', player === 1 ? '黑方' : '白方')
// //
const isPlayerTurn = const isPlayerTurn =
@ -406,7 +444,7 @@ export default {
// //
checkWinner(row, col) { checkWinner(row, col) {
const player = this.board[row][col].player // const player = this.board[row][col].player
let hasFiveInRow = false let hasFiveInRow = false
let winningPositions = [] let winningPositions = []
@ -457,7 +495,6 @@ export default {
if (!this.isValidPosition(newRow, newCol)) break if (!this.isValidPosition(newRow, newCol)) break
const nextCell = this.board[newRow][newCol] const nextCell = this.board[newRow][newCol]
if (nextCell.player !== playerType) { if (nextCell.player !== playerType) {
console.log('遇到非己方棋子:', newRow, newCol)
break break
} }
pieces.push([newRow, newCol]) pieces.push([newRow, newCol])
@ -471,20 +508,16 @@ export default {
if (!this.isValidPosition(newRow, newCol)) break if (!this.isValidPosition(newRow, newCol)) break
const nextCell = this.board[newRow][newCol] const nextCell = this.board[newRow][newCol]
if (nextCell.player !== playerType) { if (nextCell.player !== playerType) {
console.log('遇到非己方棋子:', newRow, newCol)
break break
} }
pieces.unshift([newRow, newCol]) pieces.unshift([newRow, newCol])
count++ count++
} }
console.log('找到的棋子:', pieces)
// //
const isWin = count === 5 && pieces.length === 5 const isWin = count === 5 && pieces.length === 5
if (isWin) { if (isWin) {
console.log('确认五子连珠!玩家:', playerType, '位置:', pieces)
this.playWinningAnimation() this.playWinningAnimation()
return { return {
isWin: true, isWin: true,
@ -520,14 +553,12 @@ export default {
// 1 // 1
if (curr[0] - prev[0] !== dx || curr[1] - prev[1] !== dy) { if (curr[0] - prev[0] !== dx || curr[1] - prev[1] !== dy) {
console.log('棋子不连续或方向不一致:', curr, prev)
return false return false
} }
// //
const currPiece = this.board[curr[0]][curr[1]] const currPiece = this.board[curr[0]][curr[1]]
if (this.isPlayerPiece(currPiece) !== isHuman) { if (this.isPlayerPiece(currPiece) !== isHuman) {
console.log('棋子属于不同玩家:', curr)
return false return false
} }
} }
@ -536,13 +567,6 @@ export default {
// //
validateWinningPieces(pieces, player) { validateWinningPieces(pieces, player) {
console.log('验证获胜棋子:', pieces, '期望玩家:', player)
const validation = pieces.map(([row, col]) => ({
position: [row, col],
valid: this.isValidPosition(row, col),
piece: this.board[row][col],
}))
console.log('验证详情:', validation)
return pieces.every(([row, col]) => { return pieces.every(([row, col]) => {
return this.isValidPosition(row, col) && this.board[row][col].player === player return this.isValidPosition(row, col) && this.board[row][col].player === player
}) })
@ -1200,31 +1224,31 @@ export default {
if (this.currentPlayer === (this.isPlayerBlack ? 1 : 2)) { if (this.currentPlayer === (this.isPlayerBlack ? 1 : 2)) {
stats.computerWins++ stats.computerWins++
stats.winningStreaks.current = Math.min(0, stats.winningStreaks.current) - 1 stats.winningStreaks.current = Math.min(0, stats.winningStreaks.current) - 1
stats.winningStreaks.maxLose = Math.max(
stats.winningStreaks.maxLose,
Math.abs(stats.winningStreaks.current),
)
} else { } else {
stats.playerWins++ stats.playerWins++
stats.winningStreaks.current = Math.max(0, stats.winningStreaks.current) + 1 stats.winningStreaks.current = Math.max(0, stats.winningStreaks.current) + 1
stats.winningStreaks.max = Math.max(
stats.winningStreaks.max,
stats.winningStreaks.current,
)
} }
} else if (winner === (this.isPlayerBlack ? 1 : 2)) { } else if (winner === (this.isPlayerBlack ? 1 : 2)) {
stats.playerWins++ stats.playerWins++
stats.winningStreaks.current = Math.max(0, stats.winningStreaks.current) + 1 stats.winningStreaks.current = Math.max(0, stats.winningStreaks.current) + 1
stats.winningStreaks.player = Math.max( stats.winningStreaks.max = Math.max(stats.winningStreaks.max, stats.winningStreaks.current)
stats.winningStreaks.player,
stats.winningStreaks.current,
)
} else { } else {
stats.computerWins++ stats.computerWins++
stats.winningStreaks.current = Math.min(0, stats.winningStreaks.current) - 1 stats.winningStreaks.current = Math.min(0, stats.winningStreaks.current) - 1
stats.winningStreaks.computer = Math.max( stats.winningStreaks.maxLose = Math.max(
stats.winningStreaks.computer, stats.winningStreaks.maxLose,
Math.abs(stats.winningStreaks.current), Math.abs(stats.winningStreaks.current),
) )
} }
stats.winningStreaks.max = Math.max(
stats.winningStreaks.player,
stats.winningStreaks.computer,
)
// //
this.saveStatistics() this.saveStatistics()
}, },
@ -1271,6 +1295,7 @@ export default {
<style> <style>
/* 基础样式 */ /* 基础样式 */
.container { .container {
width: 100vw;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -1294,10 +1319,27 @@ export default {
.modal-content { .modal-content {
background: white; background: white;
padding: 40px; padding: 30px;
border-radius: 16px; border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
animation: modalFadeIn 0.3s ease-out; animation: modalFadeIn 0.3s ease-out;
max-height: 90vh;
overflow-y: auto;
width: 90%;
max-width: 600px;
position: relative;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.modal-content::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #4caf50, #45a049);
border-radius: 16px 16px 0 0;
} }
.modal-content h2 { .modal-content h2 {
@ -1655,105 +1697,146 @@ export default {
z-index: 1000; z-index: 1000;
} }
.victory-modal .modal-content { .modal-content {
background: white; background: white;
padding: 40px; padding: 30px;
border-radius: 16px; border-radius: 16px;
text-align: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
animation: modalFadeIn 0.3s ease-out; animation: modalFadeIn 0.3s ease-out;
max-height: 90vh;
overflow-y: auto;
width: 90%;
max-width: 600px;
} }
.victory-modal h2 { .victory-header {
font-size: 28px; text-align: center;
color: #2c3e50;
margin-bottom: 20px; margin-bottom: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding-top: 10px;
}
.victory-emoji {
font-size: 48px;
animation: bounce 1s infinite;
}
.victory-header h2 {
font-size: 24px;
color: #2c3e50;
margin: 0;
font-weight: 600;
}
.victory-info {
display: flex;
flex-direction: column;
}
.victory-result {
background: linear-gradient(145deg, #f8f9fa, #e9ecef);
padding: 15px;
border-radius: 12px;
text-align: center;
font-size: 16px;
color: #2c3e50;
font-weight: 500;
margin-bottom: 20px;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.05);
}
.divider {
height: 1px;
background: linear-gradient(90deg, transparent, #e0e0e0, transparent);
margin: 15px 0;
}
.victory-stats {
margin: 15px 0;
}
.taunt-message {
margin: 15px 0;
padding: 12px;
background: linear-gradient(145deg, #e53935, #d32f2f);
color: white;
border-radius: 8px;
font-style: italic;
font-size: 16px;
font-weight: bold;
text-align: center;
box-shadow: 0 4px 12px rgba(229, 57, 53, 0.2);
animation: slideIn 0.3s ease-out;
} }
.modal-buttons { .modal-buttons {
display: flex; display: flex;
gap: 20px; gap: 15px;
justify-content: center; justify-content: center;
margin-top: 30px; margin-top: 20px;
padding: 10px 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
} }
.modal-button { .modal-button {
padding: 12px 24px; padding: 10px 20px;
font-size: 16px; font-size: 14px;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
background-color: #6c757d;
color: white; color: white;
} min-width: 100px;
font-weight: 500;
.modal-button.primary { text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
background-color: #4caf50;
}
.start-screen {
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
background: rgba(255, 255, 255, 0.95);
padding: 25px 40px;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(4px);
min-width: 240px;
}
.start-button {
padding: 12px 36px;
font-size: 18px;
background: linear-gradient(145deg, #4caf50, #45a049);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 12px;
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.2);
width: 100%;
}
.start-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(76, 175, 80, 0.3);
}
.turn-info {
color: #2c3e50;
font-size: 15px;
margin-top: 5px;
opacity: 0.8;
} }
.modal-button.secondary { .modal-button.secondary {
background-color: #6c757d; background-color: #6c757d;
} box-shadow: 0 2px 8px rgba(108, 117, 125, 0.2);
background: linear-gradient(145deg, #6c757d, #5a6268);
.modal-button.secondary:hover {
background-color: #5a6268;
transform: translateY(-2px);
} }
.modal-button.primary { .modal-button.primary {
background-color: #4caf50; background-color: #4caf50;
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.2);
background: linear-gradient(145deg, #4caf50, #45a049);
} }
.modal-button.primary:hover { .modal-button.exit {
background-color: #45a049; background: linear-gradient(145deg, #dc3545, #c82333);
transform: translateY(-2px); box-shadow: 0 2px 8px rgba(220, 53, 69, 0.2);
} }
.modal-button:hover { .modal-button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15);
filter: brightness(1.1);
}
@keyframes bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
.view-result { .view-result {
@ -1780,32 +1863,72 @@ export default {
box-shadow: 0 6px 15px rgba(76, 175, 80, 0.3); box-shadow: 0 6px 15px rgba(76, 175, 80, 0.3);
} }
.taunt-message { .exit {
margin-top: 15px; background-color: #dc3545;
padding: 10px;
background: linear-gradient(145deg, #e53935, #d32f2f);
color: white;
border-radius: 8px;
font-style: italic;
animation: tauntPulse 2s infinite;
font-size: 18px;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
box-shadow: 0 4px 15px rgba(229, 57, 53, 0.4);
} }
@keyframes tauntPulse { .start-screen {
from { text-align: center;
transform: scale(1); z-index: 10;
box-shadow: 0 4px 15px rgba(229, 57, 53, 0.4); background: rgba(255, 255, 255, 0.95);
} /* padding: 25px 40px; */
50% { /* border-radius: 12px; */
transform: scale(1.05); /* box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); */
box-shadow: 0 6px 20px rgba(229, 57, 53, 0.6); backdrop-filter: blur(4px);
} min-width: 240px;
100% { margin: 20px auto;
transform: scale(1); width: 80%;
box-shadow: 0 4px 15px rgba(229, 57, 53, 0.4); max-width: 300px;
} position: relative;
/* border: 1px solid rgba(0, 0, 0, 0.1); */
}
.start-button {
padding: 12px 36px;
font-size: 18px;
background: linear-gradient(145deg, #43a047, #2e7d32);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 12px;
box-shadow: 0 4px 15px rgba(67, 160, 71, 0.3);
width: 100%;
font-weight: 500;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.start-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(67, 160, 71, 0.4);
filter: brightness(1.1);
}
.start-button::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(120deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: 0.5s;
}
.start-button:hover::after {
left: 100%;
}
.turn-info {
color: #2c3e50;
font-size: 15px;
margin-top: 5px;
font-weight: 500;
padding: 8px;
background: rgba(0, 0, 0, 0.03);
border-radius: 4px;
} }
</style> </style>

View File

@ -1,9 +1,9 @@
@import './base.css'; @import './base.css';
#app { #app {
max-width: 1280px; /* max-width: 1280px; */
margin: 0 auto; margin: 0 auto;
padding: 2rem; /* padding: 2rem; */
font-weight: normal; font-weight: normal;
} }
@ -30,6 +30,6 @@ a,
#app { #app {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
padding: 0 2rem; /* padding: 0 2rem; */
} }
} }

View File

@ -1,53 +1,49 @@
<template> <template>
<div class="statistics-panel"> <div class="statistics-panel">
<h3>战绩统计</h3>
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-item"> <div class="stats-row">
<div class="stat-label">总对局</div> <div class="stat-item">
<div class="stat-value">{{ totalGames }}</div> <div class="stat-label">总对局</div>
<div class="stat-unit"></div> <div class="stat-value">{{ totalGames }}</div>
</div> <div class="stat-unit"></div>
<div class="stat-item">
<div class="stat-label">胜率</div>
<div
class="stat-value"
:class="{ 'win-rate': true, positive: winRate > 50, negative: winRate < 50 }"
>
{{ formatPercent(winRate) }}
</div> </div>
<div class="stat-unit">%</div> <div class="stat-item">
</div> <div class="stat-label">胜率</div>
<div class="stat-item"> <div
<div class="stat-label">玩家胜场</div> class="stat-value"
<div class="stat-value">{{ playerWins }}</div> :class="{ 'win-rate': true, positive: winRate > 50, negative: winRate < 50 }"
<div class="stat-unit"></div> >
</div> {{ formatPercent(winRate) }}
<div class="stat-item"> </div>
<div class="stat-label">电脑胜场</div> <div class="stat-unit">%</div>
<div class="stat-value">{{ computerWins }}</div>
<div class="stat-unit"></div>
</div>
<div class="stat-item">
<div class="stat-label">最大连胜</div>
<div class="stat-value">{{ maxStreak }}</div>
<div class="stat-unit">连胜</div>
</div>
<div class="stat-item">
<div class="stat-label">平均步数</div>
<div class="stat-value">{{ avgMoves }}</div>
<div class="stat-unit"></div>
</div>
</div>
<div class="stats-chart">
<div class="chart-label">胜率分布</div>
<div class="win-bars">
<div class="win-bar player" :style="{ width: playerBarWidth }">
<span class="bar-label">玩家</span>
<span class="bar-value">{{ formatPercent(playerWinRate) }}%</span>
</div> </div>
<div class="win-bar computer" :style="{ width: computerBarWidth }"> <div class="stat-item">
<span class="bar-label">电脑</span> <div class="stat-label">玩家胜场</div>
<span class="bar-value">{{ formatPercent(computerWinRate) }}%</span> <div class="stat-value">{{ playerWins }}</div>
<div class="stat-unit"></div>
</div>
<div class="stat-item">
<div class="stat-label">电脑胜场</div>
<div class="stat-value">{{ computerWins }}</div>
<div class="stat-unit"></div>
</div>
</div>
<div class="stats-row">
<div class="stat-item">
<div class="stat-label">最大连胜</div>
<div class="stat-value">{{ maxStreak }}</div>
<div class="stat-unit">连胜</div>
</div>
<div class="stat-item">
<div class="stat-label">最大连败</div>
<div class="stat-value">{{ maxLoseStreak }}</div>
<div class="stat-unit">连败</div>
</div>
<div class="stat-item">
<div class="stat-label">平均步数</div>
<div class="stat-value">{{ avgMoves }}</div>
<div class="stat-unit"></div>
</div> </div>
</div> </div>
</div> </div>
@ -76,6 +72,9 @@ export default {
maxStreak() { maxStreak() {
return this.stats?.winningStreaks?.max || 0 return this.stats?.winningStreaks?.max || 0
}, },
maxLoseStreak() {
return this.stats?.winningStreaks?.maxLose || 0
},
avgMoves() { avgMoves() {
return this.stats?.avgMoves || 0 return this.stats?.avgMoves || 0
}, },
@ -83,20 +82,6 @@ export default {
if (!this.totalGames) return 0 if (!this.totalGames) return 0
return (this.playerWins / this.totalGames) * 100 return (this.playerWins / this.totalGames) * 100
}, },
playerWinRate() {
if (!this.totalGames) return 0
return (this.playerWins / this.totalGames) * 100
},
computerWinRate() {
if (!this.totalGames) return 0
return (this.computerWins / this.totalGames) * 100
},
playerBarWidth() {
return `${Math.max(this.playerWinRate, 20)}%`
},
computerBarWidth() {
return `${Math.max(this.computerWinRate, 20)}%`
},
}, },
methods: { methods: {
formatPercent(value) { formatPercent(value) {
@ -108,49 +93,69 @@ export default {
<style scoped> <style scoped>
.statistics-panel { .statistics-panel {
margin: 20px 0; margin: 15px 0;
padding: 20px; padding: 15px;
background: #ffffff; background: #ffffff;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}
.statistics-panel h3 {
color: #2c3e50;
margin-bottom: 20px;
font-size: 18px;
text-align: center;
font-weight: 600;
} }
.stats-grid { .stats-grid {
display: grid; display: flex;
grid-template-columns: repeat(3, 1fr); flex-direction: column;
gap: 15px; gap: 12px;
margin-bottom: 20px; }
.stats-row {
display: flex;
gap: 12px;
justify-content: center;
} }
.stat-item { .stat-item {
flex: 1;
min-width: 120px;
text-align: center; text-align: center;
padding: 15px 10px; padding: 12px 8px;
background: #f8f9fa; background: #f8f9fa;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
transition: transform 0.3s ease; transition: all 0.3s ease;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
position: relative;
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.05);
} }
.stat-item:hover { .stat-item:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-color: rgba(76, 175, 80, 0.3);
}
.stat-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, #4caf50, #45a049);
opacity: 0;
transition: opacity 0.3s ease;
}
.stat-item:hover::before {
opacity: 1;
} }
.stat-label { .stat-label {
font-size: 14px; font-size: 14px;
color: #6c757d; color: #6c757d;
margin-bottom: 8px; margin-bottom: 6px;
font-weight: 500;
} }
.stat-value { .stat-value {
@ -159,77 +164,45 @@ export default {
color: #2c3e50; color: #2c3e50;
transition: color 0.3s ease; transition: color 0.3s ease;
margin-bottom: 2px; margin-bottom: 2px;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
white-space: nowrap;
} }
.stat-unit { .stat-unit {
font-size: 12px; font-size: 12px;
color: #6c757d; color: #6c757d;
font-weight: 500;
opacity: 0.8;
} }
.win-rate { .win-rate {
font-size: 28px; font-size: 24px;
} }
.win-rate.positive { .win-rate.positive {
color: #4caf50; color: #2e7d32;
text-shadow: 0 0 10px rgba(76, 175, 80, 0.3);
} }
.win-rate.negative { .win-rate.negative {
color: #f44336; color: #c62828;
text-shadow: 0 0 10px rgba(244, 67, 54, 0.3);
} }
.stats-chart { @media (max-width: 768px) {
margin-top: 20px; .stats-row {
background: #f8f9fa; flex-wrap: wrap;
padding: 20px; }
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); .stat-item {
width: calc(50% - 6px);
flex: none;
}
} }
.chart-label { @media (max-width: 480px) {
font-size: 14px; .stat-item {
color: #6c757d; width: 100%;
margin-bottom: 15px; }
text-align: center;
}
.win-bars {
display: flex;
flex-direction: column;
gap: 10px;
}
.win-bar {
height: 32px;
margin: 0;
color: white;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
font-size: 14px;
transition: all 0.3s ease;
min-width: 120px;
position: relative;
overflow: hidden;
}
.bar-label {
font-weight: 500;
z-index: 1;
}
.bar-value {
font-weight: 600;
z-index: 1;
}
.win-bar.player {
background: linear-gradient(90deg, #4caf50 0%, #45a049 100%);
}
.win-bar.computer {
background: linear-gradient(90deg, #f44336 0%, #e53935 100%);
} }
</style> </style>