The .card-siblings wrapper lives inside <scroll-view v-else>, so when
viewMode flips to 'expanded' the wrapper is destroyed and the
siblingFadeOpacity binding has no element to animate. Up-pull therefore
shows nothing fading in even though the computed correctly returns
0 -> 1.
Add a top-level overlay that mirrors the same four modules
(card-meta-row, info-row, creator-section, chain-section) and binds
its opacity to siblingFadeOpacity. The overlay is rendered only when
viewMode === 'expanded', positioned fixed at the bottom with z-index 60
(above the expanded card at 50, below the header at 100), and uses
pointer-events: none on the wrapper so the up-pull gesture from the
lower viewport can still reach .pull-collapse-surface; interactive
children re-enable pointer-events: auto.
The original .card-siblings inside <scroll-view> is untouched and
still drives the down-pull fade-out.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add AssetCardPullExpand component for locked-state display and wire
up/down gesture symmetry on the asset detail page.
Down-pull at top of list:
- card scales 1.0 -> 1.6 with translateY follow, sibling modules fade
out, hint text '下拉查看大图' -> '松手查看大图' past 80rpx
- on release past threshold, viewMode flips to expanded
Up-pull in expanded state:
- card shrinks 1.6 -> 1.0 with translateY follow, sibling modules fade
back in, hint text '上拉收起' -> '松手收起' past 80rpx
- on release past threshold, viewMode flips back to normal
- the locked scale(1.6) lives on the wrapper so the release animation
stays single-jump, not dual-jump
Implementation:
- AssetCardPullExpand renders the card at scale(1) and exposes isExpanded
for the parent's locked-state toggle (showMask prop gates the legacy
full-screen tap-to-collapse overlay)
- both gesture surfaces use lazy startY capture in onMove to avoid
tap-induced jumps; no CSS transition on the wrapper during active pull
- 0.15s transform transition smooths the release interpolation only
- header bar and report/share buttons stay visible in both states;
v-if/v-else-if/v-else chain (loading -> error -> expanded -> scroll-view)
preserved; existing 8 card class names and parent CSS untouched
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
profile.vue only rendered fan level digit, hiding the fact that
fans level up via accumulated exhibition hours (6h/level, cap 20),
not login/task experience. Users saw "LV X" without "X more
hours to next level", so upgrades felt invisible.
- backend: surface exhibition_hours / next_level_hours on
CurrentIdentityDTO via a new loadLevelProgress helper in
user_controller (read-only, no proto regen, no service
surface change). Full-level responses set next_level_hours =
exhibition_hours so the client can derive progress=1.
- frontend: split level-box into base track + gold fill + text
overlay; width bound to progressRatio with 0.4s transition.
Adds "距 LV X+1 还差 N 小时" hint and MAX indicator for
full-level users.