.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; 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; line-height: 1.3; /* 用 name 的 margin-top 直接控制与上方头像/角标的间距:它是 flex 子元素, margin 必定生效不折叠,不依赖 avatar margin-bottom 的 collapse 行为 (WebView/Skyline 下不一致,曾导致角标压住昵称)。 */ margin-top: 30rpx; } .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(--text); 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(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: 64rpx; 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: 48rpx; 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); } /* 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: 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); } }