feat(首页): 添加合作伙伴和作品集展示组件
添加新的HomePartnersWork组件,包含合作伙伴列表和作品集展示区 更新中英文翻译文件,新增相关文案 移除首页占位内容,使用新组件替代
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import gsap from 'gsap'
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const rootRef = ref<HTMLElement | null>(null)
|
||||
const workSectionRef = ref<HTMLElement | null>(null)
|
||||
const workPinRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const cardRefs = ref<HTMLElement[]>([])
|
||||
|
||||
const partners = [
|
||||
{
|
||||
id: 'p1',
|
||||
svg: '<svg viewBox="0 0 64 64" width="34" height="34" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M20 44V20h12c7 0 12 4 12 10s-5 10-12 10h-6v4H20Z" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M34 40l10 12" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>',
|
||||
},
|
||||
{
|
||||
id: 'p2',
|
||||
svg: '<svg viewBox="0 0 64 64" width="34" height="34" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14 32c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18Z" stroke="currentColor" stroke-width="3"/><path d="M24 32c0-4.42 3.58-8 8-8s8 3.58 8 8-3.58 8-8 8-8-3.58-8-8Z" stroke="currentColor" stroke-width="3"/></svg>',
|
||||
},
|
||||
{
|
||||
id: 'p3',
|
||||
svg: '<svg viewBox="0 0 64 64" width="34" height="34" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M32 14v10M32 40v10M14 32h10M40 32h10" stroke="currentColor" stroke-width="3" stroke-linecap="round"/><path d="M32 22c5.52 0 10 4.48 10 10s-4.48 10-10 10-10-4.48-10-10 4.48-10 10-10Z" stroke="currentColor" stroke-width="3"/></svg>',
|
||||
},
|
||||
{
|
||||
id: 'p4',
|
||||
svg: '<svg viewBox="0 0 64 64" width="34" height="34" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14 44c10-2 12-14 18-18s10-1 18-6" stroke="currentColor" stroke-width="3" stroke-linecap="round"/><path d="M16 34c8-2 10 8 18 6s10-18 18-18" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>',
|
||||
},
|
||||
{
|
||||
id: 'p5',
|
||||
svg: '<svg viewBox="0 0 64 64" width="34" height="34" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 38c0-9 6-18 14-18s14 9 14 18" stroke="currentColor" stroke-width="3" stroke-linecap="round"/><path d="M22 42h20" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>',
|
||||
},
|
||||
{
|
||||
id: 'p6',
|
||||
svg: '<svg viewBox="0 0 64 64" width="34" height="34" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 20v24M30 20v24M42 20v24" stroke="currentColor" stroke-width="3" stroke-linecap="round"/><path d="M14 20h36M14 44h36" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>',
|
||||
},
|
||||
{
|
||||
id: 'p7',
|
||||
svg: '<svg viewBox="0 0 64 64" width="34" height="34" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 44V20h10c9 0 18 7 18 12s-9 12-18 12H18Z" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/></svg>',
|
||||
},
|
||||
{
|
||||
id: 'p8',
|
||||
svg: '<svg viewBox="0 0 64 64" width="34" height="34" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M16 34c8-14 18-14 32-4" stroke="currentColor" stroke-width="3" stroke-linecap="round"/><path d="M18 44c10-10 20-10 30-2" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>',
|
||||
},
|
||||
]
|
||||
|
||||
const toImageUrl = (prompt: string, imageSize: string) => {
|
||||
const encoded = encodeURIComponent(prompt)
|
||||
return `https://coresg-normal.trae.ai/api/ide/v1/text_to_image?prompt=${encoded}&image_size=${imageSize}`
|
||||
}
|
||||
|
||||
const workItems = [
|
||||
{
|
||||
titleKey: 'home.work.items.forma',
|
||||
year: '26',
|
||||
image: toImageUrl(
|
||||
'high-end editorial product still life, minimal composition, matte ceramics and brushed aluminum on soft neutral background, studio lighting, shallow depth of field, premium, clean, realistic, 35mm photo',
|
||||
'landscape_16_9'
|
||||
),
|
||||
},
|
||||
{
|
||||
titleKey: 'home.work.items.oneStep',
|
||||
year: '24',
|
||||
image: toImageUrl(
|
||||
'architectural close-up, modern brutalist concrete facade with sharp shadows, minimal, cinematic lighting, high contrast, ultra realistic, 35mm photography, no people',
|
||||
'landscape_16_9'
|
||||
),
|
||||
},
|
||||
{
|
||||
titleKey: 'home.work.items.neroVision',
|
||||
year: '25',
|
||||
image: toImageUrl(
|
||||
'minimal graphic poster photographed in studio, black ink typography on off-white textured paper, premium editorial style, soft shadow, ultra realistic, no people',
|
||||
'landscape_16_9'
|
||||
),
|
||||
},
|
||||
{
|
||||
titleKey: 'home.work.items.boldMoves',
|
||||
year: '24',
|
||||
image: toImageUrl(
|
||||
'moody nighttime city street still life, neon reflection on wet asphalt, abstract bokeh, premium cinematic photography, no people, high detail',
|
||||
'landscape_16_9'
|
||||
),
|
||||
},
|
||||
] as const
|
||||
|
||||
let ctx: gsap.Context | null = null
|
||||
|
||||
const refresh = () => {
|
||||
ScrollTrigger.refresh()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (!rootRef.value || !workSectionRef.value || !workPinRef.value) return
|
||||
|
||||
await nextTick()
|
||||
|
||||
ctx = gsap.context(() => {
|
||||
const cards = cardRefs.value
|
||||
const count = cards.length
|
||||
|
||||
if (count < 2) return
|
||||
|
||||
const enterOffset = 160
|
||||
const scaleTo = 0.92
|
||||
const depthOffsetY = -18
|
||||
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const card = cards[i]
|
||||
|
||||
gsap.set(card, {
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
borderRadius: 22,
|
||||
overflow: 'hidden',
|
||||
transformOrigin: 'center center',
|
||||
force3D: true,
|
||||
zIndex: i + 1,
|
||||
boxShadow: '0 18px 40px rgba(0,0,0,0.12)',
|
||||
y: i === 0 ? 0 : window.innerHeight + enterOffset,
|
||||
scale: 1,
|
||||
})
|
||||
}
|
||||
|
||||
const tl = gsap.timeline()
|
||||
|
||||
for (let i = 1; i < count; i += 1) {
|
||||
const prev = cards[i - 1]
|
||||
const next = cards[i]
|
||||
const start = i - 1
|
||||
|
||||
tl.to(
|
||||
next,
|
||||
{ y: 0, duration: 1, ease: 'power2.out' },
|
||||
start
|
||||
)
|
||||
tl.to(
|
||||
prev,
|
||||
{ scale: scaleTo, y: depthOffsetY, duration: 0.6, ease: 'power2.out' },
|
||||
start + 0.35
|
||||
)
|
||||
tl.set(prev, { scale: scaleTo, y: depthOffsetY }, start + 1)
|
||||
}
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: workSectionRef.value,
|
||||
start: 'top top',
|
||||
end: () => `+=${(count - 1) * window.innerHeight}`,
|
||||
pin: workPinRef.value,
|
||||
scrub: true,
|
||||
anticipatePin: 1,
|
||||
invalidateOnRefresh: true,
|
||||
animation: tl,
|
||||
})
|
||||
|
||||
ScrollTrigger.refresh()
|
||||
}, rootRef.value)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
ctx?.revert()
|
||||
ctx = null
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section ref="rootRef" class="bg-[#F5F5F5]">
|
||||
<div class="w-page-container max-w-[calc(100%-2rem)] mx-auto px-6 md:px-10 lg:px-14 pt-24 pb-20">
|
||||
<div class="grid grid-cols-[1fr_auto_1fr] items-center">
|
||||
<p class="justify-self-start text-xs font-semibold tracking-tight text-black">( {{ t('home.partners.label') }} )</p>
|
||||
<div class="flex items-center justify-self-center gap-8 text-xs font-semibold tracking-tight text-black">
|
||||
<span class="h-1 w-1 rounded-full bg-black" />
|
||||
<span>{{ t('home.partners.years') }}</span>
|
||||
</div>
|
||||
<span class="justify-self-end h-1 w-1 rounded-full bg-black" />
|
||||
</div>
|
||||
|
||||
<div class="mt-12 grid grid-cols-2 sm:grid-cols-4 gap-5">
|
||||
<div
|
||||
v-for="item in partners"
|
||||
:key="item.id"
|
||||
class="h-[152px] rounded-[20px] bg-white flex items-center justify-center text-black border-0 shadow-none outline-none ring-0"
|
||||
v-html="item.svg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="work" ref="workSectionRef" class="bg-white">
|
||||
<div class="w-page-container max-w-[calc(100%-2rem)] mx-auto px-4 pb-28">
|
||||
<div
|
||||
ref="workPinRef"
|
||||
class="flex flex-col lg:flex-row gap-10 lg:gap-16 h-[100svh] items-stretch"
|
||||
>
|
||||
<div class="lg:w-[340px] flex flex-col gap-8 pt-8 md:pt-10">
|
||||
<p class="text-xs font-semibold tracking-tight text-black/70">( {{ t('home.work.label') }} )</p>
|
||||
<div>
|
||||
<h2 class="text-5xl md:text-6xl font-bold tracking-tighter leading-[0.92]">
|
||||
{{ t('home.work.title') }}
|
||||
<span class="text-black/40 text-base md:text-lg align-middle ml-2 font-semibold tracking-tight">
|
||||
({{ workItems.length }})
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<NuxtLink
|
||||
to="/work"
|
||||
class="inline-flex w-fit items-center gap-2 rounded-full bg-black px-6 py-3 text-white text-sm font-semibold shadow-[0_15px_30px_-5px_rgba(0,0,0,0.25)]"
|
||||
>
|
||||
{{ t('home.work.viewAll') }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="relative flex-1 h-full flex items-center">
|
||||
<div class="relative w-full h-[78svh] md:h-[82svh] lg:h-[88svh] p-[clamp(14px,2.4vw,28px)]">
|
||||
<div class="relative h-full w-full">
|
||||
<article
|
||||
v-for="(item, idx) in workItems"
|
||||
:key="`${item.titleKey}-${idx}`"
|
||||
ref="cardRefs"
|
||||
class="will-change-transform relative"
|
||||
>
|
||||
<img
|
||||
class="h-full w-full object-cover"
|
||||
:src="item.image"
|
||||
:alt="t(item.titleKey)"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@load="refresh"
|
||||
/>
|
||||
<div class="absolute left-6 top-6 flex items-baseline gap-3">
|
||||
<p class="text-white text-sm md:text-base font-semibold tracking-tight drop-shadow-[0_10px_18px_rgba(0,0,0,0.4)]">
|
||||
{{ t(item.titleKey) }}
|
||||
</p>
|
||||
<p class="text-white/80 text-xs md:text-sm font-semibold tracking-tight drop-shadow-[0_10px_18px_rgba(0,0,0,0.4)]">
|
||||
{{ item.year }} ©
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
+2
-7
@@ -6,6 +6,7 @@ import { useAppStore } from '~/stores/app'
|
||||
import { usePageScrollLock } from '~/composables/usePageScrollLock'
|
||||
import HomeIntro from '~/components/home/HomeIntro.vue'
|
||||
import HomeHero from '~/components/home/HomeHero.vue'
|
||||
import HomePartnersWork from '~/components/home/HomePartnersWork.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
@@ -51,12 +52,6 @@ watch(
|
||||
<main id="top">
|
||||
<HomeIntro ref="homeIntroRef" />
|
||||
<HomeHero ref="homeHeroRef" />
|
||||
|
||||
<section class="grid min-h-[100svh] place-items-center">
|
||||
<h2>Next Section</h2>
|
||||
</section>
|
||||
<section id="work" class="grid min-h-[100svh] place-items-center" data-cursor-label="Read more">
|
||||
<p>{{ t('home.welcome') }}</p>
|
||||
</section>
|
||||
<HomePartnersWork />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
+16
-1
@@ -24,7 +24,22 @@
|
||||
},
|
||||
"home": {
|
||||
"title": "Home",
|
||||
"welcome": "Welcome to Arctic New One"
|
||||
"welcome": "Welcome to Arctic New One",
|
||||
"partners": {
|
||||
"label": "Partners",
|
||||
"years": "2011-26©"
|
||||
},
|
||||
"work": {
|
||||
"label": "Portfolio 26©",
|
||||
"title": "Work",
|
||||
"viewAll": "View all work",
|
||||
"items": {
|
||||
"forma": "Forma Digital",
|
||||
"oneStep": "One Step",
|
||||
"neroVision": "Nero Vision",
|
||||
"boldMoves": "Bold Moves"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hero": {
|
||||
"services": {
|
||||
|
||||
+16
-1
@@ -24,7 +24,22 @@
|
||||
},
|
||||
"home": {
|
||||
"title": "首页",
|
||||
"welcome": "欢迎来到 Arctic New One"
|
||||
"welcome": "欢迎来到 Arctic New One",
|
||||
"partners": {
|
||||
"label": "合作伙伴",
|
||||
"years": "2011-26©"
|
||||
},
|
||||
"work": {
|
||||
"label": "作品集 26©",
|
||||
"title": "Work",
|
||||
"viewAll": "查看全部作品",
|
||||
"items": {
|
||||
"forma": "Forma Digital",
|
||||
"oneStep": "One Step",
|
||||
"neroVision": "Nero Vision",
|
||||
"boldMoves": "Bold Moves"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hero": {
|
||||
"services": {
|
||||
|
||||
Reference in New Issue
Block a user