490 lines
11 KiB
Vue
490 lines
11 KiB
Vue
<script setup>
|
|
import { inject, onMounted, onUnmounted, ref } from 'vue'
|
|
import { withBase } from 'vitepress'
|
|
import macbookImage from '../../../../assets/macbook.png'
|
|
|
|
// Try to inject translation context from parent or provide a default fallback
|
|
const t = inject('t', {
|
|
value: {
|
|
stories: {
|
|
cat: 'Vibe Stories',
|
|
title: '看见每一个<br><span class="highlight">闪光的你。</span>',
|
|
sub: '在这里,发现大家如何使用 AI 创造属于自己的作品。'
|
|
}
|
|
}
|
|
})
|
|
|
|
const tStories = [
|
|
{
|
|
id: 1,
|
|
title: t.value?.stories?.s1?.title || '我的第一个全栈项目',
|
|
author: t.value?.stories?.s1?.author || 'Sanbu',
|
|
avatar: '👨💻',
|
|
image: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop',
|
|
link: '/zh-cn/vibe-stories/story-1'
|
|
},
|
|
{
|
|
id: 2,
|
|
title: t.value?.stories?.s2?.title || '从产品经理到独立开发者',
|
|
author: t.value?.stories?.s2?.author || 'Alice',
|
|
avatar: '👩🎨',
|
|
image: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?q=80&w=2564&auto=format&fit=crop',
|
|
link: '/zh-cn/vibe-stories/story-2'
|
|
},
|
|
{
|
|
id: 3,
|
|
title: t.value?.stories?.s3?.title || '用 AI 提效 10 倍',
|
|
author: t.value?.stories?.s3?.author || 'Bob',
|
|
avatar: '🚀',
|
|
image: 'https://images.unsplash.com/photo-1558655146-d09347e92766?q=80&w=2564&auto=format&fit=crop',
|
|
link: '/zh-cn/vibe-stories/story-3'
|
|
}
|
|
]
|
|
|
|
const currentIndex = ref(0)
|
|
let autoplayTimer = null
|
|
const isPaginating = ref(false)
|
|
const containerRef = ref(null)
|
|
let wheelHandler = null
|
|
|
|
const transitionName = ref('slide-left')
|
|
|
|
const next = () => {
|
|
if (isPaginating.value) return
|
|
isPaginating.value = true
|
|
transitionName.value = 'slide-left'
|
|
currentIndex.value = (currentIndex.value + 1) % tStories.length
|
|
setTimeout(() => {
|
|
isPaginating.value = false
|
|
}, 800)
|
|
}
|
|
|
|
const prev = () => {
|
|
if (isPaginating.value) return
|
|
isPaginating.value = true
|
|
transitionName.value = 'slide-right'
|
|
currentIndex.value = (currentIndex.value - 1 + tStories.length) % tStories.length
|
|
setTimeout(() => {
|
|
isPaginating.value = false
|
|
}, 800)
|
|
}
|
|
|
|
const setIndex = (index) => {
|
|
if (index === currentIndex.value) return
|
|
transitionName.value = index > currentIndex.value ? 'slide-left' : 'slide-right'
|
|
currentIndex.value = index
|
|
}
|
|
|
|
const startAutoplay = () => {
|
|
autoplayTimer = setInterval(() => {
|
|
if (!isPaginating.value) {
|
|
transitionName.value = 'slide-left'
|
|
currentIndex.value = (currentIndex.value + 1) % tStories.length
|
|
}
|
|
}, 4000)
|
|
}
|
|
|
|
const stopAutoplay = () => {
|
|
if (autoplayTimer) {
|
|
clearInterval(autoplayTimer)
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
startAutoplay()
|
|
const container = containerRef.value
|
|
if (!container) return
|
|
|
|
wheelHandler = (e) => {
|
|
if (Math.abs(e.deltaX) > 20 && Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
|
|
e.preventDefault()
|
|
if (e.deltaX > 0) {
|
|
next()
|
|
} else {
|
|
prev()
|
|
}
|
|
}
|
|
}
|
|
|
|
container.addEventListener('wheel', wheelHandler, { passive: false })
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
stopAutoplay()
|
|
const container = containerRef.value
|
|
if (container && wheelHandler) {
|
|
container.removeEventListener('wheel', wheelHandler)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="containerRef" class="vibe-stories-container">
|
|
<div class="section-header">
|
|
<h3 class="section-headline" v-html="t.stories?.title || '看见每一个<br><span class=\'highlight\'>闪光的你。</span>'"></h3>
|
|
<p class="section-sub">{{ t.stories?.sub || '在这里,发现大家如何使用 AI 创造属于自己的作品。' }}</p>
|
|
</div>
|
|
|
|
<div class="laptop-wrapper" @mouseenter="stopAutoplay" @mouseleave="startAutoplay">
|
|
<div class="laptop-container">
|
|
<!-- Navigation Controls -->
|
|
<button class="nav-btn prev" aria-label="Previous story" @click="prev">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6" /></svg>
|
|
</button>
|
|
<button class="nav-btn next" aria-label="Next story" @click="next">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
|
</button>
|
|
|
|
<!-- Screen Content -->
|
|
<div class="screen-content">
|
|
<a :href="withBase(tStories[currentIndex].link)" class="screen-link">
|
|
<transition :name="transitionName">
|
|
<div :key="currentIndex" class="screen-image-wrapper">
|
|
<img
|
|
:src="tStories[currentIndex].image"
|
|
class="screen-image"
|
|
alt="Story screenshot"
|
|
/>
|
|
</div>
|
|
</transition>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Laptop Frame -->
|
|
<img :src="macbookImage" class="laptop-frame" alt="MacBook Frame" />
|
|
</div>
|
|
|
|
<!-- Story Info & Avatar -->
|
|
<div class="story-info">
|
|
<div class="story-avatar">{{ tStories[currentIndex].avatar }}</div>
|
|
<div class="story-text">
|
|
<a :href="withBase(tStories[currentIndex].link)" class="story-title">
|
|
{{ tStories[currentIndex].title }}
|
|
</a>
|
|
<div class="story-author">by {{ tStories[currentIndex].author }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Indicators -->
|
|
<div class="indicators">
|
|
<button
|
|
v-for="(_, index) in tStories"
|
|
:key="index"
|
|
class="indicator-dot"
|
|
:class="{ active: index === currentIndex }"
|
|
aria-label="Select story"
|
|
@click="setIndex(index)"
|
|
></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.vibe-stories-container {
|
|
max-width: 1120px;
|
|
margin: 0 auto;
|
|
padding: 0 20px 28px;
|
|
text-align: center;
|
|
}
|
|
|
|
.section-header {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.section-headline {
|
|
font-size: 60px;
|
|
line-height: 1.08;
|
|
font-weight: 700;
|
|
letter-spacing: -0.034em;
|
|
margin-bottom: 10px;
|
|
color: #1d1d1f;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC', sans-serif;
|
|
}
|
|
|
|
.dark .section-headline {
|
|
color: #f5f5f7;
|
|
}
|
|
|
|
.highlight {
|
|
background: linear-gradient(120deg, #0066cc, #3399ff);
|
|
background-clip: text;
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.dark .highlight {
|
|
background: linear-gradient(120deg, #2997ff, #66b3ff);
|
|
background-clip: text;
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.section-sub {
|
|
font-size: 19px;
|
|
line-height: 1.4;
|
|
font-weight: 400;
|
|
letter-spacing: -0.01em;
|
|
color: #6e6e73;
|
|
max-width: 760px;
|
|
margin: 0 auto;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC', sans-serif;
|
|
}
|
|
|
|
.dark .section-sub {
|
|
color: #a1a1a6;
|
|
}
|
|
|
|
.laptop-wrapper {
|
|
position: relative;
|
|
width: 100%;
|
|
margin-top: 0;
|
|
}
|
|
|
|
.laptop-container {
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 700px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.laptop-frame {
|
|
position: relative;
|
|
z-index: 10;
|
|
width: 100%;
|
|
height: auto;
|
|
pointer-events: none;
|
|
filter: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
|
|
}
|
|
|
|
.dark .laptop-frame {
|
|
filter: drop-shadow(0 25px 25px rgb(255 255 255 / 0.05));
|
|
}
|
|
|
|
.screen-content {
|
|
position: absolute;
|
|
z-index: 1;
|
|
background: transparent;
|
|
top: 0;
|
|
left: 10%;
|
|
width: 80%;
|
|
height: 92%;
|
|
border-top-left-radius: 12px;
|
|
border-top-right-radius: 12px;
|
|
overflow: hidden;
|
|
perspective: 1000px;
|
|
/* Force hardware acceleration and fix Safari overflow clipping bug */
|
|
transform: translateZ(0);
|
|
-webkit-transform: translateZ(0);
|
|
-webkit-mask-image: -webkit-radial-gradient(white, black);
|
|
mask-image: radial-gradient(white, black);
|
|
isolation: isolate;
|
|
}
|
|
|
|
.screen-link {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: transparent;
|
|
position: relative;
|
|
overflow: hidden;
|
|
border-radius: inherit;
|
|
/* Fix boundary bleeding in Safari */
|
|
transform: translateZ(0);
|
|
-webkit-transform: translateZ(0);
|
|
mask-image: radial-gradient(white, black);
|
|
-webkit-mask-image: -webkit-radial-gradient(white, black);
|
|
}
|
|
|
|
.screen-image-wrapper {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.screen-image {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
object-position: center;
|
|
}
|
|
|
|
/* Transitions */
|
|
.slide-left-enter-active,
|
|
.slide-left-leave-active,
|
|
.slide-right-enter-active,
|
|
.slide-right-leave-active {
|
|
transition: transform 0.6s cubic-bezier(0.25, 1, 0.5, 1);
|
|
will-change: transform;
|
|
}
|
|
|
|
.slide-left-enter-from {
|
|
transform: translateX(100%);
|
|
}
|
|
.slide-left-leave-to {
|
|
transform: translateX(-100%);
|
|
}
|
|
|
|
.slide-right-enter-from {
|
|
transform: translateX(-100%);
|
|
}
|
|
.slide-right-leave-to {
|
|
transform: translateX(100%);
|
|
}
|
|
|
|
/* Nav Buttons */
|
|
.nav-btn {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
z-index: 20;
|
|
background: rgba(255, 255, 255, 0.6);
|
|
backdrop-filter: blur(8px);
|
|
border: none;
|
|
border-radius: 50%;
|
|
width: 48px;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
color: #333;
|
|
opacity: 0;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.laptop-wrapper:hover .nav-btn {
|
|
opacity: 1;
|
|
}
|
|
|
|
.nav-btn:hover {
|
|
background: rgba(255, 255, 255, 0.9);
|
|
transform: translateY(-50%) scale(1.1);
|
|
}
|
|
|
|
.nav-btn.prev {
|
|
left: 20px;
|
|
}
|
|
|
|
.nav-btn.next {
|
|
right: 20px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.nav-btn {
|
|
opacity: 1;
|
|
width: 36px;
|
|
height: 36px;
|
|
}
|
|
.nav-btn.prev { left: 10px; }
|
|
.nav-btn.next { right: 10px; }
|
|
|
|
.section-headline { font-size: 42px; }
|
|
.section-sub { font-size: 17px; }
|
|
.laptop-container { max-width: 100%; }
|
|
.story-info {
|
|
margin-top: 18px;
|
|
gap: 12px;
|
|
}
|
|
}
|
|
|
|
/* Story Info */
|
|
.story-info {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 16px;
|
|
margin-top: 22px;
|
|
}
|
|
|
|
.story-avatar {
|
|
font-size: 48px;
|
|
line-height: 1;
|
|
background: #f5f5f7;
|
|
border-radius: 50%;
|
|
width: 72px;
|
|
height: 72px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.dark .story-avatar {
|
|
background: #2c2c2e;
|
|
}
|
|
|
|
.story-text {
|
|
text-align: left;
|
|
}
|
|
|
|
.story-title {
|
|
display: block;
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
color: #1d1d1f;
|
|
text-decoration: none;
|
|
margin-bottom: 4px;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.dark .story-title {
|
|
color: #f5f5f7;
|
|
}
|
|
|
|
.story-title:hover {
|
|
color: #0066cc;
|
|
}
|
|
|
|
.dark .story-title:hover {
|
|
color: #2997ff;
|
|
}
|
|
|
|
.story-author {
|
|
font-size: 15px;
|
|
color: #86868b;
|
|
}
|
|
|
|
/* Indicators */
|
|
.indicators {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
margin-top: 24px;
|
|
}
|
|
|
|
.indicator-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: #d2d2d7;
|
|
border: none;
|
|
padding: 0;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.dark .indicator-dot {
|
|
background: #424245;
|
|
}
|
|
|
|
.indicator-dot:hover {
|
|
background: #86868b;
|
|
}
|
|
|
|
.indicator-dot.active {
|
|
width: 24px;
|
|
border-radius: 4px;
|
|
background: #1d1d1f;
|
|
}
|
|
|
|
.dark .indicator-dot.active {
|
|
background: #f5f5f7;
|
|
}
|
|
</style>
|