chore: 删除award

This commit is contained in:
2026-04-09 14:23:52 +08:00
parent 2b4e77f4e1
commit 27651e3a46
65 changed files with 679 additions and 4986 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,529 +0,0 @@
<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>

View File

@@ -1,202 +0,0 @@
<template>
<div class="bloom flex flex-col align-center">
<div
class="title"
ref="titleRef"
>
{{ $t('AwardsPage.bloomYourCreativity') }}
</div>
<div
class="season"
ref="subtitleRef"
>
{{ $t('AwardsPage.themeOf2026') }}
</div>
<div
class="desc"
ref="textRef"
>
<p class="section-1">
{{ $t('AwardsPage.bloomText.desc1.regular1') }}
<span class="arial-bold">
{{ $t('AwardsPage.bloomText.desc1.bold1') }}
</span>
{{ $t('AwardsPage.bloomText.desc1.regular2') }}
<span class="arial-bold">
{{ $t('AwardsPage.bloomText.desc1.bold2') }}
</span>
{{ $t('AwardsPage.bloomText.desc1.regular3') }}
<span class="arial-bold">
{{ $t('AwardsPage.bloomText.desc1.bold3') }}
</span>
{{ $t('AwardsPage.bloomText.desc1.regular4') }}
<span class="arial-bold">
{{ $t('AwardsPage.bloomText.desc1.bold4') }}
</span>
{{ $t('AwardsPage.bloomText.desc1.regular5') }}
</p>
<p class="section-2">
{{ $t('AwardsPage.bloomText.desc2.regular1') }}
<span class="arial-bold">
{{ $t('AwardsPage.bloomText.desc2.bold1') }}
</span>
{{ $t('AwardsPage.bloomText.desc2.regular2') }}
</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { gsap } from 'gsap'
const { t } = useI18n()
const titleRef = ref<HTMLElement | null>(null)
const subtitleRef = ref<HTMLElement | null>(null)
const textRef = ref<HTMLElement | null>(null)
const hasPlayedBloomAnim = ref(false)
let bloomObserver: IntersectionObserver | null = null
const setupBloomInitialState = () => {
const titleEls = [titleRef.value, subtitleRef.value].filter(
Boolean
) as HTMLElement[]
if (titleEls.length) {
gsap.set(titleEls, {
opacity: 0,
// start larger than final size, then animate down to scale:1
scale: 1.6,
transformOrigin: '50% 50%'
})
}
if (textRef.value) {
// start below and hidden
gsap.set(textRef.value, {
opacity: 0,
y: 60
})
}
}
const playBloomAnimation = () => {
if (hasPlayedBloomAnim.value) return
const titleEls = [titleRef.value, subtitleRef.value].filter(
Boolean
) as HTMLElement[]
const textEl = textRef.value
if (!titleEls.length || !textEl) return
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
tl.to(titleEls, {
opacity: 1,
scale: 1,
duration: 0.9,
ease: 'back.out(1.6)',
stagger: 0.12
})
tl.to(
textEl,
{
opacity: 1,
y: -12,
scale: 1.05,
duration: 0.3,
ease: 'power2.out'
},
'-=0.3'
)
tl.to(
textEl,
{
y: 0,
scale: 1,
duration: 0.18,
ease: 'bounce.out'
},
'+=0.08'
)
hasPlayedBloomAnim.value = true
bloomObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupBloomInitialState()
if ('IntersectionObserver' in window) {
bloomObserver = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
playBloomAnimation()
}
})
},
{ threshold: 0.3 }
)
if (titleRef.value) {
bloomObserver.observe(titleRef.value)
}
} else {
// fallback
playBloomAnimation()
}
})
})
onBeforeUnmount(() => {
bloomObserver?.disconnect()
})
</script>
<style scoped lang="less">
p {
margin: 0;
padding: 0;
}
.arial-bold {
font-family: 'ArialBold';
font-weight: 700;
}
.bloom {
height: 108rem;
padding-top: 12.8rem;
font-family: 'Poppins';
background: url('@/assets/images/award/bloom_bg.png') no-repeat;
background-size: 100% 100%;
.title {
font-size: 4rem;
color: #232323;
margin-bottom: 2.4rem;
}
.logo {
margin-bottom: 2.2rem;
}
.season {
font-size: 3rem;
color: #c7342c;
margin-bottom: 6.6rem;
}
.desc {
font-family: 'Arial';
font-weight: 400;
font-size: 2.4rem;
color: #585858;
text-align: center;
padding: 0 21.5rem;
line-height: 4.5rem;
margin-bottom: 12.3rem;
white-space: pre-line;
.section-2 {
margin-top: 4rem;
}
}
}
</style>

View File

