topfans/frontend/components/laser/LaserVariantPyramid.vue
2026-06-03 22:19:22 +08:00

224 lines
5.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="laser-variant-pyramid">
<view
v-for="(image, index) in paths"
:key="index"
class="card-item"
:class="{ 'card-selected': selectedIndex === index }"
:style="getCardStyle(index)"
@tap="onSelect(index)"
>
<view class="card-frame">
<image class="card-image" :src="typeof image === 'object' ? image.url : image" mode="aspectFill" />
<view class="card-shimmer" :class="{ 'card-shimmer-fast': selectedIndex === index }" />
<view class="card-rainbow-edge" />
</view>
</view>
</view>
</template>
<script setup>
const props = defineProps({
paths: {
type: Array,
default: () => [],
},
selectedIndex: {
type: Number,
default: -1,
},
/** 礼盒是否已打开(控制卡片错峰入场) */
opened: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['select'])
const onSelect = (index) => {
emit('select', index)
}
// 复用 generation-result 中「金字塔」视觉参数rpx
const getCardStyle = (index) => {
// 顶部1张大z=30 | 中间2张中 | 底部2张小
const allPositions = [
{ left: 40, top: 624, rotate: '-5deg', scale: 0.72, zIndex: 10 }, // 0
{ left: 130, top: 580, rotate: '-8deg', scale: 0.82, zIndex: 20 }, // 1
{ left: 275, top: 500, rotate: '0deg', scale: 1.15, zIndex: 30 }, // 2
{ left: 420, top: 580, rotate: '8deg', scale: 0.82, zIndex: 20 }, // 3
{ left: 510, top: 624, rotate: '5deg', scale: 0.72, zIndex: 10 }, // 4
]
let posIndex
if (props.selectedIndex === -1) {
posIndex = index
} else {
if (index === props.selectedIndex) {
posIndex = 2
} else {
const relativePos = (index - props.selectedIndex + 5) % 5
if (relativePos === 1) posIndex = 3
else if (relativePos === 2) posIndex = 4
else if (relativePos === 3) posIndex = 0
else if (relativePos === 4) posIndex = 1
else posIndex = 2
}
}
const pos = allPositions[posIndex]
// 错峰扫光延迟:每张卡 1.6s 错开(更慢,更克制)
const shimmerDelay = `${(index * 1.6).toFixed(2)}s`
// 入场延迟:礼盒打开后从中间向两侧展开
const enterDelay = props.opened ? `${(Math.abs(index - 2) * 0.08).toFixed(2)}s` : '0s'
if (props.selectedIndex === index) {
return {
left: `${pos.left}rpx`,
top: `${pos.top}rpx`,
transform: `scale(${pos.scale * 1.15})`,
filter: 'brightness(1.05) drop-shadow(0 0 30rpx rgba(160, 200, 220, 0.45))',
zIndex: 100,
'--shimmer-delay': shimmerDelay,
'animation-delay': enterDelay,
}
}
return {
left: `${pos.left}rpx`,
top: `${pos.top}rpx`,
transform: `scale(${pos.scale})`,
zIndex: pos.zIndex,
'--shimmer-delay': shimmerDelay,
'animation-delay': enterDelay,
}
}
</script>
<style scoped>
.laser-variant-pyramid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 20;
}
.card-item {
position: absolute;
width: 200rpx;
height: 260rpx;
transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
left 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
top 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
filter 0.3s ease;
filter: drop-shadow(0 15rpx 40rpx rgba(0, 0, 0, 0.5));
animation: card-fade-in 0.55s ease-out backwards;
}
.card-frame {
width: 100%;
height: 100%;
background: linear-gradient(
135deg,
rgba(180, 220, 255, 0.95) 0%,
rgba(200, 230, 255, 0.95) 50%,
rgba(220, 240, 255, 0.95) 100%
);
border-radius: 20rpx;
overflow: hidden;
border: 5rpx solid rgba(255, 255, 255, 0.9);
padding: 10rpx;
box-sizing: border-box;
position: relative;
}
.card-image {
width: 100%;
height: 100%;
border-radius: 14rpx;
display: block;
}
/* 斜向白光扫过 — 暗示「镭射膜」核心视觉(极克制)*/
.card-shimmer {
position: absolute;
inset: 10rpx;
border-radius: 14rpx;
background: linear-gradient(
115deg,
transparent 32%,
rgba(255, 255, 255, 0.18) 46%,
rgba(255, 255, 255, 0.32) 50%,
rgba(255, 255, 255, 0.18) 54%,
transparent 68%
);
background-size: 220% 220%;
background-position: -120% -120%;
animation: card-shimmer-sweep 8s ease-in-out infinite;
animation-delay: var(--shimmer-delay, 0s);
mix-blend-mode: screen;
pointer-events: none;
}
.card-shimmer-fast {
animation-duration: 3.5s;
background-size: 260% 260%;
}
@keyframes card-shimmer-sweep {
0% {
background-position: -120% -120%;
}
55% {
background-position: -120% -120%;
}
100% {
background-position: 220% 220%;
}
}
/* 边缘彩虹描边 — 营造光栅膜的"边光"质感(极轻)*/
.card-rainbow-edge {
position: absolute;
inset: 10rpx;
border-radius: 14rpx;
pointer-events: none;
box-shadow:
inset 0 0 6rpx rgba(200, 220, 240, 0.22),
inset 0 0 14rpx rgba(220, 230, 240, 0.12);
mix-blend-mode: screen;
}
.card-item.card-selected .card-rainbow-edge {
box-shadow:
inset 0 0 10rpx rgba(200, 230, 250, 0.35),
inset 0 0 22rpx rgba(230, 230, 240, 0.18);
animation: card-rainbow-pulse 4.5s ease-in-out infinite;
}
@keyframes card-rainbow-pulse {
0%, 100% {
filter: hue-rotate(0deg);
}
50% {
filter: hue-rotate(15deg);
}
}
@keyframes card-fade-in {
0% {
opacity: 0;
transform: scale(0.6);
}
100% {
opacity: 1;
transform: scale(var(--enter-scale, 1));
}
}
</style>