Files
hejiu/pages/games/games.vue
T
cg d335ec290a refactor(app): 重构应用主题和页面结构
- 移除动态主题切换功能,改为全局统一深色主题
- 实现自定义TabBar组件替换原生tabBar,解决iOS闪白问题
- 创建main容器页面统一管理三个tab页面的生命周期和状态
- 将所有页面的onShow/onHide等生命周期方法迁移到子组件内部
- 更新页面路径从index改为main,并调整路由跳转逻辑
- 移除theme-mixin和相关主题工具函数
- 统一页面背景色设置,优化用户体验一致性
2026-09-23 20:56:14 +08:00

1334 lines
38 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page-container games-page">
<!-- 顶部导航 -->
<view class="games-nav" :style="{ paddingTop: navPaddingTop }">
<view class="nav-back" @click="goBack">
<text class="nav-back-icon">‹</text>
</view>
<text class="nav-title">{{ currentGame === 'hub' ? '酒桌小游戏' : gameMeta[currentGame].name }}</text>
<view class="nav-placeholder"></view>
</view>
<!-- ===== 游戏大厅 ===== -->
<view v-if="currentGame === 'hub'" class="game-hub">
<view class="hub-header">
<text class="hub-deco">🎲</text>
<view class="hub-title-wrap">
<text class="hub-title">今晚喝不喝?</text>
<text class="hub-sub">游戏说了算 · 愿赌服输</text>
</view>
</view>
<view class="hub-grid">
<view
class="hub-card"
v-for="(g, i) in gameList"
:key="g.id"
:class="'hub-card-' + g.id"
:style="'animation-delay:' + (i * 0.08) + 's'"
@click="enterGame(g.id)"
>
<view class="hub-card-glow"></view>
<text class="hub-card-emoji">{{ g.emoji }}</text>
<text class="hub-card-name">{{ g.name }}</text>
<text class="hub-card-desc">{{ g.desc }}</text>
<view class="hub-card-players">
<text>{{ g.players }}</text>
</view>
</view>
</view>
<view class="hub-tip">
<text class="hub-tip-icon">🍺</text>
<text class="hub-tip-text">小酌怡情,微醺即止,酒后不开车</text>
</view>
</view>
<!-- ===== 数字炸弹 ===== -->
<view v-if="currentGame === 'bomb'" class="game-stage">
<view class="bomb-arena">
<view class="bomb-range-card" :class="{ 'bomb-boom': bombBoom }">
<text class="bomb-emoji">{{ bombBoom ? '💥' : '💣' }}</text>
<view class="bomb-range-row">
<text class="bomb-num text-num">{{ bombMin }}</text>
<view class="bomb-fuse">
<view class="bomb-fuse-line" :style="{ width: bombFuseWidth }"></view>
</view>
<text class="bomb-num text-num">{{ bombMax }}</text>
</view>
<text class="bomb-hint">{{ bombBoom ? 'boom!踩中炸弹了!' : '在范围内猜一个数字,踩中就喝' }}</text>
</view>
<view v-if="bombBoom" class="result-banner result-drink">
<text class="result-emoji">🍻</text>
<text class="result-text">喝酒!一杯闷!</text>
</view>
<view v-else-if="bombSafe" class="result-banner result-safe">
<text class="result-emoji">😎</text>
<text class="result-text">安全过关,下一位!</text>
</view>
<view class="bomb-history" v-if="bombGuesses.length">
<view class="bomb-guess-tag" v-for="(g, i) in bombGuesses" :key="i" :class="g.dir === 'high' ? 'tag-high' : 'tag-low'">
<text>{{ g.val }} {{ g.dir === 'high' ? '↓' : '↑' }}</text>
</view>
</view>
<view class="bomb-input-area" v-if="!bombBoom && !bombSafe">
<input
class="bomb-input text-num"
type="number"
v-model="bombInput"
:placeholder="bombMin + ' ~ ' + bombMax"
confirm-type="done"
@confirm="bombGuess"
/>
<view class="btn-amber bomb-go-btn" @click="bombGuess">
<text>拆弹</text>
</view>
</view>
<view class="game-actions" v-if="bombBoom || bombSafe">
<view class="btn-amber game-restart-btn" @click="bombReset">
<text>再来一轮</text>
</view>
</view>
</view>
<view class="game-rule-card">
<text class="rule-title">📖 玩法</text>
<text class="rule-text">系统随机埋一个 1~100 的数字炸弹,轮流猜数,范围不断缩小,踩中炸弹的人喝酒。范围越小心跳越快…</text>
</view>
</view>
<!-- ===== 猜拳大战 ===== -->
<view v-if="currentGame === 'rps'" class="game-stage">
<view class="rps-arena">
<view class="rps-score-row">
<view class="rps-score-item">
<text class="rps-score-label">你</text>
<text class="rps-score-num text-num">{{ rpsWin }}</text>
</view>
<text class="rps-vs">VS</text>
<view class="rps-score-item">
<text class="rps-score-label">酒神AI</text>
<text class="rps-score-num text-num">{{ rpsLose }}</text>
</view>
</view>
<view class="rps-battle">
<view class="rps-hand rps-hand-left" :class="{ 'hand-shake': rpsShaking, 'hand-reveal': !rpsShaking && rpsRound > 0 }">
<text class="rps-hand-emoji">{{ rpsShaking ? '✊' : rpsPlayerHand }}</text>
</view>
<view class="rps-clash" v-if="rpsResult">
<text class="rps-clash-text">{{ rpsResult === 'win' ? '⚡' : rpsResult === 'lose' ? '💫' : '🤝' }}</text>
</view>
<view class="rps-hand rps-hand-right" :class="{ 'hand-shake': rpsShaking, 'hand-reveal': !rpsShaking && rpsRound > 0 }">
<text class="rps-hand-emoji">{{ rpsShaking ? '✊' : rpsAiHand }}</text>
</view>
</view>
<view v-if="rpsResult === 'lose'" class="result-banner result-drink">
<text class="result-emoji">🍺</text>
<text class="result-text">输了!喝酒!</text>
</view>
<view v-else-if="rpsResult === 'win'" class="result-banner result-safe">
<text class="result-emoji">🎉</text>
<text class="result-text">赢了!对方喝!</text>
</view>
<view v-else-if="rpsResult === 'draw'" class="result-banner result-draw">
<text class="result-emoji">🔄</text>
<text class="result-text">平局,再来!</text>
</view>
<view class="rps-picker" v-if="!rpsShaking">
<view
class="rps-choice"
v-for="c in rpsChoices"
:key="c.key"
:class="{ 'choice-active': rpsLastPick === c.key }"
@click="rpsPlay(c.key)"
>
<text class="rps-choice-emoji">{{ c.emoji }}</text>
<text class="rps-choice-name">{{ c.name }}</text>
</view>
</view>
<view class="rps-countdown" v-else>
<text class="rps-countdown-text">{{ rpsCountdown }}</text>
</view>
</view>
<view class="game-rule-card">
<text class="rule-title">📖 玩法</text>
<text class="rule-text">和酒神AI猜拳,三局定胜负?不存在的——输一局喝一杯,赢一局对方喝,平局一起抿一口。</text>
</view>
</view>
<!-- ===== 骰子大话 ===== -->
<view v-if="currentGame === 'dice'" class="game-stage">
<view class="dice-arena">
<view class="dice-cup" :class="{ 'cup-shaking': diceShaking }" @click="diceRoll">
<view class="dice-pair">
<view class="die" :class="{ 'die-rolling': diceShaking }">
<view class="die-grid">
<view class="die-cell" v-for="p in 9" :key="p">
<view class="die-pip" v-if="dicePips(diceA).indexOf(p) > -1"></view>
</view>
</view>
</view>
<view class="die die-2" :class="{ 'die-rolling': diceShaking }">
<view class="die-grid">
<view class="die-cell" v-for="p in 9" :key="p">
<view class="die-pip" v-if="dicePips(diceB).indexOf(p) > -1"></view>
</view>
</view>
</view>
</view>
<text class="dice-sum text-num" v-if="!diceShaking && diceRound > 0">{{ diceA + diceB }}</text>
</view>
<view v-if="diceRule && !diceShaking" class="dice-rule-result" :class="diceRule.type === 'drink' ? 'result-drink' : diceRule.type === 'safe' ? 'result-safe' : 'result-fun'">
<text class="dice-rule-emoji">{{ diceRule.emoji }}</text>
<view class="dice-rule-text-wrap">
<text class="dice-rule-name">{{ diceRule.name }}</text>
<text class="dice-rule-desc">{{ diceRule.desc }}</text>
</view>
</view>
<view class="dice-history" v-if="diceHistory.length">
<view class="dice-history-item" v-for="(h, i) in diceHistory" :key="i">
<text class="dice-history-val text-num">{{ h.a }}+{{ h.b }}</text>
<text class="dice-history-rule">{{ h.rule }}</text>
</view>
</view>
<view class="btn-amber dice-roll-btn" @click="diceRoll" :class="{ 'btn-disabled': diceShaking }">
<text>{{ diceShaking ? '摇骰中…' : '🎲 摇骰子' }}</text>
</view>
</view>
<view class="game-rule-card">
<text class="rule-title">📖 玩法</text>
<text class="rule-text">轮流摇两颗骰子:豹子全桌敬你一杯;7点安全过关;双数你喝一杯;单数指定一人喝;点数≤4 干杯!</text>
</view>
</view>
<!-- ===== 命运转盘(老虎机式) ===== -->
<view v-if="currentGame === 'wheel'" class="game-stage">
<view class="wheel-arena">
<view class="slot-machine">
<view class="slot-glow" :class="{ 'slot-glow-on': wheelSpinning }"></view>
<view class="slot-window">
<view class="slot-strip" :style="{ transform: 'translateY(' + slotOffset + 'rpx)', transition: slotTransition }">
<view
class="slot-item"
v-for="(s, i) in slotStrip"
:key="i"
:class="{ 'slot-item-active': !wheelSpinning && wheelResult && i === slotTarget }"
>
<view class="slot-item-emoji-box">
<text class="slot-item-emoji">{{ s.emoji }}</text>
</view>
<text class="slot-item-label">{{ s.label }}</text>
</view>
</view>
<view class="slot-mask-top"></view>
<view class="slot-mask-bottom"></view>
<view class="slot-indicator">
<text class="slot-ind-arrow">▶</text>
<text class="slot-ind-arrow slot-ind-arrow-r">◀</text>
</view>
</view>
</view>
<view v-if="wheelResult && !wheelSpinning" class="result-banner" :class="wheelResult.type === 'drink' ? 'result-drink' : wheelResult.type === 'safe' ? 'result-safe' : 'result-fun'">
<text class="result-emoji">{{ wheelResult.emoji }}</text>
<text class="result-text">{{ wheelResult.label }}!愿赌服输!</text>
</view>
<view class="btn-amber slot-spin-btn" :class="{ 'btn-disabled': wheelSpinning }" @click="wheelSpin">
<text>{{ wheelSpinning ? '命运转动中…' : '🎰 转一转' }}</text>
</view>
</view>
<view class="game-rule-card">
<text class="rule-title">📖 玩法</text>
<text class="rule-text">点击按钮转动命运滚轮,停下来时中间高亮的那格就是你的命运,不许耍赖!</text>
</view>
</view>
<!-- 返回大厅浮动按钮 -->
<view v-if="currentGame !== 'hub'" class="back-hub-btn" @click="currentGame = 'hub'">
<text>🏠 返回大厅</text>
</view>
</view>
</template>
<script>
export default {
data() {
let navPaddingTop = '20px'
try {
const menuBtn = uni.getMenuButtonBoundingClientRect()
navPaddingTop = menuBtn.top + 'px'
} catch (e) {}
return {
navPaddingTop,
currentGame: 'hub',
gameList: [
{ id: 'bomb', emoji: '💣', name: '数字炸弹', desc: '猜中即喝,心跳加速', players: '2人+' },
{ id: 'rps', emoji: '✊', name: '猜拳大战', desc: '挑战酒神AI,输了干杯', players: '1人+' },
{ id: 'dice', emoji: '🎲', name: '骰子大话', desc: '豹子通杀,七点保命', players: '2人+' },
{ id: 'wheel', emoji: '🎡', name: '命运转盘', desc: '转到什么喝什么', players: '3人+' }
],
gameMeta: {
bomb: { name: '💣 数字炸弹' },
rps: { name: '✊ 猜拳大战' },
dice: { name: '🎲 骰子大话' },
wheel: { name: '🎡 命运转盘' }
},
// --- 数字炸弹 ---
bombTarget: 0,
bombMin: 1,
bombMax: 100,
bombInput: '',
bombGuesses: [],
bombBoom: false,
bombSafe: false,
// --- 猜拳 ---
rpsChoices: [
{ key: 'rock', emoji: '✊', name: '石头' },
{ key: 'scissors', emoji: '✌️', name: '剪刀' },
{ key: 'paper', emoji: '🖐', name: '布' }
],
rpsPlayerHand: '❔',
rpsAiHand: '❔',
rpsShaking: false,
rpsCountdown: 3,
rpsResult: '',
rpsWin: 0,
rpsLose: 0,
rpsRound: 0,
rpsLastPick: '',
rpsTimer: null,
// --- 骰子 ---
diceA: 1,
diceB: 1,
diceShaking: false,
diceRound: 0,
diceRule: null,
diceHistory: [],
// --- 转盘(老虎机式) ---
wheelSegments: [
{ label: '喝一杯', emoji: '🍺', type: 'drink' },
{ label: '真心话', emoji: '💬', type: 'fun' },
{ label: '免一杯', emoji: '🛡️', type: 'safe' },
{ label: '大冒险', emoji: '🔥', type: 'fun' },
{ label: '干一杯', emoji: '🥂', type: 'drink' },
{ label: '指定喝', emoji: '👉', type: 'drink' },
{ label: '表演节目', emoji: '🎤', type: 'fun' },
{ label: '双倍快乐', emoji: '🍻', type: 'drink' }
],
slotOffset: -120,
slotTransition: 'none',
slotTarget: -1,
wheelSpinning: false,
wheelResult: null
}
},
computed: {
bombFuseWidth() {
const range = this.bombMax - this.bombMin
const pct = Math.max(8, Math.min(100, range))
return pct + '%'
},
// 老虎机滚动条:重复4轮方便滚动动画
slotStrip() {
const arr = []
for (let r = 0; r < 4; r++) {
this.wheelSegments.forEach(s => arr.push(s))
}
return arr
}
},
onLoad() {
this.bombReset()
},
onUnload() {
if (this.rpsTimer) clearInterval(this.rpsTimer)
},
onShareAppMessage() {
return {
title: '酒桌小游戏 - 今晚谁喝?来 battle!',
path: '/pages/games/games'
}
},
methods: {
goBack() {
if (this.currentGame !== 'hub') {
this.currentGame = 'hub'
} else {
uni.navigateBack({ delta: 1 })
}
},
enterGame(id) {
this.currentGame = id
if (id === 'bomb') this.bombReset()
if (id === 'rps') this.rpsResetRound()
if (id === 'dice') { this.diceRule = null; this.diceHistory = []; this.diceRound = 0 }
if (id === 'wheel') { this.wheelResult = null }
},
/* ========== 数字炸弹 ========== */
bombReset() {
this.bombTarget = Math.floor(Math.random() * 100) + 1
this.bombMin = 1
this.bombMax = 100
this.bombInput = ''
this.bombGuesses = []
this.bombBoom = false
this.bombSafe = false
},
bombGuess() {
const val = parseInt(this.bombInput)
if (isNaN(val) || val < this.bombMin || val > this.bombMax) {
uni.showToast({ title: `请输入 ${this.bombMin}~${this.bombMax} 的数字`, icon: 'none' })
return
}
if (val === this.bombTarget) {
this.bombBoom = true
uni.vibrateShort({ type: 'heavy' })
return
}
this.bombGuesses.push({ val, dir: val > this.bombTarget ? 'high' : 'low' })
if (val > this.bombTarget) {
this.bombMax = val - 1
} else {
this.bombMin = val + 1
}
this.bombInput = ''
uni.vibrateShort({ type: 'light' })
// 范围缩到1时安全过关
if (this.bombMin === this.bombMax && this.bombMin !== this.bombTarget) {
this.bombSafe = true
}
},
/* ========== 猜拳大战 ========== */
rpsResetRound() {
this.rpsPlayerHand = '❔'
this.rpsAiHand = '❔'
this.rpsResult = ''
this.rpsShaking = false
},
rpsPlay(key) {
if (this.rpsShaking) return
this.rpsLastPick = key
this.rpsResult = ''
this.rpsShaking = true
this.rpsCountdown = 3
this.rpsRound++
uni.vibrateShort({ type: 'light' })
this.rpsTimer = setInterval(() => {
this.rpsCountdown--
if (this.rpsCountdown <= 0) {
clearInterval(this.rpsTimer)
this.rpsTimer = null
this.rpsReveal(key)
}
}, 500)
},
rpsReveal(playerKey) {
const keys = ['rock', 'scissors', 'paper']
const emojis = { rock: '✊', scissors: '✌️', paper: '🖐' }
const aiKey = keys[Math.floor(Math.random() * 3)]
this.rpsPlayerHand = emojis[playerKey]
this.rpsAiHand = emojis[aiKey]
this.rpsShaking = false
if (playerKey === aiKey) {
this.rpsResult = 'draw'
} else if (
(playerKey === 'rock' && aiKey === 'scissors') ||
(playerKey === 'scissors' && aiKey === 'paper') ||
(playerKey === 'paper' && aiKey === 'rock')
) {
this.rpsResult = 'win'
this.rpsWin++
} else {
this.rpsResult = 'lose'
this.rpsLose++
uni.vibrateShort({ type: 'heavy' })
}
},
/* ========== 骰子大话 ========== */
// 骰子点数对应的 pip 位置(3x3 九宫格,1~9 从左到右、从上到下)
dicePips(val) {
const map = {
1: [5],
2: [1, 9],
3: [1, 5, 9],
4: [1, 3, 7, 9],
5: [1, 3, 5, 7, 9],
6: [1, 3, 4, 6, 7, 9]
}
return map[val] || []
},
diceRoll() {
if (this.diceShaking) return
this.diceShaking = true
this.diceRule = null
this.diceRound++
uni.vibrateShort({ type: 'medium' })
// 摇动期间快速随机跳面
const flicker = setInterval(() => {
this.diceA = Math.floor(Math.random() * 6) + 1
this.diceB = Math.floor(Math.random() * 6) + 1
}, 80)
setTimeout(() => {
clearInterval(flicker)
this.diceA = Math.floor(Math.random() * 6) + 1
this.diceB = Math.floor(Math.random() * 6) + 1
this.diceShaking = false
this.diceApplyRule()
uni.vibrateShort({ type: 'heavy' })
}, 1200)
},
diceApplyRule() {
const sum = this.diceA + this.diceB
let rule
if (this.diceA === this.diceB) {
rule = { emoji: '👑', name: '豹子!', desc: '天选之人!全桌敬你一杯', type: 'safe' }
} else if (sum === 7) {
rule = { emoji: '🛡️', name: '七点保命', desc: '幸运7!安全过关', type: 'safe' }
} else if (sum <= 4) {
rule = { emoji: '💀', name: '天崩开局', desc: '点数太小,直接干杯!', type: 'drink' }
} else if (sum % 2 === 0) {
rule = { emoji: '🍺', name: '双数喝一杯', desc: '愿赌服输,一杯下肚', type: 'drink' }
} else {
rule = { emoji: '👉', name: '单数点名', desc: '指定一人喝一杯,就你了!', type: 'fun' }
}
this.diceRule = rule
this.diceHistory.unshift({ a: this.diceA, b: this.diceB, rule: rule.name })
if (this.diceHistory.length > 5) this.diceHistory.pop()
},
/* ========== 命运转盘(老虎机式) ========== */
wheelSpin() {
if (this.wheelSpinning) return
this.wheelSpinning = true
this.wheelResult = null
this.slotTarget = -1
uni.vibrateShort({ type: 'medium' })
const count = this.wheelSegments.length
const segH = 120 // 每格高度 rpx
const targetIndex = Math.floor(Math.random() * count)
// 目标位置在第3轮(索引 2*count + targetIndex)
const target = count * 2 + targetIndex
this.slotTarget = target
// 先瞬间回到起始位
this.slotTransition = 'none'
this.slotOffset = -segH // 显示第2个在中间
setTimeout(() => {
// 滚动到目标,中间项对齐窗口中心
this.slotTransition = 'transform 3s cubic-bezier(0.12, 0.75, 0.18, 1)'
this.slotOffset = -(target * segH) + segH // 让 target 居中(窗口显示3格,中间格偏移=1格)
}, 50)
setTimeout(() => {
this.wheelSpinning = false
this.wheelResult = this.wheelSegments[targetIndex]
uni.vibrateShort({ type: 'heavy' })
}, 3200)
}
}
}
</script>
<style lang="scss" scoped>
.games-page {
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
}
/* === 顶部导航 === */
.games-nav {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 $sp-lg;
height: 88rpx;
position: sticky;
top: 0;
z-index: 50;
background: $bg-base;
}
.nav-back {
width: 64rpx; height: 64rpx;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
background: $bg-elevated;
transition: transform $duration-fast $ease-out;
&:active { transform: scale(0.88); }
}
.nav-back-icon {
font-size: 44rpx;
color: $text-primary;
font-weight: $fw-bold;
margin-top: -6rpx;
}
.nav-title {
font-size: $fs-lg;
font-weight: $fw-bold;
color: $text-primary;
}
.nav-placeholder { width: 64rpx; }
/* === 游戏大厅 === */
.game-hub {
padding: $sp-xl $sp-lg 0;
}
.hub-header {
display: flex;
align-items: center;
gap: $sp-lg;
margin-bottom: $sp-xl;
}
.hub-deco {
font-size: 88rpx;
animation: hub-float 3s ease-in-out infinite;
}
@keyframes hub-float {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-12rpx) rotate(8deg); }
}
.hub-title {
display: block;
font-size: $fs-2xl;
font-weight: $fw-black;
color: $text-primary;
letter-spacing: -1rpx;
}
.hub-sub {
display: block;
font-size: $fs-sm;
color: $text-tertiary;
margin-top: 8rpx;
}
.hub-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: $sp-md;
}
.hub-card {
position: relative;
padding: $sp-xl $sp-lg;
border-radius: $radius-xl;
background: $bg-card;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
overflow: hidden;
animation: card-enter 0.5s $ease-out both;
transition: transform $duration-fast $ease-out, box-shadow $duration-fast $ease-out;
&:active {
transform: scale(0.95);
box-shadow: $shadow-amber;
}
}
@keyframes card-enter {
from { opacity: 0; transform: translateY(40rpx) scale(0.92); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.hub-card-glow {
position: absolute;
width: 180rpx; height: 180rpx;
top: -60rpx; right: -60rpx;
border-radius: 50%;
pointer-events: none;
}
.hub-card-bomb .hub-card-glow { background: radial-gradient(circle, rgba(255,107,107,0.14) 0%, transparent 70%); }
.hub-card-rps .hub-card-glow { background: radial-gradient(circle, rgba(232,168,56,0.14) 0%, transparent 70%); }
.hub-card-dice .hub-card-glow { background: radial-gradient(circle, rgba(94,198,160,0.14) 0%, transparent 70%); }
.hub-card-wheel .hub-card-glow { background: radial-gradient(circle, rgba(139,133,184,0.16) 0%, transparent 70%); }
.hub-card-emoji {
display: block;
font-size: 72rpx;
margin-bottom: $sp-md;
}
.hub-card-name {
display: block;
font-size: $fs-lg;
font-weight: $fw-bold;
color: $text-primary;
margin-bottom: 8rpx;
}
.hub-card-desc {
display: block;
font-size: $fs-xs;
color: $text-tertiary;
line-height: $lh-normal;
}
.hub-card-players {
display: inline-flex;
margin-top: $sp-md;
padding: 4rpx 16rpx;
border-radius: $radius-full;
background: $bg-elevated;
text {
font-size: 18rpx;
color: $text-secondary;
}
}
.hub-tip {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-sm;
margin-top: $sp-2xl;
padding: $sp-md;
border-radius: $radius-lg;
background: rgba(232,168,56,0.06);
border: 1rpx dashed var(--border-dashed, rgba(255,255,255,0.12));
}
.hub-tip-icon { font-size: 28rpx; }
.hub-tip-text { font-size: $fs-xs; color: $text-tertiary; }
/* === 游戏舞台通用 === */
.game-stage {
padding: $sp-xl $sp-lg 0;
animation: stage-in 0.4s $ease-out both;
}
@keyframes stage-in {
from { opacity: 0; transform: translateY(30rpx); }
to { opacity: 1; transform: translateY(0); }
}
.game-rule-card {
margin-top: $sp-xl;
padding: $sp-lg;
border-radius: $radius-lg;
background: $bg-card;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
}
.rule-title {
display: block;
font-size: 30rpx;
font-weight: $fw-bold;
color: $text-secondary;
margin-bottom: $sp-sm;
}
.rule-text {
font-size: 30rpx;
color: $text-tertiary;
line-height: $lh-loose;
}
.result-banner {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-sm;
padding: $sp-lg;
border-radius: $radius-lg;
margin-top: $sp-lg;
animation: result-pop 0.4s $ease-bounce both;
&.result-drink {
background: linear-gradient(135deg, rgba(255,107,107,0.14), rgba(232,168,56,0.10));
border: 1rpx solid rgba(255,107,107,0.25);
}
&.result-safe {
background: linear-gradient(135deg, rgba(94,198,160,0.14), rgba(94,198,160,0.05));
border: 1rpx solid rgba(94,198,160,0.25);
}
&.result-fun {
background: linear-gradient(135deg, rgba(139,133,184,0.16), rgba(139,133,184,0.05));
border: 1rpx solid rgba(139,133,184,0.28);
}
&.result-draw {
background: $bg-elevated;
border: 1rpx solid var(--border-subtle, rgba(255,255,255,0.10));
}
}
@keyframes result-pop {
from { opacity: 0; transform: scale(0.7); }
to { opacity: 1; transform: scale(1); }
}
.result-emoji { font-size: 44rpx; }
.result-text { font-size: $fs-lg; font-weight: $fw-bold; color: $text-primary; }
.game-actions {
display: flex;
justify-content: center;
margin-top: $sp-xl;
}
.game-restart-btn {
min-width: 320rpx;
text-align: center;
}
.btn-amber {
display: flex;
align-items: center;
justify-content: center;
padding: $sp-md $sp-xl;
border-radius: $radius-full;
background: linear-gradient(135deg, var(--amber, #E8A838), var(--amber-deep, #C47F17));
box-shadow: var(--shadow-amber, 0 8rpx 32rpx rgba(232,168,56,0.25));
transition: transform $duration-fast $ease-out, opacity $duration-fast;
text { color: var(--text-on-amber, #0B0B14); font-size:40rpx; font-weight: $fw-bold; }
&:active { transform: scale(0.94); }
&.btn-disabled { opacity: 0.5; pointer-events: none; }
}
/* === 数字炸弹 === */
.bomb-arena { padding-top: $sp-md; }
.bomb-range-card {
text-align: center;
padding: $sp-2xl $sp-xl;
border-radius: $radius-xl;
background: $bg-card;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
transition: all $duration-base;
&.bomb-boom {
border-color: rgba(255,107,107,0.4);
background: linear-gradient(160deg, rgba(255,107,107,0.10), $bg-card);
animation: boom-shake 0.5s ease-out;
}
}
@keyframes boom-shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-14rpx) rotate(-1deg); }
40% { transform: translateX(12rpx) rotate(1deg); }
60% { transform: translateX(-8rpx); }
80% { transform: translateX(6rpx); }
}
.bomb-emoji {
display: block;
font-size: 96rpx;
margin-bottom: $sp-lg;
animation: bomb-pulse 1.6s ease-in-out infinite;
}
@keyframes bomb-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.12); }
}
.bomb-range-row {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-lg;
}
.bomb-num {
font-size: $fs-hero;
font-weight: $fw-black;
color: $text-primary;
line-height: 1;
}
.bomb-fuse {
width: 160rpx;
height: 8rpx;
border-radius: 4rpx;
background: $bg-elevated;
overflow: hidden;
}
.bomb-fuse-line {
height: 100%;
border-radius: 4rpx;
background: linear-gradient(90deg, var(--mint, #5EC6A0), var(--amber, #E8A838), var(--coral, #FF6B6B));
transition: width 0.4s $ease-out;
}
.bomb-hint {
display: block;
margin-top: $sp-lg;
font-size: $fs-sm;
color: $text-tertiary;
}
.bomb-history {
display: flex;
flex-wrap: wrap;
gap: $sp-sm;
margin-top: $sp-lg;
justify-content: center;
}
.bomb-guess-tag {
padding: 8rpx 20rpx;
border-radius: $radius-full;
animation: tag-in 0.3s $ease-bounce both;
text { font-size: $fs-sm; font-weight: $fw-medium; }
&.tag-high {
background: rgba(255,107,107,0.12);
text { color: var(--coral, #FF6B6B); }
}
&.tag-low {
background: rgba(94,198,160,0.12);
text { color: var(--mint, #5EC6A0); }
}
}
@keyframes tag-in {
from { opacity: 0; transform: scale(0.5); }
to { opacity: 1; transform: scale(1); }
}
.bomb-input-area {
display: flex;
gap: $sp-md;
margin-top: $sp-xl;
}
.bomb-input {
flex: 1;
height: 96rpx;
padding: 0 $sp-xl;
border-radius: $radius-full;
background: var(--bg-input, rgba(255,255,255,0.08));
border: 1rpx solid var(--border-subtle, rgba(255,255,255,0.10));
color: $text-primary;
font-size: $fs-xl;
text-align: center;
font-weight: $fw-bold;
}
.bomb-go-btn {
min-width: 180rpx;
height: 96rpx;
}
/* === 猜拳大战 === */
.rps-arena { padding-top: $sp-md; }
.rps-score-row {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-xl;
margin-bottom: $sp-xl;
}
.rps-score-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4rpx;
}
.rps-score-label { font-size: $fs-xs; color: $text-tertiary; }
.rps-score-num { font-size: $fs-2xl; font-weight: $fw-black; color: $amber; }
.rps-vs { font-size: $fs-sm; font-weight: $fw-black; color: $text-tertiary; letter-spacing: 2rpx; }
.rps-battle {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-xl;
padding: $sp-xl 0;
}
.rps-hand {
width: 180rpx; height: 180rpx;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
background: $bg-card;
border: 2rpx solid var(--border-faint, rgba(255,255,255,0.08));
box-shadow: $shadow-md;
}
.rps-hand-emoji { font-size: 80rpx; }
.hand-shake {
animation: hand-shake 0.5s ease-in-out infinite;
}
@keyframes hand-shake {
0%, 100% { transform: translateY(0) rotate(0deg); }
25% { transform: translateY(-20rpx) rotate(-6deg); }
75% { transform: translateY(10rpx) rotate(4deg); }
}
.hand-reveal {
animation: hand-pop 0.35s $ease-bounce both;
}
@keyframes hand-pop {
from { transform: scale(0.6) rotate(-10deg); }
to { transform: scale(1) rotate(0deg); }
}
.rps-clash-text { font-size: 56rpx; animation: clash-flash 0.6s ease-out both; }
@keyframes clash-flash {
0% { opacity: 0; transform: scale(2.4); }
100% { opacity: 1; transform: scale(1); }
}
.rps-picker {
display: flex;
justify-content: center;
gap: $sp-lg;
margin-top: $sp-lg;
}
.rps-choice {
display: flex;
flex-direction: column;
align-items: center;
gap: $sp-sm;
padding: $sp-lg $sp-xl;
border-radius: $radius-xl;
background: $bg-card;
border: 2rpx solid var(--border-faint, rgba(255,255,255,0.08));
transition: all $duration-fast $ease-out;
&:active { transform: scale(0.9); }
&.choice-active {
border-color: rgba(232,168,56,0.5);
background: rgba(232,168,56,0.08);
box-shadow: var(--shadow-amber, 0 8rpx 32rpx rgba(232,168,56,0.25));
}
}
.rps-choice-emoji { font-size: 56rpx; }
.rps-choice-name { font-size: $fs-xs; color: $text-secondary; font-weight: $fw-medium; }
.rps-countdown {
display: flex;
justify-content: center;
margin-top: $sp-lg;
}
.rps-countdown-text {
font-size: $fs-hero;
font-weight: $fw-black;
color: $amber;
animation: count-zoom 0.5s $ease-out both;
}
@keyframes count-zoom {
from { opacity: 0; transform: scale(1.8); }
to { opacity: 1; transform: scale(1); }
}
/* === 骰子大话 === */
.dice-arena {
display: flex;
flex-direction: column;
align-items: center;
padding-top: $sp-md;
}
.dice-cup {
position: relative;
width: 400rpx; height: 320rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: $sp-lg;
border-radius: $radius-xl;
background: $bg-card;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
box-shadow: $shadow-md;
transition: box-shadow $duration-base;
&.cup-shaking {
animation: cup-shake 0.15s linear infinite;
box-shadow: var(--shadow-amber, 0 8rpx 32rpx rgba(232,168,56,0.25));
}
}
@keyframes cup-shake {
0%, 100% { transform: rotate(0deg) translateX(0); }
25% { transform: rotate(-2deg) translateX(-6rpx); }
75% { transform: rotate(2deg) translateX(6rpx); }
}
.dice-pair {
display: flex;
gap: $sp-lg;
}
.die {
width: 132rpx; height: 132rpx;
display: flex;
align-items: center; justify-content: center;
border-radius: $radius-md;
background: linear-gradient(160deg, #FBF7EF, #EAE2D3);
box-shadow: inset 0 4rpx 8rpx rgba(255,255,255,0.7), inset 0 -6rpx 12rpx rgba(0,0,0,0.12), $shadow-sm;
transition: transform $duration-base $ease-bounce;
&.die-rolling { animation: die-spin 0.3s linear infinite; }
}
@keyframes die-spin {
0% { transform: rotate(0deg) scale(1); }
50% { transform: rotate(180deg) scale(0.85); }
100% { transform: rotate(360deg) scale(1); }
}
.die-grid {
display: flex;
flex-wrap: wrap;
width: 96rpx;
height: 96rpx;
}
.die-cell {
width: 32rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.die-pip {
width: 18rpx;
height: 18rpx;
border-radius: 50%;
background: #241B10;
box-shadow: inset 0 2rpx 3rpx rgba(0,0,0,0.5);
}
.dice-sum {
font-size: $fs-2xl;
font-weight: $fw-black;
color: $amber;
}
.dice-rule-result {
display: flex;
align-items: center;
gap: $sp-md;
width: 100%;
margin-top: $sp-lg;
padding: $sp-lg;
border-radius: $radius-lg;
animation: result-pop 0.4s $ease-bounce both;
&.result-drink {
background: linear-gradient(135deg, rgba(255,107,107,0.12), rgba(255,107,107,0.04));
border: 1rpx solid rgba(255,107,107,0.22);
}
&.result-safe {
background: linear-gradient(135deg, rgba(94,198,160,0.12), rgba(94,198,160,0.04));
border: 1rpx solid rgba(94,198,160,0.22);
}
&.result-fun {
background: linear-gradient(135deg, rgba(139,133,184,0.14), rgba(139,133,184,0.04));
border: 1rpx solid rgba(139,133,184,0.24);
}
}
.dice-rule-emoji { font-size: 48rpx; }
.dice-rule-name { display: block; font-size: $fs-base; font-weight: $fw-bold; color: $text-primary; }
.dice-rule-desc { display: block; font-size: $fs-xs; color: $text-tertiary; margin-top: 4rpx; }
.dice-history {
display: flex;
gap: $sp-sm;
margin-top: $sp-lg;
flex-wrap: wrap;
justify-content: center;
}
.dice-history-item {
display: flex;
align-items: center;
gap: 8rpx;
padding: 6rpx 16rpx;
border-radius: $radius-full;
background: $bg-elevated;
}
.dice-history-val { font-size: $fs-xs; font-weight: $fw-bold; color: $text-secondary; }
.dice-history-rule { font-size: 18rpx; color: $text-tertiary; }
.dice-roll-btn {
margin-top: $sp-xl;
min-width: 360rpx;
height: 96rpx;
}
/* === 命运转盘(老虎机) === */
.wheel-arena {
display: flex;
flex-direction: column;
align-items: center;
padding-top: $sp-md;
}
.slot-machine {
position: relative;
width: 100%;
display: flex;
justify-content: center;
}
.slot-glow {
position: absolute;
top: -30rpx; left: 50%;
transform: translateX(-50%);
width: 480rpx; height: 480rpx;
border-radius: 50%;
background: radial-gradient(circle, rgba(232,168,56,0.10) 0%, transparent 70%);
pointer-events: none;
opacity: 0.5;
transition: opacity $duration-base;
&.slot-glow-on {
opacity: 1;
animation: glow-pulse 0.8s ease-in-out infinite;
}
}
@keyframes glow-pulse {
0%, 100% { transform: translateX(-50%) scale(1); }
50% { transform: translateX(-50%) scale(1.08); }
}
.slot-window {
position: relative;
width: 520rpx;
height: 360rpx;
border-radius: $radius-xl;
background: $bg-card;
border: 2rpx solid var(--border-subtle, rgba(255,255,255,0.10));
box-shadow: $shadow-lg;
overflow: hidden;
}
.slot-strip {
position: absolute;
left: 0; right: 0;
top: 0;
width: 100%;
will-change: transform;
}
.slot-item {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-sm;
height: 120rpx;
border-bottom: 1rpx solid var(--border-micro, rgba(255,255,255,0.05));
transition: background $duration-base;
&.slot-item-active {
background: rgba(232,168,56,0.10);
}
}
.slot-item-emoji-box {
width: 52rpx;
height: 52rpx;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
flex-shrink: 0;
}
.slot-item-emoji { font-size: 40rpx; line-height: 1; }
.slot-item-label { font-size: $fs-base; font-weight: $fw-bold; color: $text-primary; letter-spacing: 2rpx; }
.slot-mask-top, .slot-mask-bottom {
position: absolute;
left: 0; right: 0;
height: 120rpx;
pointer-events: none;
z-index: 5;
}
.slot-mask-top {
top: 0;
background: linear-gradient(to bottom, var(--bg-card, #1A1A28) 20%, transparent);
opacity: 0.85;
}
.slot-mask-bottom {
bottom: 0;
background: linear-gradient(to top, var(--bg-card, #1A1A28) 20%, transparent);
opacity: 0.85;
}
.slot-indicator {
position: absolute;
top: 50%;
left: 0; right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
padding: 0 $sp-sm;
pointer-events: none;
z-index: 10;
}
.slot-ind-arrow {
font-size: 28rpx;
color: var(--amber, #E8A838);
text-shadow: 0 0 12rpx rgba(232,168,56,0.6);
}
.slot-spin-btn {
margin-top: $sp-xl;
min-width: 360rpx;
height: 96rpx;
}
/* === 返回大厅按钮 === */
.back-hub-btn {
position: fixed;
bottom: calc(env(safe-area-inset-bottom) + 48rpx);
left: 50%;
transform: translateX(-50%);
padding: $sp-md $sp-2xl;
border-radius: $radius-full;
background: $bg-elevated;
border: 1rpx solid var(--border-subtle, rgba(255,255,255,0.10));
box-shadow: $shadow-md;
z-index: 60;
transition: transform $duration-fast $ease-out;
&:active { transform: translateX(-50%) scale(0.92); }
text { font-size: $fs-sm; font-weight: $fw-medium; color: $text-secondary; }
}
</style>