@@ -1,233 +0,0 @@
<template>
<div class="judges-container flex flex-col align-center">
<div class="title" ref="judgesTitleRef">{{ $t('AwardsPage.panelOfJudges') }}</div>
<!-- <img src="@/assets/images/award/bloom_logo.png" class="logo" /> -->
<div class="sub-title" ref="judgesSubTitleRef">{{ $t('AwardsPage.expertise') }}</div>
<div class="judgement-list" ref="judgementListRef">
<div
class="judgement-item flex flex-col align-center"
v-for="item in judgements"
:key="item.name"
>
<img :src="item.picture" class="picture" />
<div class="name">{{ $t(item.name) }}</div>
<div class="desc">{{ $t(item.desc) }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { gsap } from 'gsap'
import jae from '@/assets/images/award/jae.png'
import diego from '@/assets/images/award/diego.png'
import gregory from '@/assets/images/award/gregory.png'
import vincenzo from '@/assets/images/award/vincenzo.png'
import tim from '@/assets/images/award/tim.png'
import desmond from '@/assets/images/award/desmond.png'
const { t } = useI18n()
const judgements = [
{
picture: jae,
name: 'Jae Hyuk Lim',
desc: 'AwardsPage.judgesHat.jae'
},
{
picture: diego,
name: 'Diego Dultzin Lacoste',
desc: 'AwardsPage.judgesHat.diego'
},
{
picture: gregory,
name: 'Gregory de la Hogue Moran',
desc: 'AwardsPage.judgesHat.gregory'
},
{
picture: vincenzo,
name: 'Vincenzo La Torre',
desc: 'AwardsPage.judgesHat.vincenzo'
},
{
picture: tim,
name: 'Tim Lim',
desc: 'AwardsPage.judgesHat.tim'
},
{
picture: desmond,
name: 'Desmond Lim',
desc: 'AwardsPage.judgesHat.desmond'
}
]
const judgesTitleRef = ref<HTMLElement | null>(null)
const judgesSubTitleRef = ref<HTMLElement | null>(null)
const judgementListRef = ref<HTMLElement | null>(null)
const hasPlayedJudgementAnim = ref(false)
let judgementObserver: IntersectionObserver | null = null
const setupJudgementInitialState = () => {
const titleEls = [judgesTitleRef.value, judgesSubTitleRef.value].filter(
Boolean
) as HTMLElement[]
if (titleEls.length) {
gsap.set(titleEls, {
opacity: 0,
scale: 0,
transformOrigin: '50% 50%'
})
}
const items =
judgementListRef.value?.querySelectorAll<HTMLElement>('.judgement-item')
if (items?.length) {
gsap.set(items, {
opacity: 0,
clipPath: 'inset(0 0 100% 0)'
})
}
}
const playJudgementAnimation = () => {
if (hasPlayedJudgementAnim.value) return
const titleEls = [judgesTitleRef.value, judgesSubTitleRef.value].filter(
Boolean
) as HTMLElement[]
const listEl = judgementListRef.value
if (!titleEls.length || !listEl) return
const items = Array.from(
listEl.querySelectorAll<HTMLElement>('.judgement-item')
)
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
tl.to(titleEls, {
opacity: 1,
scale: 1,
duration: 0.4,
ease: 'back.out(1.6)',
stagger: 0.1
})
if (items.length) {
const firstRow = items.slice(0, 3)
const secondRow = items.slice(3)
if (firstRow.length) {
tl.to(
firstRow,
{
opacity: 1,
clipPath: 'inset(0% 0% 0% 0%)',
duration: 0.45,
stagger: 0.05
},
'-=0.2'
)
}
if (secondRow.length) {
tl.to(
secondRow,
{
opacity: 1,
clipPath: 'inset(0% 0% 0% 0%)',
duration: 0.45,
stagger: 0.05
},
'+=0.1'
)
}
}
hasPlayedJudgementAnim.value = true
judgementObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupJudgementInitialState()
if ('IntersectionObserver' in window) {
judgementObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
playJudgementAnimation()
}
})
},
{ threshold: 0.3 }
)
if (judgementListRef.value) {
judgementObserver.observe(judgementListRef.value)
}
} else {
// Fallback: play immediately if IntersectionObserver unsupported
playJudgementAnimation()
}
})
})
onBeforeUnmount(() => {
judgementObserver?.disconnect()
})
</script>
<style scoped lang="less">
.judges-container {
height: 147.4rem;
background: url('@/assets/images/award/judges_bg.png') no-repeat;
background-size: 100% 100%;
padding-top: 12.8rem;
.title {
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
}
.logo {
margin: 2.4rem 0 2.2rem;
}
.sub-title {
color: #b10000;
font-family: 'Arial';
font-weight: 400;
font-size: 3rem;
margin-bottom: 12rem;
}
.judgement-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 23.22rem;
row-gap: 8rem;
padding: 0 25rem 0 26.6rem;
div{
text-align: center;
}
.judgement-item {
overflow: hidden;
.picture {
width: 20.2rem;
height: 26rem;
border-radius: 0.8rem;
}
.name {
margin: 3rem 0 2.4rem;
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.4rem;
}
.desc {
color: #585858;
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
white-space: pre-line;
text-align: center;
}
}
}
}
</style>

View File

