feat(stargalaxy): add ScatteredRanks with 9 ring items + 36s orbit animation
This commit is contained in:
parent
2bd5733ca0
commit
04609adb5e
164
frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue
Normal file
164
frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<view class="scattered-ranks">
|
||||||
|
<!-- 椭圆轨道装饰(虚线) -->
|
||||||
|
<svg class="orbit-svg" viewBox="0 0 375 170" preserveAspectRatio="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="sg-orbit-grad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="rgba(255,255,255,0.15)" />
|
||||||
|
<stop offset="50%" stop-color="rgba(255,255,255,0.4)" />
|
||||||
|
<stop offset="100%" stop-color="rgba(255,250,189,0.85)" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="sg-center-glow" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stop-color="rgba(255,250,189,0.5)" />
|
||||||
|
<stop offset="100%" stop-color="transparent" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<ellipse cx="187" cy="55" rx="80" ry="35" fill="url(#sg-center-glow)" />
|
||||||
|
<ellipse cx="187" cy="55" rx="130" ry="55" fill="none" stroke="url(#sg-orbit-grad)" stroke-width="1.5" stroke-dasharray="3,3" />
|
||||||
|
<path d="M 57,55 A 130,55 0 0,0 317,55" stroke="rgba(255,250,189,0.85)" stroke-width="2.5" fill="none" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- 9 个散落 item -->
|
||||||
|
<view
|
||||||
|
v-for="(p, i) in positions"
|
||||||
|
:key="p.rank"
|
||||||
|
class="ring-item"
|
||||||
|
:class="'r' + (p.rank - 4)"
|
||||||
|
:style="ringItemStyle(p)"
|
||||||
|
@click="handleClick(items[i])"
|
||||||
|
>
|
||||||
|
<view class="top-label">{{ formatLabel(p.rank) }}</view>
|
||||||
|
<image
|
||||||
|
class="cover-image"
|
||||||
|
:src="(items[i]?.cover_url) || (items[i]?.cover_image) || ''"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ORBIT_KEYFRAMES, RING_DELAYS } from './config.js'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
items: { type: Array, required: true }, // length 9
|
||||||
|
positions: { type: Array, required: true }, // from generateRingPositions()
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['cardClick'])
|
||||||
|
|
||||||
|
// 静态 base 位置:slot 0 中心 (187, 565),item 46×72,top-left = (164, 530)
|
||||||
|
const BASE_X = 164
|
||||||
|
const BASE_Y = 530
|
||||||
|
|
||||||
|
function ringItemStyle(p) {
|
||||||
|
return {
|
||||||
|
left: BASE_X + 'rpx',
|
||||||
|
top: BASE_Y + 'rpx',
|
||||||
|
zIndex: p.zIndex,
|
||||||
|
transform: `scale(${p.scale})`,
|
||||||
|
animationDelay: RING_DELAYS[p.rank - 4] + 's',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLabel(rank) {
|
||||||
|
return 'TOP ' + rank
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(item) {
|
||||||
|
if (item) emit('cardClick', item)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.scattered-ranks {
|
||||||
|
position: relative;
|
||||||
|
width: 750rpx;
|
||||||
|
height: 720rpx;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orbit-svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 390rpx;
|
||||||
|
left: 0;
|
||||||
|
width: 750rpx;
|
||||||
|
height: 340rpx;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring-item {
|
||||||
|
position: absolute;
|
||||||
|
width: 46rpx;
|
||||||
|
height: 72rpx; /* 14 label + 2 gap + 56 cover */
|
||||||
|
transform-origin: center;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: orbit 36s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-label {
|
||||||
|
width: 46rpx;
|
||||||
|
height: 14rpx;
|
||||||
|
background: radial-gradient(ellipse, #C8E6FF, #fff 50%, #4D9AF8);
|
||||||
|
border-radius: 7rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 7rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #FFFABD;
|
||||||
|
text-shadow: -1rpx 1rpx 2rpx rgba(206, 9, 9, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOP 4 label 是金渐变(最显眼) */
|
||||||
|
.r0 .top-label {
|
||||||
|
background: radial-gradient(ellipse, #FFFFFF, #FFFABD 30%, #4D9AF8 100%);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 250, 189, 0.55);
|
||||||
|
font-size: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-image {
|
||||||
|
margin-top: 2rpx;
|
||||||
|
width: 46rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 5rpx;
|
||||||
|
box-shadow: 3rpx 3rpx 6rpx rgba(198, 13, 13, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.r0 .cover-image {
|
||||||
|
box-shadow: 0 12rpx 28rpx rgba(255, 32, 36, 0.5), 0 0 24rpx rgba(255, 250, 189, 0.55);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 关键帧:放在非 scoped 块中,让所有 ring-item 共享 */
|
||||||
|
@keyframes orbit {
|
||||||
|
0% { transform: translate(0,0) scale(1.15); }
|
||||||
|
11.11% { transform: translate(84px,-13px) scale(1.05); }
|
||||||
|
22.22% { transform: translate(157px,-43px) scale(0.95); }
|
||||||
|
33.33% { transform: translate(113px,-83px) scale(0.85); }
|
||||||
|
44.44% { transform: translate(45px,-107px) scale(0.75); }
|
||||||
|
55.55% { transform: translate(-45px,-107px) scale(0.75); }
|
||||||
|
66.66% { transform: translate(-113px,-83px) scale(0.85); }
|
||||||
|
77.77% { transform: translate(-156px,-43px) scale(0.95); }
|
||||||
|
88.88% { transform: translate(-84px,-13px) scale(1.05); }
|
||||||
|
100% { transform: translate(0,0) scale(1.15); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes crownPulse {
|
||||||
|
0%, 100% { transform: translateX(-50%) scale(1); }
|
||||||
|
50% { transform: translateX(-50%) scale(1.15); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可访问性:减少动画 */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ring-item,
|
||||||
|
.crown {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user