530 lines
12 KiB
Vue
530 lines
12 KiB
Vue
<template>
|
||
<div
|
||
class="apply-container flex flex-col"
|
||
id="apply"
|
||
ref="applyRef"
|
||
>
|
||
<div
|
||
class="title animation-element"
|
||
ref="applyTitleRef"
|
||
>
|
||
{{ $t('AwardsPage.howToApply') }}
|
||
</div>
|
||
<div
|
||
class="sub-title animation-element"
|
||
ref="applySubTitleRef"
|
||
>
|
||
{{ $t('AwardsPage.stepByStep') }}
|
||
</div>
|
||
<div
|
||
class="requirments-list flex flex-col"
|
||
ref="reqListRef"
|
||
>
|
||
<div class="top flex">
|
||
<div
|
||
class="item-box animation-element"
|
||
v-for="(item, index) in leftRequirment"
|
||
:key="item.type"
|
||
:ref="el => { if(el) itemRefs[index] = el }"
|
||
:style="{ background: item.background || '#fff' }"
|
||
>
|
||
<div class="item-header flex flex-center">
|
||
<div class="item-title">{{ $t(item.type) }}</div>
|
||
</div>
|
||
<div class="context-container flex flex-center">
|
||
<div
|
||
class="context"
|
||
v-for="el in item.desc"
|
||
>
|
||
{{ $t(el) }}
|
||
</div>
|
||
<div
|
||
class="list"
|
||
v-if="item.listTitle"
|
||
>
|
||
<div class="list-title">{{ $t(item.listTitle) }}</div>
|
||
<ul class="list-items">
|
||
<li
|
||
class="list-item"
|
||
v-for="el in item.list"
|
||
>
|
||
{{ $t(el) }}
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="bottom flex">
|
||
<div class="step-3 flex flex-col animation-element" ref="step3Ref">
|
||
<div class="header">{{ $t('AwardsPage.step3Title') }}</div>
|
||
<div class="content flex">
|
||
<div class="content-left flex flex-col space-between">
|
||
<div class="content-item">
|
||
<div class="item-header flex align-center">
|
||
<div class="point"></div>
|
||
<div>{{ $t('AwardsPage.processVideo') }}</div>
|
||
</div>
|
||
<div class="desc-wrapper flex flex-col space-between">
|
||
<div class="item-desc">
|
||
{{ $t('AwardsPage.processVideoDesc') }}
|
||
</div>
|
||
<ul class="desc-lists">
|
||
<div class="desc-lists-title">
|
||
{{ $t('AwardsPage.videoRequirements') }}
|
||
</div>
|
||
<li>{{ $t('AwardsPage.videoFormat') }}</li>
|
||
<li>{{ $t('AwardsPage.videoResolution') }}</li>
|
||
<li>{{ $t('AwardsPage.videoDuration') }}</li>
|
||
<li>{{ $t('AwardsPage.videoSize') }}</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="content-item">
|
||
<div class="item-header flex align-center">
|
||
<div class="point"></div>
|
||
<div>{{ $t('AwardsPage.fileName') }}</div>
|
||
</div>
|
||
<div class="item-desc indent">
|
||
{{ $t('AwardsPage.fileNameDesc') }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="content-right">
|
||
<div class="content-item flex flex-col">
|
||
<div class="item-header flex align-center">
|
||
<div class="point"></div>
|
||
<div>{{ $t('AwardsPage.designPortfolio') }}</div>
|
||
</div>
|
||
<div
|
||
class="desc-wrapper flex-1 flex flex-col space-between"
|
||
>
|
||
<ul class="desc-lists">
|
||
<div class="desc-lists-title">
|
||
<p>
|
||
{{ $t('AwardsPage.submitPdf') }}
|
||
</p>
|
||
<p>{{ $t('AwardsPage.requiredStructure') }}</p>
|
||
</div>
|
||
<li>{{ $t('AwardsPage.pdfDesignTitle') }}</li>
|
||
<li>{{ $t('AwardsPage.pdfMoodboard') }}</li>
|
||
<li>{{ $t('AwardsPage.pdfConcept') }}</li>
|
||
<div>{{ $t('AwardsPage.pdfConceptDesc') }}</div>
|
||
</ul>
|
||
<ul class="desc-lists">
|
||
<div class="desc-lists-title">
|
||
<p>{{ $t('AwardsPage.pdfRequirements') }}</p>
|
||
</div>
|
||
<li>{{ $t('AwardsPage.pdfMaxPages') }}</li>
|
||
<li>{{ $t('AwardsPage.pdfMaxSize') }}</li>
|
||
<li>
|
||
{{ $t('AwardsPage.pdfLanguage') }}
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="step-4 animation-element" ref="step4Ref">
|
||
<div class="header flex flex-col flex-center">
|
||
<p>{{ $t('AwardsPage.step4Title') }}</p>
|
||
<p class="sub-title">{{ $t('AwardsPage.step4Subtitle') }}</p>
|
||
</div>
|
||
<div class="content">
|
||
<div class="content-item">
|
||
<div class="desc-wrapper flex-1 flex flex-col space-between">
|
||
<ul class="desc-lists">
|
||
<div class="desc-lists-title">
|
||
{{ $t('AwardsPage.step4Desc') }}
|
||
</div>
|
||
<li>{{ $t('AwardsPage.finalistPieces') }}</li>
|
||
<li>
|
||
{{ $t('AwardsPage.finalistBasedOn') }}
|
||
</li>
|
||
<li>
|
||
{{ $t('AwardsPage.finalistShipping') }}
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { gsap } from 'gsap'
|
||
|
||
const { t } = useI18n()
|
||
|
||
const leftRequirment = ref([
|
||
{
|
||
type: 'AwardsPage.step1Title',
|
||
desc: ['AwardsPage.step1Desc']
|
||
},
|
||
{
|
||
type: 'AwardsPage.step2Title',
|
||
desc: ['AwardsPage.step2Desc'],
|
||
listTitle: 'AwardsPage.step2ListTitle',
|
||
list: [
|
||
'AwardsPage.step2List[0]',
|
||
'AwardsPage.step2List[1]',
|
||
'AwardsPage.step2List[2]'
|
||
],
|
||
background: '#F9F9F9'
|
||
}
|
||
])
|
||
|
||
const applyRef = ref()
|
||
const applyTitleRef = ref()
|
||
const applySubTitleRef = ref()
|
||
const reqListRef = ref()
|
||
const itemRefs = ref<HTMLElement[]>([])
|
||
const step3Ref = ref()
|
||
const step4Ref = ref()
|
||
|
||
const hasPlayedAnim = ref(false)
|
||
let timeline: gsap.core.Timeline | null = null
|
||
|
||
let observer: IntersectionObserver | null = null
|
||
|
||
const setupApplyInitialState = () => {
|
||
// 设置标题和副标题的初始状态
|
||
const titleEls = [applyTitleRef.value, applySubTitleRef.value].filter(
|
||
Boolean
|
||
) as HTMLElement[]
|
||
if (titleEls.length) {
|
||
gsap.set(titleEls, {
|
||
opacity: 0,
|
||
scale: 0,
|
||
transformOrigin: '50% 50%'
|
||
})
|
||
}
|
||
|
||
// 设置步骤元素的初始状态
|
||
const allStepElements: HTMLElement[] = []
|
||
if (itemRefs.value && itemRefs.value.length > 0) {
|
||
allStepElements.push(...itemRefs.value)
|
||
}
|
||
if (step3Ref.value) {
|
||
allStepElements.push(step3Ref.value as HTMLElement)
|
||
}
|
||
if (step4Ref.value) {
|
||
allStepElements.push(step4Ref.value as HTMLElement)
|
||
}
|
||
|
||
if (allStepElements.length > 0) {
|
||
gsap.set(allStepElements, {
|
||
opacity: 0,
|
||
y: 50
|
||
})
|
||
}
|
||
}
|
||
|
||
const initAnimations = () => {
|
||
if (hasPlayedAnim.value) return
|
||
|
||
timeline = gsap.timeline({
|
||
defaults: { ease: 'back.out(1.7)' }
|
||
})
|
||
|
||
if (applyTitleRef.value && applySubTitleRef.value) {
|
||
timeline.to([applyTitleRef.value, applySubTitleRef.value], {
|
||
scale: 1,
|
||
opacity: 1,
|
||
duration: 0.6,
|
||
stagger: 0.1
|
||
})
|
||
}
|
||
|
||
const allStepElements: HTMLElement[] = []
|
||
if (itemRefs.value && itemRefs.value.length > 0) {
|
||
allStepElements.push(...itemRefs.value)
|
||
}
|
||
if (step3Ref.value) {
|
||
allStepElements.push(step3Ref.value as HTMLElement)
|
||
}
|
||
if (step4Ref.value) {
|
||
allStepElements.push(step4Ref.value as HTMLElement)
|
||
}
|
||
|
||
if (allStepElements.length > 0) {
|
||
timeline.to(allStepElements, {
|
||
opacity: 1,
|
||
y: 0,
|
||
duration: 0.6,
|
||
stagger: 0.2
|
||
}, '>')
|
||
}
|
||
|
||
hasPlayedAnim.value = true
|
||
}
|
||
|
||
onMounted(() => {
|
||
nextTick(() => {
|
||
setupApplyInitialState()
|
||
observer = new IntersectionObserver(
|
||
(entries) => {
|
||
entries.forEach((entry) => {
|
||
if (entry.isIntersecting) {
|
||
initAnimations()
|
||
observer?.disconnect()
|
||
}
|
||
})
|
||
},
|
||
{
|
||
threshold: 0.3,
|
||
rootMargin: '0px 0px -100px 0px'
|
||
}
|
||
)
|
||
|
||
// Start observing the component root element
|
||
if (applyRef.value) {
|
||
observer.observe(applyRef.value)
|
||
}
|
||
})
|
||
})
|
||
|
||
onBeforeUnmount(() => {
|
||
// Cleanup animation timeline
|
||
if (timeline) {
|
||
timeline.kill()
|
||
}
|
||
// Cleanup IntersectionObserver
|
||
if (observer) {
|
||
observer.disconnect()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
p {
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
ul {
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
.animation-element{
|
||
will-change: opacity transform;
|
||
}
|
||
.apply-container {
|
||
flex: 1;
|
||
height: 143.3rem;
|
||
background: url('@/assets/images/award/apply_bg.png') no-repeat;
|
||
background-size: 100% 100%;
|
||
padding: 12.7rem 21.4rem 12rem;
|
||
.title {
|
||
text-align: center;
|
||
color: #232323;
|
||
font-family: 'PoppinsBold';
|
||
font-weight: 600;
|
||
font-size: 4rem;
|
||
margin-bottom: 3rem;
|
||
}
|
||
.sub-title {
|
||
text-align: center;
|
||
color: #b10000;
|
||
font-size: 3rem;
|
||
font-family: 'Arial';
|
||
font-weight: 400;
|
||
margin-bottom: 8.2rem;
|
||
}
|
||
.requirments-list {
|
||
flex: 1;
|
||
row-gap: 8.2rem;
|
||
.top {
|
||
height: 27.4rem;
|
||
color: #585858;
|
||
column-gap: 4.6rem;
|
||
.item-box {
|
||
height: 27.4rem;
|
||
}
|
||
}
|
||
.item-box {
|
||
border-radius: 0.8rem;
|
||
&:nth-of-type(1) {
|
||
width: 47rem;
|
||
flex-grow: initial;
|
||
}
|
||
&:nth-of-type(2) {
|
||
flex: 1;
|
||
}
|
||
.item-header {
|
||
background-color: #424242;
|
||
border-radius: 0.8rem;
|
||
height: 7.8rem;
|
||
.item-title {
|
||
color: #fff;
|
||
font-family: 'PoppinsBold';
|
||
font-weight: 600;
|
||
font-size: 2.4rem;
|
||
text-align: center;
|
||
white-space: pre-line;
|
||
}
|
||
}
|
||
.context-container {
|
||
margin-top: 4rem;
|
||
column-gap: 7rem;
|
||
.list {
|
||
font-family: 'Instrument';
|
||
font-weight: 400;
|
||
font-size: 2.4rem;
|
||
line-height: 3rem;
|
||
}
|
||
}
|
||
.context {
|
||
// margin-top: 4rem;
|
||
// width: 46.8rem;
|
||
text-align: center;
|
||
color: #585858;
|
||
font-family: 'Arial';
|
||
font-weight: 400;
|
||
line-height: 3rem;
|
||
font-size: 2.4rem;
|
||
// padding-left: 5.6rem;
|
||
white-space: pre-line;
|
||
}
|
||
}
|
||
.bottom {
|
||
column-gap: 4.6rem;
|
||
height: 63.4rem;
|
||
.step-3 {
|
||
flex: 1;
|
||
}
|
||
.step-3,
|
||
.step-4 {
|
||
.header {
|
||
font-family: 'PoppinsBold';
|
||
font-weight: 600;
|
||
font-size: 2.4rem;
|
||
text-align: center;
|
||
height: 7.4rem;
|
||
line-height: 7.4rem;
|
||
color: #fff;
|
||
background-color: #b10000;
|
||
border-radius: 0.8rem;
|
||
}
|
||
.content {
|
||
padding: 4rem;
|
||
border-radius: 0.8rem;
|
||
column-gap: 6.4rem;
|
||
background: linear-gradient(
|
||
180deg,
|
||
#ffe8e8 0%,
|
||
#feefef 25%,
|
||
#f9f9f9 100%
|
||
);
|
||
flex: 1;
|
||
.content-left {
|
||
flex: 1;
|
||
}
|
||
|
||
.content-item {
|
||
.item-header {
|
||
column-gap: 2rem;
|
||
color: #585858;
|
||
font-family: 'InstrumentBold';
|
||
font-weight: 700;
|
||
font-size: 2.4rem;
|
||
line-height: 3rem;
|
||
.point {
|
||
width: 1.2rem;
|
||
height: 1.2rem;
|
||
background-color: #b10000;
|
||
border-radius: 50%;
|
||
}
|
||
}
|
||
.item-desc {
|
||
font-family: 'Instrument';
|
||
font-weight: 400;
|
||
font-size: 2.4rem;
|
||
color: #585858;
|
||
&.indent {
|
||
padding-left: 3.8rem;
|
||
line-height: 3rem;
|
||
padding-top: 2rem;
|
||
}
|
||
}
|
||
.desc-wrapper {
|
||
margin-top: 3rem;
|
||
/* 基线行高变量,供子元素计算方块垂直偏移以对齐首行 */
|
||
--desc-line-height: 3rem;
|
||
font-family: 'Instrument';
|
||
font-weight: 400;
|
||
color: #585858;
|
||
font-size: 2.4rem;
|
||
line-height: 3rem;
|
||
row-gap: 3rem;
|
||
.desc-lists {
|
||
/* 使用自定义方块代替浏览器 marker,保证大小为 1rem 并与文字垂直居中 */
|
||
padding-left: 0;
|
||
list-style: none;
|
||
li {
|
||
list-style: none;
|
||
/* 使内容对齐到首行顶部,方块通过 margin-top 调整到首行中间 */
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 1rem;
|
||
padding: 0;
|
||
margin: 0.4rem 0;
|
||
&::before {
|
||
content: '';
|
||
/* 固定为 1rem 方块 */
|
||
width: 0.5rem;
|
||
height: 0.5rem;
|
||
background-color: #585858;
|
||
flex: 0 0 0.5rem;
|
||
border-radius: 0;
|
||
/* 让方块垂直居中于第一行文字:(line-height - square)/2 */
|
||
margin-top: calc(
|
||
(var(--desc-line-height, 3rem) - 1rem) / 2
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.content-right {
|
||
.content-item {
|
||
height: 100%;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.step-4 {
|
||
width: 45.1rem;
|
||
.header {
|
||
color: #fff;
|
||
text-align: center;
|
||
background-color: #424242;
|
||
border-radius: 0.8rem;
|
||
height: 7.8rem;
|
||
white-space: pre-line;
|
||
font-family: 'PoppinsBold';
|
||
font-weight: 600;
|
||
font-size: 2.4rem;
|
||
line-height: 1.5;
|
||
.sub-title {
|
||
font-family: 'Poppins';
|
||
font-weight: 400;
|
||
font-size: 1.8rem;
|
||
color: #fff;
|
||
margin: 0;
|
||
}
|
||
}
|
||
.content {
|
||
background: #fff;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|