@@ -1,276 +0,0 @@
<template>
<div
class="prizes-container container flex align-center space-between"
ref="prizesRef"
>
<div class="left flex flex-col flex-center">
<div
class="title"
ref="prizesTitleRef"
>
{{ $t('AwardsPage.awardPrizes') }}
</div>
<!-- <img src="@/assets/images/award/bloom_logo.png" class="logo" /> -->
<div
class="desc"
ref="prizesSubTitleRef"
>
{{ $t('AwardsPage.recognition') }}
</div>
</div>
<div
class="right"
ref="prizesRightRef"
>
<div
class="prize-item flex flex-col flex-center"
:class="{ smaller: item.smaller }"
v-for="item in prizes"
:key="item.name"
>
<div class="prize-money">
{{ $t(item.money) }}
</div>
<div class="prize-name">{{ $t(item.name) }}</div>
<div class="prize-desc flex flex-col flex-center">
<div
class="desc-item"
v-for="el in item.desc"
>
{{ $t(el) }}
</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 props = defineProps({
isZh: {
type: Boolean,
default: false
}
})
const prizes = [
{
money: 'AwardsPage.grandMoney',
name: 'AwardsPage.grandAwards',
desc: [
'AwardsPage.cashAward',
'AwardsPage.awardCertificate',
'AwardsPage.globalMediaExposure'
]
},
{
money: 'AwardsPage.goldMoney',
name: 'AwardsPage.goldAwards',
desc: [
'AwardsPage.cashAward',
'AwardsPage.awardCertificate',
'AwardsPage.globalMediaExposure'
]
},
{
money: 'AwardsPage.silverMoney',
name: 'AwardsPage.silverAwards',
desc: [
'AwardsPage.cashAward',
'AwardsPage.awardCertificate',
'AwardsPage.globalMediaExposure'
]
},
{
money: 'AwardsPage.awardCertification',
name: 'AwardsPage.finalists',
desc: ['AwardsPage.TravelAllowance', 'AwardsPage.globalMediaExposure'],
smaller: !props.isZh
}
]
const prizesRef = ref<HTMLElement | null>(null)
const prizesTitleRef = ref<HTMLElement | null>(null)
const prizesSubTitleRef = ref<HTMLElement | null>(null)
const prizesRightRef = ref<HTMLElement | null>(null)
const hasPlayedPrizesAnim = ref(false)
let prizesObserver: IntersectionObserver | null = null
const setupPrizesInitialState = () => {
const titleEls = [prizesTitleRef.value, prizesSubTitleRef.value].filter(
Boolean
) as HTMLElement[]
if (titleEls.length) {
gsap.set(titleEls, {
opacity: 0,
scale: 0,
transformOrigin: '50% 50%'
})
}
if (prizesRightRef.value) {
gsap.set(prizesRightRef.value, {
'opacity': 0,
'y': 40,
'scale': 1.08,
'--prize-row-gap': '2rem',
'--prize-col-gap': '2rem'
})
}
}
const playPrizesAnimation = () => {
if (hasPlayedPrizesAnim.value) return
const titleEls = [prizesTitleRef.value, prizesSubTitleRef.value].filter(
Boolean
) as HTMLElement[]
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
if (titleEls.length) {
tl.to(titleEls, {
opacity: 1,
scale: 1,
duration: 0.6,
ease: 'back.out(1.6)',
stagger: 0.1
})
}
if (prizesRightRef.value) {
tl.to(
prizesRightRef.value,
{
'opacity': 1,
'y': 0,
'scale': 1,
'--prize-row-gap': '4.2rem',
'--prize-col-gap': '4.4rem',
'duration': 0.55,
'ease': 'back.out(1.4)'
},
titleEls.length ? '-=0.15' : 0
)
}
hasPlayedPrizesAnim.value = true
prizesObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupPrizesInitialState()
if ('IntersectionObserver' in window) {
prizesObserver = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
playPrizesAnimation()
}
})
},
{ threshold: 0.25 }
)
if (prizesRef.value) prizesObserver.observe(prizesRef.value)
} else {
playPrizesAnimation()
}
})
})
onBeforeUnmount(() => {
prizesObserver?.disconnect()
})
</script>
<style scoped lang="less">
.prizes-container {
background: url('@/assets/images/award/prizes_bg.png') no-repeat;
background-size: 100% 100%;
padding: 0 21.4rem 0 34.2rem;
box-sizing: border-box;
.left {
row-gap: 3.6rem;
.title {
text-align: center;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
color: #fff;
}
.desc {
text-align: center;
color: #f95750;
font-family: 'Poppins';
font-weight: 400;
font-size: 3rem;
}
}
.right {
// height: 45.4rem;
// padding: 4.6rem 6.1rem 4.6rem 0;
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
row-gap: var(--prize-row-gap, 4.2rem);
column-gap: var(--prize-col-gap, 4.4rem);
// flex: 1;
.prize-item {
width: 35.5rem;
height: 32.8rem;
color: #fff;
padding: 4.5rem 0 4.8rem 0;
justify-content: space-between;
background: url('@/assets/images/award/first_bg.png') no-repeat;
background-size: 100% 100%;
&:nth-of-type(2) {
background: url('@/assets/images/award/second_bg.png') no-repeat;
background-size: 100% 100%;
}
&:nth-of-type(3) {
background: url('@/assets/images/award/grand_bg.png') no-repeat;
background-size: 100% 100%;
}
&:nth-of-type(4) {
background: url('@/assets/images/award/certification_bg.png')
no-repeat;
background-size: 100% 100%;
}
&.smaller {
.prize-money {
font-size: 3.6rem;
line-height: 3.8rem;
}
}
.prize-money {
font-family: 'PoppinsBold';
font-weight: bold;
font-size: 4rem;
white-space: pre-line;
text-align: center;
line-height: 7.6rem;
&.smaller {
font-size: 3.6rem;
}
}
.prize-name {
font-family: 'PoppinsMedium';
font-weight: 500;
font-size: 2.8rem;
}
.prize-desc {
color: #e0e0e0;
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
line-height: 3rem;
height: 8.9rem;
}
}
}
}
</style>

View File

@@ -1,177 +0,0 @@
<template>
<div
class="selection-container container flex flex-col align-center"
ref="selectionRef"
>
<div class="title">{{ $t('AwardsPage.selectionCriteria') }}</div>
<!-- <img src="@/assets/images/award/bloom_logo.png" class="logo" /> -->
<div class="sub-title">{{ $t('AwardsPage.evaluation') }}</div>
<div class="criteria-list flex" ref="criteriaListRef">
<div
class="item flex flex-col align-center"
v-for="item in criteriaList"
:key="item.name"
>
<img :src="item.icon" class="icon" :style="item.style" />
<div class="name">{{ $t(item.name) }}</div>
<div class="desc">{{ $t(item.desc) }}</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'
import criteria1 from '@/assets/images/award/criteria_1.png'
import criteria2 from '@/assets/images/award/criteria_2.png'
import criteria3 from '@/assets/images/award/criteria_3.png'
import criteria4 from '@/assets/images/award/criteria_4.png'
const { t } = useI18n()
const criteriaList = ref([
{
icon: criteria1,
name: 'AwardsPage.originality',
desc: 'AwardsPage.originalityDesc',
style: { width: '13rem', height: '17rem' }
},
{
icon: criteria2,
name: 'AwardsPage.creativity',
desc: 'AwardsPage.creativityDesc',
style: { width: '16rem', height: '18rem' }
},
{
icon: criteria3,
name: 'AwardsPage.aidaIntegration',
desc: 'AwardsPage.aidaIntegrationDesc',
style: { width: '16rem', height: '18rem' }
},
{
icon: criteria4,
name: 'AwardsPage.execution',
desc: 'AwardsPage.executionDesc',
style: { width: '18.8rem', height: '18rem' }
}
])
const selectionRef = ref<HTMLElement | null>(null)
const criteriaListRef = ref<HTMLElement | null>(null)
const hasPlayedSelectionAnim = ref(false)
let selectionObserver: IntersectionObserver | null = null
const setupSelectionInitialState = () => {
const items =
criteriaListRef.value?.querySelectorAll<HTMLElement>('.item') ?? []
if (items.length) {
gsap.set(items, {
opacity: 0,
scale: 0,
transformOrigin: '50% 50%'
})
}
}
const playSelectionAnimation = () => {
if (hasPlayedSelectionAnim.value) return
const items =
criteriaListRef.value?.querySelectorAll<HTMLElement>('.item') ?? []
if (!items.length) return
gsap.to(items, {
opacity: 1,
scale: 1,
duration: 0.6,
ease: 'back.out(1.6)',
stagger: 0.3
})
hasPlayedSelectionAnim.value = true
selectionObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupSelectionInitialState()
if ('IntersectionObserver' in window) {
selectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
playSelectionAnimation()
}
})
},
{ threshold: 0.25 }
)
if (selectionRef.value) {
selectionObserver.observe(selectionRef.value)
}
} else {
playSelectionAnimation()
}
})
})
onBeforeUnmount(() => {
selectionObserver?.disconnect()
})
</script>
<style scoped lang="less">
.selection-container {
background: url('@/assets/images/award/selection_bg.png') no-repeat;
background-size: 100% 100%;
padding-top: 9.3rem;
.title {
color: #fff;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
}
.logo {
margin: 2.3rem 0 2.3rem;
}
.sub-title {
color: #f95750;
font-family: 'Popins';
font-weight: 400;
font-size: 3rem;
margin-bottom: 11.8rem;
}
.criteria-list {
column-gap: 6rem;
.item {
height: 44rem;
width: 32.2rem;
box-sizing: border-box;
&:nth-of-type(3) {
background: url('@/assets/images/award/criteria_bg.png') no-repeat;
background-size: 100% 100%;
}
.icon {
width: 18.8rem;
height: 18rem;
}
.name {
font-family: 'PoppinsMedium';
font-weight: 500;
font-size: 2.8rem;
color: #fff;
margin: 2rem 0 5rem;
}
.desc {
font-family: 'Arial';
font-weight: 400;
font-size: 2.4rem;
color: #e0e0e0;
text-align: center;
white-space: pre-line;
}
}
}
}
</style>

