style: 竞赛主页样式修改

This commit is contained in:
2026-01-30 17:21:11 +08:00
parent de78bfc051
commit 89a89ea5ef
18 changed files with 773 additions and 278 deletions

View File

@@ -1,28 +1,170 @@
<template>
<div class="apply-container container flex flex-col" ref="applyRef">
<div class="title" ref="applyTitleRef">How to Apply</div>
<div class="sub-title" ref="applySubTitleRef">Requirments</div>
<div class="requirments-list flex" ref="reqListRef">
<div class="left flex flex-col space-between">
<div class="item-box" v-for="item in leftRequirment" :key="item.type">
<div class="item-header flex align-center">
<img src="@/assets/images/award/bloom_logo.png" class="logo" />
<div
class="apply-container flex flex-col"
id="apply"
ref="applyRef"
>
<div
class="title animation-element"
ref="applyTitleRef"
>
How to Apply
</div>
<div
class="sub-title animation-element"
ref="applySubTitleRef"
>
Step by step
</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">{{ item.type }}</div>
</div>
<div class="context" v-for="el in item.desc">
{{ el }}
<div class="context-container flex flex-center">
<div
class="context"
v-for="el in item.desc"
>
{{ el }}
</div>
<div
class="list"
v-if="item.listTitle"
>
<div class="list-title">{{ item.listTitle }}</div>
<ul class="list-items">
<li
class="list-item"
v-for="el in item.list"
>
{{ el }}
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="right">
<div class="item-box">
<div class="item-box">
<div class="item-header flex align-center">
<img src="@/assets/images/award/bloom_logo.png" class="logo" />
<div class="item-title">{{ rightRequirment.type }}</div>
<div class="bottom flex">
<div class="step-3 flex flex-col animation-element" ref="step3Ref">
<div class="header">Step 3. Prepare Your Submission</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>Process Video</div>
</div>
<div class="desc-wrapper flex flex-col space-between">
<div class="item-desc">
Include a screenrecorded video
<br />
your creative process
<br />
using AiDA.
<br />
</div>
<ul class="desc-lists">
<div class="desc-lists-title">
Video requirements:
</div>
<li>Format: MP4</li>
<li>Resolution: 1080×1920 px</li>
<li>Duration: Maximum 1 minute</li>
<li>File size: Maximum 20MB</li>
</ul>
</div>
</div>
<div class="content-item">
<div class="item-header flex align-center">
<div class="point"></div>
<div>File Name</div>
</div>
<div class="item-desc indent">
AiDAGlobalDesignAward
<br />
2026_[Your Full Name]
</div>
</div>
</div>
<div class="context" v-for="el in rightRequirment.desc">
{{ el }}
<div class="content-right">
<div class="content-item flex flex-col">
<div class="item-header flex align-center">
<div class="point"></div>
<div>Design Portfolio(PDF)</div>
</div>
<div
class="desc-wrapper flex-1 flex flex-col space-between"
>
<ul class="desc-lists">
<div class="desc-lists-title">
<p>
Submit one single PDF file that includes:
</p>
<p>Required structure:</p>
</div>
<li>Design title</li>
<li>Moodboard</li>
<li>Concept explanation</li>
<div>(How to used AiDA to develop design)</div>
</ul>
<ul class="desc-lists">
<div class="desc-lists-title">
<p>PDF requirements:</p>
</div>
<li>Maximum 15 pages</li>
<li>Maximum file size: 20MB</li>
<li>
Language: English or native language
<br />
with English translation
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="step-4 animation-element" ref="step4Ref">
<div class="header flex flex-col flex-center">
<p>Step 4. Finalist Requirement</p>
<p class="sub-title">(for top 20 Designers)</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">
The 20 finalists will be required to
<br />
submit physical garments for final
<br />
evaluation
</div>
<li>Number of pieces: 1</li>
<li>
Garments must be produced
<br />
based on the submitted
<br />
AiDA-generated designs
</li>
<li>
Shipping instructions will be provided by
Code-create
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -32,178 +174,375 @@
</template>
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { gsap } from 'gsap'
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { gsap } from 'gsap'
const leftRequirment = ref([
{
type: 'Video',
desc: ['The process of doing design']
},
{
type: 'Design',
desc: [
'Structure: design title, moodboard and elaboration (how will you use AiDA to design)',
'Design sketch: Maximum 4 outfit design with proposed materials'
]
const leftRequirment = ref([
{
type: 'Step 1. Become an\nAiDA Subscriber',
desc: [
'All applicants must be active\nAiDA subscribers at the time of\nsubmission. You may subscribe\nunder either a monthly or yearly plan.'
]
},
{
type: 'Step 2. Create Your Design Using AiDA',
desc: [
'Applicants must create their\ndesigns exclusively using the\nAiDA platform. '
],
listTitle: 'Your work shold clearly demonstrate:',
list: [
'· How AiDA is used as a creative tool',
'· Your design concept and creative direction',
'· The intergration of AI and human creativity'
],
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 rightRequirment = ref({
type: 'Submission Format',
desc: [
'Naming as “AiDA global award 2026_applicantname”',
'Mp4\n(1080x1920pixels/20mb within 1min)',
'Single PDF file\n(within 15 pages, maximum 20mb)',
'English or native language\nwith English translation'
]
})
const initAnimations = () => {
if (hasPlayedAnim.value) return
const applyRef = ref<HTMLElement | null>(null)
const applyTitleRef = ref<HTMLElement | null>(null)
const applySubTitleRef = ref<HTMLElement | null>(null)
const reqListRef = ref<HTMLElement | null>(null)
const hasPlayedApplyAnim = ref(false)
let applyObserver: 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%'
timeline = gsap.timeline({
defaults: { ease: 'back.out(1.7)' }
})
}
const headers = reqListRef.value?.querySelectorAll<HTMLElement>('.item-header')
const contexts = reqListRef.value?.querySelectorAll<HTMLElement>('.context')
gsap.set([headers, contexts], { opacity: 0 })
}
const playApplyAnimation = () => {
if (hasPlayedApplyAnim.value) return
const titleEls = [applyTitleRef.value, applySubTitleRef.value].filter(
Boolean
) as HTMLElement[]
const headers = reqListRef.value?.querySelectorAll<HTMLElement>('.item-header')
const contexts = reqListRef.value?.querySelectorAll<HTMLElement>('.context')
if (!titleEls.length) return
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
tl.to(titleEls, {
opacity: 1,
scale: 1,
duration: 0.6,
ease: 'back.out(1.6)',
stagger: 0.1
})
if (headers?.length) {
tl.to(
headers,
{
if (applyTitleRef.value && applySubTitleRef.value) {
timeline.to([applyTitleRef.value, applySubTitleRef.value], {
scale: 1,
opacity: 1,
duration: 0.4,
duration: 0.6,
stagger: 0.1
},
'-=0.1'
)
}
if (contexts?.length) {
tl.to(
contexts,
{
})
}
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,
duration: 0.4,
stagger: 0.05
},
'-=0.05'
)
y: 0,
duration: 0.6,
stagger: 0.2
}, '>')
}
hasPlayedAnim.value = true
}
hasPlayedApplyAnim.value = true
applyObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupApplyInitialState()
if ('IntersectionObserver' in window) {
applyObserver = new IntersectionObserver(
onMounted(() => {
nextTick(() => {
setupApplyInitialState()
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
playApplyAnimation()
initAnimations()
observer?.disconnect()
}
})
},
{ threshold: 0.25 }
{
threshold: 0.3,
rootMargin: '0px 0px -100px 0px'
}
)
if (applyRef.value) applyObserver.observe(applyRef.value)
} else {
playApplyAnimation()
// 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()
}
})
})
onBeforeUnmount(() => {
applyObserver?.disconnect()
})
</script>
<style scoped lang="less">
.apply-container {
flex: 1;
background: url('@/assets/images/award/apply_bg.png') no-repeat;
background-size: 100% 100%;
padding: 12.7rem 0 16.9rem;
.title {
text-align: center;
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
margin-bottom: 3rem;
p {
margin: 0;
padding: 0;
}
.sub-title {
text-align: center;
color: #b10000;
font-size: 3rem;
font-family: 'Arial';
font-weight: 400;
ul {
margin: 0;
padding: 0;
}
.requirments-list {
.animation-element{
will-change: opacity transform;
}
.apply-container {
flex: 1;
padding-left: 41.4rem;
column-gap: 17.7rem;
margin-top: 12rem;
.left {
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;
height: 100%;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
margin-bottom: 3rem;
}
.item-box {
.item-header {
column-gap: 3.2rem;
.item-title {
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.8rem;
.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;
}
}
.context {
margin-top: 4rem;
width: 46.8rem;
color: #585858;
font-family: 'Arial';
font-weight: 400;
line-height: 3rem;
font-size: 2.4rem;
padding-left: 5.6rem;
white-space: pre-line;
.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: 24px;
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>