.container { padding: 0; padding-bottom: calc(134rpx + env(safe-area-inset-bottom)); min-height: 100vh; box-sizing: border-box; } /* Period tabs: pill segmented control, same language as settings .segmented */ .period-seg-wrap { padding: 20rpx 24rpx 8rpx; position: sticky; top: 0; z-index: 10; } .period-seg { display: flex; background: var(--bg-soft); border-radius: 999rpx; padding: 6rpx; } .period-seg-item { flex: 1; text-align: center; padding: 16rpx 0; font-size: 28rpx; color: var(--text-secondary); border-radius: 999rpx; transition: background 0.25s ease, color 0.25s ease, box-shadow 0.25s ease; } .period-seg-item.active { background: var(--card-bg); color: var(--primary); font-weight: 600; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.10); } /* Status */ .status-wrap { display: flex; flex-direction: column; align-items: center; padding-top: 120rpx; } .status-text { font-size: 28rpx; color: var(--text-secondary); } .empty-icon { width: 120rpx; height: 120rpx; opacity: 0.3; margin-bottom: 24rpx; } .empty-text { font-size: 30rpx; color: var(--text-secondary); margin-bottom: 8rpx; } .empty-sub { font-size: 26rpx; color: var(--text-secondary); opacity: 0.6; } /* 耐力榜领奖台顶部文案:哲理注脚,主色呼应 + 金句卡片质感 */ .podium-slogan { position: relative; margin: 8rpx 32rpx 4rpx; padding: 22rpx 28rpx 24rpx; text-align: center; font-size: 24rpx; line-height: 1.65; color: var(--text); font-weight: 500; font-style: italic; background: linear-gradient(135deg, rgba(var(--primary-rgb), 0.10), rgba(var(--primary-rgb), 0.03)); border: 1rpx solid rgba(var(--primary-rgb), 0.16); border-radius: 18rpx; box-shadow: 0 4rpx 14rpx rgba(var(--primary-rgb), 0.10); } /* ---- podium (top 3) ---- */ .podium { display: flex; align-items: flex-end; justify-content: center; padding: 32rpx 24rpx 0; gap: 20rpx; } .podium-slot { display: flex; flex-direction: column; align-items: center; flex: 1; } .podium-crown { width: 56rpx; height: 56rpx; margin-bottom: -8rpx; z-index: 1; animation: float 2.5s ease-in-out infinite; } .podium-avatar-wrap { position: relative; } /* Circular avatar: a rounded clips the inside it. border-radius on the itself is NOT reliably clipped by some WeChat base libraries, so we wrap it and clip on the parent instead. */ .avatar { position: relative; border-radius: 50%; overflow: hidden; background: var(--bg-soft); flex-shrink: 0; box-sizing: border-box; } .avatar__img { width: 100%; height: 100%; display: block; /* Some WeChat base libraries don't clip a remote 's native layer with the parent view's overflow:hidden (they DO clip the placeholder SVG, which is why daily looked fine but monthly/yearly real photos showed square). Rounding the itself is the cross-version-safe fix and also covers the wrapper. */ border-radius: 50%; } .avatar--md { width: 96rpx; height: 96rpx; padding: 20rpx; opacity: 0.7; border: 4rpx solid var(--card-bg); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); margin-bottom: 0; } .avatar--lg { width: 128rpx; height: 128rpx; padding: 26rpx; border: 4rpx solid rgba(var(--primary-rgb), 0.4); box-shadow: 0 6rpx 20rpx rgba(var(--primary-rgb), 0.25); opacity: 0.85; margin-bottom: 0; } /* A real photo fills the whole circle (no inner ring), mirroring the settings-page avatar. The placeholder keeps the padding ring above. */ .avatar.has-avatar { padding: 0; opacity: 1; } .podium-medal { position: absolute; bottom: -8rpx; left: 50%; transform: translateX(-50%); width: 40rpx; height: 40rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 24rpx; font-weight: 700; color: #FFFFFF; box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.15); } .podium-medal--gold { background: linear-gradient(135deg, #FFC53D, #FA8C16); } .podium-medal--silver { background: linear-gradient(135deg, #C9CDD4, #8C8C8C); } .podium-medal--bronze { background: linear-gradient(135deg, #E8A06A, #D46B08); } .podium-name { font-size: 26rpx; font-weight: 500; color: var(--text); max-width: 180rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; /* 关键对齐修复:行盒高度设为与勋章等高(30rpx)。这样 flex 的 align-items:center 对齐的是"昵称盒中心"与"勋章盒中心"两个等高盒,而非 line-height:1 下偏矮、 且中文 glyph 视觉重心偏下的行盒,图标不会再飘在昵称上方。 */ line-height: 30rpx; } .podium-name--first { font-size: 28rpx; font-weight: 600; line-height: 30rpx; /* 与首名勋章统一为 30rpx,前三名勋章等大且和昵称盒等高对齐 */ } /* 领奖台名字 + 勋章横排(居中)。名字保留自身 max-width/省略,勋章固定不挤压 */ .podium-name-row { display: flex; align-items: center; justify-content: center; gap: 6rpx; max-width: 220rpx; /* 原间距 margin-top:30rpx 从 .podium-name 移到此处:避免在 flex 子项上用 cross 轴 margin 干扰昵称与勋章的居中(那是之前图标"偏高"的隐藏元凶)。 */ margin-top: 30rpx; } .podium-badge { width: 30rpx; height: 30rpx; flex-shrink: 0; /* 不再用 margin-top 盲推:昵称行盒已设为与勋章等高(30rpx),flex align-items:center 直接对齐两盒中心,图标与昵称视觉对齐。若个别冠类勋章因图形重心仍有 ±1~2rpx 偏差,再单独给该图加 transform: translateY(2rpx) 即可,不要回退到 margin 推法。 */ } .podium-badge--first { width: 30rpx; height: 30rpx; } /* session count printed on the podium base. Pinned a fixed offset above the base's bottom edge so all three sit on the SAME horizontal line despite the stepped base heights (the podium itself is bottom-aligned via flex-end). */ .podium-base__label { position: absolute; left: 4rpx; right: 4rpx; bottom: 6rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; font-size: 22rpx; font-weight: 700; color: var(--text); line-height: 1.15; } /* 耐力榜副标签(单次日期)在领奖台底座上拆成「日期 + 时间」两行。 缩小字号 + 抬高最矮的第 3 名底座(48→64rpx),三阶统一为 96/80/64, 让两行文字在各级底座内都垂直居中、不贴顶(含微信字体缩放余量)。 */ .podium-base__date { font-size: 18rpx; line-height: 1.15; } .podium-base__time { font-size: 16rpx; line-height: 1.1; opacity: 0.85; margin-top: 2rpx; } .podium-dur { font-size: 24rpx; font-weight: 600; color: var(--text-secondary); /* line-height 收紧后行盒缩短约 8rpx,此处补偿保持与名字的视觉间距 */ margin-top: 10rpx; font-variant-numeric: tabular-nums; } .podium-dur--first { font-size: 28rpx; color: var(--primary); } /* podium bases: stepped blocks under each slot */ .podium-base { width: 100%; margin-top: 16rpx; border-radius: 16rpx 16rpx 0 0; background: linear-gradient(180deg, rgba(var(--primary-rgb), 0.10), rgba(var(--primary-rgb), 0.02)); position: relative; } .podium-base--first { height: 96rpx; background: linear-gradient(180deg, rgba(255, 197, 61, 0.55), rgba(250, 140, 22, 0.20)); box-shadow: 0 6rpx 18rpx rgba(250, 140, 22, 0.30), inset 0 2rpx 4rpx rgba(255, 255, 255, 0.45); } .podium-base--second { height: 80rpx; background: linear-gradient(180deg, rgba(201, 205, 212, 0.50), rgba(140, 140, 140, 0.16)); box-shadow: 0 6rpx 18rpx rgba(140, 140, 140, 0.22), inset 0 2rpx 4rpx rgba(255, 255, 255, 0.45); } .podium-base--third { height: 64rpx; background: linear-gradient(180deg, rgba(232, 160, 106, 0.50), rgba(212, 107, 8, 0.16)); box-shadow: 0 6rpx 18rpx rgba(212, 107, 8, 0.22), inset 0 2rpx 4rpx rgba(255, 255, 255, 0.45); } /* 虚拟领奖台:空位占位(名单不足 3 名时,空缺名次渲染成"虚位以待") */ .podium-avatar--empty { opacity: 1; border: 4rpx dashed rgba(var(--primary-rgb), 0.35); box-shadow: none; background: rgba(var(--primary-rgb), 0.04); } .podium-avatar__img--empty { opacity: 0.26; filter: grayscale(1); } .podium-empty-label { font-size: 22rpx; font-weight: 500; color: var(--text-secondary); opacity: 0.5; } .podium-crown--empty { opacity: 0.3; } /* Rank list */ .rank-list { padding: 16rpx 24rpx; } .rank-item { display: flex; align-items: center; background: var(--card-bg); border-radius: 16rpx; padding: 20rpx 24rpx; margin-bottom: 12rpx; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); transition: background-color 0.3s ease, border-color 0.3s ease; } .rank-item.is-me { background: rgba(var(--primary-rgb), 0.12); border: 2rpx solid var(--primary); } /* Rank number */ .rank-num { width: 56rpx; height: 56rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 700; color: var(--text-secondary); border-radius: 50%; background: var(--bg-soft); flex-shrink: 0; margin-right: 16rpx; } .rank-num.gold { background: rgba(250, 140, 22, 0.15); color: #FA8C16; font-size: 36rpx; } .rank-num.silver { background: var(--bg-soft); color: #8C8C8C; font-size: 36rpx; } .rank-num.bronze { background: rgba(212, 107, 8, 0.15); color: #D46B08; font-size: 36rpx; } .avatar--sm { width: 64rpx; height: 64rpx; opacity: 0.6; margin-right: 16rpx; } .rank-info { flex: 1; min-width: 0; } .rank-name { font-size: 28rpx; font-weight: 500; color: var(--text); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* 昵称 + 最新勋章横排:昵称可省略,勋章固定不挤压 */ .rank-name-row { display: flex; align-items: center; gap: 6rpx; min-width: 0; } .rank-name-row .rank-name { flex: 0 1 auto; min-width: 0; } .rank-badge { width: 30rpx; height: 30rpx; flex-shrink: 0; } .rank-sessions { font-size: 22rpx; color: var(--text-secondary); margin-top: 4rpx; } .rank-dur { font-size: 30rpx; font-weight: 700; color: var(--primary); flex-shrink: 0; margin-left: 16rpx; } /* My rank bar (pinned): solid gradient card so it reads as "my highlight" floating above the list, not a translucent outline */ .my-bar { position: fixed; bottom: calc(120rpx + env(safe-area-inset-bottom)); left: 24rpx; right: 24rpx; display: flex; align-items: center; background: linear-gradient(135deg, var(--primary), var(--primary-light)); border-radius: 20rpx; padding: 20rpx 24rpx; z-index: 10; box-shadow: 0 8rpx 24rpx rgba(var(--primary-rgb), 0.35); } .my-bar .rank-num.my-bar__num { background: rgba(255, 255, 255, 0.22); color: #FFFFFF; } .my-bar__avatar { opacity: 1; background: rgba(255, 255, 255, 0.85); border: 2rpx solid rgba(255, 255, 255, 0.6); } .my-bar .rank-name { color: #FFFFFF; font-weight: 600; } .my-bar .rank-sessions { color: rgba(255, 255, 255, 0.8); } .my-bar .rank-dur { color: #FFFFFF; } /* ---- rank item entrance animation ---- */ .rank-item--enter { animation: rankIn 0.4s cubic-bezier(0.2, 0, 0.2, 1) both; } @keyframes rankIn { from { opacity: 0; transform: translateX(-20rpx); } to { opacity: 1; transform: translateX(0); } } /* ---- empty state illustration ---- */ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; /* 垂直居中在"顶部 segmented 控件以下、底部 tabBar 以上"的可用区域。 ~120rpx ≈ seg-wrap 高度;134rpx + safe-area 与 .container 的 padding-bottom 一致,预留自定义 tabBar。 */ min-height: calc(100vh - 120rpx - 134rpx - env(safe-area-inset-bottom)); padding: 32rpx; box-sizing: border-box; } .empty-illus { position: relative; width: 280rpx; height: 280rpx; margin-bottom: 48rpx; display: flex; align-items: center; justify-content: center; } /* 光晕:主题色径向渐变圆,托住奖杯 */ .empty-halo { position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 50%; background: radial-gradient(circle at center, rgba(var(--primary-rgb), 0.20) 0%, rgba(var(--primary-rgb), 0.10) 45%, rgba(var(--primary-rgb), 0) 72%); } /* 奖杯:居中,轻微浮动 + 投影 */ .empty-trophy { position: relative; width: 144rpx; height: 144rpx; z-index: 2; animation: emptyFloat 3s ease-in-out infinite; filter: drop-shadow(0 6rpx 16rpx rgba(var(--primary-rgb), 0.25)); } /* 闪光点:散布光晕周围,错峰闪烁 + 缩放 */ .empty-spark { position: absolute; z-index: 3; opacity: 0.85; } .empty-spark--1 { top: 18rpx; left: 40rpx; width: 32rpx; height: 32rpx; animation: emptySparkle 2.4s ease-in-out infinite; } .empty-spark--2 { top: 48rpx; right: 32rpx; width: 26rpx; height: 26rpx; animation: emptySparkle 2.4s ease-in-out 0.6s infinite; } .empty-spark--3 { bottom: 40rpx; right: 60rpx; width: 22rpx; height: 22rpx; animation: emptySparkle 2.4s ease-in-out 1.2s infinite; } @keyframes emptyFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10rpx); } } @keyframes emptySparkle { 0%, 100% { opacity: 0.2; transform: scale(0.8); } 50% { opacity: 0.95; transform: scale(1.15); } } .empty-text { font-size: 30rpx; font-weight: 600; color: var(--text); margin-bottom: 12rpx; } .empty-sub { font-size: 26rpx; color: var(--text-secondary); } /* 刷新中指示器:下拉刷新时顶部显示(后台静默刷新不显示,保持无感) */ .refresh-indicator { display: flex; align-items: center; justify-content: center; gap: 10rpx; padding: 14rpx 0; animation: refreshFadeIn 0.2s ease; } .refresh-spinner { width: 28rpx; height: 28rpx; border: 3rpx solid rgba(var(--primary-rgb), 0.2); border-top-color: var(--primary); border-radius: 50%; animation: refreshSpin 0.8s linear infinite; } .refresh-text { font-size: 24rpx; color: var(--text-secondary); } @keyframes refreshSpin { to { transform: rotate(360deg); } } @keyframes refreshFadeIn { from { opacity: 0; } to { opacity: 1; } } /* Loading: 旋转加载图标 + 骨架屏容器 */ .leaderboard-skeleton { padding: 40rpx 32rpx 0; } .lb-loading { display: flex; flex-direction: column; align-items: center; gap: 20rpx; padding: 24rpx 0 48rpx; } .lb-loading__spinner { width: 56rpx; height: 56rpx; border: 4rpx solid rgba(var(--primary-rgb), 0.15); border-top-color: var(--primary); border-radius: 50%; animation: lbSpin 0.8s linear infinite; } .lb-loading__text { font-size: 26rpx; color: var(--text-secondary); } @keyframes lbSpin { to { transform: rotate(360deg); } }