View File

@@ -1,156 +0,0 @@
<template>
<div
class="blocks-list flex"
ref="root"
:class="{ 'in-view': inView }"
>
<div
class="block-item flex flex-col flex-center"
v-for="(item, idx) in blocksList"
:key="item.number"
:style="{ '--delay': `${idx * 0.18}s` }"
>
<div class="number">{{ $t(item.number) }}</div>
<div class="label">{{ $t(item.label) }}</div>
<div class="line"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const blocksList = ref([
{
number: 'AwardsPage.totalCashPrizes',
label: 'AwardsPage.totalCashPrizesLabel'
},
{
number: 'AwardsPage.globalMediaExpose',
label: 'AwardsPage.globalMediaExposeLabel'
},
{
number: 'AwardsPage.networkingOpportunities',
label: 'AwardsPage.networkingOpportunitiesLabel'
},
{
number: 'AwardsPage.awardCeremonyHongKong',
label: 'AwardsPage.awardCeremonyLabel'
}
])
const root = ref<HTMLElement | null>(null)
const inView = ref(false)
let io: IntersectionObserver | null = null
onMounted(() => {
io = new IntersectionObserver(
entries => {
for (const entry of entries) {
if (entry.isIntersecting) {
// 延迟 0.5s 后触发动画并断开观察
setTimeout(() => {
inView.value = true
}, 500)
if (io) {
io.disconnect()
}
}
}
},
{ threshold: 0.05 }
)
if (root.value) {
io.observe(root.value)
}
})
onUnmounted(() => {
io?.disconnect()
})
</script>
<style lang="less" scoped>
.blocks-list {
height: 31.4rem;
background: linear-gradient(98.55deg, #232323 18.22%, #898989 101.1%);
.block-item {
flex: 1;
height: 100%;
color: #fff;
position: relative;
text-align: center;
white-space: pre-line;
row-gap: 3rem;
/* text scale-in animations */
.number {
font-size: 3.6rem;
font-family: 'PoppinsBold';
font-weight: 600;
transform: scale(0);
opacity: 0;
will-change: transform, opacity;
}
.label {
font-size: 2.4rem;
font-family: 'Arial';
font-weight: 400;
letter-spacing: 0.05em;
transform: scale(0);
opacity: 0;
will-change: transform, opacity;
}
/* vertical line grows top -> bottom */
.line {
position: absolute;
right: 0;
/* 固定 top 为最终高度的一半位置,这样 height 从 0 -> 27.4rem 时会从上向下增长 */
top: calc(50% - 13.7rem);
width: 0.1rem;
height: 0;
background-color: #8d8d8d;
will-change: height;
}
}
}
/* 当组件进入视口并且等待 0.5s 后,.in-view 会加入根节点,下面规则触发动画 */
.in-view .block-item .number {
animation: scaleIn 0.48s cubic-bezier(0.2, 0.9, 0.2, 1) forwards;
animation-delay: var(--delay);
}
.in-view .block-item .label {
animation: scaleIn 0.48s cubic-bezier(0.2, 0.9, 0.2, 1) forwards;
animation-delay: calc(var(--delay) + 0.12s);
}
.in-view .block-item .line {
animation: growLine 0.7s cubic-bezier(0.2, 0.9, 0.2, 1) forwards;
animation-delay: calc(var(--delay) + 0.18s);
}
/* keyframes */
@keyframes scaleIn {
from {
transform: scale(0);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
@keyframes growLine {
from {
height: 0;
}
to {
height: 27.4rem;
}
}
</style>

View File

@@ -1,81 +0,0 @@
<template>
<div class="success-container flex flex-col align-center">
<img
:src="info.icon"
alt=""
class="icon-img"
/>
<div class="title">{{ $t(info.title) }}</div>
<div class="desc">
{{ $t(info.desc) }}
<!-- <div>
Please review your submitted information in the AiDA in-platform message.
</div>
<div>
You may edit it if needed. Competition updates and results will be sent
via email.
</div> -->
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import successIcon from '@/assets/images/award/successful.png'
import expiredIcon from '@/assets/images/award/expired.png'
const { t } = useI18n()
const props = defineProps({
isExpired: {
type: Boolean,
default: false
}
})
const info = computed(() => {
if (props.isExpired) {
return {
icon: expiredIcon,
title: 'AwardsPage.deadlinePassed',
desc: 'AwardsPage.deadlinePassedDesc'
}
} else {
return {
icon: successIcon,
title: 'AwardsPage.submissionSuccessful',
desc: 'AwardsPage.submissionSuccessfulDesc'
}
}
})
</script>
<style lang="less" scoped>
.success-container {
margin: 0 21.5rem;
padding: 10.6rem 27.3rem 0;
height: 50rem;
position: relative;
top: -16.8rem;
background-color: #fff;
border-radius: 0.8rem;
.icon-img {
width: 12rem;
height: 12rem;
}
.title {
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 3rem;
color: #232323;
text-align: center;
margin: 2rem 0 4rem;
}
.desc {
color: #585858;
font-family: Arial;
font-weight: 400;
font-size: 2.4rem;
text-align: center;
}
}
</style>

View File

@@ -1,370 +0,0 @@
<template>
<div
ref="containerRef"
class="timeline-container container flex flex-col align-center"
>
<div class="timeline-title">{{ $t('AwardsPage.competitionTimeline') }}</div>
<div class="desc">{{ $t('AwardsPage.shapingTheFuture') }}</div>
<div
class="timeline-point"
ref="timelineRef"
>
<!-- 顶部标签行 -->
<div class="grid-row labels-row">
<div
class="grid-cell label-cell"
v-for="item in points"
:key="'label-' + item.time"
>
<div class="main-label">{{ $t(item.label) }}</div>
<div
class="sub-label"
v-if="item.subLabel"
>
{{ $t(item.subLabel) }}
</div>
</div>
</div>
<!-- 图标行 -->
<div class="grid-row icons-row">
<div class="timeline-line"></div>
<div
class="grid-cell icon-cell"
v-for="item in points"
:key="'icon-' + item.time"
>
<img
src="@/assets/images/award/point.png"
class="point-icon"
/>
</div>
</div>
<!-- 时间行 -->
<div class="grid-row times-row">
<div
class="grid-cell time-cell"
v-for="item in points"
:key="'time-' + item.time"
>
{{ $t(item.time) }}
</div>
</div>
<!-- 描述行 -->
<div class="grid-row descs-row">
<div
class="grid-cell desc-cell"
v-for="item in points"
:key="'desc-' + item.time"
>
<div class="txt">
{{ $t(item.desc) }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { gsap } from 'gsap'
const { t } = useI18n()
const containerRef = ref<HTMLElement | null>(null)
const timelineRef = ref<HTMLElement | null>(null)
const hasAnimated = ref(false)
const points = ref([
{
label: 'AwardsPage.timelineApplicationLabel',
subLabel: 'AwardsPage.timelineDeadlineLabel',
time: 'AwardsPage.timeJul15',
desc: 'AwardsPage.applicationDeadlineDesc'
},
{
label: 'AwardsPage.twentyFinalistsAnnounced',
subLabel: 'AwardsPage.announcedLabel',
time: 'AwardsPage.timeAug30',
desc: 'AwardsPage.twentyFinalistsDesc'
},
{
label: 'AwardsPage.finalistSubmission',
subLabel: 'AwardsPage.submissionLabel',
time: 'AwardsPage.timeSept30',
desc: 'AwardsPage.finalistSubmissionDesc'
},
{
label: 'AwardsPage.receivingOutfits',
subLabel: 'AwardsPage.fromFinalistsLabel',
time: 'AwardsPage.timeOctober',
desc: 'AwardsPage.receivingOutfitsDesc'
},
{
label: 'AwardsPage.awardCeremony',
subLabel: 'AwardsPage.ceremonyLabel',
time: 'AwardsPage.timeNov12',
desc: 'AwardsPage.awardCeremonyDesc'
}
])
const playAnimation = () => {
if (!containerRef.value || hasAnimated.value) return
const title = containerRef.value.querySelector('.timeline-title')
const subtitle = containerRef.value.querySelector('.desc')
const line = containerRef.value.querySelector('.timeline-line')
const timeline = containerRef.value.querySelector('.timeline-point')
const tl = gsap.timeline()
// 我们使用一个统一的开始 label使横线、timeline 裁剪与所有文字同时启动,
// 点图标在它们完成后立即开始。
tl.addLabel('start')
// 整体 timeline 的裁剪展开(与 start 同步)
if (timeline) {
tl.fromTo(
timeline,
{
clipPath: 'inset(0 100% 0 0)'
},
{
clipPath: 'inset(0 0% 0 0)',
duration: 1.3,
ease: 'power1.out'
},
'start'
)
}
// 线条动画(与 start 同步)
if (line) {
tl.from(
line,
{
scaleX: 0,
transformOrigin: '0% 50%',
duration: 1.3,
ease: 'power1.out'
},
'start'
)
}
// 标题与副标题(与 start 同步)
if (title && subtitle) {
tl.from(
[title, subtitle],
{
scaleX: 0,
autoAlpha: 0.5,
transformOrigin: '50% 50%',
duration: 0.6,
stagger: 0.1,
ease: 'power2.out'
},
'start'
)
}
// 行内文字(标签、时间、描述、图标)与 start 同步开始
const textItems = containerRef.value.querySelectorAll('.grid-cell')
if (textItems && textItems.length) {
tl.from(
textItems,
{
// autoAlpha: 0.5,
duration: 0.7,
stagger: 0.08,
ease: 'power2.out'
},
'start'
)
}
hasAnimated.value = true
}
let observer: IntersectionObserver | null = null
onMounted(async () => {
await nextTick()
if (!containerRef.value) return
observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
playAnimation()
}
})
},
{ threshold: 0.3 }
)
observer.observe(containerRef.value)
})
onBeforeUnmount(() => {
if (observer && containerRef.value) {
observer.unobserve(containerRef.value)
}
observer = null
})
</script>
<style scoped lang="less">
.timeline-container {
background: url('@/assets/images/award/timeline_bg.png') no-repeat;
background-size: 100% 100%;
position: relative;
padding: 12.8rem 0 15.9rem;
width: 100%;
color: #fff;
.timeline-title {
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
text-align: center;
vertical-align: middle;
}
.logo {
margin: 2.4rem 0 2.2rem 0;
}
.desc {
font-family: 'Arial';
font-size: 3rem;
font-weight: 400;
color: #f95750;
}
.timeline-point {
overflow: hidden;
will-change: clip-path;
flex: 1;
width: 100%;
margin-top: 11rem;
padding: 0 13.8rem;
position: relative;
z-index: 2;
// 主网格布局5列
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: auto auto auto auto;
grid-column-gap: 0;
grid-row-gap: 0;
// 所有 grid 子行的通用样式
.grid-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-column: 1 / -1;
}
.grid-cell {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
// 图标行
.icons-row {
align-items: center;
height: 6.4rem;
position: relative;
z-index: 2;
margin-bottom: 1.6rem;
.timeline-line {
position: absolute;
top: 50%;
left: -22rem;
right: -21.2rem;
height: 0.15rem;
background: linear-gradient(
90deg,
rgba(199, 52, 44, 0) 0%,
rgba(199, 52, 44, 0.719626) 25.96%,
#c7342c 51.44%,
rgba(199, 52, 44, 0.762376) 75.96%,
rgba(199, 52, 44, 0) 100%
);
transform: translateY(-50%);
z-index: 1;
pointer-events: none;
}
.icon-cell {
position: relative;
.point-icon {
width: 6.4rem;
height: 6.4rem;
display: block;
position: relative;
z-index: 2;
}
}
}
// 标签行
.labels-row {
margin-bottom: 8rem;
position: relative;
z-index: 2;
.label-cell {
flex-direction: column;
color: #fff;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.8rem;
white-space: pre-line;
justify-content: center;
min-height: 6rem;
// .sub-label {
// font-family: 'Arial';
// font-weight: 400;
// font-size: 1.4rem;
// color: rgba(255, 255, 255, 0.8);
// margin-top: 0.4rem;
// }
}
}
// 时间行
.times-row {
margin-bottom: 6rem;
z-index: 2;
position: relative;
.time-cell {
color: #f95750;
font-family: 'Arial';
font-weight: 400;
font-size: 2.8rem;
line-height: 4.5rem;
}
}
// 描述行
.descs-row {
.desc-cell {
.txt {
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
text-align: center;
color: #e0e0e0;
width: 100%;
max-width: 31.2rem;
min-height: 10.2rem;
white-space: pre-line;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
</style>

View File

@@ -1,82 +0,0 @@
<template>
<div class="upload-status">
<div class="upload-status-item">
<div class="upload-status-item-icon">
<img
v-if="status === 'uploading'"
src="@/assets/images/award/progress.png"
alt=""
class="progress-icon"
/>
<img
v-if="status === 'success'"
src="@/assets/images/award/successful.png"
alt=""
class="progress-icon successful-icon"
/>
</div>
<div class="text">{{ $t(text) }}</div>
<div class="tips">{{ $t(tips) }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps<{
status: string
type: 'pdf' | 'video'
}>()
const textMap: Record<string, string> = {
idle: '',
uploading: 'AwardsPage.uploadInProgress',
success:'AwardsPage.uploadSuccess',
error: 'AwardsPage.fileUploadFailed'
}
const tips = computed(() => {
if (props.type === 'pdf') {
return 'AwardsPage.pdfFileTip'
} else if (props.type === 'video') {
return 'AwardsPage.videoFileTip'
}
return ''
})
const text = computed(() => {
return textMap[props.status] ?? textMap.uploading
})
</script>
<style scoped lang="less">
.upload-status {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.upload-status-item {
display: flex;
flex-direction: column;
align-items: center;
.progress-icon {
width: 12rem;
height: 12rem;
}
.text {
font-family: Arial;
font-weight: 400;
color: #585858;
font-size: 2.4rem;
}
.tips{
font-family: Arial;
font-weight: 400;
font-size: 1.8rem;
color: #aaa;
}
}
}
</style>

View File

@@ -1,191 +0,0 @@
<template>
<div class="captcha">
<input
v-for="(c, index) in getCtData"
:key="index"
type="text"
v-model="getCtData[index]"
ref="inputRefs"
inputmode="numeric"
pattern="[0-9]*"
@input="e => onInput(e.target.value, index)"
@keydown="e => onKeydown(e, index)"
@keypress="e => onKeypress(e)"
@focus="onFocus"
@pause="onPause"
:disabled="loading"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
interface Props {
ct: string[]
}
interface Emits {
(e: 'sendCaptcha', password: string): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const loading = ref(false)
const timeout = ref<NodeJS.Timeout | null>(null)
const inputRefs = ref<HTMLInputElement[]>([])
const getCtData = computed({
get: () => props.ct,
set: (value: string[]) => {
// 这里需要特殊处理因为computed通常是只读的
// 但原代码中直接修改了getCtData所以这里需要emit一个事件或者使用其他方式
// 由于这是父组件传来的props我们需要通过emit通知父组件更新
props.ct.splice(0, props.ct.length, ...value)
}
})
const ctSize = computed(() => getCtData.value.length)
const cIndex = computed(() => {
let i = getCtData.value.findIndex(item => item === '')
i = (i + ctSize.value) % ctSize.value
return i
})
const lastCode = computed(() => getCtData.value[ctSize.value - 1])
watch(cIndex, () => {
resetCaret()
})
watch(lastCode, (newVal, oldVal) => {
if (newVal && newVal !== oldVal) {
inputRefs.value[ctSize.value - 1]?.blur()
sendCaptcha()
}
})
onMounted(() => {
resetCaret()
})
const onInput = (val: string, index: number) => {
if (timeout.value) {
clearTimeout(timeout.value)
}
timeout.value = setTimeout(() => {
val = String(val).replace(/\D/g, '')
getCtData.value[index] = val
if (index === ctSize.value - 1) {
getCtData.value[ctSize.value - 1] = val[0] // 最后一个码,只允许输入一个字符。
} else if (val.length > 1) {
let i = index
for (i = index; i < ctSize.value && i - index < val.length; i++) {
getCtData.value[i] = val[i - index]
}
resetCaret()
} else if (!(val + '')) {
getCtData.value[index] = ''
}
}, 10)
}
const onPause = () => {}
const resetCaret = () => {
inputRefs.value[ctSize.value - 1]?.focus()
}
const onFocus = () => {
// 监听 focus 事件,将光标重定位到"第一个空白符的位置"。
let index = getCtData.value.findIndex(item => item === '')
index = (index + ctSize.value) % ctSize.value
inputRefs.value[index]?.focus()
}
const onKeypress = (e: KeyboardEvent) => {
// 只允许输入数字0-9
const char = String.fromCharCode((e as any).which)
if (!/[0-9]/.test(char)) {
e.preventDefault()
}
}
const onKeydown = (e: KeyboardEvent, index: number) => {
// 处理删除键
if (e.key === 'Backspace' || e.key === 'Delete') {
const val = (e.target as HTMLInputElement).value
if (val === '') {
// 删除上一个input里的值并对其focus。
if (index > 0) {
getCtData.value[index - 1] = ''
inputRefs.value[index - 1]?.focus()
}
}
}
// 阻止其他非数字字符
else if (
e.key &&
!/[0-9]/.test(e.key) &&
![
'Backspace',
'Delete',
'Tab',
'Enter',
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'ArrowDown'
].includes(e.key)
) {
e.preventDefault()
}
}
const sendCaptcha = () => {
const password = getCtData.value.map(item => item).join('')
emit('sendCaptcha', password)
}
const reset = () => {
// 重置。一般是验证码错误时触发。
getCtData.value = getCtData.value.map(() => '')
resetCaret()
}
// 暴露reset方法给父组件使用
defineExpose({
reset
})
</script>
<style scoped lang="less">
.captcha {
width: 100%;
display: flex;
justify-content: space-between;
}
input {
width: 6rem;
height: 6rem;
border: 0.2rem solid #e6e6e6;
border-radius: 0.8rem;
text-align: center;
font-size: 2.4rem;
line-height: 6rem;
outline: none;
background-color: #f6f6f4;
}
input:last-of-type {
margin-right: 0;
}
input:disabled {
color: #000;
background-color: #f6f6f4;
}
.msg {
text-align: center;
}
</style>

View File

@@ -1,268 +0,0 @@
<template>
<div class="award-container">
<div class="header-wrapper">
<div class="header flex align-center space-between">
<div class="header-left">
<img
src="@/assets/images/award/code_create_logo.png"
class="logo"
/>
</div>
<div
class="header-right flex align-center"
@click="handleBtnClick"
>
<div class="text">{{ btnText }}</div>
<img
src="@/assets/images/award/arrow.png"
alt=""
class="arrow"
/>
</div>
</div>
<div class="header-placeholder"></div>
</div>
<router-view />
<div class="footer flex space-between align-center">
<div class="social-list flex">
<a
href="https://xhslink.com/m/5Ony2FapizV"
target="_blank"
>
<img
src="@/assets/images/award/xiaohongshu.svg"
alt=""
/>
</a>
<a
href="https://www.linkedin.com/company/code-create-limited"
target="_blank"
>
<img
src="@/assets/images/award/linkdin.svg"
alt=""
/>
</a>
<a
href="https://www.facebook.com/CodeCreateAI"
target="_blank"
>
<img
src="@/assets/images/award/facebook.svg"
alt=""
/>
</a>
<a
href="https://www.tiktok.com/@aida_codecreate"
target="_blank"
>
<img
src="@/assets/images/award/tiktok.svg"
alt=""
/>
</a>
<a
href="javascript:void(0)"
@click="showQRcode = true"
>
<img
src="@/assets/images/award/weichat.svg"
alt=""
/>
</a>
</div>
<div class="copyright">© Code-Create 2026</div>
</div>
<div
class="qrcode-mask flex flex-center"
v-if="showQRcode"
>
<div class="code-wrapper flex flex-col align-center">
<img
src="@/assets/images/award/close.svg"
class="close-icon"
@click="handleCloseQRcode"
/>
<div class="code-title">{{ $t('AwardsPage.wechatTitle') }}</div>
<img
src="@/assets/images/award/qrcode.jpg"
class="qrcode"
/>
<div class="tips">{{ $t('AwardsPage.wechatDesc') }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { getCookie } from '@/tool/cookie'
const route = useRoute()
const router = useRouter()
const { locale } = useI18n()
onMounted(() => {
// 初始化语言设置
const loginLanguage = localStorage.getItem('loginLanguage')
if (loginLanguage) {
locale.value = loginLanguage
} else {
const userLanguage = getCookie('language')
if (userLanguage) {
locale.value = userLanguage
}
}
})
const showQRcode = ref(false)
const handleCloseQRcode = () => {
showQRcode.value = false
}
type BtnType = 'index' | 'form'
const btnType = ref<BtnType>('index')
const btnText = computed(() => {
if (btnType.value === 'index') {
return locale.value === 'CHINESE_SIMPLIFIED' ? '提交申请' : 'Submit your Application'
}
if (btnType.value === 'form') {
return locale.value === 'CHINESE_SIMPLIFIED' ? '赛事介绍' : 'Back to Introduction'
}
})
watch(
() => route.path,
val => {
if (val.includes('contestants')) {
btnType.value = 'form'
} else {
btnType.value = 'index'
}
},
{
immediate: true
}
)
const handleBtnClick = () => {
if (btnType.value === 'index') {
router.push('/award/contestants')
} else {
router.push('/award/index')
}
}
</script>
<style lang="less" scoped>
.award-container {
overflow: auto;
height: 100vh;
// 隐藏滚动条箭头,只显示滚动条本体
box-sizing: border-box;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
scrollbar-width: none;
-ms-overflow-style: none;
}
.header-wrapper {
.header-placeholder {
height: 8rem;
}
.header {
height: 8rem;
background-color: #232323;
padding-left: 21.5rem;
padding-right: 8.6rem;
box-sizing: border-box;
position: fixed;
top: 0;
width: 100%;
z-index: 9;
.header-left {
.logo {
width: 13rem;
height: 5rem;
}
}
.header-right {
column-gap: 1rem;
cursor: pointer;
.text {
font-size: 1.6rem;
color: #fff;
}
.arrow {
width: 2.4rem;
height: 2.4rem;
}
}
}
}
.footer {
height: 10rem;
padding-left: 21.5rem;
box-sizing: border-box;
padding-right: 22rem;
background-color: #232323;
.social-list {
column-gap: 2rem;
img {
width: 2rem;
height: 2rem;
}
}
.copyright {
color: #fff;
font-family: 'Arial';
font-weight: 400;
font-size: 1.2rem;
}
}
.qrcode-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.45);
.code-wrapper {
width: 60rem;
height: 49.4rem;
background-color: #fff;
position: relative;
border-radius: 0.8rem;
padding-top: 6rem;
.close-icon {
width: 2.4rem;
height: 2.4rem;
position: absolute;
top: 2rem;
right: 2rem;
cursor: pointer;
}
.code-title {
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 3rem;
color: #232323;
}
.qrcode {
width: 25.8rem;
height: 25.8rem;
margin: 3rem 0 1rem;
}
.tips {
font-family: Arial;
font-weight: 400;
font-size: 1.4rem;
color: #585858;
}
}
}
</style>

View File

@@ -1,143 +0,0 @@
<template>
<div
class="award-page"
:class="{ 'is-zh': isZh }"
>
<div class="banner">
<video
:src="bannerUrl"
autoplay
muted
loop
class="banner-video"
playsinline
webkit-playsinline
x5-playsinline
></video>
<div
class="submit-btn flex flex-center"
@click="handleSubmitApplication"
>
<div>{{ $t('AwardsPage.submitApplication') }}</div>
<img
src="@/assets/images/award/arrow_right.png"
alt=""
class="arrow"
/>
<div class="ddl">{{ $t('AwardsPage.applicationDeadline') }}</div>
</div>
</div>
<Slogan />
<Bloom />
<TimeLine />
<JudgesSection />
<PrizesSection :is-zh="isZh" />
<ApplySection />
<SelectionSection />
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import JudgesSection from './components/JudgesSection.vue'
import SelectionSection from './components/SelectionSection.vue'
import ApplySection from './components/ApplySection.vue'
import PrizesSection from './components/PrizesSection.vue'
import TimeLine from './components/TimeLine.vue'
import Bloom from './components/Bloom.vue'
import Slogan from './components/Slogan.vue'
import banner from '@/assets/images/award/banner.mp4'
import bannerZh from '@/assets/images/award/banner_chinese.mp4'
const router = useRouter()
const { locale } = useI18n()
const isZh = computed(() => {
return locale.value === 'CHINESE_SIMPLIFIED'
})
const bannerUrl = computed(() => {
return isZh.value ? bannerZh : banner
})
const handleSubmitApplication = () => {
router.push('/award/contestants')
}
</script>
<style lang="less" scoped>
.container {
height: 97rem;
}
.logo {
width: 2.4rem;
height: 2.4rem;
}
.banner {
height: 100rem;
// background: url('@/assets/images/award/banner.png') no-repeat;
// background-size: cover;
position: relative;
.banner-video {
width: 100%;
height: 100%;
object-fit: cover;
}
.submit-btn {
width: 41rem;
height: 6.394rem;
line-height: 6.394rem;
text-align: center;
border-radius: 3.2rem;
background-color: rgba(35, 35, 35, 0.7);
box-shadow: inset 0 0 1119px 0 rgba(255, 255, 255, 0.3),
inset -0.8px -2.4px 1.6px 0.4px rgba(255, 255, 255, 0.1),
inset 0.8px 2.4px 1.6px 0 rgba(255, 255, 255, 0.3);
color: #fff;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.4rem;
column-gap: 3.2rem;
position: absolute;
left: 42.1rem;
bottom: 15.7rem;
backdrop-filter: blur(5px);
cursor: pointer;
.arrow {
width: 3.83rem;
height: 3.83rem;
}
.ddl {
position: absolute;
bottom: -4rem;
left: 0;
text-align: center;
width: 41rem;
font-family: 'ArialBold';
font-weight: 700;
font-size: 2rem;
line-height: 2.2rem;
color: #232323e5;
}
}
}
.is-zh {
.submit-btn {
padding: 0 7.5rem;
height: 7.8rem;
border-radius: 7.74rem;
column-gap: 3.8rem;
// justify-content: space-between;
&,
.ddl {
width: 35.4rem;
}
}
}
</style>

View File

@@ -2,12 +2,11 @@
<div class="brand-profile-index">brand-profile-index</div>
</template>
<script lang="ts" setup>
</script>
<script lang="ts" setup></script>
<style scoped lang="less">
.seller-dashboard-index {
width: 100%;
height: 100%;
overflow: hidden;
}
.seller-dashboard-index {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<div class="edit-detail-wrapper">
<seller-header
title="Edit Listing Details"
:breadcrumbs="[
{ title: 'My Listings', name: 'myListingsIndex' },
{ title: 'Select Collection', name: 'myListingsSelect' },
{ title: 'Select Sketch', name: 'myListingsSelectItem' },
{ title: 'Edit Listing Details', name: 'EditDetail' }
]"
>
<template #right>
<div class="operate-menu flex">
<div class="menu-btn flex align-center save">
<span>Save Draft</span>
<SvgIcon name="CSave" color="#000000" size="16" />
</div>
<div class="menu-btn flex align-center publish">
<span>Publish</span>
<SvgIcon name="CPublish" color="#ffffff" size="16" />
</div>
</div>
</template>
</seller-header>
<div class="edit-detail-content flex space-between">
<div class="left">
<div class="main-image-container flex">
<div class="sketch"></div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
import SellerHeader from "../../seller-header.vue"
const currentIndex = ref(0)
const selectList = ref([
{
sketch: "",
mainProductImage: "",
cover: "",
productImage: [],
apparelSketch: [],
productName: "",
price: "",
desc: '',
gender: '',
category:''
}
])
</script>
<style lang="less" scoped>
.c-svg {
width: initial;
}
.edit-detail-wrapper {
.menu-btn {
height: 6rem;
border: 0.15rem solid #000000;
border-radius: 4rem;
text-align: center;
line-height: 6rem;
padding: 0 2rem;
font-weight: 400;
font-size: 1.6rem;
column-gap: 0.8rem;
cursor: pointer;
}
.operate-menu {
column-gap: 2rem;
.publish {
background-color: #000000;
color: #ffffff;
}
}
.edit-detail-content {
padding-right: 6.4rem;
}
}
</style>