.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 (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; width: 200rpx; } .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: 12rpx; } .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: 12rpx; } /* 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; line-height: 1.3; } .podium-name--first { font-size: 28rpx; font-weight: 600; } /* 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: 0; right: 0; bottom: 10rpx; text-align: center; font-size: 22rpx; font-weight: 700; color: var(--primary); line-height: 1; } .podium-dur { font-size: 24rpx; font-weight: 600; color: var(--text-secondary); margin-top: 2rpx; 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(var(--primary-rgb), 0.18), rgba(var(--primary-rgb), 0.04)); } .podium-base--second { height: 64rpx; } .podium-base--third { height: 48rpx; } /* 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-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: 200rpx; height: 200rpx; margin-bottom: 40rpx; } .empty-illus-body { position: absolute; bottom: 30rpx; left: 50%; transform: translateX(-50%); width: 160rpx; height: 30rpx; background: linear-gradient(90deg, var(--primary), var(--primary-light)); border-radius: 16rpx; } .empty-illus-arm { position: absolute; bottom: 36rpx; width: 20rpx; height: 60rpx; background: var(--primary); border-radius: 10rpx; } .empty-illus-arm--l { left: 30rpx; transform: rotate(30deg); } .empty-illus-arm--r { right: 30rpx; transform: rotate(-30deg); } .empty-illus-bubble { position: absolute; top: 0; right: 0; background: var(--primary-bg); color: var(--primary); font-size: 22rpx; font-weight: 600; padding: 8rpx 16rpx; border-radius: 16rpx; line-height: 1; } .empty-text { font-size: 30rpx; color: var(--text); margin-bottom: 12rpx; } .empty-sub { font-size: 26rpx; color: var(--text-secondary); }