refactor(metronome): 整体视觉重设计 - 现代极简风

脚印拟真化:
- 颜色 鲜绿 → 浅白(rgba 241,245,249,0.92),降低饱和不再卡通
- 形状更紧凑:脚趾紧贴脚掌、大小递减弧形排布(22→18→16→14→12rpx)
- 不规则 border-radius 模拟真实脚印轮廓
- 移除多余渐变和厚重 box-shadow

布局重组(卡片化):
- 走路区域 + 配速 badge + 状态提示  → walker-card 统一卡片
- BPM 调节 + 拍号选择            → control-card 统一卡片
- 卡片背景 rgba(255,255,255,0.035) + 1rpx 描边,有 backdrop-blur
- 移除独立 hint 区,合并到 walker-card 顶部

控件精修:
- BPM 步进按钮 高度 120→96rpx,字号 44→38rpx,圆角更小
- 拍号选择器改为 iOS 风药丸式 segmented control,放在右侧
- 主圆 460→420rpx,BPM 字号 200→180rpx,腾出空间给下方
- "速度"标签 → "BPM"(更专业)

配速 badge 智能变色:
- <100 BPM  绿(健走)
- 100-129  黄(快走)
- 130-169  橙(慢跑)
- ≥170     红(快跑)

设计 token 抽取:
- 在 SCSS 顶部定义 $bg-grad / $card-bg / $text-* / $brand 等变量
- 圆角统一:卡片 28rpx, 按钮 20rpx, 药丸 999rpx
- 间距统一:卡片间距 28rpx, 控件间隔 14rpx

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-25 12:18:29 +08:00
co-authored by Cursor
parent 69da8066a6
commit 9b6d9ce007
+354 -267
View File
@@ -1,15 +1,13 @@
<template> <template>
<view class="metronome-page" :style="rootStyle"> <view class="page" :style="rootStyle">
<view class="pulse-stage" @click="togglePlay"> <!-- ===== 主舞台:节拍器圆 ===== -->
<view class="pulse-ring" :class="{ playing: isPlaying }" /> <view class="stage" @click="togglePlay">
<view class="pulse-ring pulse-ring-2" :class="{ playing: isPlaying }" /> <view class="ring r1" :class="{ playing: isPlaying }" />
<view <view class="ring r2" :class="{ playing: isPlaying }" />
class="pulse-core" <view class="core" :class="{ playing: isPlaying, accent: isAccent }">
:class="{ playing: isPlaying, accent: isAccent }"
>
<text class="bpm-num">{{ bpm }}</text> <text class="bpm-num">{{ bpm }}</text>
<text class="bpm-label">速度</text> <text class="bpm-label">BPM</text>
<view class="ctrl-icon"> <view class="ctrl">
<view v-if="isPlaying" class="icon-pause"> <view v-if="isPlaying" class="icon-pause">
<view class="bar" /> <view class="bar" />
<view class="bar" /> <view class="bar" />
@@ -19,66 +17,76 @@
</view> </view>
</view> </view>
<!-- 脚步行走动画:跟随 BPM 同步,1 1 --> <!-- ===== 配速 + 走路动画卡片 ===== -->
<view class="walker" :class="{ playing: isPlaying }"> <view class="walker-card">
<view class="foot foot-left"> <view class="walker-header">
<view class="footprint"> <view class="badge" :class="badgeClass">
<view class="heel" /> <view class="badge-dot" />
<view class="toe toe-1" /> <text>{{ walkerTip }}</text>
<view class="toe toe-2" />
<view class="toe toe-3" />
<view class="toe toe-4" />
<view class="toe toe-5" />
</view> </view>
<view class="shadow" /> <text class="status">{{ statusHint }}</text>
</view> </view>
<view class="foot foot-right">
<view class="footprint">
<view class="heel" />
<view class="toe toe-1" />
<view class="toe toe-2" />
<view class="toe toe-3" />
<view class="toe toe-4" />
<view class="toe toe-5" />
</view>
<view class="shadow" />
</view>
<view class="ground" />
<text class="walker-tip">{{ walkerTip }}</text>
</view>
<view class="bpm-control"> <view class="walker" :class="{ playing: isPlaying }">
<view class="step-btn big" @click="adjustBpm(-10)"> <view class="foot foot-left">
<text class="step-num">-10</text> <view class="footprint">
</view> <view class="heel" />
<view class="step-btn small" @click="adjustBpm(-1)"> <view class="toe toe-1" />
<text class="step-num">-1</text> <view class="toe toe-2" />
</view> <view class="toe toe-3" />
<view class="step-btn small" @click="adjustBpm(1)"> <view class="toe toe-4" />
<text class="step-num">+1</text> <view class="toe toe-5" />
</view> </view>
<view class="step-btn big" @click="adjustBpm(10)"> <view class="shadow" />
<text class="step-num">+10</text> </view>
<view class="foot foot-right">
<view class="footprint">
<view class="heel" />
<view class="toe toe-1" />
<view class="toe toe-2" />
<view class="toe toe-3" />
<view class="toe toe-4" />
<view class="toe toe-5" />
</view>
<view class="shadow" />
</view>
<view class="ground" />
</view> </view>
</view> </view>
<view class="meter-section"> <!-- ===== 控制卡片:BPM + 拍号 ===== -->
<text class="section-label">每组拍数</text> <view class="control-card">
<view class="meter-options"> <view class="bpm-row">
<view <view class="step-btn" @click="adjustBpm(-10)">
v-for="m in METER_OPTIONS" <text class="step-num">10</text>
:key="m.value" </view>
class="meter-btn" <view class="step-btn" @click="adjustBpm(-1)">
:class="{ active: accentEvery === m.value }" <text class="step-num">1</text>
@click="onMeterChange(m.value)" </view>
> <view class="step-btn" @click="adjustBpm(1)">
<text class="meter-label">{{ m.label }}</text> <text class="step-num">+1</text>
</view>
<view class="step-btn" @click="adjustBpm(10)">
<text class="step-num">+10</text>
</view> </view>
</view> </view>
</view>
<view class="hint"> <view class="divider" />
<text>{{ statusHint }}</text>
<view class="meter-row">
<text class="meter-label">每组拍数</text>
<view class="meter-options">
<view
v-for="m in METER_OPTIONS"
:key="m.value"
class="meter-btn"
:class="{ active: accentEvery === m.value }"
@click="onMeterChange(m.value)"
>
<text>{{ m.label }}</text>
</view>
</view>
</view>
</view> </view>
</view> </view>
</template> </template>
@@ -89,9 +97,9 @@ import { onShow, onHide } from '@dcloudio/uni-app'
import { useMetronome } from '@/hooks/useMetronome' import { useMetronome } from '@/hooks/useMetronome'
const METER_OPTIONS = [ const METER_OPTIONS = [
{ label: '2', value: 2 }, { label: '2', value: 2 },
{ label: '3', value: 3 }, { label: '3', value: 3 },
{ label: '4', value: 4 }, { label: '4', value: 4 },
] ]
const metronome = useMetronome({ const metronome = useMetronome({
@@ -142,16 +150,22 @@ const statusHint = computed(() => {
const cur = ((beatIndex.value - 1) % accentEvery.value) + 1 const cur = ((beatIndex.value - 1) % accentEvery.value) + 1
return `${cur} 拍 / 共 ${accentEvery.value}` return `${cur} 拍 / 共 ${accentEvery.value}`
} }
return '轻点圆圈开始' return '轻点中央圆点开始'
}) })
const walkerTip = computed(() => { const walkerTip = computed(() => {
if (!isPlaying.value) return '开始后跟节奏踏步' if (bpm.value < 70) return '慢走 · 热身'
if (bpm.value < 70) return '走 · 适合热身' if (bpm.value < 100) return '走 · 日常'
if (bpm.value < 100) return '走 · 日常配速' if (bpm.value < 130) return '走 · 速'
if (bpm.value < 130) return '快走 · 心率提升' if (bpm.value < 170) return '慢跑 · 有氧'
if (bpm.value < 170) return '跑 · 进入有氧' return '跑 · 高强'
return '快跑 · 高强度' })
const badgeClass = computed(() => {
if (bpm.value < 100) return 'badge-mild'
if (bpm.value < 130) return 'badge-mid'
if (bpm.value < 170) return 'badge-strong'
return 'badge-hot'
}) })
onShow(() => { onShow(() => {
@@ -171,52 +185,73 @@ onUnmounted(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.metronome-page { /* ============================================================
* 设计 token
* ============================================================ */
$bg-grad: linear-gradient(180deg, #0a0f1c 0%, #141a2a 100%);
$card-bg: rgba(255, 255, 255, 0.035);
$card-border: rgba(255, 255, 255, 0.06);
$text-1: #f1f5f9;
$text-2: #cbd5e1;
$text-3: #64748b;
$brand: #22c55e;
$brand-deep: #15803d;
$accent: #f59e0b;
$accent-deep: #b45309;
$radius-card: 28rpx;
$radius-btn: 20rpx;
/* ============================================================
* 页面容器
* ============================================================ */
.page {
min-height: 100vh; min-height: 100vh;
padding: 20rpx 32rpx 80rpx; padding: 30rpx 28rpx 60rpx;
background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%); background: $bg-grad;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
color: #f1f5f9; gap: 28rpx;
color: $text-1;
} }
/* ===== 中心可点击圆 ===== */ /* ============================================================
.pulse-stage { * 节拍器主舞台
* ============================================================ */
.stage {
position: relative; position: relative;
width: 600rpx; width: 540rpx;
height: 600rpx; height: 540rpx;
margin: 40rpx 0 50rpx; margin-top: 20rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&:active .pulse-core { &:active .core {
transform: scale(0.97); transform: scale(0.97);
} }
} }
.pulse-ring { .ring {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 50%; border-radius: 50%;
border: 4rpx solid rgba(34, 197, 94, 0.18); border: 3rpx solid rgba(34, 197, 94, 0.14);
pointer-events: none; pointer-events: none;
&.playing { &.playing {
animation: ring-pulse var(--beat-duration, 750ms) ease-out infinite; animation: ring-pulse var(--beat-duration, 750ms) ease-out infinite;
} }
&.r2.playing {
&.pulse-ring-2 { animation-delay: calc(var(--beat-duration, 750ms) * -0.5);
animation-delay: calc(var(--beat-duration, 750ms) * -0.4);
} }
} }
@keyframes ring-pulse { @keyframes ring-pulse {
0% { 0% {
transform: scale(0.92); transform: scale(0.94);
opacity: 0.9; opacity: 0.85;
border-color: rgba(34, 197, 94, 0.55); border-color: rgba(34, 197, 94, 0.45);
} }
100% { 100% {
transform: scale(1.18); transform: scale(1.18);
@@ -225,83 +260,59 @@ onUnmounted(() => {
} }
} }
.pulse-core { .core {
position: relative; position: relative;
width: 460rpx; width: 420rpx;
height: 460rpx; height: 420rpx;
border-radius: 50%; border-radius: 50%;
background: linear-gradient(140deg, #22c55e 0%, #15803d 100%); background: linear-gradient(140deg, $brand 0%, $brand-deep 100%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: box-shadow:
inset 0 -20rpx 60rpx rgba(0, 0, 0, 0.18), inset 0 -16rpx 50rpx rgba(0, 0, 0, 0.22),
inset 0 20rpx 60rpx rgba(255, 255, 255, 0.12), inset 0 16rpx 50rpx rgba(255, 255, 255, 0.1),
0 24rpx 60rpx rgba(34, 197, 94, 0.45); 0 20rpx 50rpx rgba(34, 197, 94, 0.32);
transition: transform 0.12s, background 0.15s, box-shadow 0.15s; transition: transform 0.12s, background 0.18s, box-shadow 0.18s;
&.playing { &.playing {
animation: core-beat var(--beat-duration, 750ms) ease-in-out infinite; animation: core-beat var(--beat-duration, 750ms) ease-in-out infinite;
} }
&.playing.accent { &.playing.accent {
background: linear-gradient(140deg, #fbbf24 0%, #b45309 100%); background: linear-gradient(140deg, #fbbf24 0%, $accent-deep 100%);
box-shadow: box-shadow:
inset 0 -20rpx 60rpx rgba(0, 0, 0, 0.18), inset 0 -16rpx 50rpx rgba(0, 0, 0, 0.22),
inset 0 20rpx 60rpx rgba(255, 255, 255, 0.18), inset 0 16rpx 50rpx rgba(255, 255, 255, 0.18),
0 24rpx 60rpx rgba(245, 158, 11, 0.5); 0 20rpx 50rpx rgba(245, 158, 11, 0.4);
} }
.bpm-num { .bpm-num {
font-size: 200rpx; font-size: 180rpx;
font-weight: 800; font-weight: 800;
color: #fff; color: #fff;
line-height: 1; line-height: 1;
letter-spacing: -2rpx;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.18); text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.18);
} }
.bpm-label { .bpm-label {
font-size: 28rpx; font-size: 22rpx;
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.65);
letter-spacing: 8rpx; letter-spacing: 6rpx;
margin-top: 8rpx; margin-top: 10rpx;
font-weight: 600;
} }
/* ===== 中央播放/暂停 icon(CSS 几何绘制,跨机型一致) ===== */ .ctrl {
.ctrl-icon { margin-top: 26rpx;
margin-top: 36rpx; height: 76rpx;
width: 110rpx;
height: 90rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.icon-play {
width: 0;
height: 0;
border-left: 70rpx solid #fff;
border-top: 44rpx solid transparent;
border-bottom: 44rpx solid transparent;
margin-left: 16rpx;
filter: drop-shadow(0 2rpx 6rpx rgba(0, 0, 0, 0.2));
}
.icon-pause {
display: flex;
gap: 22rpx;
height: 84rpx;
.bar {
width: 22rpx;
height: 100%;
background: #fff;
border-radius: 6rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.2);
}
}
} }
@keyframes core-beat { @keyframes core-beat {
0%, 0%,
@@ -309,55 +320,132 @@ onUnmounted(() => {
transform: scale(1); transform: scale(1);
} }
50% { 50% {
transform: scale(1.045); transform: scale(1.04);
} }
} }
/* ===== 脚步行走动画 ===== */ /* 播放/暂停 icon (CSS 几何) */
.icon-play {
width: 0;
height: 0;
border-left: 60rpx solid #fff;
border-top: 38rpx solid transparent;
border-bottom: 38rpx solid transparent;
margin-left: 14rpx;
filter: drop-shadow(0 2rpx 6rpx rgba(0, 0, 0, 0.2));
}
.icon-pause {
display: flex;
gap: 18rpx;
height: 70rpx;
.bar {
width: 18rpx;
height: 100%;
background: #fff;
border-radius: 5rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.2);
}
}
/* ============================================================
* 走路动画卡片
* ============================================================ */
.walker-card {
width: 100%;
background: $card-bg;
border: 1rpx solid $card-border;
border-radius: $radius-card;
padding: 24rpx 28rpx 32rpx;
backdrop-filter: blur(20rpx);
}
.walker-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.badge {
display: flex;
align-items: center;
gap: 10rpx;
padding: 8rpx 20rpx;
background: rgba(34, 197, 94, 0.12);
border: 1rpx solid rgba(34, 197, 94, 0.3);
border-radius: 999rpx;
font-size: 24rpx;
color: $brand;
font-weight: 600;
.badge-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: currentColor;
box-shadow: 0 0 8rpx currentColor;
}
&.badge-mild {
background: rgba(34, 197, 94, 0.12);
border-color: rgba(34, 197, 94, 0.3);
color: #4ade80;
}
&.badge-mid {
background: rgba(251, 191, 36, 0.12);
border-color: rgba(251, 191, 36, 0.3);
color: #fbbf24;
}
&.badge-strong {
background: rgba(249, 115, 22, 0.12);
border-color: rgba(249, 115, 22, 0.3);
color: #fb923c;
}
&.badge-hot {
background: rgba(239, 68, 68, 0.14);
border-color: rgba(239, 68, 68, 0.32);
color: #f87171;
}
}
.status {
font-size: 24rpx;
color: $text-3;
letter-spacing: 1rpx;
}
.walker { .walker {
position: relative; position: relative;
width: 100%; width: 100%;
height: 220rpx; height: 200rpx;
margin-bottom: 50rpx;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: flex-end; align-items: flex-end;
overflow: hidden;
} }
.walker .ground { .walker .ground {
position: absolute; position: absolute;
bottom: 36rpx; bottom: 18rpx;
left: 50%; left: 10%;
transform: translateX(-50%); right: 10%;
width: 480rpx; height: 1rpx;
height: 2rpx;
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
transparent, transparent 0%,
rgba(34, 197, 94, 0.45) 30%, rgba(255, 255, 255, 0.18) 30%,
rgba(34, 197, 94, 0.45) 70%, rgba(255, 255, 255, 0.18) 70%,
transparent transparent 100%
); );
} }
.walker .walker-tip { /* ============================================================
position: absolute; * 拟真脚印(单色,内敛)
top: 12rpx; * ============================================================ */
left: 50%;
transform: translateX(-50%);
font-size: 28rpx;
color: #cbd5e1;
letter-spacing: 2rpx;
padding: 6rpx 24rpx;
background: rgba(255, 255, 255, 0.06);
border-radius: 24rpx;
}
.foot { .foot {
position: absolute; position: absolute;
bottom: 36rpx; bottom: 18rpx;
width: 110rpx; width: 100rpx;
height: 130rpx; height: 130rpx;
.footprint { .footprint {
@@ -367,86 +455,84 @@ onUnmounted(() => {
transform-origin: bottom center; transform-origin: bottom center;
} }
/* 脚跟+脚掌:一颗椭圆形 */ /* 脚跟+脚掌一体椭圆,不规则更像真实脚印 */
.heel { .heel {
position: absolute; position: absolute;
bottom: 4rpx; bottom: 0;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
width: 78rpx; width: 72rpx;
height: 90rpx; height: 86rpx;
background: linear-gradient(135deg, #22c55e, #16a34a); background: rgba(241, 245, 249, 0.92);
border-radius: 50% 50% 46% 46% / 56% 56% 44% 44%; border-radius: 48% 48% 50% 50% / 56% 56% 44% 44%;
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.15);
} }
/* 5 个脚趾(右脚视角:从左到右大→小) */ /* 5 个脚趾 - 紧贴脚掌,大小递减,呈弧形排布 */
.toe { .toe {
position: absolute; position: absolute;
background: #16a34a; background: rgba(241, 245, 249, 0.92);
border-radius: 50%; border-radius: 50%;
} }
.toe-1 { .toe-1 {
/* 大脚趾,内侧最大 */
width: 22rpx; width: 22rpx;
height: 24rpx; height: 22rpx;
bottom: 96rpx; bottom: 86rpx;
left: 18rpx; left: 16rpx;
} }
.toe-2 { .toe-2 {
width: 18rpx; width: 18rpx;
height: 20rpx; height: 18rpx;
bottom: 102rpx; bottom: 92rpx;
left: 40rpx; left: 36rpx;
} }
.toe-3 { .toe-3 {
width: 16rpx; width: 16rpx;
height: 18rpx; height: 16rpx;
bottom: 100rpx; bottom: 92rpx;
left: 58rpx; left: 52rpx;
} }
.toe-4 { .toe-4 {
width: 14rpx; width: 14rpx;
height: 16rpx; height: 14rpx;
bottom: 94rpx; bottom: 88rpx;
left: 72rpx; left: 66rpx;
} }
.toe-5 { .toe-5 {
/* 小脚趾,外侧最小 */
width: 12rpx; width: 12rpx;
height: 14rpx; height: 12rpx;
bottom: 86rpx; bottom: 82rpx;
left: 84rpx; left: 78rpx;
} }
.shadow { .shadow {
position: absolute; position: absolute;
bottom: -8rpx; bottom: -6rpx;
left: 50%; left: 50%;
transform: translateX(-50%) scaleX(1); transform: translateX(-50%);
width: 90rpx; width: 76rpx;
height: 12rpx; height: 8rpx;
background: radial-gradient( background: radial-gradient(
ellipse at center, ellipse at center,
rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.45),
transparent 70% transparent 70%
); );
border-radius: 50%; border-radius: 50%;
} }
} }
/* 左脚:置左,内层 footprint 镜像翻转(大脚趾朝外侧) */
.foot-left { .foot-left {
left: calc(50% - 130rpx); left: calc(50% - 124rpx);
} }
.foot-left .footprint { .foot-left .footprint {
transform: scaleX(-1); transform: scaleX(-1);
} }
/* 右脚:置右 */
.foot-right { .foot-right {
left: calc(50% + 20rpx); left: calc(50% + 24rpx);
} }
/* 跟节拍同步:1 拍 1 步,左右各占半周期(0.5 偏移) */ /* 跟节拍同步:1 拍 1 步,左右各占半周期 */
.walker.playing { .walker.playing {
.foot-left .footprint { .foot-left .footprint {
animation: foot-step-left var(--step-duration, 750ms) ease-in-out infinite; animation: foot-step-left var(--step-duration, 750ms) ease-in-out infinite;
@@ -470,7 +556,7 @@ onUnmounted(() => {
transform: translateY(0) rotate(0deg); transform: translateY(0) rotate(0deg);
} }
25% { 25% {
transform: translateY(-44rpx) rotate(8deg); transform: translateY(-40rpx) rotate(8deg);
} }
50% { 50% {
transform: translateY(-2rpx) rotate(0deg); transform: translateY(-2rpx) rotate(0deg);
@@ -479,15 +565,13 @@ onUnmounted(() => {
transform: translateY(0) rotate(0deg); transform: translateY(0) rotate(0deg);
} }
} }
/* 左脚保留 scaleX(-1) 镜像 */
@keyframes foot-step-left { @keyframes foot-step-left {
0%, 0%,
100% { 100% {
transform: scaleX(-1) translateY(0) rotate(0deg); transform: scaleX(-1) translateY(0) rotate(0deg);
} }
25% { 25% {
transform: scaleX(-1) translateY(-44rpx) rotate(8deg); transform: scaleX(-1) translateY(-40rpx) rotate(8deg);
} }
50% { 50% {
transform: scaleX(-1) translateY(-2rpx) rotate(0deg); transform: scaleX(-1) translateY(-2rpx) rotate(0deg);
@@ -496,7 +580,6 @@ onUnmounted(() => {
transform: scaleX(-1) translateY(0) rotate(0deg); transform: scaleX(-1) translateY(0) rotate(0deg);
} }
} }
@keyframes shadow-pulse { @keyframes shadow-pulse {
0%, 0%,
100% { 100% {
@@ -504,105 +587,109 @@ onUnmounted(() => {
transform: translateX(-50%) scaleX(1); transform: translateX(-50%) scaleX(1);
} }
25% { 25% {
/* 抬脚最高时阴影最淡最小 */ opacity: 0.18;
opacity: 0.15; transform: translateX(-50%) scaleX(0.5);
transform: translateX(-50%) scaleX(0.55);
} }
50% { 50% {
/* 落地瞬间阴影最浓 */
opacity: 0.7; opacity: 0.7;
transform: translateX(-50%) scaleX(1.1); transform: translateX(-50%) scaleX(1.08);
} }
} }
/* ===== BPM 步进按钮 ===== */ /* ============================================================
.bpm-control { * 控制卡片(BPM + 拍号)
* ============================================================ */
.control-card {
width: 100%; width: 100%;
background: $card-bg;
border: 1rpx solid $card-border;
border-radius: $radius-card;
padding: 28rpx;
backdrop-filter: blur(20rpx);
}
.bpm-row {
display: flex; display: flex;
justify-content: center; gap: 14rpx;
gap: 20rpx;
margin-bottom: 60rpx;
} }
.step-btn { .step-btn {
flex: 1; flex: 1;
height: 120rpx; height: 96rpx;
border-radius: 24rpx; border-radius: $radius-btn;
background: rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.05);
border: 2rpx solid rgba(255, 255, 255, 0.12); border: 1rpx solid rgba(255, 255, 255, 0.08);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.15s;
&:active { &:active {
background: rgba(34, 197, 94, 0.25); background: rgba(34, 197, 94, 0.18);
border-color: #22c55e; border-color: rgba(34, 197, 94, 0.5);
transform: scale(0.96);
} }
.step-num { .step-num {
font-size: 44rpx; font-size: 38rpx;
color: #f1f5f9; color: $text-1;
font-weight: 700; font-weight: 700;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} letter-spacing: -1rpx;
&.big .step-num {
font-size: 48rpx;
} }
} }
/* ===== 拍号 ===== */ .divider {
.meter-section { height: 1rpx;
width: 100%; background: rgba(255, 255, 255, 0.06);
margin: 24rpx 0;
} }
.section-label { .meter-row {
display: block; display: flex;
font-size: 28rpx; align-items: center;
color: #94a3b8; justify-content: space-between;
margin-bottom: 20rpx; gap: 20rpx;
text-align: center; }
.meter-label {
font-size: 26rpx;
color: $text-3;
letter-spacing: 1rpx;
} }
.meter-options { .meter-options {
display: flex; display: flex;
gap: 20rpx; gap: 10rpx;
background: rgba(255, 255, 255, 0.04);
padding: 6rpx;
border-radius: 999rpx;
border: 1rpx solid rgba(255, 255, 255, 0.05);
}
.meter-btn {
min-width: 84rpx;
height: 60rpx;
padding: 0 20rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center; justify-content: center;
transition: all 0.18s;
.meter-btn { text {
flex: 1; font-size: 30rpx;
max-width: 180rpx; color: $text-2;
height: 100rpx; font-weight: 600;
background: rgba(255, 255, 255, 0.06); }
border: 2rpx solid rgba(255, 255, 255, 0.1);
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
.meter-label { &.active {
font-size: 36rpx; background: $brand;
color: #cbd5e1; box-shadow: 0 4rpx 12rpx rgba(34, 197, 94, 0.4);
}
&.active { text {
background: #22c55e; color: #fff;
border-color: #22c55e;
.meter-label {
color: #fff;
font-weight: 600;
}
} }
} }
} }
/* ===== 状态提示 ===== */
.hint {
text-align: center;
font-size: 26rpx;
color: #64748b;
margin-top: 32rpx;
height: 40rpx;
}
</style> </style>